文章来源
Why is (0,obj.prop)() not a method call?
正文
这篇博文探讨了references
, 一种机制用在ECMAscript语言规范中解释一下两种语法区别
obj.prop()
(0, obj,prop)()
方法调用(Method calls) vs 函数调用(Function calls)
思考下面的对象
var obj = {
getThis: function () {
"use strict";
retutn this;
}
}
如果你执行obj.getThis
, 那你得到一个方法调用(Method call)
, this
指向储存方法的对象
> obj.getThis() === obj;
true
如果你把obj.getThis
存储在一个变量里面
,那么你在执行一个函数调用(Function call)
> var func = obj.getThis;
> func()
undefined // use strict模式下禁止指向全局对象
如果你用逗号的话
会有相同的效果,就像这样
(expr1, expr2) === expr2
就是,如果两个表达式被求值了,那整个表达式执行的结果就是expr2
如果你运行逗号操作符在执行obj.getThis
之前, 你依然是在执行一个函数调用(Function call)
> (0, obj,getThis)()
undefined
第一个操作数是什么都没有关系,这里使用0
是因为它比较短, 我希望很多JS工程师回去优化并且消除第一个操作符的求值
当然,只使用括号不会改变任何事情
> (obj.getThis)() === obj
true
所以,到底发生了什么? 这个答案在于 references
References 一种数据结构在ECMAScript规范中
References 是一种在ECMAScript语言规范中内部使用的数据结构, 一个reference
包括3个部分
- Basic Value(基础值): undefined, a primitive value(原始值,原始类型),a object(一个对象) or a environment record(一个环境记录, 在函数执行的时候会生成词法环境Lexical Environment, 词法环境中有EnvironmentRecords这个字段用来记录环境,数据结构是一个对象) ,undefined表示这个变量还没有被求值, 通过
GetBase(V)
来访问, 传入一个reference V
- Referenced name(名称): string或者是一个Symbol, 通过
GetReferencedName(V)
来访问,传入一个reference V
- Strict Reference(严格模式的标志): 标识一个
Reference
是否在严格模式下创建, 通过IsStrictReference(V)
来访问
产生references的一些js表达式示例
- Prototype reference: 对
obj.prop
在严格模式下进行求值会产生reference,(obj, 'prop', true)
- Identifier reference: 对
foo
在严格模式下进行求值,(env, 'foo', true)
,env
是一个存储了foo函数的环境记录(Environment Record)
严格模式的标记是必要的,因为某些操作在严格模式下会导致异常,但是在宽松模式(sloppy mode)下是隐性的失败(fail silently), 比如 给一个未初始化的变量赋值,在宽松模式下会创建一个全局变量,在严格模式下会抛错误ReferenceError
这里是references的其中2个操作
- GetValue(V) 如果
V
是一个value,那么返回的结果就是V
,如果V
是一个reference
,那么结果是指向reference
的一个value,这种reference
转换成reference value
的操作叫做dereferenceing(解除引用)
- PutValue(V, W)把value值
W
写入reference V
中 - GetThisValue(V)只会在
V
是一个Prototype Reference
的时候运行, 对于normal reference
, 它返回base value
, 对于通过super
创建的references
, 它返回他们拥有的附加组件(additional component)thisValue
(which is needed for super property references)
References的一些例子
现在我们准备去理解之前的例子
下面的表达式产生一个reference
obj.getThis
如果你函数调用(Function call)
这个reference ref
那么this
会设置为GetThisValue(ref)
如果你用括号包裹obj.getThis
, 什么都不会改变,括号只是在句法上对食物进行分组,不会影响如何求值(它们不是操作符),这就是为什么,下面的表达式依然是一个reference
(obj.getThis)
如果你把obj.getThis
返回的reference赋值给一个变量,那么reference会被dereference
var func = obj.getThis;
换句话说,保存在func
上的是一个function
, 而不是一个reference
, 在语法标准(Lanaguage spec)中赋值运算符通过GetValue()
把references引用转化为值
逗号运算符同样可以dereferences
其操作数(operands), 思考这个表达式
(0, obj.getThis)
逗号操作符使用GetValues()
去取保结果上的每一个操作数都是被解除引用(dereferenced)
的如果是一个reference
References and bind()
References是临时的这就是为什么你需要使用bind
如果你希望在一个回调中返回一个方法(method)
var log = console.log.bind(console);
log('hello')
如果你简单的操作
var log = console.log;
那么receiver(this)
会丢失,因为console.log
被解除引用(dereferenced)
在保存为log
变量之前
References 在实际代码中
Babel使用逗号操作符避免函数调用Function call
被转换为方法调用Method call
思考在ES6模块示例
import { func } from 'some_module';
func()
Babel解析成es5代码
'use strict'
var _some_module = require('some_module');
(0, _some_module.func)();
逗号操作符手法在最后一行保证了函数调用function call
而不是方法调用method call
,this
值不会是_some_module
为什么ECMAScript规范要使用References
那些实现ECMAScript语言规范的工程师,实际上并不使用reference,这意味这reference是一个设备
去帮助他们书写规范,去看看为什么, 考虑它们代表储存的位置,然后考虑下面所有操作储存的位置
读一个值
x
obj.prop
super.prop
obj["prop"]
运行一个函数或者方法
x()
obj.prop()
super.prop()
obj["prop"]()
赋值
x = 123
obj.prop = 123
super.prop = 123
obj["prop"] = 123
复合赋值运算
x += 5
obj.prop += 5
super.prop += 5
obj["prop"] += 5
typeof:
typeof x
typeof obj.prop
typeof super.prop
typeof obj["prop"]
delete:
delete x
delete obj.prop
delete super.prop
delete obj["prop"]
因为每个储存位置都有相同的结构表示,一个reference
,标准只需要去一个版本在所有的操作中,替代了多个版本(删除变量,删除具有固定健的属性,删除动态计算的属性等)
思考
你没有真的看到reference,当你使用js的时候,但是在有些语言中references是一等值,这可以产生有趣的应用,比如可以实现一个为你执行任务的函数。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!