什么是原型
举个例子
let a = [1,2,3]
可以看到,有一个属性是
[[ProtoType]]: Array(0),这行代码相当于a有一个属性__proto__,
是不是和上面
[[ProtoType]]: Array(0)展开的内容一致
也就是
a.__proto__ === Array.prototype ,这个__proto__称为隐式原型,prototype称为显示原型。
所有的引用类型(函数,数组,对象)都具有__proto__属性,但函数比较特殊,除了有__proto__属性之外还有prototype属性
隐式原型与显示原型之间的关系
- 隐式原型(
__proto__) :每个引用类型实例都有的属性,指向它的原型对象,是实例查找原型的 “通道”。 - 显式原型(
prototype) :只有函数(如Array、Object这些构造函数)才有的属性,指向它创建的实例的原型对象。
什么是原型链
可以发现
a.__proto__和A.prototype也属于对象,所以他们也有__proto__属性
a.__proto__指向A.prototype,而A.prototype指向了Object.prototype所有普通对象的原型默认指向 Object.prototype(这是 JavaScript 原型链的顶层之一),也就是等同于a.__proto__.__proto__ === Object.prototype,这种链式结构,就是 原型链
- 当调用某种方法或者查找某种属性时,首先会在自身调用和查找,如果自身并没有该属性或方法,则回去它的
__proto__属性中调用查找,也就是构造函数的prototype中调用查找,还没找到就继续向上查找,以此类推 通过这种特性可以实现原型的继承。
提出一个问题帮助理解: 下图中为什么p.b = b,p.a = undefined
这个问题是原型链的简单问题
稍微进阶一下 为什么 p.b = 'b',p.a = undefined?
假设图示中的代码结构如下(符合原型链常规场景):
// 1. 定义构造函数 Person,原型上有属性 a = 'a'
function Person() {}
Person.prototype.a = 'a'; // 原型上的属性 a
// 2. 创建实例 p
let p = new Person();
// 3. 给 p 赋值属性 b
p.b = 'b';
2. 为什么 p.b = 'b'?(赋值逻辑)
当给实例赋值时,JavaScript 不会去原型链上查找,而是直接在实例自身添加该属性(无论原型上有没有同名属性)。
- 这里
p自身原本没有b属性,执行p.b = 'b'后,会直接在p上新增b属性,所以p.b能拿到 'b'。
3. 为什么 p.a = undefined?(读取逻辑)
当读取实例属性时,会先查自身,再顺着原型链找:
-
第一步:先看
p自身有没有a属性?根据代码,p是新创建的实例,自身没有定义a,所以继续找; -
第二步:顺着
p.__proto__找Person.prototype,发现原型上有a = 'a',按理说应该返回 'a'? -
关键矛盾点:如果图示中 p.a 是 undefined,说明原型链的某个环节断了,最可能的原因是:
- 场景 1:
Person.prototype被重新赋值成了空对象(如Person.prototype = {}),导致原型上的a属性丢失; - 场景 2:实例
p是直接用let p = {}创建的普通对象,没有关联Person.prototype,所以找不到a。
- 场景 1:
本质总结:赋值属性只影响实例自身,读取属性才会走原型链;读取不到时返回 undefined,要么是自身和原型链都没有该属性,要么是原型链被修改导致属性丢失。