上一节我简单写了创建对象的集中方法,关于对象,还有一个有趣的概念,那就是继承。
OO语言,Object-Oriented,即面向对象编程。JS实现继承的方式叫做——实现继承,实现继承又是通过原型链来实现的。
(#`皿´) 吼吼,听听这绕口令,就问你怕不怕! (#`皿´)
一、原型链
构造函数,原型和实例的关系,先默写吧。
每个构造函数,都有一个原型对象。原型对象里包含一个指向构造函数的指针。而实例里包含一个指向原型函数的指针。他们之间的关系可以描述为:实例-->原型对象-->构造函数。
现在,我们假设,某一个原型对象a其实是某个实例类型A。那么,原型对象a(实例A)-->原型对象a的构造函数B,同时也是实例A-->A的原型对象(构造函数B)-->A的构造函数C。这样,一条原型链就出来了。
function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property; } function Sub(){ this.sub='subValue'; } Sub.prototype=new SuperType();//通过初始化继承了SuperType Sub.prototype.getSub=function(){ return this.sub; } var instance=new Sub(); console.log(instance.getSub());//subValue console.log(instance.getSuperValue());//true
上面代码可以看到,通过继承,sub拥有了superType的属性和方法。而他的实现方法就是:创建一个SuperType的实例,然后将这个实例赋给sub的prototype属性中!
还有一个默认的链条。由于所有的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype.这也就是为什么所有的自定义类型都会继承toString()、valueOf()等默认方法的原因。
二、确定原型和实例的关系
第一种方法:采用instanceof操作符。只要用这个操作符测试实例与原型链中出现过的构造函数,结果就会返回true。
console.log(instance instanceof Object);//true console.log(instance instanceof Sub);//true console.log(instance instanceof SuperType);//true
第二种方法:isPrototypeOf()方法。注意,顺序反过来。这个方法是属于对象的prototype属性的。
console.log(Object.prototype.isPrototypeOf(instance));//true console.log(Sub.prototype.isPrototypeOf(instance));//true console.log(SuperType.prototype.isPrototypeOf(instance));//true
三、原型链存在的问题
function Super(){ this.colors=['red','blue','pink']; } function Sub(){ } Sub.prototype=new Super(); var obj1=new Sub(); obj1.colors.push('black'); console.log(obj1.colors);//["red", "blue", "pink", "black"] var obj2=new Sub(); console.log(obj2.colors);//["red", "blue", "pink", "black"]
看见没有,obj1把obj2给玷污了!
包含应用类型值的原型属性会被所有实例共享!所以,这就是我们为什么要在构造函数中定义属性,而不是在原型对象中定义属性。
在通过原型实现继承时,原型实际上会变成另一个类型的实例,于是,原先的是咧也就变成了现在的原型属性了。
四、借用构造函数继承=伪造对象继承=经典继承
为了弥补原型链继承的缺陷,就有了经典继承。基本思想是:在子类型构造函数的内部调用超类型构造函数。
函数是在特定环境中执行代码的对象,通过使用apply()和call()方法,可以在新创建的对象上执行构造函数。
function Super(){ this.colors=['red','blue','pink']; } function Sub(){ //用call()方法继承Super Super.call(this); } var obj1=new Sub(); obj1.colors.push('black'); console.log(obj1.colors);//["red", "blue", "pink", "black"] var obj2=new Sub(); console.log(obj2.colors);//['red','blue','pink']
call()方法,“借调”了超类型构造函数。
相对原型链来说,构造函数的优点是:可以在子类型构造函数中向超类型的构造函数传递参数。比如下面代码:
function Super(name){ this.name=name; } function Sub(){ Super.call(this,'Alice'); this.age=18; } var instance=new Sub(); console.log(instance.name+instance.age);//Alice18
五、组合继承=伪经典继承=原型链+构造函数
组合继承,又叫伪经典继承,将原型链与构造函数组合到一起,实现的继承模式。
function Super(name){ this.name=name; this.color=['red','blue']; } Super.prototype.sayName=function(){ console.log(this.name); } function Sub(name,age){ Super.call(this,name); this.age=age; } Sub.prototype=new Super(); Sub.prototype.constructor=Sub; Sub.prototype.sayAge=function(){ console.log(this.age); } var obj1=new Sub("Alice",18); obj1.color.push('black'); console.log(obj1.color);//Array(3) ["red", "blue", "black"] obj1.sayAge();//18 obj1.sayName();//Alice
组合模式避免了两者的缺点,是js中最常用的继承模式。instanceofhe isPrototypeOf()能够识别基于组合继承创建的对象。