Hokori 其实吧,有疑惑是很正常的,因为 JS 里的 this
虽然是个关键字,行为却不是固定的,跟个变量一样。你在一楼写的神奇代码我在刚入门研究 prototype 的时候也写过无数(逃。我觉得关键还是要当成科学实验,用好控制变量法。比如这个问题“console.log(this) 的输出为什么不是 console”,可以从不同的角度来一点点试:
第一组:我就要它输出 console
console.log(this);
console.log(console);
第二组:听说可以改变指向,改成 console
再试试
console.log(this);
console.log.bind(console)(this);
console.log.bind(console)(console);
这两组会发现只有传了 console
的才会输出 console
,传 this
的都输出了 window
,即使把 log
函数里的 this 指向改变了也没用。这个时候可能会发现是传参的问题,也可能没发现,那么在假设 console.log
没 bug 的情况下,有可能是 this
出了问题。把 this
包装下:
第三组:包装不同的 this
给 console.log
输出
function a() { return this; }
var b = () => { return this; };
var c = { foo: function () { return this; } };
var d = { foo: () => { return this; } };
console.log(a());
console.log(b());
console.log(c.foo());
console.log(d.foo());
这里控制的“变量”是同样的 console.log
函数,不同执行环境下的 this
。当然我们现在是假设 console.log
没有 bug ,但它的嫌疑也不能排除。因为直接在 F12 运行语句也能得到返回值,所以可以与 console.log
的效果进行对比:
第四组:不经过 console.log
直接输出(注意:由于直接运行只显示最后一句的返回值,所以要分开四次运行)
a();
b();
c.foo();
d.foo();
会发现第三组第四组得到的结果是一样的,都只有第三个 c.foo()
输出了 { foo: f }
也就是 c
的内容,其他都输出了 window
。再结合已知的知识:
箭头函数不绑定 this
,会捕获其所在的上下文的 this
值,作为自己的 this
值
猜测 a()
和 b()
得到 window
是因为它们是在 F12 里面执行,默认在 window
下,所以相当于 window.a()
和 window.b()
那么无论绑不绑定都只能指向 window
了。
而 c.foo()
没用箭头函数,说明它绑定了 this
,绑定了谁呢?它现在是 c
,有没有可能是绑定了 c
?
而 d.foo()
用了箭头函数,按道理它不会绑定 this
,但它却是个 window
,联想上面 window.b()
的结论,它也相当于 window.d.foo()
,有没有可能就是因为用了箭头函数,导致它跳过了 d
才绑定了 window
?
于是现在得到一个猜想: x.foo()
的形式会把 this
值绑定到 x
上。
那为什么 console.log(this)
没有绑定到 console
上呢?再次控制变量,观察它们的区别:
第五组:观察 this 绑定的异同
var c = { foo: function () { return this; } };
console.log(this);
console.log(c.foo());
this;
c.foo();
发现在输出语句中明确写了 this
的都输出 window
,没写的输出了 c
(作为与输出 console
对比),有没有可能是 this
关键字在代码中位置的问题?对比发现输出了 window
的都写在了函数形参上,另一个则写在了函数体内部。有可能是传参的问题!那么验证下猜想吧。
第六组:把 this
写在 console.log
里面( 1 ~ 5 还是分开一次一次地执行 )
console.log(this); /* 1 */
// 替换掉 console.log 的实现
console.a = console.log;
console.log = function () {
// 恢复原本的行为
console.a.apply(console, arguments);
// 由于在验证 console.log ,用 console.a 输出也不可靠,直接返回出去再观察 this 值
return this;
};
console.a(this); /* 2 */
console.a.bind(console)(this); /* 3 */
console.log(); /* 4 */
console.log(this); /* 5 */
可以看到对照 1 还是原本的行为,输出了 window
;对照 2 / 3 与对照 1 一致,表明 console.a
的行为还是符合原本替换前 console.log
的;对照 4 无输出并返回了 console
,对照 5 输出 window
并返回了 console
,说明写在函数内的 this
才会绑定 console
,写在形参的 this
在传参前是什么值(这里是 window
)它输出后还是什么值。
至此,我们确定了 console.log
和 this
都没有任何问题,在 console.log(this)
中不输出 console
是因为 this
在形参上时,不会被绑定到 caller (即 console
)上。验证它是个通用的结论:
const a = {
sayLog: function (x) {
console.log(x);
console.log(this);
}
}
a.sayLog(this);
撒花~~
注 1 :我没接受过正经的科研训练,请不要把本文相关思想当作真正的控制变量法。
注 2 :@0x0001 话说,可以加精吗,可以开发个加精吗(逃