從 jQuery 源碼中學(xué)到一個(gè)有意思的設(shè)計(jì)模式
大家好,我是 ??ConardLi?
? ,今天發(fā)現(xiàn)個(gè)有意思的事。
世界上最大的電商網(wǎng)站 ??amazon?
? 居然還在用 ??jQuery?
? 。
好奇的我又去翻了翻 ??jQuery?
? 的源碼,發(fā)現(xiàn)了下面這個(gè)奇妙的寫法:
var elemData = initialValue
elemData.events = elemData = function(){};
elemData.events = {};
為了簡單理解,這里省略了很多代碼,完整源碼:http://code.jquery.com/jquery-1.4.3rc1.js
初看還有點(diǎn)奇怪, ??elemData.events?
? 為啥被賦值了兩次?后面的賦值肯定會(huì)把前面覆蓋掉???這怕不會(huì)是個(gè) Bug 吧?
仔細(xì)想了下不對, ??jQuery?
? 都已經(jīng)穩(wěn)定運(yùn)行十幾年了,哪還來的 ??Bug?
? ?下面我們仔細(xì)分析下...
賦值操作也屬于表達(dá)式
給變量賦值是我們代碼里最常見的寫法,但是你可能會(huì)忽略一點(diǎn),賦值也屬于一種表達(dá)式,這種表達(dá)式計(jì)算的值是賦值右側(cè) ( ??RHS?
? ) 的值。比如下面的代碼:
let x
if(x = 1) { // 1 is truthy
console.log(1) // 1
}
而且賦值運(yùn)算符 ??=?
? 是右結(jié)合的:
let a, b
a = b = 2 // the same as a = ( b = 2)
console.log(a) // 2
console.log(b) // 2
運(yùn)算符的優(yōu)先級
回到前面那段令人費(fèi)解的 ??jQuery?
? 代碼
elemData.events = elemData = function(){};
它包含兩種運(yùn)算符:兩個(gè)賦值運(yùn)算符和一個(gè)屬性訪問運(yùn)算符( ??elemData.events?
? )。
如果我們的一段代碼里有不同類型的運(yùn)算符, ??運(yùn)算符優(yōu)先級表?
? 會(huì)決定了哪種類型的運(yùn)算符優(yōu)先運(yùn)算。
運(yùn)算符優(yōu)先級表:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#table
從 ??運(yùn)算符優(yōu)先級表?
? 中我們得知:屬性訪問運(yùn)算符的優(yōu)先級為 ??18?
? ,而賦值運(yùn)算符只有 ??2?
? ,這意味著屬性訪問運(yùn)算符的優(yōu)先級高于賦值運(yùn)算符。
比如 ??obj.name = 'ConardLi'?
? 這段代碼,計(jì)算的第一個(gè)表達(dá)式是 ??obj.name?
? ,解析為對 ??name?
? 屬性的引用,然后才是賦值操作。
剖析代碼
把前面提到的兩個(gè)知識點(diǎn)融合一下,我們在回顧下這段代碼:
var elemData = initialValue // 1
// ...
elemData.events = elemData = function(){}; // 2
// ...
elemData.events = {}; // 3
elemData
initialValue
elemData.events
elemData = function(){}elemData
function (){}
initialValue.events = function(){}
elemData
initialValue
- 第 1 行非常簡單。
- 第 2 行:
- 第 3 行:(new) ?
?elemData.events?
? 屬性指向 ??{}?
?
可以看看下面這種圖的總結(jié):
這也讓我回想起 ??for in?
? 循環(huán),當(dāng)我們在循環(huán)進(jìn)行到一半時(shí)改變對象的綁定(即給變量重新賦值),被枚舉的屬性不會(huì)直接改變,而是會(huì)等到循環(huán)結(jié)束后再變:
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 }
實(shí)際應(yīng)用
這個(gè)模式挺巧妙的,其實(shí)我們很多業(yè)務(wù)場景的代碼都可以利用下這種寫法,比如我們可以來實(shí)現(xiàn)個(gè)鏈表:
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))
可以看下面這張圖,其實(shí)是一樣的道理: