👨你来说一下:class的内部原理--原型继承是什么
面试官:你来说下class的内部原理是什么吧
我: 这个说来话长啊🤔
原型继承的标准
在面向对象中,继承只需要一个 extends 就可以实现。而在 JS 中也可以使用class语法中的extends来实现继承,但其内部的原理依旧是原型。要搞清楚原型继承,可没有这么简单。
继承有几个标准:
- 可以访问被继承对象的属性和方法
- 可以访问被继承对象的原型属性和方法,也可以访问被继承者原型的原型的属性和方法,即继承原型链
- 能通过 instanceof 判断对象和被继承对象之间,有继承关系
先上代码
function Animal(){
this.type = 'Animal';
console.log('This is a Animal')
}
Animal.prototype.getType = function(){
return this.type
}
function Dog(){
this.subType = 'Dog';
console.log('This is a Dog');
}
Dog.prototype.getSubType = function(){
return this.subType;
}
function Extends(parentFunc, sonFunc){
function Temp(){ }
return Temp;
}
const NewDog = Extends(Animal, Dog);
const newDog = new NewDog();
console.log(newDog.type, newDog.subType); // Animal,Dog
console.log(newDog.getType(), newDog.getSubType()) // Aniaml, Dog
console.log(newDog instanceof Dog, newDog instanceof Animal); //预期输出: true true
上面有两个函数 Animal,Dog,然后通过 Extends 函数来生成一个新的函数,新的函数是基于 Dog 继承自 Animal,拥有 Dog 和 Animal 两个的函数实例对象的实例属性,也可以访问原型对象。并且两个 instanceof 判断都是 true
实现 Extends
function Extends(parentFunc, sonFunc){
// 拥有parentFunc,sonFunc两者的实例属性
function Temp(...args){
parentFunc.call(this,...args);
sonFunc.call(this, ...args);
}
// 继承parentFunc的原型链
Temp.prototype = Object.create(parentFunc.prototype);
// 补充sonFunc的原型属性
Temp.prototype = Object.assign(Temp.prototype, sonFunc.prototype);
Temp.prototype.constructor = sonFunc;
return Temp;
}
代码很简单,就不解释了
上面要求的功能这里都完整的实现了,实际运行一下看看:
效果符合预期
有个问题:最后的输出是 false,true
,表示 newDog
并不是 Dog
的实例对象,这是不对的,我们后面再讲
newDog 对象的内部结构
现在我们一起看看,newDog 对象的内部结构。先打开 vscode 调试终端:
然后在这里打上断点:
在终端执行:node test.js
我的文件名是 test.js
然后代码就会在 38 行停下来,屏幕左边就可以看到 newDog 的内部结构了:
有几个点需要注意:
- 实例属性有两个,subType 和 type,直接放在 newDog 对象的下面
- 第一层原型对象
-
- 是 Animal 的实例对象
- 有 getSubType 的方法,这是 Dog 的原型方法,我们也保留了
- 有 constructor 属性,指向了 Dog
- 第二层原型对象,
-
- 是 Animal 的原型对象
- 有 getType 方法
- 有 constrcutor 属性,指向了 Animal
-
第一层原型对象是 Animal 的实例对像。所以继承 Animal 的原型链的方式,并不是直接将 Animal 的原型对象赋值给 Temp.prototype,而是将根据 Animal.prototype 创建的对象赋值给了Temp.prototype。
这样做的目的是,当我们修改 Temp.prototype 的时候,不会修改到被继承者原型对象,也就是 Animal.prototype 的属性
-
实例属性和原型属性不能混在一起,有着明显的区别。实例属性是每个实例对象独有的,而原型属性是每个实例对象共有的
问题
在上面提到了,代码实际执行的输出是 false,true
,表示 newDog
并不是 Dog
的实例对象,这是不对的。
为什么不对,因为我们的目的是得到一个函数, 这个函数是基于 Dog 继承 Animal,如果这个函数得到的实例对象,不是 Dog 的实例化对象。那怎么让人相信得到的函数是基于 Dog,而不是基于其他的函数呢?
修改 Extends
想要做到返回的函数基于 Dog,我们先搞清楚 instanceof 的原理是什么
instanceOf 原理
instanceOf 是一个运算符,用来判断一个对象,是不是由某个函数的实例,判断是不是由这个实例化出来的
其中的本质是检测该函数的 prototype
属性是否出现在这个实例对象的原型链上。即沿着实例对象的原型链往上找,看能不能找到一个原型对象,这个原型对象正好是该函数的 prototype 属性所指向的对像
instanceof 模拟代码:
function instanceOf(obj, func){
while(obj !== null
&& obj.__proto__ !== func.prototype){
obj = obj.__proto__;
}
return obj!==null
}
也就是说,我们想要得到 instanceOf Dog
以及 instanceOf Animal
都为true
,是不是就意味着,Dog.prototype 和 Animal.prototype 都要在同一条原型链上:
new Temp() -- (__ proto__) --> Dog.prototype --(__ proto__)--> Animal.prototype
也就以为着 Dog 的原型链是必须要被修改,避免不了的
function Extends(parentFunc, sonFunc) {
// 拥有parentFunc,sonFunc两者的实例属性
function Temp(...args) {
parentFunc.call(this, ...args);
sonFunc.call(this, ...args);
}
// 继承parentFunc的原型链
const tempPrototype = sonFunc.prototype;
sonFunc.prototype = Object.create(parentFunc.prototype);
// 补充sonFunc的原型属性
Temp.prototype = Object.assign(sonFunc.prototype, tempPrototype);
Temp.prototype.constructor = sonFunc;
return Temp;
}
在 sonFunc 的原型链被修改之前,保存 sonFunc 原型对象本身的内容。
看看现在的执行结果:
执行结果完全符合预期,完美。
总结
这篇文章讲了原型继承的标准,以及用函数来实现比较完美的原型继承。在 ES6 之后,就不再需要这种方法了,直接用 Class extends 搞定。不过纵使用了 class,其内部还是原型的实现,所以学习原型继承,是很有必要的
转载自:https://juejin.cn/post/7388456632273043507