好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

事件相关的优化

事件相关的优化

大部分的事件触发依赖于 用户 与浏览器的交互,但 用户 的行为是不可控的,许多交互设计上的缺陷与无法考虑到的因素会导致事件的频繁触发。

当事件处理器内部包含大量的操作,又不需要如此 快速 的响应事件时,就需要采用一些手段来限制事件处理器的执行。

事件的优化主要有两个目的:

减少不必要的 HTTP 请求 减少本机 性能 的消耗 @H_ 419 _22@

1. 交互设计@H_ 502 _26@

通过交互的设计来优化事件是最常用到的方式。

如 用户 点击 删除 后将按钮 禁止 。

    < style  >   
    .list   .item    {   dis play  :  flex ;  justify-content  :   space-between  ;  border-b ott om  :  px dashed  #4caf50  ;  padding  :  px  ;  } 
    .list   .item   .caption    {  font-weight  :   ;  } 
    .list   .item   .operates   .delete    {  border  :  px solid  rgb  ( , ,  )  ;  color  :   rgb  ( , ,  )  ;  outline  :  none ;  cursor  :  pointer ;  } 
    </ style  >  

   < div   class   =  " list "   >  
     < div   class   =  " item "   >  
       < div   class   =  " content caption "   >  
      今天 要做 的事情
       </ div  >  
       < div   class   =  " operates caption "   >  
      操作
       </ div  >  
     </ div  >  

     < div   class   =  " item "   >  
       < div   class   =  " content "   >  
      吃火锅
       </ div  >  
       < div   class   =  " operates "   >  
         < button   class   =  " delete "   >   删除    </ button  >  
       </ div  >  
     </ div  >  

     < div   class   =  " item "   >  
       < div   class   =  " content "   >  
      和小姐姐聊天
       </ div  >  
       < div   class   =  " operates "   >  
         < button   class   =  " delete "   >   删除    </ button  >  
       </ div  >  
     </ div  >  
   </ div  >  

   < script  >   
   var  listEle  =  document .  querySelector  (  '.list'  )  ; 
   var  deleteEle  =  document .  querySelectorAll  (  '.delete'  )  ; 

  deleteEle .  forEach  (  function  ( el )   { 
    el .  addEventListener  (  'click'  ,   function  (  )   { 
      console .  log  (  '开始 删除 ...'  )  ; 

       setTimeout  (  function  (  )   { 
         var  itemEl  =  el . parentNode . parentNode ; 

        listEle .  removeChild  ( itemEl )  ; 

        console .  log  (  ' 删除 成功'  )  ; 
       }  ,   )  ; 
     }  )  ; 
   }  )  ; 
    </ script  >  
 

上述例子没有在 用户 第一次点击后,对按钮做 一个 禁止 ,或者采用一些上锁操作。

这种情况下 用户 可能会点击多次, 删除 操作通常会发送请求到服务端做处理,不对交互做优化可能会 增加 服务端的压力。

通过给予按钮状态就可以改善这个情况。

    < style  >   
    .list   .item    {   dis play  :  flex ;  justify-content  :   space-between  ;  border-b ott om  :  px dashed  #4caf50  ;  padding  :  px  ;  } 
    .list   .item   .caption    {  font-weight  :   ;  } 
    .list   .item   .operates   .delete    {  border  :  px solid  rgb  ( , ,  )  ;  color  :   rgb  ( , ,  )  ;  outline  :  none ;  cursor  :  pointer ;  } 
    </ style  >  

   < div   class   =  " list "   >  
     < div   class   =  " item "   >  
       < div   class   =  " content caption "   >  
      今天 要做 的事情
       </ div  >  
       < div   class   =  " operates caption "   >  
      操作
       </ div  >  
     </ div  >  

     < div   class   =  " item "   >  
       < div   class   =  " content "   >  
      吃火锅
       </ div  >  
       < div   class   =  " operates "   >  
         < button   class   =  " delete "   >   删除    </ button  >  
       </ div  >  
     </ div  >  

     < div   class   =  " item "   >  
       < div   class   =  " content "   >  
      和小姐姐聊天
       </ div  >  
       < div   class   =  " operates "   >  
         < button   class   =  " delete "   >   删除    </ button  >  
       </ div  >  
     </ div  >  
   </ div  >  

   < script  >   
   var  listEle  =  document .  querySelector  (  '.list'  )  ; 
   var  deleteEle  =  document .  querySelectorAll  (  '.delete'  )  ; 

  deleteEle .  forEach  (  function  ( el )   { 
    el .  addEventListener  (  'click'  ,   function  (  )   { 
      console .  log  (  '开始 删除 ...'  )  ; 

      el .  setAttribute  (  ' dis abled'  ,   ' dis abled'  )  ; 
      el . style . color  =   'rgb(226, 174, 174)'  ; 
      el . style . borderColor  =   'rgb(226, 174, 174)'  ; 
      el . style . cursor  =   'wait'  ; 
      el . innerHTML  =   '处理中...'  ; 

       setTimeout  (  function  (  )   { 
         var  itemEl  =  el . parentNode . parentNode ; 

        listEle .  removeChild  ( itemEl )  ; 

        console .  log  (  ' 删除 成功'  )  ; 
       }  ,   )  ; 
     }  )  ; 
   }  )  ; 
    </ script  >  
 

在 用户 第一次点击按钮后,就给予按钮 禁止 点击的状态,同时通过样式区分给予 用户 一个 反馈,在提高 用户 体验的同时,优化了整个事件。

2. 事件委托(代理)@H_ 502 _26@

事件委托是利用事件冒泡的特性实现的,事件委托也被称为事件代理。

通过字面意思就可以理解,子节点的事件交给父节点来执行,一旦父节点发现子节点触发了对应的事件,就执行对应的事件处理器。

如:当点击按钮的时候, 删除 列表上的项

    < style  >   
    .list   .item    {   dis play  :  flex ;  justify-content  :   space-between  ;  border-b ott om  :  px dashed  #4caf50  ;  padding  :  px  ;  } 
    .list   .item   .caption    {  font-weight  :   ;  } 
    .list   .item   .operates   .delete    {  border  :  px solid  rgb  ( , ,  )  ;  color  :   rgb  ( , ,  )  ;  outline  :  none ;  cursor  :  pointer ;  } 
    </ style  >  

   < div   class   =  " list "   >  
     < div   class   =  " item "   >  
       < div   class   =  " content caption "   >  
      今天 要做 的事情
       </ div  >  
       < div   class   =  " operates caption "   >  
      操作
       </ div  >  
     </ div  >  

     < div   class   =  " item "   >  
       < div   class   =  " content "   >  
      吃火锅
       </ div  >  
       < div   class   =  " operates "   >  
         < button   class   =  " delete "   >   删除    </ button  >  
       </ div  >  
     </ div  >  

     < div   class   =  " item "   >  
       < div   class   =  " content "   >  
      和小姐姐聊天
       </ div  >  
       < div   class   =  " operates "   >  
         < button   class   =  " delete "   >   删除    </ button  >  
       </ div  >  
     </ div  >  
   </ div  >  

   < script  >   
   var  listEle  =  document .  querySelector  (  '.list'  )  ; 

  listEle .  addEventListener  (  'click'  ,   function  ( e )   { 
     var  el  =  e . target ; 

     if   ( el . className  ===   'delete'  )   { 
      console .  log  (  '开始 删除 ...'  )  ; 

      el .  setAttribute  (  ' dis abled'  ,   ' dis abled'  )  ; 
      el . style . color  =   'rgb(226, 174, 174)'  ; 
      el . style . borderColor  =   'rgb(226, 174, 174)'  ; 
      el . style . cursor  =   'wait'  ; 
      el . innerHTML  =   '处理中...'  ; 

       setTimeout  (  function  (  )   { 
         var  itemEl  =  el . parentNode . parentNode ; 

        listEle .  removeChild  ( itemEl )  ; 

        console .  log  (  ' 删除 成功'  )  ; 
       }  ,   )  ; 
     } 
   }  )  ; 
    </ script  >  
 

和上个小节对比 效果 ,其实是一样的,但是这份示例 代码 中只在 .list 节点上绑定了事件,而上个小节则给每个按钮绑定了 一个 事件。

其关键的就是事件对象下的 target 属性 ,该 属性 表示当前事件流最终捕获到的元素。

很明显,根据 HTML 结构, 删除 按钮就是那一分支中能捕获到的最终节点。

当事件流到达捕获阶段后,则开始向上冒泡,进入冒泡阶段,在冒泡阶段会执行绑定在 .list 上的点击事件,在事件中对事件对象的 target 进行判定,如何条件就会执行真正想做的事情,这就是 一个 事件委托的流程。

事件委托 非常适合列表相关的事件处理,假设有成千上万条的列表,这个时候每个列表的操作按钮都要绑定事件,这个消耗是非常巨大的,当列表增减还需要考虑给新列表绑定事件,给 删除 的列表注销事件,这个时候使用事件委托,只需要在列表之外的 一个 节点上绑定 一个 事件,其好处 不言 而喻。

    < style  >   
    .list   .item    {   dis play  :  flex ;  justify-content  :   space-between  ;  border-b ott om  :  px dashed  #4caf50  ;  padding  :  px  ;  } 
    .list   .item   .caption    {  font-weight  :   ;  } 
    .list   .item   .operates   .delete    {  border  :  px solid  rgb  ( , ,  )  ;  color  :   rgb  ( , ,  )  ;  outline  :  none ;  cursor  :  pointer ;  } 

    .add    {   border  :  px dashed  #4caf50  ;   font-size  :  px ;   padding  :  px px ;   margin-top  :  px ;   outline  :  none ;   cursor  :  pointer ;   }    .add  :active    {   color  :  white ;   background  :   #4caf50  ;   } 
    </ style  >  

   < div   class   =  " list "   >  
     < div   class   =  " item "   >  
       < div   class   =  " content caption "   >  
      今天 要做 的事情
       </ div  >  
       < div   class   =  " operates caption "   >  
      操作
       </ div  >  
     </ div  >  

     < div   class   =  " item "   >  
       < div   class   =  " content "   >  
      吃火锅
       </ div  >  
       < div   class   =  " operates "   >  
         < button   class   =  " delete "   >   删除    </ button  >  
       </ div  >  
     </ div  >  

     < div   class   =  " item "   >  
       < div   class   =  " content "   >  
      和小姐姐聊天
       </ div  >  
       < div   class   =  " operates "   >  
         < button   class   =  " delete "   >   删除    </ button  >  
       </ div  >  
     </ div  >  
   </ div  >  

   < button   class   =  " add "   >   增加 一项   </ button  >  

   < script  >   
   var  listEle  =  document .  querySelector  (  '.list'  )  ; 
   var  deleteEle  =  document .  querySelectorAll  (  '.delete'  )  ; 

  deleteEle .  forEach  (  function  ( el )   { 
    el .  addEventListener  (  'click'  ,   function  (  )   { 
      console .  log  (  '开始 删除 ...'  )  ; 

      el .  setAttribute  (  ' dis abled'  ,   ' dis abled'  )  ; 
      el . style . color  =   'rgb(226, 174, 174)'  ; 
      el . style . borderColor  =   'rgb(226, 174, 174)'  ; 
      el . style . cursor  =   'wait'  ; 
      el . innerHTML  =   '处理中...'  ; 

       setTimeout  (  function  (  )   { 
         var  itemEl  =  el . parentNode . parentNode ; 

        listEle .  removeChild  ( itemEl )  ; 

        console .  log  (  ' 删除 成功'  )  ; 
       }  ,   )  ; 
     }  )  ; 
   }  )  ; 

  document .  querySelector  (  '.add'  )  .  addEventListener  (  'click'  ,   function  (  )   { 
     var  el  =  document .  createElement  (  'div'  )  ; 

    el . className  =   'item'  ; 

    el . innerHTML  =   [ 
       '<div class="content">'  , 
         '学习'  , 
       '</div>'  , 
       '<div class="operates">'  , 
         '<button class="delete"> 删除 </button>'  , 
       '</div>'  , 
     ]  .  join  (  ''  )  ; 

    listEle .  appendChild  ( el )  ; 
   }  )  ; 
    </ script  >  
 

稍微改写一下之前的例子,不采用事件委托的方式,这个列表中新增的项点击 删除 按钮是无用的,将这个例子改成事件委托的方式:

    < style  >   
    .list   .item    {   dis play  :  flex ;  justify-content  :   space-between  ;  border-b ott om  :  px dashed  #4caf50  ;  padding  :  px  ;  } 
    .list   .item   .caption    {  font-weight  :   ;  } 
    .list   .item   .operates   .delete    {  border  :  px solid  rgb  ( , ,  )  ;  color  :   rgb  ( , ,  )  ;  outline  :  none ;  cursor  :  pointer ;  } 

    .add    {   border  :  px dashed  #4caf50  ;   font-size  :  px ;   padding  :  px px ;   margin-top  :  px ;   outline  :  none ;   cursor  :  pointer ;   }    .add  :active    {   color  :  white ;   background  :   #4caf50  ;   } 
    </ style  >  

   < div   class   =  " list "   >  
     < div   class   =  " item "   >  
       < div   class   =  " content caption "   >  
      今天 要做 的事情
       </ div  >  
       < div   class   =  " operates caption "   >  
      操作
       </ div  >  
     </ div  >  

     < div   class   =  " item "   >  
       < div   class   =  " content "   >  
      吃火锅
       </ div  >  
       < div   class   =  " operates "   >  
         < button   class   =  " delete "   >   删除    </ button  >  
       </ div  >  
     </ div  >  

     < div   class   =  " item "   >  
       < div   class   =  " content "   >  
      和小姐姐聊天
       </ div  >  
       < div   class   =  " operates "   >  
         < button   class   =  " delete "   >   删除    </ button  >  
       </ div  >  
     </ div  >  
   </ div  >  

   < button   class   =  " add "   >   增加 一项   </ button  >  

   < script  >   
   var  listEle  =  document .  querySelector  (  '.list'  )  ; 

  listEle .  addEventListener  (  'click'  ,   function  ( e )   { 
     if   ( e . target . className  ===   'delete'  )   { 
       var  el  =  e . target ; 

      console .  log  (  '开始 删除 ...'  )  ; 

      el .  setAttribute  (  ' dis abled'  ,   ' dis abled'  )  ; 
      el . style . color  =   'rgb(226, 174, 174)'  ; 
      el . style . borderColor  =   'rgb(226, 174, 174)'  ; 
      el . style . cursor  =   'wait'  ; 
      el . innerHTML  =   '处理中...'  ; 

       setTimeout  (  function  (  )   { 
         var  itemEl  =  el . parentNode . parentNode ; 

        listEle .  removeChild  ( itemEl )  ; 

        console .  log  (  ' 删除 成功'  )  ; 
       }  ,   )  ; 
     } 
   }  )  ; 

  document .  querySelector  (  '.add'  )  .  addEventListener  (  'click'  ,   function  (  )   { 
     var  el  =  document .  createElement  (  'div'  )  ; 

    el . className  =   'item'  ; 

    el . innerHTML  =   [ 
       '<div class="content">'  , 
         '学习'  , 
       '</div>'  , 
       '<div class="operates">'  , 
         '<button class="delete"> 删除 </button>'  , 
       '</div>'  , 
     ]  .  join  (  ''  )  ; 

    listEle .  appendChild  ( el )  ; 
   }  )  ; 
    </ script  >  
 

新增的项目是不需要再重新绑定事件的。

3. 事件节流@H_ 502 _26@

事件节流用于控制事件触发的最小间隔。

如 一个 事件 100 毫秒内只能触发一次。

如窗口缩放过程中对 页面 的元素大小重新调整,因为 resize 事件的触发是非常快的, 用户 虽然在频繁的变更窗口尺寸,但 用户 单位时间内能感知到的事情是有限的,也许一秒内执行100次尺寸计算和一秒钟内执行10次尺寸计算,感知上是没有太大区别的,而且事件内有太多的操作,又在频繁触发事件,这样很容易造成浏览器的卡顿。

    < style  >   
    .outer    {   position  :  fixed ;   top  :   ;   left  :   ;   right  :   ;   b ott om  :   ;   background  :  rgb  ( , ,  )  ;   } 
    .outer   .text    {   position  :  absolute ;   top  :   ;   left  :   ;   transform  :   translate  ( -, - )  ;   color  :  white ;   font-size  :  px ;   text-shadow  :    px  #fff ,   px  #fff ,   px  #fff ,   px  #FF1177 ,   px  #FF1177 ,   px  #FF1177 ,   px  #FF1177 ,   px  #FF1177  ;   } 
    </ style  >  

   < div   class   =  " outer "   >  
     < div   class   =  " text "   >  100x200   </ div  >  
   </ div  >  

   < script  >   
   var  text  =  document .  querySelector  (  '.text'  )  ; 

   var   resize   =   function  (  )   { 
     var  height  =  window . innerHeight ; 
     var  width  =  window . innerWidth ; 

    text . innerText  =  width  +   'x'   +  height ; 
   }  ; 

  window .  addEventListener  (  'resize'  ,  resize )  ; 

   resize  (  )  ; 
    </ script  >  
 

可以看到,resize 事件的响应是非常快的,与之类似的还有 scroll 事件,即滚动条滚动时触发的事件。

这种情况下就可以使用节流的方式来优化事件。

    < style  >   
    .outer    {   position  :  fixed ;   top  :   ;   left  :   ;   right  :   ;   b ott om  :   ;   background  :  rgb  ( , ,  )  ;   } 
    .outer   .text    {   position  :  absolute ;   top  :   ;   left  :   ;   transform  :   translate  ( -, - )  ;   color  :  white ;   font-size  :  px ;   text-shadow  :    px  #fff ,   px  #fff ,   px  #fff ,   px  #FF1177 ,   px  #FF1177 ,   px  #FF1177 ,   px  #FF1177 ,   px  #FF1177  ;   } 
    </ style  >  

   < div   class   =  " outer "   >  
     < div   class   =  " text "   >     </ div  >  
   </ div  >  

   < script  >   
   var  text  =  document .  querySelector  (  '.text'  )  ; 
   var  timer  =   false  ; 

   var   resize   =   function  (  )   { 
     if   ( timer )   return  ;   // 判断是不是上一次事件执行完300毫秒内 

     var  height  =  window . innerHeight ; 
     var  width  =  window . innerWidth ; 

    text . innerText  =  width  +   'x'   +  height ; 

    timer  =   setTimeout  (  function   (  )   { 
      timer  =   null  ; 
     }  ,   )  ; 
   }  ; 

  window .  addEventListener  (  'resize'  ,  resize )  ; 

   resize  (  )  ; 
    </ script  >  
 

对例子做了 一个 简单的 修改 , 增加 了 timer 变量,用于存放定时器的标志值(定时器的返回值),每当事件触发时,给 timer 赋值,这个时候事件就会处于 一个 锁住的状态,直到 300 毫秒后,timer 再次被设置为 null,表示可以触发事件。

根据需求,业务逻辑执行的时机是在定时器内还是定时器外可以自由调配。

4. 事件防抖@H_ 502 _26@

例如设定间隔时间为 200 毫秒,防抖则是在事件触发后 200 毫秒再执行事件处理器。
假设 在这 200毫秒内又触发了相同事件,则取消上一次的事件,不执行事件处理器,以最后一次触发事件的时机开始,等待 200 毫秒执行事件处理器。

这种情况常用于 键盘 输入,如表单验证,关键词联想。

搜索 引擎基本都含有关键词联想 功能 ,即在输入关键词的时候,根据关键词联想相关 内容 ,为 用户 更好的命中 搜索结果 。

    < style  >   
   input   {   border  :  px solid  #eee  ;   padding  :  px px ;   min-width  :  px ;   font-size  :  px ;   height  :  px ;    dis play  :  block ;   margin  :   auto ;   outline  :  none ;   } 

    .result    {   text-align  :  center ;   } 
    </ style  >  

   < div  >  
     < input   type   =  " text "   >  

     < div   class   =  " result "   >     </ div  >  
   </ div  >  

   < script  >   
   var  input  =  document .  querySelector  (  'input'  )  ; 
   var  result  =  document .  querySelector  (  '.result'  )  ; 

  input .  addEventListener  (  'input'  ,   function  ( e )   { 
     var  val  =  e . target . value ; 

    result . innerText  =   '联想 内容 :'   +  val ; 
   }  )  ; 
    </ script  >  
 

假设 输出 的就是服务端返回的联想 内容 。

其实可以发现, 用户 在输入关键词的时候,基本是按照词组的方式进行的,在某 一个 词组输入完成前去 获取 联想 内容 其实意义不大。

如 搜索 网官方网站, 用户 可能会先连续打出 网 ,而对于事件而言,就触发了许多次,通过防抖就可以剔除这些无意义的事件触发。

    < style  >   
   input   {   border  :  px solid  #eee  ;   padding  :  px px ;   min-width  :  px ;   font-size  :  px ;   height  :  px ;    dis play  :  block ;   margin  :   auto ;   outline  :  none ;   } 

    .result    {   text-align  :  center ;   } 
    </ style  >  

   < div  >  
     < input   type   =  " text "   >  

     < div   class   =  " result "   >     </ div  >  
   </ div  >  

   < script  >   
   var  input  =  document .  querySelector  (  'input'  )  ; 
   var  result  =  document .  querySelector  (  '.result'  )  ; 
   var  timer  =   null  ; 

  input .  addEventListener  (  'input'  ,   function  ( e )   { 
     clearTimeout  ( timer )  ; 

    timer  =   setTimeout  (  function  (  )   { 
       var  val  =  e . target . value ; 

      result . innerText  =   '联想 内容 :'   +  val ; 
     }  ,   )  ; 
   }  )  ; 
    </ script  >  
 

通过定时器,来延迟执行事件处理器,每次触发事件,就取消上一次事件处理器,这样就达到了防抖的 效果 。

5. 异步加载的事件处理器@H_ 502 _26@

这个方案目前使用的比较少,其就是在事件被触发的时候,去加载远端的事件处理器,加载完毕后再执行事件处理器。

以前因为缺少模块化规范,基本看不到这种优化方案,现在因为新标准 动态import 的出现,使其非常容易融合进业务 代码 中。

目前有许多构建工具 支持 动态的 import ,利用构建工具可以非常简单的实现异步加载事件处理器。

  // 这是一份伪 代码  

 const  el  =  document .  querySelector  (  '.delete'  )  ; 

el .  addEventListener  (  'click'  ,   async   (  )   =>   { 
   try   { 
     const  event  =   await   import  (  './event/delete.js'  )  ; 

     // ... 
   }   catch   (  e  )   { 
     // ... 
   } 
 }  )  ; 
 

这么做其实优化的并不是事件本身,主要是为了减少首屏加载的 代码 体积。

6. 小结@H_ 502 _26@

事件的优化不一定要只从 代码 方面入手,还可以从其他方面,如交互上进行思考。

自定义事件 ? ?DOM 事件流

查看更多关于事件相关的优化的详细内容...

  阅读:36次

上一篇

下一篇

第1节:什么是 JavaScript    第2节:学习环境准备    第3节:调试方案    第4节:JavaScript 变量    第5节:JavaScript 数据类型    第6节:JavaScript if 语句    第7节:for 语句    第8节:JavaScript 算数运算符    第9节:JavaScript 比较运算符    第10节:JavaScript 逻辑运算符    第11节:JavaScript 函数    第12节:JavaScript 表达式    第13节:JavaScript 对象    第14节:JavaScript 字符串    第15节:JavaScript 数字    第16节:JavaScript 数组    第17节:JavaScript switch 语句    第18节:JavaScript while 语句    第19节:JavaScript 的 break 与 continue    第20节:JavaScript with    第21节:document.cookie    第22节:JavaScript Function    第23节:JavaScript Math    第24节:JavaScript Date    第25节:JavaScript RegExp    第26节:JavaScript JSON    第27节:什么是 DOM    第28节:DOM 和 JavaScript 的关系    第29节:获取和操作 DOM 节点    第30节:DOM 与事件    第31节:DOM 事件绑定    第32节:DOM 事件对象    第33节:DOM 事件流    第34节:事件相关的优化    第35节:自定义事件    第36节:表单校验    第37节:什么是 BOM    第38节:常用的 BOM 相关对象    第39节:BOM 常用属性和方法    第40节:AJAX    第41节:异常处理    第42节:三元运算符    第43节:逗号操作符    第44节:void    第45节:typeof    第46节:delete 操作符    第47节:debugger    第48节:getter & setter    第49节:new 运算符与构造函数    第50节:JavaScript 原型    第51节:JavaScript instanceof    第52节:JavaScript this    第53节:严格模式    第54节:作用域    第55节:闭包    第56节:变量提升    第57节:对象包装器    第58节:Lodash    第59节:moment    第60节:swiper    第61节:ECMAScript 6    第62节:Node.js    第63节:Babel    第64节:CSS 预处理器    第65节:代码规范    第66节:TypeScript    第67节:WebComponents    第68节:Vue、React、Angular    第69节:小程序    第70节:JavaScript 关键字    第71节:分号导致的问题    第72节:对象属性访问问题    第73节:this 使用问题    第74节:浮点数精度问题    第75节:独一无二的 NaN    第76节:避免全局污染    第77节:控制台观察对象问题    第78节:根据环境选择语言特性    第79节:相关资源