关于js中的继承,已经老生常谈了,本文将对js的继承做一个大概的总结.
首先我们可以看一下,es5继承关系图,理解继承的实现,然后再讨论不同的继承的实现方式的问题
1.在js实现继承靠的构造函数的原型对象(即Prototype)
2.js中所有的对象都继承Object
3.Object的原型对象的[[prototype]]指向null
看完上面的原型继承图之后,我们接下来就可以按照原型链去实现继承了
1.组合继承
function Super(age){
this.name = "lsh";
this.age = age;
}
Super.prototype.sayName = function (){
console.log(this.name);
}
function Sub(){
Super.call(this,99);
}
Sub.prototype = new Super;
Sub.prototype.constructor = Sub;
Sub.prototype.hello = function (){
console.log(this.age);
}
首先借用构造函数,利用的是js中函数执行的环境问题,call和apply可以修改函数执行的函数,实际上是修改了函数内部的this对象.其实就是在子类的构造函数内部调用父类的构造函数,通过call或apply修改其this对象,实现一种继承的现象.然后再设置子类的prototype为父类的对象,完成原型的继承
2.原型是继承
原型继承要求你必须有一个对象作为另一个对象的基础,将这个对象传入Objec.create函数中,create函数返回一个新的对象,这个新的对象的原型就是你传入的对象.这样新的对象就会在原型上拥有你传入对象的所有属性和方法,包括其原型
function Person(){
this.name = 'lsh';
this.friends = ['name1','name2'];
}
Person.prototype.sayName = function(){
console.log(this.name);
}
var person = new Person();
var person1 = Object.create(person);
person1.friends.push('name3');
var person2 = Object.create(person);
person2.name = 'xindi';
person2.friends.push('name4');
这样的结果其实就是相当于创造了一个你传入对象的副本.如果你传入的对象包含有引用类型的属性,则这个属性会被共享.
此时原型链的关系就会是
superPerson.__proto__.__proto__ === Person.prototype
superPerson.__proto__.constructor === Person
其实create方法内部的实现机制就是创造了一个临时的构造函数,然后将临时构造函数的原型指向你传入的对象,最后返回临时构造函数生成的对象.
function create(o){
function F(){};
F.prototype = o;
return new F();
}
所以当你采用原型式继承的时候,要考虑清楚这种继承是否满足你的需要.
3.寄生式继承
寄生式继承和原型式继承关系紧密,其实就是封装一个继承用的函数,在函数内部调用原型式继承,同时在函数内部可以选择给新的对象增加一个方法或属性
function createAnother(ori){
let clone = Object.create(ori);
clone.sayHi = function(){
}
return clone;
}
4.寄生组合式继承
寄生组合式继承,就是通过构造函数来继承属性,通过原型链的混成方式来继承原型的方法或属性
function inherit(superType,subType){
let prototype = Object.create(superType.prototype);
prototype.constructor = subType;
sub.prototype = prototype;
}
函数第一步创建了一个父类的原型的副本,然后让这个对象的contructor指向子类的构造函数,最后将子类构造函数的原型设置为这个对象.这样就实现了一个继承.
寄生组合式继承是最理想的继承方式,推荐使用这个方式.
ps:如何确定实例和原型的关系:
1.instance instanceof Super
2.Super.prototype.isPrototypeof instance
给子类原型添加方法,无论是重写超类的方法还是添加新的,一定要在原型被替换之后再写,也就是在调用继承之后再添加.
不过在ES6 ,js添加了class这个语法糖,其继承也只需简单的写一个extends.
class Super {
constructor(name){
this.name = 'name';
}
sayName(){
console.log(this.name);
}
}
class Sub extends Super{
constructor(name){
super(name);
this.name = 'xindi';
}
}
这个语法糖使得es6的继承变得无比简单,不过es6的继承还是和es5的稍微有点区别,如下图
es6的继承相较于es5,其实就多了一个子类构造函数和父类构造函数的关系上,在es5中两个构造函数没有任何关系,但是在es6中子类构造函数的__proto__(即[[prototype]])指向了父类的构造函数.