要彻底弄明白js中的继承,我们首先要弄清楚js中的一个很重要的概念那就是原型链。
1.什么是原型链?
我们知道每个构造函数都有一个原型对象,原型对象包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。如果,让原型对象等于另一个引用类型的实例,那么原型对象中将包含一个指向另一个原型的指针,相应地,另一个原型对象中包含着一个指向另一个构造函数的指针。假如另一个原型对象又是另一个引用类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型之间的链条。这就是所谓原型链的基本概念。关键就是让原型等于另一个引用类型的实例。
2.默认原型
我们知道,所有引用类型默认都继承了Object,而这个继承也是通过原型链来完成的。记住:所有函数的默认原型都是Object的实例,因此默认原型都包含一个内部指针,指向Object.prototype.这也是所有自定义类型都会继承toString(),toValue()等方法的根本原因。
3.确定原型和实例之间的关系
1.使用instanceof操作符,只要用这个操作符来检测实例与原型链中出现过的构造函数,结果就会返回true
2.使用isPrototypeOf(),只要是原型链中出现过的原型,都可以说是原型链中所派生出来的实例的原型,结果就会返回true。
4.谨慎地定义方法
子类型有时候需要覆盖超类中的某个方法,或者需要添加超类中不存在的某个方法。但不管怎样,给原型添加的方法一定要放在替换原型的语句之后,在通过原型实现继承时,不能使用对象字面量创建原型方法,因为这样做会重写原型,切断实例与原型之间的连接关系。
5.原型链的问题
引用类型值的原型属性会被实例共享;而这也是为什么要在构造函数中,而不是在原型对象中定义属性的原因。在通过原型实现继承的时候,原型实际上是变成另一个类型的实例,于是,原先的实例属性顺理成章地变成了现在的原型属性了。
下面代码可以说明这个问题:
function SuperType() { this.colors = [‘red‘, ‘orange‘, ‘green‘]; } function SubType() { } // 继承Supertype SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push(‘black‘); console.log(instance1.colors); // [‘red‘,‘orange‘,‘green‘,‘black‘] var instance2 = new SubType(); console.log(instance2.colors); // [‘red‘,‘orange‘,‘green‘,‘black’]
在这里我们把SubType的原型指向SuperType的一个实例,由于SuperType只实例了一次,所以实例instance1和instance2共享了colors属性,导致一变多变的现象出现,我们想的是每一个实例都有属于自己的一个属性副本,并不会进行相互的干扰。
此外,在创建子类型的实例的时候,我们无法向超类型的构造函数传递参数。
6.借用构造函数
这种技术的思想相当简单,即在子类型构造函数的内部调用超类型的构造函数,函数只不过是在特定环境中执行代码的对象,因此通过apply,call等方法也可以执行函数。
function SuperType() { this.colors = [‘red‘, ‘orange‘, ‘green‘]; } function SubType() { // 执行超类的构造函数,为子类的每个实例单独创建一个colors属性副本 SuperType.call(this) } // 继承Supertype SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push(‘black‘); console.log(instance1.colors); // [‘red‘,‘orange‘,‘green‘,‘black‘] var instance2 = new SubType(); console.log(instance2.colors); // [‘red‘,‘orange‘,‘green‘]
如上面代码所示,我们在子类型的构造函数中执行了超类型的构造函数,这样子就为SubType的每一个实例单独创建了一个colors的属性副本,做到不相互干扰。
别忘了,apply个call函数除了执行函数之外,还可以传递参数,那么我们修改上面的例子,就可以实现向超类型的构造函数传递初始化参数。
代码如下:
function SuperType(name) { this.name = name } function SubType(name) { // 执行超类的构造函数,为子类的每个实例单独创建一个colors属性副本 SuperType.call(this,name); this.colors = [‘red‘]; } // 继承Supertype SubType.prototype = new SuperType(); var instance1 = new SubType(‘instance1‘); instance1.colors.push(‘black‘); console.log(instance1.name);// instance1 console.log(instance1.colors); // [‘red‘] var instance2 = new SubType(‘instance2‘); console.log(instance2.colors); // [‘red‘] console.log(instance2.name);// instance2
借用构造函数模式的问题,方法都在构造函数中定义,因此函数复用就无从谈起了,而且在超类型的原型中定义的方法,对子类型是不可见的,结果所有类型只能使用构造函数模式。
7.组合继承
组合继承,有时候叫做伪经典继承,指的是将原型链和借用构造函数的技术组合在一起的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数实现对实例属性的继承。
看如下代码:
function SuperType(name) { this.name = name; this.colors = [‘red‘,‘orange‘,‘green‘]; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name,age) { // 执行超类的构造函数,为子类的每个实例单独创建一个colors属性副本 SuperType.call(this,name); this.age = age; } // 继承Supertype SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age); } var instance1 = new SubType(‘instance1‘,22); instance1.colors.push(‘black‘); instance1.sayName();// instance1 instance1.sayAge();// 22 console.log(instance1.colors); // [‘red‘,‘orange‘,‘green‘,‘black‘] var instance2 = new SubType(‘instance2‘,23); console.log(instance2.colors); // [‘red‘,‘orange‘,‘green‘] instance2.sayName();// instance2 instance2.sayAge();// 23
这样一来,就可以让两个不同的SubType实例既分别拥有自己属性--包括colors属性,又可以使用相同的方法了。
组合继承避免了原型链和构造函数的缺陷,融合了它们的优点,成为JavaScript中最常用的继承模式。而且instanceof和isPrototypeOf()也能够用于识别基于组合继承创建的对象。
8.原型式继承
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
代码如下:
function object(o) { function F() { } F.prototype = o; return new F(); }
这种方式必须有一个对象可以作为另一个对象原型的基础,如果有这么一个对象,就可以传递给object函数,然后在根据具体需求对得到的对象加以修改即可。
ECMAScript5中新增了Object.create()方法规范了原型式继承。接受两个参数:
1.一个用做新对象原型的对象([[prototype]]__proto__可选)
2.第二个参数是一个用作新对象自定义属性的对象(同名属性会覆盖原型对象上的同名属性)。
在没有必要创建构造函数的时候,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。但是,包含引用类型值的属性始终都会共享相应的值。
9.寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象。如下代码所示:
function object(o) { function F() { } F.prototype = o; return new F(); } function createAnother(original) { var cloen = object(original); cloen.sayHi = function () { console.log(‘hi‘); } return cloen; }
在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点于构造函数模式类似。
10.寄生组合式继承
前面说到的组合继承也有自己的不足,就是会调用两次超类型的构造函数,这样子就给子类型构造函数的原型对象上添加了额外的属性和方法。
代码如下:
function SuperType(name) { this.name = name; this.colors = [‘red‘, ‘orange‘, ‘green‘]; } SuperType.prototype.sayName = function () { console.log(this.name); } function SubType(name, age) { // 执行超类的构造函数,为子类的每个实例单独创建一个colors属性副本 SuperType.call(this, name); //第二次调用 this.age = age; } // 继承Supertype SubType.prototype = new SuperType(); // 第一次调用 SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { console.log(this.age); } var instance1 = new SubType(‘instance1‘, 22); instance1.colors.push(‘black‘); instance1.sayName();// instance1 instance1.sayAge();// 22 console.log(instance1.colors); // [‘red‘,‘orange‘,‘green‘,‘black‘] var instance2 = new SubType(‘instance2‘, 23); console.log(instance2.colors); // [‘red‘,‘orange‘,‘green‘] instance2.sayName();// instance2 instance2.sayAge();// 23
使用寄生组合继承可以这样子写:
function object(o) { function F() { } F.prototype = o; return new F(); } function inheritPrototype(subType,superType) { // 返回一个新对象 var prototype = object(superType.prototype); // 原型中constructor的指向 prototype.constructor = subType; // 重写原型 subType.prototype = prototype; }
这样子就可以替换为子类原型赋值的语句了,如下代码:
function SuperType(name) { this.name = name; this.colors = [‘red‘, ‘orange‘, ‘green‘]; } SuperType.prototype.sayName = function () { console.log(this.name); } function SubType(name, age) { // 执行超类的构造函数,为子类的每个实例单独创建一个colors属性副本 SuperType.call(this, name); this.age = age; } // 继承Supertype inheritPrototype(SubType,SuperType); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function () { console.log(this.age); } var instance1 = new SubType(‘instance1‘, 22); instance1.colors.push(‘black‘); instance1.sayName();// instance1 instance1.sayAge();// 22 console.log(instance1.colors); // [‘red‘,‘orange‘,‘green‘,‘black‘] var instance2 = new SubType(‘instance2‘, 23); console.log(instance2.colors); // [‘red‘,‘orange‘,‘green‘] instance2.sayName();// instance2 instance2.sayAge();// 23 function object(o) { function F() { } F.prototype = o; return new F(); } function inheritPrototype(subType,superType) { // 返回一个新对象 var prototype = object(superType.prototype); // 原型中constructor的指向 prototype.constructor = subType; // 重写原型 subType.prototype = prototype; }
这样子一来,我们就省略了给子类型原型赋值的操作,避免了给子类原型上面添加了额外的属性。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
原文地址:https://www.cnblogs.com/jsydb/p/12240552.html