编写Javascript的开发者都知道,JS虽然没有类(ES6添加了class语法),可是能够模拟出OOP语言的类和面向对象的概念,好比咱们都知道的一句话,Javascript中到处是对象,而面向对象语言的特性是继承,封装,多态,抽象,而本文讨论的是Javascript的继承,Javascript的继承方式有原型继承,组合继承,寄生继承等等,在平常开发中,哪一种继承方式更好用在于开发者对于程序的结果以及性能的考虑。笔者在下面列举出原型继承中常常容易被忽略的错误。c++
常见错误一:函数
function Fa(name){ this.name=name; } Fa.prototype.myname=function(){ //设置Fa的原型方法 console.log(this.name); } function Son(name){ Fa.call(this,name); //将Fa的this显示绑定给Son,这里的this显示绑定机制后续笔者会更新 } Son.prototype=Fa.prototype; //将son的原型对象引用到fa的原型对象,其实就是c++里面的指针概念,直接指向fa的原型 //注意!!!!!:当你修改Son.prototype的constructor时
// Son.prototype.constructor=Son; // 这样会致使Fa建立出来的对象的constructor也指向了Son,会使对象的类型变的很混乱 Son.prototype.sayhello=function(){ //设置son的原型方法 console.log("this is son"); } //建立对象 var son=new Son("son"); son.sayhello(); //"this is son" console.log(son); var fa=new Fa("fa"); fa.sayhello(); //"this is son" console.log(fa); //这里发如今由Fa建立的对象中也存在sayhello方法,这是由于son.prototype直接引用了Fa.prototype // Son.prototype=Fa.prototype; 并不会建立一个关联到 Son.prototype 的新对象,它只 //是让 Son.prototype 直接引用 Fa.prototype 对象。所以当你执行相似 Son.prototype. //sayhello = ... 的赋值语句时也会直接修改 Fa.prototype 对象自己。显然这不是你想要的结 //果,不然你根本不须要 Son 对象,直接使用 Fa就能够了,这样一来代码也会更简单一些。
常见错误二:性能
function Fa(name){ this.name=name; } Fa.prototype.myname=function(){ console.log(this.name); } function Son(name){ Fa.call(this,name); } Son.prototype=new Fa("fa"); //调用Fa的构造函数new一个新的对象关联给Son.prototype Son.prototype.sayhello=function(){ console.log("this is son"); } var son=new Son("son"); console.log(son); son.myname(); //son var fa=new Fa("fa"); console.log(fa); fa.sayhello(); //这里会报错。Uncaught TypeError: fa.sayhello is not a function //咱们发如今son的原型建立的方法并无影响到Fa的原型。可是在Son.prototype = new Fa()后, // var son=new Son("son");咱们输出son.name的值为son,在原型上有一个Fa实例对象,这个实例对象也有name属性 //而输出son是由于原型链上的隐式屏蔽,这一层的属性会屏蔽上一层相同属性的值。 // Son.prototype = new Fa() 的确会建立一个关联到 Son.prototype 的新对象。可是它使用 //了 Fa(..) 的“构造函数调用”,若是函数 Foo 有一些反作用(好比写日志、修改状态、注 //册到其余对象、给 this 添加数据属性,等等)的话,就会影响到 Son() 的“后代”,后果 //不堪设想 也会让原型变的臃肿
正确作法:this
function Fa(name){ this.name=name; } Fa.prototype.myname=function(){ console.log(this.name); } function Son(name){ Fa.call(this,name); }
Son.prototype=Object.create(Fa.prototype); //注意这一句 //当修改son的原型时。son的constructor也会指向fa,这里须要手动修改constructor //ES6方法 属性描述符 //IE8如下不兼容 Object.defineProperty(Son.prototype,"constructor",{ writable:true, //读写属性 ,为false时为只读,外界没法修改 configurable:true, //配置属性,为false时,外界没法删除该属性,好比delete Son.prototype.constructor会失效 在严格模式下会报错
enumerable:false, //枚举属性,此时为false,在外界的for in访问方法不会列举出该属性,为true时反之; value:Son //将constructor关联到Son }); var son=new Son("son"); console.log(son); son.myname(); console.log(son instanceof Son); //true // 这段代码的核心部分就是语句 son.prototype = Object.create( fa.prototype ) 。调用 // Object.create(..) 会凭空建立一个“新”对象并把新对象内部的 Prototype 关联到你 // 指定的对象(本例中是 fa.prototype )。 // 换句话说,这条语句的意思是:“建立一个新的 son.prototype 对象并把它关联到 fa. // prototype ”。
这里咱们对比一下三个方法:spa
和你想要的机制不同!
Son.prototype = Fa.prototype;
基本上知足你的需求,可是可能会产生一些反作用 :(
Son.prototype = new Fa();prototype
使用 Object.create(..) 而不是使用具备副指针
做用的 Fa(..) 。这样作惟一的缺点就是须要建立一个新对象而后把旧对象抛弃掉,不能
直接修改已有的默认对象。日志
若是能有一个标准而且可靠的方法来修改对象的 Prototype关联就行了。在 ES6 以前,
咱们只能经过设置 .__proto__ 属性来实现,可是这个方法并非标准而且没法兼容全部浏
览器。ES6 添加了辅助函数 Object.setPrototypeOf(..) ,能够用标准而且可靠的方法来修
改关联。code
ES6 以前须要抛弃默认的 Son.prototype
Son.ptototype = Object.create( Fa.prototype );对象
ES6 开始能够直接修改现有的 Son.prototype
Object.setPrototypeOf( Son.prototype,Fa.prototype );
若是忽略掉 Object.create(..) 方法带来的轻微性能损失(抛弃的对象须要进行垃圾回收)
它实际上比 ES6 及其以后的方法更短并且可读性更高。不过不管如何,这是两种完
全不一样的语法。
以上是笔者对于原型继承中经常忽略的错误的总结以及更好的解决方法,至于寄生继承之类的方法并非本文讨论的范围,后续笔者也会更新寄生继承的方法,若是忽视这些细节上的错误,对后续程序运行的结果也会产生一些不可预知的结果,细节决定成败。