前言
在 JavaScript 面试中,有两座大山:一座是作用域链(管变量查找),另一座就是我们今天要讲的原型链(管属性查找和继承))。很多同学容易搞混这三者:prototype、__proto__ 和 constructor。今天我们就用最通俗的语言把它们理清楚
一、 核心概念
要理解原型链,必须先搞懂这三个属性的关系。
1. prototype (显式原型)
- 归属:只有函数(构造函数) 才有这个属性。
- 作用:它是构造函数的一个属性,通过这个属性可以指向一个对象,这个对象就是该构造函数的原型
2. __proto__ (隐式原型)
- 归属:所有对象(包括函数,因为函数也是对象)都有这个属性。
- 作用:它是js对象的一个属性,通过这个属性可以指向该对象的原型(对象原型和构造函数原型是同一个)。
3. constructor (构造器)
- 归属:原型对象 拥有的属性。
- 作用:它指回关联的那个构造函数本身(“生父”)。
- 关系:
Person.prototype.constructor === Person。
二、 原型链的工作原理
什么是原型链? 当我们访问一个对象实例的属性或方法时,查找规则如下:
- 首先查找对象本身是否有该属性。
- 如果没有,浏览器会沿着对象的
__proto__属性,去它的原型对象中查找。 - 如果原型对象中还没有,就继续沿着原型的
__proto__向上查找。 - 这个过程一直持续到链条的顶端 ——
Object.prototype。 - 如果还找不到,
Object.prototype.__proto__为null,此时返回undefined。
这条由 __proto__ 串联起来的链条,就是原型链。
代码实战
function Person(name) {
this.name = name;
}
// 在原型上添加方法
Person.prototype.sayHello = function() {
console.log('Hello');
}
const p1 = new Person('Jack');
console.log(p1.name); // 'Jack' (自身属性)
p1.sayHello(); // 'Hello' (自身没有,去 p1.__proto__ 也就是 Person.prototype 找)
console.log(p1.toString());// '[object Object]' (Person.prototype 也没有,去 Object.prototype 找)
三、 图解:链条的尽头在哪里?
这是一个非常经典的面试题:原型链有终点吗?
答案是:有,终点是 null。
p1.__proto__➜Person.prototypePerson.prototype.__proto__➜Object.prototypeObject.prototype.__proto__➜null(这就是尽头)
四、 面试模拟题(挑战一下)
Q1:说一下作用域链和原型链的区别?
参考回答: 这是一个概念纠错题。
- 作用域链:是针对变量访问的。在当前执行上下文找不到变量时,会去外层词法作用域查找,直到全局作用域。
- 原型链:是针对对象属性访问的。读取属性时,如果对象自身没有,会沿着
__proto__链去原型对象查找,直到null。
Q2:Function 和 Object 是什么关系?(高难度)
参考回答: 在 JS 中,所有函数都是 Function 的实例,所有对象都是 Object 的实例。
Object本身是个构造函数,所以它是Function的实例:Object.__proto__ === Function.prototype。Function本身也是个对象,所以它继承自Object:Function.prototype.__proto__ === Object.prototype。 这就是著名的“鸡生蛋,蛋生鸡”闭环。
Q3:如何判断一个属性是对象自身的,还是继承自原型的?
参考回答: 使用 hasOwnProperty 方法。
const obj = { a: 1 };
// b 是继承自原型的
obj.hasOwnProperty('a'); // true
obj.hasOwnProperty('toString'); // false