JavaScript对象继承的方法有不少,这里总结一下几种比较经常使用的方法。
如今有一个"动物"对象的构造函数。javascript
function Animal(){ this.species = "动物"; } Animal.prototype.voice = function(){ console.log('voice'); }
还有一个"猫"对象的构造函数。html
function Cat(name, color){ this.name = name; this.color = color; }
怎样才能使"猫"继承"动物"呢?前端
第一种方法使用call或apply方法,改变了 this 的指向而实现继承,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:java
function Cat(name, color){ Animal.apply(this, arguments); this.name = name; this.color = color; } var cat1 = new Cat("大毛","黄色"); alert(cat1.species); // 动物
到目前为止一切看起来都还行,可是咱们很快会发现咱们遇到问题了,当咱们调用cat1.voice()的时候报错了,显然Cat并无继承Animal的原型对象里的方法。es6
咱们已经定义了一个新的构造器,这个构造器默认有一个空的原型属性。咱们并无让Cat从Animal的原型对象里继承方法。编程
Cat.prototype = Object.create(Animal.prototype);
这里咱们的老朋友create()又来帮忙了——在这个例子里咱们用这个函数来建立一个和Animal.prototype同样的新的原型属性值(这个属性指向一个包括属性和方法的对象),而后将其做为Cat.prototype的属性值。这意味着Cat.prototype如今会继承Animal.prototype的全部属性和方法。segmentfault
接下来,在咱们动工以前,还须要完成一件事 — 如今Cat()的prototype的constructor属性指向的是Aniaml(),这是由咱们生成Animal()的方式决定的。(这篇 Stack Overflow post 文章会告诉您详细的原理)数组
Cat.prototype.constructor = Cat;
此时再输入Cat.prototype.constructor就会获得Cat()。闭包
顺带一提,这是很重要的一点,编程时务必要遵照。下文都遵循这一点,即若是替换了prototype对象,那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。app
o.prototype = {}; o.prototype.constructor = o;
第二种方法更常见,可是不使用apply和call,而是使用prototype属性。
若是"猫"的prototype对象,指向一个Animal的实例,那么全部"猫"的实例,就能继承Animal了。
var Cat = function(){}; Cat.prototype = new Animal(); Cat.prototype.constructor = Cat; var cat1 = new Cat("大毛","黄色"); console.log(cat1.species); // => 动物
代码的第一行,咱们将Cat的prototype对象指向一个Animal的实例。
它至关于彻底删除了prototype 对象原先的值,而后赋予一个新值。可是此时Cat.prototype.constructor依然指向Animal的构造函数,因此须要将其指回Cat。
利用一个空对象做为中介。
var F = function(){}; F.prototype = Animal.prototype; Cat.prototype = new F(); Cat.prototype.constructor = Cat;
F是空对象,因此几乎不占内存。这时,修改Cat的prototype对象,就不会影响到Animal的prototype对象。
alert(Animal.prototype.constructor); // Animal
咱们将上面的方法,封装成一个函数,便于使用。
function extend(C, P) { var F = function(){}; F.prototype = P.prototype; C.prototype = new F(); C.prototype.constructor = Child; C.super = P.prototype; }
使用的时候,方法以下
extend(Cat,Animal); var cat1 = new Cat("大毛","黄色"); alert(cat1.species); // 动物
这个extend函数,就是YUI库如何实现继承的方法。
另外,说明一点,函数体最后一行
Child.super = Parent.prototype;
意思是为子对象设一个super属性,这个属性直接指向父对象的prototype属性。这等于在子对象上打开一条通道,能够直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。
好比,如今有一个对象,叫作"中国人"。
var Chinese = { nation:’中国' };
还有一个对象,叫作"医生"。
var Doctor ={ career:’医生’ }
请问怎样才能让"医生"去继承"中国人",也就是说,我怎样才能生成一个"中国医生"的对象?
这里要注意,这两个对象都是普通对象,不是构造函数,没法使用构造函数方法实现"继承"。
提到拷贝继承,不得不提到浅拷贝,浅拷贝只能拷贝一层深度,可是不少时候咱们碰到的对象结构不止一层,因此浅拷贝并不适合咱们实际开发中的使用,这里也就很少作说明了。
所谓"深拷贝",就是可以实现真正意义上的数组和对象的拷贝。它的实现并不难,只要递归调用"浅拷贝"就好了。
function deepCopy(p, c) { var c = c || {}; for (var i in p) { if (typeof p[i] === 'object') { c[i] = (p[i].constructor === Array) ? [] : {}; deepCopy(p[i], c[i]); } else { c[i] = p[i]; } } return c; }
使用的时候这样写:
var Doctor = deepCopy(Chinese);
如今,给父对象加一个属性,值为数组。而后,在子对象上修改这个属性:
Chinese.birthPlaces = ['北京','上海','香港']; Doctor.birthPlaces.push('厦门');
这时,父对象就不会受到影响了。
alert(Doctor.birthPlaces); //北京, 上海, 香港, 厦门 alert(Chinese.birthPlaces); //北京, 上海, 香港
目前,jQuery库使用的就是这种继承方法。
这个是 ES6 的语法糖,下面看下es6实现继承的方法
class Parent { constructor(name, age) { this.name = name; this.age = age; } } class Children extends Parent { constructor(name, age, job) { this.job = job; // 这里会报错 super(name, age); this.job = job; // 正确 } }
上面代码中,子类的constructor方法没有调用super以前,就使用this关键字,结果报错,而放在super方法以后就是正确的。子类Children的构造函数之中的super(),表明调用父类Parent的构造函数。这是必须的,不然 JavaScript 引擎会报错。
注意,super虽然表明了父类Parent的构造函数,可是返回的是子类Children的实例,即super内部的this指的是Children,所以super()在这里至关于Parent.prototype.constructor.call(this)。
参考:
推荐阅读:
【专题:JavaScript进阶之路】
JavaScript之深刻理解闭包
ES6 尾调用和尾递归
Git经常使用命令小结
JavaScript之call()理解
JavaScript之对象属性
我是Cloudy,年轻的前端攻城狮一枚,爱专研,爱技术,爱分享。
我的笔记,整理不易,感谢阅读、点赞和收藏。
文章有任何问题欢迎你们指出,也欢迎你们一块儿交流前端各类问题!