关于JavaScript中的创建对象的学习总结

时间:2021-09-21 06:23:21

一、最简单的对象创建方法

  在JavaScript中,直接使用Object构造函数或对象字面量都可以很轻易地创建单个对象,缺点是:创建具有同一个接口(标准的OO中的接口概念)的多个对象时,会有大量重复代码。为解决这个问题,引出了工厂模式。

二、工厂模式

  工厂模式是GoF的23中设计模式中比较简单的模式,简单总结就是定义一个用于创建对象的接口,让子类决定具体实例化那一个类。具体到JavaScript中,因为根本就没有标准OO里面类的概念,开发人员发明了一种函数,用函数来封装以特定接口来创建对象的细节。

function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        alert(this.name);
    };
    return o;
}

var person1 = createPerson("Haha", 34, "Doctor");
var person2 = createPerson("Hehe", 23, "worker");

  使用工厂模式创建对象解决了代码重复的问题,却没有解决对象识别的问题:所有由createPerson函数创建的对象都是Object构造函数的实例。为了解决这个问题,引入了构造函数模式。

三、构造函数模式

  通过创建自定义的构造函数,从而定义自定义对象类型的属性和方法。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        alert(this.name);
    };
}

var person1 = new Person("Haha", 34, "Doctor");
var person2 = new Person("Hehe", 23, "Worker");

  此处,我们必须使用new操作符来调用构造函数Person()。这样就会有以下四个步骤:

    1)创建一个新对象;

    2)将新对象赋给构造函数的作用域(因此this就指向了这个新对象);

    3)执行构造函数中的代码(为这个新对象添加属性);

    4)返回新对象(没有明确return其他对象,默认返回新对象);

  现在,person1和person2不仅仅都是Object构造函数的实例,而且都是Person构造函数的实例。构造函数模式的缺点是:每个构造函数中定义的方法在其生成的每个实例上都重新创建一遍,如果生成的实例比较多或者每个方法都比较大,这种模式会非常消耗资源。有一种变通方法如下:

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}

function sayName() {
    alert(this.name);
}

var person1 = new Person("Haha", 34, "Doctor");
var person2 = new Person("Hehe", 23, "Worker");

通过把方法转移到构造函数外部,对象person1和person2实现了共享函数sayName()。带来的问题是sayName()函数处在全局作用域中,从而失去封装性问题和大量类似函数的数量膨胀问题。为此,我们想到了原型模式。

四、原型模式

  我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型(指向这个对象的函数)的所有实例共享的属性和方法。注意:这个对象是在创建函数的同时,就已经自动被创建了,而且通过函数的prototype属性和对象的constructor属性进行了相互指向。在对函数调用了new操作符之后,所有生成的实例把函数的prototype属性指向的对象作为自己的原型对象。

function Person() {
}

Person.prototype.name = "Haha";
Person.prototype.age = 34;
Person.prototype.job = "Doctor";
Person.prototype.sayName = function() {
     alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

person1.sayName === person2.sayName;        

各个对象之间的关系:

关于JavaScript中的创建对象的学习总结

注意一:实例对象和构造函数没有直接的关联,它们只是在生成时,把自己的内部属性[[Prototype]]指向了Person Prototype对象,而和Person构造函数之间并没有之间联系起来。造成的影响是:当实例生成后去改变Person构造函数的prototype属性,使之重新指向一个对象的话,实例的[[Prototype]]属性不会跟随变化。

注意二:原型对象带来的对象属性的读和写的不对等性。

注意三:原型的动态性,本质是实例与原型之间的松散连接关系。

原型模式的问题是:1、没有了构造函数传递初始化参数这一环节;2、引用类型值的共享带来的问题。

五、组合使用构造函数模式与原型模式

  因为构造函数模式和原型模式的优缺点互补性,创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["shelBy", "court"];
}

Person.prototype = {
     constructor : Person,
     sayName : function () {
            alert(this.name);
     }
}

var person1 = new Person("Haha", 34, "Doctor");
var person2 = new Person("Hehe", 23, "Worker");            

到了这一步,JavaScript中创建对象的方法应该学习的差不多了,但是,总会有一些特殊需求。

六、动态原型模式

  如果你对上面第五种方法中的组合模式感到困惑或者不爽,那么你可以试试这个动态原型模式。它把所有的信息都封装在构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),保存了组合模式的优点。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;

    if (typeof this.sayName !== "function") {
        Person.prototype.sayName = function() {
                alert(this.name);
        };
    }

}

七、寄生构造函数模式

  该模式的基本思想是:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后在返回新创建的对象。

function Person(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        alert(this.name);
    };
    return o;
}

var friend = new Person("Haha", 34, "Doctor");
friend.sayName();

此处与工厂模式的区别仅仅是:使用new去调用构造函数。注意在构造函数中没有去使用默认生成的新对象this,并且通过显式的return语句改写了构造函数的默认返回对象。

这个模式可以在特殊的情况下用来为对象创建构造函数。

八、稳妥构造函数模式

  JavaScript中的稳妥对象(durable objects),指的是没有公共属性,而且其方法也不引用this的对象。稳妥对象适合在一些安全的环境中(禁止this和new),或者防止数据被其他应用程序改动时使用。

function Person(name, age, job) {
    var o = new Object();

    // 可在此处定义室友变量和函数

    // 添加方法
    o.sayName = function() {
        alert(name);
    };

    return o;
}

注意:此种模式创建的对象中,除了使用sayName()方法之外,没有其他办法访问name的值。