一、问题呈现
先看一段代码:
let a ={
fn(){console.log(this)}
};
(a['fn'] || function (){})() // window
let a ={
fn(){console.log(this)}
};
(a['fn'])() // a
逻辑或运算符||到底是怎样运作的?为什么会影响函数的this?
二、初步解惑
《现代JavaScript 教程》中“逻辑运算符”一章节中写道:
给定多个参与或运算的值:
result = value1 || value2 || value3;或运算符
||做了如下的事情:
- 从左到右依次计算操作数。
- 处理每一个操作数时,都将其转化为布尔值。如果结果是
true,就停止计算,返回这个操作数的初始值。- 如果所有的操作数都被计算过(也就是,转换结果都是
false),则返回最后一个操作数。返回的值是操作数的初始形式,不会做布尔转换。
注意:返回值是操作数的初始值(original value)。即调用了该函数的的valueOf()返回原始值,影响了this。
代码示例稍微变一下,以便进一步说明。
function foo(){console.log(this)}
let a ={
fn: foo
};
(a.fn || function (){})() // window
// 等价于
a.fn.valueOf()() // window
经过「逻辑或运算符」运算之后,返回值变为函数初始值,也就是foo函数,而不是a.fn。
虽然a.fn指向foo函数,但a.fn()和foo()中的this是不一样的,这也就是开篇的代码中「逻辑或运算符」影响函数的 this 的原因了。
三、深入挖掘
「操作数的初始值」,或者说「函数的初始值」到底是什么意思?
let b={
f2(){console.log(this)}
}
let a ={
f1: b.f2
};
(a.f1 || function (){})() // ???
如果这个题你能答对,那么恭喜你过关了!
控制台输出结果还是 window。
因为a.f1和b.f2在内存中,共同指向同一个函数,逻辑或运算符返回的就是这个函数,比如说命名为foo函数。同样的函数体,但是foo()、a.f1()、b.f2()函数执行时,函数体内的this各不一样。
| 调用方式 | 函数体内的this |
|---|---|
foo() | window |
a.f1() | a |
b.f2() | b |
四、解决办法
还原函数调用时常规的this,避免逻辑或运算符返回函数初始值的问题。
// 问题代码 1
(a.f1 || function(){})()
// 方法 1
a.f1 && a.f1()
// 方法 2
(a.f1 || function(){}).call(a)
// 问题代码 2
(a.f1 || b.f2)(a)
// 方法 1
(a.f1 && a.f1())|| (b.f2 && b.f2())
// 方法 2
if(a.f1 instanceof Function){
a.f1()
}else if(b.f2 instanceof Function){
b.f2()
}
五、总结
JavaScript 逻辑或运算符 ||在短路逻辑的基础上,返回的是操作数的初始值。 尤其其中涉及到函数的时候要注意,返回的是函数的初始值,这样函数调用时的this就指向了 window。
比如(a.f1 || f2)(),假设a.f1存在且为函数,则调用中的this指向window。
解决办法有:
- 用
call或apply显示地声明函数调用的this。 - 用
if语句或其他方式,代替(a.f1||f2)()这种调用形式。