创建对象的模式:
1.工厂模式
1 function createPerson(name,age){ 2 var obj = new Object(); 3 obj.name = name; 4 obj.age = age; 5 obj.sayName = function(){ 6 alert(this.name) 7 } 8 return obj; 9 } 10 11 var person1 = createPerson("张三",20) 12 var person2 = createPerson("李四",21)
软件工程领域广为人知的设计模式,抽象了创建具体对象的过程,在ECMAScript中无法创建类,用函数来封装以特定接口创建对象的细节。
工厂模式的问题
工厂模式可以接受参数无数次构建一个包含所有必要信息的自定义对象,虽然解决了创建多个相似对象的问题,但不能解决对象识别的问题,因为父亲原型都是Object,
那么新建一个createDog的工厂,构建的对象 和 createPerson 工厂构造的对象 类型其实是一致的。
2.构造函数模式
1 function Person(name,age){ 2 this.name = name; 3 this.age = age; 4 this.sayname = function(){ 5 alert(this.name) 6 } 7 } 8 9 var person1 = new Person("张三",20) 10 var person2 = new Person("李四",21)
构造函数模式相比较工厂模式
没有显示的创建对象,直接将属性和方法赋值给了this对象,没有return语句.
构造函数也是函数,只不过可以创建对象而已,创建对象必须使用new操作符,经历下面4个步骤:
a.创建一个新对象
b.将构造函数的作用域赋给新对象(this就指向这个新对象了)
c.执行构造函数中的代码(为新对象添加属性)
d.返回新对象
使用构造函数模式支持使用person1.constructor == Person //true person1.constructor == Object //true
之前的工厂模式只会 person1.constructor == Person //false person1.constructor == Object //true
创建自定义的构造函数可以将它的实例标识为一种特定的类型,这是构造函数模式胜过工厂模式的地方。
构造函数的使用方式:
a.new操作符作为构造函数使用
b.不使用new操作符
Person('李四',20);//将作用域赋给window window.name = '李四'
var o = new Object(); Person.call(o,'张三',20); //将作用域赋给o,这里作用域传递具体请看 “浅谈this”,o.name = '张三'
构造函数的问题
主要问题:在构造函数中的方法属性也是一个函数,也就需要在每个构造函数实例中都要重新创建一遍。即,使用构造函数构造两个对象,比如这2个对象的方法属性都有个sayName的方法,这两个方法都是重新创建出来的,也是2个不同的实例。因此每次构造一个对象,都会重新初始化 属于这个对象的方法属性的实例,我们可以来想象下,如果一个对象有10个方法属性,那么就是很可怕的。本来都是和业务无关的完成同样任务的方法,结果却要和对象进行绑定。
alert(person1.sayName == person2.sayName) //false
既然this对象在,我们没必要把重复使用的方法函数绑定到this上,可以通过把函数定义转移到构造函数外部解决问题。
1 function Person(name,age){ 2 this.name = name; 3 this.age = age; 4 this.sayName = sayName; 5 } 6 7 function sayName(){ 8 alert(this.name) 9 }
这样一来,person1和person2对象就共享了在全局作用域中定义的同一个sayName方法。
每当解决一个问题的时候就会出现另一个问题。
如果对象需要定义很多的方法,那么就需要定义很多个全局函数,那么这个自定义的引用类型就丝毫没有封装可言了。
3.原型模式
1 function Person(){} 2 3 Person.prototype.name = "张三" 4 Person.prototype.age = 20 5 6 Person.prototype.sayName = function(){ 7 alert(this.name) 8 } 9 10 var person1 = new Person() 11 person1.sayName();//张三 12 var person2 = new Person() 13 person2.sayName();//张三 14 alert(person1.sayName == person2.sayName) // true
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以让特定类型的所有实例共享的属性和方法。
和构造函数不同,我们将sayName方法直接添加到了Person的prototype属性中,仍然可以通过调用构造函数来创建新对象,并且新对象还会具有这个prototype属性中包含的所有属性。
alert(person1.sayName == person2.sayName) //true
原型模式的问题
a.省略了为构造函数传递初始化参数的过程,结果所有实例默认情况下取得相同的属性值。
b.原型中所有属性是被很多实例共享的,适合函数,对于那些包含基本值的属性也勉强说得过去,但是对于引用类型值的属性就出现问题了。
原型模式下的实例无法拥有属于实例的私有引用类型属性,比如数组等。因为共享,所以别的实例也可以进行修改这个引用类型属性的值。
这也是我们很少看到有人单独使用原型模式的原因。
4.组合使用构造函数模式和原型模式
1 function Person(name,age){ 2 this.name = name; 3 this.age = age; 4 this.friends = ["王五"] 5 } 6 Person.prototype = { 7 constructor:Person, 8 sayName :function(){ 9 alert(this.name) 10 } 11 } 12 13 var person1 = new Person('张三',20) 14 var person2 = new Person('李四',21) 15 16 person1.friends.push('王六') 17 alert(person1.friends) // 王五,王六 18 alert(person2.friends) // 王五 19 20 person1.friends == person2.friends //false 21 person1.sayName == person2.sayName //true
创建自定义类型的最常用方式,就是组合使用构造函数模式与原型模式。
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,结果每个实例都会有自己的一份实例属性的副本但同时又享用着对方法的引用,
最大限度的节省了内存,还支持想构造函数传递参数。
5.动态原型模式
1 function Person(name,age){ 2 this.name = name; 3 this.age = age; 4 if(typeof this.sayname != 'function'){ 5 Person.prototype.sayName = function(){ 6 alert(this.name) 7 } 8 } 9 }
动态原型模式修改了同时使用构造函数模式和原型模式的独立风格,把所有的信息都封装到构造函数中,在构造函数中初始化原型,又保持了同时使用构造函数模式和原型模式的优点。
6.寄生构造模式
1 function SpecialArray(){ 2 var values = new Array(); 3 values.push.apply(values, arguments); 4 values.toPipedString = function(){ 5 return this.join("|"); 6 } 7 return values; 8 } 9 var a = new SpecialArray(2,6,8,9,4); 10 a.toPipedString(); 11 var b = SpecialArray(2,6,8,9,4); 12 b.toPipedString();
“寄生器构造函数”可以在构造函数不适应的情况使用,比如要创建一个数组类型,像上面代码一样(因为构造函数只能创建对象类型)。为了让人一看就知道是在构造一个新的对象类型的实例,所以虽然它写的和工厂模式一样,但是创建时用了new,因此使得实现的过程不一样,(但是实现过程不重要)。
具体作用,比如创建具有额外方法的已有类型(如数组,Date类型等),但是又不污染原有的类型。
所以就算没有new也一样,只不过加上new让人清楚这是新对象类型的实例,也是“寄生器构造函数”里有个“构造函数”的原因。
7.稳妥构造函数模式
function Person(name, age, job) { var o = new Object(); // private members var nameUC = name.toUpperCase(); // public members o.sayName = function() { alert(name); }; o.sayNameUC = function() { alert(nameUC); }; return o; } var person = Person("Nicholas", 32, "software Engineer"); person.sayName(); // "Nicholas" person.sayNameUC(); // "NICHOLAS" alert(person.name); // undefined alert(person.nameUC); // undefined
这里的 private 和 public 都是从形式上类比其他 OO 语言来说的,其实现原理还是 js 中作用域、闭包和对象那一套。感觉实现得挺巧妙的。