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

原型(链) #9

Open
yangdui opened this issue May 16, 2020 · 0 comments
Open

原型(链) #9

yangdui opened this issue May 16, 2020 · 0 comments

Comments

@yangdui
Copy link
Owner

yangdui commented May 16, 2020

原型(链)

[[Prototype]]

JavaScript 中的对象有一个特殊的 [[Prototype]] 内置对象。这个内置对象和原型(链)有关。

对于默认的 [[Get]] 操作来说,首先是检查对象本身是否有这个属性,如果有就使用它。假如没有这个属性,就要继续访问 [[Prototype]] 链,查看 [[Prototype]] 链上是否有这个属性([[Prototype]] 是一个对象,在 [[Prototype]] 上可能还存在 [[Prototype]] 内部属性)。

在 ECMAScript 规范中对 [[Prototype]] 内部属性描述:

所有对象都有一个叫做 [[Prototype]] 的内部属性。此对象的值是 null 或一个对象,并且它用于实现继承。一个原生属性是否可以把宿主对象作为它的 [[Prototype]] 取决于实现。所有 [[Prototype]] 链必须是有限长度(即,从任何对象开始,递归访问 [[Prototype]] 内部属性必须最终到头,并且值是 null)。从 [[Prototype]] 对象继承来的命名数据属性(作为子对象的属性可见)可以通过 get 获取,但无法用于 通过 put 写入。命名访问器属性会把 getput 请求都继承。

从规范中我们可以发现:

  1. 每一个对象都有 [[Prototype]] 内部属性。
  2. [[Prototype]] 对象值为 null 或者一个对象,并且 [[Prototype]] 链必须是有限长度。
  3. [[Prototype]] 对象就是用于继承。
  4. [[Prototype]] 对象继承的命名数据属性(作为子对象的属性可见)可以通过 get 获取,但无法通过 put 写入。
  5. 命名访问器属性会把 getput 请求都继承。

其中第四、五点,在 **<<你不知道的 JavaScript(上卷)>>**第五章有描述:

  1. 如果在 [[Prototype]] 链上层存在名为 foo 的普通数据访问属性(参见第 3 章)并且没
    有被标记为只读( writable:false ),那就会直接在 myObject 中添加一个名为 foo 的新
    属性,它是屏蔽属性。
  2. 如果在 [[Prototype]] 链上层存在 foo ,但是它被标记为只读( writable:false ),那么
    无法修改已有属性或者在 myObject 上创建屏蔽属性。如果运行在严格模式下,代码会
    抛出一个错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽。
  3. 如果在 [[Prototype]] 链上层存在 foo 并且它是一个 setter(参见第 3 章),那就一定会
    调用这个 setter。 foo 不会被添加到(或者说屏蔽于) myObject ,也不会重新定义 foo 这
    个 setter。
var obj = {};

Object.defineProperty(obj, 'a', {
  writable: true,
  value: 1
});

var res = Object.create(obj); // create 函数后面介绍

console.log(res.a); // 1
console.log(res); {}
res.a = 10;
console.log(res.a); // 10
console.log(res); {a: 10}

obj 对象的 a 属性的属性描述符 writable 为 true 时,会直接在 obj 对象本身添加 a 属性。如果 writable 为 false,会发生屏蔽,但是能取值。

var obj = {};

Object.defineProperty(obj, 'a', {
  writable: false,
  value: 1
});

var res = Object.create(obj);

console.log(res.a); // 1
console.log(res); {}
res.a = 10;
console.log(res.a); // 1
console.log(res); {}

如果 [[Prototype]] 链上有存取描述符中的 setter,会调用 setter 函数。

var obj = {};
var val = 1;

Object.defineProperty(obj, 'a', {
  get: function() {
    return val;
  }, 
  set: function (value) {
    val = value;
  }
});

var res = Object.create(obj);

console.log(res.a); // 1
res.a = 10;
console.log(res.a); // 10
console.log(val); // 10

对于是否发生屏蔽以及是否调用 setter ,在 ECMAScript 规范中的**简单赋值 = **已经规定好了,上面只是把规定表现出来了。

Object.create

Object.create 函数很常见,作用就是按照原型创建一个新对象。

Object.create ( O [, Properties] )

create 函数按照指定的原型创建一个新对象。当调用 create 函数,采用如下步骤:

  1. 如果 Type(O) 不是 ObjectNull,则抛出一个 TypeError 异常。
  2. 令 obj 为 new Object() 创建新对象的结果,这里的 Object 是标准内置构造器名。
  3. 设定 obj 的 [[Prototype]] 内部属性为 O。
  4. 如果传入了 Properties 参数并且不是 undefined,则用 obj 和 Properties 当作参数调用标准内置函数 Object.defineProperties 一样给 obj 添加自身属性。
  5. 返回 obj。

过程很简单,new Object() 创建一个新对象 obj,并设置 obj 的 [[Prototype]] 属性为 O。

所以只要设置了 [[Prototype]] 内部属性,就可以实现继承。

var proto = {
	b: 10
};

var res = Object.create(proto, {
	foo: {
		value: 1
	}
});

console.log(res) // {foo: 1, __proto__: {b: 10, ...}}

new 运算符

在函数章节介绍了 new ,这里关注 [[Prototype]] 相关的部分。

我们知道 new 运算符 最后会调用函数对象的 [[Construct]] 内置方法。

[[Construct]]

当以一个可能的空的参数列表调用函数对象 F 的 [[Construct]] 内部方法,采用以下步骤:

  1. 令 obj 为新创建的 ECMAScript 原生对象。
  2. 依照 8.12 设定 obj 的所有内部方法。
  3. 设定 obj 的 [[Class]] 内部属性为 "Object"
  4. 设定 obj 的 [[Extensible]] 内部属性为 true
  5. 令 proto 为以参数 "prototype" 调用 F 的 [[Get]] 内部属性的值。
  6. 如果 Type(proto) 是 Object,设定 obj 的 [[Prototype]] 内部属性为 proto。
  7. 如果 Type(proto) 不是 Object,设定 obj 的 [[Prototype]] 内部属性为 15.2.4 描述的标准内置的 Object 原型对象。
  8. 以 obj 为 this 值,调用 [[Construct]] 的参数列表为 args,调用 F 的 [[Call]] 内部属性,令 result 为调用结果。
  9. 如果 Type(result) 是 Object,则返回 result。
  10. 返回 obj。

只看第五、六、七点。

六七点都是设置新创建对象 obj 的 [[Prototype]]。如果 proto 不是对象,就设置为 标准内置 Object 原型对象。如果 proto 是对象,设置 [[Prototype]] 为 proto。

proto 是从第五点得来的:函数对象 F 的 "prototype" 属性。

回到创建函数对象步骤,我们只关注以下部分:

  1. 令 proto 为仿佛使用 new Object() 表达式创建新对象的结果,其中 Object 是标准内置构造器名。
  2. 以参数 "constructor"、属性描述符 {[[Value]]: F, { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true}、false 调用 proto 的 [[DefineOwnProperty]] 内部方法。
  3. 以参数 "prototype"、属性描述符 {[[Value]]: proto, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false}、false 调用 F 的 [[DefineOwnProperty]] 内部方法。

第一点,proto 是通过 new Object() 创建的对象。

第二点,在 proto 对象上添加 constructor 属性,属性描述符中的 [[Value]] 值为函数对象。

第三点,在函数对象上添加 "prototype" 属性,属性描述符中的 [[Value]] 值为 proto 对象。

new 运算符创建的对象的 [[Prototype]] 被设置为函数对象的 prototype 属性的值,而函数对象 prototype 属性值又是通过 new Object() 得到的。

new Object() 也会新建一个对象,对象的 [[Prototype]] 内部属性为标准内置的 Objectprototype 对象 (15.2.4)。也就是我们熟悉的 constructor、**toString **、**toLocaleString **、**valueOf **、hasOwnProperty、**isPrototypeOf **、**propertyIsEnumerable **。constructor 在创建函数对象中会被屏蔽,设置为创建的函数对象。

所以 new 运算符创建的对象可以使用 **toString **、**valueOf **、hasOwnProperty 等方法。

函数对象的 [[Prototype]]

函数对象的 [[Prototype]] 内部属性和 new 运算符没有任何关系。

在创建函数对象时,会内置 [[Prototype]] 属性的值:

设定 F 的 [[Prototype]] 内部属性为 15.3.3.1 指定的标准内置 Function 对象的 prototype 属性。

将 [[Prototype]] 内部属性值设为 Function 对象的 prototype 属性。Function 对象的 prototype 属性的值是我们常见的 constructor 、**toString **、**apply **、**call **、**bind **,所以函数对象可以使用这些方法。

常见的继承

引用 <<你不知道的 JavaScript (上卷)>>中的代码

function Foo(name) {
	this.name = name;
}

Foo.prototype.myName = function () {
	return this.name;
}

function Bar(name, label) {
	Foo.call(this, name);
	this.label = label;
}

Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;
Bar.prototype.myLabel = function () {
	return this.label;
}

var a = new Bar('a', 'obj a');

a.myName(); // 'a'
a.myLabel(); // 'obj a'

Object/Function

网上有这样一个例子:

Object instanceof Object // true
Function instanceof Function // true
Function instanceof Object // true
Object instanceof Function // true

答案也给出了:

先有的Object.prototype, Object.prototype构造出Function.prototype,然后Function.prototype构造出Object和Function。
Object.prototype是鸡,Object和Function都是蛋。

参考资料

ECMAScript 英文文档

ECMAScript 维基百科 中文文档

继承与原型链

Object/Function

<<你不知道的 JavaScript (上卷)>>

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

No branches or pull requests

1 participant