说到Javascript的继承,相信只要是前端开发者都有所了解或应用,因为这是太基础的知识了。但不知各位有没有深入去理解其中的玄机与奥秘。今本人不才,但也想用自己的理解来说一说这其中的玄机和奥秘。
一、类继承的发展吏
- function实现的继承
function的继承是完全模仿了OOP的编程思想。实现的是类的继承
- object.create实现的继承
用object.create来修改其原型
- es6的继承
增加了class来模拟OOP的继承实现。上述两种继承实现,他都还是支持的。
二、各时期类继承的实现
- function继承方式的实现(OOP)
function Animate(name){ this.name = name; } Animate.prototype.getName = function(){ return this.name; } function Dog(name){ Animate.apply(this,arguments); this.leg = 4; } Dog.prototype = Inherit(Animate, Dog); Dog.prototype.say = function(){ return 'wang'; } //function模式的继承 function Inherit(parent, child){ //创建一个无原型方法的类 function f(){} f.prototype = parent.prototype; //将父对象的原型赋给临时对象 f.prototype.constructor = child; //将子类构造函数绑定到 临时对象的 prototype原型上,保持子类构造函数与prototype上的一致。 return new f(); //执行了f的构造函数,而没有执行prototype.constructor指向的构造函数 } var dog = new Dog('dog'); console.log('getName:' + dog.getName()); //dog console.log("say:" + dog.say()); //wang console.log( 'dog instanceof Animate:' + (dog instanceof Animate)); //true console.log( 'dog instanceof Animate: ' + (dog instanceof Dog)); //true
OOP方式的继承实现网上有很多种,如:原型链,实现、组合、寄生组合继承等。上述实现为寄生组合继承在,算比较通用且完美的一种方案了。
- object.create实现继承
这是一个升级版本的类式继承,需要了解object.create方法。Object.create(proto, [propertiesObject]),其中proto是新创建对象的原型对象,而propertiesObject是可选的,要添加到新创建对象的可枚举属性(即其自身定义的属性,而不是原型链接上的属性)。
我们还需要了解的方法:Object.setPrototypeOf(内部原型的写方法);Object.getPrototypeOf(内部原型的读方法)。内部原型:[[prototype]] == proto
上述继承代码的改造后
function Animate(name){ this.name = name; } Animate.prototype.getName = function(){ return this.name; } function Dog(name){ Animate.apply(this,arguments); this.leg = 4; } Inherit(Animate, Dog); //调用点改造 Dog.prototype.say = function(){ return 'wang'; } //继承实现方法改造 function Inherit(parent, child){ child.prototype = Object.create(parent.prototype); //create实现parent上的原型复制 child.prototype.constructor = child; //将构造函数指回子类 } var dog = new Dog('dog'); console.log('getName:' + dog.getName()); //dog console.log("say:" + dog.say()); //wang console.log( 'dog instanceof Animate:' + (dog instanceof Animate)); //true console.log( 'dog instanceof Animate: ' + (dog instanceof Dog)); //true
2.1 对prototype的尝试
上述示例中用了Object.create方法创建一个对象,然后再赋值给Prototype,而为什么不用Object.setPrototypeOf方法直接改变其Prototype的值呢。原因摘录来源于MDN:
由于现代 JavaScript 引擎优化属性访问所带来的特性的关系,更改对象的 [[Prototype]]在各个浏览器和 JavaScript 引擎上都是一个很慢的操作。其在更改继承的性能上的影响是微妙而又广泛的,这不仅仅限于 obj.proto = ... 语句上的时间花费,而且可能会延伸到任何代码,那些可以访问任何[[Prototype]]已被更改的对象的代码。如果你关心性能,你应该避免设置一个对象的 [[Prototype]]。相反,你应该使用 Object.create()来创建带有你想要的[[Prototype]]的新对象。
对象继承的实现:
var animate = { name: "name" }; Object.setPrototypeOf(animate,{ getName: function(){ return this.name; } }); var dog = { leg: 4 }; Object.setPrototypeOf(dog,{ say: function(){ return 'wang'; } }); Object.setPrototypeOf(Object.getPrototypeOf(dog),Object.getPrototypeOf(animate)); console.log('getName:' + dog.getName()); //dog console.log("say:" + dog.say()); //wang
三、ES6类继承的实现
es6对类继承提供了原生的支持,这让Javascript更像后端语言了,简单使用如下:
class Animate{ constructor(name){ this.name = name } getName(){ return this.name; } } class Dog extends Animate{ constructor(name){ super(name); this.leg = 4; } say(){ return "wang"; } } var dog = new Dog('dog'); console.log('getName:' + dog.getName()); //dog console.log("say:" + dog.say()); //wang console.log( 'dog instanceof Animate:' + (dog instanceof Animate)); //true console.log( 'dog instanceof Animate: ' + (dog instanceof Dog)); //true
四、总结及疑问
经过本文梳理,你是否发现Javascript的类继承实现越来越简单,更接近于Java,.net这类静态编译语言了,或许这就是所谓的万物归一的哲学道理。但在此还是存在一个极大的疑问:Object.setPrototypeOf方法在MDN不建议使用,说是更改内部的[[prototype]]属性存在性能问题 和 影响。不知道其影响为何,望大神们指定。