likes
comments
collection
share

👨你来说一下:class的内部原理--原型继承是什么

作者站长头像
站长
· 阅读数 26

面试官:你来说下class的内部原理是什么吧

我: 这个说来话长啊🤔

原型继承的标准

在面向对象中,继承只需要一个 extends 就可以实现。而在 JS 中也可以使用class语法中的extends来实现继承,但其内部的原理依旧是原型。要搞清楚原型继承,可没有这么简单。

继承有几个标准:

  1. 可以访问被继承对象的属性和方法
  2. 可以访问被继承对象的原型属性和方法,也可以访问被继承者原型的原型的属性和方法,即继承原型链
  3. 能通过 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;
}

代码很简单,就不解释了

上面要求的功能这里都完整的实现了,实际运行一下看看:👨你来说一下:class的内部原理--原型继承是什么

效果符合预期

有个问题:最后的输出是 false,true,表示 newDog 并不是 Dog 的实例对象,这是不对的,我们后面再讲

newDog 对象的内部结构

现在我们一起看看,newDog 对象的内部结构。先打开 vscode 调试终端:

👨你来说一下:class的内部原理--原型继承是什么

然后在这里打上断点:

👨你来说一下:class的内部原理--原型继承是什么

在终端执行:node test.js

我的文件名是 test.js

然后代码就会在 38 行停下来,屏幕左边就可以看到 newDog 的内部结构了:

👨你来说一下:class的内部原理--原型继承是什么

有几个点需要注意:

  1. 实例属性有两个,subType 和 type,直接放在 newDog 对象的下面
  2. 第一层原型对象
    1. 是 Animal 的实例对象
    2. 有 getSubType 的方法,这是 Dog 的原型方法,我们也保留了
    3. 有 constructor 属性,指向了 Dog
  1. 第二层原型对象,
    1. 是 Animal 的原型对象
    2. 有 getType 方法
    3. 有 constrcutor 属性,指向了 Animal
  1. 第一层原型对象是 Animal 的实例对像。所以继承 Animal 的原型链的方式,并不是直接将 Animal 的原型对象赋值给 Temp.prototype,而是将根据 Animal.prototype 创建的对象赋值给了Temp.prototype。

    这样做的目的是,当我们修改 Temp.prototype 的时候,不会修改到被继承者原型对象,也就是 Animal.prototype 的属性

  2. 实例属性和原型属性不能混在一起,有着明显的区别。实例属性是每个实例对象独有的,而原型属性是每个实例对象共有的

问题

在上面提到了,代码实际执行的输出是 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 原型对象本身的内容。

看看现在的执行结果:

👨你来说一下:class的内部原理--原型继承是什么

执行结果完全符合预期,完美。

总结

这篇文章讲了原型继承的标准,以及用函数来实现比较完美的原型继承。在 ES6 之后,就不再需要这种方法了,直接用 Class extends 搞定。不过纵使用了 class,其内部还是原型的实现,所以学习原型继承,是很有必要的

转载自:https://juejin.cn/post/7388456632273043507
评论
请登录