我们知道 JS 有对象,比如
var obj = { name: 'obj' }
我们可以对 obj 进行一些操作,包括
- 「读」属性
- 「新增」属性
- 「更新」属性
- 「删除」属性
下面我们主要来看一下「读」和「新增」属性。
原型和原型链
在我们没有对 obj
进行任何其他操作之前,发现 obj
已经有几个属性(方法)了:
也就是:
我们发现 console.dir(obj)
打出来的结果是:
-
obj 本身有一个属性
name
(这是我们给它加的) -
obj 还有一个属性叫做
__proto__
(它是一个对象) -
obj.__proto__
有很多属性,包括valueOf
、toString
、constructor
等 -
obj.__proto__
其实也有一个叫做__proto__
的属性(console.log 没有显示),值为 null
现在回到我们的问题:obj
为什么会拥有 valueOf / toString / constructor
这几个属性?
答案:
这跟 __proto__
有关。
当我们「读取」 obj.toString
时,JS 引擎会做下面的事情:
-
看看
obj
对象本身有没有toString
属性。没有就走到下一步。 -
看看
obj.__proto__
对象有没有toString
属性,发现obj.__proto__
有toString
属性,于是找到了所以
obj.toString
实际上就是第 2 步中找到的obj.__proto__.toString
。可以想象,
-
如果
obj.__proto__
没有,那么浏览器会继续查看obj.__proto__.__proto__
-
如果
obj.__proto__.__proto__
也没有,那么浏览器会继续查看obj.__proto__.__proto__.proto__
-
直到找到
toString
或者__proto__
为 null。
上面的过程,就是「读」属性的「搜索过程」。
而这个「搜索过程」,是连着由 __proto__
组成的链子一直走的。
这个链子,就叫做原型链。
共享原型链
现在我们有另一个对象
var obj2 = { name: 'obj2' }
如图:
那么 obj.toString
和 obj2.toString
其实是同一个东西,也就是 obj2.__proto__.toString
。
这有什么意义呢?
如果我们改写 obj2.__proto__.toString
,那么 obj.toString
其实也会变!
这样 obj
和 obj2
就是具有某些相同行为的对象,这就是意义所在。
差异化
如果我们想让 obj.toString
和 obj2.toString
的行为不同怎么做呢?
直接赋值就好了:
obj.toString = function(){ return '新的 toString 方法' }
总结:
-
「读」属性时会沿着原型链搜索。
-
「新增」属性时不会去看原型链(但是如果你给这个属性加了一些配置,则不一样)。
重要公式
JS 公式
对象.__proto__ === 其构造函数.prototype
根公理
Object.prototype
是所有对象的(直接或间接)原型
函数公理
所有函数都是由 Function 构造的
任何函数.__proto__ === Function.prototype
- 任意函数有
Object / Array / Function