前端面试,总是会被问到这样一类问题:
- 来来来,可以解释下什么是JS中的原型和原型链么?
![90bd9ae00a2563af84c969f67be1215d.png](https://i-blog.csdnimg.cn/blog_migrate/142968da7de67f6bb1bf65e16f47f9a9.jpeg)
最近,知乎上一个问题总是出现在我的时间线上:面试一个5年的前端,却连原型链也搞不清楚,满口都是Vue,React之类的实现,这样的人该用吗?
虽说,我们不能只凭借一道题,就给面试者盖棺定论。回答者都说的好有道理,道理我们都懂,但是到底什么是原型和原型链呢?
首先,JS是真正的“面向对象”的语言,而其他我们所熟知的例如C++、Java等,严格意义上说,是“面向类”的语言,仔细想想,还真是那么回事儿。其次,JS中,调用构造器之后,对象并不是它原型的一份拷贝,而是被链接到原型上。
Object
我们先看一段代码:
function Person(name){ // 1
this.name = name;
}
Person.prototype.sayName = function(welcome) { // 5
console.log(welcome, this.name);
}
var person1 = new Person('Smiley'); // 9
person1.sayName('Hello'); // 10
在执行这段代码之前,有个东西是一直存在的,就是有些人所说的“原型的原型”。如下图所示,我们用圆形代表function,用方形代表object:
![57950dd136c24c60aa8865886ec231fa.png](https://i-blog.csdnimg.cn/blog_migrate/9a8f838a3eb0348397f49b298e871537.jpeg)
左边的圆形是Object构造函数,就是我们一般使用var obj = new Object()时最普普通通的的构造函数。右边的方形是Object的prototype,这个东西没有名字(虽然它很重要)。左边有一个箭头指向右边,意味着Object有一个属性叫做prototype,这个属性指向的是右边的那个方块,而向左指的箭头,意味着右边方块的constructor属性是左边的Object构造函数。
这些东西在上面程序运行之前就一直存在的。
构造函数
好的,我们开始看第一行代码,运行第一行代码之后,会生成一个叫Person的构造函数,而这个构造函数的prototype属性,指向的就是它的prototype,如下图方块所示:
![233e4441ea6f0435ab7505d0a9b1268f.png](https://i-blog.csdnimg.cn/blog_migrate/9d67713a30bf0338db68664e3fc87852.jpeg)
Person与Person.prototype之间的关系,与Object和Object.prototype类似,不同的是,Person.prototype会通过__proto__指向Object.prototype。
接下来看第5行代码:我们在Person.prototype上面添加一个sayName方法,如上图所示,Person.prototype这个方块中有sayName方法。
new调用构造器
最后来看第9行代码,这行代码中,我们new了一个Person的实例,还记得我在《前端面试题——自己实现new》当中提到,使用new这个关键字的时候,JS编译器会做四件事情:
- 创建一个新的空的对象
- 把这个对象链接到原型对象上,将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 如果这个函数有返回值,则返回;否则,就会默认返回新对象
![3ff67b2fc1e2036f27cd011f3643cb59.png](https://i-blog.csdnimg.cn/blog_migrate/2a8246dad1a3ec0f369c901c90bc8b29.jpeg)
如下图所示,我们先创建一个person1的空对象,然后把person1通过__proto__指向原型对象,指向构造函数中的代码,person1就获得了一个叫做name的属性,最后返回。
我们最后运行第10行代码:person1.sayName('Hello');
person1上有sayName这个方法么?没有,那么就顺着person1的__proto__向上找,找到Person.prototype。Person.prototype上面有sayName方法么?有的,那么执行这个方法。这个方法内部使用了this.name,那么这个this的指向是什么么?我们需要看sayName的call site,是person1调用的sayName,隐式调用,this就指向person1,而person1的name就是Smiley。
是不是觉得很神奇,最后调用时候使用的属性和方法都是我们希望使用的那个,person1.sayName('Hello')看似很容易理解,JS初学者都能很容易说出最后输出结果,但是这其中的过程,恐怕只有理解了原型和原型链才能真正说明白。
明白了这些之后,我们看几个相等关系:
console.log(Person === Person.prototype.constructor);
console.log(person1.__proto__ === Person.prototype);
我们再也不用死记硬背这个关系了,而是通过上面的图直接可以推到出来。
最后问大家一个问题:console.log(person1.constructor)是什么的呢?怎么找到的呢?
好啦,这就是关于原型和原型链的一些知识 。希望看完这篇文章之后,再也不会为原型和原型链感到头疼,祝大家前端学习一切顺利。
关注我的公众号:前端三剑客
![3b6d9ad2eaa60342dce83897e3d60b85.png](https://i-blog.csdnimg.cn/blog_migrate/f41a79d92e8e80fbbe47582330642ae3.jpeg)