直接上干货,不搞七七八八的。说到继承,首先我们得从结果找原因,我们要知道继承是为了干什么?我们继承的目的是为了让一个函数从另一个函数里面获得他的属性和方法,而属性和方法在一个函数的哪里呢? 我们来看下面的代码:
function Parent() { val:'lsj' }
Parent.prototype.getValue=function() { console.log(this.val); }这个函数的属性和方法分别在这个函数本身和这个函数的原型上,所以我们要实现继承,就必须要从这个Parent函数和他的原型链上分别获取他的属性和方法。
好现在我们知道了继承了的目的是什么了。那现在我们只需要完成这个目的就好了
我们一个一个来实现。
借用原型链来实现继承
我们都知道js里面万物皆对象,对象都有一个__proto__属性,它指向我们的原型,函数的prototype也指向我们的原型,我们通过调用__proto__去使用原型上的一些方法和属性,也可以通过函数的prototype向原型上添加一些方法,而添加的这些方法就在我们的原型链上面,所以我们想要继承这些方法,就得继承这个函数的原型链。因此我们将要继承的原型指向构造函数 functionA.prototype = new FunctionB() 这样我们的functionA就继承了functionB的原型链。如下代码
function SuperType() { this.prototype = true}SuperType.prototype.getSuperValue = function () { return this.prototype;}function SubType() { this.subproperty = false}SubType.prototype= new SuperType()console.dir(SubType); 打印:
我们也可以打印console.log(SubType.prototype.getSuperValue) 得到的结果是ƒ () { return this.prototype; }
SubTypea函数的prototype指向的是构造函数SuperType,因此我们成功的继承到了SuperType的原型链,但是你不要以为这样就成功的完成了一个继承,我们继续打打印
function SuperType() { this.prototype = true, age = 'lsj' } SuperType.prototype.getSuperValue = function () { return this.prototype; } function SubType() { this.subproperty = false } SubType.prototype = new SuperType() console.log(SubType.prototype.getSuperValue) console.dir(SubType.age)打印
打印的结果显示我们并没有继承到父类的age属性,这是因为用原型链继承,只能继承原型上的属性和方法,不能继承到父类上的属性和方法。所以这个继承并不完美,接下来我们来实现父类上属性的继承。
借用构造函数方法继承
使用构造函数继承,我们通过call方法将子类的作用域指向在父类,这样我们就能调用父类的方法,代码如下:
function SuperType() { this.color = ['red','green','blue'] } function SubType() { SuperType.call(this) } var instancel = new SubType() instancel.color.push('black') console.log(instacel.color); //["red", "green", "blue", "black"] var instace2 = new SubType() console.log(instace2.color); //["red", "green", "blue"]但是同样的我们也能从上面的代码看到,绑定在原型链上的属性我们并不能继承。
现在我们就把上面两种方法结合一下:
组合继承
function Parent() { this.name = '大帅逼' } Parent.prototype.getValue = function () { console.log('你好'); } function Child() { Parent.call(this) //继承函数内部的属性 (构造函数继承) } Child.prototype = new Parent() //继承原型链上的 (原型继承) const child = new Child() child.getValue() console.log(child.name);打印:
这样我们就真正的实现了继承。
上面的代码我们还能优化一下,上面的原型继承时我们使用了构造函数,开销性能,我们来优化一下
寄生组合继承(优化方案)
我们手动创建一个对象并将父类的原型解构进去,并将构造器指向子类。代码如下
function Parent() { this.name = '大帅逼' } Parent.prototype.getValue = function () { console.log('你好'); } function Child() { Parent.call(this) //继承函数内部的属性 (构造函数继承) } Child.prototype = Object.create(Parent.prototype, //{...Parent.prototype}{ constructor: { value: Child, enumerable:false, writable: true, configurable:true } }) //继承原型链上的 (原型继承) const child = new Child() child.getValue() console.log(child.name);打印
其实我们主要了解前三种方法就好了,包括我们后来es6通过class来继承,我们也是通过组合继承的原理来实现的,class就是一个封装好的组合继承
手写class
// 实现继承,通过继承父类 prototype
function __extends(child, parent) {
// 修改对象原型
Object.setPrototypeOf(child, parent);
// 寄生继承,创建一个干净的构造函数,用于继承父类的 prototype
// 这样做的好处是,修改子类的 prototype 不会影响父类的 prototype
function __() {
// 修正 constructor 指向子类
this.constructor = child;
}
// 原型继承,继承父类原型属性,但是无法向父类构造函数传参
child.prototype =
parent === null
? Object.create(parent)
: ((__.prototype = parent.prototype), new __());
}
var B = (function() {
function B(opt) {
this.name = opt.name;
}
return B;
})();
var A = (function(_super) {
__extends(A, _super);
function A() {
// 借用继承,可以实现向父类传参, 使用 super 可以向父类传参
return (_super !== null && _super.apply(this, { name: 'B' })) || this;
}
return A;
})(B)这里的手写是参考的大佬的文章juejin.cn/post/684490…
手写new
这里讲完这些继承方法就不得不讲一下new的实现方法,因为new构造实例和组合继承实在是太像了。所以咱就顺手理一理new的手写:
我们先来看看new的实现效果:
我们能看到new的实例能继承构造函数的内部和原型上的方法属性。并且返回的是一个对象。所以我们实现new的大体步骤就是新建一个对象且返回对象,继承原型和内部属性,是不是和组合继承特别相似。
function a(num) { this.name='lsj', this.number = num}a.prototype.age = '18'function myNew (fn) { let obj= {} // 1.新建一个对象 const args = [].shift.call(arguments) //将参数中的第一个参数,即构造函数拿出来,使用了数组的shift方法 obj.__proto__ = args.prototype // 将构造函数原型指向新对象的原型 const result = args.apply(obj,arguments) //绑定作用域和参数 return typeof result === 'object' ? result : obj}let b = myNew(a,100)console.log(b.__proto__);console.log(myNew(a,100));最后:
能看到这里的人呀,都是人才。