推荐教程: 1.《深入理解JavaScript原型和闭包》:
2.《JavaScript闭包的用途》
####JS高级程序设计原文
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。如果按照字面意思来理解,那么prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。例子:
function Person(){ this.property1 = 'property 1' this.method1 = function() { alert(this.property1); }}
以上这部分就是这段话所提到的不必在构造函数中定义对象实例的信息
function Person(){}Person.prototype.property1 = 'property 1';Person.prototyoe.method1 = function(){ alert(this.property1)}
以上这部分就是这段话所提到的而是可以将这些信息直接添加到原型对象中
####Cyper笔记
var A = {name:'cyper'}var B = function(){}var b = new B;console.log(B.prototype === b.__proto__)
- 任何function加上new后就变成了构造函数。
- function有prototype属性,而对象(包括字面量A和对象b) 都没有
prototype
,但是对象有__proto__
属性,但是b的__proto__
和B的prototype指向同一个位置。而所有的对象字面量其实都是Object的实例,A的__proto__
和Object.prototype一致. (*) - B.prototype默认只有一个constructor属性,在B.prototype上还可以定义B的实例共有的属性和方法,这些属性叫b的原型属性.
- 直接在b上定义的属性叫b的实例属性。只对b起作用.
- 使用原型模式的时候,如果要用字面量的方式重写prototype属性,则在prototype中必须显式的包含contructor属性.否则它就是undefined.
B.prototype = { constructor: B newMethod: function(){ ... }}
这是因为#3中所提到的contructor在重写prototype时会丢弃。所以要重新定义. 6. 如果在构造函数中使用了return关键字,那么构造函数的返回值就是这个在function中显式定义的对象(构造函数默认会生成的一个隐含的对象用this指向它并且隐式返回this),显式定义返回对象的坏处是无法通过instanceof得到新创建对象的真实类型。 7. 显示定义返回对象的另一个坏处是它的contrustor属性指向的是内部对象的contructor,并不是这个构造函数(我们可以修正constructor这一问题),但是仍然无法解决instanceof的问题。
//MarsPerson被称为寄生构造函数.function Person(){}function MarsPerson(){ var o = new Object; o.constructor = MarsPerson; //MarsonPerson的默认contructor指向Object,这里做修正 return o;}console.log(new Person instanceof Person); //trueconsole.log(new MarsPerson instanceof MarsPerson); //false,可以看到instanceof和constructor属性没有任何关系
####原型链
- function的prototype指向的是一个隐式创建的对象(或者叫实例),这个对象默认只有一个construstor属性,但是我们可以修改这个prototype,在这个prototype上添加别的属性和方法,我们把这个原型对象或实例叫做function prototype.
- 更激进一些,我们可以用其它的实例替换掉这个隐式生成的function prototype, 于是构造函数的原型指向了替换后的实例,从而具备这个实例的所有属性和方法。(原来隐式创建的prototype依然存在)
- 只要是在原型链中出现过的原型都可以说是原型链所派生的实例的原型,因此使用instanceof没有任何问题。
- 我们知道,实例属性是私有的,而原型属性是共有的,使用原型链的一个大问题是父类的实例属性会变成子类的原型属性.
function SuperType(){ this.colors = ['red','blue','green']}function SubType(){}SubType.prototype = new SuperType();var sub1 = new SubType()var sub2 = new SubType();sub1.colors.push('black');alert(sub2.colors); //DAMN, sub2的colors也包含black了.
- 另外一个问题是,不能向父类的构造函数中传递参数。
####借用构造函数
function SuperType(){ this.colors = ['red','blue','green']}function SubType(){ SuperType.call(this);}
SuperType.call(this)这一行“借调”了父类的构造函数,通过使用call()方法或appy()方法,我们实际上是在(未来将要)新创建的SubType实例的环境下调用了SuperType构造函数。这样一来,就会在新SubType对象上执行SuperType()函数中定义的所有对象初始化代码。结果,SubType的每个实例就都会具有自己的colors属性的副本了。 和之前提到的那段代码没有太多区别。但是即可以向父类中传参,也解决了属性共享的问题。
- 借用构造函数只是借用了父类的构造函数,在父类的构造函数之外(比如protype上)定义的属性和方法都不会被继承。这就要求父类的所有属性和方法都必须在构造函数中定义。。。(但是这样就没法利用prototype的好用!)
####组合继承
- 组合继承就是利用"原型链"来继承父类中的方法,再利用“借用构造函数"来继承属性(以保证子类都有独立的属性副本)
function SuperType(name){ this.name = name; this.colors = ['red','blue','green']}SuperType.prototype.sayName = function(){ alert(this.name);}function SubType(name, age){ SuperType.call(this.name); this.age = age;}SubType.prototype = new SuperType();
通过SuperType.call(this.name);来继承属性,再通过SubType.prototype = new SuperType();来继承方法。
- 组合继承的缺点是父类的构造函数调用了两次,SubType.prototype = new SuperType()这一次的调用在SubType的prototype上添加了name和colors属性,这是完全没有必要的。有没有即能替换SubType的prototype属性,但是又不必调用SuperType构造函数的办法?
####原型式继承 对于SubType.prototype = new SuperType();可否不用new SuperType就能继承来自父类的属性和方法? SubType.prototype = SuperType.prototype? 不可以,因为这要子类和父类共用同一个prototype任何对于子类prototype的修改会同时影响到父类,解决办法如下:
function object(o){ function F(){} F.prototype = o; return new F();}//上面这段叫原型式继承, 下面这段叫做寄生式继承。var prototype = object(SuperType.prototype);prototype.constructor = SubTypeSubType.prototype = prototype;
function F(){}
新定义一个F类, F类的默认prototype为一个隐式创建的对象,这个对象只有一个contructor属性,指向F自身。这个对象的还有一个__proto__
属性,指向它的父类Object.F.prototype = o;
重写F类的protype,替换为我们的SuperType.prototype, 这时F的prototype和SuperType的prototype就一模一样了。可以把F想像成SuperType的不带参数的构造函数(javascript不支持函数的重载,但是我们这里通过引入F间接的重载了SuperType的构造函数)。我们调用F不带参数的构造函数,得到了F的实例并把这个实例做为子类的原型。- 这样我们实现了不使用new SuperType(name)但是可以继承父类的原型属性和原型方法(但是不会继承父类的实例属性和实例方法,这是因为我们没有调用SuperType的构造函数),我们其实是调用的SuperType构造函数的重载函数F..(因为F的prototype就是SuperType的prototype我们可以认为他们是兄弟,只是构造函数不同)
prototype.constructor = SubType
重写子类prototype的constructor。因为这个constructor当前是指向SuperType的。这里做修正。SubType.prototype = prototype;
替换SubType的prototype,让他指向F的实例(SuperType实例的兄弟)。
####寄生组合式继承 我们把组合继承(通过借用构造函数来继承父类的实例属性和实例方法,以及原型链来继承父类的原型属性和原型方法)
和
寄生式继承(用来继承父类的原型属性和原型方法的,但是不调用父类的构造函数从而不会产生两次去调用父类构造函数的问题),得到了继承的终极版本。
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;}function SuperType(name){ this.name = name; this.colors = ['red','blue','green'];}SuperType.prototype.sayName = function(){ alert(this.name);}function SubType(name,age){ SuperType.call(this, name); this.age = age;}inheritPrototype(SubType, SuperType); //SubType.prototype = new SuperType();SubType.prototype.sayAge = function(){ alert(this.age);}
####例外
#2有一个例外,如果重写了B的prototype,那么在重写之前定义的b的__proto__
继续指向原来的function prototype, 重写的B的prototype只和重写后其它b对象的__proto__
一致。