双赋值模式

157 阅读2分钟

这是我在 jQuery 源代码中发现的一个模式.

你可能从未听说过这种模式,因为这个名字是我编的。

在源代码中,我清楚地记得有这样写的代码(为了简单起见,我省略了很多代码。如果你感兴趣,你可以查看它的源代码):

var elemData = someInitialValue
...
elemData.events = elemData = function(){};
...
elemData.events = {};

当时我不明白为什么是 elemData.events 被赋值了两次。 因为最后一个赋值会覆盖第一个赋值。我不知道这是不是jQuery 维护者犯的一个错误。 事实证明这不是一个错误。它运用了:

  • 赋值作为表达式
  • 操作符优先级

我将在这篇文章中解释这是如何工作的。也就是说,我认为这是JavaScript语言中一个模糊的角落。尽管它看起来简洁,但您通常不需要编写这样的代码。

赋值也是一个表达式

我们每天都使用赋值来给变量赋值,但我们可能不知道,赋值除了是语句外,也是表达式。这样的表达式计算得到的值是right-hand-side(RHS)的值。

所以我们可以这样写代码:

let x
if(x = 1) { // 1 is truthy
  console.log(1) // 1
}

赋值操作符 = 是右结合的:

let a, b
a = b = 2 // the same as a = ( b = 2) 
console.log(a) // 2
console.log(b) // 2

操作符优先级

回到那个令人费解的 Jquery 代码: elemData.events = elemData = function(){};; 它包含了两种操作符: 两个赋值操作符和一个成员访问操作符

当混合使用不同类型的操作符时,操作符优先级决定了哪种类型的操作符优先。

根据操作符优先级表, 成员访问操作符(member access) 是 18,赋值操作符(assignment)是 2。所以说访问操作符优先级更高。这符合我们的直觉,比如 obj.prop = 1 ,首先执行的是 obj.prop, 解析出属性 prop 的引用,然后再赋值。

Old elemData vs new elemData

让我们重新回顾一下神秘的jQuery代码片段:

var elemData = someInitialValue // 1
// ...
elemData.events = elemData = function(){}; // 2
// ...
elemData.events = {}; // 3
  • Line 1: 首先,(old)elemData 指向 initialValue
  • Line 2: (old)elemData.events 指向 initialValue.events;然后右到左赋值,(new)elemData 重新被赋予 function(){},并丢失调之前的 initialValue 引用;最后,elemData 的值赋值给 initialValue.events,即:initialValue.events = elemData = function(){};
  • Line 3: (new)elemData.events 指向 {}

image.png

这让我想起 for in 循环: 当我们在循环中途改变对象的绑定(即,给变量重新赋值)时,被枚举的属性不会突然改变:

let obj = {a: 1, b: 2, c: 3}
let obj2 = {d: 1, e: 2, f: 3}

for(const prop in obj ) {
	console.log(prop) // a, b, c
	obj = obj2 
}

console.log(obj) // { d: 1, e: 2, f: 3 }

其他示例

可以用这个模式写一个链表:

let i = 0, root = { index: i }, node = root

while (i < 10) {
  node.next = node = {} // `node` in `node.next` is the old `node`
  node.index = ++i  // `node` in `node.index` is the new `node`
}

node = root
do {
  console.log(node.index) // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
} while ((node = node.next))

image.png

参考

  1. the double-assignment pattern in jQuery’s source code - zhenghao