function test() {
var obj = {
foo: function () { console.log(this.bar) },
bar: 1,
};
var foo = obj.foo;
bar = 2;
foo();
console.log(this.bar);
}
new test();
最后要说道 this.bar 对应的是 Property Accessors 就是那个 . 的作用,它要求对左边的表达式求值,也就是这里的 this 表达式
this 表达式的求值对应的是 ResolveThisBinding,它要从当前环境开始(包括当前环境)不断看 outer 环境是否存在 this 绑定,返回第一个有 this 绑定的环境,然后取那个环境中的 this 绑定作为求值结果。对于例子来说,只有遍历到顶层的环境才会返回,一方面是因为直到顶层都没有 this 绑定,另一方面因为顶层环境总是有 this 绑定
首先,我们可以明确的是这种情况下foo函数里面的this一定是指向全局对象的,在node里是global,在浏览器中是window 我们发现在node里面输出的是undefined,这就说明bar不在全局对象上 为什么会这样呢?回想一下var声明的变量只有全局和函数作用域,如果我们直接写成bar=2;没有var修饰那将一定是在全局作用域,改成这种写法后无论是node还是浏览器运行结果都是2了。 再深挖一下,如果我们在最后添加一行console.log(this.bar);会发现浏览器还是输出2,node又输出undefined了,这说明在这个地方node环境this并不是指向全局对象,因为我们已经证明全局对象上存在bar了而这里没有,这又是为什么?这时候this指向谁?
因为node中一个js文件就是一个模块,实际上你的代码在node中执行时至少是放在一个像这样的函数中的,这就很好的解释了为什么在node运行时var修饰的bar没有出现在全局对象上 如果你的代码写成这样:
在node和浏览器的运行结果就是相同的了,最后那个奇怪的this指向问题也得到了解答 最后,请各位大佬不要拿这种问题来做面试题!!!
@zengming00 非常感谢,谢谢前辈解惑~
我根据 语言标准 来做一个补充
foo = obj.foo对应的执行流程通过 赋值操作符(AssignmentOperator) 来解释赋值操作符
=规定引擎要对右侧的赋值表达式进行求值。求值的结果通过一个引擎内部实现使用的类型 Reference 来表示,需要补充的是 Reference 并不一定在引擎内部存在实体映射,因为它只是语言标准中为了方便描述而提出的数据结构Reference 有 base 和 referenced name 两个字段,针对例子
obj.foo的执行结果就是Reference{base: obj, referencedName: 'foo'}注意接下来的步骤,赋值操作符继续规定,要求对上一步的求值结果进一步做
GetValue(rref)操作GetValue(Reference{base: obj, referencedName: 'foo'})的结果就是返回了一个函数对象(不是 Reference 了)再看
foo()对应的描述是 调用表达式(CallExpression) 的执行过程,调用表达式的组成为两部分:MemberExpression就对应例子里foo()中的foo,标准规定要对 MemberExpression 进行求值,除了求值以外,这一步要需要准备一个变量thisValue:MemberExpression的求值结果 v 是 Reference 类型的话,那么就让thisValue等于GetThisValue(v)thisValue就是undefined。对应这里的例子,此时thisValue就是undefined了当然 Arguments 也是要求值的,但是这里就跳过了。最后的完整的函数调用形式,可以看成是这样:
没错,按标准的描述,就是
foo.call(thisValue, argumentsList)的意思,那换成例子到这里就是foo.call(undefined)继续
foo.call对应的是 [[Call]] ( thisArgument, argumentsList),可以理解成引擎内部的一个方法,它内部又会调用 OrdinaryCallBindThis 用于将thisArgument绑定到当前的执行环境最后要说道
this.bar对应的是 Property Accessors 就是那个.的作用,它要求对左边的表达式求值,也就是这里的this表达式this表达式的求值对应的是 ResolveThisBinding,它要从当前环境开始(包括当前环境)不断看 outer 环境是否存在 this 绑定,返回第一个有 this 绑定的环境,然后取那个环境中的 this 绑定作为求值结果。对于例子来说,只有遍历到顶层的环境才会返回,一方面是因为直到顶层都没有 this 绑定,另一方面因为顶层环境总是有 this 绑定所以例子在浏览器和 node 表现差异就在于
var bar = 2有没有被绑定到顶层环境一时看不懂也没事,以后想深入的话再回头看,或者也可以直接看上面给出的链接中的原文
@hsiaosiyuan0 感谢大佬,没想到一个this居然水这么深
@hsiaosiyuan0 👍
问题一:this 指向
记住:
比如:
这一点非常重要,也很简单。但往往被误以为很复杂,而且讲得也很复杂。但,很简单。
下面一点不怎么重要,但对你的问题的解答,还是有帮助的。
问题二:全局变量的归属
浏览器环境
假设有这段代码:
记住:
所以,在浏览器中,你的代码:
一般人不会这么写代码,或者说,一般人不会使用全局变量
因为这样非常容易出错
但我们经常一不小心,就声明一个不起眼全局变量,这个只能用“仔细”来避免
所以,node 干脆不让你“一不小心”声明全局变量
node 环境
在浏览器里,你可以通过
var bar = 2来声明一个全局变量(而且,必须在任何函数之外)而在 node 里,你只能通过
global.bar来声明(在任何地方都行)所以你的
var bar = 2不被赋给 global 变量所以,foo 里的输出,啥也没有(undefined)
node 控制台
不重要,不说了