ES范例解读之赋值操作符&属性接见器

ES范例解读之赋值操纵符&属性接见器

原文:https://github.com/kuitos/kuitos.github.io/issues/24
事变起源于某天某妹子同事在看angular文档中关于Scope的申明Understanding Scopes(原文) 明白angular作用域(译文)时,关于文章中的例子有一点不明白,谁人例子抽离细节以后大抵是如许的:

// 一个规范的组织函数
function Scope(){}
Scope.prototype.array = [1,2,3];
Scope.prototype.string = 'Scope';

// 天生Scope实例
var scopeInstance = new Scope();

当我们接见scopeInstance上的属性时,假如scopeInstance上不存在该属性,则js诠释器会从原型链上一层层往上找,直到找到有该属性,不然返回undefined。

// get对象上某一属性时会触发原型链查找
console.log(scopeInstance.string); // 'Scope'
console.log(scopeInstance.name); // undefined

而当我们往scopeInstance上某一属性设值时,它并不会触发原型链查找,而是直接给对象本身设值,假如对象上没有该属性则建立一个该属性。

scopeInstance.string = 'scopeInstance';
scopeInstance.array = [];
console.log(scopeInstance.string);    // 'scopeInstance'
console.log(scopeInstance.array);    // []
console.log(Scope.prototype.string); // 'Scope'
console.log(Scope.prototype.array); // [1,2,3]

总结起来,关于对象的属性的set和get操纵看上去有如许一些特征:

  1. 读(get)操纵会触发原型链查找,诠释器会从原型链一层层往上查找,直到找不到返回undefined.

  2. 写(set)操纵不会触发原型链查找,写操纵会直接在对象上举行,没有这个属性会新建一个属性。

没错,这是最基本的原型链机制,我之前一直是这么明白的,然后我也是这么跟妹子诠释的,然则文章背面的例子打了我脸。。。例子大抵是如许的:

var scope2 = new Scope();
scope2.array[1] = 1;
console.log(scope2.array); // [1,1,3]
console.log(Scope.prototype.array); // [1,1,3]

WTF!!!
根据我的明白,写操纵跟原型链无关,在对象本身操纵。
顺着这个思绪,那末 scope2.array[1]=1这行代码压根就会报错啊,由于scope2在建立array属性之前压根就没有本身的array属性啊!然则它居然没报错还把Scope.prototype给改了!
因而我又在想,是否是这类援用范例(array,object)都邑触发原型链查找,所以会涌现这个效果?
然则我又想起前面那段代码:

scopeInstance.array = [];
console.log(scopeInstance.array);    // []
console.log(Scope.prototype.array); // [1,2,3]

这下完全斯巴达了?
从表象来看,scopeInstance.array[1]的读写操纵都邑触发原型链查找,而为啥scopeInstance.array的写操纵就不会触发。假如说援用范例都邑触发,那末scopeInstace.array=[]就等价于Scope.prototype.array = [],然则现实并非如许。。。

遇到这类时刻我只要祭出神器了(ecmascript),google什么的相对不好使置信我。
翻到ecmascript关于赋值操纵符那一小节,es是如许形貌的

Simple Assignment (= )

The production AssignmentExpression : LeftHandSideExpression = AssignmentExpression is evaluated as follows:

  1. Evaluate LeftHandSideExpression.

  2. Evaluate AssignmentExpression.

  3. Call GetValue(Result(2)).

  4. Call PutValue(Result(1), Result(3)).

  5. Return Result(3).

前面三步都晓得,症结点在第四步, PutValue(Result(1), Result(3))
我们再来看看PutValue干了啥

PutValue(V, W)

  1. If Type(V) is not Reference, throw a ReferenceError exception.

  2. Call GetBase(V).

  3. If Result(2) is null, go to step 6.

  4. Call the [[Put]] method of Result(2), passing GetPropertyName(V) for the property name and W for the value.

第二步有一个GetBase(V)操纵,然后第四步依靠第二步的计算效果做终究赋值。
那末GetBase(V)终究做了什么呢(V即我们赋值操纵时刻的左值)

GetBase(V)

GetBase(V). Returns the base object component of the reference V.

翻译下来就是:返回援用V的基本对象组件。
那末什么是基本对象组件呢,举两个例子:

GetBase(this.array) => this
GetBase(this.info.name) => this.info
GetBase(this.array[1]) => this.array

我们再来看看属性接见器(Property Accessors),就是括号[]操纵符及点号.操纵符都做了什么

属性接见器(Property Accessors)

MemberExpression . Identifier is identical in its behaviour to MemberExpression [ \<identifier-string\> ]

也就是说括号跟点号对诠释器而言是一样的。

The production MemberExpression : MemberExpression [ Expression ] is evaluated as follows:

  1. Evaluate MemberExpression.

  2. Call GetValue(Result(1)).

跟到GetValue

GetValue(V)

  1. If Type(V) is not Reference, return V.

  2. Call GetBase(V).

  3. If Result(2) is null, throw a ReferenceError exception.

  4. Call the [[Get]] method of Result(2), passing GetPropertyName( V) for the property name.

第四步的私有要领[[Get]]是症结:

[[Get]]

When the [[Get]] method of O is called with property name P, the following steps are taken:

  1. If O doesn’t have a property with name P, go to step 4.

  2. Get the value of the property.

  3. Return Result(2).

  4. If the [[Prototype]] of O is null, return undefined.

  5. Call the [[Get]] method of [[Prototype]] with property name P.

  6. Return Result(5).

意义很明显,[[Get]]会触发原型链查找.
我们再回到赋值操纵符的PutValue操纵,走到第四步

Call the [[Put]] method of Result(2), passing GetPropertyName(V) for the property name and W for the value.

这里的Result(2)就是GetBase(V)的效果,拿上面的例子也就是GetBase(this.array[2]) == this.array
再看看[[Put]]操纵干了什么事变:

[[Put]]

When the [[Put]] method of O is called with property P and value V, the following steps are taken:

  1. Call the [[CanPut]] method of O with name P.

  2. If Result(1) is false, return.

  3. If O doesn’t have a property with name P, go to step 6.

  4. Set the value of the property to V. The attributes of the property are not changed.

  5. Return.

  6. Create a property with name P, set its value to V and give it empty attributes.

  7. Return.

很简单,就是给对象o的属性P赋值时,o存在属性P就直接掩盖,没有就新建属性。此时无关原型链。

此时再连系我们本身的案例来看,scopeInstance.array[1]=2scopeInstance.array=[]终究都干了啥(疏忽不相关细节):

scopeInstance.array[1]=2

  1. GetBase(scopeInstance.array\[1\]) == scopeInstance.array

  2. GetValue(scopeInstance.array) => 触发scopeInstace.array的[[Get]]要领,此时触发原型链查找 => 找到 Scope.prototype.array

  3. 设值操纵 Scope.prototype.array.\[Put];

scopeInstance.array=[]

  1. GetBase(scopeInstance.array) == scopeInstance

  2. GetValue(scopeInstance) => scopeInstance object

  3. 设值操纵 scopeInstance.\[Put];

圆满诠释一切征象!

假如思索的比较深切的同砚可能会问,scopeInstance又从哪儿取来的呢?也是相似原型链如许一层层往上查出来的么?这涉及到另一点学问,js中的作用域,详细能够看我的另一篇文章一道js面试题激发的思索

    原文作者:kuitos
    原文地址: https://segmentfault.com/a/1190000003785334
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞