JavaScript高级程序设计: 面向对象编程
工厂模式
工厂模式虽然解决了创建多个相似对象的问题,但没有解决对象识别问题.
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('Alice',23,'PHP Engineer'); person1.sayName();
构造函数
构造函数的特征:
- 没有显示地创建对象
- 直接将属性和方法都赋值给this对象
- 没有return语句
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('Alice',23,'PHP Engineer'); person1.sayName();
构造函数与其他函数的唯一区别,就在于他们的调用方式不同.任何函数只要通过new来调用,那他就可以作为构造函数.
使用构造函数的问题
构造函数模式虽然好用,但构造函数内定义的方法要在每一个实例上重新创建一次.例如定义一个person1,person2,都有一个名为 sayName 的方法,但这个两方法不是同一个 Function 的实例.不同实例上的同名函数是不相等的.
console.log( person1.sayName == person2.sayName )
原型模式
我们创建的每个函数都有一个原型属性,这个属性是一个指针,指向一个对象.而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法.
person = new Person(); Person.prototype = { constructor: 'Person', name: 'Haw', age: 31, job: 'Software Engineer', sayName: function(){ console.log(this.name); } }; var person1 = new Person(); person1.name = 'Sam'
原型对象的问题
原型中的所有属性是被很多实例共享的,对于包含引用类型值的属性来说,就产生问题了.
function Person(){ } Person.prototype = { constructor: Person, name: 'Michel', age: 25, job: 'Software Engineer', friends: ['Shelby','Court'], sayName: function(){ console.log( this.name ); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push('Van'); console.log( person2.friends ); ["Shelby", "Court", "Van"]
person1对friends数组的修改影响到了person2
组合使用构造函数模式和原型模式
创建自定义类型最常见的方式,就是组合使用构造函数模式和原型模式.
- 构造函数模式: 定义实例属性
- 原型模式: 定义方法和实例共享的属性
function Person( name , age , job ){ this.name = name; this.age = age; this.job = job; this.friends = []; } Person.prototype = { constructor: Person, sayName: function(){ console.log( this.name ); } } var person1 = new Person('Micholas',29,'Software Engineer'); var person2 = new Person('Greg',27,'Doctor'); person1.friends.push('Alice','Sam'); person2.friends.push('Bob'); console.log( person1.friends , person2.friends ); ["Alice", "Sam"] ["Bob"]
寄生构造函数模式
创建一个函数,这个函数的作用仅仅是封装创建对象代码,然后返回新创建的对象.
function Person( 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 friend = new Person('Micholas',29,'Software Engineer'); friend.sayName();
除了使用new操作符并把使用的包装函数叫做构造函数之外,这个函数跟工厂模式其实是一模一样的.
function SpecialArray(){ var values = new Array(); values.push.apply( values , arguments ); values.toPipeString = function(){ return this.join("|"); }; return values; } var colors = new SpecialArray('red','blue','green'); console.log( colors.toPipeString() ); red|blue|green
上例中,我们创建了一个具有特殊方法的数组,但是不能直接修改Array构造函数,因此同样使用了寄生构造模式. 关于寄生构造模式,返回的对象与构造函数或者与构造函数的原型属性没有关系.也就是说,构造函数返回的对象与 在构造函数外部创建的对象没有什么不同.为此不能依赖 instanceof 操作符来确定对象类型.
稳妥构造函数
稳妥构造函数遵循与寄生构造类似的模式,当创建对象的实例方法不引用 this, 也不使用 new操作符调用构造函数.
function Person( name ,age , job ){ var o = new Object(); var name = name; var age = age; var job = job o.sayName = function(){ console.log( name ); } return o; } var friend = Person('Sam',21,'PHP Engineer'); friend.sayName(); Sam friend.name undefined
变量friend中保存的是一个稳妥对象,而除了调用 sayName 方法外,没有任何别的方式可以访问其数据成员.
原型链
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针. 假如让原型对象指向一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针.另一个原型中包含着一个指向 另一个构造函数的指针,如此层层递进就够成了实例与原型的链条 -- 原型链
function SuperTYpe(){ this.property = true; } SuperTYpe.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subproperty = false; } // 继承 SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function(){ return this.subproperty; }; var instance = new SubType(); console.log( instance.getSuperValue() ); // true console.log( instance.getSubValue() ); // false
这段代码执行时: instance 指向 SubType原型 指向 SuperType原型.
getSuperValue() 方法本身还在 SuperType原型中,但是 property则位于 SubType原型中.
因为: SubType.prototype = new SuperType();
原型链的问题
在通过原型实现继承时,原型实际上会变成另一个类型的实例.原先的实例属性也就顺理成章地变成了现在的原型属性了.
function SuperType(){ this.colors = ['red','blue','green']; } function SubType(){ } SubType.prototype = new SuperType(); var instance1= new SubType(); instance1.colors.push('black'); var instance2 = new SubType(); console.log( instance2.colors ); // ["red", "blue", "green", "black"]
借用构造函数
function SuperType( name ){ this.name = name; this.colors = ['red','blue','green']; } function SubType( name ){ SuperType.call(this,name); } SubType.prototype = new SuperType(); var instance1= new SubType('NameA'); instance1.colors.push('black'); // SubType {name: "NameA", colors: Array[4]} var instance2 = new SubType('NameB'); //SubType {name: "NameB", colors: Array[3]}
SubType的构造函数借调了超类型中的构造函数,通过调用call方法,实际上是在(未来将要)新创建的SubType实例的环境 下调用了SuperType构造函数.这样一来,就会在新SubType对象上执行SuperType()函数中定义的所有对象初始化代码. 结果, SubType的每个实例就都会具有自己的colors属性的副本了.
借用函数的问题
方法都在构造函数中定义,因此函数复用也就无从谈起.
组合继承
有时也叫伪经典继承,指的是将原型链和借用构造函数的技术组合到一起。使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。
function SuperType( name ){ this.name = name; this.colors = ['red','blue','green']; } SuperType.prototype.sayName = function(){ console.log( this.name ); } function SubType( name ,age ){ SuperType.call(this,name); this.age = age; } SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; // default is SuperType SubType.prototype.sayAge = function(){ console.log( this.age ); }; var instance1= new SubType('NameA',22); instance1.colors.push('black'); instance1.sayName(); instance1.sayAge(); var instance2 = new SubType('Greg',27); instance2.sayName(); instance2.sayAge();
原型试继承
var person = { name: 'Micholas', friends: [ 'Bob','Alice' ], }; var anotherperson = Object.create( person, { name: { value: 'Greg' } }); console.log( anotherperson ); // Object {name: "Greg", friends: Array[2]} anotherperson.friends.push('Tom'); var yetanotherperson = Object.create( person, { name: { value: 'Cooler' } }); console.log( yetanotherperson ); // Object {name: "Cooler", friends: Array[3]}
包含引用类型的值,始终都会在不同的几个实例之间共享相应的值.
寄生式继承
寄生式继承类似于寄生构造函数和工厂模式,创建一个仅用于封装的继承过程的函数,该函数在内部以某种方式增强对象,最后再返回对象。
function object( o ){ function F(){} F.prototype = o; return new F(); } function createAnother(original){ var clone = object( original ); clone.sayHi = function(){ alert('hi'); }; return clone; }
寄生组合式继承
前面提到的组合式继承最大的问题就是无论在什么情况下,都会调用两次超类型构造函数。一次是在创建子类型原型的时候,一次是在子类型构造函数内部。
所谓寄生组合式继承,即使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。
function SuperType( name ){ this.name = name; this.colors = ['red','green','blue']; } SuperType.prototype.sayName = function(){ alert( this.name ); }; function SubType( name , age ){ SuperType.call(this,name); this.age = age; } function object(o){ function F(){} F.prototype = o; return new F(); } function inheritPrototype( subType , superType ){ var prototype = object( superType.prototype ); // 原型副本 prototype.constructor = subType; // 增强对象 subType.prototype = prototype; // 指定对象 } inheritPrototype( SubType , SuperType ); SubType.prototype.sayAge = function(){ alert(this.age); }; var instance1 = new SubType( 'Google',12 ); instance1.sayAge(); instance1.sayName();