Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS原型链、__proto__ 和 prototype #14

Open
HarleyWang93 opened this issue Apr 7, 2018 · 0 comments
Open

JS原型链、__proto__ 和 prototype #14

HarleyWang93 opened this issue Apr 7, 2018 · 0 comments

Comments

@HarleyWang93
Copy link
Owner

不知道你有没有想过这样一些问题:

为什么我定义一个数组,它就有 push、join、pop、shift 等方法,我明明什么也没写啊?
为什么我定义一个函数,它就有 call、apply、length 等属性/方法,我也什么都没有做呀?!
为什么我定义一个对象,它就有 toString、valueOf 等方法,我更是什么都没有做呀?

我们先来说说对象

当我们定义一个对象时

var obj = {}

proto

我们发现 obj 下面有一个属性名叫__proto__,(它是个对象)

obj.__proto__里面又有很多属性,包括 valueOf、toString、constructor 等。当我们需要找obj.valueOf这个属性时,发现 obj 本身没有没有,那么它就会去查找obj.__proto__是否有这个属性,如果还没有,它去找obj.__proto__.__proto__,直到找到这个属性或 null 为止,在这个读取属性的过程中,是沿着__proto__组成的链子来搜索的,这个链子我们称为原型链

如果 obj 自身定义了一个 valueOf 属性,那么它找到自身的 valueOf 之后就不再沿着__proto__来找,因为已经找到了,没有必要继续找了,也就是说

  • 新增的属性不会沿着__proto__查找

  • 读取属性会沿着__proto__,直到找到这个属性,或者是 null 为止。

那么obj.__proto__到底是什么呢?

__proto__是一个简单的访问器属性,它总是指向它的构造函数的 prototype ,即原型对象。

所有的对象都继承了Object.prototype的属性和方法,
它们可以被覆盖(除了以null为原型的对象,如 Object.create(null))。
例如,新的构造函数的原型覆盖原来的构造函数的原型,提供它们自己的 toString() 方法.。对象的原型的改变会传播到所有对象上,除非这些属性和方法被其他对原型链更里层的改动所覆盖。

所有的对象会动态生成一个__proto__指向它构造函数的原型 ( prototype )

当我们去查找obj.valueOf这个属性时,他会沿着原型链去查找obj.__proto__.valueOf,而obj.__proto__指向obj.constructor.prototype。即

obj.__proto__ === obj.constructor.prototype  // true
obj.__proto__.toString=== obj.constructor.prototype.toString  // true

我们知道 obj 的构造函数就是 Object,那么我们也可以这么写

obj.__proto__ === Object.prototype  // true
obj.__proto__.toString === Object.prototype.toString  // true

对于数组

以 push 方法为例,我们知道当我们定义一个空数组时,我们可以直接调用 push 方法,根据上面的解释,他会沿着这个数组的__proto__去查找这个方法。

var array = []
array.push === array.__proto__.push // true

array.__proto__指向它构造函数的prototype,那么

array.__proto__.push === array.constructor.prototype.push // true
array.__proto__.push === Array.prototype.push  // true

终于找到 push 方法了。

问题来了,我们知道 array 也是对象,那么 JS 是怎么知道 array 也是对象的呢?

  • 通过__proto__

我们先看Array.__proto__指向谁

Array.__proto__ === Function.prototype   // true

你也许会纳闷,怎么Array.__proto__指向的怎么是函数的原型对象呢?因为 Array 的构造函数就是函数,可以通过console.log(Array.constructor)验证。而函数的原型对象的__proto__最终指向 Object

Function.prototype.__proto__ === Object.prototype  // true
Array.__proto__.__proto__ === Object.prototype  // true

或者我们也可以这样写

Array.prototype.__proto__ === Object.prototype  // true

最终归宿都是 Object。

至此我们看下一个小小的 array 都经历了什么

array.__proto__  ----> Array.prototype ----> Array.prototype.__proto__---->Object.prototype

这样也就不难理解为什么数组(函数)也是个对象了。

一切皆对象?

也许你会迷惑,既然Array.__proto__.__proto__ = Object.prototype,那么

Number.__proto__.__proto__ === Object.prototype   // true
Boolean.__proto__ .__proto__ === Object.prototype   // true
String.__proto__.__proto__ === Object.prototype   // true

为什么我们不可以说数字/布尔/字符串也是对象呢?

这要看这个数字/布尔/字符串是怎么创建的了。

以数字为例

var a = 1
var b = new Number(1)

我们知道基本类型是没有属性的,即便可以访问到这个属性,也是访问的临时对象的属性,访问完就销毁了,即使你发现a.__proto__.__proto__指向是 Object,也是 new Number 指向的,跟 a 没有半毛钱关系,因为 a 就是个 number。

b 就不一样了,b 是构造函数 Number 构造出来的一个对象,只不过他的值是 1,它可是有__proto__属性的,那么 b 就可以愉快的指来指去了。

b.__proto__.__proto__ === Object.prototype  // true

所以 a 是一个 number,而 b 是一个 object。

同理字符串和布尔也是如此。

最后,我们来一起念一遍 JS 的七种数据类型:

number , string , boolean , undefined , null , object , symbol

那么 JS 一切皆对象 的说法不攻自破了。

推荐阅读

什么是 JS 原型链?

Object.prototype.proto

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant