JS「逻辑或运算符」居然影响函数调用中的 this?!

118 阅读2分钟

一、问题呈现

先看一段代码:

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.f1b.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

解决办法有:

  • callapply显示地声明函数调用的this
  • if语句或其他方式,代替(a.f1||f2)()这种调用形式。