大家好,我是ConardLi,今天发现了一个有趣的东西。世界上最大的电子商务网站亚马逊仍然使用jQuery。出于好奇,查看了jQuery的源码,发现了如下奇葩的写法:varelemData=initialValue...elemData.events=elemData=function(){};...elemData.events={};为了简单理解,这里省略了很多代码,完整源码:http://code.jquery.com/jquery-1.4.3rc1.js乍一看有点奇怪,为什么elemData.events被赋值了两次?后面的赋值肯定会覆盖前面的?恐怕这不是错误,是吗?仔细想想,jQuery已经稳定运行了十几年,那么bug在哪里呢?下面仔细分析一下……赋值操作也是表达式。给变量赋值是我们代码中最常见的一种写法,但是你可能忽略了一点。赋值也是一个表达式。这个表达式计算出的值是赋值右边(RHS)的值。例如,下面的代码:letxif(x=1){//1istruthyconsole.log(1)//1}并且赋值运算符=是右结合的:leta,ba=b=2//同a=(b=2)console.log(a)//2console.log(b)//2运算符的优先级回到令人困惑的jQuery代码elemData.events=elemData=function(){};它包含两个运算符:两个赋值运算符和一个属性访问运算符(elemData.events)。如果我们在一段代码中有不同类型的运算符,运算符优先级表将决定哪种类型的运算符具有优先级。运算符优先级表:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#table从运算符优先级表我们知道:属性访问运算符的优先级为18,而赋值运算符只有2,这意味着属性访问运算符的优先级高于赋值运算符。比如代码obj.name='ConardLi',首先计算的表达式是obj.name,解析成name属性的引用,然后进行赋值操作。分析代码结合上面提到的两个知识点,我们来回顾一下这段代码:varelemData=initialValue//1//...elemData.events=elemData=function(){};//2//...elemData.events={};//3elemDatainitialValueelemData.eventselemData=function(){}elemDatafunction(){}initialValue.events=function(){}elemDatainitialValue第1行非常简单。Line2:Line3:(new)elemData.eventspropertypointsto{}可以看到下面这张图的总结:这也让我想起了forin循环,当我们改变对象的绑定(也就是重新赋值)变量)在循环中间,枚举属性不会直接改变,而是等到循环结束:letobj={a:1,b:2,c:3}letobj2={d:1,e:2,f:3}for(constpropinobj){console.log(prop)//a,b,cobj=obj2}console.log(obj)//{d:1,e:2,f:3}实际应用这种模式还是蛮巧妙的。其实我们可以在很多业务场景中使用这种写法。例如,我们可以实现一个链表:leti=0,root={index:i},node=rootwhile(i<10){node.next=node={}//`node`in`node.next`是旧的`node`node.index=++i//`node.index`中的`node`是新的`node`}node=rootdo{console.log(node.index)//0,1,2,3,4,5,6,7,8,9,10}while((node=node.next))可以看下图,其实是一样的:
