JS 原型链
1. 原型和原型链的基础结论
1.1 函数与对象的关系
- 函数是对象,对象都是通过函数创建的。
- 函数与对象并不是简单的包含与被包含的关系。
1.2 原型的类别
- 显示原型:
prototype
,是每个函数function
独有的属性。 - 隐式原型:
__proto__
,是每个对象都具有的属性。
1.3 原型和原型链
- 原型:一个函数可以看成一个类,原型是所有类都有的一个属性,原型的作用就是给这个类的一个对象都添加一个统一的方法。
- 原型链:每个对象都有一个
__proto__
,它指向它的prototype
原型对象;它的prototype
原型对象又有一个__proto__
指向它的prototype
原型对象,就这样层层向上直到最终找到顶级对象Object
的prototype
,这个查询路径就是原型链。
1.4 JavaScript 里最顶层的两个概念
Function
是最顶层的构造器
Function
是JavaScript
里最顶层的构造器,它构造了系统中的所有对象,包括定义对象、系统内置对象、甚至包括它自己。
Object
是最顶层的对象
- 所有对象都是继承
Object
的原型 Object
也是被Function
构造出来的
1.5 instanceof
obj instanceof F
- **常见的不够正确描述:**用来判断一个对象是否是某个构造函数的实例,比如我们创建一个函数,并且将它实例化
- 「正确的描述:」
obj.__proto__.__proto__... => F.prototype
。沿着对象obj
的原型链查找是否存在对象F.prototype
,若存在则返回true
,若查找到原型链的终点Object.prototype
仍未找到,则返回false
。
2. 经典的原型和原型链的分析
「接下来我们将主要讲解以下类别:」
2.1 函数.prototype
- **前提结论:**函数都是对象,每个函数都自带一个属性叫做
prototype
- 栗子:
image-20220110144624578
- 「详细结构图:」
image-20220110144700156
- **最终结论:**每个函数下其实有个
prototype
属性,prototype
的属性值是一个对象,这个对象默认的只有一个叫做constructor
的属性,指向这个函数本身。
2.2 对象.__proto__
- **前提结论:**每个对象都有一个隐藏的属性叫做
__proto__
- 例子:
- 「解释:」
[[Prototype]]
:是对象的一个内部属性,chrome
的引擎通过__proto__
向外暴露了这个属性。实际上它可以看作就是对象的__proto__
属性__proto__
的值:等于构造该对象的函数的prototype
属性。testObj.__proto__ === testFn.prototype
- **结论:**每个对象都有一个
__proto__
属性,指向创建该对象的函数的prototype
2.3 函数.__proto__
- **前提结论:**在
JavaScript
中,函数都是对象,是对象就有隐藏的__proto__
属性 - 「解释:」
Function
是最顶级的构造器,函数对象都是通过它构造的 - 「结论:」
函数.__proto__ === Function.prototype
2.4 函数.prototype.__proto__
- 「解释:」
函数 .prototype
,它本质上是和var obj = {}
是一样的,由new Object
创建的 - 「结论:」
函数.protype.__proto__ === Object.prototype
2.5 Object.__proto__
- 「解释:」
Object
是由顶层构造函数Function
构造的 - 「结论:」
Object.__proto__ === Function.prototype
2.6 Object.prototype.__proto__
- 「结论:」
Object.prototype
较为特殊,它是顶级对象的原型,所以它的__proto__
指向是null
。
2.7 Function.__proto__
- **解释:**函数对象都是由被顶级构造函数
Function
创建。所以Function
是被自身创建的 - 「结论:」
Function.__proto__ === Function.prototype
2.8 Function.prototype.__proto__
- **解释:**函数
prototype
,它本质上是和var obj = {}
是一样的,由new Object
创建的 - 「结论:」
Function.prototype.__proto__ === Object.prototype
「最终我们将形成一个经典的原型图:」
3. 基于原型链的继承
JavaScript
对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
遵循ECMAScript
标准,someObject.[[Prototype]]
符号是用于指向 someObject
的原型。从 ECMAScript 6
开始,[[Prototype]]
可以通过 Object.getPrototypeOf()
和 Object.setPrototypeOf()
访问器来访问。这个等同于 JavaScript
的非标准但许多浏览器实现的属性 __proto__
。
但它不应该与构造函数 func
的 prototype
属性相混淆。被构造函数创建的实例对象的 [[Prototype]]
指向 func
的 prototype
属性。「Object.prototype
」 属性表示 Object
的原型对象。
这里演示当尝试访问属性时会发生什么:
// 让我们从一个函数里创建一个对象o,它自身拥有属性a和b的: let f = function () { this.a = 1; this.b = 2; } /* 这么写也一样 function f() { this.a = 1; this.b = 2; } */ let o = new f(); // {a: 1, b: 2} // 在f函数的原型上定义属性 f.prototype.b = 3; f.prototype.c = 4; // 不要在 f 函数的原型上直接定义 f.prototype = {b:3,c:4};这样会直接打破原型链 // o.[[Prototype]] 有属性 b 和 c // (其实就是 o.__proto__ 或者 o.constructor.prototype) // o.[[Prototype]].[[Prototype]] 是 Object.prototype. // 最后o.[[Prototype]].[[Prototype]].[[Prototype]]是null // 这就是原型链的末尾,即 null, // 根据定义,null 就是没有 [[Prototype]]。 // 综上,整个原型链如下: // {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null console.log(o.a); // 1 // a是o的自身属性吗?是的,该属性的值为 1 console.log(o.b); // 2 // b是o的自身属性吗?是的,该属性的值为 2 // 原型上也有一个'b'属性,但是它不会被访问到。 // 这种情况被称为"属性遮蔽 (property shadowing)" console.log(o.c); // 4 // c是o的自身属性吗?不是,那看看它的原型上有没有 // c是o.[[Prototype]]的属性吗?是的,该属性的值为 4 console.log(o.d); // undefined // d 是 o 的自身属性吗?不是,那看看它的原型上有没有 // d 是 o.[[Prototype]] 的属性吗?不是,那看看它的原型上有没有 // o.[[Prototype]].[[Prototype]] 为 null,停止搜索 // 找不到 d 属性,返回 undefined
4. 检验
最后,我们通过四个小问题来检验一下是否真的掌握了它:
- 题目 1
var A = function() {}; A.prototype.n = 1; var b = new A(); A.prototype = { n: 2, m: 3 } var c = new A(); console.log(b.n); console.log(b.m); console.log(c.n); console.log(c.m);
请写出上面编程的输出结果是什么?
- 题目 2
var F = function() {}; Object.prototype.a = function() { console.log('a'); }; Function.prototype.b = function() { console.log('b'); } var f = new F(); f.a(); f.b(); F.a(); F.b();
请写出上面编程的输出结果是什么?
- 题目 3
function Person(name) { this.name = name } let p = new Person('Tom');
问题1: p.__proto__
等于什么?
问题2:Person.__proto__
等于什么?
- 题目 4
var foo = {}, F = function(){}; Object.prototype.a = 'value a'; Function.prototype.b = 'value b'; console.log(foo.a); console.log(foo.b); console.log(F.a); console.log(F.b);
请写出上面编程的输出结果是什么?
- 题目 1 答案:
b.n -> 1 b.m -> undefined; c.n -> 2; c.m -> 3;
- 题目 2 答案:
f.a() -> a f.b() -> f.b is not a function F.a() -> a F.b() -> b
- 题目 3 答案
答案1:Person.prototype
答案2:Function.prototype
- 题目 4 答案
foo.a => value a foo.b => undefined F.a => value a F.b => value b
大家都掌握了吗?