1、工厂(Factory)模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。 考虑到ECMAScript中无法创建类,开发人员发明了这么一种函数,用函数来封装以特定接口创建对象的细节。 以下是例子,函数createPerson()能够根据接受的参数来构建一个包含所有必要信息的对象,可以无数次 地调用这个函数,而每次它都会返回一个包含三个属性和一个方法的对象。 工厂模式虽然解决了创建多个类似对象的问题,但却没有解决对象识别的问题(我们无法知道一个对象的类型)
function createPerson(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function () { console.log(this.name); } return o; } var person1 = createPerson('karl', 29, 'Software Engineer'); person1.sayName(); //karl var person2 = createPerson('Jack', 35, 'Teacher'); person2.sayName(); // Jack
2、构造函数(constructor)模式:ECMAScript中的构造函数可用来创建特定类型的对象。在下面的案例中,先创建了一个新的对象, 然后将构造函数的作用域赋给新对象(this指向了新对象),再执行构造函数中的代码,最后返回新对象。person1和person2分别保存 着Person的一个不同的实例。这两个对象都有一个constructor属性,该属性指向Person。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { console.log(this.name); } } var person1 = new Person('karl', 29, 'Software Engineer'); person1.sayName(); //karl var person2 = new Person('Jack', 35, 'Teacher'); person2.sayName(); //Jack console.log(person1.constructor == Person); //true console.log(person2.constructor == Person); //true console.log(person1.constructor == Object); //false console.log(person2.constructor == Object); //false console.log(person1 instanceof Object); //true console.log(person1 instanceof Person); //true console.log(person2 instanceof Object); //true console.log(person2 instanceof Person); //true
构造函数的缺点是:每个方法都要在每个实例 上重新创建一遍。ECMAScript中的函数就是对象,因此每定义一个函数,就是实例化了一个对象。从逻辑角度讲,此时的构造函数也可以这样定义。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = new Function('console.log(this.name)'); }
从这个角度来看构造函数,更容易明白每个Person实例都包含了一个不同的Function实例,以下的代码可以证明这一点。
console.log(person1.sayName == person2.sayName); //false
3、原型(prototype)模式:我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象, 而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法,通俗地说,prototype就是通过调用构造函数 而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说, 不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中,如下面的例子所示。
function Person() { } Person.prototype.name = 'karl'; Person.prototype.age = 29; Person.prototype.job = 'software engineer'; Person.prototype.sayName = function () { console.log(this.name); }; var person1=new Person(); person1.sayName(); //karl var person2=new Person(); person2.sayName(); //karl
在此,我们将sayName()方法和所有的属性直接添加到了Person的prototype属性中,构造函数变成了空函数。 即便如此,也仍然可以通过调用构造函数来创建新对象,而且新对象还会具有相同的属性和方法。但与构造函数不同的是, 这些属性和方法是由所有实例共享的。所以,person1和person2访问的都是同一组属性和同一个sayName()方法。
console.log(person1.sayName == person2.sayName); //true
原型模式的缺点,第一:原型模式省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。 第二:原型中所有属性是被很多实例共享的,这种共享对于函数非常合适。对于那些包含基本值的属性倒也说得过去,毕竟通过 在实例上添加一个同名属性,可以隐藏原型中的对应属性。然而,对于包含引用类型值的属性来说,问题就很突出了。
function Person() { } Person.prototype = { constructor: Person, name: 'karl', friends: ['Jack', 'Bill'], sayName: function () { console.log(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push('John'); console.log(person1.friends); //['Jack', 'Bill', 'John'] console.log(person2.friends); //['Jack', 'Bill', 'John'] console.log(person1.friends == person2.friends) //true;
如果实例中添加了同名属性,可以隐藏原型中的对应属性。
function Person() { } Person.prototype = { constructor: Person, name: 'karl', friends: ['Jack', 'Bill'], sayName: function () { console.log(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.name = 'Bob'; person1.friends = []; person1.sayName = function () { }; console.log(person1.friends); //[] console.log(person2.friends); //['Jack', 'Bill'] console.log(person1.name == person2.name) //false; console.log(person1.friends == person2.friends) //false; console.log(person1.friends == person2.friends) //false;
4、组合使用构造函数和原型模式,是创建自定义类型的最常见方式。构造函数用于定义实例属性,而原型模式用于 定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地 节省了内存。另外,这种混成模式还支持向构造函数传递参数;可谓集两种模式之长。这种构造函数和原型运城的模式, 是目前在ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ['Jack', 'Bill']; } Person.prototype = { constructor: Person, sayName: function () { console.log(this.name); } }; var person1 = new Person('karl', 29, 'software engineer'); var person2 = new Person('John', 30, 'doctor'); person1.friends.push('van'); console.log(person1.friends); //['Jack', 'Bill', 'van'] console.log(person2.friends); //['Jack', 'Bill'] console.log(person1.friends == person2.friends); //false console.log(person1.sayName == person2.sayName); //true