摘要:那么什么是基础对象组件呢,举两个例子我们再来看看属性访问器,就是括号操作符及点号操作符都做了什么属性访问器也就是说括号跟点号对解释器而言是一样的。
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操作看上去有这样一些特性:
读(get)操作会触发原型链查找,解释器会从原型链一层层往上查找,直到找不到返回undefined.
写(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是这样描述的
The production AssignmentExpression : LeftHandSideExpression = AssignmentExpression is evaluated as follows:
Evaluate LeftHandSideExpression.
Evaluate AssignmentExpression.
Call GetValue(Result(2)).
Call PutValue(Result(1), Result(3)).
Return Result(3).
前面三步都知道,关键点在第四步, PutValue(Result(1), Result(3))
我们再来看看PutValue干了啥
If Type(V) is not Reference, throw a ReferenceError exception.
Call GetBase(V).
If Result(2) is null, go to step 6.
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). 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 [
]
也就是说括号跟点号对解释器而言是一样的。
The production MemberExpression : MemberExpression [ Expression ] is evaluated as follows:
Evaluate MemberExpression.
Call GetValue(Result(1)).
...
跟到GetValue
GetValue(V)If Type(V) is not Reference, return V.
Call GetBase(V).
If Result(2) is null, throw a ReferenceError exception.
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:
If O doesn"t have a property with name P, go to step 4.
Get the value of the property.
Return Result(2).
If the [[Prototype]] of O is null, return undefined.
Call the [[Get]] method of [[Prototype]] with property name P.
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]]操作干了什么事情:
When the [[Put]] method of O is called with property P and value V, the following steps are taken:
Call the [[CanPut]] method of O with name P.
If Result(1) is false, return.
If O doesn"t have a property with name P, go to step 6.
Set the value of the property to V. The attributes of the property are not changed.
Return.
Create a property with name P, set its value to V and give it empty attributes.
Return.
很简单,就是给对象o的属性P赋值时,o存在属性P就直接覆盖,没有就新建属性。此时无关原型链。
此时再结合我们自己的案例来看,scopeInstance.array[1]=2跟scopeInstance.array=[]究竟都干了啥(忽略不相关细节):
scopeInstance.array[1]=2GetBase(scopeInstance.array[1]) == scopeInstance.array
GetValue(scopeInstance.array) => 触发scopeInstace.array的[[Get]]方法,此时触发原型链查找 => 找到 Scope.prototype.array
设值操作 Scope.prototype.array.[Put];
scopeInstance.array=[]GetBase(scopeInstance.array) == scopeInstance
GetValue(scopeInstance) => scopeInstance object
设值操作 scopeInstance.[Put];
完美解释所有现象!
如果思考的比较深入的同学可能会问,scopeInstance又从哪儿取来的呢?也是类似原型链这样一层层往上查出来的么?这涉及到另一点知识,js中的作用域,具体可以看我的另一篇文章一道js面试题引发的思考
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/86004.html
摘要:可迭代对象就具有属性,它是一种与迭代器密切相关的对象。它通过指定的函数可以返回一个作用于附属对象的迭代器。迭代器特点每次调用方法时,返回一个数组,数组中两个元素,分别表示键和值。示例之输出输出输出之迭代器特点返回集合中存在的每一个键。 Iterator由来 不推荐Iterator方法。 Iterator 函数是一个 SpiderMonkey 专有特性,并且会在某一时刻被删除。有一点,需...
摘要:深入系列第六篇,本篇我们追根溯源,从规范解读在函数调用时到底是如何确定的。因为我们要从规范开始讲起。规范类型包括和。下一篇文章深入之执行上下文深入系列深入系列目录地址。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。 JavaScript深入系列第六篇,本篇我们追根溯源,从 ECMAScript5 规范解读 this 在函数调用时到底是如何确定的。 前言 在《JavaScript...
摘要:返回布尔值,表示参数字符串是否在源字符串的头部。参考语法返回一个布尔值与的全等操作符比较兼容环境把对象的值复制到另一个对象里浅拷贝定义方法用于将所有可枚举的属性的值从一个或多个源对象复制到目标对象。语法要设置其原型的对象。 一步一步似爪牙。 前言 学习es6之前我们可能并不知道es6相比es5差距在哪, 但是这并不妨碍我们站在巨人的肩膀上; 程序员就是要乐于尝鲜; 学习es6最终目的是...
摘要:对于这种疑问,我们只能求助给出官方解释后自增操作符从上的算法描述,我们能够清晰的得知,后自增操作符是先自增赋值,然后返回自增前的值,这样的一个顺序。 ES规范解读之自增操作符 原文:https://github.com/kuitos/kuitos.github.io/issues/24几个月前,不知道什么缘由跟同事讨论了起js里自增操作符(i++)的问题,现将前因后果整理出来,传于世人...
阅读 1847·2021-11-24 09:39
阅读 2054·2021-09-22 15:50
阅读 1739·2021-09-22 14:57
阅读 678·2021-07-28 00:13
阅读 1020·2019-08-30 15:54
阅读 2341·2019-08-30 15:52
阅读 2597·2019-08-30 13:07
阅读 3738·2019-08-30 11:27