文章前言:为什么你必须读完这篇(即使现在就想关掉页面)
“说不清 prototype 和 __proto__ 的区别?抱歉,下一位。”
三年前,小明自信满满地走进字节跳动终面,却被一道基础题钉在原地:
“请画出 p1 = new Person() 的完整原型链,并解释 constructor 为何可能失效。”
他张了张嘴——那些背烂的面试八股文,此刻像被拔掉网线的代码,死活连不上。
别担心——90% 的开发者都掉进过同一个认知陷阱:
把 prototype 当对象属性,用 __proto__ 当万能胶水,却对 constructor 的“叛变”毫无防备。
但今天,这一切终结了。
本文将用一张可保存的原型链全景图 带你亲手拆解 JavaScript 原型系统的 DNA。
现在,深吸一口气,继续朝下看。
5 分钟后,你会回来感谢自己没关掉这个页面。
一张图 & 一段代码:拆解原型链
function Person(name, age) {
this.name = name;
this.age = age;
}
let p1 = new Person('Skye', 18);
prototype 属性(函数专属)
-
存在位置:只有函数有
prototype属性(如function Person(){})。 -
作用:
当通过new创建实例时,实例的__proto__会指向构造函数的prototype。
prototype是共享资源池,所有实例共享其中的属性和方法,也就是说,由此构造函数创造的对象,都可以利用定义在prototype的方法。例如我们定义的数组,都可以用数组原型链的方法:
const arr = [1,2,3,4,5,[6,7,8]]; console.log(arr.flat());
在下面的例子中,最具体的体现就是:经由
Person构造出来的实例对象,都可以利用以下定义在prototype中的方法,如:sayHi,walk,doWork......
- 结构:
function Person(name) {
this.name = name; // 实例独有属性
}
// Person.prototype 是一个对象
Person.prototype.sayHi = function() {
console.log("Hello " + this.name);
};
Person.prototype.walk = function(){
//...
}
Person.prototype.doWork = function(){
//...
}
Person.prototype.eatMeals = function(){
//...
}
Person.prototype.sleep = function(){
//...
}
-
Person.prototype默认包含:constructor属性(指向Person函数)- 自定义的共享方法(如
sayHi等等) - PS: 如果没有自定义方法,则
Person.prototype默认只包含一个constructor属性
嘿!我相信大家一定有一个疑惑:
“主播主播,prototype不是只存在于函数中吗,怎么Object和Array中也有Prototype呢?”
哎呀呀!这问题地道~
在 JavaScript 中,Object 并不是一个普通的对象字面量(如 {}),而是一个内置的构造函数。
你可以这样验证:
typeof Object; // "function"
Object instanceof Function; // true
👉 所以 Object 本质上是一个函数,和其他构造函数(如 Array、String、Person)地位相同。
| 函数 | 是否有 prototype | 说明 |
|---|---|---|
function Person(){} | ✅ 有 | 自定义构造函数 |
Array | ✅ 有 | 内置构造函数:Array.prototype |
String | ✅ 有 | 内置构造函数:String.prototype |
Object | ✅ 有 | 内置构造函数:Object.prototype |
__proto__ 属性(对象通用)
__proto__叫做double underscore proto,前后各有两个下划线,由于字体原因,显示不同,在这里连到一起了。
-
存在位置:所有对象都有
__proto__(包括函数,因为函数也是对象)。 -
作用:
-
指向创建该对象的构造函数的
prototype,是原型链的链接指针。 也就是说,__proto__会指向它们的原型对象,也就是父对象,谁创造了它们,它们的__proto__就指向谁。 -
当访问对象属性时,若自身没有,会沿着
__proto__向上查找,一直找到null为止(原型链)
-
var obj1 = {
a: 3
}
var obj2 = Object.create(obj1, {
b: {
value: 1, // 创建变量b
writable: true,
enumerable: true,
configurable: true
}
});
var obj3 = Object.create(obj2, {
c: {
value: 9, // 创建变量c
writable: true,
enumerable: true,
configurable: true
}
})
console.log(obj3.a);
在上面的例子中,我们创建了三个对象,其中关系为:obj3继承obj2,obj2继承obj1,按理说,obj3只存在c这个元素,但是我们访问obj3.a还能够访问到,在这里访问的实际上是obj1.a,引擎沿着原型链向上查找,一直查找到了原型链的顶部,找到了变量。
如果一直取到顶部都没查找到变量,就会返回undefined
- 关键关系:
const p1 = new Person("Alice");
p1.__proto__ === Person.prototype; // true ✅
- 普通对象:
{}的__proto__指向Object.prototype - 函数对象:
Person的__proto__指向Function.prototype
constructor属性
constructor属性也是所有对象都拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数,它只存在于prototype属性中。
-
prototype.constructor的作用是:标识“我是谁的原型” -
.constructor的意思就是:谁构造了我(一般用于实例或具体的函数)
比如:
p1.constructor就是谁构造了p1,输出为function Person(){ //.....}
再比如 Function.prototype.constructor ,意思就是Function是谁的原型,则答案自然指向Function。
每个对象都有constructor属性?Why?
大家学到这里可能又有疑问了:constructor属性只存在于prototype,而prototype只存在于函数,那么为什么每个对象都有constructor属性呢?
其实,并不是所有对象都拥有constructor属性,而是所有对象都能访问constructor属性
所有对象的原型链最终都能追溯到Object.prototype,而Object.prototype.constructor值为Function Object(){},当我们访问对象的constructor属性时,引擎沿着原型链向上查找,最终查找到Object.prototype,得到了其属性,返回了值,所以我们说每个对象都有constructor 属性。
总结
所有对象都拥有
__proto__以及constructor属性(实际不是拥有,只是能访问constructor)。
prototype只有函数会有,其作用是创建公用库,使得继承它的函数能够使用库里的属性和方法。
__proto__属性的作用就是访问创建该对象的构造函数的prototype,并且使得引擎访问对象属性时沿着原型链向上查找一直到null
顺嘴一提:for in 循环就是查找key用的,查找属性时会沿着原型链查找
OK,这一期就是这样了,如果这篇文章对你有帮助,请动动小手点个赞吧!如果有错误,请各位大佬指出呀!