<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"><channel><title>Vue：Vue.js专业中文社区</title><link>http://vue-js.com</link><language>zh-cn</language><description>Vue：Vue.js专业中文社区</description><item><title>Flutter全套教程_Flutter+Getx仿小米商城项目实战视频教程-V3版</title><link>http://vue-js.com/topic/653881f75784a80033303943</link><guid>http://vue-js.com/topic/653881f75784a80033303943</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;Flutter全套教程_Flutter+Getx仿小米商城项目实战视频教程-V3版&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2023年新出&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Dart Flutter网盘下载地址：&lt;a href=&quot;https://pan.baidu.com/s/1iaLGIyR9UmyJyHDGH6hepg&quot;&gt;https://pan.baidu.com/s/1iaLGIyR9UmyJyHDGH6hepg&lt;/a&gt;   提取码：abcd&lt;/p&gt;
&lt;p&gt;在线学习地址：&lt;a href=&quot;https://www.bilibili.com/video/BV1S4411E7LY?p=17&quot;&gt;https://www.bilibili.com/video/BV1S4411E7LY?p=17&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>phonegap100</author><pubDate>Wed, 25 Oct 2023 02:48:23 GMT</pubDate></item><item><title>Vue3.x教程_Vue3.x+Ts+Vuex+Antd Ui框架入门进阶视频教程-免费学</title><link>http://vue-js.com/topic/6538812b5784a80033303942</link><guid>http://vue-js.com/topic/6538812b5784a80033303942</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;Vue3.x教程_Vue3.x+Ts+Vuex+Antd Ui框架入门进阶视频教程分享：
Vue3.x视频 课件及源码在线学习及网盘下载地址：
&lt;a href=&quot;https://www.itying.com/goods-1159.html&quot;&gt;https://www.itying.com/goods-1159.html&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>phonegap100</author><pubDate>Wed, 25 Oct 2023 02:44:59 GMT</pubDate></item><item><title>JavaScript 中 Promise 高階面試題求問</title><link>http://vue-js.com/topic/6286edb0cd48c10031428a9f</link><guid>http://vue-js.com/topic/6286edb0cd48c10031428a9f</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h2&gt;問題:&lt;/h2&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;new Promise((resolve) =&amp;gt; {
    console.log(1)
    resolve()
}).then(async () =&amp;gt; {
    console.log(2)
}).then(async () =&amp;gt; {
    console.log(3)
})
new Promise((resolve) =&amp;gt; {
    console.log(&amp;#x27;a&amp;#x27;)
    resolve()
}).then(() =&amp;gt; {
    console.log(&amp;#x27;b&amp;#x27;)
}).then(() =&amp;gt; {
    console.log(&amp;#x27;c&amp;#x27;)
}).then(() =&amp;gt; {
    console.log(&amp;#x27;d&amp;#x27;)
}).then(() =&amp;gt; {
    console.log(&amp;#x27;e&amp;#x27;)
})
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;正解:&lt;/h2&gt;
&lt;p&gt;1
a
2
b
c
d
3
e&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;求問思路，我自己的答案應該是 1 a 2 b 3 c d e，就是 then 之後的我覺得都會放入 microtask&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;爬了很多篇文作了以下筆記，但還是不知道這題為何這樣?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;已知的是 async 如果沒有接 await 還是會回傳 Promise 物件&lt;/li&gt;
&lt;li&gt;.then 中沒有回傳值的話，還是會回傳 Promise 物件 return new Promise.resolve()&lt;/li&gt;
&lt;li&gt;.then 後的內容會被放入 microtask&lt;/li&gt;
&lt;li&gt;會先在 task 處理完同步的，再以先進先出的方式將 microtask 任務取出執行&lt;/li&gt;
&lt;li&gt;Promise 的狀態只能改一次改成 resolved 或者 rejected，前者可執行 then 後者 catch&lt;/li&gt;
&lt;li&gt;Promise 在 resolve/reject 前的內容是同步的&lt;/li&gt;
&lt;li&gt;本題貌似沒有出現會被放入 macrotask 的&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;</description><author>BoisonChang</author><pubDate>Fri, 20 May 2022 01:24:00 GMT</pubDate></item><item><title>vue3里怎么使用prerender-spa-plugin</title><link>http://vue-js.com/topic/6226ae6ecd48c10031428a78</link><guid>http://vue-js.com/topic/6226ae6ecd48c10031428a78</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;官方给到实例是vue2的
&lt;img src=&quot;https://static.vue-js.com/0456f0f0-9e7d-11ec-b69d-715259609f68.png&quot; alt=&quot;企业微信截图_16467020782042.png&quot;&gt;
在vue3里要怎么用啊 &lt;img src=&quot;https://static.vue-js.com/513f5b50-9e7d-11ec-b69d-715259609f68.png&quot; alt=&quot;企业微信截图_16467022157160.png&quot;&gt;
我把它放到App.vue里了打包不起作用&lt;/p&gt;
&lt;/div&gt;</description><author>dragonnext</author><pubDate>Tue, 08 Mar 2022 01:16:30 GMT</pubDate></item><item><title>vue开源社区项目</title><link>http://vue-js.com/topic/6219adae3046490032bbe99a</link><guid>http://vue-js.com/topic/6219adae3046490032bbe99a</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;为什么我用token登陆不了？&lt;/p&gt;
&lt;/div&gt;</description><author>getPokemon</author><pubDate>Sat, 26 Feb 2022 04:33:50 GMT</pubDate></item><item><title>面试官：你真的了解 v-model 吗？(vue2)__Vue.js__前端</title><link>http://vue-js.com/topic/61e67704ad745b003c9df797</link><guid>http://vue-js.com/topic/61e67704ad745b003c9df797</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h2&gt;阅读本文&lt;/h2&gt;
&lt;p&gt;你将:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;弄明白： &lt;code&gt;v-model&lt;/code&gt; 是什么的语法糖？ &lt;code&gt;vue2&lt;/code&gt; 对原生组件究竟做了什么特殊处理？&lt;/li&gt;
&lt;li&gt;弄明白： &lt;code&gt;v-model&lt;/code&gt; 到底是&lt;strong&gt;单向数据流&lt;/strong&gt;还是&lt;strong&gt;数据双向绑定&lt;/strong&gt;？&lt;/li&gt;
&lt;li&gt;弄明白： &lt;code&gt;v-model&lt;/code&gt; 在语法糖之外的『副作用』？&lt;/li&gt;
&lt;li&gt;学会如何让你的组件也支持 &lt;code&gt;v-model&lt;/code&gt; 语法。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;一、&lt;code&gt;v-model&lt;/code&gt; 的本质是语法糖。&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;『&lt;code&gt;v-model&lt;/code&gt; 本质上不过是语法糖。它负责监听用户的输入事件以更新数据，并对一些极端场景进行一些特殊处理。』 – 官方文档&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;什么是语法糖？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;语法糖，简单来说就是『便捷写法』。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在大部分情况下， &lt;code&gt;v-model=&amp;quot;foo&amp;quot;&lt;/code&gt; 等价于 &lt;code&gt;:value=&amp;quot;foo&amp;quot;&lt;/code&gt; 加上 &lt;code&gt;@input=&amp;quot;foo = $event&amp;quot;&lt;/code&gt;；&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;!-- 在大部分情况下，以下两种写法是等价的 --&amp;gt;
&amp;lt;el-input v-model=&amp;quot;foo&amp;quot; &amp;#x2F;&amp;gt;

&amp;lt;el-input :value=&amp;quot;foo&amp;quot; @input=&amp;quot;foo = $event&amp;quot; &amp;#x2F;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;没错，在&lt;strong&gt;大部分情况下&lt;/strong&gt;如此。&lt;br&gt;
但也有例外：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;vue2&lt;/code&gt; 给组件提供了 &lt;code&gt;model&lt;/code&gt; 属性，可以让用户自定义&lt;strong&gt;传值的prop名&lt;/strong&gt;和&lt;strong&gt;更新值的事件名&lt;/strong&gt;。这个暂且略过，第四节会细说。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于原生 &lt;code&gt;html&lt;/code&gt; 原生元素，&lt;code&gt;vue&lt;/code&gt; 干了大量『脏活儿』，目的是为了能让我们忽视 &lt;code&gt;html&lt;/code&gt; 在api上的差异性。以下元素的左右两种写法是等价的：&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;textarea&lt;/code&gt; 元素：&lt;br&gt;
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2f51a6c8b3c94a928a81b453789921f2~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;textarea&quot;&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;select&lt;/code&gt; 下拉框：
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2a7e13f5ebee470998e443ed99a5a03f~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;input type=&apos;radio&apos;&lt;/code&gt; 单选框：
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/13e59496402e49e5b09dec4a5b5dae87~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;radio&quot;&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;input type=&apos;checkbox&apos;&lt;/code&gt; 多选框：
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5bf128dd051e46fc884899566646fe6b~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;checkbox&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在编程思想上，这种帮助使用者『隐藏细节』的方式叫&lt;strong&gt;封装&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;二、v-model 仅仅是语法糖吗？(冷知识)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;v-model&lt;/code&gt; 不仅仅是语法糖，它还有副作用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;副作用如下：&lt;strong&gt;如果 &lt;code&gt;v-model&lt;/code&gt; 绑定的是响应式对象上某个不存在的属性，那么 &lt;code&gt;vue&lt;/code&gt; 会悄悄地增加这个属性，并让它响应式。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;举个例子，看下面的代码：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; template中：
&amp;lt;el-input v-model=&amp;quot;user.tel&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-input&amp;gt;
&amp;#x2F;&amp;#x2F; script中：
export default {
  data() {
    return {
      user: {
        name: &amp;#x27;公众号: 前端要摸鱼&amp;#x27;,
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;响应式数据中没有定义 &lt;code&gt;user.tel&lt;/code&gt; 属性，但是 &lt;code&gt;template&lt;/code&gt; 里却用 &lt;code&gt;v-model&lt;/code&gt; 绑定了 &lt;code&gt;user.tel&lt;/code&gt;，猜一猜当你输入时会发生什么？&lt;/p&gt;
&lt;p&gt;看效果：&lt;br&gt;
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dd32fbc4b8274d4782b1abf8b31022a6~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;副作用&quot;&gt;&lt;/p&gt;
&lt;p&gt;揭晓答案吧：
&lt;code&gt;user&lt;/code&gt; 上会新增 &lt;code&gt;tel&lt;/code&gt; 属性，并且 &lt;code&gt;tel&lt;/code&gt; 这个属性还是&lt;strong&gt;响应式&lt;/strong&gt;的。&lt;/p&gt;
&lt;p&gt;这就是『副作用』带来的效果，你学会了吗？&lt;/p&gt;
&lt;h2&gt;三、 &lt;code&gt;v-model&lt;/code&gt; 是双向绑定还是单向数据流？&lt;/h2&gt;
&lt;h3&gt;2.1 &lt;code&gt;v-model&lt;/code&gt; 是双向绑定吗？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;是，官方说是。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;『你可以用 v-model 指令在表单 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;、&lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; 及 &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; 元素上创建双向数据绑定。』 —— vue2官方文档&lt;/p&gt;
&lt;h3&gt;2.2 那 &lt;code&gt;v-model&lt;/code&gt; 是单向数据流吗？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;是的，它甚至是单向数据流的典型范式。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;虽然官方没有明确表示这点，但我们可以捋一捋两者的关系。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;什么是单项数据流？&lt;br&gt;
子组件不能改变父组件传递给它的 &lt;code&gt;prop&lt;/code&gt; 属性，推荐的做法是它抛出事件，通知父组件自行改变绑定的值。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;v-model&lt;/code&gt; 的做法是怎样的？&lt;br&gt;
&lt;code&gt;v-model&lt;/code&gt; 做法完全符合单项数据流。甚至于，它给出了一种在命名和事件定义上的规范。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;众所周知 &lt;code&gt;.sync&lt;/code&gt; 修饰符是单向数据流的另一个典型范式。
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fb7cb9ef059347f6b8ed76c97830230e~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;单向数据流8字真诀&quot;&gt;&lt;/p&gt;
&lt;p&gt;『单向数据流』总结起来其实也就8个字：『数据向下，事件向上』。&lt;/p&gt;
&lt;h2&gt;四、如何让你开发的组件支持 &lt;code&gt;v-model&lt;/code&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;虽然不想说，但这确实是高频面试题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在定义 &lt;code&gt;vue&lt;/code&gt; 组件时，你可以提供一个 &lt;code&gt;model&lt;/code&gt; 属性，用来定义该组件以何种方式支持 &lt;code&gt;v-model&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;model&lt;/code&gt; 属性本身是有默认值的，如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 默认的 model 属性
export default {
  model: {
    prop: &amp;#x27;value&amp;#x27;,
    event: &amp;#x27;input&amp;#x27;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;也就是说，如果你不定义 &lt;code&gt;model&lt;/code&gt; 属性，或者你按照当面方法定义属性，当其他人使用你的自定义组件时，&lt;code&gt;v-model=&amp;quot;foo&amp;quot;&lt;/code&gt; 就完全等价于 &lt;code&gt;:value=&amp;quot;foo&amp;quot;&lt;/code&gt; 加上 &lt;code&gt;@input=&amp;quot;foo = $event&amp;quot;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;如果把 &lt;code&gt;model&lt;/code&gt; 属性进行一些改装，如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 默认的 model 属性
export default {
  model: {
    prop: &amp;#x27;ame&amp;#x27;,
    event: &amp;#x27;zard&amp;#x27;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那么，&lt;code&gt;v-model=&amp;quot;foo&amp;quot;&lt;/code&gt; 就等价于 &lt;code&gt;:ame=&amp;quot;foo&amp;quot;&lt;/code&gt; 加上 &lt;code&gt;@zard=&amp;quot;foo = $event&amp;quot;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;没错，就是这么容易，让我们看个例子。&lt;/p&gt;
&lt;p&gt;先定义一个自定义组件：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
&amp;lt;div&amp;gt;
  我们是TI{{ ame }}冠军
  &amp;lt;el-button @click=&amp;quot;playDota2(1)&amp;quot;&amp;gt;加&amp;lt;&amp;#x2F;el-button&amp;gt;
  &amp;lt;el-button @click=&amp;quot;playDota2(-1)&amp;quot;&amp;gt;减&amp;lt;&amp;#x2F;el-button&amp;gt;
&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
export default {
  props: {
    ame: {
      type: Number,
      default: 8
    }
  },
  model: { &amp;#x2F;&amp;#x2F; 自定义v-model的格式
    prop: &amp;#x27;ame&amp;#x27;, &amp;#x2F;&amp;#x2F; 代表 v-model 绑定的prop名
    event: &amp;#x27;zard&amp;#x27; &amp;#x2F;&amp;#x2F; 代码 v-model 通知父组件更新属性的事件名
  },
  methods: {
    playDota2(step) {
      const newYear = this.ame + step
      this.$emit(&amp;#x27;zard&amp;#x27;, newYear)
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后我们在父组件中使用该组件：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; template中
&amp;lt;dota v-model=&amp;quot;ti&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;dota&amp;gt;
&amp;#x2F;&amp;#x2F; script中
export default {
  data() {
    return {
      ti: 8
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;看看效果：
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bed10463fbab45f6b3baad5467e9170b~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;我们是TI冠军&quot;&gt;&lt;/p&gt;
&lt;p&gt;让你的组件支持 &lt;code&gt;v-model&lt;/code&gt; 就这么容易。&lt;/p&gt;
&lt;h2&gt;五、demo和源码&lt;/h2&gt;
&lt;p&gt;获取源码请访问github &lt;a href=&quot;https://github.com/zhangshichun/blog-vue2-demos/tree/master/src/views/about-v-model&quot;&gt;zhangshichun的github&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;六、结束&lt;/h2&gt;
&lt;p&gt;我是&lt;code&gt;春哥&lt;/code&gt;。&lt;br&gt;
我热爱 &lt;code&gt;vue.js&lt;/code&gt; , &lt;code&gt;ElementUI&lt;/code&gt; , &lt;code&gt;Element Plus&lt;/code&gt; 相关技术栈，我的目标是给大家分享最实用、最有用的知识点，希望大家都可以早早下班，并可以飞速完成工作，淡定摸鱼。&lt;/p&gt;
&lt;p&gt;你可以在&lt;strong&gt;掘金&lt;/strong&gt;关注我，也可以在&lt;strong&gt;公众号&lt;/strong&gt;里找到我：&lt;code&gt;前端要摸鱼&lt;/code&gt;。&lt;br&gt;
希望大家玩得开心。&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 春哥的梦想是摸鱼
        原文链接：&lt;a href=&quot;https://juejin.im/post/7049135444310622245&quot;&gt;https://juejin.im/post/7049135444310622245&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Tue, 18 Jan 2022 08:15:00 GMT</pubDate></item><item><title>面试官：项目中常用的 .env 文件原理是什么？如何实现？__前端__JavaScript__Vue.js</title><link>http://vue-js.com/topic/61e668f4ad745b003c9df796</link><guid>http://vue-js.com/topic/61e668f4ad745b003c9df796</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;highlight: darcula
theme: smartblue&lt;/h2&gt;
&lt;h2&gt;1. 前言&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;大家好，我是&lt;a href=&quot;https://lxchuan12.gitee.io&quot;&gt;若川&lt;/a&gt;。&lt;a href=&quot;https://rank.juejin.cn/rank/2021/1415826704971918&quot;&gt;正在参加掘金年度人气作者活动，可以点此帮我投票&lt;/a&gt;。&lt;strong&gt;为了能帮助到更多对源码感兴趣、想学会看源码、提升自己前端技术能力的同学&lt;/strong&gt;。我倾力组织了&lt;a href=&quot;https://www.yuque.com/ruochuan12/topics/1&quot;&gt;源码共读活动&lt;/a&gt;，感兴趣的可以加我微信 &lt;a href=&quot;https://juejin.cn/pin/7005372623400435725&quot;&gt;ruochuan12&lt;/a&gt; 参与。每周大家一起学习200行左右的源码，已进行5个月。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;想学源码，极力推荐关注我写的专栏（目前2K人关注）&lt;a href=&quot;https://juejin.cn/column/6960551178908205093&quot;&gt;《学习源码整体架构系列》&lt;/a&gt; 包含&lt;code&gt;jQuery&lt;/code&gt;、&lt;code&gt;underscore&lt;/code&gt;、&lt;code&gt;lodash&lt;/code&gt;、&lt;code&gt;vuex&lt;/code&gt;、&lt;code&gt;sentry&lt;/code&gt;、&lt;code&gt;axios&lt;/code&gt;、&lt;code&gt;redux&lt;/code&gt;、&lt;code&gt;koa&lt;/code&gt;、&lt;code&gt;vue-devtools&lt;/code&gt;、&lt;code&gt;vuex4&lt;/code&gt;、&lt;code&gt;koa-compose&lt;/code&gt;、&lt;code&gt;vue 3.2 发布&lt;/code&gt;、&lt;code&gt;vue-this&lt;/code&gt;、&lt;code&gt;create-vue&lt;/code&gt;、&lt;code&gt;玩具vite&lt;/code&gt;等20余篇源码文章。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lxchuan12/dotenv-analysis.git&quot;&gt;本文仓库 https://github.com/lxchuan12/dotenv-analysis.git，求个star^_^&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.yuque.com/ruochuan12/topics/1&quot;&gt;源码共读活动&lt;/a&gt; 每周一期，已进行到18期。于是搜寻各种值得我们学习，且代码行数不多的源码。&lt;a href=&quot;https://github.com/motdotla/dotenv/blob/master/lib/main.js&quot;&gt;dotenv 主文件仅118行&lt;/a&gt;，非常值得我们学习。&lt;/p&gt;
&lt;p&gt;阅读本文，你将学到：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;1. 学会 dotenv 原理和实现
2. 学会使用 fs模块 获取文件并解析
3. 等等
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;2. 环境准备&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;# 推荐克隆我的项目，保证与文章同步
git clone https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;lxchuan12&amp;#x2F;dotenv-analysis.git
# npm i -g yarn
cd dotenv-analysis&amp;#x2F;dotenv &amp;amp;&amp;amp; yarn i
# VSCode 直接打开当前项目
# code .
# 我写的例子都在 examples 这个文件夹中，可以启动服务本地查看调试
# 在 dotenv-analysis 目录下
node examples&amp;#x2F;index.js

# 或者克隆官方项目
git clone https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;motdotla&amp;#x2F;dotenv.git
# npm i -g yarn
cd dotenv &amp;amp;&amp;amp; yarn i
# VSCode 直接打开当前项目
# code .
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果需要对源码进行调试，可以看我的这篇文章：&lt;a href=&quot;https://juejin.cn/post/7030584939020042254&quot;&gt;新手向：前端程序员必学基本技能——调试JS代码&lt;/a&gt;，这里就不再赘述了。&lt;/p&gt;
&lt;h2&gt;3. dotenv 的作用&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/motdotla/dotenv&quot;&gt;dotenv&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Dotenv&lt;/code&gt; 是一个零依赖模块，可将 &lt;code&gt;.env&lt;/code&gt; 文件中的环境变量加载到 &lt;code&gt;process.env&lt;/code&gt; 中。&lt;/p&gt;
&lt;p&gt;如果需要使用变量，则配合如下扩展包使用。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/motdotla/dotenv-expand&quot;&gt;dotenv-expand&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;众所周知，&lt;code&gt;.env&lt;/code&gt; 文件在我们项目中非常常见，在 &lt;code&gt;vue-cli&lt;/code&gt; 和 &lt;code&gt;create-react-app&lt;/code&gt; 中都有使用。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cli.vuejs.org/zh/guide/mode-and-env.html#%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F&quot;&gt;vue-cli .env&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://create-react-app.dev/docs/adding-custom-environment-variables/#what-other-env-files-can-be-used&quot;&gt;create-react-app .env&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;4. .env 文件使用&lt;/h2&gt;
&lt;p&gt;我们项目中经常会用到&lt;code&gt;.env 文件&lt;/code&gt;写法：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;NAME=若川
AGE=18
BLOG=https:&amp;#x2F;&amp;#x2F;lxchuan12.gitee.io
MP_WEIXIN=&amp;#x27;若川视野&amp;#x27;
ACTIVITY=每周一起学200行左右的源码共读活动
WEIXIN=加我微信 ruochuan12 参与
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;单从这个文件来看，我们可以知道有如下功能需要实现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读取 .env 文件&lt;/li&gt;
&lt;li&gt;解析 .env 文件拆成键值对的对象形式&lt;/li&gt;
&lt;li&gt;赋值到 process.env 上&lt;/li&gt;
&lt;li&gt;最后返回解析后得到的对象&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;5. 简单实现&lt;/h2&gt;
&lt;p&gt;根据分析问题，我们最终可以简单把代码实现如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const fs = require(&amp;#x27;fs&amp;#x27;);
const path = require(&amp;#x27;path&amp;#x27;);

const parse = function parse(src){
    const obj = {};
    &amp;#x2F;&amp;#x2F; 用换行符 分割
    &amp;#x2F;&amp;#x2F; 比如
    &amp;#x2F;**
     * NAME=若川
     * AGE=18
     * MP_WEIXIN=若川视野
     * BLOG=https:&amp;#x2F;&amp;#x2F;lxchuan12.gitee.io
     * ACTIVITY=每周一起学200行左右的源码共读活动
     * WEIXIN=加我微信 ruochuan12 参与
    *&amp;#x2F;
    src.toString().split(&amp;#x27;&amp;#x5C;n&amp;#x27;).forEach(function(line, index){
        &amp;#x2F;&amp;#x2F; 用等号分割
        const keyValueArr = line.split(&amp;#x27;=&amp;#x27;);
        &amp;#x2F;&amp;#x2F; NAME
        key = keyValueArr[0];
        &amp;#x2F;&amp;#x2F; 若川
        val = keyValueArr[1] || &amp;#x27;&amp;#x27;;
        obj[key] = val;
    });
    &amp;#x2F;&amp;#x2F; { NAME: &amp;#x27;若川&amp;#x27;, ... }
    return obj;
}

const config = function(){
    &amp;#x2F;&amp;#x2F; 读取 node 执行的当前路径下的 .env 文件
    let dotenvPath = path.resolve(process.cwd(), &amp;#x27;.env&amp;#x27;);
    &amp;#x2F;&amp;#x2F; 按 utf-8 解析文件，得到对象
    &amp;#x2F;&amp;#x2F; { NAME: &amp;#x27;若川&amp;#x27;, ... }
    const parsed = parse(fs.readFileSync(dotenvPath, &amp;#x27;utf-8&amp;#x27;));

    &amp;#x2F;&amp;#x2F; 键值对形式赋值到 process.env 变量上，原先存在的不赋值
    Object.keys(parsed).forEach(function(key){
        if(!Object.prototype.hasOwnProperty.call(process.env, key)){
            process.env[key] = parsed[key];
        }
    });

    &amp;#x2F;&amp;#x2F; 返回对象
    return parsed;
};

console.log(config());
console.log(process.env);

&amp;#x2F;&amp;#x2F; 导出 config parse 函数
module.exports.config = config;
module.exports.parse = parse;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;6. 继续完善 config 函数&lt;/h2&gt;
&lt;p&gt;简版的 config 函数还缺失挺多功能，比如：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;可由用户自定义路径
可由用户自定义解析编码规则
添加 debug 模式
完善报错输出，用户写的 env 文件自由度比较大，所以需要容错机制。
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;根据功能，我们很容易实现以下代码：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;function resolveHome (envPath) {
    return envPath[0] === &amp;#x27;~&amp;#x27; ? path.join(os.homedir(), envPath.slice(1)) : envPath
}

const config = function(options){
    &amp;#x2F;&amp;#x2F; 读取 node 执行的当前路径下的 .env 文件
    let dotenvPath = path.resolve(process.cwd(), &amp;#x27;.env&amp;#x27;);
    &amp;#x2F;&amp;#x2F; utf8
    let encoding = &amp;#x27;utf8&amp;#x27;;
    &amp;#x2F;&amp;#x2F; debug 模式，输出提示等信息
    let debug = false;
    &amp;#x2F;&amp;#x2F; 对象
    if (options) {
        if (options.path != null) {
            &amp;#x2F;&amp;#x2F; 解析路径
            dotenvPath = resolveHome(options.path)
        }
        &amp;#x2F;&amp;#x2F; 使用配置的编码方式
        if (options.encoding != null) {
            encoding = options.encoding
        }
        &amp;#x2F;&amp;#x2F; 有配置就设置为 true
        if (options.debug != null) {
            debug = true
        }
    }

    try {
        &amp;#x2F;&amp;#x2F; 按 utf-8 解析文件，得到对象
        &amp;#x2F;&amp;#x2F; { NAME: &amp;#x27;若川&amp;#x27;, ... }
        &amp;#x2F;&amp;#x2F; debug 传递给 parse 函数 便于
        const parsed = parse(fs.readFileSync(dotenvPath, { encoding }), { debug });

        &amp;#x2F;&amp;#x2F; 键值对形式赋值到 process.env 变量上，原先存在的不赋值
        Object.keys(parsed).forEach(function(key){
            if(!Object.prototype.hasOwnProperty.call(process.env, key)){
                process.env[key] = parsed[key];
            } else if (debug) {
                console.log(&amp;#96;&amp;quot;${key}&amp;quot; is already defined in &amp;#x5C;&amp;#96;process.env&amp;#x5C;&amp;#96; and will not be overwritten&amp;#96;);
            }
        });

        &amp;#x2F;&amp;#x2F; 返回对象
        return parsed;
    }
    catch (e) {
        return { error: e };
    }
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;dotenv&lt;/code&gt; 源码中，&lt;code&gt;parse&lt;/code&gt; 函数主要是一些正则和单双引号、跨平台等细致处理。这里就暂时不阐述，读者朋友可以查看&lt;a href=&quot;https://github.com/motdotla/dotenv/blob/master/lib/main.js&quot;&gt;dotenv 源码&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;7. 总结&lt;/h2&gt;
&lt;p&gt;鉴于文章不宜过长，文章只比较深入的分析了 &lt;code&gt;config&lt;/code&gt; 函数。&lt;code&gt;parse&lt;/code&gt; 函数目前没有深入分析。&lt;/p&gt;
&lt;p&gt;一句话总结 &lt;code&gt;dotenv&lt;/code&gt; 库的原理。&lt;strong&gt;用 &lt;code&gt;fs.readFileSync&lt;/code&gt; 读取 &lt;code&gt;.env&lt;/code&gt; 文件，并解析文件为键值对形式的对象，将最终结果对象遍历赋值到 &lt;code&gt;process.env&lt;/code&gt; 上&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我们也可以不看 &lt;code&gt;dotenv&lt;/code&gt; 源码，根据 &lt;code&gt;api&lt;/code&gt; 倒推，自己来实现这样的功能。最终看看和  &lt;code&gt;dotenv&lt;/code&gt; 源码本身有什么差别。这样也许更能锻炼自己。或者用 &lt;code&gt;ts&lt;/code&gt; 重构它。&lt;/p&gt;
&lt;p&gt;本文同时也给我们启发：围绕工作常用的技术包和库值得深入学习，做到&lt;strong&gt;知其然，知其所以然&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;值得一提的是：&lt;code&gt;dotenv&lt;/code&gt; 源码使用的是 &lt;code&gt;flow&lt;/code&gt; 类型。vue2 源码也是用的 &lt;code&gt;flow&lt;/code&gt;。vue3 源码改用 &lt;code&gt;ts&lt;/code&gt;了。&lt;/p&gt;
&lt;p&gt;最后可以持续关注我@若川。欢迎加我微信 &lt;a href=&quot;https://juejin.cn/pin/7005372623400435725&quot;&gt;ruochuan12&lt;/a&gt; 交流，参与 &lt;a href=&quot;https://juejin.cn/pin/7005372623400435725&quot;&gt;源码共读&lt;/a&gt; 活动，每周大家一起学习200行左右的源码，共同进步。&lt;p&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
作者: 若川
原文链接：&lt;a href=&quot;https://juejin.im/post/7045057475845816357&quot;&gt;https://juejin.im/post/7045057475845816357&lt;/a&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>banyungong</author><pubDate>Tue, 18 Jan 2022 07:15:00 GMT</pubDate></item><item><title>花60秒给Vue3提的PR，竟然被尤大亲自Merge了~__JavaScript__Vue.js__开源</title><link>http://vue-js.com/topic/61e65ae4ad745b003c9df794</link><guid>http://vue-js.com/topic/61e65ae4ad745b003c9df794</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;ul&gt;
&lt;li&gt;小知识，大挑战！本文正在参与“&lt;a href=&quot;https://juejin.cn/post/7008476801634680869&quot; title=&quot;https://juejin.cn/post/7008476801634680869&quot;&gt;程序员必备小知识&lt;/a&gt;”创作活动。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;先来一个图镇楼！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec1d1b52edb748a8bdf909aa4375cb5d~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;终于等到一个活动可以让我分享在去年定下的目标&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9883d795f5494760b4e82d9329eee6dd~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;0_82ac840b685208ae729eb6b958e22573.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我要成为&lt;code&gt;Vue3&lt;/code&gt;的&lt;code&gt;Contributor&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;然而，在定下目标60秒后，我就完成了它！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我成为了&lt;code&gt;Vue3&lt;/code&gt;的&lt;code&gt;Contributor&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我这个PR&lt;strong&gt;举世无双&lt;/strong&gt;的水，说是&lt;strong&gt;世界第一水&lt;/strong&gt;也不过分！&lt;/p&gt;
&lt;p&gt;但是不好意思啊，即便是如此水的一个PR，尤大也不得不Merge！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1d5a092265834f08ba4acad6b4ab320d~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;54_91e2cee52fecd67ed6bf892f954400ed.gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;尤大亲手让我成为了&lt;code&gt;Vue3&lt;/code&gt;的&lt;code&gt;Contributor&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为我确实修正了源码中一处错误！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bc1776b7ad9f496ba93eaed12e0b7130~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;哈哈哈，我甚至连那个TODO都没帮忙完成！&lt;/p&gt;
&lt;p&gt;我知道还有很多顶级项目都有这类问题，因为拼错单词是很常见的，手快了、脑抽了、晃神了都有可能拼错，这问题还不太好发现。因为错词的可读性依然很高。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Aoccdrnig to a rscheearch at an Elingsh uinervtisy, it deosn’t mttaer in waht oredr the ltteers in a wrod are, the olny iprmoetnt tihng is taht frist and lsat ltteer is at the rghit pclae. The rset can be a toatl mses and you can sitll raed it wouthit porbelm. Tihs is bcuseae we do not raed ervey lteter by itslef but the wrod as a wlohe.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;对不对？&lt;/p&gt;
&lt;p&gt;那么只要找出开源项目中那些拼错的单词再改掉，岂不是各种顶级项目的&lt;code&gt;Contributor&lt;/code&gt;标记拿到手软？&lt;/p&gt;
&lt;p&gt;下面我来教大家如何对开源项目进行单词拼写检查&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;把代码文件copy到Word里&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;把代码文件copy到Word里&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;把代码文件copy到Word里&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/da10ecc78c6447029fa7cf03e4cf2da3~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;3_a252147afe65161272c362aa89e3cdce.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;就这的话我是不是要被大家的唾沫淹死？&lt;/p&gt;
&lt;p&gt;原理就是这么个简单的原理&lt;/p&gt;
&lt;p&gt;有没有帮我们搞效检查项目代码中拼写错误的仓库呢？&lt;/p&gt;
&lt;p&gt;有的！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/401fcea35c9345818b0bb83a11d920ba~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;0_8501779.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;请大家自行前往&lt;code&gt;Github&lt;/code&gt;搜索&lt;code&gt;SpellCheck&lt;/code&gt;
或点击下方链接&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/search?q=spellcheck&quot;&gt;https://github.com/search?q=spellcheck&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;One more times and Follow me 大声say!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;大帅是&lt;code&gt;Vue3&lt;/code&gt;的&lt;code&gt;Contributor&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;弱弱的问大家一句，尤大要是看到这篇文章，还有能力撤销这个Merge吗？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ece20490be0b4a848050e25dc144799d~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;6_469436.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 大帅老猿
        原文链接：&lt;a href=&quot;https://juejin.im/post/7012449788255813669&quot;&gt;https://juejin.im/post/7012449788255813669&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Tue, 18 Jan 2022 06:15:00 GMT</pubDate></item><item><title>用Vue3.2  + Vite2.7 快速从0快速打造一个UI组件库__Vue.js__前端</title><link>http://vue-js.com/topic/61e64cd4ad745b003c9df792</link><guid>http://vue-js.com/topic/61e64cd4ad745b003c9df792</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h1&gt;1. 前言&lt;/h1&gt;
&lt;p&gt;最近自己学习写了一个基于Vue3的组件库，感觉&lt;code&gt;有点意思&lt;/code&gt;，这篇文章来记录一下我是怎么从0快速打造一个UI组件库的&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;先来个预览
&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/baf189fa7b324957a22966b30e99c7ca~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;jw-ui演示.gif&quot;&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;附上访问地址&lt;a href=&quot;https://coderyjw.github.io/jw-ui-website&quot;&gt;jw-ui&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 使用Vite搭建官网&lt;/h1&gt;
&lt;p&gt;Vite是尤雨溪开发的一种新型前端构建工具，具体介绍可以查看&lt;a href=&quot;https://cn.vitejs.dev/&quot;&gt;官方文档&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;2.1 创建项目&lt;/h2&gt;
&lt;h3&gt;2.1.1. 全局安装vite（这里我装的时候是2.7.2）&lt;/h3&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;$ yarn create vite@2.7.2
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2.1.2. 构建一个vue模板(项目名可以改成自己的名字)&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-shell&quot;&gt;&lt;code&gt;yarn create vite jw-ui --template vue
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2.1.3. 装好之后按照提示逐步执行命令&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-shell&quot;&gt;&lt;code&gt;cd jw-ui
yarn
yarn dev
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到界面&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a98637e10a7249618b49965d220466c1~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ps: 推荐的IDE和设置：VSCode + Volar&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;2.2 基本完成官网的搭建&lt;/h2&gt;
&lt;h3&gt;2.2.1. 下载vue-router&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-shell&quot;&gt;&lt;code&gt;yarn add vue-router@4
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2.2.2. 创建home首页与doc文档页 以及顶部导航栏&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;#x2F;* &amp;#x2F;views&amp;#x2F;home&amp;#x2F;index.vue 首页*&amp;#x2F;
&amp;lt;template&amp;gt;
    &amp;lt;div&amp;gt;
        Home
    &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;#x2F;* &amp;#x2F;views&amp;#x2F;doc&amp;#x2F;index.vue 文档页面 *&amp;#x2F;
&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    Doc
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;#x2F;* &amp;#x2F;components&amp;#x2F;Topnav.vue 顶部导航栏组件 *&amp;#x2F;
&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;topnav&amp;quot;&amp;gt;
    &amp;lt;router-link to=&amp;quot;&amp;#x2F;home&amp;quot;&amp;gt;首页&amp;lt;&amp;#x2F;router-link&amp;gt;
    &amp;lt;router-link to=&amp;quot;&amp;#x2F;doc&amp;quot;&amp;gt;文档&amp;lt;&amp;#x2F;router-link&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2.2.3. 配置路由&lt;/h3&gt;
&lt;p&gt;创建路由配置文件&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; router&amp;#x2F;index.ts 
import { createRouter, createWebHashHistory } from &amp;quot;vue-router&amp;quot;;
const history = createWebHashHistory();

const router = createRouter({
  history,
  routes: [
    { path: &amp;quot;&amp;#x2F;&amp;quot;, redirect: &amp;quot;&amp;quot; },
  ],
});

export default router;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在main.ts里导入，使得整个应用支持路由。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-TYPESCRIPT&quot;&gt;&lt;code&gt;import { createApp } from &amp;quot;vue&amp;quot;;
import App from &amp;quot;.&amp;#x2F;App.vue&amp;quot;;
import router from &amp;quot;.&amp;#x2F;router&amp;quot;;

const app = createApp(App);
app.use(router);

app.mount(&amp;quot;#app&amp;quot;);

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;修改App.vue&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;Topnav &amp;#x2F;&amp;gt;
  &amp;lt;router-view &amp;#x2F;&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script setup&amp;gt;
import Topnav from &amp;quot;.&amp;#x2F;components&amp;#x2F;Topnav.vue&amp;quot;;
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;到目前为止的效果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/565b1b1621944fd2b9713255873bed19~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;jw-ui演示1.gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;装饰一下顶部导航栏后的效果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9083365473f7475580f28d796d63a193~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;jw-ui演示2.gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;这里首页按照自己喜欢的来写CSS就好了，接下来讲一下文档页面。&lt;/p&gt;
&lt;p&gt;文档页需要一个侧边栏来切换不同组件的文档，这里我就举例做一个Button组件&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; doc&amp;#x2F;index.vue
&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;layout&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
      &amp;lt;aside&amp;gt;
        &amp;lt;router-link class=&amp;quot;menu-item text-overflow&amp;quot; to=&amp;quot;&amp;#x2F;doc&amp;#x2F;button&amp;quot;
          &amp;gt;Button 组件&amp;lt;&amp;#x2F;router-link
        &amp;gt;
      &amp;lt;&amp;#x2F;aside&amp;gt;
      &amp;lt;main style=&amp;quot;padding-left: 302px&amp;quot;&amp;gt;
        &amp;lt;router-view &amp;#x2F;&amp;gt;
      &amp;lt;&amp;#x2F;main&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-TYPESCRIPT&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; router&amp;#x2F;index.ts 添加一个展示的button页面
import { createRouter, createWebHashHistory } from &amp;quot;vue-router&amp;quot;;

import Home from &amp;quot;..&amp;#x2F;views&amp;#x2F;home&amp;#x2F;index.vue&amp;quot;;
import Doc from &amp;quot;..&amp;#x2F;views&amp;#x2F;doc&amp;#x2F;index.vue&amp;quot;;
import ButtonDoc from &amp;quot;..&amp;#x2F;views&amp;#x2F;doc&amp;#x2F;button&amp;#x2F;index.vue&amp;quot;;

const history = createWebHashHistory();

const router = createRouter({
  history,
  routes: [
    { path: &amp;quot;&amp;#x2F;&amp;quot;, redirect: &amp;quot;&amp;#x2F;home&amp;quot; },
    { path: &amp;quot;&amp;#x2F;home&amp;quot;, component: Home },
    {
      path: &amp;quot;&amp;#x2F;doc&amp;quot;,
      component: Doc,
      children: [{ path: &amp;quot;button&amp;quot;, component: ButtonDoc }],
    },
  ],
});

export default router;

&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-TYPESCRIPT&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; &amp;#x2F;views&amp;#x2F;doc&amp;#x2F;button&amp;#x2F;index
&amp;lt;template&amp;gt;
  &amp;lt;Button &amp;#x2F;&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script setup&amp;gt;
import Button from &amp;#x27;..&amp;#x2F;..&amp;#x2F;..&amp;#x2F;lib&amp;#x2F;button&amp;#x2F;index.vue&amp;#x27;
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;style lang=&amp;quot;scss&amp;quot; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;展示效果
&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1c062c4386c34b2cab67db872eda3c8b~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;好了到这里官网总算是基本搭建完了，我们终于就可以愉快的在&lt;code&gt;src/lib/button/index.vue&lt;/code&gt;文件里封装组件啦。（&lt;strong&gt;封装的组件都放在lib文件夹里，以后打包用&lt;/strong&gt;）&lt;/p&gt;
&lt;h1&gt;3. 封装一个Button组件&lt;/h1&gt;
&lt;p&gt;下面附上我写的一个Button组件以及使用效果&lt;/p&gt;
&lt;p&gt;&lt;code&gt;PS: 需要注意的一点是封装的样式一定要加&lt;/code&gt;&lt;strong&gt;&lt;code&gt;自己独特的前缀&lt;/code&gt;&lt;/strong&gt;&lt;code&gt;我这里是&lt;/code&gt; &lt;strong&gt;&lt;code&gt;jw&lt;/code&gt;&lt;/strong&gt; &lt;code&gt;以避免在项目中产生样式重叠&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;button class=&amp;quot;jw-button&amp;quot; :class=&amp;quot;classes&amp;quot;&amp;gt;
    &amp;lt;span v-if=&amp;quot;loading&amp;quot; class=&amp;quot;jw-loadingIndicator&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;span&amp;gt;
    &amp;lt;slot&amp;gt; {{ theme }} &amp;lt;&amp;#x2F;slot&amp;gt;
  &amp;lt;&amp;#x2F;button&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script setup lang=&amp;quot;ts&amp;quot;&amp;gt;
import { computed } from &amp;quot;vue&amp;quot;;
const props = defineProps({
  theme: {
    type: String,
    default: &amp;quot;default&amp;quot;,
  },
  dashed: {
    type: Boolean,
    default: false,
  },
  size: {
    type: String,
    default: &amp;quot;default&amp;quot;,
  },
  round: {
    type: Boolean,
    default: false,
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  loading: {
    type: Boolean,
    default: false,
  },
});

const { theme, dashed, size, round, disabled } = props;
const classes = computed(() =&amp;gt; {
  return {
    [&amp;#96;jw-theme-${theme}&amp;#96;]: theme,
    [&amp;#96;jw-theme-dashed&amp;#96;]: dashed,
    [&amp;#96;jw-size-${size}&amp;#96;]: size,
    [&amp;#96;is-round&amp;#96;]: round,
    [&amp;#96;is-disabled&amp;#96;]: disabled,
  };
});
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;script lang=&amp;quot;ts&amp;quot;&amp;gt;
export default {
  name: &amp;quot;JwButton&amp;quot;,
};
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;style lang=&amp;quot;scss&amp;quot; scoped&amp;gt;
$h-default: 32px;
$h-small: 20px;
$h-large: 48px;
$white: #fff;
$default-color: #333;
$primary-color: #36ad6a;
$info-color: #4098fc;
$success-color: #85ce61;
$warning-color: #f0a020;
$error-color: #d03050;
$grey: grey;

$default-border-color: #d9d9d9;

$radius: 3px;
$green: #18a058;

.jw-button {
  box-sizing: border-box;
  height: $h-default;
  background-color: #fff;
  padding: 0 12px;
  cursor: pointer;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  white-space: nowrap;
  border-radius: $radius;
  box-shadow: 0 1px 0 fade-out(black, 0.95);
  transition: all 250ms;
  color: $default-color;
  border: 1px solid $default-border-color;
  user-select: none;

  &amp;amp;:focus {
    outline: none;
  }

  &amp;amp;::-moz-focus-inner {
    border: 0;
  }

  &amp;amp;.jw-size-large {
    font-size: 24px;
    height: $h-large;
    padding: 0 16px;
  }
  &amp;amp;.jw-size-small {
    font-size: 12px;
    height: $h-small;
    padding: 0 8px;
  }

  &amp;amp;.is-round.jw-size-default {
    border-radius: calc($h-default &amp;#x2F; 2);
  }
  &amp;amp;.is-round.jw-size-large {
    border-radius: calc($h-large &amp;#x2F; 2);
  }
  &amp;amp;.is-round.jw-size-small {
    border-radius: calc($h-small &amp;#x2F; 2);
  }

  &amp;amp;.jw-theme-default {
    &amp;amp;:hover {
      color: $green;
      border-color: $green;

      &amp;gt; .jw-loadingIndicator {
        border-style: dashed;
        border-color: $green $green $green transparent;
      }
    }
    &amp;amp;:active {
      color: darken($green, 20%);
      border-color: darken($green, 20%);

      &amp;gt; .jw-loadingIndicator {
        border-style: dashed;
        border-color: darken($green, 20%) darken($green, 20%)
          darken($green, 20%) transparent;
      }
    }
    &amp;amp;.jw-theme-dashed {
      border-style: dashed;
    }
    &amp;gt; .jw-loadingIndicator {
      border-style: dashed;
      border-color: $default-color $default-color $default-color transparent;
    }
  }
  &amp;amp;.jw-theme-primary {
    background-color: $primary-color;
    border-color: $primary-color;
    color: $white;

    &amp;amp;:hover {
      background: lighten($primary-color, 20%);
      border-color: lighten($primary-color, 20%);
    }
    &amp;amp;:active {
      background-color: darken($primary-color, 20%);
      border-color: darken($primary-color, 20%);
    }

    &amp;amp;.is-disabled {
      cursor: not-allowed;
      background: lighten($primary-color, 20%);
      border-color: lighten($primary-color, 20%);
      &amp;amp;:hover {
        background: lighten($primary-color, 20%);
        border-color: lighten($primary-color, 20%);
      }
    }

    &amp;amp;.jw-theme-dashed {
      border-style: dashed;
      background-color: $white !important;
      color: $primary-color;

      &amp;gt; .jw-loadingIndicator {
        border-style: dashed;
        border-color: $primary-color $primary-color $primary-color transparent;
      }
    }
  }

  &amp;amp;.jw-theme-info {
    background-color: $info-color;
    border-color: $info-color;
    color: $white;
    &amp;amp;:hover {
      background: lighten($info-color, 20%);
      border-color: lighten($info-color, 20%);
    }
    &amp;amp;:active {
      background-color: darken($info-color, 20%);
      border-color: darken($info-color, 20%);
    }

    &amp;amp;.is-disabled {
      cursor: not-allowed;
      background: lighten($info-color, 20%);
      border-color: lighten($info-color, 20%);
      &amp;amp;:hover {
        background: lighten($info-color, 20%);
        border-color: lighten($info-color, 20%);
      }
    }

    &amp;amp;.jw-theme-dashed {
      border-style: dashed;
      background-color: $white !important;
      color: $info-color;

      &amp;gt; .jw-loadingIndicator {
        border-style: dashed;
        border-color: $info-color $info-color $info-color transparent;
      }
    }
  }

  &amp;amp;.jw-theme-success {
    background-color: $success-color;
    border-color: $success-color;
    color: $white;
    &amp;amp;:hover {
      background: lighten($success-color, 20%);
      border-color: lighten($success-color, 20%);
    }
    &amp;amp;:active {
      background-color: darken($success-color, 20%);
      border-color: darken($success-color, 20%);
    }

    &amp;amp;.is-disabled {
      cursor: not-allowed;
      background: lighten($success-color, 20%);
      border-color: lighten($success-color, 20%);
      &amp;amp;:hover {
        background: lighten($success-color, 20%);
        border-color: lighten($success-color, 20%);
      }
    }

    &amp;amp;.jw-theme-dashed {
      border-style: dashed;
      background-color: $white !important;
      color: $success-color;

      &amp;gt; .jw-loadingIndicator {
        border-style: dashed;
        border-color: $success-color $success-color $success-color transparent;
      }
    }
  }

  &amp;amp;.jw-theme-warning {
    background-color: $warning-color;
    border-color: $warning-color;
    color: $white;
    &amp;amp;:hover {
      background: lighten($warning-color, 20%);
      border-color: lighten($warning-color, 20%);
    }
    &amp;amp;:active {
      background-color: darken($warning-color, 20%);
      border-color: darken($warning-color, 20%);
    }

    &amp;amp;.is-disabled {
      cursor: not-allowed;
      background: lighten($warning-color, 20%);
      border-color: lighten($warning-color, 20%);
      &amp;amp;:hover {
        background: lighten($warning-color, 20%);
        border-color: lighten($warning-color, 20%);
      }
    }

    &amp;amp;.jw-theme-dashed {
      border-style: dashed;
      background-color: $white !important;
      color: $warning-color;

      &amp;gt; .jw-loadingIndicator {
        border-style: dashed;
        border-color: $warning-color $warning-color $warning-color transparent;
      }
    }
  }

  &amp;amp;.jw-theme-error {
    background-color: $error-color;
    border-color: $error-color;
    color: $white;
    &amp;amp;:hover {
      background: lighten($error-color, 20%);
      border-color: lighten($error-color, 20%);
    }
    &amp;amp;:active {
      background-color: darken($error-color, 20%);
      border-color: darken($error-color, 20%);
    }

    &amp;amp;.is-disabled {
      cursor: not-allowed;
      background: lighten($error-color, 20%);
      border-color: lighten($error-color, 20%);
      &amp;amp;:hover {
        background: lighten($error-color, 20%);
        border-color: lighten($error-color, 20%);
      }
    }

    &amp;amp;.jw-theme-dashed {
      border-style: dashed;
      background-color: $white !important;
      color: $error-color;

      &amp;gt; .jw-loadingIndicator {
        border-style: dashed;
        border-color: $error-color $error-color $error-color transparent;
      }
    }
  }

  &amp;gt; .jw-loadingIndicator {
    width: 14px;
    height: 14px;
    display: inline-block;
    margin-right: 4px;
    border-radius: 8px;
    border-color: $white $white $white transparent;
    border-style: solid;
    border-width: 2px;
    animation: jw-spin 1s infinite linear;
  }
}

@keyframes jw-spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/55721c1cee1c46cc9c453d7b2a791071~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;jw-ui演示3.gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;虽然有不完美，但差不多就这个意思吧&lt;/p&gt;
&lt;h1&gt;4. 封装Markdown组件介绍文档&lt;/h1&gt;
&lt;h2&gt;4.1. 下载&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;vite-plugin-markdown&lt;/code&gt;：一个插件可以让你导入Markdown文件作为各种格式的vite项目。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;github-markdown-css&lt;/code&gt;：复制GitHub Markdown风格&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-shell&quot;&gt;&lt;code&gt;yarn add github-markdown-css vite-plugin-markdown
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;4.2. main.ts中引入&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-TYPESCRIPT&quot;&gt;&lt;code&gt;import &amp;quot;github-markdown-css&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;4.3. vite.config.js中配置vite-plugin-markdown插件&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-TYPESCRIPT&quot;&gt;&lt;code&gt;import { defineConfig } from &amp;#x27;vite&amp;#x27;
import vue from &amp;#x27;@vitejs&amp;#x2F;plugin-vue&amp;#x27;
const md = require(&amp;quot;vite-plugin-markdown&amp;quot;);

export default defineConfig({
  plugins: [vue(),
    md.plugin({
    mode: [&amp;quot;html&amp;quot;, &amp;quot;vue&amp;quot;],
  }),]
})

&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;4.4. 封装Markdown组件&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-TYPESCRIPT&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; &amp;#x2F;components&amp;#x2F;Markdown.vue
&amp;lt;template&amp;gt;
  &amp;lt;article class=&amp;quot;markdown-body&amp;quot; v-html=&amp;quot;content&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;article&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script setup lang=&amp;quot;ts&amp;quot;&amp;gt;
&amp;#x2F;&amp;#x2F; 传入的md文件
const props = defineProps({
  content: {
    type: String,
    required: true,
  },
});
&amp;lt;&amp;#x2F;script&amp;gt;

&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;4.5. 创建介绍页面路由&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;import { h } from &amp;quot;vue&amp;quot;;
import { createRouter, createWebHashHistory } from &amp;quot;vue-router&amp;quot;;

import Home from &amp;quot;..&amp;#x2F;views&amp;#x2F;home&amp;#x2F;index.vue&amp;quot;;
import Doc from &amp;quot;..&amp;#x2F;views&amp;#x2F;doc&amp;#x2F;index.vue&amp;quot;;
import ButtonDoc from &amp;quot;..&amp;#x2F;views&amp;#x2F;doc&amp;#x2F;button&amp;#x2F;index.vue&amp;quot;;

const history = createWebHashHistory();

import Markdown from &amp;quot;..&amp;#x2F;components&amp;#x2F;Markdown.vue&amp;quot;;
const md = (string) =&amp;gt; h(Markdown, { content: string, key: string });
import { html as Intro } from &amp;quot;..&amp;#x2F;..&amp;#x2F;markdown&amp;#x2F;intro.md&amp;quot;;
const IntroDoc = md(Intro);

const router = createRouter({
  history,
  routes: [
    { path: &amp;quot;&amp;#x2F;&amp;quot;, redirect: &amp;quot;&amp;#x2F;home&amp;quot; },
    { path: &amp;quot;&amp;#x2F;home&amp;quot;, component: Home },
    {
      path: &amp;quot;&amp;#x2F;doc&amp;quot;,
      component: Doc,
      children: [
        { path: &amp;quot;intro&amp;quot;, component: IntroDoc },
        { path: &amp;quot;button&amp;quot;, component: ButtonDoc },
      ],
    },
  ],
});

export default router;

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到，最终md就能导入，并且生成了github上md的样式了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/64457a76cf894f9a8564ff06a7436c99~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h1&gt;5. 自定义代码块获取组件展示源代码&lt;/h1&gt;
&lt;h2&gt;5.1. 自定义插件vue-custom-blocks-plugin&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;import path from &amp;quot;path&amp;quot;;
import fs from &amp;quot;fs&amp;quot;;
import { baseParse } from &amp;quot;@vue&amp;#x2F;compiler-core&amp;quot;;
const vitePluginVue = {
  name: &amp;quot;preview&amp;quot;,
  transform(code, id) {
    if (
      !&amp;#x2F;&amp;#x5C;&amp;#x2F;src&amp;#x5C;&amp;#x2F;views&amp;#x5C;&amp;#x2F;doc&amp;#x5C;&amp;#x2F;.*&amp;#x5C;.preview&amp;#x5C;.vue&amp;#x2F;.test(id) ||
      !&amp;#x2F;vue&amp;amp;type=preview&amp;#x2F;.test(id)
    ) {
      return;
    }

    let path = &amp;#96;.${id.match(&amp;#x2F;&amp;#x5C;&amp;#x2F;src&amp;#x5C;&amp;#x2F;views&amp;#x5C;&amp;#x2F;doc&amp;#x5C;&amp;#x2F;.*&amp;#x5C;.preview&amp;#x5C;.vue&amp;#x2F;)[0]}&amp;#96;;
    const file = fs.readFileSync(path).toString();
    const parsed = baseParse(file).children.find((n) =&amp;gt; n.tag === &amp;quot;preview&amp;quot;);
    const title = parsed.children[0].content;
    const main = file.split(parsed.loc.source).join(&amp;quot;&amp;quot;).trim();
    return &amp;#96;export default function (Component) {
      Component.__sourceCode = ${JSON.stringify(main)}
      Component.__sourceCodeTitle = ${JSON.stringify(title)}
    }&amp;#96;.trim();
  },
};

export default vitePluginVue;


&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;5.2. 在vite.config.ts中配置&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-TYPESCRIPT&quot;&gt;&lt;code&gt;import { defineConfig } from &amp;#x27;vite&amp;#x27;
import vue from &amp;#x27;@vitejs&amp;#x2F;plugin-vue&amp;#x27;
const md = require(&amp;quot;vite-plugin-markdown&amp;quot;);
import vitePluginVue from &amp;quot;.&amp;#x2F;plugins&amp;#x2F;vue-custom-blocks-plugin&amp;quot;;

export default defineConfig({
  plugins: [vue(),
    md.plugin({
    mode: [&amp;quot;html&amp;quot;, &amp;quot;vue&amp;quot;],
  }),
  vitePluginVue]
})

&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;5.3. 封装Preview组件展示&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-TYPESCRIPT&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;pre&amp;quot;&amp;gt;
    &amp;lt;h2&amp;gt;
      {{ component.__sourceCodeTitle }}
      &amp;lt;Button @click=&amp;quot;hideCode&amp;quot; v-if=&amp;quot;codeVisible&amp;quot;&amp;gt;隐藏代码&amp;lt;&amp;#x2F;Button&amp;gt;
      &amp;lt;Button @click=&amp;quot;showCode&amp;quot; v-else&amp;gt;查看代码&amp;lt;&amp;#x2F;Button&amp;gt;
    &amp;lt;&amp;#x2F;h2&amp;gt;
    &amp;lt;div class=&amp;quot;pre-component&amp;quot;&amp;gt;
      &amp;lt;component :is=&amp;quot;component&amp;quot; &amp;#x2F;&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;

    &amp;lt;div class=&amp;quot;pre-code&amp;quot; v-if=&amp;quot;codeVisible&amp;quot;&amp;gt;
      &amp;lt;pre class=&amp;quot;language-html&amp;quot;&amp;gt;{{ component__sourceCOde }}&amp;lt;&amp;#x2F;pre&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script setup lang=&amp;quot;ts&amp;quot;&amp;gt;
import Button from &amp;quot;..&amp;#x2F;lib&amp;#x2F;button&amp;#x2F;index.vue&amp;quot;;
import { computed, ref } from &amp;quot;vue&amp;quot;;
const props = defineProps({
  component: Object,
});

const showCode = () =&amp;gt; (codeVisible.value = true);
const hideCode = () =&amp;gt; (codeVisible.value = false);
const codeVisible = ref(false);
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;style lang=&amp;quot;scss&amp;quot; scoped&amp;gt;
$border-color: #d9d9d9;

.pre {
  border: 1px solid $border-color;
  margin: 16px 0px 32px;
  max-width: 700px;
  min-width: 300px;
  &amp;gt; h2 {
    font-size: 20px;
    padding: 8px 16px;
    border-bottom: 1px solid $border-color;
    display: flex;
    justify-content: space-between;
  }

  &amp;amp;-component {
    padding: 16px;
  }

  &amp;amp;-actions {
    padding: 8px 16px;
    border-top: 1px dashed $border-color;
  }

  &amp;amp;-code {
    padding: 8px 16px;
    border-top: 1px dashed $border-color;

    &amp;gt; pre {
      line-height: 1.1;
      font-family: Consolas, &amp;quot;Courier New&amp;quot;, Courier, monospace;
      margin: 0;
      background-color: #fff;
    }
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;

&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;5.4. 使用Preview组件&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;views/doc/button/index.vue&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;Preview :component=&amp;quot;Button1&amp;quot; &amp;#x2F;&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script setup&amp;gt;
import Button1 from &amp;quot;.&amp;#x2F;Button1.preview.vue&amp;quot;;
import Preview from &amp;quot;..&amp;#x2F;..&amp;#x2F;..&amp;#x2F;components&amp;#x2F;Preview.vue&amp;quot;;
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;style lang=&amp;quot;scss&amp;quot;&amp;gt;
.jw-button + .jw-button {
  margin-left: 20px;
}
&amp;lt;&amp;#x2F;style&amp;gt;

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;/views/doc/button/Button1.preview.vue&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-TYPESCRIPT&quot;&gt;&lt;code&gt;&amp;lt;preview&amp;gt;基础示例&amp;lt;&amp;#x2F;preview&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;Button &amp;#x2F;&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script setup lang=&amp;quot;ts&amp;quot;&amp;gt;
import Button from &amp;quot;..&amp;#x2F;..&amp;#x2F;..&amp;#x2F;lib&amp;#x2F;button&amp;#x2F;index.vue&amp;quot;;
&amp;lt;&amp;#x2F;script&amp;gt;

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在，只要编写上面的以.preview.vue后缀的文件就行了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;效果：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ce6bd9f36ea7477a9cbda782701cd462~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;5.5. 高亮源代码&lt;/h2&gt;
&lt;p&gt;下载prismjs&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;yarn add prismjs
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对Preview组件做修改&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-typescript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;pre&amp;quot;&amp;gt;
    &amp;lt;h2&amp;gt;
      {{ component.__sourceCodeTitle }}
      &amp;lt;Button @click=&amp;quot;hideCode&amp;quot; v-if=&amp;quot;codeVisible&amp;quot;&amp;gt;隐藏代码&amp;lt;&amp;#x2F;Button&amp;gt;
      &amp;lt;Button @click=&amp;quot;showCode&amp;quot; v-else&amp;gt;查看代码&amp;lt;&amp;#x2F;Button&amp;gt;
    &amp;lt;&amp;#x2F;h2&amp;gt;
    &amp;lt;div class=&amp;quot;pre-component&amp;quot;&amp;gt;
      &amp;lt;component :is=&amp;quot;component&amp;quot; &amp;#x2F;&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;

    &amp;lt;div class=&amp;quot;pre-code&amp;quot; v-if=&amp;quot;codeVisible&amp;quot;&amp;gt;
      &amp;lt;pre class=&amp;quot;language-html&amp;quot; v-html=&amp;quot;html&amp;quot; &amp;#x2F;&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script setup lang=&amp;quot;ts&amp;quot;&amp;gt;
import Button from &amp;quot;..&amp;#x2F;lib&amp;#x2F;button&amp;#x2F;index.vue&amp;quot;;
import { computed, ref } from &amp;quot;vue&amp;quot;;
import &amp;quot;prismjs&amp;quot;;
import &amp;quot;prismjs&amp;#x2F;themes&amp;#x2F;prism.css&amp;quot;;
const Prism = (window as any).Prism;
const props = defineProps({
  component: Object,
});

console.log(props.component.__sourceCode);
const html = computed(() =&amp;gt; {
  return Prism.highlight(
    props.component.__sourceCode,
    Prism.languages.html,
    &amp;quot;html&amp;quot;
  );
});
const showCode = () =&amp;gt; (codeVisible.value = true);
const hideCode = () =&amp;gt; (codeVisible.value = false);
const codeVisible = ref(false);
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;效果&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/700c8478db074bb8a18fd57d130c3f0e~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h1&gt;6. 去掉示例中的文件导入&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/381de80dcb194c2b837f234f31d47de8~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;6.1. 在lib目录下创建main.ts 这个也是作为之后打包上传至npm的入口&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-TYPESCRIPT&quot;&gt;&lt;code&gt;import { App } from &amp;quot;vue&amp;quot;;
import JwButton from &amp;quot;.&amp;#x2F;button&amp;#x2F;index.vue&amp;quot;;
export {  JwButton };
const components = [JwButton];

&amp;#x2F;&amp;#x2F; 全局注册主键
export function registerJwUi(app: App): void {
  for (const component of components) {
    app.component(component.name, component);
  }
}

export default registerJwUi;

&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;6.2. main.ts中导入注册&lt;/h2&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;import JwUi from &amp;quot;.&amp;#x2F;lib&amp;#x2F;index&amp;quot;;
app.use(JwUi);
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;6.3. 这样在示例中就可以直接用了&lt;code&gt;/src/views/doc/button/Button1.preview&lt;/code&gt;&lt;/h2&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;preview&amp;gt;基础示例&amp;lt;&amp;#x2F;preview&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;jw-button &amp;#x2F;&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;6.4. 效果&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9b4ffbecdfe84d0f98af14330f981490~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h1&gt;7. 部署到github官网&lt;/h1&gt;
&lt;h2&gt;7.1. 打包&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-shell&quot;&gt;&lt;code&gt;yarn build
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;7.2. 上传至github&lt;/h2&gt;
&lt;p&gt;github创建一个新的仓库
将dist上传只仓库&lt;/p&gt;
&lt;h2&gt;7.3. 进入仓库Settings最底层&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f8fd96278942417b8129f2ba18d45fe8~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;7.4. 找到GitHub Pages&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/991888189a03436e98e73cb0cbcd7125~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;7.5. 选择master分支 点击保存 链接就生成了&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d1a3ee7e034843bc9422a13c99a2ec55~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;7.6 一键部署&lt;/h2&gt;
&lt;p&gt;创建deploy.sh文件&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-SH&quot;&gt;&lt;code&gt;rm -rf dist &amp;amp;&amp;amp;
yarn build &amp;amp;&amp;amp;
cd dist &amp;amp;&amp;amp;
git init &amp;amp;&amp;amp;
git add . &amp;amp;&amp;amp;
git commit -m &amp;quot;update&amp;quot; &amp;amp;&amp;amp;
git branch -M master &amp;amp;&amp;amp;
git remote add origin git@github.com:coderyjw&amp;#x2F;jw-ui-website.git &amp;amp;&amp;amp;
git push -f -u origin master &amp;amp;&amp;amp;
cd -
echo https:&amp;#x2F;&amp;#x2F;coderyjw.github.io&amp;#x2F;jw-ui-website&amp;#x2F;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;执行命令&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;sh deploy.sh
&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;8. 上传至npm&lt;/h1&gt;
&lt;h2&gt;8.1. 创建rollup.config.js配置文件&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-JAVASCRIPT&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 为了保证版本一致，请复制我的 package.json 到你的项目，并把 name 改成你的库名
import esbuild from &amp;#x27;rollup-plugin-esbuild&amp;#x27;
import vue from &amp;#x27;rollup-plugin-vue&amp;#x27;
import scss from &amp;#x27;rollup-plugin-scss&amp;#x27;
import dartSass from &amp;#x27;sass&amp;#x27;;
import { terser } from &amp;quot;rollup-plugin-terser&amp;quot;
import alias from &amp;#x27;@rollup&amp;#x2F;plugin-alias&amp;#x27;
import path from &amp;quot;path&amp;quot;;
import resolve from &amp;#x27;rollup-plugin-node-resolve&amp;#x27;

export default {
  input: &amp;#x27;src&amp;#x2F;lib&amp;#x2F;index.ts&amp;#x27;,
  output: [{
    globals: {
      vue: &amp;#x27;Vue&amp;#x27;
    },
    name: &amp;#x27;Yjw-ui&amp;#x27;,
    file: &amp;#x27;dist&amp;#x2F;lib&amp;#x2F;yjw-ui.js&amp;#x27;,
    format: &amp;#x27;umd&amp;#x27;,
    plugins: [terser()]
  }, {
    name: &amp;#x27;Yjw-ui&amp;#x27;,
    file: &amp;#x27;dist&amp;#x2F;lib&amp;#x2F;yjw-ui.esm.js&amp;#x27;,
    format: &amp;#x27;es&amp;#x27;,
    plugins: [terser()]
  }],
  plugins: [
    scss({ include: &amp;#x2F;&amp;#x5C;.scss$&amp;#x2F;, sass: dartSass }),
    esbuild({
      include: &amp;#x2F;&amp;#x5C;.[jt]s$&amp;#x2F;,
      minify: process.env.NODE_ENV === &amp;#x27;production&amp;#x27;,
      target: &amp;#x27;es2015&amp;#x27; 
    }),
    vue({
      include: &amp;#x2F;&amp;#x5C;.vue$&amp;#x2F;,
    }),
    alias({
      entries: [
        {
          find: &amp;#x27;@&amp;#x27;, &amp;#x2F;&amp;#x2F; 别名名称，作为依赖项目需要使用项目名
          replacement: path.resolve(__dirname, &amp;#x27;src&amp;#x27;), 
          customResolver: resolve({
            extensions: [&amp;#x27;.js&amp;#x27;, &amp;#x27;.jsx&amp;#x27;, &amp;#x27;.vue&amp;#x27;, &amp;#x27;.sass&amp;#x27;, &amp;#x27;.scss&amp;#x27;]
          })
        }
      ]
    }),
  ],
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;8.2. 执行命令打包&lt;/h2&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;rollup -c
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;8.3. 效果&lt;/h2&gt;
&lt;p&gt;可以看到dist文件下有lib文件，就是打包后的文件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/521cd318fe4e48f5b235d88845e0be92~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;8.4. 上传至npm&lt;/h2&gt;
&lt;p&gt;需要先注册npm账号&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-shell]&quot;&gt;&lt;code&gt;npm login &amp;#x2F;&amp;#x2F; 先登录
npm publish &amp;#x2F;&amp;#x2F; 发布
&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;9. 最后&lt;/h1&gt;
&lt;p&gt;ok,终于写完了，如果你觉得我写的不错，麻烦点个赞再走吧~&lt;/p&gt;
&lt;p&gt;如果觉得我写的有错的，麻烦指出再点个赞鼓励一下吧。&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 前端想进大厂QAQ
        原文链接：&lt;a href=&quot;https://juejin.im/post/7052717075168493598&quot;&gt;https://juejin.im/post/7052717075168493598&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Tue, 18 Jan 2022 05:15:00 GMT</pubDate></item><item><title>Hello @idux, 一个全新的 vue 3.x 组件库来了__Vue.js__开源</title><link>http://vue-js.com/topic/61e63ec4ad745b003c9df791</link><guid>http://vue-js.com/topic/61e63ec4ad745b003c9df791</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: awesome-green&lt;/h2&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;本文作者：&lt;strong&gt;&lt;a href=&quot;https://github.com/danranVm&quot;&gt;DanranVm&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;@idux&lt;/code&gt; 是一个基于 &lt;code&gt;Vue 3.x&lt;/code&gt; 和 &lt;code&gt;TypeScript&lt;/code&gt; 开发的开源组件库，它拥有以下特性:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Monorepo 管理模式：&lt;code&gt;cdk&lt;/code&gt;, &lt;code&gt;components&lt;/code&gt;, &lt;code&gt;pro&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;开箱即用的高质量组件&lt;/li&gt;
&lt;li&gt;全面拥抱 Composition API，从源码到文档&lt;/li&gt;
&lt;li&gt;完全使用 TypeScript 开发，提供完整的类型定义&lt;/li&gt;
&lt;li&gt;灵活的全局配置&lt;/li&gt;
&lt;li&gt;深入细节的主题定制能力&lt;/li&gt;
&lt;li&gt;国际化语言支持&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;相关链接：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Github: &lt;a href=&quot;https://github.com/IDuxFE/idux&quot;&gt;IDuxFE/idux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;官方文档: &lt;a href=&quot;https://idux.site&quot;&gt;idux.site&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;特性&lt;/h2&gt;
&lt;h3&gt;Monorepo 模式&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@idux&lt;/code&gt; 总共分成了 3 个包:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@idux/cdk&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;它是一组用于构建 UI 组件库的开发套件&lt;/li&gt;
&lt;li&gt;它主要包含了一些 UI 无关或者轻 UI 的功能&lt;/li&gt;
&lt;li&gt;其灵感来自于 &lt;a href=&quot;https://material.angular.cn/cdk&quot;&gt;@angular/cdk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@idux/components&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;它是传统意义上的基础组件库，包含了数十种常规 UI 组件&lt;/li&gt;
&lt;li&gt;我们对每一个组件的 API 都进行了精心的设计，保证了 API 一致性&lt;/li&gt;
&lt;li&gt;组件设计参考了 &lt;a href=&quot;https://ant.design/components/overview-cn/&quot;&gt;antd&lt;/a&gt; 和 &lt;a href=&quot;https://ng.ant.design/docs/introduce/zh&quot;&gt;ng-zorro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@idux/pro&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;它是对 &lt;code&gt;@idux/components&lt;/code&gt; 的进一步封装与组合，使用更少的代码去完成业务开发&lt;/li&gt;
&lt;li&gt;它主要来源于我们对自身业务的提炼&lt;/li&gt;
&lt;li&gt;它目前数量有限，我们会逐渐补充&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;开箱即用&lt;/h3&gt;
&lt;p&gt;完全基于 &lt;code&gt;esm&lt;/code&gt; 构建的包，支持开箱即用的 Tree Shaking 和按需加载，也可以再配合一些自动导入插件，可以让你的编码更加高效&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-ts&quot;&gt;&lt;code&gt;import { IxButton } from &amp;quot;@idux&amp;#x2F;components&amp;#x2F;button&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;更多内容请参见&lt;a href=&quot;https://idux.site/docs/getting-started/zh&quot;&gt;快速上手&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我们有覆盖率 &lt;code&gt;80+&lt;/code&gt; 的单元测试和完善的 CI 流程，来保证我们的组件质量&lt;/p&gt;
&lt;h3&gt;Composition API&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Vue 3.x&lt;/code&gt; 的一个重要特性就是 Composition API，我们所有的源代码和示例代码，都使用它来完成&lt;/p&gt;
&lt;p&gt;它其实是一种全新的代码组织范式，让开发者可以更好的组织代码，越是复杂的组件，就越能体现它的优雅&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2c08464bc34842c2801414a92e36c1c9~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;table&quot;&gt;&lt;/p&gt;
&lt;h3&gt;TypeScript&lt;/h3&gt;
&lt;p&gt;我们所有的源代码都是由 &lt;code&gt;TypeScript&lt;/code&gt; + &lt;code&gt;TSX&lt;/code&gt; 编写，为了给用户提供最佳的使用体验，我们为此付出了诸多努力&lt;/p&gt;
&lt;p&gt;无论用户使用 &lt;code&gt;TSX&lt;/code&gt; 还是使用 &lt;code&gt;SFC&lt;/code&gt; (需要配合 &lt;code&gt;Vue Language Features&lt;/code&gt; 插件)都可以享受到完整的类型提示与保护&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cbe40b4deee2452e93b1fc5a4a8ef6c9~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;button&quot;&gt;&lt;/p&gt;
&lt;h3&gt;国际化&lt;/h3&gt;
&lt;p&gt;我们针对部分组件的静态文案内置了 &lt;code&gt;i18n&lt;/code&gt; 的支持，不过目前仅支持中文和英文&lt;/p&gt;
&lt;p&gt;PR Welcome, 我们也渴望得到社区力量的支持和帮助，让 &lt;code&gt;@idux&lt;/code&gt; 变得更加完善&lt;/p&gt;
&lt;p&gt;更多内容请参见&lt;a href=&quot;https://idux.site/docs/i18n/zh&quot;&gt;国际化&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;全局配置&lt;/h3&gt;
&lt;p&gt;我们给众多组件添加了全局配置功能，这应该是你见过全局配置最多的组件库之一&lt;/p&gt;
&lt;p&gt;用户可以通过全局配置来定义组件的默认行为，从而减少在模板中需要写的代码&lt;/p&gt;
&lt;p&gt;支持在运行时修改全局配置项，还支持局部覆盖全局配置项&lt;/p&gt;
&lt;p&gt;更多内容请参见&lt;a href=&quot;https://idux.site/docs/config/zh&quot;&gt;全局配置&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;主题定制&lt;/h3&gt;
&lt;p&gt;我们尽最大努力地提供了丰富的主题变量，既有全局变量，也有组件级别的变量
便于用户百变百搭，灵活定制自己想要的主题&lt;/p&gt;
&lt;p&gt;注意: 当前该功能并不稳定，我们正在考虑使用 &lt;code&gt;css&lt;/code&gt; 变量的方案替换掉 &lt;code&gt;less&lt;/code&gt; 变量
等待方案完善后，我们会在每个组件的文档中声明所支持的主题变量&lt;/p&gt;
&lt;h2&gt;更多特性&lt;/h2&gt;
&lt;h3&gt;响应式表单系统&lt;/h3&gt;
&lt;p&gt;响应式表单系统提供了一种模型驱动的方式来处理表单输入，参考了 &lt;code&gt;@angular/forms&lt;/code&gt; 的实现&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;显式的表单模型：&lt;code&gt;FormGroup&lt;/code&gt;, &lt;code&gt;FormArray&lt;/code&gt;, &lt;code&gt;FormControl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;响应式的表单状态：&lt;code&gt;value&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;统一且易扩展的表单验证：&lt;code&gt;Validators&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你使用过 &lt;code&gt;@angular/forms&lt;/code&gt;, 那么你对下面的代码应该会感觉到很熟悉&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d5ff26d948ce47d1b6aebcd41d65089d~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;forms&quot;&gt;&lt;/p&gt;
&lt;h3&gt;更加受控的组件&lt;/h3&gt;
&lt;p&gt;我们为所有组件支持 &lt;code&gt;v-model&lt;/code&gt; 的 &lt;code&gt;Props&lt;/code&gt; 都添加了受控模式，参见 &lt;a href=&quot;https://github.com/IDuxFE/idux/issues/510&quot;&gt;issue #510&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;其他的 &lt;code&gt;Props&lt;/code&gt; 绝大多数也都是受控的，这是来自我们内部低代码平台的需求，他们需要完全受控的组件。&lt;/p&gt;
&lt;h3&gt;数据驱动理念的 API 设计&lt;/h3&gt;
&lt;p&gt;对于一些复杂组件，例如，&lt;code&gt;table&lt;/code&gt;, &lt;code&gt;tree&lt;/code&gt;, &lt;code&gt;menu&lt;/code&gt;, 我们更加推荐用户使用数据源(&lt;code&gt;dateSource&lt;/code&gt;)而不是在直接在 &lt;code&gt;template&lt;/code&gt; 中使用子组件。&lt;/p&gt;
&lt;p&gt;在实际的开发过程中，数据源往往是动态的，直接操作数据会比在 &lt;code&gt;template&lt;/code&gt; 中使用 &lt;code&gt;v-if&lt;/code&gt;, &lt;code&gt;v-for&lt;/code&gt; 更加直接，也更利于维护。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-diff&quot;&gt;&lt;code&gt;- &amp;lt;IxMenu&amp;gt;
-   &amp;lt;template v-for=&amp;quot;item in dataSource&amp;quot;&amp;gt;
-     &amp;lt;IxMenuItem v-if=&amp;quot;item.type === &amp;#x27;item&amp;#x27;&amp;quot; :key=&amp;quot;item.key&amp;quot; :icon=&amp;quot;item.icon&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;IxMenuItem&amp;gt;
-     &amp;lt;IxMenuDivider v-else-if=&amp;quot;item.type === &amp;#x27;divider&amp;#x27;&amp;quot; :key=&amp;quot;item.key&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;IxMenuDivider&amp;gt;
-   &amp;lt;&amp;#x2F;template&amp;gt;
- &amp;lt;&amp;#x2F;IxMenu&amp;gt;

+ &amp;lt;IxMenu :dataSource=&amp;quot;dataSource&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;IxMenu&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;更多特性等待你的发掘，快来上手体验吧。&lt;/p&gt;
&lt;h2&gt;下一步计划&lt;/h2&gt;
&lt;p&gt;目前只是 &lt;code&gt;@idux&lt;/code&gt; 的开始，它还有许多不够完善的地方，接下来我们将在持续的迭代中完善它，这是我们的下一步计划：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;组件文档的完善与补充&lt;/li&gt;
&lt;li&gt;视觉优化以及设计指南完善与补充&lt;/li&gt;
&lt;li&gt;单元测试的完善与补充&lt;/li&gt;
&lt;li&gt;主题变量方案的完善&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外，我们也在准备一个后台管理系统的模板，以供大家快速的搭建项目原型，敬请期待。&lt;/p&gt;
&lt;p&gt;最后，开源不易，希望 &lt;code&gt;@idux&lt;/code&gt; 能够得到大家的喜欢和支持。我们也将不忘开源初心，积极拥抱社区，依靠社区的力量来让 &lt;code&gt;@idux&lt;/code&gt; 获得更好的成长。非常欢迎热爱开源的朋友来一起参与 &lt;code&gt;@idux&lt;/code&gt; 项目，无论是 &lt;code&gt;star&lt;/code&gt;, &lt;code&gt;issue&lt;/code&gt; 还是 &lt;code&gt;PR&lt;/code&gt; 都是对我们的帮助与支持，感谢。&lt;/p&gt;
&lt;h2&gt;关于 IDuxFE&lt;/h2&gt;
&lt;p&gt;我们是来自深信服科技的前端团队，&lt;code&gt;@idux&lt;/code&gt; 已经在云计算&amp;amp;安全多场景落地，未来会实现集团内全产品覆盖，所以不用担心我们弃坑，放弃维护。&lt;/p&gt;
&lt;p&gt;如果对我们感兴趣的话，欢迎加入我们，可以投递简历到 &lt;code&gt;uedc@sangfor.com.cn&lt;/code&gt; 或者直接私信我。&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: IDuxFE
        原文链接：&lt;a href=&quot;https://juejin.im/post/7042565546189488159&quot;&gt;https://juejin.im/post/7042565546189488159&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Tue, 18 Jan 2022 04:15:00 GMT</pubDate></item><item><title>vue3和Vite双向加持，uni-app性能再次提升__uni-app__Vue.js</title><link>http://vue-js.com/topic/61e630b4cbbfd1003b11fa1c</link><guid>http://vue-js.com/topic/61e630b4cbbfd1003b11fa1c</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;code&gt;uni-app&lt;/code&gt;对&lt;code&gt;vue3 &amp;amp; Vite&lt;/code&gt;的升级，是一个渐进式过程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2020年9月：小程序平台支持 vue3 开发，小程序平台编译器依然使用webpack；&lt;/li&gt;
&lt;li&gt;2021年5月：H5平台支持 vue3 开发，H5平台编译器升级为 Vite；&lt;/li&gt;
&lt;li&gt;2021年8月：App平台支持 vue3 开发，App平台编译器升级为 Vite；&lt;/li&gt;
&lt;li&gt;2021年11月：小程序平台编译器升级为 Vite；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;至此，&lt;code&gt;uni-app&lt;/code&gt;在全平台支持了 &lt;code&gt;Vite&lt;/code&gt; 编译及&lt;code&gt;Vue 3.x&lt;/code&gt; 运行。&lt;/p&gt;
&lt;p&gt;so，这场持续一年之久的大版本升级，究竟给&lt;code&gt;uni-app&lt;/code&gt;项目带来了哪些提升？&lt;/p&gt;
&lt;p&gt;是时候总结（秀）一波了。&lt;/p&gt;
&lt;p&gt;新版 uni-app 框架主要做了三大改进：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;重写框架内核：基于&lt;code&gt;vue3 + ts&lt;/code&gt;重写内置组件和API，实现更彻底、更高效的&lt;code&gt;tree-shaking&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;新增支持 Vite 构建工具，在H5平台实现秒开预览；&lt;/li&gt;
&lt;li&gt;新增支持 Vue3.x，实现更灵活的开发方式，及更高的运行性能；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;基于这三大改进，uni-app项目获得了&lt;strong&gt;多快好省&lt;/strong&gt;四大收益：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;更多的语法支持，支持组合式API，业务聚焦，开发效率更高；&lt;/li&gt;
&lt;li&gt;更快的编译速度，H5平台十倍加速，小程序、App加速30%以上；&lt;/li&gt;
&lt;li&gt;更好的运行性能，用户端响应更快，体验更好；&lt;/li&gt;
&lt;li&gt;更小的代码体积，瘦身30%以上，更省体积、更省流量&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;更多的语法支持&lt;/h2&gt;
&lt;p&gt;新版&lt;code&gt;uni-app&lt;/code&gt;支持Vue 3.x框架，支持组合式API，可实现更聚焦的业务开发。&lt;/p&gt;
&lt;p&gt;Vue 3.x的一些新增特性，&lt;code&gt;uni-app&lt;/code&gt;也已经完全支持，如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;支持&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;支持&lt;code&gt;&amp;lt;style scoped&amp;gt;&lt;/code&gt;、&lt;code&gt;&amp;lt;style module&amp;gt;&lt;/code&gt;、&lt;code&gt;State-Driven Dynamic CSS(v-bind)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;支持&lt;code&gt;jsx&lt;/code&gt;、&lt;code&gt;tsx&lt;/code&gt;（h5,app 平台支持，小程序不支持）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外，在小程序平台，新版&lt;code&gt;uni-app&lt;/code&gt;也扩展了更多的语法，如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;更完善的模板语法支持（如 &lt;code&gt;class&lt;/code&gt;、&lt;code&gt;style&lt;/code&gt; 支持函数、变量等，不再局限数组、对象类型）&lt;/li&gt;
&lt;li&gt;更完整的 &lt;code&gt;props&lt;/code&gt; 支持（如传递函数）&lt;/li&gt;
&lt;li&gt;更完善的 &lt;code&gt;slot&lt;/code&gt; 支持（如作用域插槽）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;更快的编译速度&lt;/h2&gt;
&lt;p&gt;开发者日常工作中，最无聊的就是等待编译构建。&lt;/p&gt;
&lt;p&gt;某乎上还有一个”程序员在等待编译的时候都做什么？“的讨论帖，可见编译时间对开发者而言，是一个多么尴尬无聊的碎片时间。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;uni-app&lt;/code&gt;本次升级&lt;code&gt;vue3 &amp;amp; Vite&lt;/code&gt;后，在编译时间上有多少改进？带给开发者多少福利？我们安排真实测试，以数据说话。&lt;/p&gt;
&lt;p&gt;测试环境说明：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;硬件：RedmiBook 14 二代
处理器：Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz 
内存：16.0 GB
操作系统：Windows 11 专业版 64 位操作系统
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;关于编译速度，我们做了两个维度的对比：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;纵向对比：挑选&lt;code&gt;uni-app&lt;/code&gt;常用项目模板，在H5、小程序、App平台，分别测试&lt;code&gt;vue 2.6&lt;/code&gt;和&lt;code&gt;vue 3.x&lt;/code&gt;的编译时间&lt;/li&gt;
&lt;li&gt;横向对比：使用业内优秀的其它跨端框架，创建默认项目模板，记录其编译时间，和&lt;code&gt;uni-app&lt;/code&gt;的&lt;code&gt;vue 3.x&lt;/code&gt;版本进行对比&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;uni-app 历史版本纵向对比&lt;/h3&gt;
&lt;p&gt;我们选择&lt;code&gt;uni-app默认模板&lt;/code&gt;、&lt;code&gt;uni-starter&lt;/code&gt;、&lt;code&gt;hello-uniapp&lt;/code&gt;三个项目模板，分别测试&lt;code&gt;vue 2.6&lt;/code&gt;和&lt;code&gt;vue 3.x&lt;/code&gt;的编译时间。&lt;/p&gt;
&lt;p&gt;uni-app项目编译时间的采集方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;vue 2.6&lt;/code&gt;版本编译时间 = webpack 的 stats.endTime - stats.startTime&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vue 3.x&lt;/code&gt;版本编译时间 = 构建工具入口处记录 global.__vite_start_time = performance.now()，构建工具编译完成时：performance.now() - global.__vite_start_time&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;H5平台&lt;/h4&gt;
&lt;p&gt;对&lt;code&gt;uni-app&lt;/code&gt;的三个项目模板分别运行到H5平台，进行多次编译测试，并求其均值后，获得如下数据：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8e2353cca266411d825875799c4e3131~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;由此，我们可以观察到：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在&lt;code&gt;vue 2.6&lt;/code&gt;环境下，随着项目复杂度的提升，H5首页预览所需编译时间会直线增加；这是因为在&lt;code&gt;vue 2.6&lt;/code&gt;版本下，虽然仅预览首页，但依然会使用 &lt;code&gt;webpack&lt;/code&gt; 编译整个项目资源；故项目越复杂，编译时间越长；&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;vue 3.x&lt;/code&gt;环境下，H5首页预览的编译时间跟项目复杂度也有关系，但增幅不大；这是因为在&lt;code&gt;vue 3.x&lt;/code&gt;版本下，使用 &lt;code&gt;Vite&lt;/code&gt; 进行构建，预览首页时仅编译首页及首页所依赖资源，不会编译其它页面资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过图表对比，我们可以直观得出结论：&lt;code&gt;vue 3.x&lt;/code&gt;环境下的首页编译时间，平均不到&lt;code&gt;vue 2.6&lt;/code&gt;环境下的十分之一。&lt;/p&gt;
&lt;p&gt;换言之，&lt;code&gt;vue 3.x&lt;/code&gt;版本下的首页编译速度，相比&lt;code&gt;vue 2.6&lt;/code&gt;版本，有&lt;strong&gt;十倍效率提升&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这个十倍效率提升，主要得益于新版采用&lt;code&gt;Vite&lt;/code&gt;作为构建工具，由此带来了两大好处：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用原生 ESM 文件，无需打包，实现极速的服务启动；&lt;/li&gt;
&lt;li&gt;预览（运行）使用&lt;code&gt;esbuild&lt;/code&gt;作为打包工具，相比&lt;code&gt;vue 2.6&lt;/code&gt;环境下的&lt;code&gt;webpack&lt;/code&gt;，构建速度快 10-100 倍（这不是我们夸大，详见&lt;a href=&quot;https://esbuild.docschina.org/&quot;&gt;esbuild&lt;/a&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本着这个&lt;strong&gt;十倍效率提升&lt;/strong&gt;，小伙伴们还不赶紧上手试试？&lt;/p&gt;
&lt;h4&gt;小程序平台&lt;/h4&gt;
&lt;p&gt;对&lt;code&gt;uni-app&lt;/code&gt;的三个模板项目运行到小程序平台，多次编译测试，并求其均值后，获得如下数据：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6075ea69904d4fcdbe1977ed2ff4d1a7~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;从上图对比数据来看，我们可以得出结论：小程序平台，&lt;code&gt;vue 3.x&lt;/code&gt;版本下的运行编译，相比&lt;code&gt;vue 2.6&lt;/code&gt;版本，编译性能至少提升30%；且项目越复杂，编译性能提升越明显，可以达到40% ~ 50%。&lt;/p&gt;
&lt;h4&gt;App平台&lt;/h4&gt;
&lt;p&gt;对&lt;code&gt;uni-app&lt;/code&gt;的三个项目模板继续运行到App平台，多次编译测试，并求其均值后，获得如下数据：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/27f0ffcd92d24f08bd539273a913b9d9~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;vue3-08&quot;&gt;&lt;/p&gt;
&lt;p&gt;从上图对比数据来看，我们可以得出结论：App平台，&lt;code&gt;vue 3.x&lt;/code&gt;版本下的运行编译，相比&lt;code&gt;vue 2.6&lt;/code&gt;版本，编译性能提升将近50%。&lt;/p&gt;
&lt;p&gt;虽没有H5平台的十倍效率提升那么刺激，但将近50%的速度提升，经常开发小程序/App的小伙伴，还不心动？&lt;/p&gt;
&lt;h3&gt;业内优秀框架横向对比&lt;/h3&gt;
&lt;p&gt;除了采用不同版本的&lt;code&gt;uni-app&lt;/code&gt;进行纵向对比外，我们还使用业内优秀的跨端框架&lt;code&gt;Taro&lt;/code&gt;，创建空的项目模板，进行横向对比测试。&lt;/p&gt;
&lt;p&gt;具体测试方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;安装&lt;code&gt;Taro&lt;/code&gt;的最新cli，本文测试时使用的版本为&amp;quot;@Tarojs/Taro&amp;quot;: “3.3.16”&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;Taro init&lt;/code&gt;命令，分别选择&lt;code&gt;react&lt;/code&gt;、&lt;code&gt;vue&lt;/code&gt;、&lt;code&gt;vue3&lt;/code&gt;框架，创建三个默认项目模板，三个项目名称分别为&lt;code&gt;taro3-react&lt;/code&gt;、&lt;code&gt;taro3-vue&lt;/code&gt;、&lt;code&gt;taro3-vue3&lt;/code&gt;，如下图：
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a796e778344f43798a2c80393c12918a~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;npm run dev:h5&lt;/code&gt;，运行到H5平台进行预览，记录每次预览编译时间，重复执行，求其均值&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;关于&lt;code&gt;Taro&lt;/code&gt;编译时间的计算方案：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;开发一个&lt;code&gt;Taro&lt;/code&gt;扩展插件，插件规范参考&lt;a href=&quot;http://Taro-docs.jd.com/Taro/docs/plugin/&quot;&gt;Taro官网 - 插件功能&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;ctx.onBuildStart&lt;/code&gt;中记录开始编译时间&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;ctx.onBuildFinish&lt;/code&gt;中记录编译结束时间&lt;/li&gt;
&lt;li&gt;两者的时间差，即为编译过程消耗时间&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;然后使用&lt;code&gt;uni-app&lt;/code&gt;的&lt;code&gt;cli&lt;/code&gt;命令行，创建基于&lt;code&gt;vue3.x&lt;/code&gt;的空项目模板，项目命名为&lt;code&gt;uni-app-vue3&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;我们使用各自框架的命令行，将如上创建的5个项目分别编译到H5平台和小程序平台，多次测试，并求其均值。&lt;/p&gt;
&lt;p&gt;同框架版本在H5平台上的编译时间，结果如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6ec0d69f1f4047adaf6daa6969e28383~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;vue3-18.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;从图中可以看出，&lt;code&gt;uni-app&lt;/code&gt;的&lt;code&gt;vue3&lt;/code&gt;版本，在H5平台上的首页编译预览性能是遥遥领先的。&lt;/p&gt;
&lt;p&gt;继续编译到小程序平台，多次测试，求其均值，结果如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dceaa0a94a51448cbf594d1139d0fdc1~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;从图中可以看出，&lt;code&gt;uni-app&lt;/code&gt;的&lt;code&gt;vue3&lt;/code&gt;版本，在小程序平台上的编译性能也是非常领先的。&lt;/p&gt;
&lt;h2&gt;更好的运行速度&lt;/h2&gt;
&lt;p&gt;开发环节编译快了，那面向最终用户的软件，运行性能怎么样？&lt;/p&gt;
&lt;p&gt;我们进入性能测试章节。&lt;/p&gt;
&lt;p&gt;测试方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开发内容：开发一个仿微博小程序首页的复杂长列表，支持下拉刷新、上拉翻页、点赞。&lt;/li&gt;
&lt;li&gt;界面如下：
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4dd6fb97b0c44992949eac222c422ed6~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/li&gt;
&lt;li&gt;测试机型：小米 Mi 10 pro、MIUI 12.5 (21.11.3 开发版) 、微信版本 8.0.16&lt;/li&gt;
&lt;li&gt;准备工作：每次开始测试前，杀掉各App进程、清空内存，保证测试机环境基本一致；每次从本地读取静态数据，屏蔽网络差异。&lt;/li&gt;
&lt;li&gt;评测点：长列表中的某个组件，比如点赞组件，点击时是否能及时的修改未赞和已赞状态？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;测试计时方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;选中某微博，点击“点赞”按钮，实现点赞状态状态切换（已赞高亮、未赞灰色），&lt;/li&gt;
&lt;li&gt;点赞按钮 onclick函数开头开始计时，setData回调函数开头结束计时；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在小米手机上进行多次测试，求其平均值，结果如下：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;记录条数&lt;/th&gt;
&lt;th&gt;200&lt;/th&gt;
&lt;th&gt;400&lt;/th&gt;
&lt;th&gt;600&lt;/th&gt;
&lt;th&gt;800&lt;/th&gt;
&lt;th&gt;1000&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;vue2&lt;/td&gt;
&lt;td&gt;30ms&lt;/td&gt;
&lt;td&gt;43ms&lt;/td&gt;
&lt;td&gt;56ms&lt;/td&gt;
&lt;td&gt;72ms&lt;/td&gt;
&lt;td&gt;90ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vue3&lt;/td&gt;
&lt;td&gt;8ms&lt;/td&gt;
&lt;td&gt;9ms&lt;/td&gt;
&lt;td&gt;9ms&lt;/td&gt;
&lt;td&gt;8ms&lt;/td&gt;
&lt;td&gt;9ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;从表格中可以看出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;随着页面记录的增加，&lt;code&gt;vue 2.6&lt;/code&gt;版本的&lt;code&gt;uni-app&lt;/code&gt;项目，点赞组件响应时间快速增加，响应越来越慢；&lt;/li&gt;
&lt;li&gt;基于&lt;code&gt;vue 3.x&lt;/code&gt;的&lt;code&gt;uni-app&lt;/code&gt;项目，点赞组件的响应时间跟页面条数无关，一直保持极高的响应灵敏度，性能体验远高于&lt;code&gt;vue 2.6&lt;/code&gt;版本。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从这个常见的长列表组件响应实验来看，&lt;code&gt;vue 3.x&lt;/code&gt;的性能体验要远高于&lt;code&gt;vue 2.6&lt;/code&gt;版本。&lt;/p&gt;
&lt;h2&gt;更小的代码体积&lt;/h2&gt;
&lt;p&gt;项目发行后的代码体积，是一个很重要的考量指标：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;H5平台：更小的代码体积，可以帮助开发者节省服务端带宽及CDN流量，可实现更快的资源加载及页面渲染；&lt;/li&gt;
&lt;li&gt;小程序平台：更小的代码体积，可加速小程序包的下载（甚至可能免了分包加载的繁琐），帮助用户更快进入小程序业务界面；&lt;/li&gt;
&lt;li&gt;App平台：更小的代码体积，可实现更快的App启动，帮助用户更快进入App首页&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了测试&lt;code&gt;vue 3.x&lt;/code&gt;新版升级后，代码体积的变化，我们同样做了两个维度的测试：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;纵向对比：选择&lt;code&gt;uni-app&lt;/code&gt;常用项目模板，在H5、小程序、App平台，分别测试&lt;code&gt;vue 2.6&lt;/code&gt;和&lt;code&gt;vue 3.x&lt;/code&gt;的编译包大小&lt;/li&gt;
&lt;li&gt;横向对比：使用业内优秀的其它跨端框架，创建默认项目模板，记录其编译后的包体积大小，和&lt;code&gt;uni-app&lt;/code&gt;版本进行对比&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tips：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开发阶段重在编译速度，对应&lt;code&gt;npm run dev&lt;/code&gt;操作&lt;/li&gt;
&lt;li&gt;发行阶段重在编译包大小，对应&lt;code&gt;npm run build&lt;/code&gt;操作&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;uni-app 不同版本纵向对比&lt;/h3&gt;
&lt;p&gt;我们复用之前创建的&lt;code&gt;uni-app默认模板&lt;/code&gt;、&lt;code&gt;uni-starter&lt;/code&gt;、&lt;code&gt;hello-uniapp&lt;/code&gt;三个项目模板，分别测试&lt;code&gt;vue 2.6&lt;/code&gt;和&lt;code&gt;vue 3.x&lt;/code&gt;的编译包体积。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;uni-app&lt;/code&gt;项目编译包体积的采集方式：编译到对应平台后，记录编译后文件夹的大小。&lt;/p&gt;
&lt;h4&gt;H5平台&lt;/h4&gt;
&lt;p&gt;H5平台编译后代码体积记录如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b054f08c66224e8cb203f3f45d8d89a0~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;从统计结果来看，&lt;code&gt;uni-app&lt;/code&gt;的&lt;code&gt;vue3.x&lt;/code&gt;版本，在H5平台上的编译包体积至少瘦身30%以上。&lt;/p&gt;
&lt;p&gt;H5平台的瘦身优化，主要得益于&lt;code&gt;uni-app&lt;/code&gt;框架的底层全面重构，实现了更彻底的摇树优化。&lt;/p&gt;
&lt;h4&gt;小程序平台&lt;/h4&gt;
&lt;p&gt;小程序平台编译后代码体积记录如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d20ce20582c944aa9702854af727110c~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;从统计结果来看，&lt;code&gt;uni-app&lt;/code&gt;的&lt;code&gt;vue3.x&lt;/code&gt;版本，在小程序平台上也有大幅瘦身。&lt;/p&gt;
&lt;h4&gt;App平台&lt;/h4&gt;
&lt;p&gt;App平台编译后代码体积记录如下：
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5c0460ee198844168bc35146c8f1c047~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;从统计结果来看，&lt;code&gt;uni-app&lt;/code&gt;的&lt;code&gt;vue3.x&lt;/code&gt;版本，在App平台上根据项目不同，会有不同幅度的瘦身。&lt;/p&gt;
&lt;p&gt;从理论上来讲，项目中的页面模板越复杂，App平台的瘦身效果越明显。&lt;/p&gt;
&lt;h3&gt;业内优秀框架横向对比&lt;/h3&gt;
&lt;p&gt;关于编译后的代码体积，我们也和业内优秀的跨端框架&lt;code&gt;Taro&lt;/code&gt;进行了对比，复用前面章节创建的三个&lt;code&gt;Taro&lt;/code&gt;项目，分别编译到H5平台和小程序平台，计算其编译后的源码文件夹大小。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/012e95bd1e3343e6b3c6e741a28a7e45~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;从图中可以看出，&lt;code&gt;uni-app&lt;/code&gt;的vue3版本，在H5平台上编译包体积是最小的，只有友商的十分之一左右。&lt;/p&gt;
&lt;p&gt;我们继续测试，不同版本框架发行到微信小程序平台，记录其编译包大小：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c6aa5fdca3084951ba2fb2ea0389032d~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;从图中可以看出，&lt;code&gt;uni-app&lt;/code&gt;的vue3版本，在小程序平台上编译包体积也是最小的。&lt;/p&gt;
&lt;p&gt;**Tips：**细心的开发者会发现，所有框架版本编译到小程序上的代码包体积都远小于其在H5平台上的包体积，这是因为小程序由平台厂商提供内置组件及接口实现，而H5平台则需跨端框架自己实现内置组件及接口，故H5平台的代码包普遍要大一些。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;综上，我们以数字说话，阐述了vue3版本&lt;code&gt;uni-app&lt;/code&gt;开发的诸多好处，再回顾一遍：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;更多的语法&lt;/li&gt;
&lt;li&gt;更快的编译&lt;/li&gt;
&lt;li&gt;更好的运行&lt;/li&gt;
&lt;li&gt;更少的代码&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;你还不赶紧升级新版&lt;code&gt;uni-app&lt;/code&gt;来试试吗？&lt;/p&gt;
&lt;p&gt;对文本测试过程及结果有疑问的同学，欢迎到github上提交&lt;a href=&quot;https://github.com/dcloudio/uni-app&quot;&gt;issue&lt;/a&gt;，欢迎指正。&lt;p&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
作者: CHB
原文链接：&lt;a href=&quot;https://juejin.im/post/7049913295242788871&quot;&gt;https://juejin.im/post/7049913295242788871&lt;/a&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>banyungong</author><pubDate>Tue, 18 Jan 2022 03:15:00 GMT</pubDate></item><item><title>是什么事让尤大如此生气？__Vue.js__前端框架__JavaScript</title><link>http://vue-js.com/topic/61e622a4cbbfd1003b11fa19</link><guid>http://vue-js.com/topic/61e622a4cbbfd1003b11fa19</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;这件事大约发生在半年前，有人在 &lt;a href=&quot;https://github.com/vitejs/vite&quot;&gt;vite&lt;/a&gt; 的仓库下提了一个关于 &lt;code&gt;vue-router&lt;/code&gt; 的 &lt;a href=&quot;https://github.com/vitejs/vite/issues/1775&quot;&gt;issue&lt;/a&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a111ba10af1e453a8d15b8a596999a11~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;angry.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;提问者的意思是 &lt;a href=&quot;https://github.com/vuejs/vue-router-next&quot;&gt;&lt;code&gt;vue-router&lt;/code&gt;&lt;/a&gt; 有个未解决的 bug，影响到他即将上线的项目了，而 &lt;code&gt;vue-router&lt;/code&gt; 的维护者没能解决这个问题，希望尤大来帮忙解决。&lt;/p&gt;
&lt;p&gt;但是，提问者跑到 vite 仓库下来发这个 issue，就显得非常不合适了。&lt;/p&gt;
&lt;p&gt;尤大显然对这个行为很不高兴，回复也很霸气：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;不要在不相关的仓库提不相关的 issue。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;大家都很忙，没空管就是没空管，不要催。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;再犯会被 block。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;提问者跑来这里发 issue 的理由是尤大经常活跃在 vite 社区。而在我看来，尤大应该早就知道这个 issue 了，因为 &lt;code&gt;vue-router&lt;/code&gt; 的维护者也是 Vue 核心成员之一，他们肯定私下聊过这个问题。&lt;/p&gt;
&lt;p&gt;那么为什么迟迟不解决这个问题呢，我猜测有两点原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;这个问题本身不好解决，可能要牵涉到不少的代码改动。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vue 团队正在全力做他们认为重要而紧急的事情，这个事情的优先级并不高。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;那么，提问者究竟是遇到了什么问题呢？为何我会关注到这个 issue？因为最近我也遇到一个类似的问题。&lt;/p&gt;
&lt;h2&gt;类似的问题&lt;/h2&gt;
&lt;p&gt;在我的 &lt;a href=&quot;https://coding.imooc.com/class/503.html&quot;&gt;《Vue3 开发企业级音乐课 App》课程&lt;/a&gt;问答区，有个学生反馈了一个问题：&lt;code&gt;RouterView&lt;/code&gt; 配合 &lt;code&gt;KeepAilve&lt;/code&gt; 组件使用后，二级路由的歌手详情页的 &lt;code&gt;created&lt;/code&gt; 钩子函数会执行两次。&lt;/p&gt;
&lt;p&gt;我测试后发现确实有这个问题，最开始我怀疑是 Vue3 或者是 &lt;code&gt;vue-router&lt;/code&gt; 某个版本的 bug，于是我把 Vue3 和 &lt;code&gt;vue-router&lt;/code&gt; 都升级到最新版本，发现这个问题仍然存在。&lt;/p&gt;
&lt;p&gt;那么，会不会是我的业务代码写出的问题呢？直觉告诉不会，为了找出问题的根本原因，同时减少调试的复杂度，我写了个最小化复现问题的 &lt;a href=&quot;https://github.com/ustbhuangyi/vue3-keep-alive-issue&quot;&gt;demo&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;Demo 页面共有二级路由，它的定义如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;import { createRouter, createWebHashHistory } from &amp;#x27;vue-router&amp;#x27;
const Home = import(&amp;#x27;..&amp;#x2F;views&amp;#x2F;Home.vue&amp;#x27;)
const HomeSub = import(&amp;#x27;..&amp;#x2F;views&amp;#x2F;HomeSub.vue&amp;#x27;)
const Sub = import(&amp;#x27;..&amp;#x2F;views&amp;#x2F;Sub.vue&amp;#x27;)
const About = import(&amp;#x27;..&amp;#x2F;views&amp;#x2F;About.vue&amp;#x27;)

const routes = [
  {
    path: &amp;#x27;&amp;#x2F;&amp;#x27;,
    redirect: &amp;#x27;&amp;#x2F;home&amp;#x27;
  },
  {
    path: &amp;#x27;&amp;#x2F;home&amp;#x27;,
    name: &amp;#x27;Home&amp;#x27;,
    component: Home,
    children: [
      {
        path: &amp;#x27;sub&amp;#x27;,
        component: HomeSub
      }
    ]
  },
  {
    path: &amp;#x27;&amp;#x2F;about&amp;#x27;,
    name: &amp;#x27;About&amp;#x27;,
    component: About,
    children: [
      {
        path: &amp;#x27;sub&amp;#x27;,
        component: Sub
      }
    ]
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

export default router
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这里要注意，&lt;strong&gt;必须两个主路由页面都嵌套子路由&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;接着来看页面的几个 Vue 组件的定义，其中 &lt;code&gt;App.vue&lt;/code&gt; 为页面入口组件：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div id=&amp;quot;nav&amp;quot;&amp;gt;
    &amp;lt;router-link to=&amp;quot;&amp;#x2F;&amp;quot;&amp;gt;Home&amp;lt;&amp;#x2F;router-link&amp;gt; |
    &amp;lt;router-link to=&amp;quot;&amp;#x2F;about&amp;quot;&amp;gt;About&amp;lt;&amp;#x2F;router-link&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;router-view v-slot=&amp;quot;{ Component }&amp;quot;&amp;gt;
    &amp;lt;keep-alive&amp;gt;
      &amp;lt;component :is=&amp;quot;Component&amp;quot;&amp;#x2F;&amp;gt;
    &amp;lt;&amp;#x2F;keep-alive&amp;gt;
  &amp;lt;&amp;#x2F;router-view&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;Home.vue&lt;/code&gt; 是一级路由组件：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-vue&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;home&amp;quot;&amp;gt;
    &amp;lt;img alt=&amp;quot;Vue logo&amp;quot; src=&amp;quot;..&amp;#x2F;assets&amp;#x2F;logo.png&amp;quot;&amp;gt;
    &amp;lt;button @click=&amp;quot;showSub&amp;quot;&amp;gt;click me&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;router-view&amp;gt;&amp;lt;&amp;#x2F;router-view&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;

export default {
  name: &amp;#x27;Home&amp;#x27;,
  created() {
    console.log(&amp;#x27;home page created&amp;#x27;)
  },
  methods: {
    showSub() {
      this.$router.push(&amp;#x27;&amp;#x2F;home&amp;#x2F;sub&amp;#x27;)
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;HomeSub&lt;/code&gt; 是 &lt;code&gt;Home&lt;/code&gt; 组件中的二级路由组件：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-vue&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;This is home sub&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
  export default {
    name: &amp;#x27;HomeSub&amp;#x27;,
    created() {
      console.log(&amp;#x27;home sub created&amp;#x27;)
    }
  }
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;About.vue&lt;/code&gt; 是一级路由组件：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-vue&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;about&amp;quot;&amp;gt;
    &amp;lt;h1&amp;gt;This is an about page&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;button @click=&amp;quot;showSub&amp;quot;&amp;gt;click me&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;router-view&amp;gt;&amp;lt;&amp;#x2F;router-view&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
  export default {
    name: &amp;#x27;About&amp;#x27;,
    created() {
      console.log(&amp;#x27;about page created&amp;#x27;)
    },
    methods: {
      showSub() {
        this.$router.push(&amp;#x27;&amp;#x2F;about&amp;#x2F;sub&amp;#x27;)
      }
    }
  }
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;Sub.vue&lt;/code&gt; 是 &lt;code&gt;About&lt;/code&gt; 组件中的二级路由组件：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-vue&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;This is sub&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
  export default {
    name: &amp;#x27;Sub&amp;#x27;,
    created() {
      console.log(&amp;#x27;sub created&amp;#x27;)
    }
  }
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;复现的步骤很简单，首先进入 &lt;code&gt;Home&lt;/code&gt; 页：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/68df82eaef904298b4b8afed4dbcb276~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;home.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;然后点击 &lt;code&gt;About&lt;/code&gt; 标签进入 &lt;code&gt;About&lt;/code&gt; 页：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/02c7f013c4f0407b8c335b496cf72267~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;about.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;接着点击按钮，渲染 &lt;code&gt;Sub&lt;/code&gt; 子路由组件：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aa7459efc8e141e9b8705352695ca88d~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;about-sub.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;页面渲染都是正常的，但是我们发现 &lt;code&gt;Sub&lt;/code&gt; 组件的 &lt;code&gt;created&lt;/code&gt; 钩子函数执行了两次，输出了两次 &lt;code&gt;sub created&lt;/code&gt;。这就相当于渲染了两次 &lt;code&gt;Sub&lt;/code&gt; 组件，显然是有问题的。&lt;/p&gt;
&lt;h2&gt;bug 分析&lt;/h2&gt;
&lt;p&gt;我开启了调试大法，在 &lt;code&gt;Sub&lt;/code&gt; 组件的 &lt;code&gt;created&lt;/code&gt; 钩子函数中打上 &lt;code&gt;debugger&lt;/code&gt; 断点，然后顺着函数的调用堆栈一步步往前看。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bd529a3a867149a196a89ed04ca68d39~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;debugger.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;显然，&lt;code&gt;debugger&lt;/code&gt; 是在 &lt;code&gt;created&lt;/code&gt; 钩子函数内部，而该钩子函数的执行是在组件挂载阶段，那么是什么操作触发了组件的挂载呢？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/831ce83bbd4140abab4531628f091bb2~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;debugger1.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;顺着调用堆栈继续查找，我们发现最终原因是因为修改了路由中的 &lt;code&gt;currentRoute&lt;/code&gt;，触发了 &lt;code&gt;setter&lt;/code&gt;，然后触发了 &lt;code&gt;RouterView&lt;/code&gt; 组件的重新渲染，最终触发了 &lt;code&gt;Sub&lt;/code&gt; 组件的渲染。&lt;/p&gt;
&lt;p&gt;那么为什么 &lt;code&gt;currentRoute&lt;/code&gt; 的修改会触发 &lt;code&gt;RouterView&lt;/code&gt; 组件的重新渲染呢？这要从 &lt;code&gt;RouterView&lt;/code&gt; 的实现原理说起：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const RouterViewImpl = defineComponent({
  name: &amp;#x27;RouterView&amp;#x27;,
  inheritAttrs: false,
  props: {
    name: {
      type: String,
      default: &amp;#x27;default&amp;#x27;,
    },
    route: Object,
  },
  setup(props, { attrs, slots }) {
    (process.env.NODE_ENV !== &amp;#x27;production&amp;#x27;) &amp;amp;&amp;amp; warnDeprecatedUsage()
    const injectedRoute = inject(routerViewLocationKey)
    const routeToDisplay = computed(() =&amp;gt; props.route || injectedRoute.value)
    const depth = inject(viewDepthKey, 0)
    const matchedRouteRef = computed(() =&amp;gt; routeToDisplay.value.matched[depth])
    provide(viewDepthKey, depth + 1)
    provide(matchedRouteKey, matchedRouteRef)
    provide(routerViewLocationKey, routeToDisplay)
    const viewRef = ref()
    watch(() =&amp;gt; [viewRef.value, matchedRouteRef.value, props.name], ([instance, to, name], [oldInstance, from, oldName]) =&amp;gt; {
      if (to) {
        to.instances[name] = instance
        if (from &amp;amp;&amp;amp; from !== to &amp;amp;&amp;amp; instance &amp;amp;&amp;amp; instance === oldInstance) {
          if (!to.leaveGuards.size) {
            to.leaveGuards = from.leaveGuards
          }
          if (!to.updateGuards.size) {
            to.updateGuards = from.updateGuards
          }
        }
      }
      if (instance &amp;amp;&amp;amp;
        to &amp;amp;&amp;amp;
        (!from || !isSameRouteRecord(to, from) || !oldInstance)) {
        (to.enterCallbacks[name] || []).forEach(callback =&amp;gt; callback(instance))
      }
    }, { flush: &amp;#x27;post&amp;#x27; })
    return () =&amp;gt; {
      const route = routeToDisplay.value
      const matchedRoute = matchedRouteRef.value
      const ViewComponent = matchedRoute &amp;amp;&amp;amp; matchedRoute.components[props.name]
      const currentName = props.name
      if (!ViewComponent) {
        return normalizeSlot(slots.default, { Component: ViewComponent, route })
      }
      const routePropsOption = matchedRoute.props[props.name]
      const routeProps = routePropsOption
        ? routePropsOption === true
          ? route.params
          : typeof routePropsOption === &amp;#x27;function&amp;#x27;
            ? routePropsOption(route)
            : routePropsOption
        : null
      const onVnodeUnmounted = vnode =&amp;gt; {
        if (vnode.component.isUnmounted) {
          matchedRoute.instances[currentName] = null
        }
      }
      const component = h(ViewComponent, assign({}, routeProps, attrs, {
        onVnodeUnmounted,
        ref: viewRef,
      }))
      return (
        normalizeSlot(slots.default, { Component: component, route }) ||
        component)
    }
  },
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;RouterView&lt;/code&gt; 组件是基于 Composition API 实现的，我们重点看它的渲染部分，由于 &lt;code&gt;setup&lt;/code&gt; 函数的返回值是一个函数，那这个函数就是它的渲染函数。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RouterView&lt;/code&gt; 主要的思路就是根据路径 &lt;code&gt;route&lt;/code&gt; 和当前 &lt;code&gt;RouterView&lt;/code&gt; 嵌套的深度来匹配路由配置中对应的路由组件并渲染。&lt;/p&gt;
&lt;p&gt;在整个渲染过程中，会访问计算属性 &lt;code&gt;routeToDisplay&lt;/code&gt;，它的定义如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const injectedRoute = inject(routerViewLocationKey)
const routeToDisplay = computed(() =&amp;gt; props.route || injectedRoute.value)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;routeToDisplay&lt;/code&gt; 内部又会访问 &lt;code&gt;injectedRoute&lt;/code&gt;，而 &lt;code&gt;injectedRoute&lt;/code&gt; 注入的是 &lt;code&gt;key&lt;/code&gt; 为 &lt;code&gt;routerViewLocationKey&lt;/code&gt; 的数据。&lt;/p&gt;
&lt;p&gt;在执行 &lt;code&gt;createRouter&lt;/code&gt; 创建路由的时候，内部会创建 &lt;code&gt;currentRoute&lt;/code&gt; 响应式变量来维护当前的路径。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const currentRoute = shallowRef(START_LOCATION_NORMALIZED)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后在执行 &lt;code&gt;createApp(App).use(router)&lt;/code&gt; 安装路由的时候，会执行 &lt;code&gt;router&lt;/code&gt; 对象提供的 &lt;code&gt;install&lt;/code&gt; 方法，其中会把 &lt;code&gt;currentRoute&lt;/code&gt; 通过 &lt;code&gt;routerViewLocationKey&lt;/code&gt; 提供给应用使用。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;app.provide(routerViewLocationKey, currentRoute)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;因此在渲染 &lt;code&gt;RouterView&lt;/code&gt; 组件的时候，访问了 &lt;code&gt;routeToDisplay&lt;/code&gt;，内部会访问 &lt;code&gt;injectedRoute&lt;/code&gt;，进而也就访问到了 &lt;code&gt;currentRoute&lt;/code&gt;，而又由于 &lt;code&gt;currentRoute&lt;/code&gt; 是响应式对象，进而会触发它的依赖收集过程。&lt;/p&gt;
&lt;p&gt;这样当我们执行 &lt;code&gt;router&lt;/code&gt; 对象的 &lt;code&gt;push&lt;/code&gt; 方法修改路由路径时，内部会执行 &lt;code&gt;finalizeNavigation&lt;/code&gt; 方法，然后修改了 &lt;code&gt;currentRoute&lt;/code&gt;，就会触发&lt;strong&gt;所有的&lt;/strong&gt; &lt;code&gt;RouterView&lt;/code&gt; 组件的重新渲染。&lt;/p&gt;
&lt;p&gt;默认情况下，这个逻辑是没有任何问题的，那么为什么加上 &lt;code&gt;KeepAlive&lt;/code&gt; 就有问题了呢？&lt;/p&gt;
&lt;p&gt;回答这个问题前，我们不妨先思考另一个问题：示例中，在正常情况下，路由从 &lt;code&gt;Home&lt;/code&gt; 切到 &lt;code&gt;About&lt;/code&gt; 后，此时我们修改 &lt;code&gt;currentRoute&lt;/code&gt;，会触发 &lt;code&gt;Home&lt;/code&gt; 组件内部的 &lt;code&gt;RouterView&lt;/code&gt; 重新渲染吗？&lt;/p&gt;
&lt;p&gt;答案是不会的，因为当路由从 &lt;code&gt;Home&lt;/code&gt; 切到 &lt;code&gt;About&lt;/code&gt; 时，会触发 &lt;code&gt;Home&lt;/code&gt; 组件的卸载，进而也会触发其内部的 &lt;code&gt;RouterView&lt;/code&gt; 组件卸载。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RouterView&lt;/code&gt; 组件在卸载过程中，会清除组件作用域下的所有依赖，当然也包括 &lt;code&gt;currentRoute&lt;/code&gt; 收集的组件的 &lt;code&gt;render effect&lt;/code&gt;。因此当我们修改 &lt;code&gt;currentRoute&lt;/code&gt; 时，就不会触发 &lt;code&gt;Home&lt;/code&gt; 组件内部的 &lt;code&gt;RouterView&lt;/code&gt; 组件重新渲染了。&lt;/p&gt;
&lt;p&gt;但是，一旦 &lt;code&gt;Home&lt;/code&gt; 组件对应的 &lt;code&gt;RouterView&lt;/code&gt; 被 &lt;code&gt;KeepAlive&lt;/code&gt; 组件包裹后，当路由从 &lt;code&gt;Home&lt;/code&gt; 切到 &lt;code&gt;About&lt;/code&gt; 时，是不会执行 &lt;code&gt;Home&lt;/code&gt; 组件的卸载过程的，也就不会卸载内部的 &lt;code&gt;RouterView&lt;/code&gt; 组件，当然也就没有清除其作用域下的依赖。&lt;/p&gt;
&lt;p&gt;那么当我们修改 &lt;code&gt;currentRoute&lt;/code&gt; 时，不仅会渲染 &lt;code&gt;About&lt;/code&gt; 组件内部的 &lt;code&gt;RouterView&lt;/code&gt; 组件，也会触发 &lt;code&gt;Home&lt;/code&gt; 组件内部的 &lt;code&gt;RouterView&lt;/code&gt; 重新渲染。&lt;/p&gt;
&lt;p&gt;由于 &lt;code&gt;Home&lt;/code&gt; 组件内部的 &lt;code&gt;RouterView&lt;/code&gt; 和 &lt;code&gt;About&lt;/code&gt; 组件内部的 &lt;code&gt;RouterView&lt;/code&gt; 都是二级路由组件，根据 &lt;code&gt;RouterView&lt;/code&gt; 渲染的逻辑，此时 &lt;code&gt;Home&lt;/code&gt; 组件内部的 &lt;code&gt;RouterView&lt;/code&gt; 也会渲染成 &lt;code&gt;Sub&lt;/code&gt; 组件，这就是为何 &lt;code&gt;Sub&lt;/code&gt; 组件渲染两次的原因。&lt;/p&gt;
&lt;h2&gt;给 Vue3 提 issue&lt;/h2&gt;
&lt;p&gt;虽然定位出这个 bug，但一时半会儿我也想不出好的解决方案，于是我尝试给 Vue3 提了个 &lt;a href=&quot;https://github.com/vuejs/vue-next/issues/4708&quot;&gt;issue&lt;/a&gt; 。&lt;/p&gt;
&lt;p&gt;这里顺便与你分享一下提 issue 的一些注意事项：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;通常一些不错的开源项目都会有 issue template，你可以根据它的指引创建 issue。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为了让开源项目的维护者更快、更精确的定位问题，通常你需要最小化复现问题，提供一个可复现问题的 demo，而不是提供一个出问题的项目。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;建议提问前能加上一些自己对问题的分析和思考，这虽然不是必要的，但这个过程会让你更加熟悉这个开源项目，而且也可以帮助维护者更容易定位问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果确实是个 bug 且你有能力修复的话，提完 issue 可以顺便提一个 pull request，直接参与到开源项目的共建中，这个过程对自身的技术成长会有非常大的帮助。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;不过令我尴尬的是，我在提完 issue 后还不到五分钟，issue 就被关闭了，原因是它与 &lt;code&gt;vue-router-next&lt;/code&gt; 项目中的一个 &lt;a href=&quot;https://github.com/vuejs/vue-router-next/issues/626&quot;&gt;issue&lt;/a&gt; 重复了。&lt;/p&gt;
&lt;p&gt;我对该 issue 做了大致的浏览，发现它早在 2020 年 12 月 1 号就被提出了，而且有相当多的人都遇到了类似的问题。在该 issue 下面可以发现很多相关联的 issue，其中也包括文章开头提到的 issue，这就是为何我能关注到它的原因。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vue-router&lt;/code&gt; 的维护者也尝试解决过，但是遇到了一些麻烦，详情可以看他在 issue 中的回复。&lt;/p&gt;
&lt;p&gt;遗憾的是到目前为止，该 issue 也没有被解决，维护者给它贴上了 &lt;code&gt;help wanted&lt;/code&gt; 的标签，希望得到来自社区的帮助。&lt;/p&gt;
&lt;h2&gt;Vue2 也有这个问题吗？&lt;/h2&gt;
&lt;p&gt;因为我司目前还在使用 Vue2，所以我最关心的是 Vue2 是否也存在该问题。&lt;/p&gt;
&lt;p&gt;于是我用 Vue2 写了同样的 demo，令我欣慰的是 Vue2 并未有这个 bug，那这又是什么原因呢？&lt;/p&gt;
&lt;p&gt;由于 Vue 使用的是 &lt;code&gt;vue-router&lt;/code&gt; 的 3.x 版本，它对应的 &lt;code&gt;RouterView&lt;/code&gt; 组件的实现如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;var View = {
  name: &amp;#x27;RouterView&amp;#x27;,
  functional: true,
  props: {
    name: {
      type: String,
      default: &amp;#x27;default&amp;#x27;
    }
  },
  render: function render(_, ref) {
    var props = ref.props
    var children = ref.children
    var parent = ref.parent
    var data = ref.data

    data.routerView = true

    var h = parent.$createElement
    var name = props.name
    var route = parent.$route
    var cache = parent._routerViewCache || (parent._routerViewCache = {})

    var depth = 0
    var inactive = false
    while (parent &amp;amp;&amp;amp; parent._routerRoot !== parent) {
      var vnodeData = parent.$vnode ? parent.$vnode.data : {}
      if (vnodeData.routerView) {
        depth++
      }
      if (vnodeData.keepAlive &amp;amp;&amp;amp; parent._directInactive &amp;amp;&amp;amp; parent._inactive) {
        inactive = true
      }
      parent = parent.$parent
    }
    data.routerViewDepth = depth
    
    if (inactive) {
      var cachedData = cache[name]
      var cachedComponent = cachedData &amp;amp;&amp;amp; cachedData.component
      if (cachedComponent) {
        if (cachedData.configProps) {
          fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps)
        }
        return h(cachedComponent, data, children)
      } else {
        return h()
      }
    }

    var matched = route.matched[depth]
    var component = matched &amp;amp;&amp;amp; matched.components[name]
    
    if (!matched || !component) {
      cache[name] = null
      return h()
    }
    
    cache[name] = { component: component }
    
    data.registerRouteInstance = function(vm, val) {
      var current = matched.instances[name]
      if (
        (val &amp;amp;&amp;amp; current !== vm) ||
        (!val &amp;amp;&amp;amp; current === vm)
      ) {
        matched.instances[name] = val
      }
    }
    
    (data.hook || (data.hook = {})).prepatch = function(_, vnode) {
      matched.instances[name] = vnode.componentInstance
    }
    
    data.hook.init = function(vnode) {
      if (vnode.data.keepAlive &amp;amp;&amp;amp;
        vnode.componentInstance &amp;amp;&amp;amp;
        vnode.componentInstance !== matched.instances[name]
      ) {
        matched.instances[name] = vnode.componentInstance
      }
      
      handleRouteEntered(route)
    }

    var configProps = matched.props &amp;amp;&amp;amp; matched.props[name]
    if (configProps) {
      extend(cache[name], {
        route: route,
        configProps: configProps
      })
      fillPropsinData(component, data, route, configProps)
    }

    return h(component, data, children)
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;RouterView&lt;/code&gt; 组件的渲染逻辑和新版本的 &lt;code&gt;vue-router-next&lt;/code&gt; 实现一致：根据路径 &lt;code&gt;route&lt;/code&gt; 和当前 &lt;code&gt;RouterView&lt;/code&gt; 嵌套的深度来匹配路由配置中对应的路由组件并渲染。&lt;/p&gt;
&lt;p&gt;不同的是，3.x 版本的 &lt;code&gt;vue-router&lt;/code&gt; 处理了 &lt;code&gt;KeepAlive&lt;/code&gt; 的情况：如果当前的 &lt;code&gt;RouterView&lt;/code&gt; 组件所在的父组件实例身处 &lt;code&gt;KeepAlive&lt;/code&gt; 构造的树中，且是 &lt;code&gt;inactive&lt;/code&gt; 状态，那么它只会被渲染成上一次渲染的视图。&lt;/p&gt;
&lt;p&gt;因此这里有两个关键的点：一是能够判断当前所处的环境，二是需要缓存 &lt;code&gt;RouterView&lt;/code&gt; 上一次渲染的视图。&lt;/p&gt;
&lt;p&gt;显然，在 &lt;code&gt;vue-router-next&lt;/code&gt; 中，是没有对应的逻辑的，主要是因为组件实例中没有存储 &lt;code&gt;KeepAlive&lt;/code&gt; 组件相关的 &lt;code&gt;inactive&lt;/code&gt; 状态，&lt;code&gt;RouterView&lt;/code&gt; 组件也没法知道自己当前所处的环境。&lt;/p&gt;
&lt;p&gt;在我看来，如果 &lt;code&gt;vue-router-next&lt;/code&gt; 想要解决这个问题，可能还牵涉到 Vue3 内部的一些改动，提供更多的信息数据，让 &lt;code&gt;RouterView&lt;/code&gt; 组件在渲染的时候能够知道自己当前所处的环境。&lt;/p&gt;
&lt;p&gt;目前解决这个问题的一个办法是在这种嵌套路由的场景下，不使用 &lt;code&gt;KeepAlive&lt;/code&gt; 包裹 &lt;code&gt;RouterView&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;我们在做技术选型时，其实就要考虑到这层风险，当开源项目出现 bug 或者不能满足你的需求，且不能很快的响应时，你有没有办法帮助开源项目共建，或者通过魔改的方式来解决遇到的问题。&lt;/p&gt;
&lt;p&gt;我司使用的 Vue2 CSP 版本，就是基于 Vue.js 2.6.11 版本基础上魔改的，社区不提供支持，就需要自己动手了。&lt;/p&gt;
&lt;p&gt;对于开源项目的维护者而言，他们自然有自己的计划和考量，你不能因为自己的项目紧急就要求维护者立马帮你解决问题。当然，实在想寻求紧急帮助，情商高一点的做法是给开源维护者捐赠，通过付费的方式可能会提升 bug 被处理的优先级。&lt;/p&gt;
&lt;p&gt;当然，最靠谱的方式还是让自己靠谱起来，遇到 bug 后不要慌，先定位到出现 bug 的根本原因，然后找到合适的解决方案。&lt;/p&gt;
&lt;p&gt;如果你此时正在用 Vue3 开发项目，那么请务必注意这个 bug，有能力的可以好好研究，如果给 Vue3 提个 pull request 就更棒了。相比于改改拼写错误混个 contributor，我觉得能解决这类问题的人才能算真正意义上的 contributor。&lt;p&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
作者: 黄轶
原文链接：&lt;a href=&quot;https://juejin.im/post/7016628275074039815&quot;&gt;https://juejin.im/post/7016628275074039815&lt;/a&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>banyungong</author><pubDate>Tue, 18 Jan 2022 02:15:00 GMT</pubDate></item><item><title>聊一聊 Vue3 的 9 个知识点__前端__Vue.js__面试</title><link>http://vue-js.com/topic/61e61494cbbfd1003b11fa16</link><guid>http://vue-js.com/topic/61e61494cbbfd1003b11fa16</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: smartblue&lt;/h2&gt;
&lt;p&gt;这是我参与11月更文挑战的第1天，活动详情查看：&lt;a href=&quot;https://juejin.cn/post/7023643374569816095&quot; title=&quot;https://juejin.cn/post/7023643374569816095&quot;&gt;2021最后一次更文挑战&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;前一段时间一直在研究 Vue 源码，2和3的源码变化还是挺大的，但是 Vue3 在开发上需要改变以往习惯的地方真没什么，毕竟基础概念是一模一样的，可偏偏 3 的项目写起来就是能让人感觉爽得多，不动手是真体会不到&lt;/p&gt;
&lt;p&gt;所以咯，一定要动手！敲代码！不能学而不用，这是不可取的&lt;/p&gt;
&lt;p&gt;这里我总结了几个 Vue3 的知识点，如果你是 Vue2 的迁移者、学习 Vue3 或者准备面试的话，相信看完本文一定会有所收获&lt;/p&gt;
&lt;h2&gt;Vue3 有哪些变化&lt;/h2&gt;
&lt;p&gt;Vue3 是怎么得更快的？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;新增了三个组件：&lt;code&gt;Fragment&lt;/code&gt; 支持多个根节点、&lt;code&gt;Suspense&lt;/code&gt; 可以在组件渲染之前的等待时间显示指定内容、&lt;code&gt;Teleport&lt;/code&gt; 可以让子组件能够在视觉上跳出父组件(如父组件overflow:hidden)&lt;/li&gt;
&lt;li&gt;新增指令 &lt;code&gt;v-memo&lt;/code&gt;，可以缓存 html 模板，比如 v-for 列表不会变化的就缓存，简单说就是用内存换时间&lt;/li&gt;
&lt;li&gt;支持 &lt;code&gt;Tree-Shaking&lt;/code&gt;，会在打包时去除一些无用代码，没有用到的模块，使得代码打包体积更小&lt;/li&gt;
&lt;li&gt;新增 &lt;code&gt;Composition API&lt;/code&gt; 可以更好的逻辑复用和代码组织，同一功能的代码不至于像以前一样太分散，虽然 Vue2 中可以用 minxin 来实现复用代码，但也存在问题，比如方法或属性名会冲突，代码来源也不清楚等&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;Proxy&lt;/code&gt; 代替 Object.defineProperty 重构了响应式系统，可以监听到数组下标变化，及对象新增属性，因为监听的不是对象属性，而是对象本身，还可拦截 apply、has 等13种方法&lt;/li&gt;
&lt;li&gt;重构了虚拟 DOM，在编译时会将事件缓存、将 slot 编译为 lazy 函数、保存静态节点直接复用(静态提升)、以及添加静态标记、Diff 算法使用 最长递增子序列 优化了对比流程，使得虚拟 DOM 生成速度提升 &lt;code&gt;200%&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;支持在 &lt;code&gt;&amp;lt;style&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt; 里使用 &lt;code&gt;v-bind&lt;/code&gt;，给 CSS 绑定 JS 变量(&lt;code&gt;color: v-bind(str)&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;setup&lt;/code&gt; 代替了 beforeCreate 和 created 这两个生命周期&lt;/li&gt;
&lt;li&gt;新增了&lt;strong&gt;开发环境&lt;/strong&gt;的两个钩子函数，在组件更新时 &lt;code&gt;onRenderTracked&lt;/code&gt; 会跟踪组件里所有变量和方法的变化、每次触发渲染时 &lt;code&gt;onRenderTriggered&lt;/code&gt; 会返回发生变化的新旧值，可以让我们进行有针对性调试&lt;/li&gt;
&lt;li&gt;毕竟 Vue3 是用 &lt;code&gt;TS&lt;/code&gt; 写的，所以对 &lt;code&gt;TS&lt;/code&gt; 的支持度更好&lt;/li&gt;
&lt;li&gt;Vue3 不兼容 &lt;code&gt;IE11&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;说一下 Composition API&lt;/h2&gt;
&lt;p&gt;和 Options API 的区别？&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Composition API&lt;/code&gt; 也叫&lt;strong&gt;组合式 API&lt;/strong&gt;，它主要就是为了解决 Vue2 中 Options API 的问题。&lt;/p&gt;
&lt;p&gt;一是在 Vue2 中只能固定用 &lt;code&gt;data&lt;/code&gt;、&lt;code&gt;computed&lt;/code&gt;、&lt;code&gt;methods&lt;/code&gt; 等选项组织代码，在组件越来越复杂的时候，一个功能相关的属性和方法就会在文件上中下到处都有，很分散，变越来越难维护&lt;/p&gt;
&lt;p&gt;二是 Vue2 中虽然可以用 &lt;code&gt;minxin&lt;/code&gt; 来做逻辑的提取复用，但是 minxin 里的属性和方法名会和组件内部的命名冲突，还有当引入多个 minxin 的时候，我们使用的属性或方法是来于哪个 minxin 也不清楚&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;Composition API&lt;/code&gt; 刚才就解决了这两个问题，可以让我们自由的组织代码，同一功能相关的全部放在一起，代码有更好的可读性更便于维护，单独提取出来也不会造成命名冲突，所以也有更好的可扩展性&lt;/p&gt;
&lt;h2&gt;说一下 setup&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 方法
setup(props, context){ return { name:&amp;#x27;沐华&amp;#x27; } }

&amp;#x2F;&amp;#x2F; 语法糖
&amp;lt;script setup&amp;gt; ... &amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;setup()&lt;/code&gt; 方法是在 &lt;code&gt;beforeCreate()&lt;/code&gt; 生命周期函数之前执行的函数；&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3f6372e90f1f41e3b95b9a69da505470~tplv-k3u1fbpfcp-watermark.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;它接收两个参数 &lt;code&gt;props&lt;/code&gt; 和 &lt;code&gt;context&lt;/code&gt;。它里面不能使用 &lt;code&gt;this&lt;/code&gt;，而是通过 context 对象来代替当前执行上下文绑定的对象，context 对象有四个属性：&lt;code&gt;attrs&lt;/code&gt;、&lt;code&gt;slots&lt;/code&gt;、&lt;code&gt;emit&lt;/code&gt;、&lt;code&gt;expose&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;里面通过 &lt;code&gt;ref&lt;/code&gt; 和 &lt;code&gt;reactive&lt;/code&gt; 代替以前的 data 语法，&lt;code&gt;return&lt;/code&gt; 出去的内容，可以在模板直接使用，包括变量和方法&lt;/p&gt;
&lt;p&gt;而使用 &lt;code&gt;setup&lt;/code&gt; 语法糖时，不用写 &lt;code&gt;export default {}&lt;/code&gt;，子组件只需要 &lt;code&gt;import&lt;/code&gt; 就直接使用，不需要像以前一样在 components 里注册，属性和方法也不用 return。&lt;/p&gt;
&lt;p&gt;并且里面不需要用 &lt;code&gt;async&lt;/code&gt; 就可以直接使用 &lt;code&gt;await&lt;/code&gt;，因为这样默认会把组件的 &lt;code&gt;setup&lt;/code&gt; 变为 &lt;code&gt;async setup&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;用语法糖时，props、attrs、slots、emit、expose 的获取方式也不一样了&lt;/p&gt;
&lt;p&gt;3.0~3.2版本变成了通过 import 引入的 API：&lt;code&gt;defineProps&lt;/code&gt;、&lt;code&gt;defineEmit&lt;/code&gt;、&lt;code&gt;useContext&lt;/code&gt;(在3.2版本已废弃)，useContext 的属性 &lt;code&gt;{ emit, attrs, slots, expose }&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;3.2+版本不需要引入，而直接调用：&lt;code&gt;defineProps&lt;/code&gt;、&lt;code&gt;defineEmits&lt;/code&gt;、&lt;code&gt;defineExpose&lt;/code&gt;、&lt;code&gt;useSlots&lt;/code&gt;、&lt;code&gt;useAttrs&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;watch 和 watchEffect 的区别&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;watch&lt;/code&gt; 作用是对传入的某个或多个值的变化进行监听；触发时会返回新值和老值；也就是说第一次不会执行，只有变化时才会重新执行&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const name = ref(&amp;#x27;沐华&amp;#x27;)
watch(name, (newValue, oldValue)=&amp;gt;{ ... }, {immediate:true, deep:true})

&amp;#x2F;&amp;#x2F; 响应式对象
const boy = reactive({ age:18 })
watch(()=&amp;gt;boy.age, (newValue, oldValue)=&amp;gt;{ ... })

&amp;#x2F;&amp;#x2F; 监听多个
watch( [name, ()=&amp;gt;boy.age], (newValue, oldValue)=&amp;gt;{ ... } )
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;watchEffect&lt;/code&gt; 是传入一个立即执行函数，所以默认第一次也会执行一次；不需要传入监听内容，会自动收集函数内的数据源作为依赖，在依赖变化的时候又会重新执行该函数，如果没有依赖就不会执行；而且不会返回变化前后的新值和老值&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;watchEffect(onInvalidate =&amp;gt;{ ... })
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;共同点是 &lt;code&gt;watch&lt;/code&gt; 和 &lt;code&gt;watchEffect&lt;/code&gt; 会共享以下四种行为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;停止监听&lt;/code&gt;：组件卸载时都会自动停止监听&lt;/li&gt;
&lt;li&gt;&lt;code&gt;清除副作用&lt;/code&gt;：onInvalidate 会作为回调的第三个参数传入&lt;/li&gt;
&lt;li&gt;&lt;code&gt;副作用刷新时机&lt;/code&gt;：响应式系统会缓存副作用函数，并异步刷新，避免同一个 tick 中多个状态改变导致的重复调用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;监听器调试&lt;/code&gt;：开发模式下可以用 onTrack 和 onTrigger 进行调试&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://v3.cn.vuejs.org/guide/reactivity-computed-watchers.html&quot;&gt;官方文档描述&lt;/a&gt; 如下
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/39daec0928154f9cad014224020ec9a4~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Vue3 响应式原理和 Vue2 的区别&lt;/h2&gt;
&lt;p&gt;众所周知 &lt;code&gt;Vue2&lt;/code&gt; 数据响应式是通过 &lt;code&gt;Object.defineProperty()&lt;/code&gt; 劫持各个属性 getter 和 setter，在数据变化时发布消息给订阅者，触发相应的监听回调，而这之间存在几个问题&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;初始化时需要遍历对象所有 key，如果对象层次较深，性能不好&lt;/li&gt;
&lt;li&gt;通知更新过程需要维护大量 dep 实例和 watcher 实例，额外占用内存较多&lt;/li&gt;
&lt;li&gt;Object.defineProperty 无法监听到数组元素的变化，只能通过劫持重写数方法&lt;/li&gt;
&lt;li&gt;动态新增，删除对象属性无法拦截，只能用特定 set/delete API 代替&lt;/li&gt;
&lt;li&gt;不支持 Map、Set 等数据结构&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而在 &lt;code&gt;Vue3&lt;/code&gt; 中为了解决这些问题，使用原生的 &lt;code&gt;proxy&lt;/code&gt; 代替，支持监听对象和数组的变化，并且多达13种拦截方法，动态属性增删都可以拦截，新增数据结构全部支持，对象嵌套属性只代理第一层，运行时递归，用到才代理，也不需要维护特别多的依赖关系，性能取得很大进步&lt;/p&gt;
&lt;p&gt;更多细节和源码剖析可以看我另一篇文章 &lt;a href=&quot;https://juejin.cn/post/7021046293627666463&quot;&gt;Vue3.2 响应式原理源码剖析，及与 Vue2响应式的区别&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;defineProperty 和 Proxy 的区别&lt;/h2&gt;
&lt;p&gt;为什么要用 Proxy 代替 defineProperty ？好在哪里？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Object.defineProperty 是 Es5 的方法，Proxy 是 Es6 的方法&lt;/li&gt;
&lt;li&gt;defineProperty 不能监听到数组下标变化和对象新增属性，Proxy 可以&lt;/li&gt;
&lt;li&gt;defineProperty 是劫持对象属性，Proxy 是代理整个对象&lt;/li&gt;
&lt;li&gt;defineProperty 局限性大，只能针对单属性监听，所以在一开始就要全部递归监听。Proxy 对象嵌套属性运行时递归，用到才代理，也不需要维护特别多的依赖关系，性能提升很大，且首次渲染更快&lt;/li&gt;
&lt;li&gt;defineProperty 会污染原对象，修改时是修改原对象，Proxy 是对原对象进行代理并会返回一个新的代理对象，修改的是代理对象&lt;/li&gt;
&lt;li&gt;defineProperty 不兼容 IE8，Proxy 不兼容 IE11&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Vue3 的生命周期&lt;/h2&gt;
&lt;p&gt;基本上就是在 Vue2 生命周期钩子函数名基础上加了 &lt;code&gt;on&lt;/code&gt;；beforeDestory 和 destoryed 更名为 onBeforeUnmount 和 onUnmounted；然后删了两个钩子函数 beforeCreate 和 created；新增了两个开发环境用于调试的钩子&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/60f518fa89ba44bb8b59911ecd65a8a4~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;未命名文件 (1).png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Vue3 的 8 种组件通信&lt;/h2&gt;
&lt;p&gt;可以看我另一文章，就不搬过来了 &lt;a href=&quot;https://juejin.cn/post/6999687348120190983&quot;&gt;Vue3的8种和Vue2的12种组件通信，值得收藏&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Vue3 Diff算法和 Vue2 的区别&lt;/h2&gt;
&lt;p&gt;我们知道在数据变更触发页面重新渲染，会生成虚拟 DOM 并进行 patch 过程，这一过程在 Vue3 中的优化有如下&lt;/p&gt;
&lt;p&gt;编译阶段的优化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;事件缓存：将事件缓存(如: @click)，可以理解为变成静态的了&lt;/li&gt;
&lt;li&gt;静态提升：第一次创建静态节点时保存，后续直接复用&lt;/li&gt;
&lt;li&gt;添加静态标记：给节点添加静态标记，以优化 Diff 过程&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于编译阶段的优化，除了能更快的生成虚拟 DOM 以外，还使得 Diff 时可以跳过&amp;quot;永远不会变化的节点&amp;quot;，Diff 优化如下&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vue2 是全量 Diff，Vue3 是静态标记 + 非全量 Diff&lt;/li&gt;
&lt;li&gt;使用最长递增子序列优化了对比流程&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据尤大公布的数据就是 Vue3 &lt;code&gt;update&lt;/code&gt; 性能提升了 &lt;code&gt;1.3~2 倍&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;源码剖析可以看我另一篇文章 &lt;a href=&quot;https://juejin.cn/post/7010594233253888013&quot;&gt;深入浅出虚拟 DOM 和 Diff 算法，及 Vue2 与 Vue3 中的区别&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;往期精彩&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7021021063647592456&quot;&gt;12 个 Vue 开发中的性能优化小技巧&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7021688091513454622&quot;&gt;一次弄懂 Vue2 和 Vue3 的 nextTick 实现原理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7021046293627666463&quot;&gt;Vue3.2 响应式原理源码剖析，及与 Vue2 .x响应式的区别&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/editor/drafts/7009900485691834398&quot;&gt;深入浅出 Vue2 响应式原理源码剖析&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/7010594233253888013&quot;&gt;深入浅出虚拟 DOM 和 Diff 算法，及 Vue2 与 Vue3 中的区别&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/6999687348120190983&quot;&gt;Vue3的8种和Vue2的12种组件通信，值得收藏&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/6995591618371780622&quot;&gt;JavaScript进阶知识点&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://juejin.cn/post/6994629873985650696&quot;&gt;20分钟助你拿下HTTP和HTTPS，巩固你的HTTP知识体系&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/98f3b95c5cd94175bdc2db5b2055c5c3~tplv-k3u1fbpfcp-watermark.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;老铁，求点赞啊~~&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 沐华
        原文链接：&lt;a href=&quot;https://juejin.im/post/7026249448233631752&quot;&gt;https://juejin.im/post/7026249448233631752&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Tue, 18 Jan 2022 01:15:00 GMT</pubDate></item><item><title>Vue 团队公开快如闪电的全新脚手架工具 create-vue，未来将替代 Vue-CLI，才300余行代码，学它！__前端__JavaScript__Vue.js</title><link>http://vue-js.com/topic/61e587f4ad745b003c9df78d</link><guid>http://vue-js.com/topic/61e587f4ad745b003c9df78d</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;highlight: darcula
theme: smartblue&lt;/h2&gt;
&lt;p&gt;本文已参与「&lt;a href=&quot;https://juejin.cn/post/7012210233804079141/&quot; title=&quot;https://juejin.cn/post/7012210233804079141/&quot;&gt;掘力星计划&lt;/a&gt;」，赢取创作大礼包，挑战创作激励金。&lt;/p&gt;
&lt;h2&gt;1. 前言&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;大家好，我是&lt;a href=&quot;https://lxchuan12.gitee.io&quot;&gt;若川&lt;/a&gt;。欢迎关注我的&lt;a href=&quot;https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/12/13/16efe57ddc7c9eb3~tplv-t2oaga2asx-image.image&quot; title=&quot;https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/12/13/16efe57ddc7c9eb3~tplv-t2oaga2asx-image.image&quot;&gt;公众号若川视野&lt;/a&gt;，最近组织了&lt;a href=&quot;https://www.yuque.com/ruochuan12/notice&quot;&gt;&lt;strong&gt;源码共读活动&lt;/strong&gt;&lt;/a&gt;，感兴趣的可以加我微信 &lt;a href=&quot;https://juejin.cn/pin/7005372623400435725&quot;&gt;ruochuan12&lt;/a&gt; 参与，已进行两个多月，大家一起交流学习，共同进步。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;想学源码，极力推荐之前我写的&lt;a href=&quot;https://juejin.cn/column/6960551178908205093&quot;&gt;《学习源码整体架构系列》&lt;/a&gt; 包含&lt;code&gt;jQuery&lt;/code&gt;、&lt;code&gt;underscore&lt;/code&gt;、&lt;code&gt;lodash&lt;/code&gt;、&lt;code&gt;vuex&lt;/code&gt;、&lt;code&gt;sentry&lt;/code&gt;、&lt;code&gt;axios&lt;/code&gt;、&lt;code&gt;redux&lt;/code&gt;、&lt;code&gt;koa&lt;/code&gt;、&lt;code&gt;vue-devtools&lt;/code&gt;、&lt;code&gt;vuex4&lt;/code&gt;、&lt;code&gt;koa-compose&lt;/code&gt;、&lt;code&gt;vue-next-release&lt;/code&gt;、&lt;code&gt;vue-this&lt;/code&gt;等十余篇源码文章。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;美国时间 2021 年 10 月 7 日早晨，Vue 团队等主要贡献者举办了一个 &lt;code&gt;Vue Contributor Days&lt;/code&gt; 在线会议，&lt;a href=&quot;https://github.com/sodatea&quot;&gt;蒋豪群&lt;/a&gt;（&lt;a href=&quot;https://www.zhihu.com/people/sodatea&quot;&gt;知乎胖茶&lt;/a&gt;，Vue.js 官方团队成员，Vue-CLI 核心开发），在会上公开了&lt;a href=&quot;https://github.com/vuejs/create-vue&quot;&gt;&lt;code&gt;create-vue&lt;/code&gt;&lt;/a&gt;，一个全新的脚手架工具。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;create-vue&lt;/code&gt;使用&lt;code&gt;npm init vue@next&lt;/code&gt;一行命令，就能&lt;code&gt;快如闪电般&lt;/code&gt;初始化好基于&lt;code&gt;vite&lt;/code&gt;的&lt;code&gt;Vue3&lt;/code&gt;项目。&lt;/p&gt;
&lt;p&gt;本文就是通过调试和大家一起学习这个300余行的源码。&lt;/p&gt;
&lt;p&gt;阅读本文，你将学到：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;1. 学会全新的官方脚手架工具 create-vue 的使用和原理
2. 学会使用 VSCode 直接打开 github 项目
3. 学会使用测试用例调试源码
4. 学以致用，为公司初始化项目写脚手架工具。
5. 等等
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;2. 使用 npm init vue@next 初始化 vue3 项目&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/vuejs/create-vue&quot;&gt;create-vue github README&lt;/a&gt;上写着，&lt;code&gt;An easy way to start a Vue project&lt;/code&gt;。一种简单的初始化vue项目的方式。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;npm init vue@next
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;估计大多数读者，第一反应是&lt;strong&gt;这样竟然也可以，这么简单快捷？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;忍不住想动手在控制台输出命令，我在终端试过，见下图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c91fdfddab854e0d8846cb65f6df9b54~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;npm-init-vue@next&quot;&gt;&lt;/p&gt;
&lt;p&gt;最终&lt;code&gt;cd vue3-project&lt;/code&gt;、&lt;code&gt;npm install&lt;/code&gt; 、&lt;code&gt;npm run dev&lt;/code&gt;打开页面&lt;a href=&quot;http://localhost:3000&quot;&gt;http://localhost:3000&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3c6cdb2cf6c8452e92e451e24f1bbb7c~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;初始化页面&quot;&gt;&lt;/p&gt;
&lt;h3&gt;2.1 npm init &amp;amp;&amp;amp; npx&lt;/h3&gt;
&lt;p&gt;为啥 &lt;code&gt;npm init&lt;/code&gt; 也可以直接初始化一个项目，带着疑问，我们翻看 &lt;code&gt;npm&lt;/code&gt; 文档。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.npmjs.com/cli/v6/commands/npm-init&quot;&gt;npm init&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;npm init 用法：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;npm init [--force|-f|--yes|-y|--scope]
npm init &amp;lt;@scope&amp;gt; (same as &amp;#96;npx &amp;lt;@scope&amp;gt;&amp;#x2F;create&amp;#96;)
npm init [&amp;lt;@scope&amp;gt;&amp;#x2F;]&amp;lt;name&amp;gt; (same as &amp;#96;npx [&amp;lt;@scope&amp;gt;&amp;#x2F;]create-&amp;lt;name&amp;gt;&amp;#96;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;npm init &amp;lt;initializer&amp;gt;&lt;/code&gt; 时转换成&lt;code&gt;npx&lt;/code&gt;命令：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;npm init foo -&amp;gt; npx create-foo&lt;/li&gt;
&lt;li&gt;npm init @usr/foo -&amp;gt; npx @usr/create-foo&lt;/li&gt;
&lt;li&gt;npm init @usr -&amp;gt; npx @usr/create&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;看完文档，我们也就理解了：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;# 运行
npm init vue@next
# 相当于
npx create-vue@next
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们可以在这里&lt;a href=&quot;https://registry.npmjs.org/create-vue&quot;&gt;create-vue&lt;/a&gt;，找到一些信息。或者在&lt;a href=&quot;https://www.npmjs.com/package/create-vue&quot;&gt;npm create-vue&lt;/a&gt;找到版本等信息。&lt;/p&gt;
&lt;p&gt;其中&lt;code&gt;@next&lt;/code&gt;是指定版本，通过&lt;code&gt;npm dist-tag ls create-vue&lt;/code&gt;命令可以看出，&lt;code&gt;next&lt;/code&gt;版本目前对应的是&lt;code&gt;3.0.0-beta.6&lt;/code&gt;。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;npm dist-tag ls create-vue
- latest: 3.0.0-beta.6
- next: 3.0.0-beta.6
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;发布时 &lt;code&gt;npm publish --tag next&lt;/code&gt; 这种写法指定 &lt;code&gt;tag&lt;/code&gt;。默认标签是&lt;code&gt;latest&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;可能有读者对 &lt;code&gt;npx&lt;/code&gt; 不熟悉，这时找到&lt;a href=&quot;http://www.ruanyifeng.com/blog/2019/02/npx.html&quot;&gt;阮一峰老师博客 npx 介绍&lt;/a&gt;、&lt;a href=&quot;http://nodejs.cn/learn/the-npx-nodejs-package-runner&quot;&gt;nodejs.cn npx&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npx 是一个非常强大的命令，从 npm 的 5.2 版本（发布于 2017 年 7 月）开始可用。&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;简单说下容易忽略且常用的场景，&lt;code&gt;npx&lt;/code&gt;有点类似小程序提出的随用随走。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;轻松地运行本地命令&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;node_modules&amp;#x2F;.bin&amp;#x2F;vite -v
# vite&amp;#x2F;2.6.5 linux-x64 node-v14.16.0

# 等同于
# package.json script: &amp;quot;vite -v&amp;quot;
# npm run vite

npx vite -v
# vite&amp;#x2F;2.6.5 linux-x64 node-v14.16.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;使用不同的 Node.js 版本运行代码&lt;/strong&gt;
某些场景下可以临时切换 &lt;code&gt;node&lt;/code&gt; 版本，有时比 &lt;code&gt;nvm&lt;/code&gt; 包管理方便些。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;npx node@14 -v
# v14.18.0

npx -p node@14 node -v 
# v14.18.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;无需安装的命令执行&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;# 启动本地静态服务
npx http-server
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;# 无需全局安装
npx @vue&amp;#x2F;cli create vue-project
# @vue&amp;#x2F;cli 相比 npm init vue@next npx create-vue@next 很慢。

# 全局安装
npm i -g @vue&amp;#x2F;cli
vue create vue-project
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9ab5eef2c6a74801b021aeab5b6e5832~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;npx-vue-cli&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm init vue@next&lt;/code&gt; （&lt;code&gt;npx create-vue@next&lt;/code&gt;） 快的原因，主要在于依赖少（能不依赖包就不依赖），源码行数少，目前&lt;code&gt;index.js&lt;/code&gt;只有300余行。&lt;/p&gt;
&lt;h2&gt;3. 配置环境调试源码&lt;/h2&gt;
&lt;h3&gt;3.1 克隆 create-vue 项目&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lxchuan12/create-vue-analysis.git&quot;&gt;本文仓库地址 create-vue-analysis&lt;/a&gt;，求个&lt;code&gt;star&lt;/code&gt;~&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;# 可以直接克隆我的仓库，我的仓库保留的 create-vue 仓库的 git 记录
git clone https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;lxchuan12&amp;#x2F;create-vue-analysis.git
cd create-vue-analysis&amp;#x2F;create-vue
npm i
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当然不克隆也可以直接用 &lt;code&gt;VSCode&lt;/code&gt; 打开我的仓库
&lt;a href=&quot;https://open.vscode.dev/lxchuan12/create-vue-analysis&quot;&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9b3be78836f24b24aff91caeaadaf246~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;Open in Visual Studio Code&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;顺带说下：我是怎么保留 &lt;code&gt;create-vue&lt;/code&gt; 仓库的 &lt;code&gt;git&lt;/code&gt; 记录的。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;# 在 github 上新建一个仓库 &amp;#96;create-vue-analysis&amp;#96; 克隆下来
git clone https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;lxchuan12&amp;#x2F;create-vue-analysis.git
cd create-vue-analysis
git subtree add --prefix=create-vue https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;vuejs&amp;#x2F;create-vue.git main
# 这样就把 create-vue 文件夹克隆到自己的 git 仓库了。且保留的 git 记录
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;关于更多 &lt;code&gt;git subtree&lt;/code&gt;，可以看&lt;a href=&quot;https://segmentfault.com/a/1190000003969060&quot;&gt;Git Subtree 简明使用手册&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;3.2 package.json 分析&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; create-vue&amp;#x2F;package.json
{
  &amp;quot;name&amp;quot;: &amp;quot;create-vue&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;3.0.0-beta.6&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;An easy way to start a Vue project&amp;quot;,
  &amp;quot;type&amp;quot;: &amp;quot;module&amp;quot;,
  &amp;quot;bin&amp;quot;: {
    &amp;quot;create-vue&amp;quot;: &amp;quot;outfile.cjs&amp;quot;
  },
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;bin&lt;/code&gt;指定可执行脚本。也就是我们可以使用 &lt;code&gt;npx create-vue&lt;/code&gt; 的原因。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;outfile.cjs&lt;/code&gt; 是打包输出的&lt;code&gt;JS&lt;/code&gt;文件&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;{
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;build&amp;quot;: &amp;quot;esbuild --bundle index.js --format=cjs --platform=node --outfile=outfile.cjs&amp;quot;,
    &amp;quot;snapshot&amp;quot;: &amp;quot;node snapshot.js&amp;quot;,
    &amp;quot;pretest&amp;quot;: &amp;quot;run-s build snapshot&amp;quot;,
    &amp;quot;test&amp;quot;: &amp;quot;node test.js&amp;quot;
  },
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;执行 &lt;code&gt;npm run test&lt;/code&gt; 时，会先执行钩子函数 &lt;code&gt;pretest&lt;/code&gt;。&lt;code&gt;run-s&lt;/code&gt; 是 &lt;a href=&quot;https://github.com/mysticatea/npm-run-all/blob/HEAD/docs/run-s.md&quot;&gt;npm-run-all&lt;/a&gt; 提供的命令。&lt;code&gt;run-s build snapshot&lt;/code&gt; 命令相当于 &lt;code&gt;npm run build &amp;amp;&amp;amp; npm run snapshot&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;根据脚本提示，我们来看 &lt;code&gt;snapshot.js&lt;/code&gt; 文件。&lt;/p&gt;
&lt;h3&gt;3.3 生成快照 snapshot.js&lt;/h3&gt;
&lt;p&gt;这个文件主要作用是根据&lt;code&gt;const featureFlags = [&apos;typescript&apos;, &apos;jsx&apos;, &apos;router&apos;, &apos;vuex&apos;, &apos;with-tests&apos;]&lt;/code&gt; 组合生成&lt;code&gt;31种&lt;/code&gt;加上 &lt;code&gt;default&lt;/code&gt; 共计 &lt;code&gt;32种&lt;/code&gt; 组合，生成快照在 &lt;code&gt;playground&lt;/code&gt;目录。&lt;/p&gt;
&lt;p&gt;因为打包生成的 &lt;code&gt;outfile.cjs&lt;/code&gt; 代码有做一些处理，不方便调试，我们可以修改为&lt;code&gt;index.js&lt;/code&gt;便于调试。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 路径 create-vue&amp;#x2F;snapshot.js
const bin = path.resolve(__dirname, &amp;#x27;.&amp;#x2F;outfile.cjs&amp;#x27;)
&amp;#x2F;&amp;#x2F; 改成 index.js 便于调试
const bin = path.resolve(__dirname, &amp;#x27;.&amp;#x2F;index.js&amp;#x27;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们可以在&lt;code&gt;for&lt;/code&gt;和 &lt;code&gt;createProjectWithFeatureFlags&lt;/code&gt; 打上断点。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;createProjectWithFeatureFlags&lt;/code&gt;其实类似在终端输入如下执行这样的命令&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;node .&amp;#x2F;index.js --xxx --xxx --force
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;function createProjectWithFeatureFlags(flags) {
  const projectName = flags.join(&amp;#x27;-&amp;#x27;)
  console.log(&amp;#96;Creating project ${projectName}&amp;#96;)
  const { status } = spawnSync(
    &amp;#x27;node&amp;#x27;,
    [bin, projectName, ...flags.map((flag) =&amp;gt; &amp;#96;--${flag}&amp;#96;), &amp;#x27;--force&amp;#x27;],
    {
      cwd: playgroundDir,
      stdio: [&amp;#x27;pipe&amp;#x27;, &amp;#x27;pipe&amp;#x27;, &amp;#x27;inherit&amp;#x27;]
    }
  )

  if (status !== 0) {
    process.exit(status)
  }
}

&amp;#x2F;&amp;#x2F; 路径 create-vue&amp;#x2F;snapshot.js
for (const flags of flagCombinations) {
  createProjectWithFeatureFlags(flags)
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;调试&lt;/strong&gt;：&lt;code&gt;VSCode&lt;/code&gt;打开项目，&lt;code&gt;VSCode&lt;/code&gt;高版本(1.50+)可以在 &lt;code&gt;create-vue/package.json&lt;/code&gt; =&amp;gt; &lt;code&gt;scripts&lt;/code&gt; =&amp;gt; &lt;code&gt;&amp;quot;test&amp;quot;: &amp;quot;node test.js&amp;quot;&lt;/code&gt;。鼠标悬停在&lt;code&gt;test&lt;/code&gt;上会有调试脚本提示，选择调试脚本。如果对调试不熟悉，可以看我之前的文章&lt;a href=&quot;https://lxchuan12.gitee.io/koa-compose&quot;&gt;koa-compose&lt;/a&gt;，写的很详细。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;调试时，大概率你会遇到：&lt;code&gt;create-vue/index.js&lt;/code&gt; 文件中，&lt;code&gt;__dirname&lt;/code&gt; 报错问题。可以按照如下方法解决。在 &lt;code&gt;import&lt;/code&gt; 的语句后，添加如下语句，就能愉快的调试了。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 路径 create-vue&amp;#x2F;index.js
&amp;#x2F;&amp;#x2F; 解决办法和nodejs issues
&amp;#x2F;&amp;#x2F; https:&amp;#x2F;&amp;#x2F;stackoverflow.com&amp;#x2F;questions&amp;#x2F;64383909&amp;#x2F;dirname-is-not-defined-in-node-14-version
&amp;#x2F;&amp;#x2F; https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;nodejs&amp;#x2F;help&amp;#x2F;issues&amp;#x2F;2907

import { fileURLToPath } from &amp;#x27;url&amp;#x27;;
import { dirname } from &amp;#x27;path&amp;#x27;;

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;接着我们调试 index.js 文件，来学习。&lt;/p&gt;
&lt;h2&gt;4. 调试 index.js 主流程&lt;/h2&gt;
&lt;p&gt;回顾下上文 &lt;code&gt;npm init vue@next&lt;/code&gt; 初始化项目的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c91fdfddab854e0d8846cb65f6df9b54~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;npm-init-vue@next&quot;&gt;&lt;/p&gt;
&lt;p&gt;单从初始化项目输出图来看。主要是三个步骤。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;1. 输入项目名称，默认值是 vue-project
2. 询问一些配置 渲染模板等
3. 完成创建项目，输出运行提示
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;async function init() {
  &amp;#x2F;&amp;#x2F; 省略放在后文详细讲述
}

&amp;#x2F;&amp;#x2F; async 函数返回的是Promise 可以用 catch 报错
init().catch((e) =&amp;gt; {
  console.error(e)
})
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.1 解析命令行参数&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 返回运行当前脚本的工作目录的路径。
const cwd = process.cwd()
&amp;#x2F;&amp;#x2F; possible options:
&amp;#x2F;&amp;#x2F; --default
&amp;#x2F;&amp;#x2F; --typescript &amp;#x2F; --ts
&amp;#x2F;&amp;#x2F; --jsx
&amp;#x2F;&amp;#x2F; --router &amp;#x2F; --vue-router
&amp;#x2F;&amp;#x2F; --vuex
&amp;#x2F;&amp;#x2F; --with-tests &amp;#x2F; --tests &amp;#x2F; --cypress
&amp;#x2F;&amp;#x2F; --force (for force overwriting)
const argv = minimist(process.argv.slice(2), {
    alias: {
        typescript: [&amp;#x27;ts&amp;#x27;],
        &amp;#x27;with-tests&amp;#x27;: [&amp;#x27;tests&amp;#x27;, &amp;#x27;cypress&amp;#x27;],
        router: [&amp;#x27;vue-router&amp;#x27;]
    },
    &amp;#x2F;&amp;#x2F; all arguments are treated as booleans
    boolean: true
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/substack/minimist&quot;&gt;minimist&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;简单说，这个库，就是解析命令行参数的。看例子，我们比较容易看懂传参和解析结果。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;$ node example&amp;#x2F;parse.js -a beep -b boop
{ _: [], a: &amp;#x27;beep&amp;#x27;, b: &amp;#x27;boop&amp;#x27; }

$ node example&amp;#x2F;parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz
{ _: [ &amp;#x27;foo&amp;#x27;, &amp;#x27;bar&amp;#x27;, &amp;#x27;baz&amp;#x27; ],
  x: 3,
  y: 4,
  n: 5,
  a: true,
  b: true,
  c: true,
  beep: &amp;#x27;boop&amp;#x27; }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;比如&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;npm init vue@next --vuex --force
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.2 如果设置了 feature flags 跳过 prompts 询问&lt;/h3&gt;
&lt;p&gt;这种写法方便代码测试等。直接跳过交互式询问，同时也可以省时间。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; if any of the feature flags is set, we would skip the feature prompts
  &amp;#x2F;&amp;#x2F; use &amp;#96;??&amp;#96; instead of &amp;#96;||&amp;#96; once we drop Node.js 12 support
  const isFeatureFlagsUsed =
    typeof (argv.default || argv.ts || argv.jsx || argv.router || argv.vuex || argv.tests) ===
    &amp;#x27;boolean&amp;#x27;

&amp;#x2F;&amp;#x2F; 生成目录
  let targetDir = argv._[0]
  &amp;#x2F;&amp;#x2F; 默认 vue-projects
  const defaultProjectName = !targetDir ? &amp;#x27;vue-project&amp;#x27; : targetDir
  &amp;#x2F;&amp;#x2F; 强制重写文件夹，当同名文件夹存在时
  const forceOverwrite = argv.force
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.3 交互式询问一些配置&lt;/h3&gt;
&lt;p&gt;如上文&lt;code&gt;npm init vue@next&lt;/code&gt; 初始化的图示&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入项目名称&lt;/li&gt;
&lt;li&gt;还有是否删除已经存在的同名目录&lt;/li&gt;
&lt;li&gt;询问使用需要 JSX Router vuex cypress 等。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;let result = {}

  try {
    &amp;#x2F;&amp;#x2F; Prompts:
    &amp;#x2F;&amp;#x2F; - Project name:
    &amp;#x2F;&amp;#x2F;   - whether to overwrite the existing directory or not?
    &amp;#x2F;&amp;#x2F;   - enter a valid package name for package.json
    &amp;#x2F;&amp;#x2F; - Project language: JavaScript &amp;#x2F; TypeScript
    &amp;#x2F;&amp;#x2F; - Add JSX Support?
    &amp;#x2F;&amp;#x2F; - Install Vue Router for SPA development?
    &amp;#x2F;&amp;#x2F; - Install Vuex for state management? (TODO)
    &amp;#x2F;&amp;#x2F; - Add Cypress for testing?
    result = await prompts(
      [
        {
          name: &amp;#x27;projectName&amp;#x27;,
          type: targetDir ? null : &amp;#x27;text&amp;#x27;,
          message: &amp;#x27;Project name:&amp;#x27;,
          initial: defaultProjectName,
          onState: (state) =&amp;gt; (targetDir = String(state.value).trim() || defaultProjectName)
        },
        &amp;#x2F;&amp;#x2F; 省略若干配置
        {
          name: &amp;#x27;needsTests&amp;#x27;,
          type: () =&amp;gt; (isFeatureFlagsUsed ? null : &amp;#x27;toggle&amp;#x27;),
          message: &amp;#x27;Add Cypress for testing?&amp;#x27;,
          initial: false,
          active: &amp;#x27;Yes&amp;#x27;,
          inactive: &amp;#x27;No&amp;#x27;
        }
      ],
      {
        onCancel: () =&amp;gt; {
          throw new Error(red(&amp;#x27;✖&amp;#x27;) + &amp;#x27; Operation cancelled&amp;#x27;)
        }
      }
    ]
    )
  } catch (cancelled) {
    console.log(cancelled.message)
    &amp;#x2F;&amp;#x2F; 退出当前进程。
    process.exit(1)
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.4 初始化询问用户给到的参数，同时也会给到默认值&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; &amp;#96;initial&amp;#96; won&amp;#x27;t take effect if the prompt type is null
  &amp;#x2F;&amp;#x2F; so we still have to assign the default values here
  const {
    packageName = toValidPackageName(defaultProjectName),
    shouldOverwrite,
    needsJsx = argv.jsx,
    needsTypeScript = argv.typescript,
    needsRouter = argv.router,
    needsVuex = argv.vuex,
    needsTests = argv.tests
  } = result
  const root = path.join(cwd, targetDir)

  &amp;#x2F;&amp;#x2F; 如果需要强制重写，清空文件夹

  if (shouldOverwrite) {
    emptyDir(root)
    &amp;#x2F;&amp;#x2F; 如果不存在文件夹，则创建
  } else if (!fs.existsSync(root)) {
    fs.mkdirSync(root)
  }

  &amp;#x2F;&amp;#x2F; 脚手架项目目录
  console.log(&amp;#96;&amp;#x5C;nScaffolding project in ${root}...&amp;#96;)

 &amp;#x2F;&amp;#x2F; 生成 package.json 文件
  const pkg = { name: packageName, version: &amp;#x27;0.0.0&amp;#x27; }
  fs.writeFileSync(path.resolve(root, &amp;#x27;package.json&amp;#x27;), JSON.stringify(pkg, null, 2))
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.5 根据模板文件生成初始化项目所需文件&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;  &amp;#x2F;&amp;#x2F; todo:
  &amp;#x2F;&amp;#x2F; work around the esbuild issue that &amp;#96;import.meta.url&amp;#96; cannot be correctly transpiled
  &amp;#x2F;&amp;#x2F; when bundling for node and the format is cjs
  &amp;#x2F;&amp;#x2F; const templateRoot = new URL(&amp;#x27;.&amp;#x2F;template&amp;#x27;, import.meta.url).pathname
  const templateRoot = path.resolve(__dirname, &amp;#x27;template&amp;#x27;)
  const render = function render(templateName) {
    const templateDir = path.resolve(templateRoot, templateName)
    renderTemplate(templateDir, root)
  }

  &amp;#x2F;&amp;#x2F; Render base template
  render(&amp;#x27;base&amp;#x27;)

   &amp;#x2F;&amp;#x2F; 添加配置
  &amp;#x2F;&amp;#x2F; Add configs.
  if (needsJsx) {
    render(&amp;#x27;config&amp;#x2F;jsx&amp;#x27;)
  }
  if (needsRouter) {
    render(&amp;#x27;config&amp;#x2F;router&amp;#x27;)
  }
  if (needsVuex) {
    render(&amp;#x27;config&amp;#x2F;vuex&amp;#x27;)
  }
  if (needsTests) {
    render(&amp;#x27;config&amp;#x2F;cypress&amp;#x27;)
  }
  if (needsTypeScript) {
    render(&amp;#x27;config&amp;#x2F;typescript&amp;#x27;)
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.6 渲染生成代码模板&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; Render code template.
  &amp;#x2F;&amp;#x2F; prettier-ignore
  const codeTemplate =
    (needsTypeScript ? &amp;#x27;typescript-&amp;#x27; : &amp;#x27;&amp;#x27;) +
    (needsRouter ? &amp;#x27;router&amp;#x27; : &amp;#x27;default&amp;#x27;)
  render(&amp;#96;code&amp;#x2F;${codeTemplate}&amp;#96;)

  &amp;#x2F;&amp;#x2F; Render entry file (main.js&amp;#x2F;ts).
  if (needsVuex &amp;amp;&amp;amp; needsRouter) {
    render(&amp;#x27;entry&amp;#x2F;vuex-and-router&amp;#x27;)
  } else if (needsVuex) {
    render(&amp;#x27;entry&amp;#x2F;vuex&amp;#x27;)
  } else if (needsRouter) {
    render(&amp;#x27;entry&amp;#x2F;router&amp;#x27;)
  } else {
    render(&amp;#x27;entry&amp;#x2F;default&amp;#x27;)
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.7 如果配置了需要 ts&lt;/h3&gt;
&lt;p&gt;重命名所有的 &lt;code&gt;.js&lt;/code&gt; 文件改成 &lt;code&gt;.ts&lt;/code&gt;。
重命名 &lt;code&gt;jsconfig.json&lt;/code&gt; 文件为 &lt;code&gt;tsconfig.json&lt;/code&gt; 文件。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://code.visualstudio.com/docs/languages/jsconfig&quot;&gt;jsconfig.json&lt;/a&gt; 是VSCode的配置文件，可用于配置跳转等。&lt;/p&gt;
&lt;p&gt;把&lt;code&gt;index.html&lt;/code&gt; 文件里的 &lt;code&gt;main.js&lt;/code&gt; 重命名为 &lt;code&gt;main.ts&lt;/code&gt;。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; Cleanup.

if (needsTypeScript) {
    &amp;#x2F;&amp;#x2F; rename all &amp;#96;.js&amp;#96; files to &amp;#96;.ts&amp;#96;
    &amp;#x2F;&amp;#x2F; rename jsconfig.json to tsconfig.json
    preOrderDirectoryTraverse(
      root,
      () =&amp;gt; {},
      (filepath) =&amp;gt; {
        if (filepath.endsWith(&amp;#x27;.js&amp;#x27;)) {
          fs.renameSync(filepath, filepath.replace(&amp;#x2F;&amp;#x5C;.js$&amp;#x2F;, &amp;#x27;.ts&amp;#x27;))
        } else if (path.basename(filepath) === &amp;#x27;jsconfig.json&amp;#x27;) {
          fs.renameSync(filepath, filepath.replace(&amp;#x2F;jsconfig&amp;#x5C;.json$&amp;#x2F;, &amp;#x27;tsconfig.json&amp;#x27;))
        }
      }
    )

    &amp;#x2F;&amp;#x2F; Rename entry in &amp;#96;index.html&amp;#96;
    const indexHtmlPath = path.resolve(root, &amp;#x27;index.html&amp;#x27;)
    const indexHtmlContent = fs.readFileSync(indexHtmlPath, &amp;#x27;utf8&amp;#x27;)
    fs.writeFileSync(indexHtmlPath, indexHtmlContent.replace(&amp;#x27;src&amp;#x2F;main.js&amp;#x27;, &amp;#x27;src&amp;#x2F;main.ts&amp;#x27;))
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.8 配置了不需要测试&lt;/h3&gt;
&lt;p&gt;因为所有的模板都有测试文件，所以不需要测试时，执行删除 &lt;code&gt;cypress&lt;/code&gt;、&lt;code&gt;/__tests__/&lt;/code&gt; 文件夹&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;  if (!needsTests) {
    &amp;#x2F;&amp;#x2F; All templates assumes the need of tests.
    &amp;#x2F;&amp;#x2F; If the user doesn&amp;#x27;t need it:
    &amp;#x2F;&amp;#x2F; rm -rf cypress **&amp;#x2F;__tests__&amp;#x2F;
    preOrderDirectoryTraverse(
      root,
      (dirpath) =&amp;gt; {
        const dirname = path.basename(dirpath)

        if (dirname === &amp;#x27;cypress&amp;#x27; || dirname === &amp;#x27;__tests__&amp;#x27;) {
          emptyDir(dirpath)
          fs.rmdirSync(dirpath)
        }
      },
      () =&amp;gt; {}
    )
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.9 根据使用的 npm / yarn / pnpm &lt;a href=&quot;http://xn--README-hz6l727j.md&quot;&gt;生成README.md&lt;/a&gt; 文件，给出运行项目的提示&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; Instructions:
  &amp;#x2F;&amp;#x2F; Supported package managers: pnpm &amp;gt; yarn &amp;gt; npm
  &amp;#x2F;&amp;#x2F; Note: until &amp;lt;https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;pnpm&amp;#x2F;pnpm&amp;#x2F;issues&amp;#x2F;3505&amp;gt; is resolved,
  &amp;#x2F;&amp;#x2F; it is not possible to tell if the command is called by &amp;#96;pnpm init&amp;#96;.
  const packageManager = &amp;#x2F;pnpm&amp;#x2F;.test(process.env.npm_execpath)
    ? &amp;#x27;pnpm&amp;#x27;
    : &amp;#x2F;yarn&amp;#x2F;.test(process.env.npm_execpath)
    ? &amp;#x27;yarn&amp;#x27;
    : &amp;#x27;npm&amp;#x27;

  &amp;#x2F;&amp;#x2F; README generation
  fs.writeFileSync(
    path.resolve(root, &amp;#x27;README.md&amp;#x27;),
    generateReadme({
      projectName: result.projectName || defaultProjectName,
      packageManager,
      needsTypeScript,
      needsTests
    })
  )

  console.log(&amp;#96;&amp;#x5C;nDone. Now run:&amp;#x5C;n&amp;#96;)
  if (root !== cwd) {
    console.log(&amp;#96;  ${bold(green(&amp;#96;cd ${path.relative(cwd, root)}&amp;#96;))}&amp;#96;)
  }
  console.log(&amp;#96;  ${bold(green(getCommand(packageManager, &amp;#x27;install&amp;#x27;)))}&amp;#96;)
  console.log(&amp;#96;  ${bold(green(getCommand(packageManager, &amp;#x27;dev&amp;#x27;)))}&amp;#96;)
  console.log()
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;5. npm run test =&amp;gt; node test.js 测试&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; create-vue&amp;#x2F;test.js
import fs from &amp;#x27;fs&amp;#x27;
import path from &amp;#x27;path&amp;#x27;
import { fileURLToPath } from &amp;#x27;url&amp;#x27;

import { spawnSync } from &amp;#x27;child_process&amp;#x27;

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const playgroundDir = path.resolve(__dirname, &amp;#x27;.&amp;#x2F;playground&amp;#x2F;&amp;#x27;)

for (const projectName of fs.readdirSync(playgroundDir)) {
  if (projectName.endsWith(&amp;#x27;with-tests&amp;#x27;)) {
    console.log(&amp;#96;Running unit tests in ${projectName}&amp;#96;)
    const unitTestResult = spawnSync(&amp;#x27;pnpm&amp;#x27;, [&amp;#x27;test:unit:ci&amp;#x27;], {
      cwd: path.resolve(playgroundDir, projectName),
      stdio: &amp;#x27;inherit&amp;#x27;,
      shell: true
    })
    if (unitTestResult.status !== 0) {
      throw new Error(&amp;#96;Unit tests failed in ${projectName}&amp;#96;)
    }

    console.log(&amp;#96;Running e2e tests in ${projectName}&amp;#96;)
    const e2eTestResult = spawnSync(&amp;#x27;pnpm&amp;#x27;, [&amp;#x27;test:e2e:ci&amp;#x27;], {
      cwd: path.resolve(playgroundDir, projectName),
      stdio: &amp;#x27;inherit&amp;#x27;,
      shell: true
    })
    if (e2eTestResult.status !== 0) {
      throw new Error(&amp;#96;E2E tests failed in ${projectName}&amp;#96;)
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;主要对生成快照时生成的在 &lt;code&gt;playground&lt;/code&gt; 32个文件夹，进行如下测试。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;pnpm test:unit:ci

pnpm test:e2e:ci
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;6. 总结&lt;/h2&gt;
&lt;p&gt;我们使用了快如闪电般的&lt;code&gt;npm init vue@next&lt;/code&gt;，学习&lt;code&gt;npx&lt;/code&gt;命令了。学会了其原理。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;npm init vue@next =&amp;gt; npx create-vue@next
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;快如闪电的原因在于依赖的很少。很多都是自己来实现。如：&lt;code&gt;Vue-CLI&lt;/code&gt;中 &lt;code&gt;vue create vue-project&lt;/code&gt; 命令是用官方的&lt;code&gt;npm&lt;/code&gt;包&lt;a href=&quot;https://github.com/npm/validate-npm-package-name&quot;&gt;validate-npm-package-name&lt;/a&gt;，删除文件夹一般都是使用 &lt;a href=&quot;https://github.com/isaacs/rimraf&quot;&gt;rimraf&lt;/a&gt;。而 &lt;code&gt;create-vue&lt;/code&gt; 是自己实现&lt;code&gt;emptyDir&lt;/code&gt;和&lt;code&gt;isValidPackageName&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;非常建议读者朋友按照文中方法使用&lt;code&gt;VSCode&lt;/code&gt;调试 &lt;code&gt;create-vue&lt;/code&gt; 源码。源码中还有很多细节文中由于篇幅有限，未全面展开讲述。&lt;/p&gt;
&lt;p&gt;学完本文，可以为自己或者公司创建类似初始化脚手架。&lt;/p&gt;
&lt;p&gt;目前版本是&lt;code&gt;3.0.0-beta.6&lt;/code&gt;。我们持续关注学习它。&lt;/p&gt;
&lt;p&gt;最后欢迎加我微信 &lt;a href=&quot;https://juejin.cn/pin/7005372623400435725&quot;&gt;ruochuan12&lt;/a&gt; 交流，参与 &lt;a href=&quot;https://www.yuque.com/ruochuan12/notice&quot;&gt;源码共读&lt;/a&gt; 活动，大家一起学习源码，共同进步。&lt;/p&gt;
&lt;h2&gt;7. 参考资料&lt;/h2&gt;
&lt;p&gt;发现 create-vue 时打算写文章加入到&lt;a href=&quot;https://www.yuque.com/ruochuan12/notice&quot;&gt;源码共读&lt;/a&gt;计划中，大家一起学习。而源码共读群里小伙伴&lt;a href=&quot;https://github.com/upupming/frontend-learning-map&quot;&gt;&lt;code&gt;upupming&lt;/code&gt;&lt;/a&gt;比我先写完文章。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s?__biz=MzA5MjQwMzQyNw==&amp;amp;mid=2650757588&amp;amp;idx=1&amp;amp;sn=c6fb11eb09d5820957bcbece98a24087&amp;amp;chksm=88665a58bf11d34e569a4e1f6de101f8fa7637a620d5d9f7944cb47f41c5c47ab6920920c4ce&amp;amp;token=633613769&amp;amp;lang=zh_CN#rd&quot;&gt;@upupming  vue-cli 将被 create-vue 替代？初始化基于 vite 的 vue3 项目为何如此简单？&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;关于 &amp;amp;&amp;amp; 交流群&lt;/h2&gt;
&lt;p&gt;最近组织了&lt;a href=&quot;https://juejin.cn/pin/7005372623400435725&quot;&gt;&lt;strong&gt;源码共读活动&lt;/strong&gt;&lt;/a&gt;，感兴趣的可以加我微信 &lt;a href=&quot;https://juejin.cn/pin/7005372623400435725&quot;&gt;ruochuan12&lt;/a&gt; 参与，长期交流学习。&lt;/p&gt;
&lt;p&gt;作者：常以&lt;strong&gt;若川&lt;/strong&gt;为名混迹于江湖。欢迎加我微信&lt;code&gt;ruochuan12&lt;/code&gt;。前端路上 | 所知甚少，唯善学。&lt;br&gt;
&lt;a href=&quot;https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/12/13/16efe57ddc7c9eb3~tplv-t2oaga2asx-image.image&quot; title=&quot;https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/12/13/16efe57ddc7c9eb3~tplv-t2oaga2asx-image.image&quot;&gt;关注公众号若川视野&lt;/a&gt;，每周一起学源码，学会看源码，进阶高级前端。&lt;br&gt;
&lt;a href=&quot;https://link.juejin.cn?target=https%3A%2F%2Flxchuan12.gitee.io&quot; title=&quot;https://lxchuan12.gitee.io&quot;&gt;若川的博客&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fsegmentfault.com%2Fblog%2Flxchuan12&quot; title=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fsegmentfault.com%2Fblog%2Flxchuan12&quot;&gt;&lt;code&gt;segmentfault&lt;/code&gt;若川视野专栏&lt;/a&gt;，开通了&lt;strong&gt;若川视野&lt;/strong&gt;专栏，欢迎关注~&lt;br&gt;
&lt;a href=&quot;https://juejin.im/user/1415826704971918/posts&quot; title=&quot;https://juejin.im/user/1415826704971918/posts&quot;&gt;掘金专栏&lt;/a&gt;，欢迎关注~&lt;br&gt;
&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Flxchuan12&quot; title=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Flxchuan12&quot;&gt;知乎若川视野专栏&lt;/a&gt;，开通了&lt;strong&gt;若川视野&lt;/strong&gt;专栏，欢迎关注~&lt;br&gt;
&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Flxchuan12%2Fblog&quot; title=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Flxchuan12%2Fblog&quot;&gt;github blog&lt;/a&gt;，求个&lt;code&gt;star&lt;/code&gt;^_^~&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 若川
        原文链接：&lt;a href=&quot;https://juejin.im/post/7018344866811740173&quot;&gt;https://juejin.im/post/7018344866811740173&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Mon, 17 Jan 2022 15:15:00 GMT</pubDate></item><item><title>Element-UI 奇淫技巧第二弹！提升开发效率，延长摸鱼时间~__Element__前端</title><link>http://vue-js.com/topic/61e579e4cbbfd1003b11fa11</link><guid>http://vue-js.com/topic/61e579e4cbbfd1003b11fa11</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: cyanosis
highlight: atom-one-light&lt;/h2&gt;
&lt;h3&gt;主题样式修改&lt;/h3&gt;
&lt;p&gt;曾经为了修改组件风格，一个项目用了上百个样式穿透，后来才发现定制一个主题就够了！&lt;/p&gt;
&lt;h4&gt;第一步，在官网的主题页面，修改背景色、字体颜色及边框颜色&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5389bfb8437f4671a1abcde50e5deccf~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;01.jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;第二步，下载主题&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f83bcb6472c644499dca3c17baa8d57a~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1639302082(1).jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;第三步，用下载的css文件替换掉默认的css文件&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7342d4f166f5486cabaed5ce16a45502~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h4&gt;效果如下：&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5f37ae06ca1f461088fff172422103f2~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;el-backtop 回到顶部组件&lt;/h3&gt;
&lt;p&gt;有时候页面很长，我们需要一个回到顶部的按钮，推荐大家使用 &lt;code&gt;el-backtop&lt;/code&gt;，自带过渡效果。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/12e604091c60439c8034fa6cb2dde77f~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;20211213_211233 00_00_00-00_00_30.gif&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;box&amp;quot;&amp;gt;Scroll down to see the bottom-right button.&amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;el-backtop target=&amp;quot;.container&amp;quot;&amp;gt;
      &amp;lt;i class=&amp;quot;el-icon-caret-top&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;i&amp;gt;
    &amp;lt;&amp;#x2F;el-backtop&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;style lang=&amp;quot;scss&amp;quot; scoped&amp;gt;
.container {
  height: 100vh;
  overflow-x: hidden;
  .box {
    padding: 20px;
    height: 1500px;
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;注意：需要将 body 的 margin 设置为 0，不然会出现两个滚动条。&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;el-form-item label宽度自适应&lt;/h3&gt;
&lt;p&gt;通常我们是给 &lt;code&gt;label&lt;/code&gt; 设置一个固定宽度，但这完全限制了 UI 的发挥，如何让 &lt;code&gt;label&lt;/code&gt; 宽度自适应呢？答案是给控件设置一个固定宽度。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aa1f4571719548e9b730f548ec1af57a~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;el-form&amp;gt;
    &amp;lt;el-form-item label=&amp;quot;教师&amp;quot;&amp;gt;
      &amp;lt;el-input v-model=&amp;quot;form.teacher&amp;quot; style=&amp;quot;width: 625px;&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-input&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
    &amp;lt;el-form-item label=&amp;quot;请选择在线教学平台名称&amp;quot;&amp;gt;
      &amp;lt;el-select v-model=&amp;quot;form.platform&amp;quot; style=&amp;quot;width: 500px;&amp;quot;&amp;gt;
        &amp;lt;el-option label=&amp;quot;平台一&amp;quot; value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
        &amp;lt;el-option label=&amp;quot;平台二&amp;quot; value=&amp;quot;2&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
      &amp;lt;&amp;#x2F;el-select&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
  &amp;lt;&amp;#x2F;el-form&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      form: {
        teacher: &amp;#x27;&amp;#x27;,
        platform: &amp;#x27;&amp;#x27;
      }
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-form-item 循环校验&lt;/h3&gt;
&lt;p&gt;有时候表单项是一个循环的列表，需要对每一项进行校验。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/09f36b9bde724160bf849cc7747c893e~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1639314939(1).jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;el-form :model=&amp;quot;form&amp;quot; label-width=&amp;quot;100px&amp;quot; ref=&amp;quot;form&amp;quot;&amp;gt;
      &amp;lt;el-form-item
        v-for=&amp;quot;(item, index) in form.list&amp;quot;
        :key=&amp;quot;index&amp;quot;
        :label=&amp;quot;&amp;#x27;评价项目&amp;#x27; + (index + 1)&amp;quot;
        :prop=&amp;quot;&amp;#x27;list.&amp;#x27; + index + &amp;#x27;.evaluateName&amp;#x27;&amp;quot;
        :rules=&amp;quot;rules.evaluateName&amp;quot;
      &amp;gt;
        &amp;lt;el-input v-model=&amp;quot;item.evaluateName&amp;quot; &amp;#x2F;&amp;gt;
      &amp;lt;&amp;#x2F;el-form-item&amp;gt;
      &amp;lt;el-form-item&amp;gt;
        &amp;lt;el-button type=&amp;quot;primary&amp;quot; @click=&amp;quot;submitForm&amp;quot;&amp;gt;提交&amp;lt;&amp;#x2F;el-button&amp;gt;
      &amp;lt;&amp;#x2F;el-form-item&amp;gt;
    &amp;lt;&amp;#x2F;el-form&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      form: {
        list: [
          {
            evaluateName: &amp;#x27;&amp;#x27;
          },
          {
            evaluateName: &amp;#x27;&amp;#x27;
          },
          {
            evaluateName: &amp;#x27;&amp;#x27;
          }
        ]
      },
      rules: {
        evaluateName: [
          {
            required: true,
            message: &amp;#x27;请输入评价项目&amp;#x27;,
            trigger: &amp;#x27;blur&amp;#x27;
          }
        ]
      }
    }
  },
  methods: {
    submitForm() {
      this.$refs.form.validate(valid =&amp;gt; {
        if (valid) {
          alert(&amp;#x27;submit!&amp;#x27;)
        } else {
          console.log(&amp;#x27;error submit!!&amp;#x27;)
          return false
        }
      })
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-form-item 表单嵌套表格校验&lt;/h3&gt;
&lt;p&gt;如果在表单里嵌套表格，那么如何对表格里面的控件进行校验呢？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2f61fb571bba4da18f14bec455d10162~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1639315824(1).jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;el-form :model=&amp;quot;form&amp;quot; ref=&amp;quot;form&amp;quot;&amp;gt;
    &amp;lt;el-table border :data=&amp;quot;form.list&amp;quot; style=&amp;quot;margin-bottom: 10px;&amp;quot;&amp;gt;
      &amp;lt;el-table-column
        label=&amp;quot;评价项目&amp;quot;
        prop=&amp;quot;evaluateName&amp;quot;
        align=&amp;quot;center&amp;quot;
      &amp;gt;&amp;lt;&amp;#x2F;el-table-column&amp;gt;
      &amp;lt;el-table-column label=&amp;quot;评价等级&amp;quot; align=&amp;quot;center&amp;quot;&amp;gt;
        &amp;lt;template slot-scope=&amp;quot;scope&amp;quot;&amp;gt;
          &amp;lt;el-form-item
            :prop=&amp;quot;&amp;#x27;list.&amp;#x27; + scope.$index + &amp;#x27;.evaluateLevel&amp;#x27;&amp;quot;
            :rules=&amp;quot;rules.evaluateLevel&amp;quot;
            class=&amp;quot;evaluate-level&amp;quot;
          &amp;gt;
            &amp;lt;el-radio-group class=&amp;quot;radio-group&amp;quot; v-model=&amp;quot;scope.row.evaluateLevel&amp;quot;&amp;gt;
              &amp;lt;el-radio
                v-for=&amp;quot;evaluationItem in evaluateOptions&amp;quot;
                :key=&amp;quot;evaluationItem.value&amp;quot;
                :label=&amp;quot;evaluationItem.value&amp;quot;
              &amp;gt;
                {{ evaluationItem.label }}
              &amp;lt;&amp;#x2F;el-radio&amp;gt;
            &amp;lt;&amp;#x2F;el-radio-group&amp;gt;
          &amp;lt;&amp;#x2F;el-form-item&amp;gt;
        &amp;lt;&amp;#x2F;template&amp;gt;
      &amp;lt;&amp;#x2F;el-table-column&amp;gt;
    &amp;lt;&amp;#x2F;el-table&amp;gt;
    &amp;lt;el-form-item&amp;gt;
      &amp;lt;el-button type=&amp;quot;primary&amp;quot; class=&amp;quot;submit-btn&amp;quot; @click=&amp;quot;submitOpinionCheck&amp;quot;&amp;gt;
        提交
      &amp;lt;&amp;#x2F;el-button&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
  &amp;lt;&amp;#x2F;el-form&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      form: {
        list: [
          {
            id: &amp;#x27;01&amp;#x27;,
            evaluateName: &amp;#x27;指标一&amp;#x27;,
            evaluateLevel: &amp;#x27;&amp;#x27;
          },
          {
            id: &amp;#x27;02&amp;#x27;,
            evaluateName: &amp;#x27;指标二&amp;#x27;,
            evaluateLevel: &amp;#x27;&amp;#x27;
          },
          {
            id: &amp;#x27;03&amp;#x27;,
            evaluateName: &amp;#x27;指标二&amp;#x27;,
            evaluateLevel: &amp;#x27;&amp;#x27;
          }
        ]
      },
      evaluateOptions: [
        {
          value: &amp;#x27;1&amp;#x27;,
          label: &amp;#x27;优&amp;#x27;
        },
        {
          value: &amp;#x27;2&amp;#x27;,
          label: &amp;#x27;良&amp;#x27;
        },
        {
          value: &amp;#x27;3&amp;#x27;,
          label: &amp;#x27;中&amp;#x27;
        },
        {
          value: &amp;#x27;4&amp;#x27;,
          label: &amp;#x27;差&amp;#x27;
        }
      ],
      rules: {
        evaluateLevel: [{ required: true, message: &amp;#x27;请选择评价等级&amp;#x27;, trigger: &amp;#x27;change&amp;#x27; }]
      }
    }
  },
  methods: {
    submitOpinionCheck() {
      this.$refs.form.validate(valid =&amp;gt; {
        if (valid) {
          alert(&amp;#x27;submit!&amp;#x27;)
        } else {
          console.log(&amp;#x27;error submit!!&amp;#x27;)
          return false
        }
      })
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;style lang=&amp;quot;scss&amp;quot; scoped&amp;gt;
.evaluate-level {
  ::v-deep .el-form-item__error {
    width: 100px;
    &amp;#x2F;* 使校验信息居中 *&amp;#x2F;
    left: calc(50% - 50px);
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-form-item 表单校验 label只保留 *&lt;/h3&gt;
&lt;p&gt;有时候我们只需要表单校验的 &lt;code&gt;*&lt;/code&gt;， 而不需要 lable 文字，像这样：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ae92a837ac804673a130dd41358653cf~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1639312147(1).jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;el-form :model=&amp;quot;ruleForm&amp;quot; :rules=&amp;quot;rules&amp;quot; ref=&amp;quot;ruleForm&amp;quot; label-width=&amp;quot;30px&amp;quot;&amp;gt;
    &amp;lt;el-form-item label=&amp;quot; &amp;quot; prop=&amp;quot;name&amp;quot;&amp;gt;
      &amp;lt;el-input v-model=&amp;quot;ruleForm.name&amp;quot; placeholder=&amp;quot;请输入活动名称&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-input&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
    &amp;lt;el-form-item label=&amp;quot; &amp;quot; prop=&amp;quot;region&amp;quot;&amp;gt;
      &amp;lt;el-select v-model=&amp;quot;ruleForm.region&amp;quot; placeholder=&amp;quot;请选择活动区域&amp;quot;&amp;gt;
        &amp;lt;el-option label=&amp;quot;区域一&amp;quot; value=&amp;quot;shanghai&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
        &amp;lt;el-option label=&amp;quot;区域二&amp;quot; value=&amp;quot;beijing&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
      &amp;lt;&amp;#x2F;el-select&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
    &amp;lt;el-form-item&amp;gt;
      &amp;lt;el-button type=&amp;quot;primary&amp;quot; @click=&amp;quot;submitForm(&amp;#x27;ruleForm&amp;#x27;)&amp;quot;&amp;gt;提交&amp;lt;&amp;#x2F;el-button&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
  &amp;lt;&amp;#x2F;el-form&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      ruleForm: {
        name: &amp;#x27;&amp;#x27;,
        region: &amp;#x27;&amp;#x27;
      },
      rules: {
        name: [{ required: true, message: &amp;#x27;请输入活动名称&amp;#x27;, trigger: &amp;#x27;blur&amp;#x27; }],
        region: [{ required: true, message: &amp;#x27;请选择活动区域&amp;#x27;, trigger: &amp;#x27;change&amp;#x27; }]
      }
    }
  },
  methods: {
    submitForm(formName) {
      this.$refs[formName].validate(valid =&amp;gt; {
        if (valid) {
          alert(&amp;#x27;submit!&amp;#x27;)
        } else {
          console.log(&amp;#x27;error submit!!&amp;#x27;)
          return false
        }
      })
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-select 下拉框样式修改&lt;/h3&gt;
&lt;p&gt;使用样式穿透修改下拉框样式，你会发现打死都不生效，那是因为下拉框是默认挂载在 body 下面。解决办法：设置 &lt;code&gt;:popper-append-to-body=&amp;quot;false&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/74f10805b5824ca2819709e561451281~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;el-form&amp;gt;
    &amp;lt;el-form-item label=&amp;quot;活动区域&amp;quot;&amp;gt;
      &amp;lt;el-select
        class=&amp;quot;form-select&amp;quot;
        :popper-append-to-body=&amp;quot;false&amp;quot;
        v-model=&amp;quot;form.region&amp;quot;
        placeholder=&amp;quot;请选择活动区域&amp;quot;
      &amp;gt;
        &amp;lt;el-option label=&amp;quot;区域一&amp;quot; value=&amp;quot;shanghai&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
        &amp;lt;el-option label=&amp;quot;区域二&amp;quot; value=&amp;quot;beijing&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
      &amp;lt;&amp;#x2F;el-select&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
  &amp;lt;&amp;#x2F;el-form&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      form: {
        region: &amp;#x27;&amp;#x27;
      }
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;style lang=&amp;quot;scss&amp;quot; scoped&amp;gt;
.form-select {
  ::v-deep .el-select-dropdown__item.selected {
    background: pink;
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-input 打开弹框 自动聚焦&lt;/h3&gt;
&lt;p&gt;很多时候 form 表单是嵌套在 dialog 下面，为了提高用户体验，需要在打开弹框后 input 框自动聚焦。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a32cde61fe7a4e61bae715783be56c13~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;20211213_205355 00_00_00-00_00_30.gif&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;el-button type=&amp;quot;text&amp;quot; @click=&amp;quot;showDialog&amp;quot;&amp;gt;
      打开嵌套表单的 Dialog
    &amp;lt;&amp;#x2F;el-button&amp;gt;

    &amp;lt;el-dialog title=&amp;quot;活动&amp;quot; :visible.sync=&amp;quot;dialogVisible&amp;quot;&amp;gt;
      &amp;lt;el-form :model=&amp;quot;form&amp;quot;&amp;gt;
        &amp;lt;el-form-item label=&amp;quot;活动名称&amp;quot; label-width=&amp;quot;80px&amp;quot;&amp;gt;
          &amp;lt;el-input ref=&amp;quot;nameInput&amp;quot; v-model=&amp;quot;form.name&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-input&amp;gt;
        &amp;lt;&amp;#x2F;el-form-item&amp;gt;
        &amp;lt;el-form-item label=&amp;quot;活动内容&amp;quot; label-width=&amp;quot;80px&amp;quot;&amp;gt;
          &amp;lt;el-input v-model=&amp;quot;form.content&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-input&amp;gt;
        &amp;lt;&amp;#x2F;el-form-item&amp;gt;
      &amp;lt;&amp;#x2F;el-form&amp;gt;
      &amp;lt;div slot=&amp;quot;footer&amp;quot; class=&amp;quot;dialog-footer&amp;quot;&amp;gt;
        &amp;lt;el-button @click=&amp;quot;dialogVisible = false&amp;quot;&amp;gt;取 消&amp;lt;&amp;#x2F;el-button&amp;gt;
        &amp;lt;el-button type=&amp;quot;primary&amp;quot; @click=&amp;quot;dialogVisible = false&amp;quot;&amp;gt;确 定&amp;lt;&amp;#x2F;el-button&amp;gt;
      &amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;&amp;#x2F;el-dialog&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      dialogVisible: false,
      form: {
        name: &amp;#x27;&amp;#x27;,
        content: &amp;#x27;&amp;#x27;
      }
    }
  },
  methods: {
    showDialog() {
      this.dialogVisible = true
      &amp;#x2F;&amp;#x2F; 如果弹框显示，则自动聚焦
      this.$nextTick(() =&amp;gt; {
        this.$refs.nameInput.focus()
      })
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;今天的分享就到到这里，希望对你有所帮助！如果觉得有用，不要忘了点个赞呦~&lt;/p&gt;
&lt;p&gt;听说喜欢点赞的你，今年年终奖拿到手软&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 前端阿飞
        原文链接：&lt;a href=&quot;https://juejin.im/post/7041320033884045326&quot;&gt;https://juejin.im/post/7041320033884045326&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Mon, 17 Jan 2022 14:15:00 GMT</pubDate></item><item><title>后端一次给你10万条数据，如何优雅展示，到底考察我什么?__前端__Vue.js__面试</title><link>http://vue-js.com/topic/61e56bd4cbbfd1003b11fa09</link><guid>http://vue-js.com/topic/61e56bd4cbbfd1003b11fa09</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: smartblue&lt;/h2&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;大家好，我是林三心，&lt;strong&gt;用最通俗的话讲最难的知识点&lt;/strong&gt;是我的座右铭，&lt;strong&gt;基础是进阶的前提&lt;/strong&gt;是我的初心，今天跟大家来唠唠嗑，如果后端真的返回给前端10万条数据，咱们前端要怎么优雅地展示出来呢？（哈哈假设后端真的能传10万条数据到前端）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4cc34033138345488065107afcc4adf1~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;前置工作&lt;/h2&gt;
&lt;p&gt;先把前置工作给做好，后面才能进行测试&lt;/p&gt;
&lt;h3&gt;后端搭建&lt;/h3&gt;
&lt;p&gt;新建一个&lt;code&gt;server.js&lt;/code&gt;文件，简单起个服务，并返回给前端&lt;code&gt;10w&lt;/code&gt;条数据，并通过&lt;code&gt;nodemon server.js&lt;/code&gt;开启服务&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;没有安装&lt;code&gt;nodemon&lt;/code&gt;的同学可以先全局安装&lt;code&gt;npm i nodemon -g&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; server.js

const http = require(&amp;#x27;http&amp;#x27;)
const port = 8000;

http.createServer(function (req, res) {
  &amp;#x2F;&amp;#x2F; 开启Cors
  res.writeHead(200, {
    &amp;#x2F;&amp;#x2F;设置允许跨域的域名，也可设置*允许所有域名
    &amp;#x27;Access-Control-Allow-Origin&amp;#x27;: &amp;#x27;*&amp;#x27;,
    &amp;#x2F;&amp;#x2F;跨域允许的请求方法，也可设置*允许所有方法
    &amp;quot;Access-Control-Allow-Methods&amp;quot;: &amp;quot;DELETE,PUT,POST,GET,OPTIONS&amp;quot;,
    &amp;#x2F;&amp;#x2F;允许的header类型
    &amp;#x27;Access-Control-Allow-Headers&amp;#x27;: &amp;#x27;Content-Type&amp;#x27;
  })
  let list = []
  let num = 0

  &amp;#x2F;&amp;#x2F; 生成10万条数据的list
  for (let i = 0; i &amp;lt; 100000; i++) {
    num++
    list.push({
      src: &amp;#x27;https:&amp;#x2F;&amp;#x2F;p3-passport.byteacctimg.com&amp;#x2F;img&amp;#x2F;user-avatar&amp;#x2F;d71c38d1682c543b33f8d716b3b734ca~300x300.image&amp;#x27;,
      text: &amp;#96;我是${num}号嘉宾林三心&amp;#96;,
      tid: num
    })
  }
  res.end(JSON.stringify(list));
}).listen(port, function () {
  console.log(&amp;#x27;server is listening on port &amp;#x27; + port);
})
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;前端页面&lt;/h3&gt;
&lt;p&gt;先新建一个&lt;code&gt;index.html&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; index.html

&amp;#x2F;&amp;#x2F; 样式
&amp;lt;style&amp;gt;
    * {
      padding: 0;
      margin: 0;
    }
    #container {
      height: 100vh;
      overflow: auto;
    }
    .sunshine {
      display: flex;
      padding: 10px;
    }
    img {
      width: 150px;
      height: 150px;
    }
  &amp;lt;&amp;#x2F;style&amp;gt;

&amp;#x2F;&amp;#x2F; html部分
&amp;lt;body&amp;gt;
  &amp;lt;div id=&amp;quot;container&amp;quot;&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;script src=&amp;quot;.&amp;#x2F;index.js&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;&amp;#x2F;body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后新建一个&lt;code&gt;index.js&lt;/code&gt;文件，封装一个&lt;code&gt;AJAX&lt;/code&gt;函数，用来请求这&lt;code&gt;10w&lt;/code&gt;条数据&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; index.js

&amp;#x2F;&amp;#x2F; 请求函数
const getList = () =&amp;gt; {
    return new Promise((resolve, reject) =&amp;gt; {
        &amp;#x2F;&amp;#x2F;步骤一:创建异步对象
        var ajax = new XMLHttpRequest();
        &amp;#x2F;&amp;#x2F;步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数
        ajax.open(&amp;#x27;get&amp;#x27;, &amp;#x27;http:&amp;#x2F;&amp;#x2F;127.0.0.1:8000&amp;#x27;);
        &amp;#x2F;&amp;#x2F;步骤三:发送请求
        ajax.send();
        &amp;#x2F;&amp;#x2F;步骤四:注册事件 onreadystatechange 状态改变就会调用
        ajax.onreadystatechange = function () {
            if (ajax.readyState == 4 &amp;amp;&amp;amp; ajax.status == 200) {
                &amp;#x2F;&amp;#x2F;步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的
                resolve(JSON.parse(ajax.responseText))
            }
        }
    })
}

&amp;#x2F;&amp;#x2F; 获取container对象
const container = document.getElementById(&amp;#x27;container&amp;#x27;)
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;直接渲染&lt;/h2&gt;
&lt;p&gt;最直接的方式就是直接渲染出来，但是这样的做法肯定是不可取的，因为一次性渲染出&lt;code&gt;10w&lt;/code&gt;个节点，是非常耗时间的，咱们可以来看一下耗时，差不多要消耗&lt;code&gt;12秒&lt;/code&gt;，非常消耗时间&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9396a045360744e7b73dd626a1456bcd~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;截屏2021-11-18 下午10.07.45.png&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const renderList = async () =&amp;gt; {
    console.time(&amp;#x27;列表时间&amp;#x27;)
    const list = await getList()
    list.forEach(item =&amp;gt; {
        const div = document.createElement(&amp;#x27;div&amp;#x27;)
        div.className = &amp;#x27;sunshine&amp;#x27;
        div.innerHTML = &amp;#96;&amp;lt;img src=&amp;quot;${item.src}&amp;quot; &amp;#x2F;&amp;gt;&amp;lt;span&amp;gt;${item.text}&amp;lt;&amp;#x2F;span&amp;gt;&amp;#96;
        container.appendChild(div)
    })
    console.timeEnd(&amp;#x27;列表时间&amp;#x27;)
}
renderList()
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;setTimeout分页渲染&lt;/h2&gt;
&lt;p&gt;这个方法就是，把&lt;code&gt;10w&lt;/code&gt;按照每页数量&lt;code&gt;limit&lt;/code&gt;分成总共&lt;code&gt;Math.ceil(total / limit)&lt;/code&gt;页，然后利用&lt;code&gt;setTimeout&lt;/code&gt;，每次渲染1页数据，这样的话，渲染出首页数据的时间大大缩减了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/faeac54d664a40ab97ac75325e0984ff~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;截屏2021-11-18 下午10.14.46.png&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const renderList = async () =&amp;gt; {
    console.time(&amp;#x27;列表时间&amp;#x27;)
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total &amp;#x2F; limit)

    const render = (page) =&amp;gt; {
        if (page &amp;gt;= totalPage) return
        setTimeout(() =&amp;gt; {
            for (let i = page * limit; i &amp;lt; page * limit + limit; i++) {
                const item = list[i]
                const div = document.createElement(&amp;#x27;div&amp;#x27;)
                div.className = &amp;#x27;sunshine&amp;#x27;
                div.innerHTML = &amp;#96;&amp;lt;img src=&amp;quot;${item.src}&amp;quot; &amp;#x2F;&amp;gt;&amp;lt;span&amp;gt;${item.text}&amp;lt;&amp;#x2F;span&amp;gt;&amp;#96;
                container.appendChild(div)
            }
            render(page + 1)
        }, 0)
    }
    render(page)
    console.timeEnd(&amp;#x27;列表时间&amp;#x27;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;requestAnimationFrame&lt;/h2&gt;
&lt;p&gt;使用&lt;code&gt;requestAnimationFrame&lt;/code&gt;代替&lt;code&gt;setTimeout&lt;/code&gt;，减少了&lt;code&gt;重排&lt;/code&gt;的次数，极大提高了性能，建议大家在渲染方面多使用&lt;code&gt;requestAnimationFrame&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const renderList = async () =&amp;gt; {
    console.time(&amp;#x27;列表时间&amp;#x27;)
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total &amp;#x2F; limit)

    const render = (page) =&amp;gt; {
        if (page &amp;gt;= totalPage) return
        &amp;#x2F;&amp;#x2F; 使用requestAnimationFrame代替setTimeout
        requestAnimationFrame(() =&amp;gt; {
            for (let i = page * limit; i &amp;lt; page * limit + limit; i++) {
                const item = list[i]
                const div = document.createElement(&amp;#x27;div&amp;#x27;)
                div.className = &amp;#x27;sunshine&amp;#x27;
                div.innerHTML = &amp;#96;&amp;lt;img src=&amp;quot;${item.src}&amp;quot; &amp;#x2F;&amp;gt;&amp;lt;span&amp;gt;${item.text}&amp;lt;&amp;#x2F;span&amp;gt;&amp;#96;
                container.appendChild(div)
            }
            render(page + 1)
        })
    }
    render(page)
    console.timeEnd(&amp;#x27;列表时间&amp;#x27;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;文档碎片 + requestAnimationFrame&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;文档碎片&lt;/code&gt;的好处&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1、之前都是每次创建一个&lt;code&gt;div&lt;/code&gt;标签就&lt;code&gt;appendChild&lt;/code&gt;一次，但是有了&lt;code&gt;文档碎片&lt;/code&gt;可以先把1页的&lt;code&gt;div&lt;/code&gt;标签先放进&lt;code&gt;文档碎片&lt;/code&gt;中，然后一次性&lt;code&gt;appendChild&lt;/code&gt;到&lt;code&gt;container&lt;/code&gt;中，这样减少了&lt;code&gt;appendChild&lt;/code&gt;的次数，极大提高了性能&lt;/li&gt;
&lt;li&gt;2、页面只会渲染&lt;code&gt;文档碎片&lt;/code&gt;包裹着的元素，而不会渲染&lt;code&gt;文档碎片&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const renderList = async () =&amp;gt; {
    console.time(&amp;#x27;列表时间&amp;#x27;)
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total &amp;#x2F; limit)

    const render = (page) =&amp;gt; {
        if (page &amp;gt;= totalPage) return
        requestAnimationFrame(() =&amp;gt; {
            &amp;#x2F;&amp;#x2F; 创建一个文档碎片
            const fragment = document.createDocumentFragment()
            for (let i = page * limit; i &amp;lt; page * limit + limit; i++) {
                const item = list[i]
                const div = document.createElement(&amp;#x27;div&amp;#x27;)
                div.className = &amp;#x27;sunshine&amp;#x27;
                div.innerHTML = &amp;#96;&amp;lt;img src=&amp;quot;${item.src}&amp;quot; &amp;#x2F;&amp;gt;&amp;lt;span&amp;gt;${item.text}&amp;lt;&amp;#x2F;span&amp;gt;&amp;#96;
                &amp;#x2F;&amp;#x2F; 先塞进文档碎片
                fragment.appendChild(div)
            }
            &amp;#x2F;&amp;#x2F; 一次性appendChild
            container.appendChild(fragment)
            render(page + 1)
        })
    }
    render(page)
    console.timeEnd(&amp;#x27;列表时间&amp;#x27;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;懒加载&lt;/h2&gt;
&lt;p&gt;为了比较通俗的讲解，咱们启动一个&lt;code&gt;vue&lt;/code&gt;前端项目，后端服务还是开着&lt;/p&gt;
&lt;p&gt;其实实现原理很简单，咱们通过一张图来展示，就是在列表尾部放一个空节点&lt;code&gt;blank&lt;/code&gt;，然后先渲染第1页数据，向上滚动，等到&lt;code&gt;blank&lt;/code&gt;出现在视图中，就说明到底了，这时候再加载第二页，往后以此类推。&lt;/p&gt;
&lt;p&gt;至于怎么判断&lt;code&gt;blank&lt;/code&gt;出现在视图上，可以使用&lt;code&gt;getBoundingClientRect&lt;/code&gt;方法获取&lt;code&gt;top&lt;/code&gt;属性&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;IntersectionObserver&lt;/code&gt; 性能更好，但是我这里就拿&lt;code&gt;getBoundingClientRect&lt;/code&gt;来举例&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/af9e2974b1d84d3c852c0e7f606794cd~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;截屏2021-11-18 下午10.41.01.png&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;script setup lang=&amp;quot;ts&amp;quot;&amp;gt;
import { onMounted, ref, computed } from &amp;#x27;vue&amp;#x27;
const getList = () =&amp;gt; {
  &amp;#x2F;&amp;#x2F; 跟上面一样的代码
}

const container = ref&amp;lt;HTMLElement&amp;gt;() &amp;#x2F;&amp;#x2F; container节点
const blank = ref&amp;lt;HTMLElement&amp;gt;() &amp;#x2F;&amp;#x2F; blank节点
const list = ref&amp;lt;any&amp;gt;([]) &amp;#x2F;&amp;#x2F; 列表
const page = ref(1) &amp;#x2F;&amp;#x2F; 当前页数
const limit = 200 &amp;#x2F;&amp;#x2F; 一页展示
&amp;#x2F;&amp;#x2F; 最大页数
const maxPage = computed(() =&amp;gt; Math.ceil(list.value.length &amp;#x2F; limit))
&amp;#x2F;&amp;#x2F; 真实展示的列表
const showList = computed(() =&amp;gt; list.value.slice(0, page.value * limit))
const handleScroll = () =&amp;gt; {
  &amp;#x2F;&amp;#x2F; 当前页数与最大页数的比较
  if (page.value &amp;gt; maxPage.value) return
  const clientHeight = container.value?.clientHeight
  const blankTop = blank.value?.getBoundingClientRect().top
  if (clientHeight === blankTop) {
    &amp;#x2F;&amp;#x2F; blank出现在视图，则当前页数加1
    page.value++
  }
}

onMounted(async () =&amp;gt; {
  const res = await getList()
  list.value = res
})
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div id=&amp;quot;container&amp;quot; @scroll=&amp;quot;handleScroll&amp;quot; ref=&amp;quot;container&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;sunshine&amp;quot; v-for=&amp;quot;(item) in showList&amp;quot; :key=&amp;quot;item.tid&amp;quot;&amp;gt;
      &amp;lt;img :src=&amp;quot;item.src&amp;quot; &amp;#x2F;&amp;gt;
      &amp;lt;span&amp;gt;{{ item.text }}&amp;lt;&amp;#x2F;span&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;div ref=&amp;quot;blank&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;虚拟列表&lt;/h2&gt;
&lt;p&gt;虚拟列表需要讲解的比较多，在这里我分享一下我的一篇&lt;code&gt;虚拟列表&lt;/code&gt;的文章，哈哈我自认为讲的不错吧哈哈哈哈哈哈&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6966179727329460232&quot;&gt;结合“康熙选秀”，给大家讲讲“虚拟列表”&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;如果你觉得此文对你有一丁点帮助，点个赞，鼓励一下林三心哈哈。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你想一起学习前端或者摸鱼，那你可以加我，加入我的摸鱼学习群，点击这里&lt;/strong&gt; —&amp;gt; &lt;a href=&quot;https://juejin.cn/pin/7035153948126216206&quot; title=&quot;https://juejin.cn/pin/7035153948126216206&quot;&gt;摸鱼沸点&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你是有其他目的的，别加我，我不想跟你交朋友，我只想简简单单学习前端，不想搞一些有的没的！！！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: Sunshine_Lin
        原文链接：&lt;a href=&quot;https://juejin.im/post/7031923575044964389&quot;&gt;https://juejin.im/post/7031923575044964389&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Mon, 17 Jan 2022 13:15:00 GMT</pubDate></item><item><title>研究大佬写的倒计时组件(Vue)，学到了不少东西__Vue.js__前端</title><link>http://vue-js.com/topic/61e55dc4ad745b003c9df77a</link><guid>http://vue-js.com/topic/61e55dc4ad745b003c9df77a</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0234702e819c4f1c94fce6869e0d88c6~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;file&quot;&gt;&lt;/p&gt;
&lt;p&gt;本文首发于：&lt;a href=&quot;https://github.com/bigo-frontend/blog/&quot;&gt;https://github.com/bigo-frontend/blog/&lt;/a&gt; 欢迎关注、转载。&lt;/p&gt;
&lt;h2&gt;一、前言&lt;/h2&gt;
&lt;p&gt;入职的第一个需求是跟着一位前端大佬一起完成的一个活动项目。&lt;/p&gt;
&lt;p&gt;由于是一起开发，当然不会放过阅读大佬的代码的机会。&lt;/p&gt;
&lt;p&gt;因为我的页面中需要使用到倒计时功能，发现大佬的已经写了个现成的倒计时组件，于是直接就拿过来用了。&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/19cbeea00ba94feda62b720dcca08098~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;传个参数就实现了功能的感觉真是太棒了。项目完成后，就膜拜了一下大佬的倒计时组件的代码。真是让我学到了不少。列举如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计时器为什么要用setTimeout而不用setInterval&lt;/li&gt;
&lt;li&gt;为什么不直接将剩余时间-1。&lt;/li&gt;
&lt;li&gt;如何将所需要的时间返回出去（有可能我只需要分钟和秒数，那就只返回分钟和秒数，也有可能我全都要）。&lt;/li&gt;
&lt;li&gt;不确定接口返回的是剩余时间还是截止日期，该怎么同时兼容这两种情况。&lt;/li&gt;
&lt;li&gt;不确定接口返回的时间是秒还是毫秒单位。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;好了，你可能不太理解这些问题，但是没关系，看完下面的解释，相信你会豁然开朗。
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5313d084d073497a863901a6a907f0ab~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;h2&gt;二、开始手操&lt;/h2&gt;
&lt;h3&gt;1. 先创建一个vue组件&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
   
  }),
  props: {
    
  },
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 实现基本的倒计时组件&lt;/h3&gt;
&lt;p&gt;接下来，假设接口获得的是一个剩余时间。&lt;/p&gt;
&lt;p&gt;将剩余时间&lt;code&gt;time&lt;/code&gt;传入这个倒计时组件，由于time可能是秒为单位的，也有可能是毫秒为单位的，所以我们需要在传入&lt;code&gt;time&lt;/code&gt;的是有也传入一个&lt;code&gt;isMilliSecond&lt;/code&gt;来告诉倒计时组件这个&lt;code&gt;time&lt;/code&gt;是毫秒还是秒为单位的。如下代码中的&lt;code&gt;props&lt;/code&gt;所示。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;computed&lt;/code&gt;中的duration是将time进行转化的结果，不管&lt;code&gt;time&lt;/code&gt;是毫秒还是秒，都转化为秒
不知道你注意到了没有：&lt;code&gt;+this.time&lt;/code&gt;。为什么要在前面加个‘&lt;strong&gt;+&lt;/strong&gt;’号。这点很值得我们学习，因为接口返回的一串数字有时候是字符串的形式，有时候是数字的形式（&lt;s&gt;不能过分相信后端同学，必须自己做好防范&lt;/s&gt;）。所以通过前面加个‘&lt;strong&gt;+&lt;/strong&gt;’号 通通转化为数字。现在的&lt;code&gt;duration&lt;/code&gt;就是转化后的&lt;code&gt;time&lt;/code&gt;啦！&lt;/p&gt;
&lt;p&gt;我们获得duration之后就可以开始倒计时了&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  &amp;#x2F;&amp;#x2F; 新增代码：
  mounted() {
    this.countDown();
  },
  methods: {
    countDown() {
      this.getTime(this.duration);
    },
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在这里创建了一个countDown方法，表示开始倒计时的意思，已进入页面就开始执行countdown方法。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;countDown&lt;/code&gt;方法调用了getTime方法，getTime需要传入duration这个参数，也就是我们获得的剩余时间。&lt;/p&gt;
&lt;p&gt;现在来实现一下这个方法。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
    还剩{{day}}天{{hours}}:{{mins}}:{{seconds}}
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
    days: &amp;#x27;0&amp;#x27;,
    hours: &amp;#x27;00&amp;#x27;,
    mins: &amp;#x27;00&amp;#x27;,
    seconds: &amp;#x27;00&amp;#x27;,
    timer: null,
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  mounted() {
    this.countDown();
  },
  methods: {
    countDown() {
      this.getTime(this.duration);
    },
    &amp;#x2F;&amp;#x2F; 新增代码：
    getTime(duration) {
      this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);
      if (duration &amp;lt; 0) {
        return;
      }
      const { dd, hh, mm, ss } = this.durationFormatter(duration);
      this.days = dd || 0;
      this.hours = hh || 0;
      this.mins = mm || 0;
      this.seconds = ss || 0;
      this.timer = setTimeout(() =&amp;gt; {
        this.getTime(duration - 1);
      }, 1000);
    }
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到，&lt;strong&gt;getTime&lt;/strong&gt;的目的就是获得 days，hours,mins，seconds，然后显示到html上，并且通过定时器实时来刷新days，hours,mins，seconds这个几个值。从而实现了倒计时。很简单，有木有？&lt;/p&gt;
&lt;p&gt;&lt;code&gt;durationFormatter&lt;/code&gt;是一个将&lt;code&gt;duration&lt;/code&gt;转化成天数，小时，分钟，秒数的方法，很简单，可以看下它的具体实现。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;durationFormatter(time) {
  if (!time) return { ss: 0 };
  let t = time;
  const ss = t % 60;
  t = (t - ss) &amp;#x2F; 60;
  if (t &amp;lt; 1) return { ss };
  const mm = t % 60;
  t = (t - mm) &amp;#x2F; 60;
  if (t &amp;lt; 1) return { mm, ss };
  const hh = t % 24;
  t = (t - hh) &amp;#x2F; 24;
  if (t &amp;lt; 1) return { hh, mm, ss };
  const dd = t;
  return { dd, hh, mm, ss };
},
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;好了，问题开始来了！！
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/36794496b1f4436499617fa23eb621e7~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;h3&gt;3. 为什么要&lt;strong&gt;用setTimeout来模拟setInterval的行为&lt;/strong&gt;？&lt;/h3&gt;
&lt;p&gt;这里用setInerval不是更方便吗？&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;setTimeout(function(){··· }, n); &amp;#x2F;&amp;#x2F; n毫秒后执行function
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;setInterval(function(){··· }, n); &amp;#x2F;&amp;#x2F; 每隔n毫秒执行一次function
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看看setInterval有什么缺点：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;再次强调，定时器指定的时间间隔，表示的是何时将定时器的代码添加到消息队列，而不是何时执行代码。所以真正何时执行代码的时间是不能保证的，取决于何时被主线程的事件循环取到，并执行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;setInterval(function, N)  
&amp;#x2F;&amp;#x2F;即：每隔N秒把function事件推到消息队列中
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/39aa93f16e274e499f567cf7e31e33f6~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;上图可见，setInterval每隔100ms往队列中添加一个事件；100ms后，添加T1定时器代码至队列中，主线程中还有任务在执行，所以等待，some event执行结束后执行T1定时器代码；又过了100ms，T2定时器被添加到队列中，主线程还在执行T1代码，所以等待；又过了100ms，理论上又要往队列里推一个定时器代码，但由于此时T2还在队列中，所以T3不会被添加，结果就是此时被跳过；这里我们可以看到，T1定时器执行结束后马上执行了T2代码，所以并没有达到定时器的效果。&lt;/p&gt;
&lt;p&gt;综上所述，setInterval有两个缺点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用setInterval时，某些间隔会被跳过；&lt;/li&gt;
&lt;li&gt;可能多个定时器会连续执行；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可以这么理解：&lt;strong&gt;每个setTimeout产生的任务会直接push到任务队列中；而setInterval在每次把任务push到任务队列前，都要进行一下判断(看上次的任务是否仍在队列中)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因而我们一般用setTimeout模拟setInterval，来规避掉上面的缺点。&lt;/p&gt;
&lt;h3&gt;4. 为什么要clearTimeout(this.timer)&lt;/h3&gt;
&lt;p&gt;第二问：为什么要有&lt;code&gt;this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);&lt;/code&gt;这一句？&lt;/p&gt;
&lt;p&gt;假设一个场景：&lt;/p&gt;
&lt;p&gt;如图所示，在倒计时的父组件中，有两个按钮，点击活动一就会传入活动一的剩余时间，点击活动二，就会传入活动二的时间。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1f5ba9e8f4df440f80df305a0860a4f9~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;如果此时倒计时组件正在做活动一的倒计时，然后点击活动二，就要会马上传入新的time，这个时候就需要重新计时。当然，这里并不会重新计时，因为组件的mounted只会执行一次。也就是说&lt;code&gt;this.countDown();&lt;/code&gt;只会执行一次，也就是说&lt;code&gt;this.getTime(this.duration);&lt;/code&gt;只会执行一次，因此duration还是活动一的时间，怎么办呢？watch派上用场了。&lt;/p&gt;
&lt;p&gt;我们来监听duration，如果发现duration变化，说明新的时间time传入组件，这时就要重新调用this.countDown()。&lt;/p&gt;
&lt;p&gt;代码如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
    还剩{{day}}天{{hours}}:{{mins}}:{{seconds}}
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
    days: &amp;#x27;0&amp;#x27;,
    hours: &amp;#x27;00&amp;#x27;,
    mins: &amp;#x27;00&amp;#x27;,
    seconds: &amp;#x27;00&amp;#x27;,
    timer: null,
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  mounted() {
    this.countDown();
  },
  &amp;#x2F;&amp;#x2F; 新增代码：
  watch: {
    duration() {
      this.countDown();
    }
  },
  methods: {
    countDown() {
      this.getTime(this.duration);
    },
    durationFormatter(){...}
    getTime(duration) {
      this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);
      if (duration &amp;lt; 0) {
        return;
      }
      const { dd, hh, mm, ss } = this.durationFormatter(duration);
      this.days = dd || 0;
      this.hours = hh || 0;
      this.mins = mm || 0;
      this.seconds = ss || 0;
      this.timer = setTimeout(() =&amp;gt; {
        this.getTime(duration - 1);
      }, 1000);
    }
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;好了，但是并没有解释上面提出的那个问题：为什么要有&lt;code&gt;this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);&lt;/code&gt;这一句？&lt;/p&gt;
&lt;p&gt;这样，假设现在页面显示的是活动一的时间，这时，执行到setTimeout，在&lt;strong&gt;一秒后&lt;/strong&gt;就会把setTimeout里的回调函数放到任务队列中，&lt;strong&gt;注意是一秒后哦&lt;/strong&gt;！这时，然而，在这一秒的开头，我们点击了活动二按钮，这时候的活动二的时间就会传入倒计时组件中，然后触发&lt;code&gt;countDown()&lt;/code&gt;,也就调用&lt;code&gt;this.getTime(this.duration);&lt;/code&gt;，然后执行到setTimeout，也会一秒后把回调函数放到任务队列中。&lt;/p&gt;
&lt;p&gt;这时，任务队列中就会有两个setTimeout的回调函数了。等待一秒过去，两个回调函数相继执行，我们就会看到页面上的时间一下子背减了2，实际上是很快速地进行了两遍减1的操作。&lt;/p&gt;
&lt;p&gt;这就是为什么要添加上&lt;code&gt;this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);&lt;/code&gt;这一句的原因了。就是要把上一个setTimeout清除掉。&lt;/p&gt;
&lt;h3&gt;5. 使用 diffTime&lt;/h3&gt;
&lt;p&gt;当你认为这是一个完美的组件的时候，你想把这个组件用到项目上，假设你也确实用了，而且还上线了，确发现出现了个大问题：当页面打开的时候，倒计时开始了，时间是 &lt;code&gt;还剩1天12：25：25&lt;/code&gt;，然后有人给你发微信，你马上切换到微信，回复消息后切回浏览器，发现倒计时时间却还是&lt;code&gt;还剩1天12：25：25&lt;/code&gt;。你慌了：&lt;strong&gt;你写的代码出现bug了！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是怎么回事？&lt;/p&gt;
&lt;p&gt;出于节能的考虑, 部分浏览器在进入后台时(或者失去焦点时), &lt;strong&gt;会将 setTimeout 等定时任务暂停
待用户回到浏览器时, 才会重新激活定时任务&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;说是暂停, 其实应该说是延迟, 1s 的任务延迟到 2s, 2s 的延迟到 5s, 实际情况因浏览器而异。&lt;/p&gt;
&lt;p&gt;原来如此，看来不能每次都只是减1这么简单了（毕竟你把浏览器切到后台之后setTimeout就冷却了，等几秒后切回，然后执行setTimeout，只是减了一秒而已）。&lt;/p&gt;
&lt;p&gt;所以我们需要改写一下getTime方法。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
    还剩{{day}}天{{hours}}:{{mins}}:{{seconds}}
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
    days: &amp;#x27;0&amp;#x27;,
    hours: &amp;#x27;00&amp;#x27;,
    mins: &amp;#x27;00&amp;#x27;,
    seconds: &amp;#x27;00&amp;#x27;,
    timer: null,
    curTime: 0,&amp;#x2F;&amp;#x2F; 新增代码：
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  mounted() {
    this.countDown();
  },
  
  watch: {
    duration() {
      this.countDown();
    }
  },
  methods: {
    countDown() {
      &amp;#x2F;&amp;#x2F; 新增代码：
      this.curTime = Date.now();
      this.getTime(this.duration);
    },
    durationFormatter(){...}
    getTime(duration) {
      this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);
      if (duration &amp;lt; 0) {
        return;
      }
      const { dd, hh, mm, ss } = this.durationFormatter(duration);
      this.days = dd || 0;
      this.hours = hh || 0;
      this.mins = mm || 0;
      this.seconds = ss || 0;
      this.timer = setTimeout(() =&amp;gt; {
        &amp;#x2F;&amp;#x2F; 新增代码：
        const now = Date.now();
        const diffTime = Math.floor((now - this.curTime) &amp;#x2F; 1000);
        this.curTime = now;
        this.getTime(duration - diffTime);
      }, 1000);
    }
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到，我们在三个位置添加了新的代码。&lt;/p&gt;
&lt;p&gt;首先在data了添加了curTime这个变量，然后在执行countDown的时候给&lt;code&gt;curTime&lt;/code&gt;赋值&lt;code&gt;Date.now()&lt;/code&gt;，也就是当前的时刻，也就是显示在页面上的那个时刻。&lt;/p&gt;
&lt;p&gt;然后看修改的第三处代码。可以看到是将&lt;code&gt;-1&lt;/code&gt;改成了&lt;code&gt;-diffTime&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;now 是 setTimeout的回调函数执行的时候的那个时刻。&lt;/p&gt;
&lt;p&gt;因而 diffTime 则 表示 &lt;strong&gt;当前这个setTimeout的回调函数执行的时刻距离上 页面上的剩余时间上一次变化的时间段&lt;/strong&gt;。其实也就是 &lt;strong&gt;当前这个setTimeout的回调函数执行的时刻距离上 一个setTimeout的回调函数执行的时刻时间段。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可能你还是不太能理解diffTime。举个例子：&lt;/p&gt;
&lt;p&gt;你打开了这个倒计时页面，于是执行了countDown，也就是说要执行getTime这个方法了。也就是会马上执行下列的代码。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;执行完这些代码页面上就会出现剩余时间。&lt;/p&gt;
&lt;p&gt;而&lt;code&gt;this.curTime = Date.now();&lt;/code&gt; 就记录下了此刻的时间点。&lt;/p&gt;
&lt;p&gt;然后一秒后执行setTimeout里的回调函数：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;const now = Date.now();&lt;/code&gt; 记录当前这个setTimeout的回调函数执行的时间点。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;const diffTime = Math.floor((now - this.curTime) / 1000);&lt;/code&gt; 记录当前这个setTimeout的回调函数执行的时间点距离页面上开始 渲染 剩余时间的 这一段时间。其实此时的diffTime就是=1。&lt;/p&gt;
&lt;p&gt;然后&lt;code&gt;this.curTime = now;&lt;/code&gt; 将curTime的值变成当前这个setTimeout的回调函数执行的时间点。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;this.getTime(duration - diffTime);&lt;/code&gt; 其实就是&lt;code&gt;this.getTime(duration - 1);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;然后又执行getTime，就会重新执行下面的代码，有渲染了新的剩余时间。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后一秒后又要执行setTmieout的回调函数，在这一秒还没结束的时候，我们将浏览器切到后台，此时setTimeout冷却了。等5秒后再切回。于是setTmieout的回调函数才得以执行。&lt;/p&gt;
&lt;p&gt;这时&lt;code&gt;const now = Date.now();&lt;/code&gt; 记录当前这个setTimeout的回调函数执行的时间点。&lt;/p&gt;
&lt;p&gt;而curTime是上一个setTimeout的回调函数执行的时间。&lt;/p&gt;
&lt;p&gt;所以&lt;code&gt;const diffTime = Math.floor((now - this.curTime) / 1000);&lt;/code&gt;实际上，diffTime的值就是5秒。&lt;/p&gt;
&lt;p&gt;因而&lt;code&gt;this.getTime(duration - diffTime);&lt;/code&gt; 其实就是&lt;code&gt;this.getTime(duration - 5);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这样就完美解决了因为浏览器切到后台，导致剩余时间不变的问题。&lt;/p&gt;
&lt;h3&gt;6. 添加新功能：可以传入到期时间。&lt;/h3&gt;
&lt;p&gt;之前是只能传入剩余时间的，现在希望也支持传入到期时间。&lt;/p&gt;
&lt;p&gt;只需要改动一下duration就好了。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;  computed: {
    duration() {
      if (this.end) {
        let end = String(this.end).length &amp;gt;= 13 ? +this.end : +this.end * 1000;
        end -= Date.now();
        return end;
      }
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;判断传入的end的长度是否大于13来判断是秒还是毫秒。轻松！&lt;/p&gt;
&lt;h3&gt;7. 添加新功能：可以选择要显示的内容，例如只显示秒，或者只显示小时。&lt;/h3&gt;
&lt;p&gt;只需要改动一下html：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down no-rtl&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
      &amp;lt;slot v-bind=&amp;quot;{
        d: days, h: hours, m: mins, s: seconds,
        hh: &amp;#96;00${hours}&amp;#96;.slice(-2),
        mm: &amp;#96;00${mins}&amp;#96;.slice(-2),
        ss: &amp;#96;00${seconds}&amp;#96;.slice(-2),
      }&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;slot&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;很巧妙有没有，只需要用插槽，就把倒计时组件，也就是把子组件的值传递给父组件了。&lt;/p&gt;
&lt;p&gt;看看父组件是怎么使用这个组件的。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;base-counter v-slot=&amp;quot;timeObj&amp;quot; :time=&amp;quot;countDown&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;count-down&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;icon&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;div&amp;gt;
    {{timeObj.d}}天{{timeObj.hh}}小时{{timeObj.mm}}分钟{{timeObj.ss}}秒
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;base-counter&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;看，如此巧妙又简单。&lt;/p&gt;
&lt;p&gt;发现&lt;code&gt;00${hours}&lt;/code&gt;.slice(-2) 这种写法也很值得学习。以前在获得到分钟的时候，要手动判断获得的分钟是两位数还是一位数，如果是一位数的话就要在前面手动补上0。就像下面的代码：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;var StartMinute = startDate.getMinutes().toString().length &amp;gt;= 2 ? startDate.getMinutes() : &amp;#x27;0&amp;#x27; + startDate.getHours();
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而&lt;code&gt;00${hours}&lt;/code&gt;.slice(-2) 则不用判断，先补上0再说，然后再从后面往前截取两位。
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/72a2f7a957e64e9d96b00a420fa2725b~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;到此。&lt;/p&gt;
&lt;p&gt;一个完美的倒计时组件就完成了。
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f23f102146a1443db651239f5cba83df~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;h2&gt;三、学习总结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;明白了setInterval的缺点以及用setTimeout代替setInterval。&lt;/li&gt;
&lt;li&gt;学到了&lt;code&gt;“+”&lt;/code&gt;，操作，不管三七二十一，将接口得到的长串数字转化为数字保平安。&lt;/li&gt;
&lt;li&gt;利用clearTimeout来清除掉之前的计时器，以防止造成影响。&lt;/li&gt;
&lt;li&gt;学会使用v-slot来子传父传值&lt;/li&gt;
&lt;li&gt;学会一个倒计时组件，为了以后方便cv操作。把组件完整代码贴上：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down no-rtl&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
      &amp;lt;slot v-bind=&amp;quot;{
        d: days, h: hours, m: mins, s: seconds,
        hh: &amp;#96;00${hours}&amp;#96;.slice(-2),
        mm: &amp;#96;00${mins}&amp;#96;.slice(-2),
        ss: &amp;#96;00${seconds}&amp;#96;.slice(-2),
      }&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;slot&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
&amp;#x2F;* eslint-disable object-curly-newline *&amp;#x2F;

export default {
  data: () =&amp;gt; ({
    days: &amp;#x27;0&amp;#x27;,
    hours: &amp;#x27;00&amp;#x27;,
    mins: &amp;#x27;00&amp;#x27;,
    seconds: &amp;#x27;00&amp;#x27;,
    timer: null,
    curTime: 0
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    refreshCounter: {
      type: [Number, String],
      default: 0
    },
    end: {
      type: [Number, String],
      default: 0
    },
    isMiniSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      if (this.end) {
        let end = String(this.end).length &amp;gt;= 13 ? +this.end : +this.end * 1000;
        end -= Date.now();
        return end;
      }
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  mounted() {
    this.countDown();
  },
  watch: {
    duration() {
      this.countDown();
    },
    refreshCounter() {
      this.countDown();
    }
  },
  methods: {
    durationFormatter(time) {
      if (!time) return { ss: 0 };
      let t = time;
      const ss = t % 60;
      t = (t - ss) &amp;#x2F; 60;
      if (t &amp;lt; 1) return { ss };
      const mm = t % 60;
      t = (t - mm) &amp;#x2F; 60;
      if (t &amp;lt; 1) return { mm, ss };
      const hh = t % 24;
      t = (t - hh) &amp;#x2F; 24;
      if (t &amp;lt; 1) return { hh, mm, ss };
      const dd = t;
      return { dd, hh, mm, ss };
    },
    countDown() {
      &amp;#x2F;&amp;#x2F; eslint-disable-next-line no-unused-expressions
      this.curTime = Date.now();
      this.getTime(this.duration);
    },
    getTime(time) {
      &amp;#x2F;&amp;#x2F; eslint-disable-next-line no-unused-expressions
      this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);
      if (time &amp;lt; 0) {
        return;
      }
      &amp;#x2F;&amp;#x2F; eslint-disable-next-line object-curly-newline
      const { dd, hh, mm, ss } = this.durationFormatter(time);
      this.days = dd || 0;
      &amp;#x2F;&amp;#x2F; this.hours = &amp;#96;00${hh || &amp;#x27;&amp;#x27;}&amp;#96;.slice(-2);
      &amp;#x2F;&amp;#x2F; this.mins = &amp;#96;00${mm || &amp;#x27;&amp;#x27;}&amp;#96;.slice(-2);
      &amp;#x2F;&amp;#x2F; this.seconds = &amp;#96;00${ss || &amp;#x27;&amp;#x27;}&amp;#96;.slice(-2);
      this.hours = hh || 0;
      this.mins = mm || 0;
      this.seconds = ss || 0;
      this.timer = setTimeout(() =&amp;gt; {
        const now = Date.now();
        const diffTime = Math.floor((now - this.curTime) &amp;#x2F; 1000);
        const step = diffTime &amp;gt; 1 ? diffTime : 1; &amp;#x2F;&amp;#x2F; 页面退到后台的时候不会计时，对比时间差，大于1s的重置倒计时
        this.curTime = now;
        this.getTime(time - step);
      }, 1000);
    }
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;
@import &amp;#x27;~@assets&amp;#x2F;css&amp;#x2F;common.scss&amp;#x27;;

._base-count-down {
  color: #fff;
  text-align: left;
  position: relative;
  .content {
    width: auto;
    display: flex;
    align-items: center;
  }
  span {
    display: inline-block;
  }
  .section {
    position: relative;
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;你学到了什么，欢迎补充！！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;欢迎大家留言讨论，祝工作顺利、生活愉快！&lt;/p&gt;
&lt;p&gt;我是bigo前端，下期见。&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: bigo前端
        原文链接：&lt;a href=&quot;https://juejin.im/post/7038405108371030047&quot;&gt;https://juejin.im/post/7038405108371030047&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Mon, 17 Jan 2022 12:15:00 GMT</pubDate></item><item><title>就因为JSON.stringify，我的年终奖差点打水漂了__JavaScript__Vue.js__前端</title><link>http://vue-js.com/topic/61e54fb4ad745b003c9df779</link><guid>http://vue-js.com/topic/61e54fb4ad745b003c9df779</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: juejin&lt;/h2&gt;
&lt;p&gt;本文已参与 &lt;a href=&quot;https://juejin.cn/post/7012210233804079141&quot; title=&quot;https://juejin.cn/post/7012210233804079141&quot;&gt;「掘力星计划」&lt;/a&gt; ，赢取创作大礼包，挑战创作激励金。&lt;/p&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;「欢迎在评论区讨论，掘金官方将在&lt;a href=&quot;https://juejin.cn/post/7012210233804079141&quot; title=&quot;https://juejin.cn/post/7012210233804079141&quot;&gt;掘力星计划&lt;/a&gt;活动结束后，在评论区抽送100份掘金周边，抽奖详情见活动文章」&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;开发要对线上环境有一颗敬畏之心，任何一个点都有可能导致线上故障，也有可能让你的年终奖泡汤(⊙︿⊙)。比如使用了&lt;code&gt;JSON.stringify&lt;/code&gt;，这个无比熟悉但又无比陌生的API。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;看完本文您可以收获：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;了解一个差点让我年终奖打水漂的悲伤的故事o(╥﹏╥)o&lt;/li&gt;
&lt;li&gt;学习JSON.stringify的9大特性和转换规则 &lt;strong&gt;(重点)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;了解如何判断一个对象是否存在循环引用 &lt;strong&gt;(重点)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;从零开始手写一个JSON.stringify &lt;strong&gt;(重点)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;等等&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;说一个悲伤的故事&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;最近组内有个小伙伴离职了，他所负责的一块业务由我去维护，结果刚接手，代码还没捂热乎，差点背上p0的锅。请让我花一点时间和你说清楚来龙去脉。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;悲伤伊始&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;这一天&lt;strong&gt;胖头鱼&lt;/strong&gt;正徜徉在代码的海洋不可自拔，突然被拉进了一个线上问题排查群，群里不可谓不热闹。
&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e2a584a72ebc4440b53f8b34d943e391~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;产品同学&lt;/strong&gt;在诉苦：线上用户不能提交表单了，带来了好多客诉，估计会是p0故障，希望尽快解决。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;测试同学&lt;/strong&gt;在纳闷：这个场景测试和预发环境明明验过的，怎么线上就不行了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;后端同学&lt;/strong&gt;在讲原因：接口缺少了value字段，导致出错了。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;就是木有人说问题怎么解决!!!&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;就是木有人说问题怎么解决!!!&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;就是木有人说问题怎么解决!!!&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这样的场景不知道你是不是也似曾相识呢？o(╥﹏╥)o，不管咋说第一要务还是先把线上问题解决掉，减少持续影响，赶紧把交接的代码翻出来，开始了排查过程。&lt;/p&gt;
&lt;h3&gt;问题原因&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;如下图：有这样一个动态表单搜集页面，用户选择或者填写了信息之后（&lt;code&gt;各字段非必填情况下也可以直接提交&lt;/code&gt;），接着前端把数据发送给后端，结束，看起来没有多复杂的逻辑。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a48b74e1bad74c2588047e8ffbe8825f~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;直接错误原因&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;非必填情况下，signInfo字段中经过&lt;code&gt;JSON.stringify&lt;/code&gt;后的字符串对象缺少&lt;code&gt;value&lt;/code&gt; key，导致后端parse之后无法正确读取value值，进而报接口系统异常，用户无法进行下一步动作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 异常入参数据，数组字符串中没有value key
{
  signInfo: &amp;#x27;[{&amp;quot;fieldId&amp;quot;:539},{&amp;quot;fieldId&amp;quot;:540},{&amp;quot;fieldId&amp;quot;:546,&amp;quot;value&amp;quot;:&amp;quot;10:30&amp;quot;}]&amp;#x27;
}

&amp;#x2F;&amp;#x2F; 正常入参数据
{
  signInfo: &amp;#x27;[{&amp;quot;fieldId&amp;quot;:539,&amp;quot;value&amp;quot;:&amp;quot;银卡&amp;quot;},{&amp;quot;fieldId&amp;quot;:540,&amp;quot;value&amp;quot;:&amp;quot;2021-03-01&amp;quot;},{&amp;quot;fieldId&amp;quot;:546,&amp;quot;value&amp;quot;:&amp;quot;10:30&amp;quot;}]&amp;#x27;
}

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;异常数据是如何产生的&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 默认情况下数据是这样的
let signInfo = [
  {
    fieldId: 539,
    value: undefined
  },
  {
    fieldId: 540,
    value: undefined
  },
  {
    fieldId: 546,
    value: undefined
  },
]
&amp;#x2F;&amp;#x2F; 经过JSON.stringify之后的数据,少了value key,导致后端无法读取value值进行报错
&amp;#x2F;&amp;#x2F; 具体原因是&amp;#96;undefined&amp;#96;、&amp;#96;任意的函数&amp;#96;以及&amp;#96;symbol值&amp;#96;，出现在&amp;#96;非数组对象&amp;#96;的属性值中时在序列化过程中会被忽略
console.log(JSON.stringify(signInfo))
&amp;#x2F;&amp;#x2F; &amp;#x27;[{&amp;quot;fieldId&amp;quot;:539},{&amp;quot;fieldId&amp;quot;:540},{&amp;quot;fieldId&amp;quot;:546}]&amp;#x27;

&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;问题的原因找到了，解决方式 &lt;strong&gt;(这里只讲前端的解决方案，当然也可以由后端解决)&lt;/strong&gt; 也很简单，将value值为undefined的项转化为空字符串再提交即可。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;方案一：新开一个对象处理&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;let signInfo = [
  {
    fieldId: 539,
    value: undefined
  },
  {
    fieldId: 540,
    value: undefined
  },
  {
    fieldId: 546,
    value: undefined
  },
]

let newSignInfo = signInfo.map((it) =&amp;gt; {
  const value = typeof it.value === &amp;#x27;undefined&amp;#x27; ? &amp;#x27;&amp;#x27; : it.value
  return {
    ...it,
    value
  }
})

console.log(JSON.stringify(newSignInfo))
&amp;#x2F;&amp;#x2F; &amp;#x27;[{&amp;quot;fieldId&amp;quot;:539,&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;},{&amp;quot;fieldId&amp;quot;:540,&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;},{&amp;quot;fieldId&amp;quot;:546,&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;}]&amp;#x27;

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;方案二：利用&lt;code&gt;JSON.stringify&lt;/code&gt;第二个参数，直接处理&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;方案一的缺陷是需要新开一个对象进行一顿操作才能解决，&lt;code&gt;不够优雅&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;let signInfo = [
  {
    fieldId: 539,
    value: undefined
  },
  {
    fieldId: 540,
    value: undefined
  },
  {
    fieldId: 546,
    value: undefined
  },
]

&amp;#x2F;&amp;#x2F; 判断到value为undefined，返回空字符串即可
JSON.stringify(signInfo, (key, value) =&amp;gt; typeof value === &amp;#x27;undefined&amp;#x27; ? &amp;#x27;&amp;#x27; : value)
&amp;#x2F;&amp;#x2F; &amp;#x27;[{&amp;quot;fieldId&amp;quot;:539,&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;},{&amp;quot;fieldId&amp;quot;:540,&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;},{&amp;quot;fieldId&amp;quot;:546,&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;}]&amp;#x27;

&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;故事后续&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原本这是一个已经上线有一段时间的页面，为何会突然出现这个问题，之前却没有呢？仔细询问下，原来是中途产品同学提了一个小的优化点，离职的小伙伴感觉点比较小直接就改了代码上线了，未曾想出现了线上问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;后面针对这件事从产品到测试、到后端、到前端单独做了一个完整的复盘，细节就不再展开说了。&lt;/p&gt;
&lt;p&gt;因为从发现问题到解决问题速度较快、影响用户数较少，还未达到问责程度，&lt;strong&gt;俺的年终奖可算是保住了o(╥﹏╥)o。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;重学JSON.stringify&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;经过这件事情，我觉得有必要重新审视一下&lt;code&gt;JSON.stringify&lt;/code&gt;这个方法，彻底搞清楚转换规则，并尝试手写实现一个&lt;code&gt;JSON.stringify&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果你曾遇到和我一样的问题，欢迎一起来重新学习一次，一定会有不一样的收获噢！&lt;/p&gt;
&lt;h2&gt;学透JSON.stringify&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;JSON.stringify()&lt;/code&gt; 方法将一个 &lt;code&gt;JavaScript 对象&lt;/code&gt;或&lt;code&gt;值&lt;/code&gt;转换为 JSON 字符串，如果指定了一个 replacer 函数，则可以选择性地替换值，或者指定的 replacer 是数组，则可选择性地仅包含数组指定的属性。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以下信息来自&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#syntax&quot;&gt;MDN&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;语法&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;JSON.stringify(value[, replacer [, space]])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#parameters&quot; title=&quot;Permalink to 参数&quot;&gt;参数&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;value&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;将要序列化成 一个 JSON 字符串的值。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;replacer&lt;/code&gt; 可选&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果该参数是一个函数，则在序列化过程中，被序列化的值的每个属性都会经过该函数的转换和处理；&lt;/li&gt;
&lt;li&gt;如果该参数是一个数组，则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中；&lt;/li&gt;
&lt;li&gt;如果该参数为 null 或者未提供，则对象所有的属性都会被序列化。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;space&lt;/code&gt; 可选&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;指定缩进用的空白字符串，用于美化输出（pretty-print）；&lt;/li&gt;
&lt;li&gt;如果参数是个数字，它代表有多少的空格；上限为10。&lt;/li&gt;
&lt;li&gt;该值若小于1，则意味着没有空格；&lt;/li&gt;
&lt;li&gt;如果该参数为字符串（当字符串长度超过10个字母，取其前10个字母），该字符串将被作为空格；&lt;/li&gt;
&lt;li&gt;如果该参数没有提供（或者为 null），将没有空格。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;返回值&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;一个表示给定值的JSON字符串。
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#%E5%BC%82%E5%B8%B8&quot; title=&quot;Permalink to 异常&quot;&gt;异常&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当在循环引用时会抛出异常&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypeError&quot;&gt;&lt;code&gt;TypeError&lt;/code&gt;&lt;/a&gt; (“cyclic object value”)（循环对象值）&lt;/li&gt;
&lt;li&gt;当尝试去转换 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt&quot;&gt;&lt;code&gt;BigInt&lt;/code&gt;&lt;/a&gt; 类型的值会抛出&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypeError&quot;&gt;&lt;code&gt;TypeError&lt;/code&gt;&lt;/a&gt; (“BigInt value can’t be serialized in JSON”)（BigInt值不能JSON序列化）.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;基本使用&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;JSON.stringify可以转换对象或者值（平常用的更多的是转换对象）&lt;/li&gt;
&lt;li&gt;可以指定&lt;code&gt;replacer&lt;/code&gt;为函数选择性的地替换&lt;/li&gt;
&lt;li&gt;也可以指定&lt;code&gt;replacer&lt;/code&gt;为数组，可转换指定的属性&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这里仅仅是&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify&quot;&gt;MDN&lt;/a&gt;上关于&lt;code&gt;JSON.stringify&lt;/code&gt;其中最基础的说明，咱们先打个码试试这几个特性&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 1. 转换对象
console.log(JSON.stringify({ name: &amp;#x27;前端胖头鱼&amp;#x27;, sex: &amp;#x27;boy&amp;#x27; })) &amp;#x2F;&amp;#x2F; &amp;#x27;{&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;sex&amp;quot;:&amp;quot;boy&amp;quot;}&amp;#x27;

&amp;#x2F;&amp;#x2F; 2. 转换普通值
console.log(JSON.stringify(&amp;#x27;前端胖头鱼&amp;#x27;)) &amp;#x2F;&amp;#x2F; &amp;quot;前端胖头鱼&amp;quot;
console.log(JSON.stringify(1)) &amp;#x2F;&amp;#x2F; &amp;quot;1&amp;quot;
console.log(JSON.stringify(true)) &amp;#x2F;&amp;#x2F; &amp;quot;true&amp;quot;
console.log(JSON.stringify(null)) &amp;#x2F;&amp;#x2F; &amp;quot;null&amp;quot;

&amp;#x2F;&amp;#x2F; 3. 指定replacer函数
console.log(JSON.stringify({ name: &amp;#x27;前端胖头鱼&amp;#x27;, sex: &amp;#x27;boy&amp;#x27;, age: 100 }, (key, value) =&amp;gt; {
  return typeof value === &amp;#x27;number&amp;#x27; ? undefined : value
}))
&amp;#x2F;&amp;#x2F; &amp;#x27;{&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;sex&amp;quot;:&amp;quot;boy&amp;quot;}&amp;#x27;

&amp;#x2F;&amp;#x2F; 4. 指定数组
console.log(JSON.stringify({ name: &amp;#x27;前端胖头鱼&amp;#x27;, sex: &amp;#x27;boy&amp;#x27;, age: 100 }, [ &amp;#x27;name&amp;#x27; ]))
&amp;#x2F;&amp;#x2F; &amp;#x27;{&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;}&amp;#x27;

&amp;#x2F;&amp;#x2F; 5. 指定space(美化输出)
console.log(JSON.stringify({ name: &amp;#x27;前端胖头鱼&amp;#x27;, sex: &amp;#x27;boy&amp;#x27;, age: 100 }))
&amp;#x2F;&amp;#x2F; &amp;#x27;{&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;sex&amp;quot;:&amp;quot;boy&amp;quot;,&amp;quot;age&amp;quot;:100}&amp;#x27;
console.log(JSON.stringify({ name: &amp;#x27;前端胖头鱼&amp;#x27;, sex: &amp;#x27;boy&amp;#x27;, age: 100 }, null , 2))
&amp;#x2F;*
{
  &amp;quot;name&amp;quot;: &amp;quot;前端胖头鱼&amp;quot;,
  &amp;quot;sex&amp;quot;: &amp;quot;boy&amp;quot;,
  &amp;quot;age&amp;quot;: 100
}
*&amp;#x2F;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;9大特性要记住&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;以前仅仅是使用了这个方法，却没有详细了解他的转换规则，居然有9个之多。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;特性一&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;undefined&lt;/code&gt;、&lt;code&gt;任意的函数&lt;/code&gt;以及&lt;code&gt;symbol值&lt;/code&gt;，出现在&lt;code&gt;非数组对象&lt;/code&gt;的属性值中时在序列化过程中会被忽略&lt;/li&gt;
&lt;li&gt;&lt;code&gt;undefined&lt;/code&gt;、&lt;code&gt;任意的函数&lt;/code&gt;以及&lt;code&gt;symbol值&lt;/code&gt;出现在&lt;code&gt;数组&lt;/code&gt;中时会被转换成 &lt;code&gt;null&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;undefined&lt;/code&gt;、&lt;code&gt;任意的函数&lt;/code&gt;以及&lt;code&gt;symbol值&lt;/code&gt;被&lt;code&gt;单独转换&lt;/code&gt;时，会返回 undefined&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 1. 对象中存在这三种值会被忽略
console.log(JSON.stringify({
  name: &amp;#x27;前端胖头鱼&amp;#x27;,
  sex: &amp;#x27;boy&amp;#x27;,
  &amp;#x2F;&amp;#x2F; 函数会被忽略
  showName () {
    console.log(&amp;#x27;前端胖头鱼&amp;#x27;)
  },
  &amp;#x2F;&amp;#x2F; undefined会被忽略
  age: undefined,
  &amp;#x2F;&amp;#x2F; Symbol会被忽略
  symbolName: Symbol(&amp;#x27;前端胖头鱼&amp;#x27;)
}))
&amp;#x2F;&amp;#x2F; &amp;#x27;{&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;sex&amp;quot;:&amp;quot;boy&amp;quot;}&amp;#x27;

&amp;#x2F;&amp;#x2F; 2. 数组中存在着三种值会被转化为null
console.log(JSON.stringify([
  &amp;#x27;前端胖头鱼&amp;#x27;,
  &amp;#x27;boy&amp;#x27;,
  &amp;#x2F;&amp;#x2F; 函数会被转化为null
  function showName () {
    console.log(&amp;#x27;前端胖头鱼&amp;#x27;)
  },
  &amp;#x2F;&amp;#x2F;undefined会被转化为null
  undefined,
  &amp;#x2F;&amp;#x2F;Symbol会被转化为null
  Symbol(&amp;#x27;前端胖头鱼&amp;#x27;)
]))
&amp;#x2F;&amp;#x2F; &amp;#x27;[&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;boy&amp;quot;,null,null,null]&amp;#x27;

&amp;#x2F;&amp;#x2F; 3.单独转换会返回undefined
console.log(JSON.stringify(
  function showName () {
    console.log(&amp;#x27;前端胖头鱼&amp;#x27;)
  }
)) &amp;#x2F;&amp;#x2F; undefined
console.log(JSON.stringify(undefined)) &amp;#x2F;&amp;#x2F; undefined
console.log(JSON.stringify(Symbol(&amp;#x27;前端胖头鱼&amp;#x27;))) &amp;#x2F;&amp;#x2F; undefined

&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性二&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;布尔值&lt;/code&gt;、&lt;code&gt;数字&lt;/code&gt;、&lt;code&gt;字符串&lt;/code&gt;的包装对象在序列化过程中会自动转换成对应的原始值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;console.log(JSON.stringify([new Number(1), new String(&amp;quot;前端胖头鱼&amp;quot;), new Boolean(false)]))
&amp;#x2F;&amp;#x2F; &amp;#x27;[1,&amp;quot;前端胖头鱼&amp;quot;,false]&amp;#x27;
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性三&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;所有以&lt;code&gt;symbol&lt;/code&gt;为属性键的属性都会被完全忽略掉，即便 &lt;code&gt;replacer&lt;/code&gt; 参数中强制指定包含了它们。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;console.log(JSON.stringify({
  [Symbol(&amp;#x27;前端胖头鱼&amp;#x27;)]: &amp;#x27;前端胖头鱼&amp;#x27;}
)) 
&amp;#x2F;&amp;#x2F; &amp;#x27;{}&amp;#x27;
console.log(JSON.stringify({
  [ Symbol(&amp;#x27;前端胖头鱼&amp;#x27;) ]: &amp;#x27;前端胖头鱼&amp;#x27;,
}, (key, value) =&amp;gt; {
  if (typeof key === &amp;#x27;symbol&amp;#x27;) {
    return value
  }
}))
&amp;#x2F;&amp;#x2F; undefined
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性四&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;NaN 和 Infinity 格式的数值及 null 都会被当做 null。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;console.log(JSON.stringify({
  age: NaN,
  age2: Infinity,
  name: null
}))
&amp;#x2F;&amp;#x2F; &amp;#x27;{&amp;quot;age&amp;quot;:null,&amp;quot;age2&amp;quot;:null,&amp;quot;name&amp;quot;:null}&amp;#x27;

&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性五&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;转换值如果有 toJSON() 方法，该方法定义什么值将被序列化。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;const toJSONObj = {
  name: &amp;#x27;前端胖头鱼&amp;#x27;,
  toJSON () {
    return &amp;#x27;JSON.stringify&amp;#x27;
  }
}

console.log(JSON.stringify(toJSONObj))
&amp;#x2F;&amp;#x2F; &amp;quot;JSON.stringify&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性六&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Date 日期调用了 toJSON() 将其转换为了 string 字符串（同Date.toISOString()），因此会被当做字符串处理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;const d = new Date()

console.log(d.toJSON()) &amp;#x2F;&amp;#x2F; 2021-10-05T14:01:23.932Z
console.log(JSON.stringify(d)) &amp;#x2F;&amp;#x2F; &amp;quot;2021-10-05T14:01:23.932Z&amp;quot;

&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性七&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;对包含循环引用的对象（对象之间相互引用，形成无限循环）执行此方法，会抛出错误。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;let cyclicObj = {
  name: &amp;#x27;前端胖头鱼&amp;#x27;,
}

cyclicObj.obj = cyclicObj

console.log(JSON.stringify(cyclicObj))
&amp;#x2F;&amp;#x2F; Converting circular structure to JSON
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性八&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;其他类型的对象，包括 Map/Set/WeakMap/WeakSet，仅会序列化可枚举的属性&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;let enumerableObj = {}

Object.defineProperties(enumerableObj, {
  name: {
    value: &amp;#x27;前端胖头鱼&amp;#x27;,
    enumerable: true
  },
  sex: {
    value: &amp;#x27;boy&amp;#x27;,
    enumerable: false
  },
})

console.log(JSON.stringify(enumerableObj))
&amp;#x2F;&amp;#x2F; &amp;#x27;{&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;}&amp;#x27;
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性九&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;当尝试去转换 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt&quot;&gt;&lt;code&gt;BigInt&lt;/code&gt;&lt;/a&gt; 类型的值会抛出错误&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;const alsoHuge = BigInt(9007199254740991)

console.log(JSON.stringify(alsoHuge))
&amp;#x2F;&amp;#x2F; TypeError: Do not know how to serialize a BigInt
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;手写一个JSON.stringify&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;终于重新学完&lt;code&gt;JSON.stringify&lt;/code&gt;的众多特性啦！咱们根据这些特性来手写一个简单版本的吧（&lt;strong&gt;无replacer函数和space&lt;/strong&gt;）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;源码实现&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;const jsonstringify = (data) =&amp;gt; {
  &amp;#x2F;&amp;#x2F; 确认一个对象是否存在循环引用
  const isCyclic = (obj) =&amp;gt; {
  &amp;#x2F;&amp;#x2F; 使用Set数据类型来存储已经检测过的对象
  let stackSet = new Set()
  let detected = false

  const detect = (obj) =&amp;gt; {
    &amp;#x2F;&amp;#x2F; 不是对象类型的话，可以直接跳过
    if (obj &amp;amp;&amp;amp; typeof obj != &amp;#x27;object&amp;#x27;) {
      return
    }
    &amp;#x2F;&amp;#x2F; 当要检查的对象已经存在于stackSet中时，表示存在循环引用
    if (stackSet.has(obj)) {
      return detected = true
    }
    &amp;#x2F;&amp;#x2F; 将当前obj存如stackSet
    stackSet.add(obj)

    for (let key in obj) {
      &amp;#x2F;&amp;#x2F; 对obj下的属性进行挨个检测
      if (obj.hasOwnProperty(key)) {
        detect(obj[key])
      }
    }
    &amp;#x2F;&amp;#x2F; 平级检测完成之后，将当前对象删除，防止误判
    &amp;#x2F;*
      例如：对象的属性指向同一引用，如果不删除的话，会被认为是循环引用
      let tempObj = {
        name: &amp;#x27;前端胖头鱼&amp;#x27;
      }
      let obj4 = {
        obj1: tempObj,
        obj2: tempObj
      }
    *&amp;#x2F;
    stackSet.delete(obj)
  }

  detect(obj)

  return detected
}

  &amp;#x2F;&amp;#x2F; 特性七:
  &amp;#x2F;&amp;#x2F; 对包含循环引用的对象（对象之间相互引用，形成无限循环）执行此方法，会抛出错误。
  if (isCyclic(data)) {
    throw new TypeError(&amp;#x27;Converting circular structure to JSON&amp;#x27;)
  }

  &amp;#x2F;&amp;#x2F; 特性九:
  &amp;#x2F;&amp;#x2F; 当尝试去转换 BigInt 类型的值会抛出错误
  if (typeof data === &amp;#x27;bigint&amp;#x27;) {
    throw new TypeError(&amp;#x27;Do not know how to serialize a BigInt&amp;#x27;)
  }

  const type = typeof data
  const commonKeys1 = [&amp;#x27;undefined&amp;#x27;, &amp;#x27;function&amp;#x27;, &amp;#x27;symbol&amp;#x27;]
  const getType = (s) =&amp;gt; {
    return Object.prototype.toString.call(s).replace(&amp;#x2F;&amp;#x5C;[object (.*?)&amp;#x5C;]&amp;#x2F;, &amp;#x27;$1&amp;#x27;).toLowerCase()
  }

  &amp;#x2F;&amp;#x2F; 非对象
  if (type !== &amp;#x27;object&amp;#x27; || data === null) {
    let result = data
    &amp;#x2F;&amp;#x2F; 特性四：
    &amp;#x2F;&amp;#x2F; NaN 和 Infinity 格式的数值及 null 都会被当做 null。
    if ([NaN, Infinity, null].includes(data)) {
      result = &amp;#x27;null&amp;#x27;
      &amp;#x2F;&amp;#x2F; 特性一：
      &amp;#x2F;&amp;#x2F; &amp;#96;undefined&amp;#96;、&amp;#96;任意的函数&amp;#96;以及&amp;#96;symbol值&amp;#96;被&amp;#96;单独转换&amp;#96;时，会返回 undefined
    } else if (commonKeys1.includes(type)) {
      &amp;#x2F;&amp;#x2F; 直接得到undefined，并不是一个字符串&amp;#x27;undefined&amp;#x27;
      return undefined
    } else if (type === &amp;#x27;string&amp;#x27;) {
      result = &amp;#x27;&amp;quot;&amp;#x27; + data + &amp;#x27;&amp;quot;&amp;#x27;
    }

    return String(result)
  } else if (type === &amp;#x27;object&amp;#x27;) {
    &amp;#x2F;&amp;#x2F; 特性五:
    &amp;#x2F;&amp;#x2F; 转换值如果有 toJSON() 方法，该方法定义什么值将被序列化
    &amp;#x2F;&amp;#x2F; 特性六:
    &amp;#x2F;&amp;#x2F; Date 日期调用了 toJSON() 将其转换为了 string 字符串（同Date.toISOString()），因此会被当做字符串处理。
    if (typeof data.toJSON === &amp;#x27;function&amp;#x27;) {
      return jsonstringify(data.toJSON())
    } else if (Array.isArray(data)) {
      let result = data.map((it) =&amp;gt; {
        &amp;#x2F;&amp;#x2F; 特性一:
        &amp;#x2F;&amp;#x2F; &amp;#96;undefined&amp;#96;、&amp;#96;任意的函数&amp;#96;以及&amp;#96;symbol值&amp;#96;出现在&amp;#96;数组&amp;#96;中时会被转换成 &amp;#96;null&amp;#96;
        return commonKeys1.includes(typeof it) ? &amp;#x27;null&amp;#x27; : jsonstringify(it)
      })

      return &amp;#96;[${result}]&amp;#96;.replace(&amp;#x2F;&amp;#x27;&amp;#x2F;g, &amp;#x27;&amp;quot;&amp;#x27;)
    } else {
      &amp;#x2F;&amp;#x2F; 特性二：
      &amp;#x2F;&amp;#x2F; 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
      if ([&amp;#x27;boolean&amp;#x27;, &amp;#x27;number&amp;#x27;].includes(getType(data))) {
        return String(data)
      } else if (getType(data) === &amp;#x27;string&amp;#x27;) {
        return &amp;#x27;&amp;quot;&amp;#x27; + data + &amp;#x27;&amp;quot;&amp;#x27;
      } else {
        let result = []
        &amp;#x2F;&amp;#x2F; 特性八
        &amp;#x2F;&amp;#x2F; 其他类型的对象，包括 Map&amp;#x2F;Set&amp;#x2F;WeakMap&amp;#x2F;WeakSet，仅会序列化可枚举的属性
        Object.keys(data).forEach((key) =&amp;gt; {
          &amp;#x2F;&amp;#x2F; 特性三:
          &amp;#x2F;&amp;#x2F; 所有以symbol为属性键的属性都会被完全忽略掉，即便 replacer 参数中强制指定包含了它们。
          if (typeof key !== &amp;#x27;symbol&amp;#x27;) {
            const value = data[key]
            &amp;#x2F;&amp;#x2F; 特性一
            &amp;#x2F;&amp;#x2F; &amp;#96;undefined&amp;#96;、&amp;#96;任意的函数&amp;#96;以及&amp;#96;symbol值&amp;#96;，出现在&amp;#96;非数组对象&amp;#96;的属性值中时在序列化过程中会被忽略
            if (!commonKeys1.includes(typeof value)) {
              result.push(&amp;#96;&amp;quot;${key}&amp;quot;:${jsonstringify(value)}&amp;#96;)
            }
          }
        })

        return &amp;#96;{${result}}&amp;#96;.replace(&amp;#x2F;&amp;#x27;&amp;#x2F;, &amp;#x27;&amp;quot;&amp;#x27;)
      }
    }
  }
}


&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;测试一把&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 1. 测试一下基本输出
console.log(jsonstringify(undefined)) &amp;#x2F;&amp;#x2F; undefined 
console.log(jsonstringify(() =&amp;gt; { })) &amp;#x2F;&amp;#x2F; undefined
console.log(jsonstringify(Symbol(&amp;#x27;前端胖头鱼&amp;#x27;))) &amp;#x2F;&amp;#x2F; undefined
console.log(jsonstringify((NaN))) &amp;#x2F;&amp;#x2F; null
console.log(jsonstringify((Infinity))) &amp;#x2F;&amp;#x2F; null
console.log(jsonstringify((null))) &amp;#x2F;&amp;#x2F; null
console.log(jsonstringify({
  name: &amp;#x27;前端胖头鱼&amp;#x27;,
  toJSON() {
    return {
      name: &amp;#x27;前端胖头鱼2&amp;#x27;,
      sex: &amp;#x27;boy&amp;#x27;
    }
  }
}))
&amp;#x2F;&amp;#x2F; {&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼2&amp;quot;,&amp;quot;sex&amp;quot;:&amp;quot;boy&amp;quot;}

&amp;#x2F;&amp;#x2F; 2. 和原生的JSON.stringify转换进行比较
console.log(jsonstringify(null) === JSON.stringify(null));
&amp;#x2F;&amp;#x2F; true
console.log(jsonstringify(undefined) === JSON.stringify(undefined));
&amp;#x2F;&amp;#x2F; true
console.log(jsonstringify(false) === JSON.stringify(false));
&amp;#x2F;&amp;#x2F; true
console.log(jsonstringify(NaN) === JSON.stringify(NaN));
&amp;#x2F;&amp;#x2F; true
console.log(jsonstringify(Infinity) === JSON.stringify(Infinity));
&amp;#x2F;&amp;#x2F; true
let str = &amp;quot;前端胖头鱼&amp;quot;;
console.log(jsonstringify(str) === JSON.stringify(str));
&amp;#x2F;&amp;#x2F; true
let reg = new RegExp(&amp;quot;&amp;#x5C;w&amp;quot;);
console.log(jsonstringify(reg) === JSON.stringify(reg));
&amp;#x2F;&amp;#x2F; true
let date = new Date();
console.log(jsonstringify(date) === JSON.stringify(date));
&amp;#x2F;&amp;#x2F; true
let sym = Symbol(&amp;#x27;前端胖头鱼&amp;#x27;);
console.log(jsonstringify(sym) === JSON.stringify(sym));
&amp;#x2F;&amp;#x2F; true
let array = [1, 2, 3];
console.log(jsonstringify(array) === JSON.stringify(array));
&amp;#x2F;&amp;#x2F; true
let obj = {
  name: &amp;#x27;前端胖头鱼&amp;#x27;,
  age: 18,
  attr: [&amp;#x27;coding&amp;#x27;, 123],
  date: new Date(),
  uni: Symbol(2),
  sayHi: function () {
    console.log(&amp;quot;hello world&amp;quot;)
  },
  info: {
    age: 16,
    intro: {
      money: undefined,
      job: null
    }
  },
  pakingObj: {
    boolean: new Boolean(false),
    string: new String(&amp;#x27;前端胖头鱼&amp;#x27;),
    number: new Number(1),
  }
}
console.log(jsonstringify(obj) === JSON.stringify(obj)) 
&amp;#x2F;&amp;#x2F; true
console.log((jsonstringify(obj)))
&amp;#x2F;&amp;#x2F; {&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;age&amp;quot;:18,&amp;quot;attr&amp;quot;:[&amp;quot;coding&amp;quot;,123],&amp;quot;date&amp;quot;:&amp;quot;2021-10-06T14:59:58.306Z&amp;quot;,&amp;quot;info&amp;quot;:{&amp;quot;age&amp;quot;:16,&amp;quot;intro&amp;quot;:{&amp;quot;job&amp;quot;:null}},&amp;quot;pakingObj&amp;quot;:{&amp;quot;boolean&amp;quot;:false,&amp;quot;string&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;number&amp;quot;:1}}
console.log(JSON.stringify(obj))
&amp;#x2F;&amp;#x2F; {&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;age&amp;quot;:18,&amp;quot;attr&amp;quot;:[&amp;quot;coding&amp;quot;,123],&amp;quot;date&amp;quot;:&amp;quot;2021-10-06T14:59:58.306Z&amp;quot;,&amp;quot;info&amp;quot;:{&amp;quot;age&amp;quot;:16,&amp;quot;intro&amp;quot;:{&amp;quot;job&amp;quot;:null}},&amp;quot;pakingObj&amp;quot;:{&amp;quot;boolean&amp;quot;:false,&amp;quot;string&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;number&amp;quot;:1}}

&amp;#x2F;&amp;#x2F; 3. 测试可遍历对象
let enumerableObj = {}

Object.defineProperties(enumerableObj, {
  name: {
    value: &amp;#x27;前端胖头鱼&amp;#x27;,
    enumerable: true
  },
  sex: {
    value: &amp;#x27;boy&amp;#x27;,
    enumerable: false
  },
})

console.log(jsonstringify(enumerableObj))
&amp;#x2F;&amp;#x2F; {&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;}

&amp;#x2F;&amp;#x2F; 4. 测试循环引用和Bigint

let obj1 = { a: &amp;#x27;aa&amp;#x27; }
let obj2 = { name: &amp;#x27;前端胖头鱼&amp;#x27;, a: obj1, b: obj1 }
obj2.obj = obj2

console.log(jsonstringify(obj2))
&amp;#x2F;&amp;#x2F; TypeError: Converting circular structure to JSON
console.log(jsonStringify(BigInt(1)))
&amp;#x2F;&amp;#x2F; TypeError: Do not know how to serialize a BigInt

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;通过上面测试可以看出，&lt;code&gt;jsonstringify&lt;/code&gt;基本和&lt;code&gt;JSON.stringify&lt;/code&gt;表现一致，（也有可能测试用例不够全面，欢迎提出一起学习）&lt;/p&gt;
&lt;h2&gt;结尾&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;因为一个BUG，重学了&lt;code&gt;JSON.stringify&lt;/code&gt;,了解到原来它还有这么多平时没有注意到特性，前端娱乐圈水太深了，愿大家都被温柔以待，少些bug，多些关怀。晚安&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 前端胖头鱼
        原文链接：&lt;a href=&quot;https://juejin.im/post/7017588385615200270&quot;&gt;https://juejin.im/post/7017588385615200270&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Mon, 17 Jan 2022 11:15:00 GMT</pubDate></item><item><title>浅析我们为什么要上Vue3？__前端__Vue.js</title><link>http://vue-js.com/topic/61e541a4ad745b003c9df778</link><guid>http://vue-js.com/topic/61e541a4ad745b003c9df778</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: vue-pro
highlight: a11y-dark&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;专栏地址：&lt;a href=&quot;https://juejin.cn/column/7007233236925415432&quot;&gt;Vue 源码解析与实践&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本文：&lt;a href=&quot;https://juejin.cn/user/870468940732983&quot;&gt;我是970&lt;/a&gt;&lt;br&gt;
原文链接：&lt;a href=&quot;https://juejin.cn/post/7040769191393099783&quot;&gt;https://juejin.cn/post/7040769191393099783&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;✨ 开篇&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;RT，我们为什么要上 Vue3？使用 Vue3 影响我开法拉利吗？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最近在 Vue3 发布了&lt;code&gt;3.2&lt;/code&gt; 大版本之后，掘金上关于 Vue3 的文章越来越多，本来想着让子弹再飞一会，但最近公司上了 Vue3 的项目，自己也跟着学了起来。&lt;/p&gt;
&lt;p&gt;昨天还是 Vue2 的 Options API 的忠实信徒，结果今天搞了 Vue3 的 Composition API 之后，直呼 Vue3 真香！&lt;/p&gt;
&lt;p&gt;接下来，我们从分析 Vue2 优缺点入手，以及结合图片和用例来了解 Vue3 的优势。&lt;/p&gt;
&lt;h2&gt;Vue2 ⚔ Vue3&lt;/h2&gt;
&lt;h3&gt;Vue2&lt;/h3&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;p&gt;不可否认 Vue2 在取得的成功，在 Github 的 Front-End 分类的排行榜上也能看到 Vue 仓库的排名是第一，粉丝足够多，活跃用户足够多&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a8dace12e30945d181ec50023345c6cb~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/46751d216b4847e48714d649f02cd97a~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;比 React 足足多了 12k+ star&lt;/p&gt;
&lt;p&gt;从我的角度来看，最大的功臣莫过于这三个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;响应式数据&lt;/li&gt;
&lt;li&gt;Options API&lt;/li&gt;
&lt;li&gt;SFC&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除去这三位大将，不可或缺 &lt;code&gt;Vuex&lt;/code&gt;/&lt;code&gt;Vue-Router&lt;/code&gt; 这两位功臣，以及丰富的周边插件和活跃的社区。&lt;/p&gt;
&lt;h4&gt;缺点&lt;/h4&gt;
&lt;p&gt;在一个组件仅承担单一逻辑的时候，使用 Options API 来书写组件是很清晰的。&lt;/p&gt;
&lt;p&gt;但是在我们实际的业务场景中，一个父组件总是要包含多个子组件，父组件需要给子组件传值、处理子组件事件、直接操作子组件以及处理各种各样的调接口的逻辑，这时候我们的父组件的逻辑就会变得复杂。&lt;/p&gt;
&lt;p&gt;我们从代码维护者的角度出发，假设这个时候有 10 个函数方法，自然我们要把他们放入到&lt;code&gt;methods&lt;/code&gt;中，而这个 10 个函数方法又分别操作 10 个数据，这个 10 个数据又分别需要进行&lt;code&gt;Watch&lt;/code&gt;操作。&lt;/p&gt;
&lt;p&gt;这时候，我们根据&lt;code&gt;Vue2&lt;/code&gt;的&lt;code&gt;Options API&lt;/code&gt;的写法，就写完了 10 个&lt;code&gt;method&lt;/code&gt;、10 个&lt;code&gt;data&lt;/code&gt;、10 个&lt;code&gt;watch&lt;/code&gt;，我们就将本来 10 个的函数方法，分割在 30 个不同的地方。&lt;/p&gt;
&lt;p&gt;这时候父组件的代码，对代码维护者来说是很不友好的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可能有人说，这么写可以增加代码量，老板夸我牛逼！哈哈哈哈 &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Vue3&lt;/h3&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;自由，自由，还是 TM 的自由！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;更强的性能，更好的 tree shaking&lt;/li&gt;
&lt;li&gt;Composition API + setup&lt;/li&gt;
&lt;li&gt;更好地支持 TypeScript&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;可能的缺点&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Vue3: 让你自由过了火&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;使用&lt;code&gt;Composition API&lt;/code&gt;在&lt;code&gt;setup&lt;/code&gt;这个舞台上尽情的表演之后，可能存在一个问题：那就是如何优雅地组织代码？&lt;/p&gt;
&lt;p&gt;代码不能优雅的组织，在代码量上去之后，也一样很难维护。&lt;/p&gt;
&lt;h3&gt;SFC 写法变化&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/19a5449cbc1b45f4b6bb8941fe940180~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;Options API对比Composition API.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Vue2 一个普通的 Vue 文件&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;{{ person.name }}&amp;lt;&amp;#x2F;p&amp;gt;
    &amp;lt;p&amp;gt;{{ car.name }}&amp;lt;&amp;#x2F;p&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: &amp;quot;Person&amp;quot;,

  data() {
    return {
      person: {
        name: &amp;quot;小明&amp;quot;,
        sex: &amp;quot;male&amp;quot;,
      },
      car: {
        name: &amp;quot;宝马&amp;quot;,
        price: &amp;quot;40w&amp;quot;,
      }
    };
  },

  watch:{
      &amp;#x27;person.name&amp;#x27;: (value) =&amp;gt; {
          console.log(&amp;#96;名字被修改了, 修改为 ${value}&amp;#96;)
      },
      &amp;#x27;person.sex&amp;#x27;: (value) =&amp;gt; {
          console.log(&amp;#96;性别被修改了, 修改为 ${value}&amp;#96;)
      }
  },

  methods: {
    changePersonName() {
      this.person.name = &amp;quot;小浪&amp;quot;;
    },

    changeCarPrice() {
      this.car.price = &amp;quot;80w&amp;quot;;
    }
  },
};
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;采用 Vue3 Composition API 进行重写&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;p&amp;gt;{{ person.name }}&amp;lt;&amp;#x2F;p&amp;gt;
  &amp;lt;p&amp;gt;{{ car.name }}&amp;lt;&amp;#x2F;p&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script lang=&amp;quot;ts&amp;quot; setup&amp;gt;
import { reactive, watch } from &amp;quot;vue&amp;quot;;

&amp;#x2F;&amp;#x2F; person的逻辑
const person = reactive&amp;lt;{ name: string; sex: string }&amp;gt;({
  name: &amp;quot;小明&amp;quot;,
  sex: &amp;quot;male&amp;quot;,
});
watch(
  () =&amp;gt; [person.name, person.sex],
  ([nameVal, sexVal]) =&amp;gt; {
    console.log(&amp;#96;名字被修改了, 修改为 ${nameVal}&amp;#96;);
    console.log(&amp;#96;名字被修改了, 修改为 ${sexVal}&amp;#96;);
  }
);
function changePersonName() {
  person.name = &amp;quot;小浪&amp;quot;;
}

&amp;#x2F;&amp;#x2F; car的逻辑
const car = reactive&amp;lt;{ name: string; price: string }&amp;gt;({
  name: &amp;quot;宝马&amp;quot;,
  price: &amp;quot;40w&amp;quot;,
});
function changeCarPrice() {
  car.price = &amp;quot;80w&amp;quot;;
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;采用 Vue3 自定义 Hook 的方式，进一步拆分&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;p&amp;gt;{{ person.name }}&amp;lt;&amp;#x2F;p&amp;gt;
  &amp;lt;p&amp;gt;{{ car.name }}&amp;lt;&amp;#x2F;p&amp;gt;
  &amp;lt;p&amp;gt;{{ animal.name }}&amp;lt;&amp;#x2F;p&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script lang=&amp;quot;ts&amp;quot; setup&amp;gt;
import { usePerson, useCar, useAnimal } from &amp;quot;.&amp;#x2F;hooks&amp;quot;;

const { person, changePersonName } = usePerson();

const { car } = useCar();
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-ts&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; usePerson.ts
import { reactive, watch } from &amp;quot;vue&amp;quot;;

export default function usePerson() {
  const person = reactive&amp;lt;{ name: string; sex: string }&amp;gt;({
    name: &amp;quot;小明&amp;quot;,
    sex: &amp;quot;male&amp;quot;,
  });
  watch(
    () =&amp;gt; [person.name, person.sex],
    ([nameVal, sexVal]) =&amp;gt; {
      console.log(&amp;#96;名字被修改了, 修改为 ${nameVal}&amp;#96;);
      console.log(&amp;#96;名字被修改了, 修改为 ${sexVal}&amp;#96;);
    }
  );
  function changePersonName() {
    person.name = &amp;quot;小浪&amp;quot;;
  }
  return {
    person,
    changePersonName,
  };
}
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-ts&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; useCar.ts
import { reactive } from &amp;quot;vue&amp;quot;;

export default function useCar() {
  const car = reactive&amp;lt;{ name: string; price: string }&amp;gt;({
    name: &amp;quot;宝马&amp;quot;,
    price: &amp;quot;40w&amp;quot;,
  });
  function changeCarPrice() {
    car.price = &amp;quot;80w&amp;quot;;
  }
  return {
    car,
    changeCarPrice,
  };
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对比完之后，我们会发现，Vue3 可以让我们更好组织代码。&lt;code&gt;person&lt;/code&gt;和&lt;code&gt;car&lt;/code&gt;的逻辑都被单独放置在一块&lt;/p&gt;
&lt;p&gt;仅仅是代码组织的优势吗？不不不，我们再看看模板中的一些变化&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;标签中起始便签可以不用&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;标签，因为&lt;code&gt;Vue3&lt;/code&gt;提供了&lt;code&gt;片段&lt;/code&gt;的能力，使用&lt;code&gt;Vue-detools&lt;/code&gt;中查看，可以看到有&lt;code&gt;fragment&lt;/code&gt;的标记&lt;br&gt;
&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e2d30c1c9db648bea38eeecb4c7d75f1~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Watch&lt;/code&gt;也不需要罗列多个了，&lt;code&gt;Vue3&lt;/code&gt;中支持侦听多个源&lt;/li&gt;
&lt;li&gt;很自然的使用&lt;code&gt;TypeScript&lt;/code&gt;，类型约束更加简单&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;性能方面&lt;/h3&gt;
&lt;p&gt;Vue3 主要在这几个方面进行了提升&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;编译阶段。对 diff 算法优化、静态提升等等&lt;/li&gt;
&lt;li&gt;响应式系统。&lt;code&gt;Proxy()&lt;/code&gt;替代&lt;code&gt;Object.defineProperty()&lt;/code&gt;监听对象。监听一个对象，不需要再深度遍历，&lt;code&gt;Proxy()&lt;/code&gt;就可以劫持整个对象&lt;/li&gt;
&lt;li&gt;体积包减少。Compostion API 的写法，可以更好的进行 tree shaking，减少上下文没有引入的代码，减少打包后的文件体积&lt;/li&gt;
&lt;li&gt;新增&lt;code&gt;片段&lt;/code&gt;特性。Vue 文件的&lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;标签内，不再需要强制声明一个的&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;标签，节省额外的节点开销&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt; 总结&lt;/h2&gt;
&lt;p&gt;Vue3 的优点和新特性还有很多很多，本篇文章主要从&lt;code&gt;Vue2/3版本的优缺点&lt;/code&gt;和&lt;code&gt;SFC写法变化&lt;/code&gt;这两个角度介入，给大家介绍我们为什么要上 Vue3。&lt;/p&gt;
&lt;p&gt;Vue3 很香，更好地支持 TypeScript，让 Vue3 成为了企业级 Vue 项目的首选，各种新特性也迎合前端技术的发展趋势。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ie 浏览器：那我走？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt; 最后&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;翻译翻译什么是 Vue3 ？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果这篇文章对你有帮助，那麻烦你动动小手点个赞 ，你的每一个点赞都是我继续创作的莫大鼓励！&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://vue3js.cn/interview/vue3/performance.html#%E4%B8%80%E3%80%81%E7%BC%96%E8%AF%91%E9%98%B6%E6%AE%B5&quot; title=&quot;面试官：Vue3.0性能提升主要是通过哪几方面体现的？&quot;&gt;面试官：Vue3.0 性能提升主要是通过哪几方面体现的？&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6903171037211557895&quot; title=&quot;Vue3教程：Vue 3.x快在哪里？&quot;&gt;Vue3 教程：Vue 3.x 快在哪里？&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;其他&lt;/h2&gt;
&lt;p&gt;掘金年度人气创作者打榜最后一天啦，欢迎大家为我们团队投票~ &lt;a href=&quot;https://rank.juejin.cn/rank/2021/3104676565234429?t=team&amp;amp;utm_campaign=annual_2021&amp;amp;utm_medium=self_web_share&amp;amp;utm_source=IDuxFE&quot;&gt;IDuxFE@深信服前端开发团队&lt;/a&gt;
感谢大家的支持~&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: IDuxFE
        原文链接：&lt;a href=&quot;https://juejin.im/post/7046922345864232974&quot;&gt;https://juejin.im/post/7046922345864232974&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Mon, 17 Jan 2022 10:15:00 GMT</pubDate></item><item><title>在 Vue 中为什么不推荐用 index 做 key__前端__JavaScript__Vue.js</title><link>http://vue-js.com/topic/61e53395cbbfd1003b11fa02</link><guid>http://vue-js.com/topic/61e53395cbbfd1003b11fa02</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d4eb6a01e6564840817b127265b07edd~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8ec9270b418647e4ab27f5ccbd983824~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;谱根.png&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这是第 120 篇不掺水的原创，想获取更多原创好文，请搜索公众号关注我们吧~ 本文首发于政采云前端博客：&lt;a href=&quot;https://zoo.team/article/vue-index&quot;&gt;在 Vue 中为什么不推荐用 index 做 key&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;前端开发中，只要涉及到列表渲染，那么无论是 React 还是 Vue 框架，都会提示或要求每个列表项使用唯一的 key，那很多开发者就会直接使用数组的 index 作为 key 的值，而并不知道 key 的原理。那么这篇文章就会讲解 key 的作用以及为什么最好不要使用 index 作为 key 的属性值。&lt;/p&gt;
&lt;h2&gt;key 的作用&lt;/h2&gt;
&lt;p&gt;Vue 中使用虚拟 dom 且根据 diff 算法进行新旧 DOM 对比，从而更新真实 dom ，key 是虚拟 DOM 对象的唯一标识, 在 diff 算法中 key 起着极其重要的作用。&lt;/p&gt;
&lt;h2&gt;key 在 diff 算法中的角色&lt;/h2&gt;
&lt;p&gt;其实在 React，Vue，中 diff 算法大致是差不多，但是 diff 比对方式还是有较大差异的，甚至每个版本 diff 都大有不同。下面我们就以 Vue3.0 diff 算法为切入点，剖析 key 在 diff 算法中的作用&lt;/p&gt;
&lt;p&gt;具体 diff 流程如下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0f3108cefc3e4974acc53b305c42f707~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;未命名表单 (1).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Vue3.0中 在 patchChildren 方法中有这么一段源码&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;if (patchFlag &amp;gt; 0) {
      if (patchFlag &amp;amp; PatchFlags.KEYED_FRAGMENT) { 
         &amp;#x2F;* 对于存在 key 的情况用于 diff 算法 *&amp;#x2F;
        patchKeyedChildren(
         ...
        )
        return
      } else if (patchFlag &amp;amp; PatchFlags.UNKEYED_FRAGMENT) {
         &amp;#x2F;* 对于不存在 key 的情况,直接 patch  *&amp;#x2F;
        patchUnkeyedChildren( 
          ...
        )
        return
      }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;patchChildren 根据是否存在 key 进行真正的 diff 或者直接 patch。对于 key 不存在的情况我们就不做深入研究了。&lt;/p&gt;
&lt;p&gt;我们先来看看一些声明的变量。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;*  c1 老的 vnode c2 新的vnode  *&amp;#x2F;
let i = 0              &amp;#x2F;* 记录索引 *&amp;#x2F;
const l2 = c2.length   &amp;#x2F;* 新 vnode的数量 *&amp;#x2F;
let e1 = c1.length - 1 &amp;#x2F;* 老 vnode 最后一个节点的索引 *&amp;#x2F;
let e2 = l2 - 1        &amp;#x2F;* 新节点最后一个节点的索引 *&amp;#x2F;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;同步头部节点&lt;/h3&gt;
&lt;p&gt;第一步的事情就是从头开始寻找相同的 vnode，然后进行 patch ,如果发现不是相同的节点，那么立即跳出循环。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;(a b) c
&amp;#x2F;&amp;#x2F;(a b) d e
&amp;#x2F;* 从头对比找到有相同的节点 patch ，发现不同，立即跳出*&amp;#x2F;
    while (i &amp;lt;= e1 &amp;amp;&amp;amp; i &amp;lt;= e2) {
      const n1 = c1[i]
      const n2 = (c2[i] = optimized
        ? cloneIfMounted(c2[i] as VNode)
        : normalizeVNode(c2[i]))
        &amp;#x2F;* 判断 key ，type 是否相等 *&amp;#x2F;
      if (isSameVNodeType(n1, n2)) {
        patch(
          ...
        )
      } else {
        break
      }
      i++
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;流程如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7c2e234667684ce0b19e085dc96add9f~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;isSameVNodeType 作用就是判断当前 vnode 类型 和 vnode 的 key 是否相等&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
  return n1.type === n2.type &amp;amp;&amp;amp; n1.key === n2.key
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其实看到这，是不是已经知道 key 在 diff 算法的作用，就是用来判断是否是同一个节点。&lt;/p&gt;
&lt;h3&gt;同步尾部节点&lt;/h3&gt;
&lt;p&gt;第二步从尾开始同前 diff&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;a (b c)
&amp;#x2F;&amp;#x2F;d e (b c)
&amp;#x2F;* 如果第一步没有 patch 完，立即，从后往前开始 patch  如果发现不同立即跳出循环 *&amp;#x2F;
    while (i &amp;lt;= e1 &amp;amp;&amp;amp; i &amp;lt;= e2) {
      const n1 = c1[e1]
      const n2 = (c2[e2] = optimized
        ? cloneIfMounted(c2[e2] as VNode)
        : normalizeVNode(c2[e2]))
      if (isSameVNodeType(n1, n2)) {
        patch(
         ...
        )
      } else {
        break
      }
      e1--
      e2--
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;经历第一步操作之后，如果发现没有 patch 完，那么立即进行第二步，从尾部开始遍历依次向前 diff。如果发现不是相同的节点，那么立即跳出循环。
流程如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/86855d1c542f4841993743a5a8a9cd49~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (1).png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;添加新的节点&lt;/h3&gt;
&lt;p&gt;第三步如果老节点是否全部 patch，新节点没有被 patch 完,创建新的 vnode&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;(a b)
&amp;#x2F;&amp;#x2F;(a b) c
&amp;#x2F;&amp;#x2F;i = 2, e1 = 1, e2 = 2
&amp;#x2F;&amp;#x2F;(a b)
&amp;#x2F;&amp;#x2F;c (a b)
&amp;#x2F;&amp;#x2F;i = 0, e1 = -1, e2 = 0
&amp;#x2F;* 如果新的节点大于老的节点数 ，对于剩下的节点全部以新的 vnode 处理（这种情况说明已经 patch 完相同的 vnode ） *&amp;#x2F;
    if (i &amp;gt; e1) {
      if (i &amp;lt;= e2) {
        const nextPos = e2 + 1
        const anchor = nextPos &amp;lt; l2 ? (c2[nextPos] as VNode).el : parentAnchor
        while (i &amp;lt;= e2) {
          patch( &amp;#x2F;* 创建新的节点*&amp;#x2F;
            ...
          )
          i++
        }
      }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;流程如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e054a7fc8aa8417ea463fd1d8a6ad5a1~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (2).png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;删除多余节点&lt;/h3&gt;
&lt;p&gt;第四步如果新节点全部被 patch，老节点有剩余，那么卸载所有老节点&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;i &amp;gt; e2
&amp;#x2F;&amp;#x2F;(a b) c
&amp;#x2F;&amp;#x2F;(a b)
&amp;#x2F;&amp;#x2F;i = 2, e1 = 2, e2 = 1
&amp;#x2F;&amp;#x2F;a (b c)
&amp;#x2F;&amp;#x2F;(b c)
&amp;#x2F;&amp;#x2F;i = 0, e1 = 0, e2 = -1
else if (i &amp;gt; e2) {
   while (i &amp;lt;= e1) {
      unmount(c1[i], parentComponent, parentSuspense, true)
      i++
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;流程如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/429c92305790409680f4b7fef4e1c510~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (3).png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;最长递增子序列&lt;/h3&gt;
&lt;p&gt;到了这一步，比较核心的场景还没有出现，如果运气好，可能到这里就结束了，那我们也不能全靠运气。剩下的一个场景是新老节点都还有多个子节点存在的情况。那接下来看看，Vue3 是怎么做的。为了结合 move、新增和卸载的操作&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/77805129d7384dc78922898b3b0e018d~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (4).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;每次在对元素进行移动的时候，我们可以发现一个规律，如果想要移动的次数最少，就意味着需要有一部分元素是稳定不动的，那么究竟能够保持稳定不动的元素有一些什么规律呢？&lt;/p&gt;
&lt;p&gt;可以看一下上面这个例子：c  h  d  e  VS  d  e  i  c，在比对的时候，凭着肉眼可以看出只需要将 c 进行移动到最后，然后卸载 h，新增 i 就好了。d  e 可以保持不动，可以发现 d  e 在新老节点中的顺序都是不变的，d 在 e 的后面，下标处于递增状态。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-plain&quot;&gt;&lt;code&gt;这里引入一个概念，叫最长递增子序列。
官方解释：在一个给定的数组中，找到一组递增的数值，并且长度尽可能的大。
有点比较难理解，那来看具体例子：

const arr = [10, 9, 2, 5, 3, 7, 101, 18]
=&amp;gt; [2, 3, 7, 18]
这一列数组就是arr的最长递增子序列，其实[2, 3, 7, 101]也是。
所以最长递增子序列符合三个要求：
1、子序列内的数值是递增的
2、子序列内数值的下标在原数组中是递增的
3、这个子序列是能够找到的最长的
但是我们一般会找到数值较小的那一组数列，因为他们可以增长的空间会更多。
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那接下来的思路是：如果能找到老节点在新节点序列中顺序不变的节点们，就知道，哪一些节点不需要移动，然后只需要把不在这里的节点插入进来就可以了。**因为最后要呈现出来的顺序是新节点的顺序，移动是只要老节点移动，所以只要老节点保持最长顺序不变，通过移动个别节点，就能够跟它保持一致。**所以在此之前，先把所有节点都找到，再找对应的序列。最后其实要得到的则是这一个数组：[2, 3, 新增 , 0]。其实这就是 diff 移动的思路了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/340a3dd032be4da685cf9dfa89f5b1c8~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (5).png&quot;&gt;&lt;/p&gt;
&lt;h1&gt;为什么不要用index&lt;/h1&gt;
&lt;h3&gt;性能消耗&lt;/h3&gt;
&lt;p&gt;使用 index 做 key，破坏顺序操作的时候， 因为每一个节点都找不到对应的 key，导致部分节点不能复用,所有的新 vnode 都需要重新创建。&lt;/p&gt;
&lt;p&gt;例子：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;hello&amp;quot;&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;li v-for=&amp;quot;(item,index) in studentList&amp;quot; :key=&amp;quot;index&amp;quot;&amp;gt;{{item.name}}&amp;lt;&amp;#x2F;li&amp;gt;
      &amp;lt;br&amp;gt;
      &amp;lt;button @click=&amp;quot;addStudent&amp;quot;&amp;gt;添加一条数据&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;&amp;#x2F;ul&amp;gt;

  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: &amp;#x27;HelloWorld&amp;#x27;,
  data() {
    return {
      studentList: [
        { id: 1, name: &amp;#x27;张三&amp;#x27;, age: 18 },
        { id: 2, name: &amp;#x27;李四&amp;#x27;, age: 19 },
      ],
    };
  },
  methods:{
    addStudent(){
      const studentObj = { id: 3, name: &amp;#x27;王五&amp;#x27;, age: 20 };
      this.studentList=[studentObj,...this.studentList]
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们先把 Chorme 调试器打开，我们双击把里面文本修改一下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ad4acbae89724e10bdfccdefbc8b3650~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (6).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们运行以上上面的代码，看下运行结果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e69a1c09aee425ab35b2a0dfbfaf952~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;chrome-capture (2).gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;从上面运行结果可以看出来，我们只是添加了一条数据，但是三条数据都需要重新渲染是不是很惊奇，我明明只是插入了一条数据，怎么三条数据都要重新渲染？而我想要的只是新增的那一条数据新渲染出来就行了。&lt;/p&gt;
&lt;p&gt;上面我们也讲过 diif 比较方式,下面根据 diff 比较绘制一张图，看看具体是怎么比较的吧&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f3346f6ba3c641ef8cf51ebd0505b7b9~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (7).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;当我们在前面加了一条数据时 index 顺序就会被打断，导致新节点 key 全部都改变了，所以导致我们页面上的数据都被重新渲染了。&lt;/p&gt;
&lt;p&gt;下面我们下面生成1000个 DOM 来比较一下采用 index ，和不采用 index 性能比较，为了保证 key 的唯一性我们采用 uuid 作为 key&lt;/p&gt;
&lt;p&gt;我们用 index 做为 key 现执行一遍&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;hello&amp;quot;&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;button @click=&amp;quot;addStudent&amp;quot;&amp;gt;添加一条数据&amp;lt;&amp;#x2F;button&amp;gt;
      &amp;lt;br&amp;gt;
      &amp;lt;li v-for=&amp;quot;(item,index) in studentList&amp;quot; :key=&amp;quot;index&amp;quot;&amp;gt;{{item.id}}&amp;lt;&amp;#x2F;li&amp;gt;
    &amp;lt;&amp;#x2F;ul&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import uuidv1 from &amp;#x27;uuid&amp;#x2F;v1&amp;#x27;
export default {
  name: &amp;#x27;HelloWorld&amp;#x27;,
  data() {
    return {
      studentList: [{id:uuidv1()}],
    };
  },
  created(){
    for (let i = 0; i &amp;lt; 1000; i++) {
      this.studentList.push({
        id: uuidv1(),
      });
    }
  },
  beforeUpdate(){
    console.time(&amp;#x27;for&amp;#x27;);
  },
  updated(){
    console.timeEnd(&amp;#x27;for&amp;#x27;)&amp;#x2F;&amp;#x2F;for: 75.259033203125 ms
  },
  methods:{
    addStudent(){
      const studentObj = { id: uuidv1() };
      this.studentList=[studentObj,...this.studentList]
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;换成 id 作为 key&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;hello&amp;quot;&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;button @click=&amp;quot;addStudent&amp;quot;&amp;gt;添加一条数据&amp;lt;&amp;#x2F;button&amp;gt;
      &amp;lt;br&amp;gt;
      &amp;lt;li v-for=&amp;quot;(item,index) in studentList&amp;quot; :key=&amp;quot;item.id&amp;quot;&amp;gt;{{item.id}}&amp;lt;&amp;#x2F;li&amp;gt;
    &amp;lt;&amp;#x2F;ul&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
  beforeUpdate(){
    console.time(&amp;#x27;for&amp;#x27;);
  },
  updated(){
    console.timeEnd(&amp;#x27;for&amp;#x27;)&amp;#x2F;&amp;#x2F;for: 42.200927734375 ms
  },
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从上面比较可以看出，用唯一值作为 key 可以节约开销&lt;/p&gt;
&lt;h3&gt;数据错位&lt;/h3&gt;
&lt;p&gt;上述例子可能觉得用 index 做 key 只是影响页面加载的效率，认为少量的数据影响不大，那面下面这种情况，可能用 index 就可能出现一些意想不到的问题了，还是上面的场景，这时我先再每个文本内容后面加一个 input 输入框，并且手动在输入框内填写一些内容，然后通过 button 向前追加一位同学看看&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;hello&amp;quot;&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;li v-for=&amp;quot;(item,index) in studentList&amp;quot; :key=&amp;quot;index&amp;quot;&amp;gt;{{item.name}}&amp;lt;input &amp;#x2F;&amp;gt;&amp;lt;&amp;#x2F;li&amp;gt;
      &amp;lt;br&amp;gt;
      &amp;lt;button @click=&amp;quot;addStudent&amp;quot;&amp;gt;添加一条数据&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;&amp;#x2F;ul&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: &amp;#x27;HelloWorld&amp;#x27;,
  data() {
    return {
      studentList: [
        { id: 1, name: &amp;#x27;张三&amp;#x27;, age: 18 },
        { id: 2, name: &amp;#x27;李四&amp;#x27;, age: 19 },
      ],
    };
  },
  methods:{
    addStudent(){
      const studentObj = { id: 3, name: &amp;#x27;王五&amp;#x27;, age: 20 };
      this.studentList=[studentObj,...this.studentList]
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们往 input 里面输入一些值，添加一位同学看下效果：
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6e9185cea3bb4b5db540848dca67c859~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;chrome-capture (3).gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;这时候我们就会发现，在添加之前输入的数据错位了。添加之后王五的输入框残留着张三的信息，这很显然不是我们想要的结果。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/36be485e696848709de8e7d77a4616da~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (8).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;从上面比对可以看出来这时因为采用 index 作为 key 时，当在比较时，发现虽然文本值变了，但是当继续向下比较时发现 &amp;lt;input /&amp;gt;  DOM 节点还是和原来一摸一样，就复用了，但是没想到 input 输入框残留输入的值，这时候就会出现输入的值出现错位的情况&lt;/p&gt;
&lt;h1&gt;解决方案&lt;/h1&gt;
&lt;p&gt;既然知道用 index 在某些情况下带来很不好的影响，那平时我们在开发当中怎么去解决这种情况呢？其实只要保证 key 唯一不变就行，一般在开发中用的比较多就是下面三种情况。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在开发中最好每条数据使用唯一标识固定的数据作为 key，比如后台返回的 ID，手机号，身份证号等唯一值&lt;/li&gt;
&lt;li&gt;可以采用 Symbol 作为 key，Symbol 是 ES6 引入了一种新的原始数据类型 Symbol ，表示独一无二的值，最大的用法是用来定义对象的唯一属性名。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language-plain&quot;&gt;&lt;code&gt;let a=Symbol(&amp;#x27;测试&amp;#x27;)
let b=Symbol(&amp;#x27;测试&amp;#x27;)
console.log(a===b)&amp;#x2F;&amp;#x2F;false
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;可以采用 uuid 作为 key ，uuid 是 Universally Unique Identifier 的缩写，它是在一定的范围内（从特定的名字空间到全球）唯一的机器生成的标识符&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们采用上面第一种方案作为 key 在看一下上面情况，如图所示。key 相同的节点都做到了复用。起到了diff 算法的真正作用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5b1c1738c0284796b9d0f34e3f36d029~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;chrome-capture (4).gif&quot;&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/81fa5ab929644d9bb3fd5dcf7649446c~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;chrome-capture (5).gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3e9bcc175af14a4ab09ab800517293b8~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (9).png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;用 index 作为 key 时，在对数据进行，逆序添加，逆序删除等破坏顺序的操作时，会产生没必要的真实 DOM更新，从而导致效率低&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用 index 作为 key 时，如果结构中包含输入类的 DOM，会产生错误的 DOM 更新&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在开发中最好每条数据使用唯一标识固定的数据作为 key，比如后台返回的 ID，手机号，身份证号等唯一值&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如果不存在对数据逆序添加，逆序删除等破坏顺序的操作时，仅用于渲染展示用时，使用 index 作为 key 也是可以的（但是还是不建议使用，养成良好开发习惯）。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考链接&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/zl_Alien/article/details/106595459&quot;&gt;vue3.0 diff算法详解(超详细)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://segmentfault.com/a/1190000038654183&quot;&gt;Vue 3 Virtual Dom Diff源码阅读&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;推荐阅读&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/7002746459456176158&quot;&gt;电商最小存货 - SKU 和 算法实现&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6997536906967777316&quot;&gt;你需要知道的项目管理知识&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/7013131773756309517&quot;&gt;浏览器渲染之回流重绘&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/7018296556323340324&quot;&gt;防抖节流场景及应用&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;开源作品&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;政采云前端小报&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;开源地址 &lt;a href=&quot;https://www.zoo.team/openweekly/&quot;&gt;www.zoo.team/openweekly/&lt;/a&gt;&lt;/strong&gt; (小报官网首页有微信交流群)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;skuDemo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;开源地址 &lt;a href=&quot;https://github.com/zcy-inc/skuPathFinder-back&quot;&gt;https://github.com/zcy-inc/skuPathFinder-back/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;招贤纳士&lt;/h2&gt;
&lt;p&gt;政采云前端团队（ZooTeam），一个年轻富有激情和创造力的前端团队，隶属于政采云产品研发部，Base 在风景如画的杭州。团队现有 50 余个前端小伙伴，平均年龄 27 岁，近 3 成是全栈工程师，妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵，也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外，还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战，推动并落地了一系列的内部技术产品，持续探索前端技术体系的新边界。&lt;/p&gt;
&lt;p&gt;如果你想改变一直被事折腾，希望开始能折腾事；如果你想改变一直被告诫需要多些想法，却无从破局；如果你想改变你有能力去做成那个结果，却不需要你；如果你想改变你想做成的事需要一个团队去支撑，但没你带人的位置；如果你想改变既定的节奏，将会是“5 年工作时间 3 年工作经验”；如果你想改变本来悟性不错，但总是有那一层窗户纸的模糊… 如果你相信相信的力量，相信平凡人能成就非凡事，相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程，亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程，我觉得我们该聊聊。任何时间，等着你写点什么，发给 &lt;code&gt;ZooTeam@cai-inc.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/98d3aa3d1f8646a8bcda8cfd9e335a4b~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 政采云前端团队
        原文链接：&lt;a href=&quot;https://juejin.im/post/7026119446162997261&quot;&gt;https://juejin.im/post/7026119446162997261&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Mon, 17 Jan 2022 09:15:01 GMT</pubDate></item><item><title>你会用ES6，那倒是用啊！__前端__Vue.js__React.js</title><link>http://vue-js.com/topic/61e52585cbbfd1003b11fa01</link><guid>http://vue-js.com/topic/61e52585cbbfd1003b11fa01</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: fancy
highlight: arta&lt;/h2&gt;
&lt;p&gt;本文已参与「&lt;a href=&quot;https://juejin.cn/post/7012210233804079141/&quot; title=&quot;https://juejin.cn/post/7012210233804079141/&quot;&gt;掘力星计划&lt;/a&gt;」，赢取创作大礼包，挑战创作激励金。&lt;/p&gt;
&lt;p&gt;不是标题党，这是一位leader在一次代码评审会对小组成员发出的“怒吼”，原因是在代码评审中发现很多地方还是采用ES5的写法，也不是说用ES5写法不行，会有BUG，只是造成代码量增多，可读性变差而已。&lt;/p&gt;
&lt;p&gt;恰好，这位leader有代码洁癖，面对3~5年经验的成员，还写这种水平的代码，极为不满，不断对代码进行吐槽。不过对于他的吐槽，我感觉还是有很大收获的，故就把leader的吐槽记录下来，分享给掘友们，觉得有收获点个赞，有错误的或者更好的写法，非常欢迎在评论中留言。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ps：ES5之后的JS语法统称ES6！！！&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;一、关于取值的吐槽&lt;/h2&gt;
&lt;p&gt;取值在程序中非常常见，比如从对象&lt;code&gt;obj&lt;/code&gt;中取值。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const obj = {
    a:1,
    b:2,
    c:3,
    d:4,
    e:5,
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = obj.a;
const b = obj.b;
const c = obj.c;
const d = obj.d;
const e = obj.e;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;或者&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const f = obj.a + obj.d;
const g = obj.c + obj.e;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;吐槽：“不会用ES6的解构赋值来取值吗？5行代码用1行代码搞定不香吗？直接用对象名加属性名去取值，要是对象名短还好，很长呢？搞得代码中到处都是这个对象名。”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const {a,b,c,d,e} = obj;
const f = a + d;
const g = c + e;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;反驳&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不是不用ES6的解构赋值，而是服务端返回的数据对象中的属性名不是我想要的，这样取值，不是还得重新创建个遍历赋值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;看来你对ES6的解构赋值掌握的还是不够彻底。如果想创建的变量名和对象的属性名不一致，可以这么写：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const {a:a1} = obj;
console.log(a1);&amp;#x2F;&amp;#x2F; 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;补充&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6的解构赋值虽然好用。但是要注意解构的对象不能为&lt;code&gt;undefined&lt;/code&gt;、&lt;code&gt;null&lt;/code&gt;。否则会报错，故要给被解构的对象一个默认值。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const {a,b,c,d,e} = obj || {};
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;二、关于合并数据的吐槽&lt;/h2&gt;
&lt;p&gt;比如合并两个数组，合并两个对象。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = [1,2,3];
const b = [1,5,6];
const c = a.concat(b);&amp;#x2F;&amp;#x2F;[1,2,3,1,5,6]

const obj1 = {
  a:1,
}
const obj2 = {
  b:1,
}
const obj = Object.assign({}, obj1, obj2);&amp;#x2F;&amp;#x2F;{a:1,b:1}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6的扩展运算符是不是忘记了，还有数组的合并不考虑去重吗？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = [1,2,3];
const b = [1,5,6];
const c = [...new Set([...a,...b])];&amp;#x2F;&amp;#x2F;[1,2,3,5,6]

const obj1 = {
  a:1,
}
const obj2 = {
  b:1,
}
const obj = {...obj1,...obj2};&amp;#x2F;&amp;#x2F;{a:1,b:1}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;三、关于拼接字符串的吐槽&lt;/h2&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const name = &amp;#x27;小明&amp;#x27;;
const score = 59;
let result = &amp;#x27;&amp;#x27;;
if(score &amp;gt; 60){
  result = &amp;#96;${name}的考试成绩及格&amp;#96;; 
}else{
  result = &amp;#96;${name}的考试成绩不及格&amp;#96;; 
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;像你们这样用ES6字符串模板，还不如不用，你们根本不清楚在&lt;code&gt;${}&lt;/code&gt;中可以做什么操作。在&lt;code&gt;${}&lt;/code&gt;中可以放入任意的JavaScript表达式，可以进行运算，以及引用对象属性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const name = &amp;#x27;小明&amp;#x27;;
const score = 59;
const result = &amp;#96;${name}${score &amp;gt; 60?&amp;#x27;的考试成绩及格&amp;#x27;:&amp;#x27;的考试成绩不及格&amp;#x27;}&amp;#96;;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;四、关于if中判断条件的吐槽&lt;/h2&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;if(
    type == 1 ||
    type == 2 ||
    type == 3 ||
    type == 4 ||
){
   &amp;#x2F;&amp;#x2F;...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6中数组实例方法&lt;code&gt;includes&lt;/code&gt;会不会使用呢？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const condition = [1,2,3,4];

if( condition.includes(type) ){
   &amp;#x2F;&amp;#x2F;...
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;五、关于列表搜索的吐槽&lt;/h2&gt;
&lt;p&gt;在项目中，一些没分页的列表的搜索功能由前端来实现，搜索一般分为精确搜索和模糊搜索。搜索也要叫过滤，一般用&lt;code&gt;filter&lt;/code&gt;来实现。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = [1,2,3,4,5];
const result = a.filter( 
  item =&amp;gt;{
    return item === 3
  }
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果是精确搜索不会用ES6中的&lt;code&gt;find&lt;/code&gt;吗？性能优化懂么，&lt;code&gt;find&lt;/code&gt;方法中找到符合条件的项，就不会继续遍历数组。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = [1,2,3,4,5];
const result = a.find( 
  item =&amp;gt;{
    return item === 3
  }
)
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;六、关于扁平化数组的吐槽&lt;/h2&gt;
&lt;p&gt;一个部门JSON数据中，属性名是部门id，属性值是个部门成员id数组集合，现在要把有部门的成员id都提取到一个数组集合中。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const deps = {
&amp;#x27;采购部&amp;#x27;:[1,2,3],
&amp;#x27;人事部&amp;#x27;:[5,8,12],
&amp;#x27;行政部&amp;#x27;:[5,14,79],
&amp;#x27;运输部&amp;#x27;:[3,64,105],
}
let member = [];
for (let item in deps){
    const value = deps[item];
    if(Array.isArray(value)){
        member = [...member,...value]
    }
}
member = [...new Set(member)]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;获取对象的全部属性值还要遍历吗？&lt;code&gt;Object.values&lt;/code&gt;忘记了吗？还有涉及到数组的扁平化处理，为啥不用ES6提供的&lt;code&gt;flat&lt;/code&gt;方法呢，还好这次的数组的深度最多只到2维，还要是遇到4维、5维深度的数组，是不是得循环嵌套循环来扁平化？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const deps = {
    &amp;#x27;采购部&amp;#x27;:[1,2,3],
    &amp;#x27;人事部&amp;#x27;:[5,8,12],
    &amp;#x27;行政部&amp;#x27;:[5,14,79],
    &amp;#x27;运输部&amp;#x27;:[3,64,105],
}
let member = Object.values(deps).flat(Infinity);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中使用&lt;code&gt;Infinity&lt;/code&gt;作为&lt;code&gt;flat&lt;/code&gt;的参数，使得无需知道被扁平化的数组的维度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;补充&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flat&lt;/code&gt;方法不支持IE浏览器。&lt;/p&gt;
&lt;h2&gt;七、关于获取对象属性值的吐槽&lt;/h2&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const name = obj &amp;amp;&amp;amp; obj.name;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6中的可选链操作符会使用么？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const name = obj?.name;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;八、关于添加对象属性的吐槽&lt;/h2&gt;
&lt;p&gt;当给对象添加属性时，如果属性名是动态变化的，该怎么处理。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;let obj = {};
let index = 1;
let key = &amp;#96;topic${index}&amp;#96;;
obj[key] = &amp;#x27;话题内容&amp;#x27;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为何要额外创建一个变量。不知道ES6中的对象属性名是可以用表达式吗？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;let obj = {};
let index = 1;
obj[&amp;#96;topic${index}&amp;#96;] = &amp;#x27;话题内容&amp;#x27;;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;九、关于输入框非空的判断&lt;/h2&gt;
&lt;p&gt;在处理输入框相关业务时，往往会判断输入框未输入值的场景。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;if(value !== null &amp;amp;&amp;amp; value !== undefined &amp;amp;&amp;amp; value !== &amp;#x27;&amp;#x27;){
    &amp;#x2F;&amp;#x2F;...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6中新出的空值合并运算符了解过吗，要写那么多条件吗？&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;if((value??&amp;#x27;&amp;#x27;) !== &amp;#x27;&amp;#x27;){
  &amp;#x2F;&amp;#x2F;...
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;十、关于异步函数的吐槽&lt;/h2&gt;
&lt;p&gt;异步函数很常见，经常是用 Promise 来实现。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const fn1 = () =&amp;gt;{
  return new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve(1);
    }, 300);
  });
}
const fn2 = () =&amp;gt;{
  return new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve(2);
    }, 600);
  });
}
const fn = () =&amp;gt;{
   fn1().then(res1 =&amp;gt;{
      console.log(res1);&amp;#x2F;&amp;#x2F; 1
      fn2().then(res2 =&amp;gt;{
        console.log(res2)
      })
   })
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果这样调用异步函数，不怕形成地狱回调啊！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const fn = async () =&amp;gt;{
  const res1 = await fn1();
  const res2 = await fn2();
  console.log(res1);&amp;#x2F;&amp;#x2F; 1
  console.log(res2);&amp;#x2F;&amp;#x2F; 2
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;补充&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但是要做并发请求时，还是要用到&lt;code&gt;Promise.all()&lt;/code&gt;。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const fn = () =&amp;gt;{
   Promise.all([fn1(),fn2()]).then(res =&amp;gt;{
       console.log(res);&amp;#x2F;&amp;#x2F; [1,2]
   }) 
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果并发请求时，只要其中一个异步函数处理完成，就返回结果，要用到&lt;code&gt;Promise.race()&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;十一、后续&lt;/h2&gt;
&lt;p&gt;欢迎来对以上十点leader的吐槽进行反驳，你的反驳如果有道理的，下次代码评审会上，我替你反驳。&lt;/p&gt;
&lt;p&gt;此外以上的整理内容有误的地方，欢迎在评论中指正，万分感谢。&lt;/p&gt;
&lt;p&gt;如果你还有其它想吐槽的，也非常欢迎在评论中留下你的吐槽。&lt;/p&gt;
&lt;p&gt;读完有帮助，不妨关注一下，点个赞支持一下。&lt;p&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
作者: 红尘炼心
原文链接：&lt;a href=&quot;https://juejin.im/post/7016520448204603423&quot;&gt;https://juejin.im/post/7016520448204603423&lt;/a&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>banyungong</author><pubDate>Mon, 17 Jan 2022 08:15:01 GMT</pubDate></item><item><title>「自我检验」熬夜总结50个Vue知识点，全都会你就是神！！！__前端__面试__Vue.js</title><link>http://vue-js.com/topic/61e519d4cbbfd1003b11f9fd</link><guid>http://vue-js.com/topic/61e519d4cbbfd1003b11f9fd</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: smartblue&lt;/h2&gt;
&lt;p&gt;「本文已参与好文召集令活动，点击查看：&lt;a href=&quot;https://juejin.cn/post/6978685539985653767&quot; title=&quot;https://juejin.cn/post/6978685539985653767&quot;&gt;后端、大前端双赛道投稿，2万元奖池等你挑战！&lt;/a&gt;」&lt;/p&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;大家好，我是林三心，这几天发生了很多事（具体是啥我就不说了），这些事，吓得我把我这些年珍藏的&lt;code&gt;Vue知识点&lt;/code&gt;都拿出来整理了一遍，巴不得能多总结出一道是一道，我拿出了我的笔记，并且使劲回忆，终于悟出了这50道&lt;code&gt;知识点&lt;/code&gt;（咱不要太俗哈，不叫面试题，咱叫知识点）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fc16e535f5384256bbb1145aa235416f~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;荣耀黄金&lt;/h2&gt;
&lt;h3&gt;1. Vue的优点？Vue的缺点？&lt;/h3&gt;
&lt;p&gt;优点：渐进式，组件化，轻量级，虚拟dom，响应式，单页面路由，数据与视图分开&lt;/p&gt;
&lt;p&gt;缺点：单页面不利于seo，不支持IE8以下，首屏加载时间长&lt;/p&gt;
&lt;h3&gt;2. 为什么说Vue是一个渐进式框架？&lt;/h3&gt;
&lt;p&gt;渐进式：通俗点讲就是，你想用啥你就用啥，咱也不强求你。你想用component就用，不用也行，你想用vuex就用，不用也可以&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/005b7f7d279f405686b354f00a01383a~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;3. Vue跟React的异同点？&lt;/h3&gt;
&lt;p&gt;相同点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.都使用了虚拟dom&lt;/li&gt;
&lt;li&gt;2.组件化开发&lt;/li&gt;
&lt;li&gt;3.都是单向数据流(父子组件之间，不建议子修改父传下来的数据)&lt;/li&gt;
&lt;li&gt;4.都支持服务端渲染
不同点：&lt;/li&gt;
&lt;li&gt;1.React的JSX，Vue的template&lt;/li&gt;
&lt;li&gt;2.数据变化，React手动(setState)，Vue自动(初始化已响应式处理，Object.defineProperty)&lt;/li&gt;
&lt;li&gt;3.React单向绑定，Vue双向绑定&lt;/li&gt;
&lt;li&gt;4.React的Redux，Vue的Vuex&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. MVVM是什么？和MVC有何区别呢？&lt;/h3&gt;
&lt;p&gt;MVC&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Model(模型)：负责从数据库中取数据&lt;/li&gt;
&lt;li&gt;View(视图)：负责展示数据的地方&lt;/li&gt;
&lt;li&gt;Controller(控制器)：用户交互的地方，例如点击事件等等&lt;/li&gt;
&lt;li&gt;思想：Controller将Model的数据展示在View上
&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4636ebbfa25049179c27a6b5ab8bb308~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MVVM&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VM：也就是View-Model，做了两件事达到了数据的双向绑定 一是将【模型】转化成【视图】，即将后端传递的数据转化成所看到的页面。实现的方式是：数据绑定。二是将【视图】转化成【模型】，即将所看到的页面转化成后端的数据。实现的方式是：DOM 事件监听。&lt;/li&gt;
&lt;li&gt;思想：实现了 View 和 Model 的自动同步，也就是当 Model 的属性改变时，我们不用再自己手动操作 Dom 元素，来改变 View 的显示，而是改变属性后该属性对应 View 层显示会自动改变（对应Vue数据驱动的思想）
&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aac31b27392b4b0e90ca2f67c64c59c2~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;区别&lt;/p&gt;
&lt;p&gt;整体看来，MVVM 比 MVC 精简很多，不仅简化了业务与界面的依赖，还解决了数据频繁更新的问题，不用再用选择器操作 DOM 元素。因为在 MVVM 中，View 不知道 Model 的存在，Model 和 ViewModel 也观察不到 View，这种低耦合模式提高代码的可重用性&lt;/p&gt;
&lt;p&gt;Vue是不是MVVM框架？&lt;/p&gt;
&lt;p&gt;Vue是MVVM框架，但是不是严格符合MVVM，因为MVVM规定Model和View不能直接通信，而Vue的&lt;code&gt;ref&lt;/code&gt;可以做到这点&lt;/p&gt;
&lt;h3&gt;5. Vue和JQuery的区别在哪？为什么放弃JQuery用Vue？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.jQuery是直接操作DOM，Vue不直接操作DOM，Vue的数据与视图是分开的，Vue只需要操作数据即可&lt;/li&gt;
&lt;li&gt;2.在操作DOM频繁的场景里，jQuery的操作DOM行为是频繁的，而Vue利用虚拟DOM的技术，大大提高了更新DOM时的性能&lt;/li&gt;
&lt;li&gt;3.Vue中不倡导直接操作DOM，开发者只需要把大部分精力放在数据层面上&lt;/li&gt;
&lt;li&gt;4.Vue集成的一些库，大大提高开发效率，比如Vuex，Router等&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6. Vue的作者是谁？大声说出它的名字！！！&lt;/h3&gt;
&lt;p&gt;他的名字就是：鱿鱼西&lt;/p&gt;
&lt;h2&gt;永恒钻石&lt;/h2&gt;
&lt;h3&gt;7. 为什么data是个函数并且返回一个对象呢？&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;data&lt;/code&gt;之所以是一个函数，是因为一个组件可能会多处调用，而每一次调用就会执行&lt;code&gt;data函数&lt;/code&gt;并返回新的数据对象，这样，可以避免多处调用之间的&lt;code&gt;数据污染&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;8. 使用过哪些Vue的修饰符呢？&lt;/h3&gt;
&lt;p&gt;可以看我这篇文章&lt;a href=&quot;https://juejin.cn/post/6981628129089421326&quot;&gt;「百毒不侵」面试官最喜欢问的13种Vue修饰符&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5a1c911988f74cea91da79af3c6049c2~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;截屏2021-07-11 下午9.56.53.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;9. 使用过哪些Vue的内部指令呢？&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d39d348e686b449e8931f5a85802e3c6~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;10. 组件之间的传值方式有哪些？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;父组件传值给子组件，子组件使用&lt;code&gt;props&lt;/code&gt;进行接收&lt;/li&gt;
&lt;li&gt;子组件传值给父组件，子组件使用&lt;code&gt;$emit+事件&lt;/code&gt;对父组件进行传值&lt;/li&gt;
&lt;li&gt;组件中可以使用&lt;code&gt;$parent&lt;/code&gt;和&lt;code&gt;$children&lt;/code&gt;获取到父组件实例和子组件实例，进而获取数据&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;$attrs&lt;/code&gt;和&lt;code&gt;$listeners&lt;/code&gt;，在对一些组件进行二次封装时可以方便传值，例如A-&amp;gt;B-&amp;gt;C&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;$refs&lt;/code&gt;获取组件实例，进而获取数据&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;Vuex&lt;/code&gt;进行状态管理&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;eventBus&lt;/code&gt;进行跨组件触发事件，进而传递数据&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;provide&lt;/code&gt;和&lt;code&gt;inject&lt;/code&gt;，官方建议我们不要用这个，我在看&lt;code&gt;ElementUI&lt;/code&gt;源码时发现大量使用&lt;/li&gt;
&lt;li&gt;使用浏览器本地缓存，例如&lt;code&gt;localStorage&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;11. 路由有哪些模式呢？又有什么不同呢？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;hash模式：通过&lt;code&gt;#号&lt;/code&gt;后面的内容的更改，触发&lt;code&gt;hashchange&lt;/code&gt;事件，实现路由切换&lt;/li&gt;
&lt;li&gt;history模式：通过&lt;code&gt;pushState&lt;/code&gt;和&lt;code&gt;replaceState&lt;/code&gt;切换url，触发&lt;code&gt;popstate&lt;/code&gt;事件，实现路由切换，需要后端配合&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;12. 如何设置动态class，动态style？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;动态class对象：&lt;code&gt;&amp;lt;div :class=&amp;quot;{ &apos;is-active&apos;: true, &apos;red&apos;: isRed }&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;动态class数组：&lt;code&gt;&amp;lt;div :class=&amp;quot;[&apos;is-active&apos;, isRed ? &apos;red&apos; : &apos;&apos; ]&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;动态style对象：&lt;code&gt;&amp;lt;div :style=&amp;quot;{ color: textColor, fontSize: &apos;18px&apos; }&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;动态style数组：&lt;code&gt;&amp;lt;div :style=&amp;quot;[{ color: textColor, fontSize: &apos;18px&apos; }, { fontWeight: &apos;300&apos; }]&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;13. v-if和v-show有何区别？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.&lt;code&gt;v-if&lt;/code&gt;是通过控制dom元素的删除和生成来实现显隐，每一次显隐都会使组件重新跑一遍生命周期，因为显隐决定了组件的生成和销毁&lt;/li&gt;
&lt;li&gt;2.&lt;code&gt;v-show&lt;/code&gt;是通过控制dom元素的css样式来实现显隐，不会销毁&lt;/li&gt;
&lt;li&gt;3.频繁或者大数量显隐使用&lt;code&gt;v-show&lt;/code&gt;，否则使用&lt;code&gt;v-if&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;14. computed和watch有何区别？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.&lt;code&gt;computed&lt;/code&gt;是依赖已有的变量来计算一个目标变量，大多数情况都是&lt;code&gt;多个变量&lt;/code&gt;凑在一起计算出&lt;code&gt;一个变量&lt;/code&gt;，并且&lt;code&gt;computed&lt;/code&gt;具有&lt;code&gt;缓存机制&lt;/code&gt;，依赖值不变的情况下其会直接读取缓存进行复用，&lt;code&gt;computed&lt;/code&gt;不能进行&lt;code&gt;异步操作&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;2.&lt;code&gt;watch&lt;/code&gt;是监听某一个变量的变化，并执行相应的回调函数，通常是&lt;code&gt;一个变量&lt;/code&gt;的变化决定&lt;code&gt;多个变量&lt;/code&gt;的变化，&lt;code&gt;watch&lt;/code&gt;可以进行&lt;code&gt;异步操作&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;3.简单记就是：一般情况下&lt;code&gt;computed&lt;/code&gt;是&lt;code&gt;多对一&lt;/code&gt;，&lt;code&gt;watch&lt;/code&gt;是&lt;code&gt;一对多&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;15. Vue的生命周期，讲一讲？&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/892fde0e56324868921d0e924c84858a~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;16. 为什么v-if和v-for不建议用在同一标签？&lt;/h3&gt;
&lt;p&gt;在Vue2中，&lt;code&gt;v-for&lt;/code&gt;优先级是高于&lt;code&gt;v-if&lt;/code&gt;的，咱们来看例子&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;div v-for=&amp;quot;item in [1, 2, 3, 4, 5, 6, 7]&amp;quot; v-if=&amp;quot;item !== 3&amp;quot;&amp;gt;
    {{item}}
&amp;lt;&amp;#x2F;div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;上面的写法是&lt;code&gt;v-for&lt;/code&gt;和&lt;code&gt;v-if&lt;/code&gt;同时存在，会先把7个元素都遍历出来，然后再一个个判断是否为3，并把3给隐藏掉，这样的坏处就是，渲染了无用的3节点，增加无用的dom操作，建议使用computed来解决这个问题：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;div v-for=&amp;quot;item in list&amp;quot;&amp;gt;
    {{item}}
&amp;lt;&amp;#x2F;div&amp;gt;

computed() {
    list() {
        return [1, 2, 3, 4, 5, 6, 7].filter(item =&amp;gt; item !== 3)
    }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;17. vuex的有哪些属性？用处是什么？&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9566927e955c4d0ba19df942534e5b53~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;State：定义了应用状态的数据结构，可以在这里设置默认的初始状态。&lt;/li&gt;
&lt;li&gt;Getter：允许组件从 Store 中获取数据，mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。&lt;/li&gt;
&lt;li&gt;Mutation：是唯一更改 store 中状态的方法，且必须是同步函数。&lt;/li&gt;
&lt;li&gt;Action：用于提交 mutation，而不是直接变更状态，可以包含任意异步操作。&lt;/li&gt;
&lt;li&gt;Module：允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;至尊星耀&lt;/h2&gt;
&lt;h3&gt;18. 不需要响应式的数据应该怎么处理？&lt;/h3&gt;
&lt;p&gt;在我们的Vue开发中，会有一些数据，从始至终都&lt;code&gt;未曾改变过&lt;/code&gt;，这种&lt;code&gt;死数据&lt;/code&gt;，既然&lt;code&gt;不改变&lt;/code&gt;，那也就&lt;code&gt;不需要对他做响应式处理&lt;/code&gt;了，不然只会做一些无用功消耗性能，比如一些写死的下拉框，写死的表格数据，这些数据量大的&lt;code&gt;死数据&lt;/code&gt;，如果都进行响应式处理，那会消耗大量性能。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 方法一：将数据定义在data之外
data () {
    this.list1 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list2 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list3 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list4 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list5 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    return {}
 }
    
&amp;#x2F;&amp;#x2F; 方法二：Object.freeze()
data () {
    return {
        list1: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list2: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list3: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list4: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list5: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
    }
 }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;19. watch有哪些属性，分别有什么用？&lt;/h3&gt;
&lt;p&gt;当我们监听一个基本数据类型时：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;watch: {
    value () {
        &amp;#x2F;&amp;#x2F; do something
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当我们监听一个引用数据类型时：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;watch: {
    obj: {
       handler () { &amp;#x2F;&amp;#x2F; 执行回调
           &amp;#x2F;&amp;#x2F; do something
       },
       deep: true, &amp;#x2F;&amp;#x2F; 是否进行深度监听
       immediate: true &amp;#x2F;&amp;#x2F; 是否初始执行handler函数
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;20. 父子组件生命周期顺序&lt;/h3&gt;
&lt;p&gt;父beforeCreate -&amp;gt; 父created -&amp;gt; 父beforeMount -&amp;gt; 子beforeCreate -&amp;gt; 子created -&amp;gt; 子beforeMount -&amp;gt; 子mounted -&amp;gt; 父mounted&lt;/p&gt;
&lt;h3&gt;21. 对象新属性无法更新视图，删除属性无法更新视图，为什么？怎么办？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;原因：&lt;code&gt;Object.defineProperty&lt;/code&gt;没有对对象的新属性进行属性劫持&lt;/li&gt;
&lt;li&gt;对象新属性无法更新视图：使用&lt;code&gt;Vue.$set(obj, key, value)&lt;/code&gt;，组件中&lt;code&gt;this.$set(obj, key, value)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;删除属性无法更新视图：使用&lt;code&gt;Vue.$delete(obj, key)&lt;/code&gt;，组件中&lt;code&gt;this.$delete(obj, key)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;22. 直接arr[index] = xxx无法更新视图怎么办？为什么？怎么办？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;原因：Vue没有对数组进行&lt;code&gt;Object.defineProperty&lt;/code&gt;的属性劫持，所以直接arr[index] = xxx是无法更新视图的&lt;/li&gt;
&lt;li&gt;使用数组的splice方法，&lt;code&gt;arr.splice(index, 1, item)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;Vue.$set(arr, index, value)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;23. 自定义指令&lt;/h3&gt;
&lt;p&gt;建议看这篇文章&lt;a href=&quot;https://www.cnblogs.com/lzq035/p/14183553.html&quot;&gt;8个非常实用的Vue自定义指令&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;24. 插槽的使用以及原理？&lt;/h3&gt;
&lt;p&gt;建议看我这篇文章&lt;a href=&quot;https://juejin.cn/post/6949848530781470733&quot;&gt;「Vue源码学习」你真的知道插槽Slot是怎么“插”的吗&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;25. 为什么不建议用index做key，为什么不建议用随机数做key？&lt;/h3&gt;
&lt;p&gt;举个例子：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;div v-for=&amp;quot;(item, index) in list&amp;quot; :key=&amp;quot;index&amp;quot;&amp;gt;{{item.name}}&amp;lt;&amp;#x2F;div&amp;gt;

list: [
    { name: &amp;#x27;小明&amp;#x27;, id: &amp;#x27;123&amp;#x27; },
    { name: &amp;#x27;小红&amp;#x27;, id: &amp;#x27;124&amp;#x27; },
    { name: &amp;#x27;小花&amp;#x27;, id: &amp;#x27;125&amp;#x27; }
]

渲染为
&amp;lt;div key=&amp;quot;0&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;1&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;2&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;

现在我执行 list.unshift({ name: &amp;#x27;小林&amp;#x27;, id: &amp;#x27;122&amp;#x27; })

渲染为
&amp;lt;div key=&amp;quot;0&amp;quot;&amp;gt;小林&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;1&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;2&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;3&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;


新旧对比

&amp;lt;div key=&amp;quot;0&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;  &amp;lt;div key=&amp;quot;0&amp;quot;&amp;gt;小林&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;1&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;  &amp;lt;div key=&amp;quot;1&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;2&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;  &amp;lt;div key=&amp;quot;2&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;
                         &amp;lt;div key=&amp;quot;3&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;

可以看出，如果用index做key的话，其实是更新了原有的三项，并新增了小花，虽然达到了渲染目的，但是损耗性能

现在我们使用id来做key，渲染为

&amp;lt;div key=&amp;quot;123&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;124&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;125&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;

现在我执行 list.unshift({ name: &amp;#x27;小林&amp;#x27;, id: &amp;#x27;122&amp;#x27; })，渲染为

&amp;lt;div key=&amp;quot;122&amp;quot;&amp;gt;小林&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;123&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;124&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;125&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;

新旧对比

                           &amp;lt;div key=&amp;quot;122&amp;quot;&amp;gt;小林&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;123&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;  &amp;lt;div key=&amp;quot;123&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;124&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;  &amp;lt;div key=&amp;quot;124&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;125&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;  &amp;lt;div key=&amp;quot;125&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;

可以看出，原有的三项都不变，只是新增了小林这个人，这才是最理想的结果
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;用&lt;code&gt;index&lt;/code&gt;和用&lt;code&gt;随机数&lt;/code&gt;都是同理，&lt;code&gt;随机数&lt;/code&gt;每次都在变，做不到专一性，很&lt;code&gt;渣男&lt;/code&gt;，也很消耗性能，所以，拒绝&lt;code&gt;渣男&lt;/code&gt;，选择&lt;code&gt;老实人&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;26. 说说nextTick的用处？&lt;/h3&gt;
&lt;p&gt;我举个例子，在vue中：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;this.name = &amp;#x27;林三心&amp;#x27;
this.age = 18
this.gender = &amp;#x27;男&amp;#x27;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们修改了三个变量，那问题来了，是每修改一次，DOM就更新一次吗？不是的，Vue采用的是&lt;code&gt;异步更新&lt;/code&gt;的策略，通俗点说就是，&lt;code&gt;同一事件循环内&lt;/code&gt;多次修改，会&lt;code&gt;统一&lt;/code&gt;进行一次&lt;code&gt;视图更新&lt;/code&gt;，这样才能节省性能嘛&lt;/p&gt;
&lt;p&gt;看懂了上面，那你应该也看得懂下面的例子了吧：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;div ref=&amp;quot;testDiv&amp;quot;&amp;gt;{{name}}&amp;lt;&amp;#x2F;div&amp;gt;

name: &amp;#x27;小林&amp;#x27;

this.name = &amp;#x27;林三心&amp;#x27;
console.log(this.$refs.testDiv.innerHTML) &amp;#x2F;&amp;#x2F; 这里是啥呢
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;答案是“小林”，前面说了，Vue是&lt;code&gt;异步更新&lt;/code&gt;，所以数据一更新，视图却还没更新，所以拿到的还是上一次的旧视图数据，那么想要拿到最新视图数据怎么办呢？&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;this.name = &amp;#x27;林三心&amp;#x27;
this.$nextTick(() =&amp;gt; {
    console.log(this.$refs.testDiv.innerHTML) &amp;#x2F;&amp;#x2F; 林三心
})
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;27. Vue的SSR是什么？有什么好处？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SSR&lt;/code&gt;就是服务端渲染&lt;/li&gt;
&lt;li&gt;基于&lt;code&gt;nodejs serve&lt;/code&gt;服务环境开发，所有&lt;code&gt;html&lt;/code&gt;代码在服务端渲染&lt;/li&gt;
&lt;li&gt;数据返回给前端，然后前端进行“激活”，即可成为浏览器识别的html代码&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SSR&lt;/code&gt;首次加载更快，有更好的用户体验，有更好的seo优化，因为爬虫能看到整个页面的内容，如果是vue项目，由于数据还要经过解析，这就造成爬虫并不会等待你的数据加载完成，所以其实Vue项目的seo体验并不是很好&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;最强王者&lt;/h2&gt;
&lt;h3&gt;28. Vue响应式是怎么实现的？&lt;/h3&gt;
&lt;p&gt;整体思路是数据劫持+观察者模式&lt;/p&gt;
&lt;p&gt;对象内部通过&lt;code&gt;defineReactive&lt;/code&gt; 方法，使用 &lt;code&gt;Object.defineProperty&lt;/code&gt; 将属性进行劫持（只会劫持已经存在的属性），数组则是通过重写数组方法来实现。当页面使用对应属性时，每个属性都拥有自己的&lt;code&gt;dep&lt;/code&gt;属性，存放他所依赖的&lt;code&gt;watcher&lt;/code&gt;（依赖收集），当属性变化后会通知自己对应的&lt;code&gt;watcher&lt;/code&gt; 去更新(派发更新)。&lt;/p&gt;
&lt;p&gt;想详细了解过程，建议阅读我的&lt;a href=&quot;https://juejin.cn/column/6969563635194527758&quot;&gt;Vue源码解析系列&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const { arrayMethods } = require(&amp;#x27;.&amp;#x2F;array&amp;#x27;)

class Observer {
    constructor(value) {
        Object.defineProperty(value, &amp;#x27;__ob__&amp;#x27;, {
            value: this,
            enumerable: false,
            writable: true,
            configurable: true
        })
        if(Array.isArray(value)) {
            value.__proto__ = arrayMethods
            this.observeArray(value)
        } else {
            this.walk(value)
        }
    }

    walk(data) {
        let keys = Object.keys(data)
        for(let i = 0; i &amp;lt; keys.length; i++) {
            const key = keys[i]
            const value = data[key]
            defineReactive(data, key, value)
        }
    }

    observeArray(items) {
        for(let i = 0; i &amp;lt; items.length; i++) {
            observe(items[i])
        }
    }
}

function defineReactive(data, key, value) {
    const childOb = observe(value)

    const dep = new Dep()

    Object.defineProperty(data, key, {
        get() {
            console.log(&amp;#x27;获取值&amp;#x27;)
            if (Dep.target) {
                dep.depend()

                if (childOb) {
                    childOb.dep.depend()

                    if (Array.isArray(value)) {
                        dependArray(value)
                    }
                }
            }
            return value
        },
        set(newVal) {
            if (newVal === value) return
            observe(newVal)
            value = newVal
            dep.notify()
        }
    })
}

function observe(value) {
    if (Object.prototype.toString.call(value) === &amp;#x27;[object Object]&amp;#x27; || Array.isArray(value)) {
        return new Observer(value)
    }
}

function dependArray(value) {
    for(let e, i = 0, l = value.length; i &amp;lt; l; i++) {
        e = value[i]

        e &amp;amp;&amp;amp; e.__ob__ &amp;amp;&amp;amp; e.__ob__.dep.depend()

        if (Array.isArray(e)) {
            dependArray(e)
        }
    }
}

&amp;#x2F;&amp;#x2F; array.js
const arrayProto = Array.prototype

const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
    &amp;#x27;push&amp;#x27;,
    &amp;#x27;pop&amp;#x27;,
    &amp;#x27;shift&amp;#x27;,
    &amp;#x27;unshift&amp;#x27;,
    &amp;#x27;splice&amp;#x27;,
    &amp;#x27;reverse&amp;#x27;,
    &amp;#x27;sort&amp;#x27;
]

methodsToPatch.forEach(method =&amp;gt; {
    arrayMethods[method] = function (...args) {
        const result = arrayProto[method].apply(this, args)

        const ob = this.__ob__

        var inserted

        switch (method) {
            case &amp;#x27;push&amp;#x27;:
            case &amp;#x27;unshift&amp;#x27;:
                inserted = args
                break;
            case &amp;#x27;splice&amp;#x27;:
                inserted = args.slice(2)
            default:
                break;
        }

        if (inserted) ob.observeArray(inserted)

        ob.dep.notify()

        return result
    }
})

&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;29. 为什么只对对象劫持，而要对数组进行方法重写？&lt;/h3&gt;
&lt;p&gt;因为对象最多也就几十个属性，拦截起来数量不多，但是数组可能会有几百几千项，拦截起来非常耗性能，所以直接重写数组原型上的方法，是比较节省性能的方案&lt;/p&gt;
&lt;h3&gt;30. Vue的模板编译原理？&lt;/h3&gt;
&lt;p&gt;因为这个问题讲起来可能比较长，所以：&lt;/p&gt;
&lt;p&gt;建议看我这篇&lt;a href=&quot;https://juejin.cn/post/6969563640416436232&quot;&gt;「Vue源码学习(二)」你不知道的-模板编译原理&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;31. Vue的computed和watch的原理？&lt;/h3&gt;
&lt;p&gt;因为这个问题讲起来可能比较长，所以：&lt;/p&gt;
&lt;p&gt;建议看我这篇&lt;a href=&quot;https://juejin.cn/post/6974293549135167495&quot;&gt;「Vue源码学习(四)」立志写一篇人人都看的懂的computed，watch原理&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;32. Vue.set方法的原理？&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;function set(target, key, val) {
    &amp;#x2F;&amp;#x2F; 判断是否是数组
    if (Array.isArray(target)) {
        &amp;#x2F;&amp;#x2F; 判断谁大谁小
        target.length = Math.max(target.length, key)
        &amp;#x2F;&amp;#x2F; 执行splice
        target.splice(key, 1, val)
        return val
    }

    const ob = target.__ob__

    &amp;#x2F;&amp;#x2F; 如果此对象没有不是响应式对象，直接设置并返回
    if (key in target &amp;amp;&amp;amp; !(key in target.prototype) || !ob) {
        target[key] = val
        return val
    }

    &amp;#x2F;&amp;#x2F; 否则，新增属性，并响应式处理
    defineReactive(target, key, val)
    return val
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;33. Vue.delete方法的原理？&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;function del (target, key) {
    &amp;#x2F;&amp;#x2F; 判断是否为数组
    if (Array.isArray(target)) {
        &amp;#x2F;&amp;#x2F; 执行splice
        target.splice(key, 1)
        return
    }

    const ob = target.__ob__

    &amp;#x2F;&amp;#x2F; 对象本身就没有这个属性，直接返回
    if (!(key in target)) return


    &amp;#x2F;&amp;#x2F; 否则，删除这个属性
    delete target[key]

    &amp;#x2F;&amp;#x2F; 判断是否是响应式对象，不是的话，直接返回
    if (!ob) return
    &amp;#x2F;&amp;#x2F; 是的话，删除后要通知视图更新
    ob.dep.notify()
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;34. nextTick的原理？&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;let callbacks = []; &amp;#x2F;&amp;#x2F;回调函数
let pending = false;
function flushCallbacks() {
  pending = false; &amp;#x2F;&amp;#x2F;把标志还原为false
  &amp;#x2F;&amp;#x2F; 依次执行回调
  for (let i = 0; i &amp;lt; callbacks.length; i++) {
    callbacks[i]();
  }
}
let timerFunc; &amp;#x2F;&amp;#x2F;先采用微任务并按照优先级优雅降级的方式实现异步刷新
if (typeof Promise !== &amp;quot;undefined&amp;quot;) {
  &amp;#x2F;&amp;#x2F; 如果支持promise
  const p = Promise.resolve();
  timerFunc = () =&amp;gt; {
    p.then(flushCallbacks);
  };
} else if (typeof MutationObserver !== &amp;quot;undefined&amp;quot;) {
  &amp;#x2F;&amp;#x2F; MutationObserver 主要是监听dom变化 也是一个异步方法
  let counter = 1;
  const observer = new MutationObserver(flushCallbacks);
  const textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {
    characterData: true,
  });
  timerFunc = () =&amp;gt; {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
} else if (typeof setImmediate !== &amp;quot;undefined&amp;quot;) {
  &amp;#x2F;&amp;#x2F; 如果前面都不支持 判断setImmediate
  timerFunc = () =&amp;gt; {
    setImmediate(flushCallbacks);
  };
} else {
  &amp;#x2F;&amp;#x2F; 最后降级采用setTimeout
  timerFunc = () =&amp;gt; {
    setTimeout(flushCallbacks, 0);
  };
}

export function nextTick(cb) {
  callbacks.push(cb);
  if (!pending) {
    pending = true;
    timerFunc();
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;35. key有什么用？说说diff算法吧？&lt;/h3&gt;
&lt;p&gt;直接看这篇吧：&lt;a href=&quot;https://juejin.cn/post/6844904113587634184&quot;&gt;为什么 Vue 中不要用 index 作为 key？（diff 算法详解）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我讲的没他好&lt;/p&gt;
&lt;h2&gt;冷门的知识点&lt;/h2&gt;
&lt;h3&gt;36. 如果子组件改变props里的数据会发生什么&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;改变的props数据是基本类型&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;如果修改的是基本类型，则会报错&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;props: {
    num: Number,
  }
created() {
    this.num = 999
  }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/047859eda3bf47a5930dd0b28e842017~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;0458e2ff1538ee85d42953cec9a94ca.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;改变的props数据是引用类型&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;props: {
    item: {
      default: () =&amp;gt; ({}),
    }
  }
created() {
    &amp;#x2F;&amp;#x2F; 不报错，并且父级数据会跟着变
    this.item.name = &amp;#x27;sanxin&amp;#x27;;
    
    &amp;#x2F;&amp;#x2F; 会报错，跟基础类型报错一样
    this.item = &amp;#x27;sss&amp;#x27;
  },
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;37. props怎么自定义验证&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;props: {
    num: {
      default: 1,
      validator: function (value) {
          &amp;#x2F;&amp;#x2F; 返回值为false则验证不通过，报错
          return [
            1, 2, 3, 4, 5
          ].indexOf(value) !== -1
    }
    }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;38. watch的immediate属性有什么用？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;比如平时created时要请求一次数据，并且当搜索值改变，也要请求数据，我们会这么写：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;created(){
  this.getList()
},
watch: {
  searchInputValue(){
    this.getList()
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;使用&lt;code&gt;immediate&lt;/code&gt;完全可以这么写，当它为&lt;code&gt;true&lt;/code&gt;时，会初始执行一次&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;watch: {
  searchInputValue:{
    handler: &amp;#x27;getList&amp;#x27;,
    immediate: true
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;39. watch监听一个对象时，如何排除某些属性的监听&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;下面代码是，params发生改变就重新请求数据，无论是a，b，c，d属性改变&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;data() {
    return {
      params: {
        a: 1,
        b: 2,
        c: 3,
        d: 4
      },
    };
  },
watch: {
    params: {
      deep: true,
      handler() {
        this.getList;
      },
    },
  }
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;但是如果我只想要a，b改变时重新请求，c，d改变时不重新请求呢？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;mounted() {
    Object.keys(this.params)
      .filter((_) =&amp;gt; ![&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;].includes(_)) &amp;#x2F;&amp;#x2F; 排除对c，d属性的监听
      .forEach((_) =&amp;gt; {
        this.$watch((vm) =&amp;gt; vm.params[_], handler, {
          deep: true,
        });
      });
  },
data() {
    return {
      params: {
        a: 1,
        b: 2,
        c: 3,
        d: 4
      },
    };
  },
watch: {
    params: {
      deep: true,
      handler() {
        this.getList;
      },
    },
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;40. 审查元素时发现data-v-xxxxx，这是啥？&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ff63f90f2d924ff6b20622a2d05ba367~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这是在标记vue文件中css时使用scoped标记产生的，因为要保证各文件中的css不相互影响，给每个component都做了唯一的标记，所以每引入一个component就会出现一个新的’data-v-xxx’标记&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;41. computed如何实现传参？&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; html
&amp;lt;div&amp;gt;{{ total(3) }}

&amp;#x2F;&amp;#x2F; js
computed: {
    total() {
      return function(n) {
          return n * this.num
         }
    },
  }

&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;42. vue的hook的使用&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;同一组件中使用&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;这是我们常用的使用定时器的方式&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;export default{
  data(){
    timer:null  
  },
  mounted(){
      this.timer = setInterval(()=&amp;gt;{
      &amp;#x2F;&amp;#x2F;具体执行内容
      console.log(&amp;#x27;1&amp;#x27;);
    },1000);
  }
  beforeDestory(){
    clearInterval(this.timer);
    this.timer = null;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;上面做法不好的地方在于：得全局多定义一个timer变量，可以使用hook这么做：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;export default{
  methods:{
    fn(){
      let timer = setInterval(()=&amp;gt;{
        &amp;#x2F;&amp;#x2F;具体执行代码
        console.log(&amp;#x27;1&amp;#x27;);
      },1000);
      this.$once(&amp;#x27;hook:beforeDestroy&amp;#x27;,()=&amp;gt;{
        clearInterval(timer);
        timer = null;
      })
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;7.2 父子组件使用&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;如果子组件需要在mounted时触发父组件的某一个函数，平时都会这么写：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;父组件
&amp;lt;rl-child @childMounted=&amp;quot;childMountedHandle&amp;quot;
&amp;#x2F;&amp;gt;
method () {
  childMountedHandle() {
  &amp;#x2F;&amp;#x2F; do something...
  }
},

&amp;#x2F;&amp;#x2F; 子组件
mounted () {
  this.$emit(&amp;#x27;childMounted&amp;#x27;)
},
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;使用hook的话可以更方便：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;父组件
&amp;lt;rl-child @hook:mounted=&amp;quot;childMountedHandle&amp;quot;
&amp;#x2F;&amp;gt;
method () {
  childMountedHandle() {
  &amp;#x2F;&amp;#x2F; do something...
  }
},
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;43. provide和inject是响应式的吗？&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 祖先组件
provide(){
    return {
   &amp;#x2F;&amp;#x2F; keyName: { name: this.name }, &amp;#x2F;&amp;#x2F; value 是对象才能实现响应式，也就是引用类型
      keyName: this.changeValue &amp;#x2F;&amp;#x2F; 通过函数的方式也可以[注意，这里是把函数作为value，而不是this.changeValue()]
   &amp;#x2F;&amp;#x2F; keyName: &amp;#x27;test&amp;#x27; value 如果是基本类型，就无法实现响应式
    }
  },
data(){
  return {
	name:&amp;#x27;张三&amp;#x27;
}
  },
  methods: {
  	changeValue(){
  		this.name = &amp;#x27;改变后的名字-李四&amp;#x27;
  	}
  }  
  
  &amp;#x2F;&amp;#x2F; 后代组件
  inject:[&amp;#x27;keyName&amp;#x27;]
  create(){
	console.log(this.keyName) &amp;#x2F;&amp;#x2F; 改变后的名字-李四
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;44.Vue的el属性和$mount优先级？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;比如下面这种情况，Vue会渲染到哪个节点上&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;new Vue({
  router,
  store,
  el: &amp;#x27;#app&amp;#x27;,
  render: h =&amp;gt; h(App)
}).$mount(&amp;#x27;#ggg&amp;#x27;)
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;这是官方的一张图，可以看出&lt;code&gt;el&lt;/code&gt;和&lt;code&gt;$mount&lt;/code&gt;同时存在时，&lt;code&gt;el优先级&lt;/code&gt; &amp;gt; &lt;code&gt;$mount&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/da6331091cb145459e0b543c76e9bfc6~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;45. 动态指令和参数使用过吗？&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    ...
    &amp;lt;aButton @[someEvent]=&amp;quot;handleSomeEvent()&amp;quot; :[someProps]=&amp;quot;1000&amp;quot; &amp;#x2F;&amp;gt;...
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
  ...
  data(){
    return{
      ...
      someEvent: someCondition ? &amp;quot;click&amp;quot; : &amp;quot;dbclick&amp;quot;,
      someProps: someCondition ? &amp;quot;num&amp;quot; : &amp;quot;price&amp;quot;
    }
  },
  methods: {
    handleSomeEvent(){
      &amp;#x2F;&amp;#x2F; handle some event
    }
  }  
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;46. 相同的路由组件如何重新渲染？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;开发人员经常遇到的情况是，多个路由解析为同一个Vue组件。问题是，Vue出于性能原因，默认情况下共享组件将不会重新渲染，如果你尝试在使用相同组件的路由之间进行切换，则不会发生任何变化。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const routes = [
  {
    path: &amp;quot;&amp;#x2F;a&amp;quot;,
    component: MyComponent
  },
  {
    path: &amp;quot;&amp;#x2F;b&amp;quot;,
    component: MyComponent
  },
];
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;如果依然想重新渲染，怎么办呢？可以使用&lt;code&gt;key&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;router-view :key=&amp;quot;$route.path&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;router-view&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;47. 自定义v-model&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;默认情况下，v-model 是 @input 事件侦听器和 :value 属性上的语法糖。但是，你可以在你的Vue组件中指定一个模型属性来定义使用什么事件和value属性——非常棒！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;export default: {
  model: {
    event: &amp;#x27;change&amp;#x27;,
    prop: &amp;#x27;checked&amp;#x27;  
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;48. 如何将获取data中某一个数据的初始状态？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;在开发中，有时候需要拿初始状态去计算。例如&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;data() {
    return {
      num: 10
  },
mounted() {
    this.num = 1000
  },
methods: {
    howMuch() {
        &amp;#x2F;&amp;#x2F; 计算出num增加了多少，那就是1000 - 初始值
        &amp;#x2F;&amp;#x2F; 可以通过this.$options.data().xxx来获取初始值
        console.log(1000 - this.$options.data().num)
    }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;49.为什么不建议v-for和v-if同时存在&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;div v-for=&amp;quot;item in [1, 2, 3, 4, 5, 6, 7]&amp;quot; v-if=&amp;quot;item !== 3&amp;quot;&amp;gt;
    {{item}}
&amp;lt;&amp;#x2F;div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;上面的写法是v-for和v-if同时存在，会先把7个元素都遍历出来，然后再一个个判断是否为3，并把3给隐藏掉，这样的坏处就是，渲染了无用的3节点，增加无用的dom操作，建议使用computed来解决这个问题：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;div v-for=&amp;quot;item in list&amp;quot;&amp;gt;
    {{item}}
&amp;lt;&amp;#x2F;div&amp;gt;

computed() {
    list() {
        return [1, 2, 3, 4, 5, 6, 7].filter(item =&amp;gt; item !== 3)
    }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;50.计算变量时，methods和computed哪个好？&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;div&amp;gt;
    &amp;lt;div&amp;gt;{{howMuch1()}}&amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;div&amp;gt;{{howMuch2}}&amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;div&amp;gt;{{index}}&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;div&amp;gt;

data: () {
    return {
         index: 0
       }
     }
methods: {
    howMuch1() {
        return this.num + this.price
    }
  }
computed: {
    howMuch2() {
        return this.num + this.price
    }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;computed&lt;/code&gt;会好一些，因为computed会有&lt;code&gt;缓存&lt;/code&gt;。例如index由0变成1，那么会触发视图更新，这时候methods会重新执行一次，而computed不会，因为computed依赖的两个变量num和price都没变。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;如果你觉得此文对你有一丁点帮助，点个赞，鼓励一下林三心哈哈。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你想一起学习前端或者摸鱼，那你可以加我，加入我的摸鱼学习群，点击这里&lt;/strong&gt; —&amp;gt; &lt;a href=&quot;https://juejin.cn/pin/7035153948126216206&quot; title=&quot;https://juejin.cn/pin/7035153948126216206&quot;&gt;摸鱼沸点&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你是有其他目的的，别加我，我不想跟你交朋友，我只想简简单单学习前端，不想搞一些有的没的！！！&lt;/strong&gt;&lt;p&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
作者: Sunshine_Lin
原文链接：&lt;a href=&quot;https://juejin.im/post/6984210440276410399&quot;&gt;https://juejin.im/post/6984210440276410399&lt;/a&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>banyungong</author><pubDate>Mon, 17 Jan 2022 07:25:08 GMT</pubDate></item><item><title>『焦点访谈』最厉害靠谱的回血导师「手机搜狐网」</title><link>http://vue-js.com/topic/61e5195dad745b003c9df773</link><guid>http://vue-js.com/topic/61e5195dad745b003c9df773</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;最厉害靠谱的回血导师【+老师企@鹅-３６３９５８７】【-信誉誷-ｚｈ５７點ｖｉｐ丨邀-请-码66413333】☆问鼎中崋★九五至尊【顶-尖-名-师丨信.誉.平.台丨购&lt;code&gt;采&lt;/code&gt;首`选丨一.对.一丨全.天.在.线.辅.导】财联社12月31日电，天眼查显示，12月30日，四川蜀能矿产有限责任公司成立，注册资本5亿人民币，法定代表人徐开贵。经营范围含矿产资源（非煤矿山）开采；矿产资源勘查；再生资源回收（除生产性废旧金属）；五金产品研发；贵金属冶炼等。股权穿透图显示，该公司由四川路桥建设集团股份有限公司、比亚迪股份有限公司等共同持股。=来源：凤凰资讯-网易新闻-知乎日报-热点资讯-腾讯新闻-新浪新闻-星岛环球网-国际在线-凤凰周刊-搜狐视频-环球时报-百色新闻网-宣汉新闻网-360新闻-360新闻-香港文汇网-CRI国际在线-美联社-合众国际社-路透社-俄罗斯通讯社&lt;/p&gt;
&lt;/div&gt;</description><author>jkshdfj</author><pubDate>Mon, 17 Jan 2022 07:23:09 GMT</pubDate></item><item><title>【央视新闻】求带回血的良心导师《手机搜狐网》</title><link>http://vue-js.com/topic/61e51950cbbfd1003b11f9fc</link><guid>http://vue-js.com/topic/61e51950cbbfd1003b11f9fc</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;求带回血的良心导师【+老师企@鹅-３６３９５８７】【-信誉誷-ｚｈ５７點ｖｉｐ丨邀-请-码66413333】☆问鼎中崋★九五至尊【顶-尖-名-师丨信.誉.平.台丨购&lt;code&gt;采&lt;/code&gt;首`选丨一.对.一丨全.天.在.线.辅.导】财联社12月31日电，天眼查显示，12月30日，四川蜀能矿产有限责任公司成立，注册资本5亿人民币，法定代表人徐开贵。经营范围含矿产资源（非煤矿山）开采；矿产资源勘查；再生资源回收（除生产性废旧金属）；五金产品研发；贵金属冶炼等。股权穿透图显示，该公司由四川路桥建设集团股份有限公司、比亚迪股份有限公司等共同持股。=来源：凤凰资讯-网易新闻-知乎日报-热点资讯-腾讯新闻-新浪新闻-星岛环球网-国际在线-凤凰周刊-搜狐视频-环球时报-百色新闻网-宣汉新闻网-360新闻-360新闻-香港文汇网-CRI国际在线-美联社-合众国际社-路透社-俄罗斯通讯社&lt;/p&gt;
&lt;/div&gt;</description><author>jkshdfj</author><pubDate>Mon, 17 Jan 2022 07:22:56 GMT</pubDate></item><item><title>在 Vue 中为什么不推荐用 index 做 key__前端__JavaScript__Vue.js</title><link>http://vue-js.com/topic/61e13f14cbbfd1003b11f98c</link><guid>http://vue-js.com/topic/61e13f14cbbfd1003b11f98c</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d4eb6a01e6564840817b127265b07edd~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8ec9270b418647e4ab27f5ccbd983824~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;谱根.png&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这是第 120 篇不掺水的原创，想获取更多原创好文，请搜索公众号关注我们吧~ 本文首发于政采云前端博客：&lt;a href=&quot;https://zoo.team/article/vue-index&quot;&gt;在 Vue 中为什么不推荐用 index 做 key&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;前端开发中，只要涉及到列表渲染，那么无论是 React 还是 Vue 框架，都会提示或要求每个列表项使用唯一的 key，那很多开发者就会直接使用数组的 index 作为 key 的值，而并不知道 key 的原理。那么这篇文章就会讲解 key 的作用以及为什么最好不要使用 index 作为 key 的属性值。&lt;/p&gt;
&lt;h2&gt;key 的作用&lt;/h2&gt;
&lt;p&gt;Vue 中使用虚拟 dom 且根据 diff 算法进行新旧 DOM 对比，从而更新真实 dom ，key 是虚拟 DOM 对象的唯一标识, 在 diff 算法中 key 起着极其重要的作用。&lt;/p&gt;
&lt;h2&gt;key 在 diff 算法中的角色&lt;/h2&gt;
&lt;p&gt;其实在 React，Vue，中 diff 算法大致是差不多，但是 diff 比对方式还是有较大差异的，甚至每个版本 diff 都大有不同。下面我们就以 Vue3.0 diff 算法为切入点，剖析 key 在 diff 算法中的作用&lt;/p&gt;
&lt;p&gt;具体 diff 流程如下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0f3108cefc3e4974acc53b305c42f707~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;未命名表单 (1).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Vue3.0中 在 patchChildren 方法中有这么一段源码&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;if (patchFlag &amp;gt; 0) {
      if (patchFlag &amp;amp; PatchFlags.KEYED_FRAGMENT) { 
         &amp;#x2F;* 对于存在 key 的情况用于 diff 算法 *&amp;#x2F;
        patchKeyedChildren(
         ...
        )
        return
      } else if (patchFlag &amp;amp; PatchFlags.UNKEYED_FRAGMENT) {
         &amp;#x2F;* 对于不存在 key 的情况,直接 patch  *&amp;#x2F;
        patchUnkeyedChildren( 
          ...
        )
        return
      }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;patchChildren 根据是否存在 key 进行真正的 diff 或者直接 patch。对于 key 不存在的情况我们就不做深入研究了。&lt;/p&gt;
&lt;p&gt;我们先来看看一些声明的变量。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;*  c1 老的 vnode c2 新的vnode  *&amp;#x2F;
let i = 0              &amp;#x2F;* 记录索引 *&amp;#x2F;
const l2 = c2.length   &amp;#x2F;* 新 vnode的数量 *&amp;#x2F;
let e1 = c1.length - 1 &amp;#x2F;* 老 vnode 最后一个节点的索引 *&amp;#x2F;
let e2 = l2 - 1        &amp;#x2F;* 新节点最后一个节点的索引 *&amp;#x2F;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;同步头部节点&lt;/h3&gt;
&lt;p&gt;第一步的事情就是从头开始寻找相同的 vnode，然后进行 patch ,如果发现不是相同的节点，那么立即跳出循环。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;(a b) c
&amp;#x2F;&amp;#x2F;(a b) d e
&amp;#x2F;* 从头对比找到有相同的节点 patch ，发现不同，立即跳出*&amp;#x2F;
    while (i &amp;lt;= e1 &amp;amp;&amp;amp; i &amp;lt;= e2) {
      const n1 = c1[i]
      const n2 = (c2[i] = optimized
        ? cloneIfMounted(c2[i] as VNode)
        : normalizeVNode(c2[i]))
        &amp;#x2F;* 判断 key ，type 是否相等 *&amp;#x2F;
      if (isSameVNodeType(n1, n2)) {
        patch(
          ...
        )
      } else {
        break
      }
      i++
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;流程如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7c2e234667684ce0b19e085dc96add9f~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;isSameVNodeType 作用就是判断当前 vnode 类型 和 vnode 的 key 是否相等&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
  return n1.type === n2.type &amp;amp;&amp;amp; n1.key === n2.key
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其实看到这，是不是已经知道 key 在 diff 算法的作用，就是用来判断是否是同一个节点。&lt;/p&gt;
&lt;h3&gt;同步尾部节点&lt;/h3&gt;
&lt;p&gt;第二步从尾开始同前 diff&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;a (b c)
&amp;#x2F;&amp;#x2F;d e (b c)
&amp;#x2F;* 如果第一步没有 patch 完，立即，从后往前开始 patch  如果发现不同立即跳出循环 *&amp;#x2F;
    while (i &amp;lt;= e1 &amp;amp;&amp;amp; i &amp;lt;= e2) {
      const n1 = c1[e1]
      const n2 = (c2[e2] = optimized
        ? cloneIfMounted(c2[e2] as VNode)
        : normalizeVNode(c2[e2]))
      if (isSameVNodeType(n1, n2)) {
        patch(
         ...
        )
      } else {
        break
      }
      e1--
      e2--
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;经历第一步操作之后，如果发现没有 patch 完，那么立即进行第二步，从尾部开始遍历依次向前 diff。如果发现不是相同的节点，那么立即跳出循环。
流程如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/86855d1c542f4841993743a5a8a9cd49~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (1).png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;添加新的节点&lt;/h3&gt;
&lt;p&gt;第三步如果老节点是否全部 patch，新节点没有被 patch 完,创建新的 vnode&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;(a b)
&amp;#x2F;&amp;#x2F;(a b) c
&amp;#x2F;&amp;#x2F;i = 2, e1 = 1, e2 = 2
&amp;#x2F;&amp;#x2F;(a b)
&amp;#x2F;&amp;#x2F;c (a b)
&amp;#x2F;&amp;#x2F;i = 0, e1 = -1, e2 = 0
&amp;#x2F;* 如果新的节点大于老的节点数 ，对于剩下的节点全部以新的 vnode 处理（这种情况说明已经 patch 完相同的 vnode ） *&amp;#x2F;
    if (i &amp;gt; e1) {
      if (i &amp;lt;= e2) {
        const nextPos = e2 + 1
        const anchor = nextPos &amp;lt; l2 ? (c2[nextPos] as VNode).el : parentAnchor
        while (i &amp;lt;= e2) {
          patch( &amp;#x2F;* 创建新的节点*&amp;#x2F;
            ...
          )
          i++
        }
      }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;流程如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e054a7fc8aa8417ea463fd1d8a6ad5a1~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (2).png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;删除多余节点&lt;/h3&gt;
&lt;p&gt;第四步如果新节点全部被 patch，老节点有剩余，那么卸载所有老节点&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;i &amp;gt; e2
&amp;#x2F;&amp;#x2F;(a b) c
&amp;#x2F;&amp;#x2F;(a b)
&amp;#x2F;&amp;#x2F;i = 2, e1 = 2, e2 = 1
&amp;#x2F;&amp;#x2F;a (b c)
&amp;#x2F;&amp;#x2F;(b c)
&amp;#x2F;&amp;#x2F;i = 0, e1 = 0, e2 = -1
else if (i &amp;gt; e2) {
   while (i &amp;lt;= e1) {
      unmount(c1[i], parentComponent, parentSuspense, true)
      i++
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;流程如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/429c92305790409680f4b7fef4e1c510~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (3).png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;最长递增子序列&lt;/h3&gt;
&lt;p&gt;到了这一步，比较核心的场景还没有出现，如果运气好，可能到这里就结束了，那我们也不能全靠运气。剩下的一个场景是新老节点都还有多个子节点存在的情况。那接下来看看，Vue3 是怎么做的。为了结合 move、新增和卸载的操作&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/77805129d7384dc78922898b3b0e018d~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (4).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;每次在对元素进行移动的时候，我们可以发现一个规律，如果想要移动的次数最少，就意味着需要有一部分元素是稳定不动的，那么究竟能够保持稳定不动的元素有一些什么规律呢？&lt;/p&gt;
&lt;p&gt;可以看一下上面这个例子：c  h  d  e  VS  d  e  i  c，在比对的时候，凭着肉眼可以看出只需要将 c 进行移动到最后，然后卸载 h，新增 i 就好了。d  e 可以保持不动，可以发现 d  e 在新老节点中的顺序都是不变的，d 在 e 的后面，下标处于递增状态。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-plain&quot;&gt;&lt;code&gt;这里引入一个概念，叫最长递增子序列。
官方解释：在一个给定的数组中，找到一组递增的数值，并且长度尽可能的大。
有点比较难理解，那来看具体例子：

const arr = [10, 9, 2, 5, 3, 7, 101, 18]
=&amp;gt; [2, 3, 7, 18]
这一列数组就是arr的最长递增子序列，其实[2, 3, 7, 101]也是。
所以最长递增子序列符合三个要求：
1、子序列内的数值是递增的
2、子序列内数值的下标在原数组中是递增的
3、这个子序列是能够找到的最长的
但是我们一般会找到数值较小的那一组数列，因为他们可以增长的空间会更多。
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那接下来的思路是：如果能找到老节点在新节点序列中顺序不变的节点们，就知道，哪一些节点不需要移动，然后只需要把不在这里的节点插入进来就可以了。**因为最后要呈现出来的顺序是新节点的顺序，移动是只要老节点移动，所以只要老节点保持最长顺序不变，通过移动个别节点，就能够跟它保持一致。**所以在此之前，先把所有节点都找到，再找对应的序列。最后其实要得到的则是这一个数组：[2, 3, 新增 , 0]。其实这就是 diff 移动的思路了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/340a3dd032be4da685cf9dfa89f5b1c8~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (5).png&quot;&gt;&lt;/p&gt;
&lt;h1&gt;为什么不要用index&lt;/h1&gt;
&lt;h3&gt;性能消耗&lt;/h3&gt;
&lt;p&gt;使用 index 做 key，破坏顺序操作的时候， 因为每一个节点都找不到对应的 key，导致部分节点不能复用,所有的新 vnode 都需要重新创建。&lt;/p&gt;
&lt;p&gt;例子：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;hello&amp;quot;&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;li v-for=&amp;quot;(item,index) in studentList&amp;quot; :key=&amp;quot;index&amp;quot;&amp;gt;{{item.name}}&amp;lt;&amp;#x2F;li&amp;gt;
      &amp;lt;br&amp;gt;
      &amp;lt;button @click=&amp;quot;addStudent&amp;quot;&amp;gt;添加一条数据&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;&amp;#x2F;ul&amp;gt;

  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: &amp;#x27;HelloWorld&amp;#x27;,
  data() {
    return {
      studentList: [
        { id: 1, name: &amp;#x27;张三&amp;#x27;, age: 18 },
        { id: 2, name: &amp;#x27;李四&amp;#x27;, age: 19 },
      ],
    };
  },
  methods:{
    addStudent(){
      const studentObj = { id: 3, name: &amp;#x27;王五&amp;#x27;, age: 20 };
      this.studentList=[studentObj,...this.studentList]
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们先把 Chorme 调试器打开，我们双击把里面文本修改一下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ad4acbae89724e10bdfccdefbc8b3650~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (6).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们运行以上上面的代码，看下运行结果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e69a1c09aee425ab35b2a0dfbfaf952~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;chrome-capture (2).gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;从上面运行结果可以看出来，我们只是添加了一条数据，但是三条数据都需要重新渲染是不是很惊奇，我明明只是插入了一条数据，怎么三条数据都要重新渲染？而我想要的只是新增的那一条数据新渲染出来就行了。&lt;/p&gt;
&lt;p&gt;上面我们也讲过 diif 比较方式,下面根据 diff 比较绘制一张图，看看具体是怎么比较的吧&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f3346f6ba3c641ef8cf51ebd0505b7b9~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (7).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;当我们在前面加了一条数据时 index 顺序就会被打断，导致新节点 key 全部都改变了，所以导致我们页面上的数据都被重新渲染了。&lt;/p&gt;
&lt;p&gt;下面我们下面生成1000个 DOM 来比较一下采用 index ，和不采用 index 性能比较，为了保证 key 的唯一性我们采用 uuid 作为 key&lt;/p&gt;
&lt;p&gt;我们用 index 做为 key 现执行一遍&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;hello&amp;quot;&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;button @click=&amp;quot;addStudent&amp;quot;&amp;gt;添加一条数据&amp;lt;&amp;#x2F;button&amp;gt;
      &amp;lt;br&amp;gt;
      &amp;lt;li v-for=&amp;quot;(item,index) in studentList&amp;quot; :key=&amp;quot;index&amp;quot;&amp;gt;{{item.id}}&amp;lt;&amp;#x2F;li&amp;gt;
    &amp;lt;&amp;#x2F;ul&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import uuidv1 from &amp;#x27;uuid&amp;#x2F;v1&amp;#x27;
export default {
  name: &amp;#x27;HelloWorld&amp;#x27;,
  data() {
    return {
      studentList: [{id:uuidv1()}],
    };
  },
  created(){
    for (let i = 0; i &amp;lt; 1000; i++) {
      this.studentList.push({
        id: uuidv1(),
      });
    }
  },
  beforeUpdate(){
    console.time(&amp;#x27;for&amp;#x27;);
  },
  updated(){
    console.timeEnd(&amp;#x27;for&amp;#x27;)&amp;#x2F;&amp;#x2F;for: 75.259033203125 ms
  },
  methods:{
    addStudent(){
      const studentObj = { id: uuidv1() };
      this.studentList=[studentObj,...this.studentList]
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;换成 id 作为 key&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;hello&amp;quot;&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;button @click=&amp;quot;addStudent&amp;quot;&amp;gt;添加一条数据&amp;lt;&amp;#x2F;button&amp;gt;
      &amp;lt;br&amp;gt;
      &amp;lt;li v-for=&amp;quot;(item,index) in studentList&amp;quot; :key=&amp;quot;item.id&amp;quot;&amp;gt;{{item.id}}&amp;lt;&amp;#x2F;li&amp;gt;
    &amp;lt;&amp;#x2F;ul&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
  beforeUpdate(){
    console.time(&amp;#x27;for&amp;#x27;);
  },
  updated(){
    console.timeEnd(&amp;#x27;for&amp;#x27;)&amp;#x2F;&amp;#x2F;for: 42.200927734375 ms
  },
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从上面比较可以看出，用唯一值作为 key 可以节约开销&lt;/p&gt;
&lt;h3&gt;数据错位&lt;/h3&gt;
&lt;p&gt;上述例子可能觉得用 index 做 key 只是影响页面加载的效率，认为少量的数据影响不大，那面下面这种情况，可能用 index 就可能出现一些意想不到的问题了，还是上面的场景，这时我先再每个文本内容后面加一个 input 输入框，并且手动在输入框内填写一些内容，然后通过 button 向前追加一位同学看看&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;hello&amp;quot;&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;li v-for=&amp;quot;(item,index) in studentList&amp;quot; :key=&amp;quot;index&amp;quot;&amp;gt;{{item.name}}&amp;lt;input &amp;#x2F;&amp;gt;&amp;lt;&amp;#x2F;li&amp;gt;
      &amp;lt;br&amp;gt;
      &amp;lt;button @click=&amp;quot;addStudent&amp;quot;&amp;gt;添加一条数据&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;&amp;#x2F;ul&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: &amp;#x27;HelloWorld&amp;#x27;,
  data() {
    return {
      studentList: [
        { id: 1, name: &amp;#x27;张三&amp;#x27;, age: 18 },
        { id: 2, name: &amp;#x27;李四&amp;#x27;, age: 19 },
      ],
    };
  },
  methods:{
    addStudent(){
      const studentObj = { id: 3, name: &amp;#x27;王五&amp;#x27;, age: 20 };
      this.studentList=[studentObj,...this.studentList]
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们往 input 里面输入一些值，添加一位同学看下效果：
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6e9185cea3bb4b5db540848dca67c859~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;chrome-capture (3).gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;这时候我们就会发现，在添加之前输入的数据错位了。添加之后王五的输入框残留着张三的信息，这很显然不是我们想要的结果。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/36be485e696848709de8e7d77a4616da~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (8).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;从上面比对可以看出来这时因为采用 index 作为 key 时，当在比较时，发现虽然文本值变了，但是当继续向下比较时发现 &amp;lt;input /&amp;gt;  DOM 节点还是和原来一摸一样，就复用了，但是没想到 input 输入框残留输入的值，这时候就会出现输入的值出现错位的情况&lt;/p&gt;
&lt;h1&gt;解决方案&lt;/h1&gt;
&lt;p&gt;既然知道用 index 在某些情况下带来很不好的影响，那平时我们在开发当中怎么去解决这种情况呢？其实只要保证 key 唯一不变就行，一般在开发中用的比较多就是下面三种情况。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在开发中最好每条数据使用唯一标识固定的数据作为 key，比如后台返回的 ID，手机号，身份证号等唯一值&lt;/li&gt;
&lt;li&gt;可以采用 Symbol 作为 key，Symbol 是 ES6 引入了一种新的原始数据类型 Symbol ，表示独一无二的值，最大的用法是用来定义对象的唯一属性名。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language-plain&quot;&gt;&lt;code&gt;let a=Symbol(&amp;#x27;测试&amp;#x27;)
let b=Symbol(&amp;#x27;测试&amp;#x27;)
console.log(a===b)&amp;#x2F;&amp;#x2F;false
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;可以采用 uuid 作为 key ，uuid 是 Universally Unique Identifier 的缩写，它是在一定的范围内（从特定的名字空间到全球）唯一的机器生成的标识符&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们采用上面第一种方案作为 key 在看一下上面情况，如图所示。key 相同的节点都做到了复用。起到了diff 算法的真正作用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5b1c1738c0284796b9d0f34e3f36d029~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;chrome-capture (4).gif&quot;&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/81fa5ab929644d9bb3fd5dcf7649446c~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;chrome-capture (5).gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3e9bcc175af14a4ab09ab800517293b8~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (9).png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;用 index 作为 key 时，在对数据进行，逆序添加，逆序删除等破坏顺序的操作时，会产生没必要的真实 DOM更新，从而导致效率低&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用 index 作为 key 时，如果结构中包含输入类的 DOM，会产生错误的 DOM 更新&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在开发中最好每条数据使用唯一标识固定的数据作为 key，比如后台返回的 ID，手机号，身份证号等唯一值&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如果不存在对数据逆序添加，逆序删除等破坏顺序的操作时，仅用于渲染展示用时，使用 index 作为 key 也是可以的（但是还是不建议使用，养成良好开发习惯）。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考链接&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/zl_Alien/article/details/106595459&quot;&gt;vue3.0 diff算法详解(超详细)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://segmentfault.com/a/1190000038654183&quot;&gt;Vue 3 Virtual Dom Diff源码阅读&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;推荐阅读&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/7002746459456176158&quot;&gt;电商最小存货 - SKU 和 算法实现&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6997536906967777316&quot;&gt;你需要知道的项目管理知识&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/7013131773756309517&quot;&gt;浏览器渲染之回流重绘&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/7018296556323340324&quot;&gt;防抖节流场景及应用&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;开源作品&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;政采云前端小报&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;开源地址 &lt;a href=&quot;https://www.zoo.team/openweekly/&quot;&gt;www.zoo.team/openweekly/&lt;/a&gt;&lt;/strong&gt; (小报官网首页有微信交流群)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;skuDemo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;开源地址 &lt;a href=&quot;https://github.com/zcy-inc/skuPathFinder-back&quot;&gt;https://github.com/zcy-inc/skuPathFinder-back/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;招贤纳士&lt;/h2&gt;
&lt;p&gt;政采云前端团队（ZooTeam），一个年轻富有激情和创造力的前端团队，隶属于政采云产品研发部，Base 在风景如画的杭州。团队现有 50 余个前端小伙伴，平均年龄 27 岁，近 3 成是全栈工程师，妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵，也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外，还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战，推动并落地了一系列的内部技术产品，持续探索前端技术体系的新边界。&lt;/p&gt;
&lt;p&gt;如果你想改变一直被事折腾，希望开始能折腾事；如果你想改变一直被告诫需要多些想法，却无从破局；如果你想改变你有能力去做成那个结果，却不需要你；如果你想改变你想做成的事需要一个团队去支撑，但没你带人的位置；如果你想改变既定的节奏，将会是“5 年工作时间 3 年工作经验”；如果你想改变本来悟性不错，但总是有那一层窗户纸的模糊… 如果你相信相信的力量，相信平凡人能成就非凡事，相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程，亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程，我觉得我们该聊聊。任何时间，等着你写点什么，发给 &lt;code&gt;ZooTeam@cai-inc.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/98d3aa3d1f8646a8bcda8cfd9e335a4b~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 政采云前端团队
        原文链接：&lt;a href=&quot;https://juejin.im/post/7026119446162997261&quot;&gt;https://juejin.im/post/7026119446162997261&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Fri, 14 Jan 2022 09:15:00 GMT</pubDate></item><item><title>研究大佬写的倒计时组件(Vue)，学到了不少东西__Vue.js__前端</title><link>http://vue-js.com/topic/61e13ee5cbbfd1003b11f98b</link><guid>http://vue-js.com/topic/61e13ee5cbbfd1003b11f98b</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0234702e819c4f1c94fce6869e0d88c6~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;file&quot;&gt;&lt;/p&gt;
&lt;p&gt;本文首发于：&lt;a href=&quot;https://github.com/bigo-frontend/blog/&quot;&gt;https://github.com/bigo-frontend/blog/&lt;/a&gt; 欢迎关注、转载。&lt;/p&gt;
&lt;h2&gt;一、前言&lt;/h2&gt;
&lt;p&gt;入职的第一个需求是跟着一位前端大佬一起完成的一个活动项目。&lt;/p&gt;
&lt;p&gt;由于是一起开发，当然不会放过阅读大佬的代码的机会。&lt;/p&gt;
&lt;p&gt;因为我的页面中需要使用到倒计时功能，发现大佬的已经写了个现成的倒计时组件，于是直接就拿过来用了。&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/19cbeea00ba94feda62b720dcca08098~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;传个参数就实现了功能的感觉真是太棒了。项目完成后，就膜拜了一下大佬的倒计时组件的代码。真是让我学到了不少。列举如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计时器为什么要用setTimeout而不用setInterval&lt;/li&gt;
&lt;li&gt;为什么不直接将剩余时间-1。&lt;/li&gt;
&lt;li&gt;如何将所需要的时间返回出去（有可能我只需要分钟和秒数，那就只返回分钟和秒数，也有可能我全都要）。&lt;/li&gt;
&lt;li&gt;不确定接口返回的是剩余时间还是截止日期，该怎么同时兼容这两种情况。&lt;/li&gt;
&lt;li&gt;不确定接口返回的时间是秒还是毫秒单位。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;好了，你可能不太理解这些问题，但是没关系，看完下面的解释，相信你会豁然开朗。
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5313d084d073497a863901a6a907f0ab~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;h2&gt;二、开始手操&lt;/h2&gt;
&lt;h3&gt;1. 先创建一个vue组件&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
   
  }),
  props: {
    
  },
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 实现基本的倒计时组件&lt;/h3&gt;
&lt;p&gt;接下来，假设接口获得的是一个剩余时间。&lt;/p&gt;
&lt;p&gt;将剩余时间&lt;code&gt;time&lt;/code&gt;传入这个倒计时组件，由于time可能是秒为单位的，也有可能是毫秒为单位的，所以我们需要在传入&lt;code&gt;time&lt;/code&gt;的是有也传入一个&lt;code&gt;isMilliSecond&lt;/code&gt;来告诉倒计时组件这个&lt;code&gt;time&lt;/code&gt;是毫秒还是秒为单位的。如下代码中的&lt;code&gt;props&lt;/code&gt;所示。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;computed&lt;/code&gt;中的duration是将time进行转化的结果，不管&lt;code&gt;time&lt;/code&gt;是毫秒还是秒，都转化为秒
不知道你注意到了没有：&lt;code&gt;+this.time&lt;/code&gt;。为什么要在前面加个‘&lt;strong&gt;+&lt;/strong&gt;’号。这点很值得我们学习，因为接口返回的一串数字有时候是字符串的形式，有时候是数字的形式（&lt;s&gt;不能过分相信后端同学，必须自己做好防范&lt;/s&gt;）。所以通过前面加个‘&lt;strong&gt;+&lt;/strong&gt;’号 通通转化为数字。现在的&lt;code&gt;duration&lt;/code&gt;就是转化后的&lt;code&gt;time&lt;/code&gt;啦！&lt;/p&gt;
&lt;p&gt;我们获得duration之后就可以开始倒计时了&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  &amp;#x2F;&amp;#x2F; 新增代码：
  mounted() {
    this.countDown();
  },
  methods: {
    countDown() {
      this.getTime(this.duration);
    },
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在这里创建了一个countDown方法，表示开始倒计时的意思，已进入页面就开始执行countdown方法。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;countDown&lt;/code&gt;方法调用了getTime方法，getTime需要传入duration这个参数，也就是我们获得的剩余时间。&lt;/p&gt;
&lt;p&gt;现在来实现一下这个方法。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
    还剩{{day}}天{{hours}}:{{mins}}:{{seconds}}
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
    days: &amp;#x27;0&amp;#x27;,
    hours: &amp;#x27;00&amp;#x27;,
    mins: &amp;#x27;00&amp;#x27;,
    seconds: &amp;#x27;00&amp;#x27;,
    timer: null,
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  mounted() {
    this.countDown();
  },
  methods: {
    countDown() {
      this.getTime(this.duration);
    },
    &amp;#x2F;&amp;#x2F; 新增代码：
    getTime(duration) {
      this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);
      if (duration &amp;lt; 0) {
        return;
      }
      const { dd, hh, mm, ss } = this.durationFormatter(duration);
      this.days = dd || 0;
      this.hours = hh || 0;
      this.mins = mm || 0;
      this.seconds = ss || 0;
      this.timer = setTimeout(() =&amp;gt; {
        this.getTime(duration - 1);
      }, 1000);
    }
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到，&lt;strong&gt;getTime&lt;/strong&gt;的目的就是获得 days，hours,mins，seconds，然后显示到html上，并且通过定时器实时来刷新days，hours,mins，seconds这个几个值。从而实现了倒计时。很简单，有木有？&lt;/p&gt;
&lt;p&gt;&lt;code&gt;durationFormatter&lt;/code&gt;是一个将&lt;code&gt;duration&lt;/code&gt;转化成天数，小时，分钟，秒数的方法，很简单，可以看下它的具体实现。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;durationFormatter(time) {
  if (!time) return { ss: 0 };
  let t = time;
  const ss = t % 60;
  t = (t - ss) &amp;#x2F; 60;
  if (t &amp;lt; 1) return { ss };
  const mm = t % 60;
  t = (t - mm) &amp;#x2F; 60;
  if (t &amp;lt; 1) return { mm, ss };
  const hh = t % 24;
  t = (t - hh) &amp;#x2F; 24;
  if (t &amp;lt; 1) return { hh, mm, ss };
  const dd = t;
  return { dd, hh, mm, ss };
},
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;好了，问题开始来了！！
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/36794496b1f4436499617fa23eb621e7~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;h3&gt;3. 为什么要&lt;strong&gt;用setTimeout来模拟setInterval的行为&lt;/strong&gt;？&lt;/h3&gt;
&lt;p&gt;这里用setInerval不是更方便吗？&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;setTimeout(function(){··· }, n); &amp;#x2F;&amp;#x2F; n毫秒后执行function
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;setInterval(function(){··· }, n); &amp;#x2F;&amp;#x2F; 每隔n毫秒执行一次function
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看看setInterval有什么缺点：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;再次强调，定时器指定的时间间隔，表示的是何时将定时器的代码添加到消息队列，而不是何时执行代码。所以真正何时执行代码的时间是不能保证的，取决于何时被主线程的事件循环取到，并执行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;setInterval(function, N)  
&amp;#x2F;&amp;#x2F;即：每隔N秒把function事件推到消息队列中
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/39aa93f16e274e499f567cf7e31e33f6~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;上图可见，setInterval每隔100ms往队列中添加一个事件；100ms后，添加T1定时器代码至队列中，主线程中还有任务在执行，所以等待，some event执行结束后执行T1定时器代码；又过了100ms，T2定时器被添加到队列中，主线程还在执行T1代码，所以等待；又过了100ms，理论上又要往队列里推一个定时器代码，但由于此时T2还在队列中，所以T3不会被添加，结果就是此时被跳过；这里我们可以看到，T1定时器执行结束后马上执行了T2代码，所以并没有达到定时器的效果。&lt;/p&gt;
&lt;p&gt;综上所述，setInterval有两个缺点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用setInterval时，某些间隔会被跳过；&lt;/li&gt;
&lt;li&gt;可能多个定时器会连续执行；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可以这么理解：&lt;strong&gt;每个setTimeout产生的任务会直接push到任务队列中；而setInterval在每次把任务push到任务队列前，都要进行一下判断(看上次的任务是否仍在队列中)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因而我们一般用setTimeout模拟setInterval，来规避掉上面的缺点。&lt;/p&gt;
&lt;h3&gt;4. 为什么要clearTimeout(this.timer)&lt;/h3&gt;
&lt;p&gt;第二问：为什么要有&lt;code&gt;this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);&lt;/code&gt;这一句？&lt;/p&gt;
&lt;p&gt;假设一个场景：&lt;/p&gt;
&lt;p&gt;如图所示，在倒计时的父组件中，有两个按钮，点击活动一就会传入活动一的剩余时间，点击活动二，就会传入活动二的时间。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1f5ba9e8f4df440f80df305a0860a4f9~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;如果此时倒计时组件正在做活动一的倒计时，然后点击活动二，就要会马上传入新的time，这个时候就需要重新计时。当然，这里并不会重新计时，因为组件的mounted只会执行一次。也就是说&lt;code&gt;this.countDown();&lt;/code&gt;只会执行一次，也就是说&lt;code&gt;this.getTime(this.duration);&lt;/code&gt;只会执行一次，因此duration还是活动一的时间，怎么办呢？watch派上用场了。&lt;/p&gt;
&lt;p&gt;我们来监听duration，如果发现duration变化，说明新的时间time传入组件，这时就要重新调用this.countDown()。&lt;/p&gt;
&lt;p&gt;代码如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
    还剩{{day}}天{{hours}}:{{mins}}:{{seconds}}
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
    days: &amp;#x27;0&amp;#x27;,
    hours: &amp;#x27;00&amp;#x27;,
    mins: &amp;#x27;00&amp;#x27;,
    seconds: &amp;#x27;00&amp;#x27;,
    timer: null,
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  mounted() {
    this.countDown();
  },
  &amp;#x2F;&amp;#x2F; 新增代码：
  watch: {
    duration() {
      this.countDown();
    }
  },
  methods: {
    countDown() {
      this.getTime(this.duration);
    },
    durationFormatter(){...}
    getTime(duration) {
      this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);
      if (duration &amp;lt; 0) {
        return;
      }
      const { dd, hh, mm, ss } = this.durationFormatter(duration);
      this.days = dd || 0;
      this.hours = hh || 0;
      this.mins = mm || 0;
      this.seconds = ss || 0;
      this.timer = setTimeout(() =&amp;gt; {
        this.getTime(duration - 1);
      }, 1000);
    }
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;好了，但是并没有解释上面提出的那个问题：为什么要有&lt;code&gt;this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);&lt;/code&gt;这一句？&lt;/p&gt;
&lt;p&gt;这样，假设现在页面显示的是活动一的时间，这时，执行到setTimeout，在&lt;strong&gt;一秒后&lt;/strong&gt;就会把setTimeout里的回调函数放到任务队列中，&lt;strong&gt;注意是一秒后哦&lt;/strong&gt;！这时，然而，在这一秒的开头，我们点击了活动二按钮，这时候的活动二的时间就会传入倒计时组件中，然后触发&lt;code&gt;countDown()&lt;/code&gt;,也就调用&lt;code&gt;this.getTime(this.duration);&lt;/code&gt;，然后执行到setTimeout，也会一秒后把回调函数放到任务队列中。&lt;/p&gt;
&lt;p&gt;这时，任务队列中就会有两个setTimeout的回调函数了。等待一秒过去，两个回调函数相继执行，我们就会看到页面上的时间一下子背减了2，实际上是很快速地进行了两遍减1的操作。&lt;/p&gt;
&lt;p&gt;这就是为什么要添加上&lt;code&gt;this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);&lt;/code&gt;这一句的原因了。就是要把上一个setTimeout清除掉。&lt;/p&gt;
&lt;h3&gt;5. 使用 diffTime&lt;/h3&gt;
&lt;p&gt;当你认为这是一个完美的组件的时候，你想把这个组件用到项目上，假设你也确实用了，而且还上线了，确发现出现了个大问题：当页面打开的时候，倒计时开始了，时间是 &lt;code&gt;还剩1天12：25：25&lt;/code&gt;，然后有人给你发微信，你马上切换到微信，回复消息后切回浏览器，发现倒计时时间却还是&lt;code&gt;还剩1天12：25：25&lt;/code&gt;。你慌了：&lt;strong&gt;你写的代码出现bug了！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是怎么回事？&lt;/p&gt;
&lt;p&gt;出于节能的考虑, 部分浏览器在进入后台时(或者失去焦点时), &lt;strong&gt;会将 setTimeout 等定时任务暂停
待用户回到浏览器时, 才会重新激活定时任务&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;说是暂停, 其实应该说是延迟, 1s 的任务延迟到 2s, 2s 的延迟到 5s, 实际情况因浏览器而异。&lt;/p&gt;
&lt;p&gt;原来如此，看来不能每次都只是减1这么简单了（毕竟你把浏览器切到后台之后setTimeout就冷却了，等几秒后切回，然后执行setTimeout，只是减了一秒而已）。&lt;/p&gt;
&lt;p&gt;所以我们需要改写一下getTime方法。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
    还剩{{day}}天{{hours}}:{{mins}}:{{seconds}}
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
    days: &amp;#x27;0&amp;#x27;,
    hours: &amp;#x27;00&amp;#x27;,
    mins: &amp;#x27;00&amp;#x27;,
    seconds: &amp;#x27;00&amp;#x27;,
    timer: null,
    curTime: 0,&amp;#x2F;&amp;#x2F; 新增代码：
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  mounted() {
    this.countDown();
  },
  
  watch: {
    duration() {
      this.countDown();
    }
  },
  methods: {
    countDown() {
      &amp;#x2F;&amp;#x2F; 新增代码：
      this.curTime = Date.now();
      this.getTime(this.duration);
    },
    durationFormatter(){...}
    getTime(duration) {
      this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);
      if (duration &amp;lt; 0) {
        return;
      }
      const { dd, hh, mm, ss } = this.durationFormatter(duration);
      this.days = dd || 0;
      this.hours = hh || 0;
      this.mins = mm || 0;
      this.seconds = ss || 0;
      this.timer = setTimeout(() =&amp;gt; {
        &amp;#x2F;&amp;#x2F; 新增代码：
        const now = Date.now();
        const diffTime = Math.floor((now - this.curTime) &amp;#x2F; 1000);
        this.curTime = now;
        this.getTime(duration - diffTime);
      }, 1000);
    }
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到，我们在三个位置添加了新的代码。&lt;/p&gt;
&lt;p&gt;首先在data了添加了curTime这个变量，然后在执行countDown的时候给&lt;code&gt;curTime&lt;/code&gt;赋值&lt;code&gt;Date.now()&lt;/code&gt;，也就是当前的时刻，也就是显示在页面上的那个时刻。&lt;/p&gt;
&lt;p&gt;然后看修改的第三处代码。可以看到是将&lt;code&gt;-1&lt;/code&gt;改成了&lt;code&gt;-diffTime&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;now 是 setTimeout的回调函数执行的时候的那个时刻。&lt;/p&gt;
&lt;p&gt;因而 diffTime 则 表示 &lt;strong&gt;当前这个setTimeout的回调函数执行的时刻距离上 页面上的剩余时间上一次变化的时间段&lt;/strong&gt;。其实也就是 &lt;strong&gt;当前这个setTimeout的回调函数执行的时刻距离上 一个setTimeout的回调函数执行的时刻时间段。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可能你还是不太能理解diffTime。举个例子：&lt;/p&gt;
&lt;p&gt;你打开了这个倒计时页面，于是执行了countDown，也就是说要执行getTime这个方法了。也就是会马上执行下列的代码。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;执行完这些代码页面上就会出现剩余时间。&lt;/p&gt;
&lt;p&gt;而&lt;code&gt;this.curTime = Date.now();&lt;/code&gt; 就记录下了此刻的时间点。&lt;/p&gt;
&lt;p&gt;然后一秒后执行setTimeout里的回调函数：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;const now = Date.now();&lt;/code&gt; 记录当前这个setTimeout的回调函数执行的时间点。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;const diffTime = Math.floor((now - this.curTime) / 1000);&lt;/code&gt; 记录当前这个setTimeout的回调函数执行的时间点距离页面上开始 渲染 剩余时间的 这一段时间。其实此时的diffTime就是=1。&lt;/p&gt;
&lt;p&gt;然后&lt;code&gt;this.curTime = now;&lt;/code&gt; 将curTime的值变成当前这个setTimeout的回调函数执行的时间点。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;this.getTime(duration - diffTime);&lt;/code&gt; 其实就是&lt;code&gt;this.getTime(duration - 1);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;然后又执行getTime，就会重新执行下面的代码，有渲染了新的剩余时间。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后一秒后又要执行setTmieout的回调函数，在这一秒还没结束的时候，我们将浏览器切到后台，此时setTimeout冷却了。等5秒后再切回。于是setTmieout的回调函数才得以执行。&lt;/p&gt;
&lt;p&gt;这时&lt;code&gt;const now = Date.now();&lt;/code&gt; 记录当前这个setTimeout的回调函数执行的时间点。&lt;/p&gt;
&lt;p&gt;而curTime是上一个setTimeout的回调函数执行的时间。&lt;/p&gt;
&lt;p&gt;所以&lt;code&gt;const diffTime = Math.floor((now - this.curTime) / 1000);&lt;/code&gt;实际上，diffTime的值就是5秒。&lt;/p&gt;
&lt;p&gt;因而&lt;code&gt;this.getTime(duration - diffTime);&lt;/code&gt; 其实就是&lt;code&gt;this.getTime(duration - 5);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这样就完美解决了因为浏览器切到后台，导致剩余时间不变的问题。&lt;/p&gt;
&lt;h3&gt;6. 添加新功能：可以传入到期时间。&lt;/h3&gt;
&lt;p&gt;之前是只能传入剩余时间的，现在希望也支持传入到期时间。&lt;/p&gt;
&lt;p&gt;只需要改动一下duration就好了。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;  computed: {
    duration() {
      if (this.end) {
        let end = String(this.end).length &amp;gt;= 13 ? +this.end : +this.end * 1000;
        end -= Date.now();
        return end;
      }
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;判断传入的end的长度是否大于13来判断是秒还是毫秒。轻松！&lt;/p&gt;
&lt;h3&gt;7. 添加新功能：可以选择要显示的内容，例如只显示秒，或者只显示小时。&lt;/h3&gt;
&lt;p&gt;只需要改动一下html：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down no-rtl&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
      &amp;lt;slot v-bind=&amp;quot;{
        d: days, h: hours, m: mins, s: seconds,
        hh: &amp;#96;00${hours}&amp;#96;.slice(-2),
        mm: &amp;#96;00${mins}&amp;#96;.slice(-2),
        ss: &amp;#96;00${seconds}&amp;#96;.slice(-2),
      }&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;slot&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;很巧妙有没有，只需要用插槽，就把倒计时组件，也就是把子组件的值传递给父组件了。&lt;/p&gt;
&lt;p&gt;看看父组件是怎么使用这个组件的。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;base-counter v-slot=&amp;quot;timeObj&amp;quot; :time=&amp;quot;countDown&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;count-down&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;icon&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;div&amp;gt;
    {{timeObj.d}}天{{timeObj.hh}}小时{{timeObj.mm}}分钟{{timeObj.ss}}秒
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;base-counter&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;看，如此巧妙又简单。&lt;/p&gt;
&lt;p&gt;发现&lt;code&gt;00${hours}&lt;/code&gt;.slice(-2) 这种写法也很值得学习。以前在获得到分钟的时候，要手动判断获得的分钟是两位数还是一位数，如果是一位数的话就要在前面手动补上0。就像下面的代码：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;var StartMinute = startDate.getMinutes().toString().length &amp;gt;= 2 ? startDate.getMinutes() : &amp;#x27;0&amp;#x27; + startDate.getHours();
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而&lt;code&gt;00${hours}&lt;/code&gt;.slice(-2) 则不用判断，先补上0再说，然后再从后面往前截取两位。
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/72a2f7a957e64e9d96b00a420fa2725b~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;到此。&lt;/p&gt;
&lt;p&gt;一个完美的倒计时组件就完成了。
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f23f102146a1443db651239f5cba83df~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;h2&gt;三、学习总结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;明白了setInterval的缺点以及用setTimeout代替setInterval。&lt;/li&gt;
&lt;li&gt;学到了&lt;code&gt;“+”&lt;/code&gt;，操作，不管三七二十一，将接口得到的长串数字转化为数字保平安。&lt;/li&gt;
&lt;li&gt;利用clearTimeout来清除掉之前的计时器，以防止造成影响。&lt;/li&gt;
&lt;li&gt;学会使用v-slot来子传父传值&lt;/li&gt;
&lt;li&gt;学会一个倒计时组件，为了以后方便cv操作。把组件完整代码贴上：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down no-rtl&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
      &amp;lt;slot v-bind=&amp;quot;{
        d: days, h: hours, m: mins, s: seconds,
        hh: &amp;#96;00${hours}&amp;#96;.slice(-2),
        mm: &amp;#96;00${mins}&amp;#96;.slice(-2),
        ss: &amp;#96;00${seconds}&amp;#96;.slice(-2),
      }&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;slot&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
&amp;#x2F;* eslint-disable object-curly-newline *&amp;#x2F;

export default {
  data: () =&amp;gt; ({
    days: &amp;#x27;0&amp;#x27;,
    hours: &amp;#x27;00&amp;#x27;,
    mins: &amp;#x27;00&amp;#x27;,
    seconds: &amp;#x27;00&amp;#x27;,
    timer: null,
    curTime: 0
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    refreshCounter: {
      type: [Number, String],
      default: 0
    },
    end: {
      type: [Number, String],
      default: 0
    },
    isMiniSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      if (this.end) {
        let end = String(this.end).length &amp;gt;= 13 ? +this.end : +this.end * 1000;
        end -= Date.now();
        return end;
      }
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  mounted() {
    this.countDown();
  },
  watch: {
    duration() {
      this.countDown();
    },
    refreshCounter() {
      this.countDown();
    }
  },
  methods: {
    durationFormatter(time) {
      if (!time) return { ss: 0 };
      let t = time;
      const ss = t % 60;
      t = (t - ss) &amp;#x2F; 60;
      if (t &amp;lt; 1) return { ss };
      const mm = t % 60;
      t = (t - mm) &amp;#x2F; 60;
      if (t &amp;lt; 1) return { mm, ss };
      const hh = t % 24;
      t = (t - hh) &amp;#x2F; 24;
      if (t &amp;lt; 1) return { hh, mm, ss };
      const dd = t;
      return { dd, hh, mm, ss };
    },
    countDown() {
      &amp;#x2F;&amp;#x2F; eslint-disable-next-line no-unused-expressions
      this.curTime = Date.now();
      this.getTime(this.duration);
    },
    getTime(time) {
      &amp;#x2F;&amp;#x2F; eslint-disable-next-line no-unused-expressions
      this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);
      if (time &amp;lt; 0) {
        return;
      }
      &amp;#x2F;&amp;#x2F; eslint-disable-next-line object-curly-newline
      const { dd, hh, mm, ss } = this.durationFormatter(time);
      this.days = dd || 0;
      &amp;#x2F;&amp;#x2F; this.hours = &amp;#96;00${hh || &amp;#x27;&amp;#x27;}&amp;#96;.slice(-2);
      &amp;#x2F;&amp;#x2F; this.mins = &amp;#96;00${mm || &amp;#x27;&amp;#x27;}&amp;#96;.slice(-2);
      &amp;#x2F;&amp;#x2F; this.seconds = &amp;#96;00${ss || &amp;#x27;&amp;#x27;}&amp;#96;.slice(-2);
      this.hours = hh || 0;
      this.mins = mm || 0;
      this.seconds = ss || 0;
      this.timer = setTimeout(() =&amp;gt; {
        const now = Date.now();
        const diffTime = Math.floor((now - this.curTime) &amp;#x2F; 1000);
        const step = diffTime &amp;gt; 1 ? diffTime : 1; &amp;#x2F;&amp;#x2F; 页面退到后台的时候不会计时，对比时间差，大于1s的重置倒计时
        this.curTime = now;
        this.getTime(time - step);
      }, 1000);
    }
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;
@import &amp;#x27;~@assets&amp;#x2F;css&amp;#x2F;common.scss&amp;#x27;;

._base-count-down {
  color: #fff;
  text-align: left;
  position: relative;
  .content {
    width: auto;
    display: flex;
    align-items: center;
  }
  span {
    display: inline-block;
  }
  .section {
    position: relative;
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;你学到了什么，欢迎补充！！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;欢迎大家留言讨论，祝工作顺利、生活愉快！&lt;/p&gt;
&lt;p&gt;我是bigo前端，下期见。&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: bigo前端
        原文链接：&lt;a href=&quot;https://juejin.im/post/7038405108371030047&quot;&gt;https://juejin.im/post/7038405108371030047&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Fri, 14 Jan 2022 09:14:13 GMT</pubDate></item><item><title>Element-UI 奇淫技巧第二弹！提升开发效率，延长摸鱼时间~__Element__前端</title><link>http://vue-js.com/topic/61e13ee5ad745b003c9df704</link><guid>http://vue-js.com/topic/61e13ee5ad745b003c9df704</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: cyanosis
highlight: atom-one-light&lt;/h2&gt;
&lt;h3&gt;主题样式修改&lt;/h3&gt;
&lt;p&gt;曾经为了修改组件风格，一个项目用了上百个样式穿透，后来才发现定制一个主题就够了！&lt;/p&gt;
&lt;h4&gt;第一步，在官网的主题页面，修改背景色、字体颜色及边框颜色&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5389bfb8437f4671a1abcde50e5deccf~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;01.jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;第二步，下载主题&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f83bcb6472c644499dca3c17baa8d57a~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1639302082(1).jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;第三步，用下载的css文件替换掉默认的css文件&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7342d4f166f5486cabaed5ce16a45502~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h4&gt;效果如下：&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5f37ae06ca1f461088fff172422103f2~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;el-backtop 回到顶部组件&lt;/h3&gt;
&lt;p&gt;有时候页面很长，我们需要一个回到顶部的按钮，推荐大家使用 &lt;code&gt;el-backtop&lt;/code&gt;，自带过渡效果。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/12e604091c60439c8034fa6cb2dde77f~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;20211213_211233 00_00_00-00_00_30.gif&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;box&amp;quot;&amp;gt;Scroll down to see the bottom-right button.&amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;el-backtop target=&amp;quot;.container&amp;quot;&amp;gt;
      &amp;lt;i class=&amp;quot;el-icon-caret-top&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;i&amp;gt;
    &amp;lt;&amp;#x2F;el-backtop&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;style lang=&amp;quot;scss&amp;quot; scoped&amp;gt;
.container {
  height: 100vh;
  overflow-x: hidden;
  .box {
    padding: 20px;
    height: 1500px;
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;注意：需要将 body 的 margin 设置为 0，不然会出现两个滚动条。&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;el-form-item label宽度自适应&lt;/h3&gt;
&lt;p&gt;通常我们是给 &lt;code&gt;label&lt;/code&gt; 设置一个固定宽度，但这完全限制了 UI 的发挥，如何让 &lt;code&gt;label&lt;/code&gt; 宽度自适应呢？答案是给控件设置一个固定宽度。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aa1f4571719548e9b730f548ec1af57a~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;el-form&amp;gt;
    &amp;lt;el-form-item label=&amp;quot;教师&amp;quot;&amp;gt;
      &amp;lt;el-input v-model=&amp;quot;form.teacher&amp;quot; style=&amp;quot;width: 625px;&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-input&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
    &amp;lt;el-form-item label=&amp;quot;请选择在线教学平台名称&amp;quot;&amp;gt;
      &amp;lt;el-select v-model=&amp;quot;form.platform&amp;quot; style=&amp;quot;width: 500px;&amp;quot;&amp;gt;
        &amp;lt;el-option label=&amp;quot;平台一&amp;quot; value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
        &amp;lt;el-option label=&amp;quot;平台二&amp;quot; value=&amp;quot;2&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
      &amp;lt;&amp;#x2F;el-select&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
  &amp;lt;&amp;#x2F;el-form&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      form: {
        teacher: &amp;#x27;&amp;#x27;,
        platform: &amp;#x27;&amp;#x27;
      }
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-form-item 循环校验&lt;/h3&gt;
&lt;p&gt;有时候表单项是一个循环的列表，需要对每一项进行校验。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/09f36b9bde724160bf849cc7747c893e~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1639314939(1).jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;el-form :model=&amp;quot;form&amp;quot; label-width=&amp;quot;100px&amp;quot; ref=&amp;quot;form&amp;quot;&amp;gt;
      &amp;lt;el-form-item
        v-for=&amp;quot;(item, index) in form.list&amp;quot;
        :key=&amp;quot;index&amp;quot;
        :label=&amp;quot;&amp;#x27;评价项目&amp;#x27; + (index + 1)&amp;quot;
        :prop=&amp;quot;&amp;#x27;list.&amp;#x27; + index + &amp;#x27;.evaluateName&amp;#x27;&amp;quot;
        :rules=&amp;quot;rules.evaluateName&amp;quot;
      &amp;gt;
        &amp;lt;el-input v-model=&amp;quot;item.evaluateName&amp;quot; &amp;#x2F;&amp;gt;
      &amp;lt;&amp;#x2F;el-form-item&amp;gt;
      &amp;lt;el-form-item&amp;gt;
        &amp;lt;el-button type=&amp;quot;primary&amp;quot; @click=&amp;quot;submitForm&amp;quot;&amp;gt;提交&amp;lt;&amp;#x2F;el-button&amp;gt;
      &amp;lt;&amp;#x2F;el-form-item&amp;gt;
    &amp;lt;&amp;#x2F;el-form&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      form: {
        list: [
          {
            evaluateName: &amp;#x27;&amp;#x27;
          },
          {
            evaluateName: &amp;#x27;&amp;#x27;
          },
          {
            evaluateName: &amp;#x27;&amp;#x27;
          }
        ]
      },
      rules: {
        evaluateName: [
          {
            required: true,
            message: &amp;#x27;请输入评价项目&amp;#x27;,
            trigger: &amp;#x27;blur&amp;#x27;
          }
        ]
      }
    }
  },
  methods: {
    submitForm() {
      this.$refs.form.validate(valid =&amp;gt; {
        if (valid) {
          alert(&amp;#x27;submit!&amp;#x27;)
        } else {
          console.log(&amp;#x27;error submit!!&amp;#x27;)
          return false
        }
      })
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-form-item 表单嵌套表格校验&lt;/h3&gt;
&lt;p&gt;如果在表单里嵌套表格，那么如何对表格里面的控件进行校验呢？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2f61fb571bba4da18f14bec455d10162~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1639315824(1).jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;el-form :model=&amp;quot;form&amp;quot; ref=&amp;quot;form&amp;quot;&amp;gt;
    &amp;lt;el-table border :data=&amp;quot;form.list&amp;quot; style=&amp;quot;margin-bottom: 10px;&amp;quot;&amp;gt;
      &amp;lt;el-table-column
        label=&amp;quot;评价项目&amp;quot;
        prop=&amp;quot;evaluateName&amp;quot;
        align=&amp;quot;center&amp;quot;
      &amp;gt;&amp;lt;&amp;#x2F;el-table-column&amp;gt;
      &amp;lt;el-table-column label=&amp;quot;评价等级&amp;quot; align=&amp;quot;center&amp;quot;&amp;gt;
        &amp;lt;template slot-scope=&amp;quot;scope&amp;quot;&amp;gt;
          &amp;lt;el-form-item
            :prop=&amp;quot;&amp;#x27;list.&amp;#x27; + scope.$index + &amp;#x27;.evaluateLevel&amp;#x27;&amp;quot;
            :rules=&amp;quot;rules.evaluateLevel&amp;quot;
            class=&amp;quot;evaluate-level&amp;quot;
          &amp;gt;
            &amp;lt;el-radio-group class=&amp;quot;radio-group&amp;quot; v-model=&amp;quot;scope.row.evaluateLevel&amp;quot;&amp;gt;
              &amp;lt;el-radio
                v-for=&amp;quot;evaluationItem in evaluateOptions&amp;quot;
                :key=&amp;quot;evaluationItem.value&amp;quot;
                :label=&amp;quot;evaluationItem.value&amp;quot;
              &amp;gt;
                {{ evaluationItem.label }}
              &amp;lt;&amp;#x2F;el-radio&amp;gt;
            &amp;lt;&amp;#x2F;el-radio-group&amp;gt;
          &amp;lt;&amp;#x2F;el-form-item&amp;gt;
        &amp;lt;&amp;#x2F;template&amp;gt;
      &amp;lt;&amp;#x2F;el-table-column&amp;gt;
    &amp;lt;&amp;#x2F;el-table&amp;gt;
    &amp;lt;el-form-item&amp;gt;
      &amp;lt;el-button type=&amp;quot;primary&amp;quot; class=&amp;quot;submit-btn&amp;quot; @click=&amp;quot;submitOpinionCheck&amp;quot;&amp;gt;
        提交
      &amp;lt;&amp;#x2F;el-button&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
  &amp;lt;&amp;#x2F;el-form&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      form: {
        list: [
          {
            id: &amp;#x27;01&amp;#x27;,
            evaluateName: &amp;#x27;指标一&amp;#x27;,
            evaluateLevel: &amp;#x27;&amp;#x27;
          },
          {
            id: &amp;#x27;02&amp;#x27;,
            evaluateName: &amp;#x27;指标二&amp;#x27;,
            evaluateLevel: &amp;#x27;&amp;#x27;
          },
          {
            id: &amp;#x27;03&amp;#x27;,
            evaluateName: &amp;#x27;指标二&amp;#x27;,
            evaluateLevel: &amp;#x27;&amp;#x27;
          }
        ]
      },
      evaluateOptions: [
        {
          value: &amp;#x27;1&amp;#x27;,
          label: &amp;#x27;优&amp;#x27;
        },
        {
          value: &amp;#x27;2&amp;#x27;,
          label: &amp;#x27;良&amp;#x27;
        },
        {
          value: &amp;#x27;3&amp;#x27;,
          label: &amp;#x27;中&amp;#x27;
        },
        {
          value: &amp;#x27;4&amp;#x27;,
          label: &amp;#x27;差&amp;#x27;
        }
      ],
      rules: {
        evaluateLevel: [{ required: true, message: &amp;#x27;请选择评价等级&amp;#x27;, trigger: &amp;#x27;change&amp;#x27; }]
      }
    }
  },
  methods: {
    submitOpinionCheck() {
      this.$refs.form.validate(valid =&amp;gt; {
        if (valid) {
          alert(&amp;#x27;submit!&amp;#x27;)
        } else {
          console.log(&amp;#x27;error submit!!&amp;#x27;)
          return false
        }
      })
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;style lang=&amp;quot;scss&amp;quot; scoped&amp;gt;
.evaluate-level {
  ::v-deep .el-form-item__error {
    width: 100px;
    &amp;#x2F;* 使校验信息居中 *&amp;#x2F;
    left: calc(50% - 50px);
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-form-item 表单校验 label只保留 *&lt;/h3&gt;
&lt;p&gt;有时候我们只需要表单校验的 &lt;code&gt;*&lt;/code&gt;， 而不需要 lable 文字，像这样：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ae92a837ac804673a130dd41358653cf~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1639312147(1).jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;el-form :model=&amp;quot;ruleForm&amp;quot; :rules=&amp;quot;rules&amp;quot; ref=&amp;quot;ruleForm&amp;quot; label-width=&amp;quot;30px&amp;quot;&amp;gt;
    &amp;lt;el-form-item label=&amp;quot; &amp;quot; prop=&amp;quot;name&amp;quot;&amp;gt;
      &amp;lt;el-input v-model=&amp;quot;ruleForm.name&amp;quot; placeholder=&amp;quot;请输入活动名称&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-input&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
    &amp;lt;el-form-item label=&amp;quot; &amp;quot; prop=&amp;quot;region&amp;quot;&amp;gt;
      &amp;lt;el-select v-model=&amp;quot;ruleForm.region&amp;quot; placeholder=&amp;quot;请选择活动区域&amp;quot;&amp;gt;
        &amp;lt;el-option label=&amp;quot;区域一&amp;quot; value=&amp;quot;shanghai&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
        &amp;lt;el-option label=&amp;quot;区域二&amp;quot; value=&amp;quot;beijing&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
      &amp;lt;&amp;#x2F;el-select&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
    &amp;lt;el-form-item&amp;gt;
      &amp;lt;el-button type=&amp;quot;primary&amp;quot; @click=&amp;quot;submitForm(&amp;#x27;ruleForm&amp;#x27;)&amp;quot;&amp;gt;提交&amp;lt;&amp;#x2F;el-button&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
  &amp;lt;&amp;#x2F;el-form&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      ruleForm: {
        name: &amp;#x27;&amp;#x27;,
        region: &amp;#x27;&amp;#x27;
      },
      rules: {
        name: [{ required: true, message: &amp;#x27;请输入活动名称&amp;#x27;, trigger: &amp;#x27;blur&amp;#x27; }],
        region: [{ required: true, message: &amp;#x27;请选择活动区域&amp;#x27;, trigger: &amp;#x27;change&amp;#x27; }]
      }
    }
  },
  methods: {
    submitForm(formName) {
      this.$refs[formName].validate(valid =&amp;gt; {
        if (valid) {
          alert(&amp;#x27;submit!&amp;#x27;)
        } else {
          console.log(&amp;#x27;error submit!!&amp;#x27;)
          return false
        }
      })
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-select 下拉框样式修改&lt;/h3&gt;
&lt;p&gt;使用样式穿透修改下拉框样式，你会发现打死都不生效，那是因为下拉框是默认挂载在 body 下面。解决办法：设置 &lt;code&gt;:popper-append-to-body=&amp;quot;false&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/74f10805b5824ca2819709e561451281~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;el-form&amp;gt;
    &amp;lt;el-form-item label=&amp;quot;活动区域&amp;quot;&amp;gt;
      &amp;lt;el-select
        class=&amp;quot;form-select&amp;quot;
        :popper-append-to-body=&amp;quot;false&amp;quot;
        v-model=&amp;quot;form.region&amp;quot;
        placeholder=&amp;quot;请选择活动区域&amp;quot;
      &amp;gt;
        &amp;lt;el-option label=&amp;quot;区域一&amp;quot; value=&amp;quot;shanghai&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
        &amp;lt;el-option label=&amp;quot;区域二&amp;quot; value=&amp;quot;beijing&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
      &amp;lt;&amp;#x2F;el-select&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
  &amp;lt;&amp;#x2F;el-form&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      form: {
        region: &amp;#x27;&amp;#x27;
      }
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;style lang=&amp;quot;scss&amp;quot; scoped&amp;gt;
.form-select {
  ::v-deep .el-select-dropdown__item.selected {
    background: pink;
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-input 打开弹框 自动聚焦&lt;/h3&gt;
&lt;p&gt;很多时候 form 表单是嵌套在 dialog 下面，为了提高用户体验，需要在打开弹框后 input 框自动聚焦。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a32cde61fe7a4e61bae715783be56c13~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;20211213_205355 00_00_00-00_00_30.gif&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;el-button type=&amp;quot;text&amp;quot; @click=&amp;quot;showDialog&amp;quot;&amp;gt;
      打开嵌套表单的 Dialog
    &amp;lt;&amp;#x2F;el-button&amp;gt;

    &amp;lt;el-dialog title=&amp;quot;活动&amp;quot; :visible.sync=&amp;quot;dialogVisible&amp;quot;&amp;gt;
      &amp;lt;el-form :model=&amp;quot;form&amp;quot;&amp;gt;
        &amp;lt;el-form-item label=&amp;quot;活动名称&amp;quot; label-width=&amp;quot;80px&amp;quot;&amp;gt;
          &amp;lt;el-input ref=&amp;quot;nameInput&amp;quot; v-model=&amp;quot;form.name&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-input&amp;gt;
        &amp;lt;&amp;#x2F;el-form-item&amp;gt;
        &amp;lt;el-form-item label=&amp;quot;活动内容&amp;quot; label-width=&amp;quot;80px&amp;quot;&amp;gt;
          &amp;lt;el-input v-model=&amp;quot;form.content&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-input&amp;gt;
        &amp;lt;&amp;#x2F;el-form-item&amp;gt;
      &amp;lt;&amp;#x2F;el-form&amp;gt;
      &amp;lt;div slot=&amp;quot;footer&amp;quot; class=&amp;quot;dialog-footer&amp;quot;&amp;gt;
        &amp;lt;el-button @click=&amp;quot;dialogVisible = false&amp;quot;&amp;gt;取 消&amp;lt;&amp;#x2F;el-button&amp;gt;
        &amp;lt;el-button type=&amp;quot;primary&amp;quot; @click=&amp;quot;dialogVisible = false&amp;quot;&amp;gt;确 定&amp;lt;&amp;#x2F;el-button&amp;gt;
      &amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;&amp;#x2F;el-dialog&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      dialogVisible: false,
      form: {
        name: &amp;#x27;&amp;#x27;,
        content: &amp;#x27;&amp;#x27;
      }
    }
  },
  methods: {
    showDialog() {
      this.dialogVisible = true
      &amp;#x2F;&amp;#x2F; 如果弹框显示，则自动聚焦
      this.$nextTick(() =&amp;gt; {
        this.$refs.nameInput.focus()
      })
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;今天的分享就到到这里，希望对你有所帮助！如果觉得有用，不要忘了点个赞呦~&lt;/p&gt;
&lt;p&gt;听说喜欢点赞的你，今年年终奖拿到手软&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 前端阿飞
        原文链接：&lt;a href=&quot;https://juejin.im/post/7041320033884045326&quot;&gt;https://juejin.im/post/7041320033884045326&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Fri, 14 Jan 2022 09:14:13 GMT</pubDate></item><item><title>就因为JSON.stringify，我的年终奖差点打水漂了__JavaScript__Vue.js__前端</title><link>http://vue-js.com/topic/61e13ee5cbbfd1003b11f98a</link><guid>http://vue-js.com/topic/61e13ee5cbbfd1003b11f98a</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: juejin&lt;/h2&gt;
&lt;p&gt;本文已参与 &lt;a href=&quot;https://juejin.cn/post/7012210233804079141&quot; title=&quot;https://juejin.cn/post/7012210233804079141&quot;&gt;「掘力星计划」&lt;/a&gt; ，赢取创作大礼包，挑战创作激励金。&lt;/p&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;「欢迎在评论区讨论，掘金官方将在&lt;a href=&quot;https://juejin.cn/post/7012210233804079141&quot; title=&quot;https://juejin.cn/post/7012210233804079141&quot;&gt;掘力星计划&lt;/a&gt;活动结束后，在评论区抽送100份掘金周边，抽奖详情见活动文章」&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;开发要对线上环境有一颗敬畏之心，任何一个点都有可能导致线上故障，也有可能让你的年终奖泡汤(⊙︿⊙)。比如使用了&lt;code&gt;JSON.stringify&lt;/code&gt;，这个无比熟悉但又无比陌生的API。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;看完本文您可以收获：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;了解一个差点让我年终奖打水漂的悲伤的故事o(╥﹏╥)o&lt;/li&gt;
&lt;li&gt;学习JSON.stringify的9大特性和转换规则 &lt;strong&gt;(重点)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;了解如何判断一个对象是否存在循环引用 &lt;strong&gt;(重点)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;从零开始手写一个JSON.stringify &lt;strong&gt;(重点)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;等等&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;说一个悲伤的故事&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;最近组内有个小伙伴离职了，他所负责的一块业务由我去维护，结果刚接手，代码还没捂热乎，差点背上p0的锅。请让我花一点时间和你说清楚来龙去脉。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;悲伤伊始&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;这一天&lt;strong&gt;胖头鱼&lt;/strong&gt;正徜徉在代码的海洋不可自拔，突然被拉进了一个线上问题排查群，群里不可谓不热闹。
&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e2a584a72ebc4440b53f8b34d943e391~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;产品同学&lt;/strong&gt;在诉苦：线上用户不能提交表单了，带来了好多客诉，估计会是p0故障，希望尽快解决。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;测试同学&lt;/strong&gt;在纳闷：这个场景测试和预发环境明明验过的，怎么线上就不行了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;后端同学&lt;/strong&gt;在讲原因：接口缺少了value字段，导致出错了。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;就是木有人说问题怎么解决!!!&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;就是木有人说问题怎么解决!!!&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;就是木有人说问题怎么解决!!!&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这样的场景不知道你是不是也似曾相识呢？o(╥﹏╥)o，不管咋说第一要务还是先把线上问题解决掉，减少持续影响，赶紧把交接的代码翻出来，开始了排查过程。&lt;/p&gt;
&lt;h3&gt;问题原因&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;如下图：有这样一个动态表单搜集页面，用户选择或者填写了信息之后（&lt;code&gt;各字段非必填情况下也可以直接提交&lt;/code&gt;），接着前端把数据发送给后端，结束，看起来没有多复杂的逻辑。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a48b74e1bad74c2588047e8ffbe8825f~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;直接错误原因&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;非必填情况下，signInfo字段中经过&lt;code&gt;JSON.stringify&lt;/code&gt;后的字符串对象缺少&lt;code&gt;value&lt;/code&gt; key，导致后端parse之后无法正确读取value值，进而报接口系统异常，用户无法进行下一步动作。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 异常入参数据，数组字符串中没有value key
{
  signInfo: &amp;#x27;[{&amp;quot;fieldId&amp;quot;:539},{&amp;quot;fieldId&amp;quot;:540},{&amp;quot;fieldId&amp;quot;:546,&amp;quot;value&amp;quot;:&amp;quot;10:30&amp;quot;}]&amp;#x27;
}

&amp;#x2F;&amp;#x2F; 正常入参数据
{
  signInfo: &amp;#x27;[{&amp;quot;fieldId&amp;quot;:539,&amp;quot;value&amp;quot;:&amp;quot;银卡&amp;quot;},{&amp;quot;fieldId&amp;quot;:540,&amp;quot;value&amp;quot;:&amp;quot;2021-03-01&amp;quot;},{&amp;quot;fieldId&amp;quot;:546,&amp;quot;value&amp;quot;:&amp;quot;10:30&amp;quot;}]&amp;#x27;
}

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;异常数据是如何产生的&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 默认情况下数据是这样的
let signInfo = [
  {
    fieldId: 539,
    value: undefined
  },
  {
    fieldId: 540,
    value: undefined
  },
  {
    fieldId: 546,
    value: undefined
  },
]
&amp;#x2F;&amp;#x2F; 经过JSON.stringify之后的数据,少了value key,导致后端无法读取value值进行报错
&amp;#x2F;&amp;#x2F; 具体原因是&amp;#96;undefined&amp;#96;、&amp;#96;任意的函数&amp;#96;以及&amp;#96;symbol值&amp;#96;，出现在&amp;#96;非数组对象&amp;#96;的属性值中时在序列化过程中会被忽略
console.log(JSON.stringify(signInfo))
&amp;#x2F;&amp;#x2F; &amp;#x27;[{&amp;quot;fieldId&amp;quot;:539},{&amp;quot;fieldId&amp;quot;:540},{&amp;quot;fieldId&amp;quot;:546}]&amp;#x27;

&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;问题的原因找到了，解决方式 &lt;strong&gt;(这里只讲前端的解决方案，当然也可以由后端解决)&lt;/strong&gt; 也很简单，将value值为undefined的项转化为空字符串再提交即可。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;方案一：新开一个对象处理&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;let signInfo = [
  {
    fieldId: 539,
    value: undefined
  },
  {
    fieldId: 540,
    value: undefined
  },
  {
    fieldId: 546,
    value: undefined
  },
]

let newSignInfo = signInfo.map((it) =&amp;gt; {
  const value = typeof it.value === &amp;#x27;undefined&amp;#x27; ? &amp;#x27;&amp;#x27; : it.value
  return {
    ...it,
    value
  }
})

console.log(JSON.stringify(newSignInfo))
&amp;#x2F;&amp;#x2F; &amp;#x27;[{&amp;quot;fieldId&amp;quot;:539,&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;},{&amp;quot;fieldId&amp;quot;:540,&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;},{&amp;quot;fieldId&amp;quot;:546,&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;}]&amp;#x27;

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;方案二：利用&lt;code&gt;JSON.stringify&lt;/code&gt;第二个参数，直接处理&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;方案一的缺陷是需要新开一个对象进行一顿操作才能解决，&lt;code&gt;不够优雅&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;let signInfo = [
  {
    fieldId: 539,
    value: undefined
  },
  {
    fieldId: 540,
    value: undefined
  },
  {
    fieldId: 546,
    value: undefined
  },
]

&amp;#x2F;&amp;#x2F; 判断到value为undefined，返回空字符串即可
JSON.stringify(signInfo, (key, value) =&amp;gt; typeof value === &amp;#x27;undefined&amp;#x27; ? &amp;#x27;&amp;#x27; : value)
&amp;#x2F;&amp;#x2F; &amp;#x27;[{&amp;quot;fieldId&amp;quot;:539,&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;},{&amp;quot;fieldId&amp;quot;:540,&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;},{&amp;quot;fieldId&amp;quot;:546,&amp;quot;value&amp;quot;:&amp;quot;&amp;quot;}]&amp;#x27;

&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;故事后续&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;原本这是一个已经上线有一段时间的页面，为何会突然出现这个问题，之前却没有呢？仔细询问下，原来是中途产品同学提了一个小的优化点，离职的小伙伴感觉点比较小直接就改了代码上线了，未曾想出现了线上问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;后面针对这件事从产品到测试、到后端、到前端单独做了一个完整的复盘，细节就不再展开说了。&lt;/p&gt;
&lt;p&gt;因为从发现问题到解决问题速度较快、影响用户数较少，还未达到问责程度，&lt;strong&gt;俺的年终奖可算是保住了o(╥﹏╥)o。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;重学JSON.stringify&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;经过这件事情，我觉得有必要重新审视一下&lt;code&gt;JSON.stringify&lt;/code&gt;这个方法，彻底搞清楚转换规则，并尝试手写实现一个&lt;code&gt;JSON.stringify&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果你曾遇到和我一样的问题，欢迎一起来重新学习一次，一定会有不一样的收获噢！&lt;/p&gt;
&lt;h2&gt;学透JSON.stringify&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;JSON.stringify()&lt;/code&gt; 方法将一个 &lt;code&gt;JavaScript 对象&lt;/code&gt;或&lt;code&gt;值&lt;/code&gt;转换为 JSON 字符串，如果指定了一个 replacer 函数，则可以选择性地替换值，或者指定的 replacer 是数组，则可选择性地仅包含数组指定的属性。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以下信息来自&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#syntax&quot;&gt;MDN&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;语法&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;JSON.stringify(value[, replacer [, space]])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#parameters&quot; title=&quot;Permalink to 参数&quot;&gt;参数&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;value&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;将要序列化成 一个 JSON 字符串的值。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;replacer&lt;/code&gt; 可选&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果该参数是一个函数，则在序列化过程中，被序列化的值的每个属性都会经过该函数的转换和处理；&lt;/li&gt;
&lt;li&gt;如果该参数是一个数组，则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中；&lt;/li&gt;
&lt;li&gt;如果该参数为 null 或者未提供，则对象所有的属性都会被序列化。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;space&lt;/code&gt; 可选&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;指定缩进用的空白字符串，用于美化输出（pretty-print）；&lt;/li&gt;
&lt;li&gt;如果参数是个数字，它代表有多少的空格；上限为10。&lt;/li&gt;
&lt;li&gt;该值若小于1，则意味着没有空格；&lt;/li&gt;
&lt;li&gt;如果该参数为字符串（当字符串长度超过10个字母，取其前10个字母），该字符串将被作为空格；&lt;/li&gt;
&lt;li&gt;如果该参数没有提供（或者为 null），将没有空格。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;返回值&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;一个表示给定值的JSON字符串。
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#%E5%BC%82%E5%B8%B8&quot; title=&quot;Permalink to 异常&quot;&gt;异常&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当在循环引用时会抛出异常&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypeError&quot;&gt;&lt;code&gt;TypeError&lt;/code&gt;&lt;/a&gt; (“cyclic object value”)（循环对象值）&lt;/li&gt;
&lt;li&gt;当尝试去转换 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt&quot;&gt;&lt;code&gt;BigInt&lt;/code&gt;&lt;/a&gt; 类型的值会抛出&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/TypeError&quot;&gt;&lt;code&gt;TypeError&lt;/code&gt;&lt;/a&gt; (“BigInt value can’t be serialized in JSON”)（BigInt值不能JSON序列化）.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;基本使用&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;JSON.stringify可以转换对象或者值（平常用的更多的是转换对象）&lt;/li&gt;
&lt;li&gt;可以指定&lt;code&gt;replacer&lt;/code&gt;为函数选择性的地替换&lt;/li&gt;
&lt;li&gt;也可以指定&lt;code&gt;replacer&lt;/code&gt;为数组，可转换指定的属性&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这里仅仅是&lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify&quot;&gt;MDN&lt;/a&gt;上关于&lt;code&gt;JSON.stringify&lt;/code&gt;其中最基础的说明，咱们先打个码试试这几个特性&lt;/p&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 1. 转换对象
console.log(JSON.stringify({ name: &amp;#x27;前端胖头鱼&amp;#x27;, sex: &amp;#x27;boy&amp;#x27; })) &amp;#x2F;&amp;#x2F; &amp;#x27;{&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;sex&amp;quot;:&amp;quot;boy&amp;quot;}&amp;#x27;

&amp;#x2F;&amp;#x2F; 2. 转换普通值
console.log(JSON.stringify(&amp;#x27;前端胖头鱼&amp;#x27;)) &amp;#x2F;&amp;#x2F; &amp;quot;前端胖头鱼&amp;quot;
console.log(JSON.stringify(1)) &amp;#x2F;&amp;#x2F; &amp;quot;1&amp;quot;
console.log(JSON.stringify(true)) &amp;#x2F;&amp;#x2F; &amp;quot;true&amp;quot;
console.log(JSON.stringify(null)) &amp;#x2F;&amp;#x2F; &amp;quot;null&amp;quot;

&amp;#x2F;&amp;#x2F; 3. 指定replacer函数
console.log(JSON.stringify({ name: &amp;#x27;前端胖头鱼&amp;#x27;, sex: &amp;#x27;boy&amp;#x27;, age: 100 }, (key, value) =&amp;gt; {
  return typeof value === &amp;#x27;number&amp;#x27; ? undefined : value
}))
&amp;#x2F;&amp;#x2F; &amp;#x27;{&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;sex&amp;quot;:&amp;quot;boy&amp;quot;}&amp;#x27;

&amp;#x2F;&amp;#x2F; 4. 指定数组
console.log(JSON.stringify({ name: &amp;#x27;前端胖头鱼&amp;#x27;, sex: &amp;#x27;boy&amp;#x27;, age: 100 }, [ &amp;#x27;name&amp;#x27; ]))
&amp;#x2F;&amp;#x2F; &amp;#x27;{&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;}&amp;#x27;

&amp;#x2F;&amp;#x2F; 5. 指定space(美化输出)
console.log(JSON.stringify({ name: &amp;#x27;前端胖头鱼&amp;#x27;, sex: &amp;#x27;boy&amp;#x27;, age: 100 }))
&amp;#x2F;&amp;#x2F; &amp;#x27;{&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;sex&amp;quot;:&amp;quot;boy&amp;quot;,&amp;quot;age&amp;quot;:100}&amp;#x27;
console.log(JSON.stringify({ name: &amp;#x27;前端胖头鱼&amp;#x27;, sex: &amp;#x27;boy&amp;#x27;, age: 100 }, null , 2))
&amp;#x2F;*
{
  &amp;quot;name&amp;quot;: &amp;quot;前端胖头鱼&amp;quot;,
  &amp;quot;sex&amp;quot;: &amp;quot;boy&amp;quot;,
  &amp;quot;age&amp;quot;: 100
}
*&amp;#x2F;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;9大特性要记住&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;以前仅仅是使用了这个方法，却没有详细了解他的转换规则，居然有9个之多。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;特性一&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;undefined&lt;/code&gt;、&lt;code&gt;任意的函数&lt;/code&gt;以及&lt;code&gt;symbol值&lt;/code&gt;，出现在&lt;code&gt;非数组对象&lt;/code&gt;的属性值中时在序列化过程中会被忽略&lt;/li&gt;
&lt;li&gt;&lt;code&gt;undefined&lt;/code&gt;、&lt;code&gt;任意的函数&lt;/code&gt;以及&lt;code&gt;symbol值&lt;/code&gt;出现在&lt;code&gt;数组&lt;/code&gt;中时会被转换成 &lt;code&gt;null&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;undefined&lt;/code&gt;、&lt;code&gt;任意的函数&lt;/code&gt;以及&lt;code&gt;symbol值&lt;/code&gt;被&lt;code&gt;单独转换&lt;/code&gt;时，会返回 undefined&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 1. 对象中存在这三种值会被忽略
console.log(JSON.stringify({
  name: &amp;#x27;前端胖头鱼&amp;#x27;,
  sex: &amp;#x27;boy&amp;#x27;,
  &amp;#x2F;&amp;#x2F; 函数会被忽略
  showName () {
    console.log(&amp;#x27;前端胖头鱼&amp;#x27;)
  },
  &amp;#x2F;&amp;#x2F; undefined会被忽略
  age: undefined,
  &amp;#x2F;&amp;#x2F; Symbol会被忽略
  symbolName: Symbol(&amp;#x27;前端胖头鱼&amp;#x27;)
}))
&amp;#x2F;&amp;#x2F; &amp;#x27;{&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;sex&amp;quot;:&amp;quot;boy&amp;quot;}&amp;#x27;

&amp;#x2F;&amp;#x2F; 2. 数组中存在着三种值会被转化为null
console.log(JSON.stringify([
  &amp;#x27;前端胖头鱼&amp;#x27;,
  &amp;#x27;boy&amp;#x27;,
  &amp;#x2F;&amp;#x2F; 函数会被转化为null
  function showName () {
    console.log(&amp;#x27;前端胖头鱼&amp;#x27;)
  },
  &amp;#x2F;&amp;#x2F;undefined会被转化为null
  undefined,
  &amp;#x2F;&amp;#x2F;Symbol会被转化为null
  Symbol(&amp;#x27;前端胖头鱼&amp;#x27;)
]))
&amp;#x2F;&amp;#x2F; &amp;#x27;[&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;boy&amp;quot;,null,null,null]&amp;#x27;

&amp;#x2F;&amp;#x2F; 3.单独转换会返回undefined
console.log(JSON.stringify(
  function showName () {
    console.log(&amp;#x27;前端胖头鱼&amp;#x27;)
  }
)) &amp;#x2F;&amp;#x2F; undefined
console.log(JSON.stringify(undefined)) &amp;#x2F;&amp;#x2F; undefined
console.log(JSON.stringify(Symbol(&amp;#x27;前端胖头鱼&amp;#x27;))) &amp;#x2F;&amp;#x2F; undefined

&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性二&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;布尔值&lt;/code&gt;、&lt;code&gt;数字&lt;/code&gt;、&lt;code&gt;字符串&lt;/code&gt;的包装对象在序列化过程中会自动转换成对应的原始值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;console.log(JSON.stringify([new Number(1), new String(&amp;quot;前端胖头鱼&amp;quot;), new Boolean(false)]))
&amp;#x2F;&amp;#x2F; &amp;#x27;[1,&amp;quot;前端胖头鱼&amp;quot;,false]&amp;#x27;
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性三&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;所有以&lt;code&gt;symbol&lt;/code&gt;为属性键的属性都会被完全忽略掉，即便 &lt;code&gt;replacer&lt;/code&gt; 参数中强制指定包含了它们。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;console.log(JSON.stringify({
  [Symbol(&amp;#x27;前端胖头鱼&amp;#x27;)]: &amp;#x27;前端胖头鱼&amp;#x27;}
)) 
&amp;#x2F;&amp;#x2F; &amp;#x27;{}&amp;#x27;
console.log(JSON.stringify({
  [ Symbol(&amp;#x27;前端胖头鱼&amp;#x27;) ]: &amp;#x27;前端胖头鱼&amp;#x27;,
}, (key, value) =&amp;gt; {
  if (typeof key === &amp;#x27;symbol&amp;#x27;) {
    return value
  }
}))
&amp;#x2F;&amp;#x2F; undefined
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性四&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;NaN 和 Infinity 格式的数值及 null 都会被当做 null。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;console.log(JSON.stringify({
  age: NaN,
  age2: Infinity,
  name: null
}))
&amp;#x2F;&amp;#x2F; &amp;#x27;{&amp;quot;age&amp;quot;:null,&amp;quot;age2&amp;quot;:null,&amp;quot;name&amp;quot;:null}&amp;#x27;

&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性五&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;转换值如果有 toJSON() 方法，该方法定义什么值将被序列化。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;const toJSONObj = {
  name: &amp;#x27;前端胖头鱼&amp;#x27;,
  toJSON () {
    return &amp;#x27;JSON.stringify&amp;#x27;
  }
}

console.log(JSON.stringify(toJSONObj))
&amp;#x2F;&amp;#x2F; &amp;quot;JSON.stringify&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性六&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Date 日期调用了 toJSON() 将其转换为了 string 字符串（同Date.toISOString()），因此会被当做字符串处理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;const d = new Date()

console.log(d.toJSON()) &amp;#x2F;&amp;#x2F; 2021-10-05T14:01:23.932Z
console.log(JSON.stringify(d)) &amp;#x2F;&amp;#x2F; &amp;quot;2021-10-05T14:01:23.932Z&amp;quot;

&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性七&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;对包含循环引用的对象（对象之间相互引用，形成无限循环）执行此方法，会抛出错误。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;let cyclicObj = {
  name: &amp;#x27;前端胖头鱼&amp;#x27;,
}

cyclicObj.obj = cyclicObj

console.log(JSON.stringify(cyclicObj))
&amp;#x2F;&amp;#x2F; Converting circular structure to JSON
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性八&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;其他类型的对象，包括 Map/Set/WeakMap/WeakSet，仅会序列化可枚举的属性&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;let enumerableObj = {}

Object.defineProperties(enumerableObj, {
  name: {
    value: &amp;#x27;前端胖头鱼&amp;#x27;,
    enumerable: true
  },
  sex: {
    value: &amp;#x27;boy&amp;#x27;,
    enumerable: false
  },
})

console.log(JSON.stringify(enumerableObj))
&amp;#x2F;&amp;#x2F; &amp;#x27;{&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;}&amp;#x27;
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;特性九&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;当尝试去转换 &lt;a href=&quot;https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/BigInt&quot;&gt;&lt;code&gt;BigInt&lt;/code&gt;&lt;/a&gt; 类型的值会抛出错误&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;const alsoHuge = BigInt(9007199254740991)

console.log(JSON.stringify(alsoHuge))
&amp;#x2F;&amp;#x2F; TypeError: Do not know how to serialize a BigInt
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;手写一个JSON.stringify&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;终于重新学完&lt;code&gt;JSON.stringify&lt;/code&gt;的众多特性啦！咱们根据这些特性来手写一个简单版本的吧（&lt;strong&gt;无replacer函数和space&lt;/strong&gt;）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;源码实现&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;const jsonstringify = (data) =&amp;gt; {
  &amp;#x2F;&amp;#x2F; 确认一个对象是否存在循环引用
  const isCyclic = (obj) =&amp;gt; {
  &amp;#x2F;&amp;#x2F; 使用Set数据类型来存储已经检测过的对象
  let stackSet = new Set()
  let detected = false

  const detect = (obj) =&amp;gt; {
    &amp;#x2F;&amp;#x2F; 不是对象类型的话，可以直接跳过
    if (obj &amp;amp;&amp;amp; typeof obj != &amp;#x27;object&amp;#x27;) {
      return
    }
    &amp;#x2F;&amp;#x2F; 当要检查的对象已经存在于stackSet中时，表示存在循环引用
    if (stackSet.has(obj)) {
      return detected = true
    }
    &amp;#x2F;&amp;#x2F; 将当前obj存如stackSet
    stackSet.add(obj)

    for (let key in obj) {
      &amp;#x2F;&amp;#x2F; 对obj下的属性进行挨个检测
      if (obj.hasOwnProperty(key)) {
        detect(obj[key])
      }
    }
    &amp;#x2F;&amp;#x2F; 平级检测完成之后，将当前对象删除，防止误判
    &amp;#x2F;*
      例如：对象的属性指向同一引用，如果不删除的话，会被认为是循环引用
      let tempObj = {
        name: &amp;#x27;前端胖头鱼&amp;#x27;
      }
      let obj4 = {
        obj1: tempObj,
        obj2: tempObj
      }
    *&amp;#x2F;
    stackSet.delete(obj)
  }

  detect(obj)

  return detected
}

  &amp;#x2F;&amp;#x2F; 特性七:
  &amp;#x2F;&amp;#x2F; 对包含循环引用的对象（对象之间相互引用，形成无限循环）执行此方法，会抛出错误。
  if (isCyclic(data)) {
    throw new TypeError(&amp;#x27;Converting circular structure to JSON&amp;#x27;)
  }

  &amp;#x2F;&amp;#x2F; 特性九:
  &amp;#x2F;&amp;#x2F; 当尝试去转换 BigInt 类型的值会抛出错误
  if (typeof data === &amp;#x27;bigint&amp;#x27;) {
    throw new TypeError(&amp;#x27;Do not know how to serialize a BigInt&amp;#x27;)
  }

  const type = typeof data
  const commonKeys1 = [&amp;#x27;undefined&amp;#x27;, &amp;#x27;function&amp;#x27;, &amp;#x27;symbol&amp;#x27;]
  const getType = (s) =&amp;gt; {
    return Object.prototype.toString.call(s).replace(&amp;#x2F;&amp;#x5C;[object (.*?)&amp;#x5C;]&amp;#x2F;, &amp;#x27;$1&amp;#x27;).toLowerCase()
  }

  &amp;#x2F;&amp;#x2F; 非对象
  if (type !== &amp;#x27;object&amp;#x27; || data === null) {
    let result = data
    &amp;#x2F;&amp;#x2F; 特性四：
    &amp;#x2F;&amp;#x2F; NaN 和 Infinity 格式的数值及 null 都会被当做 null。
    if ([NaN, Infinity, null].includes(data)) {
      result = &amp;#x27;null&amp;#x27;
      &amp;#x2F;&amp;#x2F; 特性一：
      &amp;#x2F;&amp;#x2F; &amp;#96;undefined&amp;#96;、&amp;#96;任意的函数&amp;#96;以及&amp;#96;symbol值&amp;#96;被&amp;#96;单独转换&amp;#96;时，会返回 undefined
    } else if (commonKeys1.includes(type)) {
      &amp;#x2F;&amp;#x2F; 直接得到undefined，并不是一个字符串&amp;#x27;undefined&amp;#x27;
      return undefined
    } else if (type === &amp;#x27;string&amp;#x27;) {
      result = &amp;#x27;&amp;quot;&amp;#x27; + data + &amp;#x27;&amp;quot;&amp;#x27;
    }

    return String(result)
  } else if (type === &amp;#x27;object&amp;#x27;) {
    &amp;#x2F;&amp;#x2F; 特性五:
    &amp;#x2F;&amp;#x2F; 转换值如果有 toJSON() 方法，该方法定义什么值将被序列化
    &amp;#x2F;&amp;#x2F; 特性六:
    &amp;#x2F;&amp;#x2F; Date 日期调用了 toJSON() 将其转换为了 string 字符串（同Date.toISOString()），因此会被当做字符串处理。
    if (typeof data.toJSON === &amp;#x27;function&amp;#x27;) {
      return jsonstringify(data.toJSON())
    } else if (Array.isArray(data)) {
      let result = data.map((it) =&amp;gt; {
        &amp;#x2F;&amp;#x2F; 特性一:
        &amp;#x2F;&amp;#x2F; &amp;#96;undefined&amp;#96;、&amp;#96;任意的函数&amp;#96;以及&amp;#96;symbol值&amp;#96;出现在&amp;#96;数组&amp;#96;中时会被转换成 &amp;#96;null&amp;#96;
        return commonKeys1.includes(typeof it) ? &amp;#x27;null&amp;#x27; : jsonstringify(it)
      })

      return &amp;#96;[${result}]&amp;#96;.replace(&amp;#x2F;&amp;#x27;&amp;#x2F;g, &amp;#x27;&amp;quot;&amp;#x27;)
    } else {
      &amp;#x2F;&amp;#x2F; 特性二：
      &amp;#x2F;&amp;#x2F; 布尔值、数字、字符串的包装对象在序列化过程中会自动转换成对应的原始值。
      if ([&amp;#x27;boolean&amp;#x27;, &amp;#x27;number&amp;#x27;].includes(getType(data))) {
        return String(data)
      } else if (getType(data) === &amp;#x27;string&amp;#x27;) {
        return &amp;#x27;&amp;quot;&amp;#x27; + data + &amp;#x27;&amp;quot;&amp;#x27;
      } else {
        let result = []
        &amp;#x2F;&amp;#x2F; 特性八
        &amp;#x2F;&amp;#x2F; 其他类型的对象，包括 Map&amp;#x2F;Set&amp;#x2F;WeakMap&amp;#x2F;WeakSet，仅会序列化可枚举的属性
        Object.keys(data).forEach((key) =&amp;gt; {
          &amp;#x2F;&amp;#x2F; 特性三:
          &amp;#x2F;&amp;#x2F; 所有以symbol为属性键的属性都会被完全忽略掉，即便 replacer 参数中强制指定包含了它们。
          if (typeof key !== &amp;#x27;symbol&amp;#x27;) {
            const value = data[key]
            &amp;#x2F;&amp;#x2F; 特性一
            &amp;#x2F;&amp;#x2F; &amp;#96;undefined&amp;#96;、&amp;#96;任意的函数&amp;#96;以及&amp;#96;symbol值&amp;#96;，出现在&amp;#96;非数组对象&amp;#96;的属性值中时在序列化过程中会被忽略
            if (!commonKeys1.includes(typeof value)) {
              result.push(&amp;#96;&amp;quot;${key}&amp;quot;:${jsonstringify(value)}&amp;#96;)
            }
          }
        })

        return &amp;#96;{${result}}&amp;#96;.replace(&amp;#x2F;&amp;#x27;&amp;#x2F;, &amp;#x27;&amp;quot;&amp;#x27;)
      }
    }
  }
}


&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;测试一把&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language- javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 1. 测试一下基本输出
console.log(jsonstringify(undefined)) &amp;#x2F;&amp;#x2F; undefined 
console.log(jsonstringify(() =&amp;gt; { })) &amp;#x2F;&amp;#x2F; undefined
console.log(jsonstringify(Symbol(&amp;#x27;前端胖头鱼&amp;#x27;))) &amp;#x2F;&amp;#x2F; undefined
console.log(jsonstringify((NaN))) &amp;#x2F;&amp;#x2F; null
console.log(jsonstringify((Infinity))) &amp;#x2F;&amp;#x2F; null
console.log(jsonstringify((null))) &amp;#x2F;&amp;#x2F; null
console.log(jsonstringify({
  name: &amp;#x27;前端胖头鱼&amp;#x27;,
  toJSON() {
    return {
      name: &amp;#x27;前端胖头鱼2&amp;#x27;,
      sex: &amp;#x27;boy&amp;#x27;
    }
  }
}))
&amp;#x2F;&amp;#x2F; {&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼2&amp;quot;,&amp;quot;sex&amp;quot;:&amp;quot;boy&amp;quot;}

&amp;#x2F;&amp;#x2F; 2. 和原生的JSON.stringify转换进行比较
console.log(jsonstringify(null) === JSON.stringify(null));
&amp;#x2F;&amp;#x2F; true
console.log(jsonstringify(undefined) === JSON.stringify(undefined));
&amp;#x2F;&amp;#x2F; true
console.log(jsonstringify(false) === JSON.stringify(false));
&amp;#x2F;&amp;#x2F; true
console.log(jsonstringify(NaN) === JSON.stringify(NaN));
&amp;#x2F;&amp;#x2F; true
console.log(jsonstringify(Infinity) === JSON.stringify(Infinity));
&amp;#x2F;&amp;#x2F; true
let str = &amp;quot;前端胖头鱼&amp;quot;;
console.log(jsonstringify(str) === JSON.stringify(str));
&amp;#x2F;&amp;#x2F; true
let reg = new RegExp(&amp;quot;&amp;#x5C;w&amp;quot;);
console.log(jsonstringify(reg) === JSON.stringify(reg));
&amp;#x2F;&amp;#x2F; true
let date = new Date();
console.log(jsonstringify(date) === JSON.stringify(date));
&amp;#x2F;&amp;#x2F; true
let sym = Symbol(&amp;#x27;前端胖头鱼&amp;#x27;);
console.log(jsonstringify(sym) === JSON.stringify(sym));
&amp;#x2F;&amp;#x2F; true
let array = [1, 2, 3];
console.log(jsonstringify(array) === JSON.stringify(array));
&amp;#x2F;&amp;#x2F; true
let obj = {
  name: &amp;#x27;前端胖头鱼&amp;#x27;,
  age: 18,
  attr: [&amp;#x27;coding&amp;#x27;, 123],
  date: new Date(),
  uni: Symbol(2),
  sayHi: function () {
    console.log(&amp;quot;hello world&amp;quot;)
  },
  info: {
    age: 16,
    intro: {
      money: undefined,
      job: null
    }
  },
  pakingObj: {
    boolean: new Boolean(false),
    string: new String(&amp;#x27;前端胖头鱼&amp;#x27;),
    number: new Number(1),
  }
}
console.log(jsonstringify(obj) === JSON.stringify(obj)) 
&amp;#x2F;&amp;#x2F; true
console.log((jsonstringify(obj)))
&amp;#x2F;&amp;#x2F; {&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;age&amp;quot;:18,&amp;quot;attr&amp;quot;:[&amp;quot;coding&amp;quot;,123],&amp;quot;date&amp;quot;:&amp;quot;2021-10-06T14:59:58.306Z&amp;quot;,&amp;quot;info&amp;quot;:{&amp;quot;age&amp;quot;:16,&amp;quot;intro&amp;quot;:{&amp;quot;job&amp;quot;:null}},&amp;quot;pakingObj&amp;quot;:{&amp;quot;boolean&amp;quot;:false,&amp;quot;string&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;number&amp;quot;:1}}
console.log(JSON.stringify(obj))
&amp;#x2F;&amp;#x2F; {&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;age&amp;quot;:18,&amp;quot;attr&amp;quot;:[&amp;quot;coding&amp;quot;,123],&amp;quot;date&amp;quot;:&amp;quot;2021-10-06T14:59:58.306Z&amp;quot;,&amp;quot;info&amp;quot;:{&amp;quot;age&amp;quot;:16,&amp;quot;intro&amp;quot;:{&amp;quot;job&amp;quot;:null}},&amp;quot;pakingObj&amp;quot;:{&amp;quot;boolean&amp;quot;:false,&amp;quot;string&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;,&amp;quot;number&amp;quot;:1}}

&amp;#x2F;&amp;#x2F; 3. 测试可遍历对象
let enumerableObj = {}

Object.defineProperties(enumerableObj, {
  name: {
    value: &amp;#x27;前端胖头鱼&amp;#x27;,
    enumerable: true
  },
  sex: {
    value: &amp;#x27;boy&amp;#x27;,
    enumerable: false
  },
})

console.log(jsonstringify(enumerableObj))
&amp;#x2F;&amp;#x2F; {&amp;quot;name&amp;quot;:&amp;quot;前端胖头鱼&amp;quot;}

&amp;#x2F;&amp;#x2F; 4. 测试循环引用和Bigint

let obj1 = { a: &amp;#x27;aa&amp;#x27; }
let obj2 = { name: &amp;#x27;前端胖头鱼&amp;#x27;, a: obj1, b: obj1 }
obj2.obj = obj2

console.log(jsonstringify(obj2))
&amp;#x2F;&amp;#x2F; TypeError: Converting circular structure to JSON
console.log(jsonStringify(BigInt(1)))
&amp;#x2F;&amp;#x2F; TypeError: Do not know how to serialize a BigInt

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;通过上面测试可以看出，&lt;code&gt;jsonstringify&lt;/code&gt;基本和&lt;code&gt;JSON.stringify&lt;/code&gt;表现一致，（也有可能测试用例不够全面，欢迎提出一起学习）&lt;/p&gt;
&lt;h2&gt;结尾&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;因为一个BUG，重学了&lt;code&gt;JSON.stringify&lt;/code&gt;,了解到原来它还有这么多平时没有注意到特性，前端娱乐圈水太深了，愿大家都被温柔以待，少些bug，多些关怀。晚安&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 前端胖头鱼
        原文链接：&lt;a href=&quot;https://juejin.im/post/7017588385615200270&quot;&gt;https://juejin.im/post/7017588385615200270&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Fri, 14 Jan 2022 09:14:13 GMT</pubDate></item><item><title>浅析我们为什么要上Vue3？__前端__Vue.js</title><link>http://vue-js.com/topic/61e13ee5ad745b003c9df703</link><guid>http://vue-js.com/topic/61e13ee5ad745b003c9df703</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: vue-pro
highlight: a11y-dark&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;专栏地址：&lt;a href=&quot;https://juejin.cn/column/7007233236925415432&quot;&gt;Vue 源码解析与实践&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本文：&lt;a href=&quot;https://juejin.cn/user/870468940732983&quot;&gt;我是970&lt;/a&gt;&lt;br&gt;
原文链接：&lt;a href=&quot;https://juejin.cn/post/7040769191393099783&quot;&gt;https://juejin.cn/post/7040769191393099783&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;✨ 开篇&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;RT，我们为什么要上 Vue3？使用 Vue3 影响我开法拉利吗？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最近在 Vue3 发布了&lt;code&gt;3.2&lt;/code&gt; 大版本之后，掘金上关于 Vue3 的文章越来越多，本来想着让子弹再飞一会，但最近公司上了 Vue3 的项目，自己也跟着学了起来。&lt;/p&gt;
&lt;p&gt;昨天还是 Vue2 的 Options API 的忠实信徒，结果今天搞了 Vue3 的 Composition API 之后，直呼 Vue3 真香！&lt;/p&gt;
&lt;p&gt;接下来，我们从分析 Vue2 优缺点入手，以及结合图片和用例来了解 Vue3 的优势。&lt;/p&gt;
&lt;h2&gt;Vue2 ⚔ Vue3&lt;/h2&gt;
&lt;h3&gt;Vue2&lt;/h3&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;p&gt;不可否认 Vue2 在取得的成功，在 Github 的 Front-End 分类的排行榜上也能看到 Vue 仓库的排名是第一，粉丝足够多，活跃用户足够多&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a8dace12e30945d181ec50023345c6cb~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/46751d216b4847e48714d649f02cd97a~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;比 React 足足多了 12k+ star&lt;/p&gt;
&lt;p&gt;从我的角度来看，最大的功臣莫过于这三个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;响应式数据&lt;/li&gt;
&lt;li&gt;Options API&lt;/li&gt;
&lt;li&gt;SFC&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除去这三位大将，不可或缺 &lt;code&gt;Vuex&lt;/code&gt;/&lt;code&gt;Vue-Router&lt;/code&gt; 这两位功臣，以及丰富的周边插件和活跃的社区。&lt;/p&gt;
&lt;h4&gt;缺点&lt;/h4&gt;
&lt;p&gt;在一个组件仅承担单一逻辑的时候，使用 Options API 来书写组件是很清晰的。&lt;/p&gt;
&lt;p&gt;但是在我们实际的业务场景中，一个父组件总是要包含多个子组件，父组件需要给子组件传值、处理子组件事件、直接操作子组件以及处理各种各样的调接口的逻辑，这时候我们的父组件的逻辑就会变得复杂。&lt;/p&gt;
&lt;p&gt;我们从代码维护者的角度出发，假设这个时候有 10 个函数方法，自然我们要把他们放入到&lt;code&gt;methods&lt;/code&gt;中，而这个 10 个函数方法又分别操作 10 个数据，这个 10 个数据又分别需要进行&lt;code&gt;Watch&lt;/code&gt;操作。&lt;/p&gt;
&lt;p&gt;这时候，我们根据&lt;code&gt;Vue2&lt;/code&gt;的&lt;code&gt;Options API&lt;/code&gt;的写法，就写完了 10 个&lt;code&gt;method&lt;/code&gt;、10 个&lt;code&gt;data&lt;/code&gt;、10 个&lt;code&gt;watch&lt;/code&gt;，我们就将本来 10 个的函数方法，分割在 30 个不同的地方。&lt;/p&gt;
&lt;p&gt;这时候父组件的代码，对代码维护者来说是很不友好的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可能有人说，这么写可以增加代码量，老板夸我牛逼！哈哈哈哈 &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Vue3&lt;/h3&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;自由，自由，还是 TM 的自由！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;更强的性能，更好的 tree shaking&lt;/li&gt;
&lt;li&gt;Composition API + setup&lt;/li&gt;
&lt;li&gt;更好地支持 TypeScript&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;可能的缺点&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Vue3: 让你自由过了火&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;使用&lt;code&gt;Composition API&lt;/code&gt;在&lt;code&gt;setup&lt;/code&gt;这个舞台上尽情的表演之后，可能存在一个问题：那就是如何优雅地组织代码？&lt;/p&gt;
&lt;p&gt;代码不能优雅的组织，在代码量上去之后，也一样很难维护。&lt;/p&gt;
&lt;h3&gt;SFC 写法变化&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/19a5449cbc1b45f4b6bb8941fe940180~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;Options API对比Composition API.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Vue2 一个普通的 Vue 文件&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;{{ person.name }}&amp;lt;&amp;#x2F;p&amp;gt;
    &amp;lt;p&amp;gt;{{ car.name }}&amp;lt;&amp;#x2F;p&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: &amp;quot;Person&amp;quot;,

  data() {
    return {
      person: {
        name: &amp;quot;小明&amp;quot;,
        sex: &amp;quot;male&amp;quot;,
      },
      car: {
        name: &amp;quot;宝马&amp;quot;,
        price: &amp;quot;40w&amp;quot;,
      }
    };
  },

  watch:{
      &amp;#x27;person.name&amp;#x27;: (value) =&amp;gt; {
          console.log(&amp;#96;名字被修改了, 修改为 ${value}&amp;#96;)
      },
      &amp;#x27;person.sex&amp;#x27;: (value) =&amp;gt; {
          console.log(&amp;#96;性别被修改了, 修改为 ${value}&amp;#96;)
      }
  },

  methods: {
    changePersonName() {
      this.person.name = &amp;quot;小浪&amp;quot;;
    },

    changeCarPrice() {
      this.car.price = &amp;quot;80w&amp;quot;;
    }
  },
};
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;采用 Vue3 Composition API 进行重写&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;p&amp;gt;{{ person.name }}&amp;lt;&amp;#x2F;p&amp;gt;
  &amp;lt;p&amp;gt;{{ car.name }}&amp;lt;&amp;#x2F;p&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script lang=&amp;quot;ts&amp;quot; setup&amp;gt;
import { reactive, watch } from &amp;quot;vue&amp;quot;;

&amp;#x2F;&amp;#x2F; person的逻辑
const person = reactive&amp;lt;{ name: string; sex: string }&amp;gt;({
  name: &amp;quot;小明&amp;quot;,
  sex: &amp;quot;male&amp;quot;,
});
watch(
  () =&amp;gt; [person.name, person.sex],
  ([nameVal, sexVal]) =&amp;gt; {
    console.log(&amp;#96;名字被修改了, 修改为 ${nameVal}&amp;#96;);
    console.log(&amp;#96;名字被修改了, 修改为 ${sexVal}&amp;#96;);
  }
);
function changePersonName() {
  person.name = &amp;quot;小浪&amp;quot;;
}

&amp;#x2F;&amp;#x2F; car的逻辑
const car = reactive&amp;lt;{ name: string; price: string }&amp;gt;({
  name: &amp;quot;宝马&amp;quot;,
  price: &amp;quot;40w&amp;quot;,
});
function changeCarPrice() {
  car.price = &amp;quot;80w&amp;quot;;
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;采用 Vue3 自定义 Hook 的方式，进一步拆分&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;p&amp;gt;{{ person.name }}&amp;lt;&amp;#x2F;p&amp;gt;
  &amp;lt;p&amp;gt;{{ car.name }}&amp;lt;&amp;#x2F;p&amp;gt;
  &amp;lt;p&amp;gt;{{ animal.name }}&amp;lt;&amp;#x2F;p&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script lang=&amp;quot;ts&amp;quot; setup&amp;gt;
import { usePerson, useCar, useAnimal } from &amp;quot;.&amp;#x2F;hooks&amp;quot;;

const { person, changePersonName } = usePerson();

const { car } = useCar();
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-ts&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; usePerson.ts
import { reactive, watch } from &amp;quot;vue&amp;quot;;

export default function usePerson() {
  const person = reactive&amp;lt;{ name: string; sex: string }&amp;gt;({
    name: &amp;quot;小明&amp;quot;,
    sex: &amp;quot;male&amp;quot;,
  });
  watch(
    () =&amp;gt; [person.name, person.sex],
    ([nameVal, sexVal]) =&amp;gt; {
      console.log(&amp;#96;名字被修改了, 修改为 ${nameVal}&amp;#96;);
      console.log(&amp;#96;名字被修改了, 修改为 ${sexVal}&amp;#96;);
    }
  );
  function changePersonName() {
    person.name = &amp;quot;小浪&amp;quot;;
  }
  return {
    person,
    changePersonName,
  };
}
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-ts&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; useCar.ts
import { reactive } from &amp;quot;vue&amp;quot;;

export default function useCar() {
  const car = reactive&amp;lt;{ name: string; price: string }&amp;gt;({
    name: &amp;quot;宝马&amp;quot;,
    price: &amp;quot;40w&amp;quot;,
  });
  function changeCarPrice() {
    car.price = &amp;quot;80w&amp;quot;;
  }
  return {
    car,
    changeCarPrice,
  };
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对比完之后，我们会发现，Vue3 可以让我们更好组织代码。&lt;code&gt;person&lt;/code&gt;和&lt;code&gt;car&lt;/code&gt;的逻辑都被单独放置在一块&lt;/p&gt;
&lt;p&gt;仅仅是代码组织的优势吗？不不不，我们再看看模板中的一些变化&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;标签中起始便签可以不用&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;标签，因为&lt;code&gt;Vue3&lt;/code&gt;提供了&lt;code&gt;片段&lt;/code&gt;的能力，使用&lt;code&gt;Vue-detools&lt;/code&gt;中查看，可以看到有&lt;code&gt;fragment&lt;/code&gt;的标记&lt;br&gt;
&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e2d30c1c9db648bea38eeecb4c7d75f1~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Watch&lt;/code&gt;也不需要罗列多个了，&lt;code&gt;Vue3&lt;/code&gt;中支持侦听多个源&lt;/li&gt;
&lt;li&gt;很自然的使用&lt;code&gt;TypeScript&lt;/code&gt;，类型约束更加简单&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;性能方面&lt;/h3&gt;
&lt;p&gt;Vue3 主要在这几个方面进行了提升&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;编译阶段。对 diff 算法优化、静态提升等等&lt;/li&gt;
&lt;li&gt;响应式系统。&lt;code&gt;Proxy()&lt;/code&gt;替代&lt;code&gt;Object.defineProperty()&lt;/code&gt;监听对象。监听一个对象，不需要再深度遍历，&lt;code&gt;Proxy()&lt;/code&gt;就可以劫持整个对象&lt;/li&gt;
&lt;li&gt;体积包减少。Compostion API 的写法，可以更好的进行 tree shaking，减少上下文没有引入的代码，减少打包后的文件体积&lt;/li&gt;
&lt;li&gt;新增&lt;code&gt;片段&lt;/code&gt;特性。Vue 文件的&lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;标签内，不再需要强制声明一个的&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;标签，节省额外的节点开销&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt; 总结&lt;/h2&gt;
&lt;p&gt;Vue3 的优点和新特性还有很多很多，本篇文章主要从&lt;code&gt;Vue2/3版本的优缺点&lt;/code&gt;和&lt;code&gt;SFC写法变化&lt;/code&gt;这两个角度介入，给大家介绍我们为什么要上 Vue3。&lt;/p&gt;
&lt;p&gt;Vue3 很香，更好地支持 TypeScript，让 Vue3 成为了企业级 Vue 项目的首选，各种新特性也迎合前端技术的发展趋势。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ie 浏览器：那我走？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt; 最后&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;翻译翻译什么是 Vue3 ？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果这篇文章对你有帮助，那麻烦你动动小手点个赞 ，你的每一个点赞都是我继续创作的莫大鼓励！&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://vue3js.cn/interview/vue3/performance.html#%E4%B8%80%E3%80%81%E7%BC%96%E8%AF%91%E9%98%B6%E6%AE%B5&quot; title=&quot;面试官：Vue3.0性能提升主要是通过哪几方面体现的？&quot;&gt;面试官：Vue3.0 性能提升主要是通过哪几方面体现的？&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6903171037211557895&quot; title=&quot;Vue3教程：Vue 3.x快在哪里？&quot;&gt;Vue3 教程：Vue 3.x 快在哪里？&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;其他&lt;/h2&gt;
&lt;p&gt;掘金年度人气创作者打榜最后一天啦，欢迎大家为我们团队投票~ &lt;a href=&quot;https://rank.juejin.cn/rank/2021/3104676565234429?t=team&amp;amp;utm_campaign=annual_2021&amp;amp;utm_medium=self_web_share&amp;amp;utm_source=IDuxFE&quot;&gt;IDuxFE@深信服前端开发团队&lt;/a&gt;
感谢大家的支持~&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: IDuxFE
        原文链接：&lt;a href=&quot;https://juejin.im/post/7046922345864232974&quot;&gt;https://juejin.im/post/7046922345864232974&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Fri, 14 Jan 2022 09:14:13 GMT</pubDate></item><item><title>后端一次给你10万条数据，如何优雅展示，到底考察我什么?__前端__Vue.js__面试</title><link>http://vue-js.com/topic/61e13ee5cbbfd1003b11f989</link><guid>http://vue-js.com/topic/61e13ee5cbbfd1003b11f989</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: smartblue&lt;/h2&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;大家好，我是林三心，&lt;strong&gt;用最通俗的话讲最难的知识点&lt;/strong&gt;是我的座右铭，&lt;strong&gt;基础是进阶的前提&lt;/strong&gt;是我的初心，今天跟大家来唠唠嗑，如果后端真的返回给前端10万条数据，咱们前端要怎么优雅地展示出来呢？（哈哈假设后端真的能传10万条数据到前端）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4cc34033138345488065107afcc4adf1~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;前置工作&lt;/h2&gt;
&lt;p&gt;先把前置工作给做好，后面才能进行测试&lt;/p&gt;
&lt;h3&gt;后端搭建&lt;/h3&gt;
&lt;p&gt;新建一个&lt;code&gt;server.js&lt;/code&gt;文件，简单起个服务，并返回给前端&lt;code&gt;10w&lt;/code&gt;条数据，并通过&lt;code&gt;nodemon server.js&lt;/code&gt;开启服务&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;没有安装&lt;code&gt;nodemon&lt;/code&gt;的同学可以先全局安装&lt;code&gt;npm i nodemon -g&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; server.js

const http = require(&amp;#x27;http&amp;#x27;)
const port = 8000;

http.createServer(function (req, res) {
  &amp;#x2F;&amp;#x2F; 开启Cors
  res.writeHead(200, {
    &amp;#x2F;&amp;#x2F;设置允许跨域的域名，也可设置*允许所有域名
    &amp;#x27;Access-Control-Allow-Origin&amp;#x27;: &amp;#x27;*&amp;#x27;,
    &amp;#x2F;&amp;#x2F;跨域允许的请求方法，也可设置*允许所有方法
    &amp;quot;Access-Control-Allow-Methods&amp;quot;: &amp;quot;DELETE,PUT,POST,GET,OPTIONS&amp;quot;,
    &amp;#x2F;&amp;#x2F;允许的header类型
    &amp;#x27;Access-Control-Allow-Headers&amp;#x27;: &amp;#x27;Content-Type&amp;#x27;
  })
  let list = []
  let num = 0

  &amp;#x2F;&amp;#x2F; 生成10万条数据的list
  for (let i = 0; i &amp;lt; 100000; i++) {
    num++
    list.push({
      src: &amp;#x27;https:&amp;#x2F;&amp;#x2F;p3-passport.byteacctimg.com&amp;#x2F;img&amp;#x2F;user-avatar&amp;#x2F;d71c38d1682c543b33f8d716b3b734ca~300x300.image&amp;#x27;,
      text: &amp;#96;我是${num}号嘉宾林三心&amp;#96;,
      tid: num
    })
  }
  res.end(JSON.stringify(list));
}).listen(port, function () {
  console.log(&amp;#x27;server is listening on port &amp;#x27; + port);
})
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;前端页面&lt;/h3&gt;
&lt;p&gt;先新建一个&lt;code&gt;index.html&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; index.html

&amp;#x2F;&amp;#x2F; 样式
&amp;lt;style&amp;gt;
    * {
      padding: 0;
      margin: 0;
    }
    #container {
      height: 100vh;
      overflow: auto;
    }
    .sunshine {
      display: flex;
      padding: 10px;
    }
    img {
      width: 150px;
      height: 150px;
    }
  &amp;lt;&amp;#x2F;style&amp;gt;

&amp;#x2F;&amp;#x2F; html部分
&amp;lt;body&amp;gt;
  &amp;lt;div id=&amp;quot;container&amp;quot;&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;script src=&amp;quot;.&amp;#x2F;index.js&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;&amp;#x2F;body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后新建一个&lt;code&gt;index.js&lt;/code&gt;文件，封装一个&lt;code&gt;AJAX&lt;/code&gt;函数，用来请求这&lt;code&gt;10w&lt;/code&gt;条数据&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; index.js

&amp;#x2F;&amp;#x2F; 请求函数
const getList = () =&amp;gt; {
    return new Promise((resolve, reject) =&amp;gt; {
        &amp;#x2F;&amp;#x2F;步骤一:创建异步对象
        var ajax = new XMLHttpRequest();
        &amp;#x2F;&amp;#x2F;步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数
        ajax.open(&amp;#x27;get&amp;#x27;, &amp;#x27;http:&amp;#x2F;&amp;#x2F;127.0.0.1:8000&amp;#x27;);
        &amp;#x2F;&amp;#x2F;步骤三:发送请求
        ajax.send();
        &amp;#x2F;&amp;#x2F;步骤四:注册事件 onreadystatechange 状态改变就会调用
        ajax.onreadystatechange = function () {
            if (ajax.readyState == 4 &amp;amp;&amp;amp; ajax.status == 200) {
                &amp;#x2F;&amp;#x2F;步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的
                resolve(JSON.parse(ajax.responseText))
            }
        }
    })
}

&amp;#x2F;&amp;#x2F; 获取container对象
const container = document.getElementById(&amp;#x27;container&amp;#x27;)
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;直接渲染&lt;/h2&gt;
&lt;p&gt;最直接的方式就是直接渲染出来，但是这样的做法肯定是不可取的，因为一次性渲染出&lt;code&gt;10w&lt;/code&gt;个节点，是非常耗时间的，咱们可以来看一下耗时，差不多要消耗&lt;code&gt;12秒&lt;/code&gt;，非常消耗时间&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9396a045360744e7b73dd626a1456bcd~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;截屏2021-11-18 下午10.07.45.png&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const renderList = async () =&amp;gt; {
    console.time(&amp;#x27;列表时间&amp;#x27;)
    const list = await getList()
    list.forEach(item =&amp;gt; {
        const div = document.createElement(&amp;#x27;div&amp;#x27;)
        div.className = &amp;#x27;sunshine&amp;#x27;
        div.innerHTML = &amp;#96;&amp;lt;img src=&amp;quot;${item.src}&amp;quot; &amp;#x2F;&amp;gt;&amp;lt;span&amp;gt;${item.text}&amp;lt;&amp;#x2F;span&amp;gt;&amp;#96;
        container.appendChild(div)
    })
    console.timeEnd(&amp;#x27;列表时间&amp;#x27;)
}
renderList()
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;setTimeout分页渲染&lt;/h2&gt;
&lt;p&gt;这个方法就是，把&lt;code&gt;10w&lt;/code&gt;按照每页数量&lt;code&gt;limit&lt;/code&gt;分成总共&lt;code&gt;Math.ceil(total / limit)&lt;/code&gt;页，然后利用&lt;code&gt;setTimeout&lt;/code&gt;，每次渲染1页数据，这样的话，渲染出首页数据的时间大大缩减了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/faeac54d664a40ab97ac75325e0984ff~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;截屏2021-11-18 下午10.14.46.png&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const renderList = async () =&amp;gt; {
    console.time(&amp;#x27;列表时间&amp;#x27;)
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total &amp;#x2F; limit)

    const render = (page) =&amp;gt; {
        if (page &amp;gt;= totalPage) return
        setTimeout(() =&amp;gt; {
            for (let i = page * limit; i &amp;lt; page * limit + limit; i++) {
                const item = list[i]
                const div = document.createElement(&amp;#x27;div&amp;#x27;)
                div.className = &amp;#x27;sunshine&amp;#x27;
                div.innerHTML = &amp;#96;&amp;lt;img src=&amp;quot;${item.src}&amp;quot; &amp;#x2F;&amp;gt;&amp;lt;span&amp;gt;${item.text}&amp;lt;&amp;#x2F;span&amp;gt;&amp;#96;
                container.appendChild(div)
            }
            render(page + 1)
        }, 0)
    }
    render(page)
    console.timeEnd(&amp;#x27;列表时间&amp;#x27;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;requestAnimationFrame&lt;/h2&gt;
&lt;p&gt;使用&lt;code&gt;requestAnimationFrame&lt;/code&gt;代替&lt;code&gt;setTimeout&lt;/code&gt;，减少了&lt;code&gt;重排&lt;/code&gt;的次数，极大提高了性能，建议大家在渲染方面多使用&lt;code&gt;requestAnimationFrame&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const renderList = async () =&amp;gt; {
    console.time(&amp;#x27;列表时间&amp;#x27;)
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total &amp;#x2F; limit)

    const render = (page) =&amp;gt; {
        if (page &amp;gt;= totalPage) return
        &amp;#x2F;&amp;#x2F; 使用requestAnimationFrame代替setTimeout
        requestAnimationFrame(() =&amp;gt; {
            for (let i = page * limit; i &amp;lt; page * limit + limit; i++) {
                const item = list[i]
                const div = document.createElement(&amp;#x27;div&amp;#x27;)
                div.className = &amp;#x27;sunshine&amp;#x27;
                div.innerHTML = &amp;#96;&amp;lt;img src=&amp;quot;${item.src}&amp;quot; &amp;#x2F;&amp;gt;&amp;lt;span&amp;gt;${item.text}&amp;lt;&amp;#x2F;span&amp;gt;&amp;#96;
                container.appendChild(div)
            }
            render(page + 1)
        })
    }
    render(page)
    console.timeEnd(&amp;#x27;列表时间&amp;#x27;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;文档碎片 + requestAnimationFrame&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;文档碎片&lt;/code&gt;的好处&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1、之前都是每次创建一个&lt;code&gt;div&lt;/code&gt;标签就&lt;code&gt;appendChild&lt;/code&gt;一次，但是有了&lt;code&gt;文档碎片&lt;/code&gt;可以先把1页的&lt;code&gt;div&lt;/code&gt;标签先放进&lt;code&gt;文档碎片&lt;/code&gt;中，然后一次性&lt;code&gt;appendChild&lt;/code&gt;到&lt;code&gt;container&lt;/code&gt;中，这样减少了&lt;code&gt;appendChild&lt;/code&gt;的次数，极大提高了性能&lt;/li&gt;
&lt;li&gt;2、页面只会渲染&lt;code&gt;文档碎片&lt;/code&gt;包裹着的元素，而不会渲染&lt;code&gt;文档碎片&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const renderList = async () =&amp;gt; {
    console.time(&amp;#x27;列表时间&amp;#x27;)
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total &amp;#x2F; limit)

    const render = (page) =&amp;gt; {
        if (page &amp;gt;= totalPage) return
        requestAnimationFrame(() =&amp;gt; {
            &amp;#x2F;&amp;#x2F; 创建一个文档碎片
            const fragment = document.createDocumentFragment()
            for (let i = page * limit; i &amp;lt; page * limit + limit; i++) {
                const item = list[i]
                const div = document.createElement(&amp;#x27;div&amp;#x27;)
                div.className = &amp;#x27;sunshine&amp;#x27;
                div.innerHTML = &amp;#96;&amp;lt;img src=&amp;quot;${item.src}&amp;quot; &amp;#x2F;&amp;gt;&amp;lt;span&amp;gt;${item.text}&amp;lt;&amp;#x2F;span&amp;gt;&amp;#96;
                &amp;#x2F;&amp;#x2F; 先塞进文档碎片
                fragment.appendChild(div)
            }
            &amp;#x2F;&amp;#x2F; 一次性appendChild
            container.appendChild(fragment)
            render(page + 1)
        })
    }
    render(page)
    console.timeEnd(&amp;#x27;列表时间&amp;#x27;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;懒加载&lt;/h2&gt;
&lt;p&gt;为了比较通俗的讲解，咱们启动一个&lt;code&gt;vue&lt;/code&gt;前端项目，后端服务还是开着&lt;/p&gt;
&lt;p&gt;其实实现原理很简单，咱们通过一张图来展示，就是在列表尾部放一个空节点&lt;code&gt;blank&lt;/code&gt;，然后先渲染第1页数据，向上滚动，等到&lt;code&gt;blank&lt;/code&gt;出现在视图中，就说明到底了，这时候再加载第二页，往后以此类推。&lt;/p&gt;
&lt;p&gt;至于怎么判断&lt;code&gt;blank&lt;/code&gt;出现在视图上，可以使用&lt;code&gt;getBoundingClientRect&lt;/code&gt;方法获取&lt;code&gt;top&lt;/code&gt;属性&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;IntersectionObserver&lt;/code&gt; 性能更好，但是我这里就拿&lt;code&gt;getBoundingClientRect&lt;/code&gt;来举例&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/af9e2974b1d84d3c852c0e7f606794cd~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;截屏2021-11-18 下午10.41.01.png&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;script setup lang=&amp;quot;ts&amp;quot;&amp;gt;
import { onMounted, ref, computed } from &amp;#x27;vue&amp;#x27;
const getList = () =&amp;gt; {
  &amp;#x2F;&amp;#x2F; 跟上面一样的代码
}

const container = ref&amp;lt;HTMLElement&amp;gt;() &amp;#x2F;&amp;#x2F; container节点
const blank = ref&amp;lt;HTMLElement&amp;gt;() &amp;#x2F;&amp;#x2F; blank节点
const list = ref&amp;lt;any&amp;gt;([]) &amp;#x2F;&amp;#x2F; 列表
const page = ref(1) &amp;#x2F;&amp;#x2F; 当前页数
const limit = 200 &amp;#x2F;&amp;#x2F; 一页展示
&amp;#x2F;&amp;#x2F; 最大页数
const maxPage = computed(() =&amp;gt; Math.ceil(list.value.length &amp;#x2F; limit))
&amp;#x2F;&amp;#x2F; 真实展示的列表
const showList = computed(() =&amp;gt; list.value.slice(0, page.value * limit))
const handleScroll = () =&amp;gt; {
  &amp;#x2F;&amp;#x2F; 当前页数与最大页数的比较
  if (page.value &amp;gt; maxPage.value) return
  const clientHeight = container.value?.clientHeight
  const blankTop = blank.value?.getBoundingClientRect().top
  if (clientHeight === blankTop) {
    &amp;#x2F;&amp;#x2F; blank出现在视图，则当前页数加1
    page.value++
  }
}

onMounted(async () =&amp;gt; {
  const res = await getList()
  list.value = res
})
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div id=&amp;quot;container&amp;quot; @scroll=&amp;quot;handleScroll&amp;quot; ref=&amp;quot;container&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;sunshine&amp;quot; v-for=&amp;quot;(item) in showList&amp;quot; :key=&amp;quot;item.tid&amp;quot;&amp;gt;
      &amp;lt;img :src=&amp;quot;item.src&amp;quot; &amp;#x2F;&amp;gt;
      &amp;lt;span&amp;gt;{{ item.text }}&amp;lt;&amp;#x2F;span&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;div ref=&amp;quot;blank&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;虚拟列表&lt;/h2&gt;
&lt;p&gt;虚拟列表需要讲解的比较多，在这里我分享一下我的一篇&lt;code&gt;虚拟列表&lt;/code&gt;的文章，哈哈我自认为讲的不错吧哈哈哈哈哈哈&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6966179727329460232&quot;&gt;结合“康熙选秀”，给大家讲讲“虚拟列表”&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;如果你觉得此文对你有一丁点帮助，点个赞，鼓励一下林三心哈哈。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你想一起学习前端或者摸鱼，那你可以加我，加入我的摸鱼学习群，点击这里&lt;/strong&gt; —&amp;gt; &lt;a href=&quot;https://juejin.cn/pin/7035153948126216206&quot; title=&quot;https://juejin.cn/pin/7035153948126216206&quot;&gt;摸鱼沸点&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你是有其他目的的，别加我，我不想跟你交朋友，我只想简简单单学习前端，不想搞一些有的没的！！！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: Sunshine_Lin
        原文链接：&lt;a href=&quot;https://juejin.im/post/7031923575044964389&quot;&gt;https://juejin.im/post/7031923575044964389&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Fri, 14 Jan 2022 09:14:13 GMT</pubDate></item><item><title>你会用ES6，那倒是用啊！__前端__Vue.js__React.js</title><link>http://vue-js.com/topic/61e116d4cbbfd1003b11f988</link><guid>http://vue-js.com/topic/61e116d4cbbfd1003b11f988</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: fancy
highlight: arta&lt;/h2&gt;
&lt;p&gt;本文已参与「&lt;a href=&quot;https://juejin.cn/post/7012210233804079141/&quot; title=&quot;https://juejin.cn/post/7012210233804079141/&quot;&gt;掘力星计划&lt;/a&gt;」，赢取创作大礼包，挑战创作激励金。&lt;/p&gt;
&lt;p&gt;不是标题党，这是一位leader在一次代码评审会对小组成员发出的“怒吼”，原因是在代码评审中发现很多地方还是采用ES5的写法，也不是说用ES5写法不行，会有BUG，只是造成代码量增多，可读性变差而已。&lt;/p&gt;
&lt;p&gt;恰好，这位leader有代码洁癖，面对3~5年经验的成员，还写这种水平的代码，极为不满，不断对代码进行吐槽。不过对于他的吐槽，我感觉还是有很大收获的，故就把leader的吐槽记录下来，分享给掘友们，觉得有收获点个赞，有错误的或者更好的写法，非常欢迎在评论中留言。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ps：ES5之后的JS语法统称ES6！！！&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;一、关于取值的吐槽&lt;/h2&gt;
&lt;p&gt;取值在程序中非常常见，比如从对象&lt;code&gt;obj&lt;/code&gt;中取值。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const obj = {
    a:1,
    b:2,
    c:3,
    d:4,
    e:5,
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = obj.a;
const b = obj.b;
const c = obj.c;
const d = obj.d;
const e = obj.e;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;或者&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const f = obj.a + obj.d;
const g = obj.c + obj.e;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;吐槽：“不会用ES6的解构赋值来取值吗？5行代码用1行代码搞定不香吗？直接用对象名加属性名去取值，要是对象名短还好，很长呢？搞得代码中到处都是这个对象名。”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const {a,b,c,d,e} = obj;
const f = a + d;
const g = c + e;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;反驳&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不是不用ES6的解构赋值，而是服务端返回的数据对象中的属性名不是我想要的，这样取值，不是还得重新创建个遍历赋值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;看来你对ES6的解构赋值掌握的还是不够彻底。如果想创建的变量名和对象的属性名不一致，可以这么写：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const {a:a1} = obj;
console.log(a1);&amp;#x2F;&amp;#x2F; 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;补充&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6的解构赋值虽然好用。但是要注意解构的对象不能为&lt;code&gt;undefined&lt;/code&gt;、&lt;code&gt;null&lt;/code&gt;。否则会报错，故要给被解构的对象一个默认值。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const {a,b,c,d,e} = obj || {};
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;二、关于合并数据的吐槽&lt;/h2&gt;
&lt;p&gt;比如合并两个数组，合并两个对象。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = [1,2,3];
const b = [1,5,6];
const c = a.concat(b);&amp;#x2F;&amp;#x2F;[1,2,3,1,5,6]

const obj1 = {
  a:1,
}
const obj2 = {
  b:1,
}
const obj = Object.assign({}, obj1, obj2);&amp;#x2F;&amp;#x2F;{a:1,b:1}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6的扩展运算符是不是忘记了，还有数组的合并不考虑去重吗？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = [1,2,3];
const b = [1,5,6];
const c = [...new Set([...a,...b])];&amp;#x2F;&amp;#x2F;[1,2,3,5,6]

const obj1 = {
  a:1,
}
const obj2 = {
  b:1,
}
const obj = {...obj1,...obj2};&amp;#x2F;&amp;#x2F;{a:1,b:1}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;三、关于拼接字符串的吐槽&lt;/h2&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const name = &amp;#x27;小明&amp;#x27;;
const score = 59;
let result = &amp;#x27;&amp;#x27;;
if(score &amp;gt; 60){
  result = &amp;#96;${name}的考试成绩及格&amp;#96;; 
}else{
  result = &amp;#96;${name}的考试成绩不及格&amp;#96;; 
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;像你们这样用ES6字符串模板，还不如不用，你们根本不清楚在&lt;code&gt;${}&lt;/code&gt;中可以做什么操作。在&lt;code&gt;${}&lt;/code&gt;中可以放入任意的JavaScript表达式，可以进行运算，以及引用对象属性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const name = &amp;#x27;小明&amp;#x27;;
const score = 59;
const result = &amp;#96;${name}${score &amp;gt; 60?&amp;#x27;的考试成绩及格&amp;#x27;:&amp;#x27;的考试成绩不及格&amp;#x27;}&amp;#96;;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;四、关于if中判断条件的吐槽&lt;/h2&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;if(
    type == 1 ||
    type == 2 ||
    type == 3 ||
    type == 4 ||
){
   &amp;#x2F;&amp;#x2F;...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6中数组实例方法&lt;code&gt;includes&lt;/code&gt;会不会使用呢？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const condition = [1,2,3,4];

if( condition.includes(type) ){
   &amp;#x2F;&amp;#x2F;...
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;五、关于列表搜索的吐槽&lt;/h2&gt;
&lt;p&gt;在项目中，一些没分页的列表的搜索功能由前端来实现，搜索一般分为精确搜索和模糊搜索。搜索也要叫过滤，一般用&lt;code&gt;filter&lt;/code&gt;来实现。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = [1,2,3,4,5];
const result = a.filter( 
  item =&amp;gt;{
    return item === 3
  }
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果是精确搜索不会用ES6中的&lt;code&gt;find&lt;/code&gt;吗？性能优化懂么，&lt;code&gt;find&lt;/code&gt;方法中找到符合条件的项，就不会继续遍历数组。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = [1,2,3,4,5];
const result = a.find( 
  item =&amp;gt;{
    return item === 3
  }
)
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;六、关于扁平化数组的吐槽&lt;/h2&gt;
&lt;p&gt;一个部门JSON数据中，属性名是部门id，属性值是个部门成员id数组集合，现在要把有部门的成员id都提取到一个数组集合中。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const deps = {
&amp;#x27;采购部&amp;#x27;:[1,2,3],
&amp;#x27;人事部&amp;#x27;:[5,8,12],
&amp;#x27;行政部&amp;#x27;:[5,14,79],
&amp;#x27;运输部&amp;#x27;:[3,64,105],
}
let member = [];
for (let item in deps){
    const value = deps[item];
    if(Array.isArray(value)){
        member = [...member,...value]
    }
}
member = [...new Set(member)]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;获取对象的全部属性值还要遍历吗？&lt;code&gt;Object.values&lt;/code&gt;忘记了吗？还有涉及到数组的扁平化处理，为啥不用ES6提供的&lt;code&gt;flat&lt;/code&gt;方法呢，还好这次的数组的深度最多只到2维，还要是遇到4维、5维深度的数组，是不是得循环嵌套循环来扁平化？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const deps = {
    &amp;#x27;采购部&amp;#x27;:[1,2,3],
    &amp;#x27;人事部&amp;#x27;:[5,8,12],
    &amp;#x27;行政部&amp;#x27;:[5,14,79],
    &amp;#x27;运输部&amp;#x27;:[3,64,105],
}
let member = Object.values(deps).flat(Infinity);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中使用&lt;code&gt;Infinity&lt;/code&gt;作为&lt;code&gt;flat&lt;/code&gt;的参数，使得无需知道被扁平化的数组的维度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;补充&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flat&lt;/code&gt;方法不支持IE浏览器。&lt;/p&gt;
&lt;h2&gt;七、关于获取对象属性值的吐槽&lt;/h2&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const name = obj &amp;amp;&amp;amp; obj.name;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6中的可选链操作符会使用么？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const name = obj?.name;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;八、关于添加对象属性的吐槽&lt;/h2&gt;
&lt;p&gt;当给对象添加属性时，如果属性名是动态变化的，该怎么处理。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;let obj = {};
let index = 1;
let key = &amp;#96;topic${index}&amp;#96;;
obj[key] = &amp;#x27;话题内容&amp;#x27;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为何要额外创建一个变量。不知道ES6中的对象属性名是可以用表达式吗？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;let obj = {};
let index = 1;
obj[&amp;#96;topic${index}&amp;#96;] = &amp;#x27;话题内容&amp;#x27;;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;九、关于输入框非空的判断&lt;/h2&gt;
&lt;p&gt;在处理输入框相关业务时，往往会判断输入框未输入值的场景。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;if(value !== null &amp;amp;&amp;amp; value !== undefined &amp;amp;&amp;amp; value !== &amp;#x27;&amp;#x27;){
    &amp;#x2F;&amp;#x2F;...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6中新出的空值合并运算符了解过吗，要写那么多条件吗？&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;if((value??&amp;#x27;&amp;#x27;) !== &amp;#x27;&amp;#x27;){
  &amp;#x2F;&amp;#x2F;...
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;十、关于异步函数的吐槽&lt;/h2&gt;
&lt;p&gt;异步函数很常见，经常是用 Promise 来实现。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const fn1 = () =&amp;gt;{
  return new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve(1);
    }, 300);
  });
}
const fn2 = () =&amp;gt;{
  return new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve(2);
    }, 600);
  });
}
const fn = () =&amp;gt;{
   fn1().then(res1 =&amp;gt;{
      console.log(res1);&amp;#x2F;&amp;#x2F; 1
      fn2().then(res2 =&amp;gt;{
        console.log(res2)
      })
   })
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果这样调用异步函数，不怕形成地狱回调啊！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const fn = async () =&amp;gt;{
  const res1 = await fn1();
  const res2 = await fn2();
  console.log(res1);&amp;#x2F;&amp;#x2F; 1
  console.log(res2);&amp;#x2F;&amp;#x2F; 2
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;补充&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但是要做并发请求时，还是要用到&lt;code&gt;Promise.all()&lt;/code&gt;。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const fn = () =&amp;gt;{
   Promise.all([fn1(),fn2()]).then(res =&amp;gt;{
       console.log(res);&amp;#x2F;&amp;#x2F; [1,2]
   }) 
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果并发请求时，只要其中一个异步函数处理完成，就返回结果，要用到&lt;code&gt;Promise.race()&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;十一、后续&lt;/h2&gt;
&lt;p&gt;欢迎来对以上十点leader的吐槽进行反驳，你的反驳如果有道理的，下次代码评审会上，我替你反驳。&lt;/p&gt;
&lt;p&gt;此外以上的整理内容有误的地方，欢迎在评论中指正，万分感谢。&lt;/p&gt;
&lt;p&gt;如果你还有其它想吐槽的，也非常欢迎在评论中留下你的吐槽。&lt;/p&gt;
&lt;p&gt;读完有帮助，不妨关注一下，点个赞支持一下。&lt;p&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
作者: 红尘炼心
原文链接：&lt;a href=&quot;https://juejin.im/post/7016520448204603423&quot;&gt;https://juejin.im/post/7016520448204603423&lt;/a&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>banyungong</author><pubDate>Fri, 14 Jan 2022 06:23:16 GMT</pubDate></item><item><title>「自我检验」熬夜总结50个Vue知识点，全都会你就是神！！！__前端__面试__Vue.js</title><link>http://vue-js.com/topic/61e116c7cbbfd1003b11f987</link><guid>http://vue-js.com/topic/61e116c7cbbfd1003b11f987</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: smartblue&lt;/h2&gt;
&lt;p&gt;「本文已参与好文召集令活动，点击查看：&lt;a href=&quot;https://juejin.cn/post/6978685539985653767&quot; title=&quot;https://juejin.cn/post/6978685539985653767&quot;&gt;后端、大前端双赛道投稿，2万元奖池等你挑战！&lt;/a&gt;」&lt;/p&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;大家好，我是林三心，这几天发生了很多事（具体是啥我就不说了），这些事，吓得我把我这些年珍藏的&lt;code&gt;Vue知识点&lt;/code&gt;都拿出来整理了一遍，巴不得能多总结出一道是一道，我拿出了我的笔记，并且使劲回忆，终于悟出了这50道&lt;code&gt;知识点&lt;/code&gt;（咱不要太俗哈，不叫面试题，咱叫知识点）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fc16e535f5384256bbb1145aa235416f~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;荣耀黄金&lt;/h2&gt;
&lt;h3&gt;1. Vue的优点？Vue的缺点？&lt;/h3&gt;
&lt;p&gt;优点：渐进式，组件化，轻量级，虚拟dom，响应式，单页面路由，数据与视图分开&lt;/p&gt;
&lt;p&gt;缺点：单页面不利于seo，不支持IE8以下，首屏加载时间长&lt;/p&gt;
&lt;h3&gt;2. 为什么说Vue是一个渐进式框架？&lt;/h3&gt;
&lt;p&gt;渐进式：通俗点讲就是，你想用啥你就用啥，咱也不强求你。你想用component就用，不用也行，你想用vuex就用，不用也可以&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/005b7f7d279f405686b354f00a01383a~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;3. Vue跟React的异同点？&lt;/h3&gt;
&lt;p&gt;相同点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.都使用了虚拟dom&lt;/li&gt;
&lt;li&gt;2.组件化开发&lt;/li&gt;
&lt;li&gt;3.都是单向数据流(父子组件之间，不建议子修改父传下来的数据)&lt;/li&gt;
&lt;li&gt;4.都支持服务端渲染
不同点：&lt;/li&gt;
&lt;li&gt;1.React的JSX，Vue的template&lt;/li&gt;
&lt;li&gt;2.数据变化，React手动(setState)，Vue自动(初始化已响应式处理，Object.defineProperty)&lt;/li&gt;
&lt;li&gt;3.React单向绑定，Vue双向绑定&lt;/li&gt;
&lt;li&gt;4.React的Redux，Vue的Vuex&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. MVVM是什么？和MVC有何区别呢？&lt;/h3&gt;
&lt;p&gt;MVC&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Model(模型)：负责从数据库中取数据&lt;/li&gt;
&lt;li&gt;View(视图)：负责展示数据的地方&lt;/li&gt;
&lt;li&gt;Controller(控制器)：用户交互的地方，例如点击事件等等&lt;/li&gt;
&lt;li&gt;思想：Controller将Model的数据展示在View上
&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4636ebbfa25049179c27a6b5ab8bb308~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MVVM&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VM：也就是View-Model，做了两件事达到了数据的双向绑定 一是将【模型】转化成【视图】，即将后端传递的数据转化成所看到的页面。实现的方式是：数据绑定。二是将【视图】转化成【模型】，即将所看到的页面转化成后端的数据。实现的方式是：DOM 事件监听。&lt;/li&gt;
&lt;li&gt;思想：实现了 View 和 Model 的自动同步，也就是当 Model 的属性改变时，我们不用再自己手动操作 Dom 元素，来改变 View 的显示，而是改变属性后该属性对应 View 层显示会自动改变（对应Vue数据驱动的思想）
&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aac31b27392b4b0e90ca2f67c64c59c2~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;区别&lt;/p&gt;
&lt;p&gt;整体看来，MVVM 比 MVC 精简很多，不仅简化了业务与界面的依赖，还解决了数据频繁更新的问题，不用再用选择器操作 DOM 元素。因为在 MVVM 中，View 不知道 Model 的存在，Model 和 ViewModel 也观察不到 View，这种低耦合模式提高代码的可重用性&lt;/p&gt;
&lt;p&gt;Vue是不是MVVM框架？&lt;/p&gt;
&lt;p&gt;Vue是MVVM框架，但是不是严格符合MVVM，因为MVVM规定Model和View不能直接通信，而Vue的&lt;code&gt;ref&lt;/code&gt;可以做到这点&lt;/p&gt;
&lt;h3&gt;5. Vue和JQuery的区别在哪？为什么放弃JQuery用Vue？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.jQuery是直接操作DOM，Vue不直接操作DOM，Vue的数据与视图是分开的，Vue只需要操作数据即可&lt;/li&gt;
&lt;li&gt;2.在操作DOM频繁的场景里，jQuery的操作DOM行为是频繁的，而Vue利用虚拟DOM的技术，大大提高了更新DOM时的性能&lt;/li&gt;
&lt;li&gt;3.Vue中不倡导直接操作DOM，开发者只需要把大部分精力放在数据层面上&lt;/li&gt;
&lt;li&gt;4.Vue集成的一些库，大大提高开发效率，比如Vuex，Router等&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6. Vue的作者是谁？大声说出它的名字！！！&lt;/h3&gt;
&lt;p&gt;他的名字就是：鱿鱼西&lt;/p&gt;
&lt;h2&gt;永恒钻石&lt;/h2&gt;
&lt;h3&gt;7. 为什么data是个函数并且返回一个对象呢？&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;data&lt;/code&gt;之所以是一个函数，是因为一个组件可能会多处调用，而每一次调用就会执行&lt;code&gt;data函数&lt;/code&gt;并返回新的数据对象，这样，可以避免多处调用之间的&lt;code&gt;数据污染&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;8. 使用过哪些Vue的修饰符呢？&lt;/h3&gt;
&lt;p&gt;可以看我这篇文章&lt;a href=&quot;https://juejin.cn/post/6981628129089421326&quot;&gt;「百毒不侵」面试官最喜欢问的13种Vue修饰符&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5a1c911988f74cea91da79af3c6049c2~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;截屏2021-07-11 下午9.56.53.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;9. 使用过哪些Vue的内部指令呢？&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d39d348e686b449e8931f5a85802e3c6~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;10. 组件之间的传值方式有哪些？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;父组件传值给子组件，子组件使用&lt;code&gt;props&lt;/code&gt;进行接收&lt;/li&gt;
&lt;li&gt;子组件传值给父组件，子组件使用&lt;code&gt;$emit+事件&lt;/code&gt;对父组件进行传值&lt;/li&gt;
&lt;li&gt;组件中可以使用&lt;code&gt;$parent&lt;/code&gt;和&lt;code&gt;$children&lt;/code&gt;获取到父组件实例和子组件实例，进而获取数据&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;$attrs&lt;/code&gt;和&lt;code&gt;$listeners&lt;/code&gt;，在对一些组件进行二次封装时可以方便传值，例如A-&amp;gt;B-&amp;gt;C&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;$refs&lt;/code&gt;获取组件实例，进而获取数据&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;Vuex&lt;/code&gt;进行状态管理&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;eventBus&lt;/code&gt;进行跨组件触发事件，进而传递数据&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;provide&lt;/code&gt;和&lt;code&gt;inject&lt;/code&gt;，官方建议我们不要用这个，我在看&lt;code&gt;ElementUI&lt;/code&gt;源码时发现大量使用&lt;/li&gt;
&lt;li&gt;使用浏览器本地缓存，例如&lt;code&gt;localStorage&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;11. 路由有哪些模式呢？又有什么不同呢？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;hash模式：通过&lt;code&gt;#号&lt;/code&gt;后面的内容的更改，触发&lt;code&gt;hashchange&lt;/code&gt;事件，实现路由切换&lt;/li&gt;
&lt;li&gt;history模式：通过&lt;code&gt;pushState&lt;/code&gt;和&lt;code&gt;replaceState&lt;/code&gt;切换url，触发&lt;code&gt;popstate&lt;/code&gt;事件，实现路由切换，需要后端配合&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;12. 如何设置动态class，动态style？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;动态class对象：&lt;code&gt;&amp;lt;div :class=&amp;quot;{ &apos;is-active&apos;: true, &apos;red&apos;: isRed }&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;动态class数组：&lt;code&gt;&amp;lt;div :class=&amp;quot;[&apos;is-active&apos;, isRed ? &apos;red&apos; : &apos;&apos; ]&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;动态style对象：&lt;code&gt;&amp;lt;div :style=&amp;quot;{ color: textColor, fontSize: &apos;18px&apos; }&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;动态style数组：&lt;code&gt;&amp;lt;div :style=&amp;quot;[{ color: textColor, fontSize: &apos;18px&apos; }, { fontWeight: &apos;300&apos; }]&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;13. v-if和v-show有何区别？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.&lt;code&gt;v-if&lt;/code&gt;是通过控制dom元素的删除和生成来实现显隐，每一次显隐都会使组件重新跑一遍生命周期，因为显隐决定了组件的生成和销毁&lt;/li&gt;
&lt;li&gt;2.&lt;code&gt;v-show&lt;/code&gt;是通过控制dom元素的css样式来实现显隐，不会销毁&lt;/li&gt;
&lt;li&gt;3.频繁或者大数量显隐使用&lt;code&gt;v-show&lt;/code&gt;，否则使用&lt;code&gt;v-if&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;14. computed和watch有何区别？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.&lt;code&gt;computed&lt;/code&gt;是依赖已有的变量来计算一个目标变量，大多数情况都是&lt;code&gt;多个变量&lt;/code&gt;凑在一起计算出&lt;code&gt;一个变量&lt;/code&gt;，并且&lt;code&gt;computed&lt;/code&gt;具有&lt;code&gt;缓存机制&lt;/code&gt;，依赖值不变的情况下其会直接读取缓存进行复用，&lt;code&gt;computed&lt;/code&gt;不能进行&lt;code&gt;异步操作&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;2.&lt;code&gt;watch&lt;/code&gt;是监听某一个变量的变化，并执行相应的回调函数，通常是&lt;code&gt;一个变量&lt;/code&gt;的变化决定&lt;code&gt;多个变量&lt;/code&gt;的变化，&lt;code&gt;watch&lt;/code&gt;可以进行&lt;code&gt;异步操作&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;3.简单记就是：一般情况下&lt;code&gt;computed&lt;/code&gt;是&lt;code&gt;多对一&lt;/code&gt;，&lt;code&gt;watch&lt;/code&gt;是&lt;code&gt;一对多&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;15. Vue的生命周期，讲一讲？&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/892fde0e56324868921d0e924c84858a~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;16. 为什么v-if和v-for不建议用在同一标签？&lt;/h3&gt;
&lt;p&gt;在Vue2中，&lt;code&gt;v-for&lt;/code&gt;优先级是高于&lt;code&gt;v-if&lt;/code&gt;的，咱们来看例子&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;div v-for=&amp;quot;item in [1, 2, 3, 4, 5, 6, 7]&amp;quot; v-if=&amp;quot;item !== 3&amp;quot;&amp;gt;
    {{item}}
&amp;lt;&amp;#x2F;div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;上面的写法是&lt;code&gt;v-for&lt;/code&gt;和&lt;code&gt;v-if&lt;/code&gt;同时存在，会先把7个元素都遍历出来，然后再一个个判断是否为3，并把3给隐藏掉，这样的坏处就是，渲染了无用的3节点，增加无用的dom操作，建议使用computed来解决这个问题：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;div v-for=&amp;quot;item in list&amp;quot;&amp;gt;
    {{item}}
&amp;lt;&amp;#x2F;div&amp;gt;

computed() {
    list() {
        return [1, 2, 3, 4, 5, 6, 7].filter(item =&amp;gt; item !== 3)
    }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;17. vuex的有哪些属性？用处是什么？&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9566927e955c4d0ba19df942534e5b53~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;State：定义了应用状态的数据结构，可以在这里设置默认的初始状态。&lt;/li&gt;
&lt;li&gt;Getter：允许组件从 Store 中获取数据，mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。&lt;/li&gt;
&lt;li&gt;Mutation：是唯一更改 store 中状态的方法，且必须是同步函数。&lt;/li&gt;
&lt;li&gt;Action：用于提交 mutation，而不是直接变更状态，可以包含任意异步操作。&lt;/li&gt;
&lt;li&gt;Module：允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;至尊星耀&lt;/h2&gt;
&lt;h3&gt;18. 不需要响应式的数据应该怎么处理？&lt;/h3&gt;
&lt;p&gt;在我们的Vue开发中，会有一些数据，从始至终都&lt;code&gt;未曾改变过&lt;/code&gt;，这种&lt;code&gt;死数据&lt;/code&gt;，既然&lt;code&gt;不改变&lt;/code&gt;，那也就&lt;code&gt;不需要对他做响应式处理&lt;/code&gt;了，不然只会做一些无用功消耗性能，比如一些写死的下拉框，写死的表格数据，这些数据量大的&lt;code&gt;死数据&lt;/code&gt;，如果都进行响应式处理，那会消耗大量性能。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 方法一：将数据定义在data之外
data () {
    this.list1 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list2 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list3 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list4 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list5 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    return {}
 }
    
&amp;#x2F;&amp;#x2F; 方法二：Object.freeze()
data () {
    return {
        list1: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list2: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list3: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list4: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list5: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
    }
 }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;19. watch有哪些属性，分别有什么用？&lt;/h3&gt;
&lt;p&gt;当我们监听一个基本数据类型时：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;watch: {
    value () {
        &amp;#x2F;&amp;#x2F; do something
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当我们监听一个引用数据类型时：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;watch: {
    obj: {
       handler () { &amp;#x2F;&amp;#x2F; 执行回调
           &amp;#x2F;&amp;#x2F; do something
       },
       deep: true, &amp;#x2F;&amp;#x2F; 是否进行深度监听
       immediate: true &amp;#x2F;&amp;#x2F; 是否初始执行handler函数
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;20. 父子组件生命周期顺序&lt;/h3&gt;
&lt;p&gt;父beforeCreate -&amp;gt; 父created -&amp;gt; 父beforeMount -&amp;gt; 子beforeCreate -&amp;gt; 子created -&amp;gt; 子beforeMount -&amp;gt; 子mounted -&amp;gt; 父mounted&lt;/p&gt;
&lt;h3&gt;21. 对象新属性无法更新视图，删除属性无法更新视图，为什么？怎么办？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;原因：&lt;code&gt;Object.defineProperty&lt;/code&gt;没有对对象的新属性进行属性劫持&lt;/li&gt;
&lt;li&gt;对象新属性无法更新视图：使用&lt;code&gt;Vue.$set(obj, key, value)&lt;/code&gt;，组件中&lt;code&gt;this.$set(obj, key, value)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;删除属性无法更新视图：使用&lt;code&gt;Vue.$delete(obj, key)&lt;/code&gt;，组件中&lt;code&gt;this.$delete(obj, key)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;22. 直接arr[index] = xxx无法更新视图怎么办？为什么？怎么办？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;原因：Vue没有对数组进行&lt;code&gt;Object.defineProperty&lt;/code&gt;的属性劫持，所以直接arr[index] = xxx是无法更新视图的&lt;/li&gt;
&lt;li&gt;使用数组的splice方法，&lt;code&gt;arr.splice(index, 1, item)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;Vue.$set(arr, index, value)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;23. 自定义指令&lt;/h3&gt;
&lt;p&gt;建议看这篇文章&lt;a href=&quot;https://www.cnblogs.com/lzq035/p/14183553.html&quot;&gt;8个非常实用的Vue自定义指令&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;24. 插槽的使用以及原理？&lt;/h3&gt;
&lt;p&gt;建议看我这篇文章&lt;a href=&quot;https://juejin.cn/post/6949848530781470733&quot;&gt;「Vue源码学习」你真的知道插槽Slot是怎么“插”的吗&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;25. 为什么不建议用index做key，为什么不建议用随机数做key？&lt;/h3&gt;
&lt;p&gt;举个例子：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;div v-for=&amp;quot;(item, index) in list&amp;quot; :key=&amp;quot;index&amp;quot;&amp;gt;{{item.name}}&amp;lt;&amp;#x2F;div&amp;gt;

list: [
    { name: &amp;#x27;小明&amp;#x27;, id: &amp;#x27;123&amp;#x27; },
    { name: &amp;#x27;小红&amp;#x27;, id: &amp;#x27;124&amp;#x27; },
    { name: &amp;#x27;小花&amp;#x27;, id: &amp;#x27;125&amp;#x27; }
]

渲染为
&amp;lt;div key=&amp;quot;0&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;1&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;2&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;

现在我执行 list.unshift({ name: &amp;#x27;小林&amp;#x27;, id: &amp;#x27;122&amp;#x27; })

渲染为
&amp;lt;div key=&amp;quot;0&amp;quot;&amp;gt;小林&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;1&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;2&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;3&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;


新旧对比

&amp;lt;div key=&amp;quot;0&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;  &amp;lt;div key=&amp;quot;0&amp;quot;&amp;gt;小林&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;1&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;  &amp;lt;div key=&amp;quot;1&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;2&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;  &amp;lt;div key=&amp;quot;2&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;
                         &amp;lt;div key=&amp;quot;3&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;

可以看出，如果用index做key的话，其实是更新了原有的三项，并新增了小花，虽然达到了渲染目的，但是损耗性能

现在我们使用id来做key，渲染为

&amp;lt;div key=&amp;quot;123&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;124&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;125&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;

现在我执行 list.unshift({ name: &amp;#x27;小林&amp;#x27;, id: &amp;#x27;122&amp;#x27; })，渲染为

&amp;lt;div key=&amp;quot;122&amp;quot;&amp;gt;小林&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;123&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;124&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;125&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;

新旧对比

                           &amp;lt;div key=&amp;quot;122&amp;quot;&amp;gt;小林&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;123&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;  &amp;lt;div key=&amp;quot;123&amp;quot;&amp;gt;小明&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;124&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;  &amp;lt;div key=&amp;quot;124&amp;quot;&amp;gt;小红&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;div key=&amp;quot;125&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;  &amp;lt;div key=&amp;quot;125&amp;quot;&amp;gt;小花&amp;lt;&amp;#x2F;div&amp;gt;

可以看出，原有的三项都不变，只是新增了小林这个人，这才是最理想的结果
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;用&lt;code&gt;index&lt;/code&gt;和用&lt;code&gt;随机数&lt;/code&gt;都是同理，&lt;code&gt;随机数&lt;/code&gt;每次都在变，做不到专一性，很&lt;code&gt;渣男&lt;/code&gt;，也很消耗性能，所以，拒绝&lt;code&gt;渣男&lt;/code&gt;，选择&lt;code&gt;老实人&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;26. 说说nextTick的用处？&lt;/h3&gt;
&lt;p&gt;我举个例子，在vue中：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;this.name = &amp;#x27;林三心&amp;#x27;
this.age = 18
this.gender = &amp;#x27;男&amp;#x27;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们修改了三个变量，那问题来了，是每修改一次，DOM就更新一次吗？不是的，Vue采用的是&lt;code&gt;异步更新&lt;/code&gt;的策略，通俗点说就是，&lt;code&gt;同一事件循环内&lt;/code&gt;多次修改，会&lt;code&gt;统一&lt;/code&gt;进行一次&lt;code&gt;视图更新&lt;/code&gt;，这样才能节省性能嘛&lt;/p&gt;
&lt;p&gt;看懂了上面，那你应该也看得懂下面的例子了吧：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;div ref=&amp;quot;testDiv&amp;quot;&amp;gt;{{name}}&amp;lt;&amp;#x2F;div&amp;gt;

name: &amp;#x27;小林&amp;#x27;

this.name = &amp;#x27;林三心&amp;#x27;
console.log(this.$refs.testDiv.innerHTML) &amp;#x2F;&amp;#x2F; 这里是啥呢
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;答案是“小林”，前面说了，Vue是&lt;code&gt;异步更新&lt;/code&gt;，所以数据一更新，视图却还没更新，所以拿到的还是上一次的旧视图数据，那么想要拿到最新视图数据怎么办呢？&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;this.name = &amp;#x27;林三心&amp;#x27;
this.$nextTick(() =&amp;gt; {
    console.log(this.$refs.testDiv.innerHTML) &amp;#x2F;&amp;#x2F; 林三心
})
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;27. Vue的SSR是什么？有什么好处？&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SSR&lt;/code&gt;就是服务端渲染&lt;/li&gt;
&lt;li&gt;基于&lt;code&gt;nodejs serve&lt;/code&gt;服务环境开发，所有&lt;code&gt;html&lt;/code&gt;代码在服务端渲染&lt;/li&gt;
&lt;li&gt;数据返回给前端，然后前端进行“激活”，即可成为浏览器识别的html代码&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SSR&lt;/code&gt;首次加载更快，有更好的用户体验，有更好的seo优化，因为爬虫能看到整个页面的内容，如果是vue项目，由于数据还要经过解析，这就造成爬虫并不会等待你的数据加载完成，所以其实Vue项目的seo体验并不是很好&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;最强王者&lt;/h2&gt;
&lt;h3&gt;28. Vue响应式是怎么实现的？&lt;/h3&gt;
&lt;p&gt;整体思路是数据劫持+观察者模式&lt;/p&gt;
&lt;p&gt;对象内部通过&lt;code&gt;defineReactive&lt;/code&gt; 方法，使用 &lt;code&gt;Object.defineProperty&lt;/code&gt; 将属性进行劫持（只会劫持已经存在的属性），数组则是通过重写数组方法来实现。当页面使用对应属性时，每个属性都拥有自己的&lt;code&gt;dep&lt;/code&gt;属性，存放他所依赖的&lt;code&gt;watcher&lt;/code&gt;（依赖收集），当属性变化后会通知自己对应的&lt;code&gt;watcher&lt;/code&gt; 去更新(派发更新)。&lt;/p&gt;
&lt;p&gt;想详细了解过程，建议阅读我的&lt;a href=&quot;https://juejin.cn/column/6969563635194527758&quot;&gt;Vue源码解析系列&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const { arrayMethods } = require(&amp;#x27;.&amp;#x2F;array&amp;#x27;)

class Observer {
    constructor(value) {
        Object.defineProperty(value, &amp;#x27;__ob__&amp;#x27;, {
            value: this,
            enumerable: false,
            writable: true,
            configurable: true
        })
        if(Array.isArray(value)) {
            value.__proto__ = arrayMethods
            this.observeArray(value)
        } else {
            this.walk(value)
        }
    }

    walk(data) {
        let keys = Object.keys(data)
        for(let i = 0; i &amp;lt; keys.length; i++) {
            const key = keys[i]
            const value = data[key]
            defineReactive(data, key, value)
        }
    }

    observeArray(items) {
        for(let i = 0; i &amp;lt; items.length; i++) {
            observe(items[i])
        }
    }
}

function defineReactive(data, key, value) {
    const childOb = observe(value)

    const dep = new Dep()

    Object.defineProperty(data, key, {
        get() {
            console.log(&amp;#x27;获取值&amp;#x27;)
            if (Dep.target) {
                dep.depend()

                if (childOb) {
                    childOb.dep.depend()

                    if (Array.isArray(value)) {
                        dependArray(value)
                    }
                }
            }
            return value
        },
        set(newVal) {
            if (newVal === value) return
            observe(newVal)
            value = newVal
            dep.notify()
        }
    })
}

function observe(value) {
    if (Object.prototype.toString.call(value) === &amp;#x27;[object Object]&amp;#x27; || Array.isArray(value)) {
        return new Observer(value)
    }
}

function dependArray(value) {
    for(let e, i = 0, l = value.length; i &amp;lt; l; i++) {
        e = value[i]

        e &amp;amp;&amp;amp; e.__ob__ &amp;amp;&amp;amp; e.__ob__.dep.depend()

        if (Array.isArray(e)) {
            dependArray(e)
        }
    }
}

&amp;#x2F;&amp;#x2F; array.js
const arrayProto = Array.prototype

const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
    &amp;#x27;push&amp;#x27;,
    &amp;#x27;pop&amp;#x27;,
    &amp;#x27;shift&amp;#x27;,
    &amp;#x27;unshift&amp;#x27;,
    &amp;#x27;splice&amp;#x27;,
    &amp;#x27;reverse&amp;#x27;,
    &amp;#x27;sort&amp;#x27;
]

methodsToPatch.forEach(method =&amp;gt; {
    arrayMethods[method] = function (...args) {
        const result = arrayProto[method].apply(this, args)

        const ob = this.__ob__

        var inserted

        switch (method) {
            case &amp;#x27;push&amp;#x27;:
            case &amp;#x27;unshift&amp;#x27;:
                inserted = args
                break;
            case &amp;#x27;splice&amp;#x27;:
                inserted = args.slice(2)
            default:
                break;
        }

        if (inserted) ob.observeArray(inserted)

        ob.dep.notify()

        return result
    }
})

&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;29. 为什么只对对象劫持，而要对数组进行方法重写？&lt;/h3&gt;
&lt;p&gt;因为对象最多也就几十个属性，拦截起来数量不多，但是数组可能会有几百几千项，拦截起来非常耗性能，所以直接重写数组原型上的方法，是比较节省性能的方案&lt;/p&gt;
&lt;h3&gt;30. Vue的模板编译原理？&lt;/h3&gt;
&lt;p&gt;因为这个问题讲起来可能比较长，所以：&lt;/p&gt;
&lt;p&gt;建议看我这篇&lt;a href=&quot;https://juejin.cn/post/6969563640416436232&quot;&gt;「Vue源码学习(二)」你不知道的-模板编译原理&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;31. Vue的computed和watch的原理？&lt;/h3&gt;
&lt;p&gt;因为这个问题讲起来可能比较长，所以：&lt;/p&gt;
&lt;p&gt;建议看我这篇&lt;a href=&quot;https://juejin.cn/post/6974293549135167495&quot;&gt;「Vue源码学习(四)」立志写一篇人人都看的懂的computed，watch原理&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;32. Vue.set方法的原理？&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;function set(target, key, val) {
    &amp;#x2F;&amp;#x2F; 判断是否是数组
    if (Array.isArray(target)) {
        &amp;#x2F;&amp;#x2F; 判断谁大谁小
        target.length = Math.max(target.length, key)
        &amp;#x2F;&amp;#x2F; 执行splice
        target.splice(key, 1, val)
        return val
    }

    const ob = target.__ob__

    &amp;#x2F;&amp;#x2F; 如果此对象没有不是响应式对象，直接设置并返回
    if (key in target &amp;amp;&amp;amp; !(key in target.prototype) || !ob) {
        target[key] = val
        return val
    }

    &amp;#x2F;&amp;#x2F; 否则，新增属性，并响应式处理
    defineReactive(target, key, val)
    return val
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;33. Vue.delete方法的原理？&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;function del (target, key) {
    &amp;#x2F;&amp;#x2F; 判断是否为数组
    if (Array.isArray(target)) {
        &amp;#x2F;&amp;#x2F; 执行splice
        target.splice(key, 1)
        return
    }

    const ob = target.__ob__

    &amp;#x2F;&amp;#x2F; 对象本身就没有这个属性，直接返回
    if (!(key in target)) return


    &amp;#x2F;&amp;#x2F; 否则，删除这个属性
    delete target[key]

    &amp;#x2F;&amp;#x2F; 判断是否是响应式对象，不是的话，直接返回
    if (!ob) return
    &amp;#x2F;&amp;#x2F; 是的话，删除后要通知视图更新
    ob.dep.notify()
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;34. nextTick的原理？&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;let callbacks = []; &amp;#x2F;&amp;#x2F;回调函数
let pending = false;
function flushCallbacks() {
  pending = false; &amp;#x2F;&amp;#x2F;把标志还原为false
  &amp;#x2F;&amp;#x2F; 依次执行回调
  for (let i = 0; i &amp;lt; callbacks.length; i++) {
    callbacks[i]();
  }
}
let timerFunc; &amp;#x2F;&amp;#x2F;先采用微任务并按照优先级优雅降级的方式实现异步刷新
if (typeof Promise !== &amp;quot;undefined&amp;quot;) {
  &amp;#x2F;&amp;#x2F; 如果支持promise
  const p = Promise.resolve();
  timerFunc = () =&amp;gt; {
    p.then(flushCallbacks);
  };
} else if (typeof MutationObserver !== &amp;quot;undefined&amp;quot;) {
  &amp;#x2F;&amp;#x2F; MutationObserver 主要是监听dom变化 也是一个异步方法
  let counter = 1;
  const observer = new MutationObserver(flushCallbacks);
  const textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {
    characterData: true,
  });
  timerFunc = () =&amp;gt; {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
} else if (typeof setImmediate !== &amp;quot;undefined&amp;quot;) {
  &amp;#x2F;&amp;#x2F; 如果前面都不支持 判断setImmediate
  timerFunc = () =&amp;gt; {
    setImmediate(flushCallbacks);
  };
} else {
  &amp;#x2F;&amp;#x2F; 最后降级采用setTimeout
  timerFunc = () =&amp;gt; {
    setTimeout(flushCallbacks, 0);
  };
}

export function nextTick(cb) {
  callbacks.push(cb);
  if (!pending) {
    pending = true;
    timerFunc();
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;35. key有什么用？说说diff算法吧？&lt;/h3&gt;
&lt;p&gt;直接看这篇吧：&lt;a href=&quot;https://juejin.cn/post/6844904113587634184&quot;&gt;为什么 Vue 中不要用 index 作为 key？（diff 算法详解）&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;我讲的没他好&lt;/p&gt;
&lt;h2&gt;冷门的知识点&lt;/h2&gt;
&lt;h3&gt;36. 如果子组件改变props里的数据会发生什么&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;改变的props数据是基本类型&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;如果修改的是基本类型，则会报错&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;props: {
    num: Number,
  }
created() {
    this.num = 999
  }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/047859eda3bf47a5930dd0b28e842017~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;0458e2ff1538ee85d42953cec9a94ca.png&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;改变的props数据是引用类型&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;props: {
    item: {
      default: () =&amp;gt; ({}),
    }
  }
created() {
    &amp;#x2F;&amp;#x2F; 不报错，并且父级数据会跟着变
    this.item.name = &amp;#x27;sanxin&amp;#x27;;
    
    &amp;#x2F;&amp;#x2F; 会报错，跟基础类型报错一样
    this.item = &amp;#x27;sss&amp;#x27;
  },
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;37. props怎么自定义验证&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;props: {
    num: {
      default: 1,
      validator: function (value) {
          &amp;#x2F;&amp;#x2F; 返回值为false则验证不通过，报错
          return [
            1, 2, 3, 4, 5
          ].indexOf(value) !== -1
    }
    }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;38. watch的immediate属性有什么用？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;比如平时created时要请求一次数据，并且当搜索值改变，也要请求数据，我们会这么写：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;created(){
  this.getList()
},
watch: {
  searchInputValue(){
    this.getList()
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;使用&lt;code&gt;immediate&lt;/code&gt;完全可以这么写，当它为&lt;code&gt;true&lt;/code&gt;时，会初始执行一次&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;watch: {
  searchInputValue:{
    handler: &amp;#x27;getList&amp;#x27;,
    immediate: true
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;39. watch监听一个对象时，如何排除某些属性的监听&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;下面代码是，params发生改变就重新请求数据，无论是a，b，c，d属性改变&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;data() {
    return {
      params: {
        a: 1,
        b: 2,
        c: 3,
        d: 4
      },
    };
  },
watch: {
    params: {
      deep: true,
      handler() {
        this.getList;
      },
    },
  }
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;但是如果我只想要a，b改变时重新请求，c，d改变时不重新请求呢？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;mounted() {
    Object.keys(this.params)
      .filter((_) =&amp;gt; ![&amp;quot;c&amp;quot;, &amp;quot;d&amp;quot;].includes(_)) &amp;#x2F;&amp;#x2F; 排除对c，d属性的监听
      .forEach((_) =&amp;gt; {
        this.$watch((vm) =&amp;gt; vm.params[_], handler, {
          deep: true,
        });
      });
  },
data() {
    return {
      params: {
        a: 1,
        b: 2,
        c: 3,
        d: 4
      },
    };
  },
watch: {
    params: {
      deep: true,
      handler() {
        this.getList;
      },
    },
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;40. 审查元素时发现data-v-xxxxx，这是啥？&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ff63f90f2d924ff6b20622a2d05ba367~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这是在标记vue文件中css时使用scoped标记产生的，因为要保证各文件中的css不相互影响，给每个component都做了唯一的标记，所以每引入一个component就会出现一个新的’data-v-xxx’标记&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;41. computed如何实现传参？&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; html
&amp;lt;div&amp;gt;{{ total(3) }}

&amp;#x2F;&amp;#x2F; js
computed: {
    total() {
      return function(n) {
          return n * this.num
         }
    },
  }

&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;42. vue的hook的使用&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;同一组件中使用&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;这是我们常用的使用定时器的方式&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;export default{
  data(){
    timer:null  
  },
  mounted(){
      this.timer = setInterval(()=&amp;gt;{
      &amp;#x2F;&amp;#x2F;具体执行内容
      console.log(&amp;#x27;1&amp;#x27;);
    },1000);
  }
  beforeDestory(){
    clearInterval(this.timer);
    this.timer = null;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;上面做法不好的地方在于：得全局多定义一个timer变量，可以使用hook这么做：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;export default{
  methods:{
    fn(){
      let timer = setInterval(()=&amp;gt;{
        &amp;#x2F;&amp;#x2F;具体执行代码
        console.log(&amp;#x27;1&amp;#x27;);
      },1000);
      this.$once(&amp;#x27;hook:beforeDestroy&amp;#x27;,()=&amp;gt;{
        clearInterval(timer);
        timer = null;
      })
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;7.2 父子组件使用&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;如果子组件需要在mounted时触发父组件的某一个函数，平时都会这么写：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;父组件
&amp;lt;rl-child @childMounted=&amp;quot;childMountedHandle&amp;quot;
&amp;#x2F;&amp;gt;
method () {
  childMountedHandle() {
  &amp;#x2F;&amp;#x2F; do something...
  }
},

&amp;#x2F;&amp;#x2F; 子组件
mounted () {
  this.$emit(&amp;#x27;childMounted&amp;#x27;)
},
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;使用hook的话可以更方便：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;父组件
&amp;lt;rl-child @hook:mounted=&amp;quot;childMountedHandle&amp;quot;
&amp;#x2F;&amp;gt;
method () {
  childMountedHandle() {
  &amp;#x2F;&amp;#x2F; do something...
  }
},
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;43. provide和inject是响应式的吗？&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 祖先组件
provide(){
    return {
   &amp;#x2F;&amp;#x2F; keyName: { name: this.name }, &amp;#x2F;&amp;#x2F; value 是对象才能实现响应式，也就是引用类型
      keyName: this.changeValue &amp;#x2F;&amp;#x2F; 通过函数的方式也可以[注意，这里是把函数作为value，而不是this.changeValue()]
   &amp;#x2F;&amp;#x2F; keyName: &amp;#x27;test&amp;#x27; value 如果是基本类型，就无法实现响应式
    }
  },
data(){
  return {
	name:&amp;#x27;张三&amp;#x27;
}
  },
  methods: {
  	changeValue(){
  		this.name = &amp;#x27;改变后的名字-李四&amp;#x27;
  	}
  }  
  
  &amp;#x2F;&amp;#x2F; 后代组件
  inject:[&amp;#x27;keyName&amp;#x27;]
  create(){
	console.log(this.keyName) &amp;#x2F;&amp;#x2F; 改变后的名字-李四
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;44.Vue的el属性和$mount优先级？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;比如下面这种情况，Vue会渲染到哪个节点上&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;new Vue({
  router,
  store,
  el: &amp;#x27;#app&amp;#x27;,
  render: h =&amp;gt; h(App)
}).$mount(&amp;#x27;#ggg&amp;#x27;)
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;这是官方的一张图，可以看出&lt;code&gt;el&lt;/code&gt;和&lt;code&gt;$mount&lt;/code&gt;同时存在时，&lt;code&gt;el优先级&lt;/code&gt; &amp;gt; &lt;code&gt;$mount&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/da6331091cb145459e0b543c76e9bfc6~tplv-k3u1fbpfcp-watermark.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;45. 动态指令和参数使用过吗？&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    ...
    &amp;lt;aButton @[someEvent]=&amp;quot;handleSomeEvent()&amp;quot; :[someProps]=&amp;quot;1000&amp;quot; &amp;#x2F;&amp;gt;...
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
  ...
  data(){
    return{
      ...
      someEvent: someCondition ? &amp;quot;click&amp;quot; : &amp;quot;dbclick&amp;quot;,
      someProps: someCondition ? &amp;quot;num&amp;quot; : &amp;quot;price&amp;quot;
    }
  },
  methods: {
    handleSomeEvent(){
      &amp;#x2F;&amp;#x2F; handle some event
    }
  }  
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;46. 相同的路由组件如何重新渲染？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;开发人员经常遇到的情况是，多个路由解析为同一个Vue组件。问题是，Vue出于性能原因，默认情况下共享组件将不会重新渲染，如果你尝试在使用相同组件的路由之间进行切换，则不会发生任何变化。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const routes = [
  {
    path: &amp;quot;&amp;#x2F;a&amp;quot;,
    component: MyComponent
  },
  {
    path: &amp;quot;&amp;#x2F;b&amp;quot;,
    component: MyComponent
  },
];
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;如果依然想重新渲染，怎么办呢？可以使用&lt;code&gt;key&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
    &amp;lt;router-view :key=&amp;quot;$route.path&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;router-view&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;47. 自定义v-model&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;默认情况下，v-model 是 @input 事件侦听器和 :value 属性上的语法糖。但是，你可以在你的Vue组件中指定一个模型属性来定义使用什么事件和value属性——非常棒！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;export default: {
  model: {
    event: &amp;#x27;change&amp;#x27;,
    prop: &amp;#x27;checked&amp;#x27;  
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;48. 如何将获取data中某一个数据的初始状态？&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;在开发中，有时候需要拿初始状态去计算。例如&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;data() {
    return {
      num: 10
  },
mounted() {
    this.num = 1000
  },
methods: {
    howMuch() {
        &amp;#x2F;&amp;#x2F; 计算出num增加了多少，那就是1000 - 初始值
        &amp;#x2F;&amp;#x2F; 可以通过this.$options.data().xxx来获取初始值
        console.log(1000 - this.$options.data().num)
    }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;49.为什么不建议v-for和v-if同时存在&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;div v-for=&amp;quot;item in [1, 2, 3, 4, 5, 6, 7]&amp;quot; v-if=&amp;quot;item !== 3&amp;quot;&amp;gt;
    {{item}}
&amp;lt;&amp;#x2F;div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;上面的写法是v-for和v-if同时存在，会先把7个元素都遍历出来，然后再一个个判断是否为3，并把3给隐藏掉，这样的坏处就是，渲染了无用的3节点，增加无用的dom操作，建议使用computed来解决这个问题：&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;div v-for=&amp;quot;item in list&amp;quot;&amp;gt;
    {{item}}
&amp;lt;&amp;#x2F;div&amp;gt;

computed() {
    list() {
        return [1, 2, 3, 4, 5, 6, 7].filter(item =&amp;gt; item !== 3)
    }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;50.计算变量时，methods和computed哪个好？&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;div&amp;gt;
    &amp;lt;div&amp;gt;{{howMuch1()}}&amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;div&amp;gt;{{howMuch2}}&amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;div&amp;gt;{{index}}&amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;div&amp;gt;

data: () {
    return {
         index: 0
       }
     }
methods: {
    howMuch1() {
        return this.num + this.price
    }
  }
computed: {
    howMuch2() {
        return this.num + this.price
    }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;computed&lt;/code&gt;会好一些，因为computed会有&lt;code&gt;缓存&lt;/code&gt;。例如index由0变成1，那么会触发视图更新，这时候methods会重新执行一次，而computed不会，因为computed依赖的两个变量num和price都没变。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;如果你觉得此文对你有一丁点帮助，点个赞，鼓励一下林三心哈哈。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你想一起学习前端或者摸鱼，那你可以加我，加入我的摸鱼学习群，点击这里&lt;/strong&gt; —&amp;gt; &lt;a href=&quot;https://juejin.cn/pin/7035153948126216206&quot; title=&quot;https://juejin.cn/pin/7035153948126216206&quot;&gt;摸鱼沸点&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你是有其他目的的，别加我，我不想跟你交朋友，我只想简简单单学习前端，不想搞一些有的没的！！！&lt;/strong&gt;&lt;p&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
作者: Sunshine_Lin
原文链接：&lt;a href=&quot;https://juejin.im/post/6984210440276410399&quot;&gt;https://juejin.im/post/6984210440276410399&lt;/a&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>banyungong</author><pubDate>Fri, 14 Jan 2022 06:23:03 GMT</pubDate></item><item><title>实现无感刷新token，我是这样做的__前端__Vue.js</title><link>http://vue-js.com/topic/61df9934cbbfd1003b11f961</link><guid>http://vue-js.com/topic/61df9934cbbfd1003b11f961</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h1&gt;主题列表：juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black, awesome-green, qklhk-chocolate&lt;/h1&gt;
&lt;h1&gt;贡献主题：&lt;a href=&quot;https://github.com/xitu/juejin-markdown-themes&quot;&gt;https://github.com/xitu/juejin-markdown-themes&lt;/a&gt;&lt;/h1&gt;
&lt;h2&gt;theme: condensed-night-purple
highlight:&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6983582201690456071&quot;&gt;原文链接&lt;/a&gt;
&lt;strong&gt;作者：zz&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;前言
最近在做需求的时候,涉及到登录token,产品提出一个问题:能不能让token过期时间长一点，我频繁的要去登录。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;前端：后端，你能不能把token 过期时间设置的长一点。&lt;/p&gt;
&lt;p&gt;后端：可以,但是那样做不安全，你可以用更好的方法。&lt;/p&gt;
&lt;p&gt;前端：什么方法?&lt;/p&gt;
&lt;p&gt;后端：给你刷新token的接口，定时去刷新token&lt;/p&gt;
&lt;p&gt;前端：好，让我思考一下&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;需求
当token过期的时候，刷新token,前端需要做到无感刷新token,即刷token时要做到用户无感知，避免频繁登录。实现思路&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;方法一&lt;/h3&gt;
&lt;p&gt;后端返回过期时间，前端判断token过期时间,去调用刷新token接口&lt;/p&gt;
&lt;p&gt;缺点：需要后端额外提供一个token过期时间的字段；使用了本地时间判断，若本地时间被篡改，特别是本地时间比服务器时间慢时，拦截会失败。&lt;/p&gt;
&lt;h3&gt;方法二&lt;/h3&gt;
&lt;p&gt;写个定时器，定时刷新token接口&lt;/p&gt;
&lt;p&gt;缺点：浪费资源,消耗性能,不建议采用。&lt;/p&gt;
&lt;h3&gt;方法三&lt;/h3&gt;
&lt;p&gt;在响应拦截器中拦截，判断token 返回过期后，调用刷新token接口&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;实现&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;axios&lt;/code&gt;的基本骨架,利用&lt;code&gt;service.interceptors.response&lt;/code&gt;进行拦截&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;import axios from &amp;#x27;axios&amp;#x27;

service.interceptors.response.use(
  response =&amp;gt; {
    if (response.data.code === 409) {
        return refreshToken({ refreshToken: localStorage.getItem(&amp;#x27;refreshToken&amp;#x27;), token: getToken() }).then(res =&amp;gt; {
          const { token } = res.data
          setToken(token)
          response.headers.Authorization = &amp;#96;${token}&amp;#96;
        }).catch(err =&amp;gt; {
          removeToken()
          router.push(&amp;#x27;&amp;#x2F;login&amp;#x27;)
          return Promise.reject(err)
        })
    }
    return response &amp;amp;&amp;amp; response.data
  },
  (error) =&amp;gt; {
    Message.error(error.response.data.msg)
    return Promise.reject(error)
  })
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;问题解决&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;问题一：如何防止多次刷新token&lt;/h3&gt;
&lt;p&gt;我们通过一个变量isRefreshing 去控制是否在刷新token的状态。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;import axios from &amp;#x27;axios&amp;#x27;

service.interceptors.response.use(
  response =&amp;gt; {
    if (response.data.code === 409) {
      if (!isRefreshing) {
        isRefreshing = true
        return refreshToken({ refreshToken: localStorage.getItem(&amp;#x27;refreshToken&amp;#x27;), token: getToken() }).then(res =&amp;gt; {
          const { token } = res.data
          setToken(token)
          response.headers.Authorization = &amp;#96;${token}&amp;#96;
        }).catch(err =&amp;gt; {
          removeToken()
          router.push(&amp;#x27;&amp;#x2F;login&amp;#x27;)
          return Promise.reject(err)
        }).finally(() =&amp;gt; {
          isRefreshing = false
        })
      }
    }
    return response &amp;amp;&amp;amp; response.data
  },
  (error) =&amp;gt; {
    Message.error(error.response.data.msg)
    return Promise.reject(error)
  })
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;问题二：同时发起两个或者两个以上的请求时，其他接口怎么解决&lt;/h3&gt;
&lt;p&gt;当第二个过期的请求进来，token正在刷新，我们先将这个请求存到一个数组队列中，想办法让这个请求处于等待中，一直等到刷新token后再逐个重试清空请求队列。那么如何做到让这个请求处于等待中呢？为了解决这个问题，我们得借助Promise。将请求存进队列中后，同时返回一个Promise，让这个Promise一直处于Pending状态（即不调用resolve），此时这个请求就会一直等啊等，只要我们不执行resolve，这个请求就会一直在等待。当刷新请求的接口返回来后，我们再调用resolve，逐个重试。最终代码：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;import axios from &amp;#x27;axios&amp;#x27;

&amp;#x2F;&amp;#x2F; 是否正在刷新的标记
let isRefreshing = false
&amp;#x2F;&amp;#x2F;重试队列
let requests = []
service.interceptors.response.use(
  response =&amp;gt; {
  &amp;#x2F;&amp;#x2F;约定code 409 token 过期
    if (response.data.code === 409) {
      if (!isRefreshing) {
        isRefreshing = true
        &amp;#x2F;&amp;#x2F;调用刷新token的接口
        return refreshToken({ refreshToken: localStorage.getItem(&amp;#x27;refreshToken&amp;#x27;), token: getToken() }).then(res =&amp;gt; {
          const { token } = res.data
          &amp;#x2F;&amp;#x2F; 替换token
          setToken(token)
          response.headers.Authorization = &amp;#96;${token}&amp;#96;
           &amp;#x2F;&amp;#x2F; token 刷新后将数组的方法重新执行
          requests.forEach((cb) =&amp;gt; cb(token))
          requests = [] &amp;#x2F;&amp;#x2F; 重新请求完清空
          return service(response.config)
        }).catch(err =&amp;gt; {
        &amp;#x2F;&amp;#x2F;跳到登录页
          removeToken()
          router.push(&amp;#x27;&amp;#x2F;login&amp;#x27;)
          return Promise.reject(err)
        }).finally(() =&amp;gt; {
          isRefreshing = false
        })
      } else {
        &amp;#x2F;&amp;#x2F; 返回未执行 resolve 的 Promise
        return new Promise(resolve =&amp;gt; {
          &amp;#x2F;&amp;#x2F; 用函数形式将 resolve 存入，等待刷新后再执行
          requests.push(token =&amp;gt; {
            response.headers.Authorization = &amp;#96;${token}&amp;#96;
            resolve(service(response.config))
          })
        })
      }
    }
    return response &amp;amp;&amp;amp; response.data
  },
  (error) =&amp;gt; {
    Message.error(error.response.data.msg)
    return Promise.reject(error)
  }
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 远航_
        原文链接：&lt;a href=&quot;https://juejin.im/post/7018439775476514823&quot;&gt;https://juejin.im/post/7018439775476514823&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Thu, 13 Jan 2022 03:15:00 GMT</pubDate></item><item><title>Vue 团队公开快如闪电的全新脚手架工具 create-vue，未来将替代 Vue-CLI，才300余行代码，学它！__前端__JavaScript__Vue.js</title><link>http://vue-js.com/topic/61df97a3cbbfd1003b11f960</link><guid>http://vue-js.com/topic/61df97a3cbbfd1003b11f960</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;highlight: darcula
theme: smartblue&lt;/h2&gt;
&lt;p&gt;本文已参与「&lt;a href=&quot;https://juejin.cn/post/7012210233804079141/&quot; title=&quot;https://juejin.cn/post/7012210233804079141/&quot;&gt;掘力星计划&lt;/a&gt;」，赢取创作大礼包，挑战创作激励金。&lt;/p&gt;
&lt;h2&gt;1. 前言&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;大家好，我是&lt;a href=&quot;https://lxchuan12.gitee.io&quot;&gt;若川&lt;/a&gt;。欢迎关注我的&lt;a href=&quot;https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/12/13/16efe57ddc7c9eb3~tplv-t2oaga2asx-image.image&quot; title=&quot;https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/12/13/16efe57ddc7c9eb3~tplv-t2oaga2asx-image.image&quot;&gt;公众号若川视野&lt;/a&gt;，最近组织了&lt;a href=&quot;https://www.yuque.com/ruochuan12/notice&quot;&gt;&lt;strong&gt;源码共读活动&lt;/strong&gt;&lt;/a&gt;，感兴趣的可以加我微信 &lt;a href=&quot;https://juejin.cn/pin/7005372623400435725&quot;&gt;ruochuan12&lt;/a&gt; 参与，已进行两个多月，大家一起交流学习，共同进步。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;想学源码，极力推荐之前我写的&lt;a href=&quot;https://juejin.cn/column/6960551178908205093&quot;&gt;《学习源码整体架构系列》&lt;/a&gt; 包含&lt;code&gt;jQuery&lt;/code&gt;、&lt;code&gt;underscore&lt;/code&gt;、&lt;code&gt;lodash&lt;/code&gt;、&lt;code&gt;vuex&lt;/code&gt;、&lt;code&gt;sentry&lt;/code&gt;、&lt;code&gt;axios&lt;/code&gt;、&lt;code&gt;redux&lt;/code&gt;、&lt;code&gt;koa&lt;/code&gt;、&lt;code&gt;vue-devtools&lt;/code&gt;、&lt;code&gt;vuex4&lt;/code&gt;、&lt;code&gt;koa-compose&lt;/code&gt;、&lt;code&gt;vue-next-release&lt;/code&gt;、&lt;code&gt;vue-this&lt;/code&gt;等十余篇源码文章。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;美国时间 2021 年 10 月 7 日早晨，Vue 团队等主要贡献者举办了一个 &lt;code&gt;Vue Contributor Days&lt;/code&gt; 在线会议，&lt;a href=&quot;https://github.com/sodatea&quot;&gt;蒋豪群&lt;/a&gt;（&lt;a href=&quot;https://www.zhihu.com/people/sodatea&quot;&gt;知乎胖茶&lt;/a&gt;，Vue.js 官方团队成员，Vue-CLI 核心开发），在会上公开了&lt;a href=&quot;https://github.com/vuejs/create-vue&quot;&gt;&lt;code&gt;create-vue&lt;/code&gt;&lt;/a&gt;，一个全新的脚手架工具。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;create-vue&lt;/code&gt;使用&lt;code&gt;npm init vue@next&lt;/code&gt;一行命令，就能&lt;code&gt;快如闪电般&lt;/code&gt;初始化好基于&lt;code&gt;vite&lt;/code&gt;的&lt;code&gt;Vue3&lt;/code&gt;项目。&lt;/p&gt;
&lt;p&gt;本文就是通过调试和大家一起学习这个300余行的源码。&lt;/p&gt;
&lt;p&gt;阅读本文，你将学到：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;1. 学会全新的官方脚手架工具 create-vue 的使用和原理
2. 学会使用 VSCode 直接打开 github 项目
3. 学会使用测试用例调试源码
4. 学以致用，为公司初始化项目写脚手架工具。
5. 等等
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;2. 使用 npm init vue@next 初始化 vue3 项目&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/vuejs/create-vue&quot;&gt;create-vue github README&lt;/a&gt;上写着，&lt;code&gt;An easy way to start a Vue project&lt;/code&gt;。一种简单的初始化vue项目的方式。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;npm init vue@next
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;估计大多数读者，第一反应是&lt;strong&gt;这样竟然也可以，这么简单快捷？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;忍不住想动手在控制台输出命令，我在终端试过，见下图。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c91fdfddab854e0d8846cb65f6df9b54~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;npm-init-vue@next&quot;&gt;&lt;/p&gt;
&lt;p&gt;最终&lt;code&gt;cd vue3-project&lt;/code&gt;、&lt;code&gt;npm install&lt;/code&gt; 、&lt;code&gt;npm run dev&lt;/code&gt;打开页面&lt;a href=&quot;http://localhost:3000&quot;&gt;http://localhost:3000&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3c6cdb2cf6c8452e92e451e24f1bbb7c~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;初始化页面&quot;&gt;&lt;/p&gt;
&lt;h3&gt;2.1 npm init &amp;amp;&amp;amp; npx&lt;/h3&gt;
&lt;p&gt;为啥 &lt;code&gt;npm init&lt;/code&gt; 也可以直接初始化一个项目，带着疑问，我们翻看 &lt;code&gt;npm&lt;/code&gt; 文档。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.npmjs.com/cli/v6/commands/npm-init&quot;&gt;npm init&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;npm init 用法：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;npm init [--force|-f|--yes|-y|--scope]
npm init &amp;lt;@scope&amp;gt; (same as &amp;#96;npx &amp;lt;@scope&amp;gt;&amp;#x2F;create&amp;#96;)
npm init [&amp;lt;@scope&amp;gt;&amp;#x2F;]&amp;lt;name&amp;gt; (same as &amp;#96;npx [&amp;lt;@scope&amp;gt;&amp;#x2F;]create-&amp;lt;name&amp;gt;&amp;#96;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;npm init &amp;lt;initializer&amp;gt;&lt;/code&gt; 时转换成&lt;code&gt;npx&lt;/code&gt;命令：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;npm init foo -&amp;gt; npx create-foo&lt;/li&gt;
&lt;li&gt;npm init @usr/foo -&amp;gt; npx @usr/create-foo&lt;/li&gt;
&lt;li&gt;npm init @usr -&amp;gt; npx @usr/create&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;看完文档，我们也就理解了：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;# 运行
npm init vue@next
# 相当于
npx create-vue@next
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们可以在这里&lt;a href=&quot;https://registry.npmjs.org/create-vue&quot;&gt;create-vue&lt;/a&gt;，找到一些信息。或者在&lt;a href=&quot;https://www.npmjs.com/package/create-vue&quot;&gt;npm create-vue&lt;/a&gt;找到版本等信息。&lt;/p&gt;
&lt;p&gt;其中&lt;code&gt;@next&lt;/code&gt;是指定版本，通过&lt;code&gt;npm dist-tag ls create-vue&lt;/code&gt;命令可以看出，&lt;code&gt;next&lt;/code&gt;版本目前对应的是&lt;code&gt;3.0.0-beta.6&lt;/code&gt;。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;npm dist-tag ls create-vue
- latest: 3.0.0-beta.6
- next: 3.0.0-beta.6
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;发布时 &lt;code&gt;npm publish --tag next&lt;/code&gt; 这种写法指定 &lt;code&gt;tag&lt;/code&gt;。默认标签是&lt;code&gt;latest&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;可能有读者对 &lt;code&gt;npx&lt;/code&gt; 不熟悉，这时找到&lt;a href=&quot;http://www.ruanyifeng.com/blog/2019/02/npx.html&quot;&gt;阮一峰老师博客 npx 介绍&lt;/a&gt;、&lt;a href=&quot;http://nodejs.cn/learn/the-npx-nodejs-package-runner&quot;&gt;nodejs.cn npx&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npx 是一个非常强大的命令，从 npm 的 5.2 版本（发布于 2017 年 7 月）开始可用。&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;简单说下容易忽略且常用的场景，&lt;code&gt;npx&lt;/code&gt;有点类似小程序提出的随用随走。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;轻松地运行本地命令&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;node_modules&amp;#x2F;.bin&amp;#x2F;vite -v
# vite&amp;#x2F;2.6.5 linux-x64 node-v14.16.0

# 等同于
# package.json script: &amp;quot;vite -v&amp;quot;
# npm run vite

npx vite -v
# vite&amp;#x2F;2.6.5 linux-x64 node-v14.16.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;使用不同的 Node.js 版本运行代码&lt;/strong&gt;
某些场景下可以临时切换 &lt;code&gt;node&lt;/code&gt; 版本，有时比 &lt;code&gt;nvm&lt;/code&gt; 包管理方便些。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;npx node@14 -v
# v14.18.0

npx -p node@14 node -v 
# v14.18.0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;无需安装的命令执行&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;# 启动本地静态服务
npx http-server
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;# 无需全局安装
npx @vue&amp;#x2F;cli create vue-project
# @vue&amp;#x2F;cli 相比 npm init vue@next npx create-vue@next 很慢。

# 全局安装
npm i -g @vue&amp;#x2F;cli
vue create vue-project
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9ab5eef2c6a74801b021aeab5b6e5832~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;npx-vue-cli&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm init vue@next&lt;/code&gt; （&lt;code&gt;npx create-vue@next&lt;/code&gt;） 快的原因，主要在于依赖少（能不依赖包就不依赖），源码行数少，目前&lt;code&gt;index.js&lt;/code&gt;只有300余行。&lt;/p&gt;
&lt;h2&gt;3. 配置环境调试源码&lt;/h2&gt;
&lt;h3&gt;3.1 克隆 create-vue 项目&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lxchuan12/create-vue-analysis.git&quot;&gt;本文仓库地址 create-vue-analysis&lt;/a&gt;，求个&lt;code&gt;star&lt;/code&gt;~&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;# 可以直接克隆我的仓库，我的仓库保留的 create-vue 仓库的 git 记录
git clone https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;lxchuan12&amp;#x2F;create-vue-analysis.git
cd create-vue-analysis&amp;#x2F;create-vue
npm i
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当然不克隆也可以直接用 &lt;code&gt;VSCode&lt;/code&gt; 打开我的仓库
&lt;a href=&quot;https://open.vscode.dev/lxchuan12/create-vue-analysis&quot;&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9b3be78836f24b24aff91caeaadaf246~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;Open in Visual Studio Code&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;顺带说下：我是怎么保留 &lt;code&gt;create-vue&lt;/code&gt; 仓库的 &lt;code&gt;git&lt;/code&gt; 记录的。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;# 在 github 上新建一个仓库 &amp;#96;create-vue-analysis&amp;#96; 克隆下来
git clone https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;lxchuan12&amp;#x2F;create-vue-analysis.git
cd create-vue-analysis
git subtree add --prefix=create-vue https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;vuejs&amp;#x2F;create-vue.git main
# 这样就把 create-vue 文件夹克隆到自己的 git 仓库了。且保留的 git 记录
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;关于更多 &lt;code&gt;git subtree&lt;/code&gt;，可以看&lt;a href=&quot;https://segmentfault.com/a/1190000003969060&quot;&gt;Git Subtree 简明使用手册&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;3.2 package.json 分析&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; create-vue&amp;#x2F;package.json
{
  &amp;quot;name&amp;quot;: &amp;quot;create-vue&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;3.0.0-beta.6&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;An easy way to start a Vue project&amp;quot;,
  &amp;quot;type&amp;quot;: &amp;quot;module&amp;quot;,
  &amp;quot;bin&amp;quot;: {
    &amp;quot;create-vue&amp;quot;: &amp;quot;outfile.cjs&amp;quot;
  },
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;bin&lt;/code&gt;指定可执行脚本。也就是我们可以使用 &lt;code&gt;npx create-vue&lt;/code&gt; 的原因。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;outfile.cjs&lt;/code&gt; 是打包输出的&lt;code&gt;JS&lt;/code&gt;文件&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;{
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;build&amp;quot;: &amp;quot;esbuild --bundle index.js --format=cjs --platform=node --outfile=outfile.cjs&amp;quot;,
    &amp;quot;snapshot&amp;quot;: &amp;quot;node snapshot.js&amp;quot;,
    &amp;quot;pretest&amp;quot;: &amp;quot;run-s build snapshot&amp;quot;,
    &amp;quot;test&amp;quot;: &amp;quot;node test.js&amp;quot;
  },
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;执行 &lt;code&gt;npm run test&lt;/code&gt; 时，会先执行钩子函数 &lt;code&gt;pretest&lt;/code&gt;。&lt;code&gt;run-s&lt;/code&gt; 是 &lt;a href=&quot;https://github.com/mysticatea/npm-run-all/blob/HEAD/docs/run-s.md&quot;&gt;npm-run-all&lt;/a&gt; 提供的命令。&lt;code&gt;run-s build snapshot&lt;/code&gt; 命令相当于 &lt;code&gt;npm run build &amp;amp;&amp;amp; npm run snapshot&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;根据脚本提示，我们来看 &lt;code&gt;snapshot.js&lt;/code&gt; 文件。&lt;/p&gt;
&lt;h3&gt;3.3 生成快照 snapshot.js&lt;/h3&gt;
&lt;p&gt;这个文件主要作用是根据&lt;code&gt;const featureFlags = [&apos;typescript&apos;, &apos;jsx&apos;, &apos;router&apos;, &apos;vuex&apos;, &apos;with-tests&apos;]&lt;/code&gt; 组合生成&lt;code&gt;31种&lt;/code&gt;加上 &lt;code&gt;default&lt;/code&gt; 共计 &lt;code&gt;32种&lt;/code&gt; 组合，生成快照在 &lt;code&gt;playground&lt;/code&gt;目录。&lt;/p&gt;
&lt;p&gt;因为打包生成的 &lt;code&gt;outfile.cjs&lt;/code&gt; 代码有做一些处理，不方便调试，我们可以修改为&lt;code&gt;index.js&lt;/code&gt;便于调试。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 路径 create-vue&amp;#x2F;snapshot.js
const bin = path.resolve(__dirname, &amp;#x27;.&amp;#x2F;outfile.cjs&amp;#x27;)
&amp;#x2F;&amp;#x2F; 改成 index.js 便于调试
const bin = path.resolve(__dirname, &amp;#x27;.&amp;#x2F;index.js&amp;#x27;)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们可以在&lt;code&gt;for&lt;/code&gt;和 &lt;code&gt;createProjectWithFeatureFlags&lt;/code&gt; 打上断点。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;createProjectWithFeatureFlags&lt;/code&gt;其实类似在终端输入如下执行这样的命令&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;node .&amp;#x2F;index.js --xxx --xxx --force
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;function createProjectWithFeatureFlags(flags) {
  const projectName = flags.join(&amp;#x27;-&amp;#x27;)
  console.log(&amp;#96;Creating project ${projectName}&amp;#96;)
  const { status } = spawnSync(
    &amp;#x27;node&amp;#x27;,
    [bin, projectName, ...flags.map((flag) =&amp;gt; &amp;#96;--${flag}&amp;#96;), &amp;#x27;--force&amp;#x27;],
    {
      cwd: playgroundDir,
      stdio: [&amp;#x27;pipe&amp;#x27;, &amp;#x27;pipe&amp;#x27;, &amp;#x27;inherit&amp;#x27;]
    }
  )

  if (status !== 0) {
    process.exit(status)
  }
}

&amp;#x2F;&amp;#x2F; 路径 create-vue&amp;#x2F;snapshot.js
for (const flags of flagCombinations) {
  createProjectWithFeatureFlags(flags)
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;调试&lt;/strong&gt;：&lt;code&gt;VSCode&lt;/code&gt;打开项目，&lt;code&gt;VSCode&lt;/code&gt;高版本(1.50+)可以在 &lt;code&gt;create-vue/package.json&lt;/code&gt; =&amp;gt; &lt;code&gt;scripts&lt;/code&gt; =&amp;gt; &lt;code&gt;&amp;quot;test&amp;quot;: &amp;quot;node test.js&amp;quot;&lt;/code&gt;。鼠标悬停在&lt;code&gt;test&lt;/code&gt;上会有调试脚本提示，选择调试脚本。如果对调试不熟悉，可以看我之前的文章&lt;a href=&quot;https://lxchuan12.gitee.io/koa-compose&quot;&gt;koa-compose&lt;/a&gt;，写的很详细。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;调试时，大概率你会遇到：&lt;code&gt;create-vue/index.js&lt;/code&gt; 文件中，&lt;code&gt;__dirname&lt;/code&gt; 报错问题。可以按照如下方法解决。在 &lt;code&gt;import&lt;/code&gt; 的语句后，添加如下语句，就能愉快的调试了。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 路径 create-vue&amp;#x2F;index.js
&amp;#x2F;&amp;#x2F; 解决办法和nodejs issues
&amp;#x2F;&amp;#x2F; https:&amp;#x2F;&amp;#x2F;stackoverflow.com&amp;#x2F;questions&amp;#x2F;64383909&amp;#x2F;dirname-is-not-defined-in-node-14-version
&amp;#x2F;&amp;#x2F; https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;nodejs&amp;#x2F;help&amp;#x2F;issues&amp;#x2F;2907

import { fileURLToPath } from &amp;#x27;url&amp;#x27;;
import { dirname } from &amp;#x27;path&amp;#x27;;

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;接着我们调试 index.js 文件，来学习。&lt;/p&gt;
&lt;h2&gt;4. 调试 index.js 主流程&lt;/h2&gt;
&lt;p&gt;回顾下上文 &lt;code&gt;npm init vue@next&lt;/code&gt; 初始化项目的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c91fdfddab854e0d8846cb65f6df9b54~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;npm-init-vue@next&quot;&gt;&lt;/p&gt;
&lt;p&gt;单从初始化项目输出图来看。主要是三个步骤。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;1. 输入项目名称，默认值是 vue-project
2. 询问一些配置 渲染模板等
3. 完成创建项目，输出运行提示
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;async function init() {
  &amp;#x2F;&amp;#x2F; 省略放在后文详细讲述
}

&amp;#x2F;&amp;#x2F; async 函数返回的是Promise 可以用 catch 报错
init().catch((e) =&amp;gt; {
  console.error(e)
})
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.1 解析命令行参数&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 返回运行当前脚本的工作目录的路径。
const cwd = process.cwd()
&amp;#x2F;&amp;#x2F; possible options:
&amp;#x2F;&amp;#x2F; --default
&amp;#x2F;&amp;#x2F; --typescript &amp;#x2F; --ts
&amp;#x2F;&amp;#x2F; --jsx
&amp;#x2F;&amp;#x2F; --router &amp;#x2F; --vue-router
&amp;#x2F;&amp;#x2F; --vuex
&amp;#x2F;&amp;#x2F; --with-tests &amp;#x2F; --tests &amp;#x2F; --cypress
&amp;#x2F;&amp;#x2F; --force (for force overwriting)
const argv = minimist(process.argv.slice(2), {
    alias: {
        typescript: [&amp;#x27;ts&amp;#x27;],
        &amp;#x27;with-tests&amp;#x27;: [&amp;#x27;tests&amp;#x27;, &amp;#x27;cypress&amp;#x27;],
        router: [&amp;#x27;vue-router&amp;#x27;]
    },
    &amp;#x2F;&amp;#x2F; all arguments are treated as booleans
    boolean: true
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/substack/minimist&quot;&gt;minimist&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;简单说，这个库，就是解析命令行参数的。看例子，我们比较容易看懂传参和解析结果。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;$ node example&amp;#x2F;parse.js -a beep -b boop
{ _: [], a: &amp;#x27;beep&amp;#x27;, b: &amp;#x27;boop&amp;#x27; }

$ node example&amp;#x2F;parse.js -x 3 -y 4 -n5 -abc --beep=boop foo bar baz
{ _: [ &amp;#x27;foo&amp;#x27;, &amp;#x27;bar&amp;#x27;, &amp;#x27;baz&amp;#x27; ],
  x: 3,
  y: 4,
  n: 5,
  a: true,
  b: true,
  c: true,
  beep: &amp;#x27;boop&amp;#x27; }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;比如&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;npm init vue@next --vuex --force
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.2 如果设置了 feature flags 跳过 prompts 询问&lt;/h3&gt;
&lt;p&gt;这种写法方便代码测试等。直接跳过交互式询问，同时也可以省时间。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; if any of the feature flags is set, we would skip the feature prompts
  &amp;#x2F;&amp;#x2F; use &amp;#96;??&amp;#96; instead of &amp;#96;||&amp;#96; once we drop Node.js 12 support
  const isFeatureFlagsUsed =
    typeof (argv.default || argv.ts || argv.jsx || argv.router || argv.vuex || argv.tests) ===
    &amp;#x27;boolean&amp;#x27;

&amp;#x2F;&amp;#x2F; 生成目录
  let targetDir = argv._[0]
  &amp;#x2F;&amp;#x2F; 默认 vue-projects
  const defaultProjectName = !targetDir ? &amp;#x27;vue-project&amp;#x27; : targetDir
  &amp;#x2F;&amp;#x2F; 强制重写文件夹，当同名文件夹存在时
  const forceOverwrite = argv.force
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.3 交互式询问一些配置&lt;/h3&gt;
&lt;p&gt;如上文&lt;code&gt;npm init vue@next&lt;/code&gt; 初始化的图示&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入项目名称&lt;/li&gt;
&lt;li&gt;还有是否删除已经存在的同名目录&lt;/li&gt;
&lt;li&gt;询问使用需要 JSX Router vuex cypress 等。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;let result = {}

  try {
    &amp;#x2F;&amp;#x2F; Prompts:
    &amp;#x2F;&amp;#x2F; - Project name:
    &amp;#x2F;&amp;#x2F;   - whether to overwrite the existing directory or not?
    &amp;#x2F;&amp;#x2F;   - enter a valid package name for package.json
    &amp;#x2F;&amp;#x2F; - Project language: JavaScript &amp;#x2F; TypeScript
    &amp;#x2F;&amp;#x2F; - Add JSX Support?
    &amp;#x2F;&amp;#x2F; - Install Vue Router for SPA development?
    &amp;#x2F;&amp;#x2F; - Install Vuex for state management? (TODO)
    &amp;#x2F;&amp;#x2F; - Add Cypress for testing?
    result = await prompts(
      [
        {
          name: &amp;#x27;projectName&amp;#x27;,
          type: targetDir ? null : &amp;#x27;text&amp;#x27;,
          message: &amp;#x27;Project name:&amp;#x27;,
          initial: defaultProjectName,
          onState: (state) =&amp;gt; (targetDir = String(state.value).trim() || defaultProjectName)
        },
        &amp;#x2F;&amp;#x2F; 省略若干配置
        {
          name: &amp;#x27;needsTests&amp;#x27;,
          type: () =&amp;gt; (isFeatureFlagsUsed ? null : &amp;#x27;toggle&amp;#x27;),
          message: &amp;#x27;Add Cypress for testing?&amp;#x27;,
          initial: false,
          active: &amp;#x27;Yes&amp;#x27;,
          inactive: &amp;#x27;No&amp;#x27;
        }
      ],
      {
        onCancel: () =&amp;gt; {
          throw new Error(red(&amp;#x27;✖&amp;#x27;) + &amp;#x27; Operation cancelled&amp;#x27;)
        }
      }
    ]
    )
  } catch (cancelled) {
    console.log(cancelled.message)
    &amp;#x2F;&amp;#x2F; 退出当前进程。
    process.exit(1)
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.4 初始化询问用户给到的参数，同时也会给到默认值&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; &amp;#96;initial&amp;#96; won&amp;#x27;t take effect if the prompt type is null
  &amp;#x2F;&amp;#x2F; so we still have to assign the default values here
  const {
    packageName = toValidPackageName(defaultProjectName),
    shouldOverwrite,
    needsJsx = argv.jsx,
    needsTypeScript = argv.typescript,
    needsRouter = argv.router,
    needsVuex = argv.vuex,
    needsTests = argv.tests
  } = result
  const root = path.join(cwd, targetDir)

  &amp;#x2F;&amp;#x2F; 如果需要强制重写，清空文件夹

  if (shouldOverwrite) {
    emptyDir(root)
    &amp;#x2F;&amp;#x2F; 如果不存在文件夹，则创建
  } else if (!fs.existsSync(root)) {
    fs.mkdirSync(root)
  }

  &amp;#x2F;&amp;#x2F; 脚手架项目目录
  console.log(&amp;#96;&amp;#x5C;nScaffolding project in ${root}...&amp;#96;)

 &amp;#x2F;&amp;#x2F; 生成 package.json 文件
  const pkg = { name: packageName, version: &amp;#x27;0.0.0&amp;#x27; }
  fs.writeFileSync(path.resolve(root, &amp;#x27;package.json&amp;#x27;), JSON.stringify(pkg, null, 2))
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.5 根据模板文件生成初始化项目所需文件&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;  &amp;#x2F;&amp;#x2F; todo:
  &amp;#x2F;&amp;#x2F; work around the esbuild issue that &amp;#96;import.meta.url&amp;#96; cannot be correctly transpiled
  &amp;#x2F;&amp;#x2F; when bundling for node and the format is cjs
  &amp;#x2F;&amp;#x2F; const templateRoot = new URL(&amp;#x27;.&amp;#x2F;template&amp;#x27;, import.meta.url).pathname
  const templateRoot = path.resolve(__dirname, &amp;#x27;template&amp;#x27;)
  const render = function render(templateName) {
    const templateDir = path.resolve(templateRoot, templateName)
    renderTemplate(templateDir, root)
  }

  &amp;#x2F;&amp;#x2F; Render base template
  render(&amp;#x27;base&amp;#x27;)

   &amp;#x2F;&amp;#x2F; 添加配置
  &amp;#x2F;&amp;#x2F; Add configs.
  if (needsJsx) {
    render(&amp;#x27;config&amp;#x2F;jsx&amp;#x27;)
  }
  if (needsRouter) {
    render(&amp;#x27;config&amp;#x2F;router&amp;#x27;)
  }
  if (needsVuex) {
    render(&amp;#x27;config&amp;#x2F;vuex&amp;#x27;)
  }
  if (needsTests) {
    render(&amp;#x27;config&amp;#x2F;cypress&amp;#x27;)
  }
  if (needsTypeScript) {
    render(&amp;#x27;config&amp;#x2F;typescript&amp;#x27;)
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.6 渲染生成代码模板&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; Render code template.
  &amp;#x2F;&amp;#x2F; prettier-ignore
  const codeTemplate =
    (needsTypeScript ? &amp;#x27;typescript-&amp;#x27; : &amp;#x27;&amp;#x27;) +
    (needsRouter ? &amp;#x27;router&amp;#x27; : &amp;#x27;default&amp;#x27;)
  render(&amp;#96;code&amp;#x2F;${codeTemplate}&amp;#96;)

  &amp;#x2F;&amp;#x2F; Render entry file (main.js&amp;#x2F;ts).
  if (needsVuex &amp;amp;&amp;amp; needsRouter) {
    render(&amp;#x27;entry&amp;#x2F;vuex-and-router&amp;#x27;)
  } else if (needsVuex) {
    render(&amp;#x27;entry&amp;#x2F;vuex&amp;#x27;)
  } else if (needsRouter) {
    render(&amp;#x27;entry&amp;#x2F;router&amp;#x27;)
  } else {
    render(&amp;#x27;entry&amp;#x2F;default&amp;#x27;)
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.7 如果配置了需要 ts&lt;/h3&gt;
&lt;p&gt;重命名所有的 &lt;code&gt;.js&lt;/code&gt; 文件改成 &lt;code&gt;.ts&lt;/code&gt;。
重命名 &lt;code&gt;jsconfig.json&lt;/code&gt; 文件为 &lt;code&gt;tsconfig.json&lt;/code&gt; 文件。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://code.visualstudio.com/docs/languages/jsconfig&quot;&gt;jsconfig.json&lt;/a&gt; 是VSCode的配置文件，可用于配置跳转等。&lt;/p&gt;
&lt;p&gt;把&lt;code&gt;index.html&lt;/code&gt; 文件里的 &lt;code&gt;main.js&lt;/code&gt; 重命名为 &lt;code&gt;main.ts&lt;/code&gt;。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; Cleanup.

if (needsTypeScript) {
    &amp;#x2F;&amp;#x2F; rename all &amp;#96;.js&amp;#96; files to &amp;#96;.ts&amp;#96;
    &amp;#x2F;&amp;#x2F; rename jsconfig.json to tsconfig.json
    preOrderDirectoryTraverse(
      root,
      () =&amp;gt; {},
      (filepath) =&amp;gt; {
        if (filepath.endsWith(&amp;#x27;.js&amp;#x27;)) {
          fs.renameSync(filepath, filepath.replace(&amp;#x2F;&amp;#x5C;.js$&amp;#x2F;, &amp;#x27;.ts&amp;#x27;))
        } else if (path.basename(filepath) === &amp;#x27;jsconfig.json&amp;#x27;) {
          fs.renameSync(filepath, filepath.replace(&amp;#x2F;jsconfig&amp;#x5C;.json$&amp;#x2F;, &amp;#x27;tsconfig.json&amp;#x27;))
        }
      }
    )

    &amp;#x2F;&amp;#x2F; Rename entry in &amp;#96;index.html&amp;#96;
    const indexHtmlPath = path.resolve(root, &amp;#x27;index.html&amp;#x27;)
    const indexHtmlContent = fs.readFileSync(indexHtmlPath, &amp;#x27;utf8&amp;#x27;)
    fs.writeFileSync(indexHtmlPath, indexHtmlContent.replace(&amp;#x27;src&amp;#x2F;main.js&amp;#x27;, &amp;#x27;src&amp;#x2F;main.ts&amp;#x27;))
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.8 配置了不需要测试&lt;/h3&gt;
&lt;p&gt;因为所有的模板都有测试文件，所以不需要测试时，执行删除 &lt;code&gt;cypress&lt;/code&gt;、&lt;code&gt;/__tests__/&lt;/code&gt; 文件夹&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;  if (!needsTests) {
    &amp;#x2F;&amp;#x2F; All templates assumes the need of tests.
    &amp;#x2F;&amp;#x2F; If the user doesn&amp;#x27;t need it:
    &amp;#x2F;&amp;#x2F; rm -rf cypress **&amp;#x2F;__tests__&amp;#x2F;
    preOrderDirectoryTraverse(
      root,
      (dirpath) =&amp;gt; {
        const dirname = path.basename(dirpath)

        if (dirname === &amp;#x27;cypress&amp;#x27; || dirname === &amp;#x27;__tests__&amp;#x27;) {
          emptyDir(dirpath)
          fs.rmdirSync(dirpath)
        }
      },
      () =&amp;gt; {}
    )
  }
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4.9 根据使用的 npm / yarn / pnpm &lt;a href=&quot;http://xn--README-hz6l727j.md&quot;&gt;生成README.md&lt;/a&gt; 文件，给出运行项目的提示&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; Instructions:
  &amp;#x2F;&amp;#x2F; Supported package managers: pnpm &amp;gt; yarn &amp;gt; npm
  &amp;#x2F;&amp;#x2F; Note: until &amp;lt;https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;pnpm&amp;#x2F;pnpm&amp;#x2F;issues&amp;#x2F;3505&amp;gt; is resolved,
  &amp;#x2F;&amp;#x2F; it is not possible to tell if the command is called by &amp;#96;pnpm init&amp;#96;.
  const packageManager = &amp;#x2F;pnpm&amp;#x2F;.test(process.env.npm_execpath)
    ? &amp;#x27;pnpm&amp;#x27;
    : &amp;#x2F;yarn&amp;#x2F;.test(process.env.npm_execpath)
    ? &amp;#x27;yarn&amp;#x27;
    : &amp;#x27;npm&amp;#x27;

  &amp;#x2F;&amp;#x2F; README generation
  fs.writeFileSync(
    path.resolve(root, &amp;#x27;README.md&amp;#x27;),
    generateReadme({
      projectName: result.projectName || defaultProjectName,
      packageManager,
      needsTypeScript,
      needsTests
    })
  )

  console.log(&amp;#96;&amp;#x5C;nDone. Now run:&amp;#x5C;n&amp;#96;)
  if (root !== cwd) {
    console.log(&amp;#96;  ${bold(green(&amp;#96;cd ${path.relative(cwd, root)}&amp;#96;))}&amp;#96;)
  }
  console.log(&amp;#96;  ${bold(green(getCommand(packageManager, &amp;#x27;install&amp;#x27;)))}&amp;#96;)
  console.log(&amp;#96;  ${bold(green(getCommand(packageManager, &amp;#x27;dev&amp;#x27;)))}&amp;#96;)
  console.log()
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;5. npm run test =&amp;gt; node test.js 测试&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; create-vue&amp;#x2F;test.js
import fs from &amp;#x27;fs&amp;#x27;
import path from &amp;#x27;path&amp;#x27;
import { fileURLToPath } from &amp;#x27;url&amp;#x27;

import { spawnSync } from &amp;#x27;child_process&amp;#x27;

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const playgroundDir = path.resolve(__dirname, &amp;#x27;.&amp;#x2F;playground&amp;#x2F;&amp;#x27;)

for (const projectName of fs.readdirSync(playgroundDir)) {
  if (projectName.endsWith(&amp;#x27;with-tests&amp;#x27;)) {
    console.log(&amp;#96;Running unit tests in ${projectName}&amp;#96;)
    const unitTestResult = spawnSync(&amp;#x27;pnpm&amp;#x27;, [&amp;#x27;test:unit:ci&amp;#x27;], {
      cwd: path.resolve(playgroundDir, projectName),
      stdio: &amp;#x27;inherit&amp;#x27;,
      shell: true
    })
    if (unitTestResult.status !== 0) {
      throw new Error(&amp;#96;Unit tests failed in ${projectName}&amp;#96;)
    }

    console.log(&amp;#96;Running e2e tests in ${projectName}&amp;#96;)
    const e2eTestResult = spawnSync(&amp;#x27;pnpm&amp;#x27;, [&amp;#x27;test:e2e:ci&amp;#x27;], {
      cwd: path.resolve(playgroundDir, projectName),
      stdio: &amp;#x27;inherit&amp;#x27;,
      shell: true
    })
    if (e2eTestResult.status !== 0) {
      throw new Error(&amp;#96;E2E tests failed in ${projectName}&amp;#96;)
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;主要对生成快照时生成的在 &lt;code&gt;playground&lt;/code&gt; 32个文件夹，进行如下测试。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;pnpm test:unit:ci

pnpm test:e2e:ci
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;6. 总结&lt;/h2&gt;
&lt;p&gt;我们使用了快如闪电般的&lt;code&gt;npm init vue@next&lt;/code&gt;，学习&lt;code&gt;npx&lt;/code&gt;命令了。学会了其原理。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-sh&quot;&gt;&lt;code&gt;npm init vue@next =&amp;gt; npx create-vue@next
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;快如闪电的原因在于依赖的很少。很多都是自己来实现。如：&lt;code&gt;Vue-CLI&lt;/code&gt;中 &lt;code&gt;vue create vue-project&lt;/code&gt; 命令是用官方的&lt;code&gt;npm&lt;/code&gt;包&lt;a href=&quot;https://github.com/npm/validate-npm-package-name&quot;&gt;validate-npm-package-name&lt;/a&gt;，删除文件夹一般都是使用 &lt;a href=&quot;https://github.com/isaacs/rimraf&quot;&gt;rimraf&lt;/a&gt;。而 &lt;code&gt;create-vue&lt;/code&gt; 是自己实现&lt;code&gt;emptyDir&lt;/code&gt;和&lt;code&gt;isValidPackageName&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;非常建议读者朋友按照文中方法使用&lt;code&gt;VSCode&lt;/code&gt;调试 &lt;code&gt;create-vue&lt;/code&gt; 源码。源码中还有很多细节文中由于篇幅有限，未全面展开讲述。&lt;/p&gt;
&lt;p&gt;学完本文，可以为自己或者公司创建类似初始化脚手架。&lt;/p&gt;
&lt;p&gt;目前版本是&lt;code&gt;3.0.0-beta.6&lt;/code&gt;。我们持续关注学习它。&lt;/p&gt;
&lt;p&gt;最后欢迎加我微信 &lt;a href=&quot;https://juejin.cn/pin/7005372623400435725&quot;&gt;ruochuan12&lt;/a&gt; 交流，参与 &lt;a href=&quot;https://www.yuque.com/ruochuan12/notice&quot;&gt;源码共读&lt;/a&gt; 活动，大家一起学习源码，共同进步。&lt;/p&gt;
&lt;h2&gt;7. 参考资料&lt;/h2&gt;
&lt;p&gt;发现 create-vue 时打算写文章加入到&lt;a href=&quot;https://www.yuque.com/ruochuan12/notice&quot;&gt;源码共读&lt;/a&gt;计划中，大家一起学习。而源码共读群里小伙伴&lt;a href=&quot;https://github.com/upupming/frontend-learning-map&quot;&gt;&lt;code&gt;upupming&lt;/code&gt;&lt;/a&gt;比我先写完文章。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://mp.weixin.qq.com/s?__biz=MzA5MjQwMzQyNw==&amp;amp;mid=2650757588&amp;amp;idx=1&amp;amp;sn=c6fb11eb09d5820957bcbece98a24087&amp;amp;chksm=88665a58bf11d34e569a4e1f6de101f8fa7637a620d5d9f7944cb47f41c5c47ab6920920c4ce&amp;amp;token=633613769&amp;amp;lang=zh_CN#rd&quot;&gt;@upupming  vue-cli 将被 create-vue 替代？初始化基于 vite 的 vue3 项目为何如此简单？&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;关于 &amp;amp;&amp;amp; 交流群&lt;/h2&gt;
&lt;p&gt;最近组织了&lt;a href=&quot;https://juejin.cn/pin/7005372623400435725&quot;&gt;&lt;strong&gt;源码共读活动&lt;/strong&gt;&lt;/a&gt;，感兴趣的可以加我微信 &lt;a href=&quot;https://juejin.cn/pin/7005372623400435725&quot;&gt;ruochuan12&lt;/a&gt; 参与，长期交流学习。&lt;/p&gt;
&lt;p&gt;作者：常以&lt;strong&gt;若川&lt;/strong&gt;为名混迹于江湖。欢迎加我微信&lt;code&gt;ruochuan12&lt;/code&gt;。前端路上 | 所知甚少，唯善学。&lt;br&gt;
&lt;a href=&quot;https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/12/13/16efe57ddc7c9eb3~tplv-t2oaga2asx-image.image&quot; title=&quot;https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/12/13/16efe57ddc7c9eb3~tplv-t2oaga2asx-image.image&quot;&gt;关注公众号若川视野&lt;/a&gt;，每周一起学源码，学会看源码，进阶高级前端。&lt;br&gt;
&lt;a href=&quot;https://link.juejin.cn?target=https%3A%2F%2Flxchuan12.gitee.io&quot; title=&quot;https://lxchuan12.gitee.io&quot;&gt;若川的博客&lt;/a&gt;&lt;br&gt;
&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fsegmentfault.com%2Fblog%2Flxchuan12&quot; title=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fsegmentfault.com%2Fblog%2Flxchuan12&quot;&gt;&lt;code&gt;segmentfault&lt;/code&gt;若川视野专栏&lt;/a&gt;，开通了&lt;strong&gt;若川视野&lt;/strong&gt;专栏，欢迎关注~&lt;br&gt;
&lt;a href=&quot;https://juejin.im/user/1415826704971918/posts&quot; title=&quot;https://juejin.im/user/1415826704971918/posts&quot;&gt;掘金专栏&lt;/a&gt;，欢迎关注~&lt;br&gt;
&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Flxchuan12&quot; title=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Flxchuan12&quot;&gt;知乎若川视野专栏&lt;/a&gt;，开通了&lt;strong&gt;若川视野&lt;/strong&gt;专栏，欢迎关注~&lt;br&gt;
&lt;a href=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Flxchuan12%2Fblog&quot; title=&quot;https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Flxchuan12%2Fblog&quot;&gt;github blog&lt;/a&gt;，求个&lt;code&gt;star&lt;/code&gt;^_^~&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 若川
        原文链接：&lt;a href=&quot;https://juejin.im/post/7018344866811740173&quot;&gt;https://juejin.im/post/7018344866811740173&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Thu, 13 Jan 2022 03:08:19 GMT</pubDate></item><item><title>遇事不决nextTick，为什么​nextTick这么神奇？__前端__Vue.js</title><link>http://vue-js.com/topic/61def3f4cbbfd1003b11f95f</link><guid>http://vue-js.com/topic/61def3f4cbbfd1003b11f95f</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h3&gt;场景说明&lt;/h3&gt;
&lt;p&gt;最近使用Vue全家桶做后台系统的时候，遇到了一个很奇葩的问题：有一个输入框只允许输入数字，当输入其它类型的数据时，输入的内容会被重置为null。为了实现这一功能，使用了一个父组件和子组件。为了方便陈述，这里将业务场景简化，具体代码如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 父组件
&amp;lt;template&amp;gt;
  &amp;lt;Input v-model=&amp;quot;form.a&amp;quot; @on-change=&amp;quot;onChange&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;Input&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script type=&amp;quot;javascript&amp;quot;&amp;gt;
export default {
	data() {
    return {
      form: {
        a: null
      }
    }
  },
  methods: {
    async onChange(value) {
   		if (typeof value !== &amp;#x27;number&amp;#x27;) {
        &amp;#x2F;&amp;#x2F; await this.$nextTick()
        this.form.a = null
      }   
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;#x2F;&amp;#x2F; 子组件
&amp;lt;template&amp;gt;
  &amp;lt;input v-model=&amp;quot;currentValue&amp;quot; @input=&amp;quot;onInput&amp;quot; &amp;#x2F;&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script type=&amp;quot;javascript&amp;quot;&amp;gt;
export default {
	name: &amp;#x27;Input&amp;#x27;,
	props: {
    value: {
      type: [Number, Null],
      default: null 
    }
  },
	data() {
    return {
      currentValue: null
    }
  },
  methods: {
    onInput(event) {
      const value = event.target.value
      this.$emit(&amp;#x27;input&amp;#x27;, value)
      const oldValue = this.value
      if (oldValue === value) return
   		this.$emit(&amp;#x27;on-change&amp;#x27;, value)
    }
  },
  watch: {
    value(value, oldValue) {
      this.currentValue = value
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;将以上代码放到项目中去运行，你会很神奇地发现，在输入框输入字符串’abc’之后，输入框的值并没有被重置为空，而是保持为’abc’没有变化。在将注释的nextTick取消注释以后，输入框的值被重置为空。真的非常神奇。&lt;/p&gt;
&lt;p&gt;其实之前有好几次同事也碰到了类似的场景：数据层发生了变化，dom并没有随之响应。在数据层发生变化以后，执行一次nextTick，dom就按照预期地更新了。这样几次以后，我们甚至都调侃：遇事不决nextTick。&lt;/p&gt;
&lt;h3&gt;代码执行顺序&lt;/h3&gt;
&lt;p&gt;那么，到底nextTick做了什么呢？这里以上面的代码为例，我们先来理一理我们代码是怎么执行的。具体来说，以上代码执行顺序如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;form.a初始值为null&lt;/li&gt;
&lt;li&gt;用户输入字符串abc&lt;/li&gt;
&lt;li&gt;触发input事件，form.a的值改为abc&lt;/li&gt;
&lt;li&gt;触发on-change事件，form.a的值改为null&lt;/li&gt;
&lt;li&gt;由于form.a的值到这里还是为null&lt;/li&gt;
&lt;li&gt;主线程任务执行完毕，检查watch的回调函数是否需要执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个顺序一理，我们就发现了输入框展示abc不置空的原因：原来form.a的值在主线程中间虽然发生了变化，但是最开始到最后始终为null。也就是说，子组件的props的value没有发生变化。自然，watch的回调函数也就不会执行。&lt;/p&gt;
&lt;p&gt;但是这样一来，我们就有另外一个问题了：为什么触发input事件，form.a的值改为null的时候，没有触发watch的回调呢？为了说明这一点，我们需要深入Vue源码，看看$emit和watch的回调函数分别是在什么时候执行的。&lt;/p&gt;
&lt;h3&gt;$emit做了什么？&lt;/h3&gt;
&lt;p&gt;我们首先看看$emit对应的源码。由于Vue 2.X版本源码是使用flow写的，无形中增加了理解成本。考虑到这一点，我们直接找到Vue的dist包中的vue.js文件，并搜索emit函数&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;Vue.prototype.$emit = function (event) {
  var vm = this;
  {
    var lowerCaseEvent = event.toLowerCase();
    if (lowerCaseEvent !== event &amp;amp;&amp;amp; vm._events[lowerCaseEvent]) {
      tip(
        &amp;quot;Event &amp;#x5C;&amp;quot;&amp;quot; + lowerCaseEvent + &amp;quot;&amp;#x5C;&amp;quot; is emitted in component &amp;quot; +
        (formatComponentName(vm)) + &amp;quot; but the handler is registered for &amp;#x5C;&amp;quot;&amp;quot; + event + &amp;quot;&amp;#x5C;&amp;quot;. &amp;quot; +
        &amp;quot;Note that HTML attributes are case-insensitive and you cannot use &amp;quot; +
        &amp;quot;v-on to listen to camelCase events when using in-DOM templates. &amp;quot; +
        &amp;quot;You should probably use &amp;#x5C;&amp;quot;&amp;quot; + (hyphenate(event)) + &amp;quot;&amp;#x5C;&amp;quot; instead of &amp;#x5C;&amp;quot;&amp;quot; + event + &amp;quot;&amp;#x5C;&amp;quot;.&amp;quot;
      );
    }
  }
  var cbs = vm._events[event];
  if (cbs) {
    cbs = cbs.length &amp;gt; 1 ? toArray(cbs) : cbs;
    var args = toArray(arguments, 1);
    var info = &amp;quot;event handler for &amp;#x5C;&amp;quot;&amp;quot; + event + &amp;quot;&amp;#x5C;&amp;quot;&amp;quot;;
    for (var i = 0, l = cbs.length; i &amp;lt; l; i++) {
      invokeWithErrorHandling(cbs[i], vm, args, vm, info);
    }
  }
  return vm
};

function invokeWithErrorHandling (
  handler,
  context,
  args,
  vm,
  info
) {
  var res;
  try {
    res = args ? handler.apply(context, args) : handler.call(context);
    if (res &amp;amp;&amp;amp; !res._isVue &amp;amp;&amp;amp; isPromise(res) &amp;amp;&amp;amp; !res._handled) {
      res.catch(function (e) { return handleError(e, vm, info + &amp;quot; (Promise&amp;#x2F;async)&amp;quot;); });
      &amp;#x2F;&amp;#x2F; issue #9511
      &amp;#x2F;&amp;#x2F; avoid catch triggering multiple times when nested calls
      res._handled = true;
    }
  } catch (e) {
    handleError(e, vm, info);
  }
  return res
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;源码的内容其实很简单，就是把提前注册（或者说订阅）的函数放到一个数组中，执行$emit函数时就把数组中的函数一一取出并执行。可以看出，这是一个发布-订阅模式的使用。&lt;/p&gt;
&lt;p&gt;也就是说，emit的执行是同步的。那么，watch是怎么执行的呢？相比之下，watch的执行会比较繁琐。理解了watch的流程，也就理解了Vue的核心。&lt;/p&gt;
&lt;p&gt;首先，在初始化Vue组件时，有一个initWatch函数，我们来看看这个函数做了什么。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;function initWatch (vm, watch) {
  for (var key in watch) {
    var handler = watch[key];
    if (Array.isArray(handler)) {
      for (var i = 0; i &amp;lt; handler.length; i++) {
        createWatcher(vm, key, handler[i]);
      }
    } else {
      createWatcher(vm, key, handler);
    }
  }
}

function createWatcher (
  vm,
  expOrFn,
  handler,
  options
) {
  if (isPlainObject(handler)) {
    options = handler;
    handler = handler.handler;
  }
  if (typeof handler === &amp;#x27;string&amp;#x27;) {
    handler = vm[handler];
  }
  return vm.$watch(expOrFn, handler, options)
}

Vue.prototype.$watch = function (
  expOrFn,
  cb,
  options
) {
  var vm = this;
  if (isPlainObject(cb)) {
    return createWatcher(vm, expOrFn, cb, options)
  }
  options = options || {};
  options.user = true;
  var watcher = new Watcher(vm, expOrFn, cb, options);
  if (options.immediate) {
    try {
      cb.call(vm, watcher.value);
    } catch (error) {
      handleError(error, vm, (&amp;quot;callback for immediate watcher &amp;#x5C;&amp;quot;&amp;quot; + (watcher.expression) + &amp;quot;&amp;#x5C;&amp;quot;&amp;quot;));
    }
  }
  return function unwatchFn () {
    watcher.teardown();
  }
}

var Watcher = function Watcher (
  vm,
  expOrFn,
  cb,
  options,
  isRenderWatcher
) {
  this.vm = vm;
  if (isRenderWatcher) {
    vm._watcher = this;
  }
  vm._watchers.push(this);
  &amp;#x2F;&amp;#x2F; options
  if (options) {
    this.deep = !!options.deep;
    this.user = !!options.user;
    this.lazy = !!options.lazy;
    this.sync = !!options.sync;
    this.before = options.before;
  } else {
    this.deep = this.user = this.lazy = this.sync = false;
  }
  this.cb = cb;
  this.id = ++uid$2; &amp;#x2F;&amp;#x2F; uid for batching
  this.active = true;
  this.dirty = this.lazy; &amp;#x2F;&amp;#x2F; for lazy watchers
  this.deps = [];
  this.newDeps = [];
  this.depIds = new _Set();
  this.newDepIds = new _Set();
  this.expression = expOrFn.toString();
  &amp;#x2F;&amp;#x2F; parse expression for getter
  if (typeof expOrFn === &amp;#x27;function&amp;#x27;) {
    this.getter = expOrFn;
  } else {
    this.getter = parsePath(expOrFn);
    if (!this.getter) {
      this.getter = noop;
      warn(
        &amp;quot;Failed watching path: &amp;#x5C;&amp;quot;&amp;quot; + expOrFn + &amp;quot;&amp;#x5C;&amp;quot; &amp;quot; +
        &amp;#x27;Watcher only accepts simple dot-delimited paths. &amp;#x27; +
        &amp;#x27;For full control, use a function instead.&amp;#x27;,
        vm
      );
    }
  }
  this.value = this.lazy
    ? undefined
    : this.get();
}

function parsePath (path) {
  if (bailRE.test(path)) {
    return
  }
  var segments = path.split(&amp;#x27;.&amp;#x27;);
  return function (obj) {
    for (var i = 0; i &amp;lt; segments.length; i++) {
      if (!obj) { return }
      obj = obj[segments[i]];
    }
    return obj
  }
}

Watcher.prototype.get = function get () {
  pushTarget(this);
  var value;
  var vm = this.vm;
  try {
    value = this.getter.call(vm, vm);
  } catch (e) {
    if (this.user) {
      handleError(e, vm, (&amp;quot;getter for watcher &amp;#x5C;&amp;quot;&amp;quot; + (this.expression) + &amp;quot;&amp;#x5C;&amp;quot;&amp;quot;));
    } else {
      throw e
    }
  } finally {
    &amp;#x2F;&amp;#x2F; &amp;quot;touch&amp;quot; every property so they are all tracked as
    &amp;#x2F;&amp;#x2F; dependencies for deep watching
    if (this.deep) {
      traverse(value);
    }
    popTarget();
    this.cleanupDeps();
  }
  return value
}

function defineReactive$$1 (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  var dep = new Dep();

  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property &amp;amp;&amp;amp; property.configurable === false) {
    return
  }

  &amp;#x2F;&amp;#x2F; cater for pre-defined getter&amp;#x2F;setters
  var getter = property &amp;amp;&amp;amp; property.get;
  var setter = property &amp;amp;&amp;amp; property.set;
  if ((!getter || setter) &amp;amp;&amp;amp; arguments.length === 2) {
    val = obj[key];
  }

  var childOb = !shallow &amp;amp;&amp;amp; observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      &amp;#x2F;* eslint-disable no-self-compare *&amp;#x2F;
      if (newVal === value || (newVal !== newVal &amp;amp;&amp;amp; value !== value)) {
        return
      }
      &amp;#x2F;* eslint-enable no-self-compare *&amp;#x2F;
      if (customSetter) {
        customSetter();
      }
      &amp;#x2F;&amp;#x2F; #7981: for accessor properties without setter
      if (getter &amp;amp;&amp;amp; !setter) { return }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow &amp;amp;&amp;amp; observe(newVal);
      dep.notify();
    }
  });
}

var Dep = function Dep () {
  this.id = uid++;
  this.subs = [];
}

Dep.prototype.addSub = function addSub (sub) {
  this.subs.push(sub);
};

Dep.prototype.removeSub = function removeSub (sub) {
  remove(this.subs, sub);
};

Dep.prototype.depend = function depend () {
  if (Dep.target) {
    Dep.target.addDep(this);
  }
};

Dep.prototype.notify = function notify () {
  &amp;#x2F;&amp;#x2F; stabilize the subscriber list first
  var subs = this.subs.slice();
  if (!config.async) {
    &amp;#x2F;&amp;#x2F; subs aren&amp;#x27;t sorted in scheduler if not running async
    &amp;#x2F;&amp;#x2F; we need to sort them now to make sure they fire in correct
    &amp;#x2F;&amp;#x2F; order
    subs.sort(function (a, b) { return a.id - b.id; });
  }
  for (var i = 0, l = subs.length; i &amp;lt; l; i++) {
    subs[i].update();
  }
}

Watcher.prototype.update = function update () {
  &amp;#x2F;* istanbul ignore else *&amp;#x2F;
  if (this.lazy) {
    this.dirty = true;
  } else if (this.sync) {
    this.run();
  } else {
    queueWatcher(this);
  }
}

Dep.target = null;
var targetStack = [];

function pushTarget (target) {
  targetStack.push(target);
  Dep.target = target;
}

function popTarget () {
  targetStack.pop();
  Dep.target = targetStack[targetStack.length - 1];
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们看到，watch相关联的函数接近20个。这么多函数在来回跳的时候，很容易把逻辑弄丢了。这里我们来讲一讲整个流程。&lt;/p&gt;
&lt;p&gt;在初始化Vue实例时，执行initWatch，initWatch函数往下走，创建了一个watcher实例。watcher实例执行了getter函数，getter函数读取了data某个属性的值，因此触发了Object.defineProperty中的get函数。get函数执行了dep.depend函数，这个函数用于收集依赖。所谓的依赖其实就是回调函数。在我们说的这个例子中，就是value的watch回调函数。&lt;/p&gt;
&lt;p&gt;讲到这里，我们发现watch的回调函数只是在这里进行了注册，还没有执行。那么，watch真正的执行是在哪里呢？我们回到最开始代码的执行顺序来看。在第3步的时候，form.a=abc，这里有一个设置的操作。这个操作触发了Object.defineProperty的set函数，set函数执行了dep.notify函数。执行了update函数，update函数的核心就是queueWatcher函数。为了更好地说明，我们把queueWatcher函数单独列出来看看。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;function queueWatcher (watcher) {
  var id = watcher.id;
  if (has[id] == null) {
    has[id] = true;
    if (!flushing) {
      queue.push(watcher);
    } else {
      &amp;#x2F;&amp;#x2F; if already flushing, splice the watcher based on its id
      &amp;#x2F;&amp;#x2F; if already past its id, it will be run next immediately.
      var i = queue.length - 1;
      while (i &amp;gt; index &amp;amp;&amp;amp; queue[i].id &amp;gt; watcher.id) {
        i--;
      }
      queue.splice(i + 1, 0, watcher);
    }
    &amp;#x2F;&amp;#x2F; queue the flush
    if (!waiting) {
      waiting = true;

      if (!config.async) {
        flushSchedulerQueue();
        return
      }
      nextTick(flushSchedulerQueue);
    }
  }
}

function flushSchedulerQueue () {
  currentFlushTimestamp = getNow();
  flushing = true;
  var watcher, id;

  &amp;#x2F;&amp;#x2F; Sort queue before flush.
  &amp;#x2F;&amp;#x2F; This ensures that:
  &amp;#x2F;&amp;#x2F; 1. Components are updated from parent to child. (because parent is always
  &amp;#x2F;&amp;#x2F;    created before the child)
  &amp;#x2F;&amp;#x2F; 2. A component&amp;#x27;s user watchers are run before its render watcher (because
  &amp;#x2F;&amp;#x2F;    user watchers are created before the render watcher)
  &amp;#x2F;&amp;#x2F; 3. If a component is destroyed during a parent component&amp;#x27;s watcher run,
  &amp;#x2F;&amp;#x2F;    its watchers can be skipped.
  queue.sort(function (a, b) { return a.id - b.id; });

  &amp;#x2F;&amp;#x2F; do not cache length because more watchers might be pushed
  &amp;#x2F;&amp;#x2F; as we run existing watchers
  for (index = 0; index &amp;lt; queue.length; index++) {
    watcher = queue[index];
    if (watcher.before) {
      watcher.before();
    }
    id = watcher.id;
    has[id] = null;
    watcher.run();
    &amp;#x2F;&amp;#x2F; in dev build, check and stop circular updates.
    if (has[id] != null) {
      circular[id] = (circular[id] || 0) + 1;
      if (circular[id] &amp;gt; MAX_UPDATE_COUNT) {
        warn(
          &amp;#x27;You may have an infinite update loop &amp;#x27; + (
            watcher.user
              ? (&amp;quot;in watcher with expression &amp;#x5C;&amp;quot;&amp;quot; + (watcher.expression) + &amp;quot;&amp;#x5C;&amp;quot;&amp;quot;)
              : &amp;quot;in a component render function.&amp;quot;
          ),
          watcher.vm
        );
        break
      }
    }
  }

  &amp;#x2F;&amp;#x2F; keep copies of post queues before resetting state
  var activatedQueue = activatedChildren.slice();
  var updatedQueue = queue.slice();

  resetSchedulerState();

  &amp;#x2F;&amp;#x2F; call component updated and activated hooks
  callActivatedHooks(activatedQueue);
  callUpdatedHooks(updatedQueue);

  &amp;#x2F;&amp;#x2F; devtool hook
  &amp;#x2F;* istanbul ignore if *&amp;#x2F;
  if (devtools &amp;amp;&amp;amp; config.devtools) {
    devtools.emit(&amp;#x27;flush&amp;#x27;);
  }
}

Watcher.prototype.run = function run () {
  if (this.active) {
    var value = this.get();
    if (
      value !== this.value ||
      &amp;#x2F;&amp;#x2F; Deep watchers and watchers on Object&amp;#x2F;Arrays should fire even
      &amp;#x2F;&amp;#x2F; when the value is the same, because the value may
      &amp;#x2F;&amp;#x2F; have mutated.
      isObject(value) ||
      this.deep
    ) {
      &amp;#x2F;&amp;#x2F; set new value
      var oldValue = this.value;
      this.value = value;
      if (this.user) {
        try {
          this.cb.call(this.vm, value, oldValue);
        } catch (e) {
          handleError(e, this.vm, (&amp;quot;callback for watcher &amp;#x5C;&amp;quot;&amp;quot; + (this.expression) + &amp;quot;&amp;#x5C;&amp;quot;&amp;quot;));
        }
      } else {
        this.cb.call(this.vm, value, oldValue);
      }
    }
  }
}

function nextTick (cb, ctx) {
  var _resolve;
  callbacks.push(function () {
    if (cb) {
      try {
        cb.call(ctx);
      } catch (e) {
        handleError(e, ctx, &amp;#x27;nextTick&amp;#x27;);
      }
    } else if (_resolve) {
      _resolve(ctx);
    }
  });
  if (!pending) {
    pending = true;
    timerFunc();
  }
  &amp;#x2F;&amp;#x2F; $flow-disable-line
  if (!cb &amp;amp;&amp;amp; typeof Promise !== &amp;#x27;undefined&amp;#x27;) {
    return new Promise(function (resolve) {
      _resolve = resolve;
    })
  }
}

var timerFunc;

&amp;#x2F;&amp;#x2F; The nextTick behavior leverages the microtask queue, which can be accessed
&amp;#x2F;&amp;#x2F; via either native Promise.then or MutationObserver.
&amp;#x2F;&amp;#x2F; MutationObserver has wider support, however it is seriously bugged in
&amp;#x2F;&amp;#x2F; UIWebView in iOS &amp;gt;= 9.3.3 when triggered in touch event handlers. It
&amp;#x2F;&amp;#x2F; completely stops working after triggering a few times... so, if native
&amp;#x2F;&amp;#x2F; Promise is available, we will use it:
&amp;#x2F;* istanbul ignore next, $flow-disable-line *&amp;#x2F;
if (typeof Promise !== &amp;#x27;undefined&amp;#x27; &amp;amp;&amp;amp; isNative(Promise)) {
  var p = Promise.resolve();
  timerFunc = function () {
    p.then(flushCallbacks);
    &amp;#x2F;&amp;#x2F; In problematic UIWebViews, Promise.then doesn&amp;#x27;t completely break, but
    &amp;#x2F;&amp;#x2F; it can get stuck in a weird state where callbacks are pushed into the
    &amp;#x2F;&amp;#x2F; microtask queue but the queue isn&amp;#x27;t being flushed, until the browser
    &amp;#x2F;&amp;#x2F; needs to do some other work, e.g. handle a timer. Therefore we can
    &amp;#x2F;&amp;#x2F; &amp;quot;force&amp;quot; the microtask queue to be flushed by adding an empty timer.
    if (isIOS) { setTimeout(noop); }
  };
  isUsingMicroTask = true;
} else if (!isIE &amp;amp;&amp;amp; typeof MutationObserver !== &amp;#x27;undefined&amp;#x27; &amp;amp;&amp;amp; (
  isNative(MutationObserver) ||
  &amp;#x2F;&amp;#x2F; PhantomJS and iOS 7.x
  MutationObserver.toString() === &amp;#x27;[object MutationObserverConstructor]&amp;#x27;
)) {
  &amp;#x2F;&amp;#x2F; Use MutationObserver where native Promise is not available,
  &amp;#x2F;&amp;#x2F; e.g. PhantomJS, iOS7, Android 4.4
  &amp;#x2F;&amp;#x2F; (#6466 MutationObserver is unreliable in IE11)
  var counter = 1;
  var observer = new MutationObserver(flushCallbacks);
  var textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {
    characterData: true
  });
  timerFunc = function () {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
  isUsingMicroTask = true;
} else if (typeof setImmediate !== &amp;#x27;undefined&amp;#x27; &amp;amp;&amp;amp; isNative(setImmediate)) {
  &amp;#x2F;&amp;#x2F; Fallback to setImmediate.
  &amp;#x2F;&amp;#x2F; Technically it leverages the (macro) task queue,
  &amp;#x2F;&amp;#x2F; but it is still a better choice than setTimeout.
  timerFunc = function () {
    setImmediate(flushCallbacks);
  };
} else {
  &amp;#x2F;&amp;#x2F; Fallback to setTimeout.
  timerFunc = function () {
    setTimeout(flushCallbacks, 0);
  };
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在queueWatcher函数中，我们看到了熟悉的面孔：nextTick。我们发现，nextTick就是一个微任务的平稳降级：它将根据所在环境，依次使用Promise、MutationObserver、setImmediate以及setTimeout执行任务。我们看到，执行form.a=abc时，实际上是先注册了一个微任务，在这里我们可以理解为watch回调函数的包裹函数。这个微任务将在主线程任务走完以后执行，因此它将被先挂起。&lt;/p&gt;
&lt;p&gt;随后主线程执行了form.a=null，再次触发了setter。由于都是form.a注册的，在推入微任务队列前会去重，避免watch的回调多次执行。到这里，主线程任务执行完成，微任务队列中watcher回调函数的包裹函数被推出执行，由于form.a的值始终都为null，因此不会执行回调函数。&lt;/p&gt;
&lt;p&gt;在加入$nextTick函数以后，在form.a=null之前先执行了nextTick函数，nextTick函数执行了watcher的回调函数的包裹函数，此时form.a的值为abc，旧的值和新的值不一样，因此执行了watch回调函数。至此，整个逻辑就理顺了。&lt;/p&gt;
&lt;h3&gt;后话&lt;/h3&gt;
&lt;p&gt;没想到，一个简简单单nextTick的使用居然关系到了Vue的核心原理！&lt;p&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
作者: 王先生2022加油
原文链接：&lt;a href=&quot;https://juejin.im/post/6976246978850062367&quot;&gt;https://juejin.im/post/6976246978850062367&lt;/a&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>banyungong</author><pubDate>Wed, 12 Jan 2022 15:29:56 GMT</pubDate></item><item><title>面试官：项目中常用的 .env 文件原理是什么？如何实现？__前端__JavaScript__Vue.js</title><link>http://vue-js.com/topic/61def036cbbfd1003b11f95e</link><guid>http://vue-js.com/topic/61def036cbbfd1003b11f95e</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;highlight: darcula
theme: smartblue&lt;/h2&gt;
&lt;h2&gt;1. 前言&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;大家好，我是&lt;a href=&quot;https://lxchuan12.gitee.io&quot;&gt;若川&lt;/a&gt;。&lt;a href=&quot;https://rank.juejin.cn/rank/2021/1415826704971918&quot;&gt;正在参加掘金年度人气作者活动，可以点此帮我投票&lt;/a&gt;。&lt;strong&gt;为了能帮助到更多对源码感兴趣、想学会看源码、提升自己前端技术能力的同学&lt;/strong&gt;。我倾力组织了&lt;a href=&quot;https://www.yuque.com/ruochuan12/topics/1&quot;&gt;源码共读活动&lt;/a&gt;，感兴趣的可以加我微信 &lt;a href=&quot;https://juejin.cn/pin/7005372623400435725&quot;&gt;ruochuan12&lt;/a&gt; 参与。每周大家一起学习200行左右的源码，已进行5个月。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;想学源码，极力推荐关注我写的专栏（目前2K人关注）&lt;a href=&quot;https://juejin.cn/column/6960551178908205093&quot;&gt;《学习源码整体架构系列》&lt;/a&gt; 包含&lt;code&gt;jQuery&lt;/code&gt;、&lt;code&gt;underscore&lt;/code&gt;、&lt;code&gt;lodash&lt;/code&gt;、&lt;code&gt;vuex&lt;/code&gt;、&lt;code&gt;sentry&lt;/code&gt;、&lt;code&gt;axios&lt;/code&gt;、&lt;code&gt;redux&lt;/code&gt;、&lt;code&gt;koa&lt;/code&gt;、&lt;code&gt;vue-devtools&lt;/code&gt;、&lt;code&gt;vuex4&lt;/code&gt;、&lt;code&gt;koa-compose&lt;/code&gt;、&lt;code&gt;vue 3.2 发布&lt;/code&gt;、&lt;code&gt;vue-this&lt;/code&gt;、&lt;code&gt;create-vue&lt;/code&gt;、&lt;code&gt;玩具vite&lt;/code&gt;等20余篇源码文章。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lxchuan12/dotenv-analysis.git&quot;&gt;本文仓库 https://github.com/lxchuan12/dotenv-analysis.git，求个star^_^&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.yuque.com/ruochuan12/topics/1&quot;&gt;源码共读活动&lt;/a&gt; 每周一期，已进行到18期。于是搜寻各种值得我们学习，且代码行数不多的源码。&lt;a href=&quot;https://github.com/motdotla/dotenv/blob/master/lib/main.js&quot;&gt;dotenv 主文件仅118行&lt;/a&gt;，非常值得我们学习。&lt;/p&gt;
&lt;p&gt;阅读本文，你将学到：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;1. 学会 dotenv 原理和实现
2. 学会使用 fs模块 获取文件并解析
3. 等等
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;2. 环境准备&lt;/h2&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;# 推荐克隆我的项目，保证与文章同步
git clone https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;lxchuan12&amp;#x2F;dotenv-analysis.git
# npm i -g yarn
cd dotenv-analysis&amp;#x2F;dotenv &amp;amp;&amp;amp; yarn i
# VSCode 直接打开当前项目
# code .
# 我写的例子都在 examples 这个文件夹中，可以启动服务本地查看调试
# 在 dotenv-analysis 目录下
node examples&amp;#x2F;index.js

# 或者克隆官方项目
git clone https:&amp;#x2F;&amp;#x2F;github.com&amp;#x2F;motdotla&amp;#x2F;dotenv.git
# npm i -g yarn
cd dotenv &amp;amp;&amp;amp; yarn i
# VSCode 直接打开当前项目
# code .
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果需要对源码进行调试，可以看我的这篇文章：&lt;a href=&quot;https://juejin.cn/post/7030584939020042254&quot;&gt;新手向：前端程序员必学基本技能——调试JS代码&lt;/a&gt;，这里就不再赘述了。&lt;/p&gt;
&lt;h2&gt;3. dotenv 的作用&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/motdotla/dotenv&quot;&gt;dotenv&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Dotenv&lt;/code&gt; 是一个零依赖模块，可将 &lt;code&gt;.env&lt;/code&gt; 文件中的环境变量加载到 &lt;code&gt;process.env&lt;/code&gt; 中。&lt;/p&gt;
&lt;p&gt;如果需要使用变量，则配合如下扩展包使用。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/motdotla/dotenv-expand&quot;&gt;dotenv-expand&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;众所周知，&lt;code&gt;.env&lt;/code&gt; 文件在我们项目中非常常见，在 &lt;code&gt;vue-cli&lt;/code&gt; 和 &lt;code&gt;create-react-app&lt;/code&gt; 中都有使用。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cli.vuejs.org/zh/guide/mode-and-env.html#%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F&quot;&gt;vue-cli .env&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://create-react-app.dev/docs/adding-custom-environment-variables/#what-other-env-files-can-be-used&quot;&gt;create-react-app .env&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;4. .env 文件使用&lt;/h2&gt;
&lt;p&gt;我们项目中经常会用到&lt;code&gt;.env 文件&lt;/code&gt;写法：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-bash&quot;&gt;&lt;code&gt;NAME=若川
AGE=18
BLOG=https:&amp;#x2F;&amp;#x2F;lxchuan12.gitee.io
MP_WEIXIN=&amp;#x27;若川视野&amp;#x27;
ACTIVITY=每周一起学200行左右的源码共读活动
WEIXIN=加我微信 ruochuan12 参与
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;单从这个文件来看，我们可以知道有如下功能需要实现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读取 .env 文件&lt;/li&gt;
&lt;li&gt;解析 .env 文件拆成键值对的对象形式&lt;/li&gt;
&lt;li&gt;赋值到 process.env 上&lt;/li&gt;
&lt;li&gt;最后返回解析后得到的对象&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;5. 简单实现&lt;/h2&gt;
&lt;p&gt;根据分析问题，我们最终可以简单把代码实现如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const fs = require(&amp;#x27;fs&amp;#x27;);
const path = require(&amp;#x27;path&amp;#x27;);

const parse = function parse(src){
    const obj = {};
    &amp;#x2F;&amp;#x2F; 用换行符 分割
    &amp;#x2F;&amp;#x2F; 比如
    &amp;#x2F;**
     * NAME=若川
     * AGE=18
     * MP_WEIXIN=若川视野
     * BLOG=https:&amp;#x2F;&amp;#x2F;lxchuan12.gitee.io
     * ACTIVITY=每周一起学200行左右的源码共读活动
     * WEIXIN=加我微信 ruochuan12 参与
    *&amp;#x2F;
    src.toString().split(&amp;#x27;&amp;#x5C;n&amp;#x27;).forEach(function(line, index){
        &amp;#x2F;&amp;#x2F; 用等号分割
        const keyValueArr = line.split(&amp;#x27;=&amp;#x27;);
        &amp;#x2F;&amp;#x2F; NAME
        key = keyValueArr[0];
        &amp;#x2F;&amp;#x2F; 若川
        val = keyValueArr[1] || &amp;#x27;&amp;#x27;;
        obj[key] = val;
    });
    &amp;#x2F;&amp;#x2F; { NAME: &amp;#x27;若川&amp;#x27;, ... }
    return obj;
}

const config = function(){
    &amp;#x2F;&amp;#x2F; 读取 node 执行的当前路径下的 .env 文件
    let dotenvPath = path.resolve(process.cwd(), &amp;#x27;.env&amp;#x27;);
    &amp;#x2F;&amp;#x2F; 按 utf-8 解析文件，得到对象
    &amp;#x2F;&amp;#x2F; { NAME: &amp;#x27;若川&amp;#x27;, ... }
    const parsed = parse(fs.readFileSync(dotenvPath, &amp;#x27;utf-8&amp;#x27;));

    &amp;#x2F;&amp;#x2F; 键值对形式赋值到 process.env 变量上，原先存在的不赋值
    Object.keys(parsed).forEach(function(key){
        if(!Object.prototype.hasOwnProperty.call(process.env, key)){
            process.env[key] = parsed[key];
        }
    });

    &amp;#x2F;&amp;#x2F; 返回对象
    return parsed;
};

console.log(config());
console.log(process.env);

&amp;#x2F;&amp;#x2F; 导出 config parse 函数
module.exports.config = config;
module.exports.parse = parse;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;6. 继续完善 config 函数&lt;/h2&gt;
&lt;p&gt;简版的 config 函数还缺失挺多功能，比如：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;可由用户自定义路径
可由用户自定义解析编码规则
添加 debug 模式
完善报错输出，用户写的 env 文件自由度比较大，所以需要容错机制。
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;根据功能，我们很容易实现以下代码：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;function resolveHome (envPath) {
    return envPath[0] === &amp;#x27;~&amp;#x27; ? path.join(os.homedir(), envPath.slice(1)) : envPath
}

const config = function(options){
    &amp;#x2F;&amp;#x2F; 读取 node 执行的当前路径下的 .env 文件
    let dotenvPath = path.resolve(process.cwd(), &amp;#x27;.env&amp;#x27;);
    &amp;#x2F;&amp;#x2F; utf8
    let encoding = &amp;#x27;utf8&amp;#x27;;
    &amp;#x2F;&amp;#x2F; debug 模式，输出提示等信息
    let debug = false;
    &amp;#x2F;&amp;#x2F; 对象
    if (options) {
        if (options.path != null) {
            &amp;#x2F;&amp;#x2F; 解析路径
            dotenvPath = resolveHome(options.path)
        }
        &amp;#x2F;&amp;#x2F; 使用配置的编码方式
        if (options.encoding != null) {
            encoding = options.encoding
        }
        &amp;#x2F;&amp;#x2F; 有配置就设置为 true
        if (options.debug != null) {
            debug = true
        }
    }

    try {
        &amp;#x2F;&amp;#x2F; 按 utf-8 解析文件，得到对象
        &amp;#x2F;&amp;#x2F; { NAME: &amp;#x27;若川&amp;#x27;, ... }
        &amp;#x2F;&amp;#x2F; debug 传递给 parse 函数 便于
        const parsed = parse(fs.readFileSync(dotenvPath, { encoding }), { debug });

        &amp;#x2F;&amp;#x2F; 键值对形式赋值到 process.env 变量上，原先存在的不赋值
        Object.keys(parsed).forEach(function(key){
            if(!Object.prototype.hasOwnProperty.call(process.env, key)){
                process.env[key] = parsed[key];
            } else if (debug) {
                console.log(&amp;#96;&amp;quot;${key}&amp;quot; is already defined in &amp;#x5C;&amp;#96;process.env&amp;#x5C;&amp;#96; and will not be overwritten&amp;#96;);
            }
        });

        &amp;#x2F;&amp;#x2F; 返回对象
        return parsed;
    }
    catch (e) {
        return { error: e };
    }
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;dotenv&lt;/code&gt; 源码中，&lt;code&gt;parse&lt;/code&gt; 函数主要是一些正则和单双引号、跨平台等细致处理。这里就暂时不阐述，读者朋友可以查看&lt;a href=&quot;https://github.com/motdotla/dotenv/blob/master/lib/main.js&quot;&gt;dotenv 源码&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;7. 总结&lt;/h2&gt;
&lt;p&gt;鉴于文章不宜过长，文章只比较深入的分析了 &lt;code&gt;config&lt;/code&gt; 函数。&lt;code&gt;parse&lt;/code&gt; 函数目前没有深入分析。&lt;/p&gt;
&lt;p&gt;一句话总结 &lt;code&gt;dotenv&lt;/code&gt; 库的原理。&lt;strong&gt;用 &lt;code&gt;fs.readFileSync&lt;/code&gt; 读取 &lt;code&gt;.env&lt;/code&gt; 文件，并解析文件为键值对形式的对象，将最终结果对象遍历赋值到 &lt;code&gt;process.env&lt;/code&gt; 上&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我们也可以不看 &lt;code&gt;dotenv&lt;/code&gt; 源码，根据 &lt;code&gt;api&lt;/code&gt; 倒推，自己来实现这样的功能。最终看看和  &lt;code&gt;dotenv&lt;/code&gt; 源码本身有什么差别。这样也许更能锻炼自己。或者用 &lt;code&gt;ts&lt;/code&gt; 重构它。&lt;/p&gt;
&lt;p&gt;本文同时也给我们启发：围绕工作常用的技术包和库值得深入学习，做到&lt;strong&gt;知其然，知其所以然&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;值得一提的是：&lt;code&gt;dotenv&lt;/code&gt; 源码使用的是 &lt;code&gt;flow&lt;/code&gt; 类型。vue2 源码也是用的 &lt;code&gt;flow&lt;/code&gt;。vue3 源码改用 &lt;code&gt;ts&lt;/code&gt;了。&lt;/p&gt;
&lt;p&gt;最后可以持续关注我@若川。欢迎加我微信 &lt;a href=&quot;https://juejin.cn/pin/7005372623400435725&quot;&gt;ruochuan12&lt;/a&gt; 交流，参与 &lt;a href=&quot;https://juejin.cn/pin/7005372623400435725&quot;&gt;源码共读&lt;/a&gt; 活动，每周大家一起学习200行左右的源码，共同进步。&lt;p&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
作者: 若川
原文链接：&lt;a href=&quot;https://juejin.im/post/7045057475845816357&quot;&gt;https://juejin.im/post/7045057475845816357&lt;/a&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>banyungong</author><pubDate>Wed, 12 Jan 2022 15:13:58 GMT</pubDate></item><item><title>vue3保姆级教程__Vue.js__前端</title><link>http://vue-js.com/topic/61def036ad745b003c9df6db</link><guid>http://vue-js.com/topic/61def036ad745b003c9df6db</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: channing-cyan&lt;/h2&gt;
&lt;h1&gt;初学vue3&lt;/h1&gt;
&lt;p&gt;说来惭愧，vue3都发布了一年左右了，我才来学习vue3，看来我是真的弱鸡，现在的我是站在巨人的肩膀上来学习，刚出来vue3全是英文，看都看不懂，现在起码有中文了，结合了许多大佬的见解写出来的小白通俗易懂vue3理解，写的不好希望大家轻喷&lt;/p&gt;
&lt;p&gt;ok，ok同志们，没有什么花里胡哨的开场白，今天我们来了解下vue3.&lt;/p&gt;
&lt;h2&gt;了解vue3创建方式&lt;/h2&gt;
&lt;p&gt;首先我们先创建vue3文件，当然这一步和vue2一样，我就不一一描述了哈，懂的都懂。&lt;/p&gt;
&lt;p&gt;都创建好了吧，创建好了我们就来讲一讲&lt;a href=&quot;https://vitejs.cn/&quot;&gt;vite&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;第一我们要先了解&lt;strong&gt;vite&lt;/strong&gt;是什么，vite是尤雨溪团队开发的新一代的前端构建工具，意图取代&lt;strong&gt;webpack&lt;/strong&gt;，首先我们先来看一看vite有什么优点&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a05a30f8ae504d378e44944371a4ccf0~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;vite.jpg&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无需打包，快速的冷服务器启动&lt;/li&gt;
&lt;li&gt;即时热模块更换（HMR，热更新）&lt;/li&gt;
&lt;li&gt;真正的按需编译
webpack是一开始是入口文件，然后分析路由，然后模块，最后进行打包，然后告诉你，服务器准备好了（默认8080）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4dcb2c8157004850ad319f478cea528b~tplv-k3u1fbpfcp-watermark.image?%60width=100px%60&quot; alt=&quot;bundler.37740380.png&quot;&gt;
然而vite是什么，它一开始是先告诉你服务器准备完成，然后等你发送HTTP请求，然后是入口文件，&lt;code&gt;Dynamic import&lt;/code&gt;（动态导入）&lt;code&gt;code split point&lt;/code&gt;（代码分割）
&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dedecd49c0344bf5b11f3fa3e4910787~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;esm.3070012d.png&quot;&gt;
如何使用vite呢，大家可以去看&lt;a href=&quot;https://vitejs.cn/&quot;&gt;官网&lt;/a&gt;，也可以看我写的&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;要构建一个 Vite + Vue 项目，运行，使用 NPM:
npm init @vitejs&amp;#x2F;app 项目名
&amp;#x2F;&amp;#x2F;使用 Yarn:
yarn create @vitejs&amp;#x2F;app 项目名
你会觉得非常快速的创建了项目，然而它并没有给你下载依赖，你还有进入文件然后
npm install (or yarn)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后它的打开方式不是 &lt;code&gt;serve&lt;/code&gt; 变成了&lt;code&gt;dev&lt;/code&gt;
&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/37464be282ed4d20bac0a27a680c0a52~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1636687125(1).png&quot;&gt;
&lt;code&gt;Edit components/HelloWorld.vue to test hot module replacement.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;编辑components/HelloWorld.vue以测试热模块更换。（也就是热更新更快）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/02ad78b38ddd4e02a3a95038203cacdd~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1636687189(1).jpg&quot;&gt;
然而我们只是简单了解下，我们现在的重点是vue3，如果以后vite成为主流，我们也可以在回头看看。&lt;/p&gt;
&lt;p&gt;现在还是以主流的方式创建并进行讲解&lt;/p&gt;
&lt;h1&gt;分析vue3&lt;/h1&gt;
&lt;h2&gt;基本了解&lt;/h2&gt;
&lt;p&gt;当我们创建完成vue3项目后，点击它的main.js，你会发现写法发生了改变&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e91e06642dc64e3fa93bf57aad29359b~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1636688554(1).jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9b0acecab2ed4c05a5ad458eb1b31826~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1636688564(1).jpg&quot;&gt;
引入的不是vue构造函数，而是&lt;code&gt;createApp&lt;/code&gt;工厂函数然而，创建实例对象其实就相当于vue2中的&lt;code&gt;vm&lt;/code&gt;，&lt;code&gt;mount（&apos;#app&apos;）&lt;/code&gt;就相当于&lt;code&gt;$mount(&apos;#app&apos;)&lt;/code&gt;，并且vue2的写法在vue3不能兼容&lt;/p&gt;
&lt;p&gt;现在我们进入App组件，你会发现什么不一样的地方，他没有了根标签，在vue2的时候，我们都是在div根标签里面写东西，所以在vue3里面可以没有根标签&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/97d17e33f76e43fba68e625543030905~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1636697972(1).jpg&quot;&gt;&lt;/p&gt;
&lt;h2&gt;常用组合式API（重点！！！）&lt;/h2&gt;
&lt;h3&gt;setup&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;setup函数是 Composition API（组合API）的入口&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在setup函数中定义的变量和方法最后都是需要 return 出去的 不然无法再模板中使用&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;script&amp;gt;
 export default {
  name: &amp;#x27;App&amp;#x27;,
  setup(){
   let name = &amp;#x27;流星&amp;#x27;
   let age = 18
   &amp;#x2F;&amp;#x2F;方法
   function say(){
    console.log(&amp;#96;我叫${name},今年${age}岁&amp;#96;)
   }

   &amp;#x2F;&amp;#x2F;返回一个对象
   return {
    name,
    age,
    say
   }
  }
 }
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当然这不是响应式的写法，然后你们可能会问，为什么没有用&lt;code&gt;this&lt;/code&gt;，我们要想一想之前为什么要用&lt;code&gt;this&lt;/code&gt;，还不是作用域的问题，然而这次我们都在&lt;code&gt;setup&lt;/code&gt;里面，所以不会用到&lt;code&gt;this&lt;/code&gt;，而且这里兼容vue2的写法如：&lt;code&gt;data，methods...&lt;/code&gt;，并且在vue2中可以读取到vue3里的配置但是vue3里面不能读取到vue2的配置，所以，vue3和vue2不要混用，如果有重名那么优先&lt;code&gt;setup&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;ps.如果大家不喜欢&lt;code&gt;return&lt;/code&gt;这样的写法的话，可以用vue3新语法糖&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;，
&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;就相当于在编译运行是把代码放到了 setup 函数中运行，然后把导出的变量定义到上下文中，并包含在返回的对象中。具体操作可以看掘金其他大佬&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/7002490039066165279&quot;&gt;script setup基本使用&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6983626263327932429&quot;&gt;上手后才知道 ，Vue3 的 script setup 语法糖是真的爽&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6944190150406570020&quot;&gt;vue3新语法糖——setup script&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;setup还有几个注意点&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它比&lt;code&gt;beforeCreate&lt;/code&gt;和&lt;code&gt;created&lt;/code&gt;这两个生命周期还要&lt;strong&gt;快&lt;/strong&gt;，就是说，setup在&lt;code&gt;beforeCreate,created&lt;/code&gt;前，它里面的this打印出来是&lt;code&gt;undefined&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;setup可以接受两个参数，第一个参数是&lt;code&gt;props&lt;/code&gt;,也就是组件传值，第二个参数是&lt;code&gt;context&lt;/code&gt;,上下文对象，&lt;code&gt;context&lt;/code&gt;里面还有三个很重要的东西&lt;code&gt;attrs&lt;/code&gt;，&lt;code&gt;slots&lt;/code&gt;,&lt;code&gt;emit&lt;/code&gt;，它们就相当于vue2里面的&lt;code&gt;this.$attrs&lt;/code&gt;,&lt;code&gt;this.$slots&lt;/code&gt;,&lt;code&gt;this.$emit&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过打印，你可以看到传值，但是会有警告，那是因为我传了两个值，却只接收了一个，要是两个都接收就不会出现警告了
&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d912e962dacd47dc8af8f06b00464c68~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1636791090(1).jpg&quot;&gt;
这个是因为vue3中要求我们用&lt;code&gt;emits&lt;/code&gt;去接收，接收后就不会警告了，但是也可以不理警告直接用
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c74e9b227640468fb9a7c58bfae25a73~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1636794022.jpg&quot;&gt;
使用插槽时，不能使用 &lt;code&gt;slot=&amp;quot;XXX&amp;quot;&lt;/code&gt;,要使用&lt;code&gt;v-slot&lt;/code&gt;，不然会报错&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;父
&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;home&amp;quot;&amp;gt;
    &amp;lt;HelloWorld wish=&amp;quot;不掉发&amp;quot; wishes=&amp;quot;变瘦&amp;quot; @carried=&amp;quot;carried&amp;quot;&amp;gt;
      &amp;lt;h3&amp;gt;实现插槽1&amp;lt;&amp;#x2F;h3&amp;gt;
      &amp;lt;template v-slot:dome&amp;gt;
        &amp;lt;h4&amp;gt;实现插槽2&amp;lt;&amp;#x2F;h4&amp;gt;
      &amp;lt;&amp;#x2F;template&amp;gt;
    &amp;lt;&amp;#x2F;HelloWorld&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import HelloWorld from &amp;quot;.&amp;#x2F;components&amp;#x2F;HelloWorld&amp;quot;;
export default {
  name: &amp;#x27;Home&amp;#x27;,
  components:{
    HelloWorld
  },
  setup(){
    function carried(value){
      alert(&amp;#96;牛呀，都实现了！！！${value}&amp;#96;)
    }
    return {
      carried
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
-------
子
&amp;lt;template&amp;gt;
  &amp;lt;h1&amp;gt;HelloWorld&amp;lt;&amp;#x2F;h1&amp;gt;
  &amp;lt;h1&amp;gt;{{ wish }}&amp;lt;&amp;#x2F;h1&amp;gt;
  &amp;lt;button @click=&amp;quot;dream&amp;quot;&amp;gt;点击实现&amp;lt;&amp;#x2F;button&amp;gt;
  &amp;lt;slot&amp;gt;&amp;lt;&amp;#x2F;slot&amp;gt;
  &amp;lt;slot name=&amp;quot;dome&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;slot&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: &amp;quot;HelloWorld&amp;quot;,
  props: [&amp;quot;wish&amp;quot;,&amp;#x27;wishes&amp;#x27;],
  emits:[&amp;#x27;carried&amp;#x27;],
  setup(props,context) {
    console.log(props)
    console.log(context.attrs)
    function dream(){
      context.emit(&amp;#x27;carried&amp;#x27;,666)
    }
    return{
      dream
    }
  },
};
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;style scoped&amp;gt;&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;ref与reactive&lt;/h3&gt;
&lt;h4&gt;ref&lt;/h4&gt;
&lt;p&gt;上方我说到，我们写的不是响应式数据，我们写的只是字符串和数字，那怎么变成响应式数据呢，那就呀引入&lt;strong&gt;ref&lt;/strong&gt;，但是如果我们直接在代码里面修改是修改不了的,不如打印一下name和age，你会发现ref把它们变成了对象
并且还是&lt;code&gt;RefImpl&lt;/code&gt;的实例对象&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;home&amp;quot;&amp;gt;
    &amp;lt;h1&amp;gt;姓名：{{name}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;h1&amp;gt;年龄：{{age}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;button @click=&amp;quot;say&amp;quot;&amp;gt;修改&amp;lt;&amp;#x2F;button&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import {ref} from &amp;#x27;vue&amp;#x27;
export default {
  name: &amp;#x27;Home&amp;#x27;,
  setup(){
    let name = ref(&amp;#x27;燕儿&amp;#x27;)
    let age = ref(18)
    console.log(name)
    console.log(age)
    &amp;#x2F;&amp;#x2F;方法
    function say(){
      name=&amp;#x27;苒苒&amp;#x27;
      age=20
    }
    return {
      name,
      age,
      say
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;所以，在修改的时候要.value去修改，里面还是走的get与set去修改页面
&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d2ea4dacbe214770b189f4bac2125303~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1636706595(1).jpg&quot;&gt;
其实按道理的话，我们在页面上用的话应该要&lt;code&gt;{{name.value}}&lt;/code&gt;显示的，但是因为vue3检测到你是ref对象，它就自动给你.value了&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;function say(){
  name.value=&amp;#x27;苒苒&amp;#x27;
  age.value=20
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那么要是我定义的ref是个对象呢，因为我们知道尽管ref后会变成&lt;code&gt;RefImpl&lt;/code&gt;的实例对象，所以我们就用&lt;code&gt;XX.value.xx&lt;/code&gt;进行修改&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;home&amp;quot;&amp;gt;
    &amp;lt;h1&amp;gt;姓名：{{name}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;h1&amp;gt;年龄：{{age}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;h2&amp;gt;职业：{{job.occupation}}&amp;lt;&amp;#x2F;h2&amp;gt;
    &amp;lt;h2&amp;gt;薪资：{{job.salary}}&amp;lt;&amp;#x2F;h2&amp;gt;
    &amp;lt;button @click=&amp;quot;say&amp;quot;&amp;gt;修改&amp;lt;&amp;#x2F;button&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import {ref} from &amp;#x27;vue&amp;#x27;
export default {
  name: &amp;#x27;Home&amp;#x27;,
  setup(){
    let name = ref(&amp;#x27;燕儿&amp;#x27;)
    let age = ref(18)
    let job=ref({
      occupation:&amp;#x27;程序员&amp;#x27;,
      salary:&amp;#x27;10k&amp;#x27;
    })
    console.log(name)
    console.log(age)
    &amp;#x2F;&amp;#x2F;方法
    function say(){
      job.value.salary=&amp;#x27;12k&amp;#x27;
    }
    return {
      name,
      age,
      job,
      say
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是我们打印job.value,你会发现，它不再是&lt;code&gt;RefImpl&lt;/code&gt;实例对象，变成了&lt;code&gt;Proxy&lt;/code&gt;实例对象，他只是vue3底层，把对象都变成了&lt;code&gt;Proxy&lt;/code&gt;实例对象，对于基本数据类型就是按照&lt;code&gt;Object.defineProperty&lt;/code&gt;里面的&lt;code&gt;get&lt;/code&gt;和&lt;code&gt;set&lt;/code&gt;进行数据劫持然后进行响应式，但是如果是对象类型的话，是用到的&lt;code&gt;Proxy&lt;/code&gt;，但是vue3把它封装在新函数&lt;strong&gt;reactive&lt;/strong&gt;里，就相当于，ref中是对象，自动会调用&lt;strong&gt;reactive&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b45c187d48a1469eac0364e1739f5c37~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1636707807(1).jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;reactive&lt;/h4&gt;
&lt;p&gt;reactive只能定义对象类型的响应式数据，前面说到的ref里是对象的话，会自动调用&lt;strong&gt;reactive&lt;/strong&gt;，把&lt;code&gt;Object&lt;/code&gt;转换为&lt;code&gt;Proxy&lt;/code&gt;，那我们来打印一下，你会发现就直接变成了&lt;code&gt;Proxy&lt;/code&gt;，之前为什么会.value呢，是因为要去获取值，然后通过&lt;strong&gt;reactive&lt;/strong&gt;变成&lt;code&gt;Proxy&lt;/code&gt;，但是现在是直接通过&lt;strong&gt;reactive&lt;/strong&gt;变成&lt;code&gt;Proxy&lt;/code&gt;，而且它是进行的一个深层次的响应式，也可以进行数组的响应式&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;home&amp;quot;&amp;gt;
    &amp;lt;h1&amp;gt;姓名：{{name}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;h1&amp;gt;年龄：{{age}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;h2&amp;gt;职业：{{job.occupation}}&amp;lt;br&amp;gt;薪资：{{job.salary}}&amp;lt;&amp;#x2F;h2&amp;gt;
    &amp;lt;h3&amp;gt;爱好：{{hobby[0]}},{{hobby[1]}},{{ hobby[2] }}&amp;lt;&amp;#x2F;h3&amp;gt;
    &amp;lt;button @click=&amp;quot;say&amp;quot;&amp;gt;修改&amp;lt;&amp;#x2F;button&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import {ref,reactive} from &amp;#x27;vue&amp;#x27;
export default {
  name: &amp;#x27;Home&amp;#x27;,
  setup(){
    let name = ref(&amp;#x27;燕儿&amp;#x27;)
    let age = ref(18)
    let job=reactive({
      occupation:&amp;#x27;程序员&amp;#x27;,
      salary:&amp;#x27;10k&amp;#x27;
    })
    let hobby=reactive([&amp;#x27;刷剧&amp;#x27;,&amp;#x27;吃鸡&amp;#x27;,&amp;#x27;睡觉&amp;#x27;])
    console.log(name)
    console.log(age)
    &amp;#x2F;&amp;#x2F;方法
    function say(){
      job.salary=&amp;#x27;12k&amp;#x27;
      hobby[0]=&amp;#x27;学习&amp;#x27;
    }
    return {
      name,
      age,
      job,
      say,
      hobby
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;有些人可能觉得，哎呀，我记不住，我就用&lt;code&gt;ref&lt;/code&gt;，每次就&lt;code&gt;.value&lt;/code&gt;可以了，香香香。他喵的，你正常点，要是一个页面就几个数据的话那还好，要是一堆数据，不得把你&lt;code&gt;.value&lt;/code&gt;点的冒烟吗？，其实你可以按照之前vue2中data的形式来写，这样你就会觉得&lt;code&gt;reactive&lt;/code&gt;香的一批了&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;home&amp;quot;&amp;gt;
    &amp;lt;h1&amp;gt;姓名：{{data.name}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;h1&amp;gt;年龄：{{data.age}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;h2&amp;gt;职业：{{data.job.occupation}}&amp;lt;br&amp;gt;薪资：{{data.job.salary}}&amp;lt;&amp;#x2F;h2&amp;gt;
    &amp;lt;h3&amp;gt;爱好：{{data.hobby[0]}},{{data.hobby[1]}},{{ data.hobby[2] }}&amp;lt;&amp;#x2F;h3&amp;gt;
    &amp;lt;button @click=&amp;quot;say&amp;quot;&amp;gt;修改&amp;lt;&amp;#x2F;button&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import {reactive} from &amp;#x27;vue&amp;#x27;
export default {
  name: &amp;#x27;Home&amp;#x27;,
  setup(){
    let data=reactive({
      name:&amp;#x27;燕儿&amp;#x27;,
      age:18,
      job:{
        occupation:&amp;#x27;程序员&amp;#x27;,
        salary:&amp;#x27;10k&amp;#x27;
      },
      hobby:[&amp;#x27;刷剧&amp;#x27;,&amp;#x27;吃鸡&amp;#x27;,&amp;#x27;睡觉&amp;#x27;]
    })
    &amp;#x2F;&amp;#x2F;方法
    function say(){
      data.job.salary=&amp;#x27;12k&amp;#x27;
      data.hobby[0]=&amp;#x27;学习&amp;#x27;
    }
    return {
      data,
      say,
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;怎么样，是不是直接暴露出去个data就好了，这样起码更能理解，不会让人摸不着头脑为什么要&lt;code&gt;.value&lt;/code&gt;，是吧&lt;/p&gt;
&lt;h4&gt;ref与reactive的区别&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;ref用来定义：&lt;strong&gt;基本类型数据&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;ref通过&lt;code&gt;Object.defineProperty()&lt;/code&gt;的&lt;code&gt;get&lt;/code&gt;与&lt;code&gt;set&lt;/code&gt;来实现响应式（数据劫持）。&lt;/li&gt;
&lt;li&gt;ref定义的数据：操作数据需要&lt;code&gt;.value&lt;/code&gt;，读取数据时模板中直接读取不需要&lt;code&gt;.value&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;reactive用来定义：&lt;strong&gt;对象或数组类型数据&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;reactive通过使用&lt;strong&gt;Proxy&lt;/strong&gt;来实现响应式（数据劫持）, 并通过&lt;strong&gt;Reflect&lt;/strong&gt;操作源代码内部的数据。&lt;/li&gt;
&lt;li&gt;reactive定义的数据：操作数据与读取数据：均不需要&lt;code&gt;.value&lt;/code&gt;。
当然，我之前就说过，ref可以定义对象或数组的，它只是内部自动调用了&lt;strong&gt;reactive&lt;/strong&gt;来转换。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;vue3的响应式原理&lt;/h3&gt;
&lt;p&gt;说到vue3的响应式原理，那我们就不得不提一句vue2的响应式了，（狗都知道的一句）通过&lt;code&gt;Object.defineProperty&lt;/code&gt;的&lt;code&gt;get&lt;/code&gt;，&lt;code&gt;set&lt;/code&gt;来进行数据劫持，修改，从而响应式,但是它有什么缺点呢&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;由于只有get()、set() 方式，所以只能捕获到属性读取和修改操作，当 新增、删除属性时，捕获不到，导致界面也不会更新。&lt;/li&gt;
&lt;li&gt;直接通过下标修改数组，界面也不会自动更新。
ok，vue2就聊这么多，什么？你还想听vue2底层？那你就Alt+←，拜拜了您嘞。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于vue3中的响应式，我们用到的&lt;strong&gt;Proxy&lt;/strong&gt;，当然，我们在vue2里面知道，&lt;strong&gt;Proxy&lt;/strong&gt;是什么，是代理，当然，并不是只用到了它，还有个Window上的内置对象&lt;code&gt;Reflect（反射）&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过Proxy（代理）: 拦截对象中任意属性的变化, 包括：属性值的读写、属性的添加、属性的删除等。&lt;/li&gt;
&lt;li&gt;通过Reflect（反射）: 对源对象的属性进行操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const p=new Proxy(data, {
&amp;#x2F;&amp;#x2F; 读取属性时调用
    get (target, propName) {
    	return Reflect.get(target, propName)
    },
&amp;#x2F;&amp;#x2F;修改属性或添加属性时调用
    set (target, propName, value) {
    	return Reflect.set(target, propName, value)
    },
&amp;#x2F;&amp;#x2F;删除属性时调用
    deleteProperty (target, propName) {
    	return Reflect.deleteProperty(target, propName)
    }
}) 
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;computed，watch与watchEffect&lt;/h3&gt;
&lt;h4&gt;computed&lt;/h4&gt;
&lt;p&gt;在vue3中，把&lt;code&gt;computed&lt;/code&gt;变成为组合式API，那么就意味着你要去引入它，代码如下，一个简易的计算就完成了&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;home&amp;quot;&amp;gt;
    姓：&amp;lt;input type=&amp;quot;text&amp;quot; v-model=&amp;quot;names.familyName&amp;quot;&amp;gt;&amp;lt;br&amp;gt;
    名：&amp;lt;input type=&amp;quot;text&amp;quot; v-model=&amp;quot;names.lastName&amp;quot;&amp;gt;&amp;lt;br&amp;gt;
    姓名：{{fullName}}&amp;lt;br&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import {reactive,computed} from &amp;#x27;vue&amp;#x27;
export default {
  name: &amp;#x27;Home&amp;#x27;,
  setup(){
    let names=reactive({
      familyName:&amp;#x27;阿&amp;#x27;,
      lastName:&amp;#x27;斌&amp;#x27;
    })
    fullName=computed(()=&amp;gt;{
      return names.familyName+&amp;#x27;.&amp;#x27;+names.lastName
    })
    return {
      names,
      fullName
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;要是你去修改计算出来的东西，你知道会发生什么吗？警告的意思是计算出来的东西是一个只读属性。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/21f44a23aef5480c94583e13b0b21d2b~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1636807524(1).jpg&quot;&gt;
那要是我们想要修改怎么办呢,那么就要用到&lt;code&gt;computed&lt;/code&gt;的终结写法了&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;home&amp;quot;&amp;gt;
    姓：&amp;lt;input type=&amp;quot;text&amp;quot; v-model=&amp;quot;names.familyName&amp;quot;&amp;gt;&amp;lt;br&amp;gt;
    名：&amp;lt;input type=&amp;quot;text&amp;quot; v-model=&amp;quot;names.lastName&amp;quot;&amp;gt;&amp;lt;br&amp;gt;
    姓名：&amp;lt;input type=&amp;quot;text&amp;quot; v-model=&amp;quot;names.fullName&amp;quot;&amp;gt;&amp;lt;br&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import {reactive,computed} from &amp;#x27;vue&amp;#x27;
export default {
  name: &amp;#x27;Home&amp;#x27;,
  setup(){
    let names=reactive({
      familyName:&amp;#x27;阿&amp;#x27;,
      lastName:&amp;#x27;斌&amp;#x27;
    })
    names.fullName=computed({
      get(){
        return names.familyName+&amp;#x27;.&amp;#x27;+names.lastName
      },
      set(value){
        let  nameList=value.split(&amp;#x27;.&amp;#x27;)
        names.familyName=nameList[0]
        names.lastName=nameList[1]
      }
    })
    return {
      names
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是，yysy（有一说一），他喵的，我寻思也没有人会去改计算属性吧？如果有，就当我没说&lt;/p&gt;
&lt;h4&gt;watch&lt;/h4&gt;
&lt;p&gt;你可能会想到&lt;code&gt;computed&lt;/code&gt;都是组合式API，那么&lt;code&gt;watch&lt;/code&gt;会不会也是组合式API呢？大胆点，它也是，
那么我们就来进行监视&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;home&amp;quot;&amp;gt;
    &amp;lt;h1&amp;gt;当前数字为:{{num}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;button @click=&amp;quot;num++&amp;quot;&amp;gt;点击数字加一&amp;lt;&amp;#x2F;button&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import {ref,watch} from &amp;#x27;vue&amp;#x27;
export default {
  name: &amp;#x27;Home&amp;#x27;,
  setup(){
    let num=ref(&amp;#x27;0&amp;#x27;)
    watch(num,(newValue,oldValue)=&amp;gt;{
      console.log(&amp;#96;当前数字增加了,${newValue},${oldValue}&amp;#96;)
    })
    return {
      num
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当然这是监听&lt;code&gt;ref&lt;/code&gt;定义出来的单个响应式数据，要是监听多个数据应该怎么办呢？其实可以用多个&lt;code&gt;watch&lt;/code&gt;去进行监听，当然这不是最好的方法，最好的办法其实是监视数组&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt; watch([num,msg],(newValue,oldValue)=&amp;gt;{
      console.log(&amp;#x27;当前改变了&amp;#x27;,newValue,oldValue)
    })
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;既然我们监听的是数组，那么我们得到的&lt;code&gt;newValue&lt;/code&gt;和&lt;code&gt;oldValue&lt;/code&gt;也就是数组，那么数组中的第一个就是你监视的第一个参数。&lt;/p&gt;
&lt;p&gt;ps.当然之前在vue2中&lt;code&gt;watch&lt;/code&gt;不是有什么其他参数吗，vue3中也有,是写在最后的。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;watch([num,msg],(newValue,oldValue)=&amp;gt;{
      console.log(&amp;#x27;当前改变了&amp;#x27;,newValue,oldValue)
    },{immediate:true,deep:true})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;之前我说过，我们现在监听的是监听&lt;code&gt;ref&lt;/code&gt;定义出来数据，那么要是我们监听的是&lt;code&gt;reactive&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;home&amp;quot;&amp;gt;
    &amp;lt;h1&amp;gt;当前姓名:{{names.familyName}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;h1&amp;gt;当前年龄:{{names.age}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;h1&amp;gt;当前薪水:{{names.job.salary}}K&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;button @click=&amp;quot;names.familyName+=&amp;#x27;!&amp;#x27;&amp;quot;&amp;gt;点击加!&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;button @click=&amp;quot;names.age++&amp;quot;&amp;gt;点击加一&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;button @click=&amp;quot;names.job.salary++&amp;quot;&amp;gt;点击薪水加一&amp;lt;&amp;#x2F;button&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import {reactive,watch} from &amp;#x27;vue&amp;#x27;
export default {
  name: &amp;#x27;Home&amp;#x27;,
  setup(){
    let names=reactive({
      familyName: &amp;#x27;鳌&amp;#x27;,
      age:23,
      job:{
        salary:10
      }
    })
    watch(names,(newValue,oldValue)=&amp;gt;{
      console.log(&amp;#96;names改变了&amp;#96;,newValue,oldValue)
    },{deep:false})
    return {
      names
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是你会发现一个问题，为什么&lt;code&gt;newValue&lt;/code&gt;与&lt;code&gt;oldValue&lt;/code&gt;一样呢，就很尴尬，都是新的数据，就算你使用&lt;code&gt;ref&lt;/code&gt;来定义，还是没有办法监听到&lt;code&gt;oldValue&lt;/code&gt;（他喵的，都给你说了&lt;code&gt;ref&lt;/code&gt;定义的对象会自动调用&lt;code&gt;reactive&lt;/code&gt;）,所以&lt;strong&gt;在监视reactive定义的响应式数据时，oldValue无法正确获取&lt;/strong&gt;,并且你会发现，它是&lt;strong&gt;强制开启深度监视（&lt;code&gt;deep:true&lt;/code&gt;），并且无法关闭。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;然而现在我们监视的是&lt;code&gt;reactive&lt;/code&gt;定义的响应式数据的全部属性，是只监听其中的一个属性，那怎么办呢，可能大家会&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;watch(names.age,(newValue,oldValue)=&amp;gt;{
  console.log(&amp;#96;names改变了&amp;#96;,newValue,oldValue)
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;来进行监视，但是，vue3会警告只能监听&lt;code&gt;reactive&lt;/code&gt;定义的或者&lt;code&gt;ref&lt;/code&gt;定义的,并且不能监听。
&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a76b56471c0446579504381d723dd36f~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1636813377(1).jpg&quot;&gt;
那么我们就必须这样写（不会还有人不知道&lt;code&gt;return&lt;/code&gt;可以省略吧？不会吧？不会吧？不会那个人就是你吧？）&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;watch(()=&amp;gt;names.age,(newValue,oldValue)=&amp;gt;{
  console.log(&amp;#x27;names改变了&amp;#x27;,newValue,oldValue)
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那么要是我们监听的是多个属性，那怎么办呢？emmmm，你正常点，我上面都写了监听多个&lt;code&gt;ref&lt;/code&gt;定义的响应式数据，你就不会举一反三吗？敲代码很累的好吧！！！他喵的，为了防止你们问多个&lt;code&gt;reactive&lt;/code&gt;定义的一个属性，我就只能说和这个是一样的！！！能不能聪明点！！！&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;watch([()=&amp;gt;names.age,()=&amp;gt;names.familyName],(newValue,oldValue)=&amp;gt;{
  console.log(&amp;#x27;names改变了&amp;#x27;,newValue,oldValue)
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;ok，要是我们监听的是深度的属性那要怎么办呢？你会发现我要是只监听第一层是监听不到的，那么我们有两种写法&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;第一种
watch(()=&amp;gt; names.job.salary,(newValue,oldValue)=&amp;gt;{
  console.log(&amp;#x27;names改变了&amp;#x27;,newValue,oldValue)
})
&amp;#x2F;&amp;#x2F;第二种
watch(()=&amp;gt; names.job,(newValue,oldValue)=&amp;gt;{
  console.log(&amp;#x27;names改变了&amp;#x27;,newValue,oldValue)
},{deep:true})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那么我们就可以这样理解，&lt;strong&gt;如果监视的是&lt;code&gt;reactive&lt;/code&gt;定义的响应式数据的属性，并且这个属性是对象，那么我们可以开启深度监视&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;（人都写傻了，写了四千多字了，还没写完…）&lt;/p&gt;
&lt;h4&gt;watchEffect&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;watchEffect&lt;/code&gt;是vue3的新函数，它是来和&lt;code&gt;watch&lt;/code&gt;来抢饭碗的，它和&lt;code&gt;watch&lt;/code&gt;是一样的功能，那它有什么优势呢？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自动默认开启了&lt;code&gt;immediate:true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;用到了谁就监视谁&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;watchEffect(()=&amp;gt;{
    const one = num.value
    const tow = person.age
    console.log(&amp;#x27;watchEffect执行了&amp;#x27;)
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其实吧，&lt;code&gt;watchEffect&lt;/code&gt;有点像&lt;code&gt;computed&lt;/code&gt;，都是里面的值发生了改变就调用一次，但是呢&lt;code&gt;computed&lt;/code&gt;要写返回值,而&lt;code&gt;watchEffect&lt;/code&gt;不用写返回值。&lt;/p&gt;
&lt;h3&gt;生命周期&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8ef5cbcfa4dd4fc6a2cb536353f7026d~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;lifecycle.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/98911351167640b0b6092df1509a2fd4~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;2021060423231561.png&quot;&gt;
我们先来简单分析下，在vue2中，我们是先&lt;code&gt;new Vue()&lt;/code&gt;，然后执行&lt;code&gt;beforeCreate&lt;/code&gt;与&lt;code&gt;created&lt;/code&gt;接着问你有没有&lt;code&gt;vm.$mount(el)&lt;/code&gt;，有，才继续执行，但是在vue3中，它是先全部准备好后然后再进行函数。&lt;/p&gt;
&lt;p&gt;其实在vue3中生命周期没有多大的改变，只是改变了改变了销毁前，和销毁，让它更加语义化了
&lt;strong&gt;&lt;code&gt;beforeDestroy&lt;/code&gt;改名为&lt;code&gt;beforeUnmount&lt;/code&gt;,&lt;code&gt;destroyed&lt;/code&gt;改名为&lt;code&gt;unmounted&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;然后在vue3中，&lt;strong&gt;&lt;code&gt;beforeCreate&lt;/code&gt;与&lt;code&gt;created&lt;/code&gt;并没有组合式API中,&lt;code&gt;setup&lt;/code&gt;就相当于这两个生命周期函数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在vue3中也可以按照之前的生命周期函数那样写，只是要记得有些函数名称发生了改变&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;setup&lt;/code&gt;里面应该这样写&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;beforeCreate&lt;/code&gt;===&amp;gt;&lt;code&gt;Not needed*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;created&lt;/code&gt;=======&amp;gt;&lt;code&gt;Not needed*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;beforeMount&lt;/code&gt; ===&amp;gt;&lt;code&gt;onBeforeMount&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mounted&lt;/code&gt;=======&amp;gt;&lt;code&gt;onMounted&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;beforeUpdate&lt;/code&gt;===&amp;gt;&lt;code&gt;onBeforeUpdate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;updated&lt;/code&gt; =======&amp;gt;&lt;code&gt;onUpdated&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;beforeUnmount&lt;/code&gt; ==&amp;gt;&lt;code&gt;onBeforeUnmount&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;unmounted&lt;/code&gt; =====&amp;gt;&lt;code&gt;onUnmounted&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;hooks函数&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Vue3 的 hook函数 相当于 vue2 的 mixin, 不同在与 hooks 是函数&lt;/li&gt;
&lt;li&gt;Vue3 的 hook函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其实就是代码的复用，可以用到外部的数据，生命钩子函数…，具体怎么用直接看代码，&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;一般都是建一个hooks文件夹，都写在里面
import {reactive,onMounted,onBeforeUnmount} from &amp;#x27;vue&amp;#x27;
export default function (){
   &amp;#x2F;&amp;#x2F;鼠标点击坐标
   let point = reactive({
      x:0,
      y:0
   })

   &amp;#x2F;&amp;#x2F;实现鼠标点击获取坐标的方法
   function savePoint(event){
      point.x = event.pageX
      point.y = event.pageY
      console.log(event.pageX,event.pageY)
   }

   &amp;#x2F;&amp;#x2F;实现鼠标点击获取坐标的方法的生命周期钩子
   onMounted(()=&amp;gt;{
      window.addEventListener(&amp;#x27;click&amp;#x27;,savePoint)
   })

   onBeforeUnmount(()=&amp;gt;{
      window.removeEventListener(&amp;#x27;click&amp;#x27;,savePoint)
   })

   return point
}
&amp;#x2F;&amp;#x2F;在其他地方调用
import useMousePosition from &amp;#x27;.&amp;#x2F;hooks&amp;#x2F;useMousePosition&amp;#x27;
let point = useMousePosition（）
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;toRef与toRefs&lt;/h3&gt;
&lt;h4&gt;toRef&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;toRef&lt;/code&gt;翻译过来其实就是把什么变成&lt;code&gt;ref&lt;/code&gt;类型的数据，可能大家会觉得没有什么用，毕竟我们之前定义时就已经定义成&lt;code&gt;ref&lt;/code&gt;,但是你们想一想，我们在之前是怎么写的&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;home&amp;quot;&amp;gt;
    &amp;lt;h1&amp;gt;当前姓名:{{names.name}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;h1&amp;gt;当前年龄:{{names.age}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;h1&amp;gt;当前薪水:{{names.job.salary}}K&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;button @click=&amp;quot;names.name+=&amp;#x27;!&amp;#x27;&amp;quot;&amp;gt;点击加!&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;button @click=&amp;quot;names.age++&amp;quot;&amp;gt;点击加一&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;button @click=&amp;quot;names.job.salary++&amp;quot;&amp;gt;点击薪水加一&amp;lt;&amp;#x2F;button&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import {reactive} from &amp;#x27;vue&amp;#x27;
export default {
  name: &amp;#x27;Home&amp;#x27;,
  setup(){
    let names=reactive({
      name:&amp;#x27;老谭&amp;#x27;,
      age:23,
      job:{
        salary:10
      }
    })
    return {
      names
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;是不是一直都是用到代码&lt;code&gt;name.xx&lt;/code&gt;，可能你会说，那我就&lt;code&gt;return&lt;/code&gt;的时候不这样写，改成这样&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;return {
  name:names.name,
  age:names.age,
  salary:names.job.salary
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是你要是在页面进行操作时就不是响应式了，为什么呢？那是因为你现在暴露出去的是简简单单的字符串，字符串会有响应式吗？肯定没有呀，但是你要是用到了&lt;code&gt;toRef&lt;/code&gt;,那就是把&lt;code&gt;name.xx&lt;/code&gt;变为响应式，然后操作它时会自动的去修改&lt;code&gt;name&lt;/code&gt;里面的数据&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;return {
  name:toRef(names,&amp;#x27;name&amp;#x27;),
  age:toRef(names,&amp;#x27;age&amp;#x27;),
  salary:toRef(names.job,&amp;#x27;salary&amp;#x27;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是有的人可能会说那我为什么不用&lt;code&gt;ref&lt;/code&gt;去改变？可能大家会发现在页面里也是响应式的，但是我告诉你们，根本用到的不是&lt;code&gt;names&lt;/code&gt;里面的数据，而是你单独定义出来的数据，所以要是这样写，无论怎么修改都不会修改到&lt;code&gt;names&lt;/code&gt;里的数据&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;return {
  name:ref(names.name),
  age:ref(names.age),
  salary:ref(names.job.salary),
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;toRefs&lt;/h4&gt;
&lt;p&gt;聪明一点，&lt;code&gt;toRefs&lt;/code&gt;与&lt;code&gt;toRef&lt;/code&gt;有什么不同，加了个&lt;code&gt;s&lt;/code&gt;,&lt;code&gt;toRef&lt;/code&gt;是单个转化为响应式，那&lt;code&gt;toRefs&lt;/code&gt;就是多个转化为响应式咯，这样的话就减少代码，不然要是有成千上万个，那你不是要当憨憨闷写吗？（&lt;code&gt;...&lt;/code&gt;是结构哈,看不懂就麻溜的alt+←)，当然它只会结构一层，深层里的代码还是要老实的写&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt; &amp;lt;h1&amp;gt;当前姓名:{{name}}&amp;lt;&amp;#x2F;h1&amp;gt;
 &amp;lt;h1&amp;gt;当前薪水:{{job.salary}}K&amp;lt;&amp;#x2F;h1&amp;gt;
return {
    ...toRefs(names)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;其他组合式API（了解，我寻思你基本上不会用）&lt;/h2&gt;
&lt;h3&gt;shallowReactive 与 shallowRef&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;shallowReactive&lt;/code&gt;浅层次的响应式,它就是只把第一层的数据变为响应式，深层的数据不会变为响应式,&lt;code&gt;shallowRef&lt;/code&gt;如果定义的是基本类型的数据，那么它和&lt;code&gt;ref&lt;/code&gt;是一样的不会有什么改变，但是要是定义的是对象类型的数据，那么它就不会进行响应式，之前我们说过如果&lt;code&gt;ref&lt;/code&gt;定义的是对象，那么它会自动调用&lt;code&gt;reactive&lt;/code&gt;变为&lt;code&gt;Proxy&lt;/code&gt;,但是要是用到的是&lt;code&gt;shallowRef&lt;/code&gt;那么就不会调用&lt;code&gt;reactive&lt;/code&gt;去进行响应式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;shallowReactive：只处理对象最外层属性的响应式（浅响应式）。&lt;/strong&gt;
&lt;strong&gt;shallowRef：只处理基本数据类型的响应式, 不进行对象的响应式处理。&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;let person = shallowReactive({
 name:&amp;#x27;大理段氏&amp;#x27;,
 age:10,
 job:{
   salary:20
 }
})
let x = shallowRef({
 y:0
})
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;readonly 与 shallowReadonly&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;readonly&lt;/code&gt;是接收了一个响应式数据然后重新赋值，返回的数据就不允许修改（深层只读），&lt;code&gt;shallowReadonly&lt;/code&gt;却只是浅层只读（第一层只读，其余层可以进行修改）&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;names=readonly(names)
names=shallowReadonly(names)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;toRaw 与 markRaw&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;toRaw&lt;/code&gt;其实就是将一个由&lt;code&gt;reactive&lt;/code&gt;生成的&lt;strong&gt;响应式对象&lt;/strong&gt;转为&lt;strong&gt;普通对象&lt;/strong&gt;。如果是&lt;code&gt;ref&lt;/code&gt;定义的话，是没有效果的（包括&lt;code&gt;ref&lt;/code&gt;定义的对象）&lt;strong&gt;如果在后续操作中对数据进行了添加的话，添加的数据为响应式数据&lt;/strong&gt;，当然要是将数据进行&lt;code&gt;markRaw&lt;/code&gt;操作后就不会变为响应式，可能大家会说，不就是和&lt;code&gt;readonly&lt;/code&gt;一样吗？那肯定不一样咯，&lt;code&gt;readonly&lt;/code&gt;是根本没办法改，但&lt;code&gt;markRaw&lt;/code&gt;是不转化为响应式，但是数据还会发生改变。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;home&amp;quot;&amp;gt;
    &amp;lt;h1&amp;gt;当前姓名:{{names.name}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;h1&amp;gt;当前年龄:{{names.age}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;h1&amp;gt;当前薪水:{{names.job.salary}}K&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;h1 v-if=&amp;quot;names.girlFriend&amp;quot;&amp;gt;女朋友：{{names.girlFriend}}&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;button @click=&amp;quot;names.name+=&amp;#x27;!&amp;#x27;&amp;quot;&amp;gt;点击加!&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;button @click=&amp;quot;addAges&amp;quot;&amp;gt;点击加一&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;button @click=&amp;quot;addSalary&amp;quot;&amp;gt;点击薪水加一&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;button @click=&amp;quot;add&amp;quot;&amp;gt;添加女朋友&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;button @click=&amp;quot;addAge&amp;quot;&amp;gt;添加女朋友年龄&amp;lt;&amp;#x2F;button&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import {reactive,toRaw,markRaw} from &amp;#x27;vue&amp;#x27;
export default {
  name: &amp;#x27;Home&amp;#x27;,
  setup(){
    let names=reactive({
      name:&amp;#x27;老伍&amp;#x27;,
      age:23,
      job:{
        salary:10
      }
    })
    function addAges(){
      names.age++
      console.log(names)
    }
    function addSalary(){
      let fullName=toRaw(names)
      fullName.job.salary++
      console.log(fullName)
    }
    function add(){
      let girlFriend={sex:&amp;#x27;女&amp;#x27;,age:40}
      names.girlFriend=markRaw(girlFriend)
    }
    function addAge(){
      names.girlFriend.age++
      console.log(names.girlFriend.age)
    }
    return {
      names,
      add,
      addAge,
      addAges,
      addSalary
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;customRef&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;customRef&lt;/code&gt;创建一个自定义的 ref，并对其依赖项跟踪和更新触发进行显式控制。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;单纯觉得这个东西的作用只有防抖的作用（要是知道其他的用法可以告知一下我）&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;input type=&amp;quot;text&amp;quot; v-model=&amp;quot;keyWord&amp;quot;&amp;gt;
  &amp;lt;h3&amp;gt;{{keyWord}}&amp;lt;&amp;#x2F;h3&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import {customRef} from &amp;#x27;vue&amp;#x27;
export default {
  name: &amp;#x27;App&amp;#x27;,
  setup() {
    &amp;#x2F;&amp;#x2F;自定义一个ref——名为：myRef
    function myRef(value,times){
      let time
      return customRef((track,trigger)=&amp;gt;{
        return {
          get(){
            console.log(&amp;#96;有人从myRef中读取数据了，我把${value}给他了&amp;#96;)
            track() &amp;#x2F;&amp;#x2F;通知Vue追踪value的变化（必须要有，并且必须要在return之前）
            return value
          },
          set(newValue){
            console.log(&amp;#96;有人把myRef中数据改为了：${newValue}&amp;#96;)
            clearTimeout(time)
            time = setTimeout(()=&amp;gt;{
              value = newValue
              trigger() &amp;#x2F;&amp;#x2F;通知Vue去重新解析模板（必须要有）
            },times)
          },
        }
      })
    }
    let keyWord = myRef(&amp;#x27;HelloWorld&amp;#x27;,1000) &amp;#x2F;&amp;#x2F;使用自定义的ref

    return {keyWord}
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;给你们说个趣事吧，顺便你们讲一下防抖节流的区别，之前有人在我公司面试的时候哈，给我说对项目进行了防抖节流处理，但是我细问防抖与节流的区别时却支支吾吾的，就很尴尬，然后我们就心照不宣的跳过了这个话题&lt;/p&gt;
&lt;p&gt;我是这样理解的&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;防抖：在第一次触发事件时，不立即执行函数，而是给出一个时间段，如果短时间内大量触发同一事件，只会执行一次函数。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;节流：函数执行一次后，在某个时间段内暂时失效，过了这段时间后再重新激活，如果短时间内大量触发同一事件，那么在函数执行一次之后，该函数在指定的时间期限内不再生效，直至过了这段时间才重新生效。&lt;/strong&gt;（可以理解为游戏技能冷却期）&lt;/p&gt;
&lt;p&gt;如果还是不是很能理解的话去看大佬的 &lt;a href=&quot;https://juejin.cn/post/7016502001911463950#heading-5&quot;&gt;一杯茶的时间，带你彻底学会手写防抖节流&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;provide 与 inject&lt;/h3&gt;
&lt;p&gt;都知道组件传值吧，在vue2中，如果要在后代组件中使用父组件的数据，那么要一层一层的父子组件传值或者用到&lt;code&gt;vuex&lt;/code&gt;，但是现在，无论组件层次结构有多深，父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分：父组件有一个 &lt;code&gt;provide&lt;/code&gt; 选项来提供数据，子组件有一个 &lt;code&gt;inject&lt;/code&gt; 选项来开始使用这些数据。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b3f7e77d5057475a91010b7d3608fa93~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;components_provide.png&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;父
import { provide } from &amp;#x27;vue&amp;#x27;
setup(){
 let fullname = reactive({name:&amp;#x27;阿月&amp;#x27;,salary:&amp;#x27;15k&amp;#x27;})
 provide(&amp;#x27;fullname&amp;#x27;,fullname) &amp;#x2F;&amp;#x2F;给自己的后代组件传递数据
 return {...toRefs(fullname)}
}
&amp;#x2F;&amp;#x2F;后代
import {inject} from &amp;#x27;vue&amp;#x27;
setup(){
 let fullname = inject(&amp;#x27;fullname&amp;#x27;)
 return {fullname}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;当然子组件也可以用，但是请记住，父子组件传参是有方法的！！！别瞎搞。&lt;/p&gt;
&lt;h3&gt;响应式判断&lt;/h3&gt;
&lt;p&gt;下面是vue3给的一些判断方法&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;isRef: 检查值是否为一个 ref 对象。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;isReactive：检查对象是否是由 &lt;a href=&quot;https://v3.cn.vuejs.org/api/basic-reactivity.html#reactive&quot;&gt;&lt;code&gt;reactive&lt;/code&gt;&lt;/a&gt; 创建的响应式代理。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;isReadonly: 检查对象是否是由 &lt;a href=&quot;https://v3.cn.vuejs.org/api/basic-reactivity.html#readonly&quot;&gt;&lt;code&gt;readonly&lt;/code&gt;&lt;/a&gt; 创建的只读代理。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;isProxy：检查对象是否是由 &lt;a href=&quot;https://v3.cn.vuejs.org/api/basic-reactivity.html#reactive&quot;&gt;&lt;code&gt;reactive&lt;/code&gt;&lt;/a&gt; 或 &lt;a href=&quot;https://v3.cn.vuejs.org/api/basic-reactivity.html#readonly&quot;&gt;&lt;code&gt;readonly&lt;/code&gt;&lt;/a&gt; 创建的 proxy。&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;import {ref, reactive,readonly,isRef,isReactive,isReadonly,isProxy } from &amp;#x27;vue&amp;#x27;
export default {
  name:&amp;#x27;App&amp;#x27;,
  setup(){
    let fullName = reactive({name:&amp;#x27;小唐&amp;#x27;,price:&amp;#x27;20k&amp;#x27;})
    let num = ref(0)
    let fullNames = readonly(fullName)
    console.log(isRef(num))
    console.log(isReactive(fullName))
    console.log(isReadonly(fullNames))
    console.log(isProxy(fullName))
    console.log(isProxy(fullNames))
    console.log(isProxy(num))
    return {}
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;有趣的组件&lt;/h2&gt;
&lt;h3&gt;Fragment&lt;/h3&gt;
&lt;p&gt;对我而言这个更像是一种概念，它的意思就相当于创建页面时，给了一个虚拟根标签&lt;code&gt;VNode&lt;/code&gt;，因为我们知道在vue2里面，我们是有根标签这个概念的，但是到来vue3，它是自动给你创建个虚拟根标签&lt;code&gt;VNode&lt;/code&gt;（&lt;code&gt;Fragment&lt;/code&gt;）,所以可以不要根标签。好处就是
&lt;strong&gt;减少标签层级, 减小内存占用&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Teleport&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;teleport&lt;/code&gt; 提供了一种有趣的方法，允许我们控制在 DOM 中哪个父节点下渲染了 HTML，而不必求助于全局状态或将其拆分为两个组件。&lt;/p&gt;
&lt;p&gt;其实就是可以不考虑你写在什么位置，你可以定义&lt;code&gt;teleport&lt;/code&gt;在任意标签里进行定位等（常见操作为模态框），除了body外，还可以写css选择器（&lt;code&gt;id，class&lt;/code&gt;）&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;id定位
 &amp;lt;teleport to=&amp;quot;#app&amp;quot;&amp;gt;
            &amp;lt;div class=&amp;quot;four&amp;quot;&amp;gt;
              &amp;lt;div class=&amp;quot;five&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;div&amp;gt;
            &amp;lt;&amp;#x2F;div&amp;gt;
 &amp;lt;&amp;#x2F;teleport&amp;gt;
&amp;#x2F;&amp;#x2F;class定位
 &amp;lt;teleport to=&amp;quot;.one&amp;quot;&amp;gt;
            &amp;lt;div class=&amp;quot;four&amp;quot;&amp;gt;
              &amp;lt;div class=&amp;quot;five&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;div&amp;gt;
            &amp;lt;&amp;#x2F;div&amp;gt;
 &amp;lt;&amp;#x2F;teleport&amp;gt;
&amp;#x2F;&amp;#x2F;示例
&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;one&amp;quot;&amp;gt;
    &amp;lt;h1&amp;gt;第一层&amp;lt;&amp;#x2F;h1&amp;gt;
    &amp;lt;div class=&amp;quot;two&amp;quot;&amp;gt;
      &amp;lt;h1&amp;gt;第二层&amp;lt;&amp;#x2F;h1&amp;gt;
      &amp;lt;div class=&amp;quot;three&amp;quot;&amp;gt;
        &amp;lt;h1&amp;gt;第三层&amp;lt;&amp;#x2F;h1&amp;gt;
        &amp;lt;teleport to=&amp;quot;body&amp;quot;&amp;gt;
            &amp;lt;div class=&amp;quot;four&amp;quot;&amp;gt;
              &amp;lt;div class=&amp;quot;five&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;div&amp;gt;
            &amp;lt;&amp;#x2F;div&amp;gt;
        &amp;lt;&amp;#x2F;teleport&amp;gt;
      &amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name:&amp;#x27;App&amp;#x27;,
  setup(){
    return {}
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;style lang=&amp;quot;less&amp;quot;&amp;gt;
.one{
  width: 100%;
  background-color: blue;
  .two{
    margin: 20px;
    background-color: aqua;
    .three{
      margin: 20px;
      background-color: aliceblue;
    }
  }
}
.four{
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: rgba(0, 0, 0, 0.5);
  .five{
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
    width: 300px;
    height: 300px;
    left: 50%;
    background-color:#f60;
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;打开控制台然后触发事件，观看效果&lt;/p&gt;
&lt;h3&gt;Suspense&lt;/h3&gt;
&lt;p&gt;大家都知道在渲染组件之前进行一些异步请求是很常见的事，&lt;code&gt;suspense&lt;/code&gt; 组件提供了一个方案，允许将等待过程提升到组件树中处理，而不是在单个组件中。但是！！！在vue3中特别说明了，&lt;strong&gt;Suspense 是一个试验性的新特性，其 API 可能随时会发生变动。特此声明，以便社区能够为当前的实现提供反馈。&lt;/strong&gt;，我直接尬住，以后估计会发生改变，但是你们愿意卷的话可以去看文章&lt;a href=&quot;https://juejin.cn/post/6977869719546101796#heading-12&quot;&gt;vue3的传送门teleport究竟有多神奇？suspense发起异步请求有多简约？&lt;/a&gt;，嗯，记得写800字观后感&lt;/p&gt;
&lt;h2&gt;vue3其他改动&lt;/h2&gt;
&lt;h3&gt;router&lt;/h3&gt;
&lt;p&gt;可能大家会想到路由跳转的问题，可能大家会以为还是用&lt;code&gt;this.$router.push&lt;/code&gt;来进行跳转，但是哦，在vue3中，这些东西是没有的，它是定义了一个&lt;code&gt;vue-router&lt;/code&gt;然后引入的&lt;code&gt;useRoute&lt;/code&gt;,&lt;code&gt;useRouter&lt;/code&gt; 相当于vue2的 &lt;code&gt;this.$route&lt;/code&gt;，&lt;code&gt;this.$router&lt;/code&gt;,然后其他之前vue2的操作都可以进行&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;import {useRouter,useRoute} from &amp;quot;vue-router&amp;quot;;
setup(){
  const router= useRouter()
  const route= useRoute()
  function fn(){
    this.$route.push(&amp;#x27;&amp;#x2F;about&amp;#x27;)
  }
  onMounted(()=&amp;gt;{
    console.log(route.query.code)
  })
  return{
    fn
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;全局API的转移&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;2.x 全局 API（&lt;code&gt;Vue&lt;/code&gt;）&lt;/th&gt;
&lt;th&gt;3.x 实例 API (&lt;code&gt;app&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vue.config.xxxx&lt;/td&gt;
&lt;td&gt;app.config.xxxx&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vue.config.productionTip&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;移除&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vue.component&lt;/td&gt;
&lt;td&gt;app.component&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vue.directive&lt;/td&gt;
&lt;td&gt;app.directive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vue.mixin&lt;/td&gt;
&lt;td&gt;app.mixin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vue.use&lt;/td&gt;
&lt;td&gt;app.use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vue.prototype&lt;/td&gt;
&lt;td&gt;app.config.globalProperties&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;其他改变&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;移除&lt;/strong&gt;keyCode作为 v-on 的修饰符，同时也不再支持&lt;code&gt;config.keyCodes&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;移除&lt;/strong&gt;&lt;code&gt;v-on.native&lt;/code&gt;修饰符&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;移除&lt;/strong&gt;过滤器（&lt;code&gt;filter&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;p&gt;对了有手痒的想实战项目的话可以去看一看大佬的&lt;a href=&quot;https://juejin.cn/post/6945072070132760590&quot;&gt;Vue 3 + Element Plus + Vite 2 的后台管理系统开源啦&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;完结撒花&lt;/h1&gt;
&lt;p&gt;&lt;em&gt;在这里感谢各位大佬们的vue3讲解，感谢他们的大力支持！！！&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;感谢蒙古海军对本栏目的大力支持。蒙古海军：‘我们要征服的可是星辰大海！’&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;感谢素未谋面的女友对本栏目的大力支持。‘躲猫猫是吧？你藏这么隐蔽我怎么去找？快给我留线索！！！立刻马上！’&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 迷鹿鱼
        原文链接：&lt;a href=&quot;https://juejin.im/post/7030992475271495711&quot;&gt;https://juejin.im/post/7030992475271495711&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Wed, 12 Jan 2022 15:13:58 GMT</pubDate></item><item><title>遇事不决nextTick，为什么​nextTick这么神奇？__前端__Vue.js</title><link>http://vue-js.com/topic/61def036cbbfd1003b11f95d</link><guid>http://vue-js.com/topic/61def036cbbfd1003b11f95d</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h3&gt;场景说明&lt;/h3&gt;
&lt;p&gt;最近使用Vue全家桶做后台系统的时候，遇到了一个很奇葩的问题：有一个输入框只允许输入数字，当输入其它类型的数据时，输入的内容会被重置为null。为了实现这一功能，使用了一个父组件和子组件。为了方便陈述，这里将业务场景简化，具体代码如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 父组件
&amp;lt;template&amp;gt;
  &amp;lt;Input v-model=&amp;quot;form.a&amp;quot; @on-change=&amp;quot;onChange&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;Input&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script type=&amp;quot;javascript&amp;quot;&amp;gt;
export default {
	data() {
    return {
      form: {
        a: null
      }
    }
  },
  methods: {
    async onChange(value) {
   		if (typeof value !== &amp;#x27;number&amp;#x27;) {
        &amp;#x2F;&amp;#x2F; await this.$nextTick()
        this.form.a = null
      }   
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;#x2F;&amp;#x2F; 子组件
&amp;lt;template&amp;gt;
  &amp;lt;input v-model=&amp;quot;currentValue&amp;quot; @input=&amp;quot;onInput&amp;quot; &amp;#x2F;&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script type=&amp;quot;javascript&amp;quot;&amp;gt;
export default {
	name: &amp;#x27;Input&amp;#x27;,
	props: {
    value: {
      type: [Number, Null],
      default: null 
    }
  },
	data() {
    return {
      currentValue: null
    }
  },
  methods: {
    onInput(event) {
      const value = event.target.value
      this.$emit(&amp;#x27;input&amp;#x27;, value)
      const oldValue = this.value
      if (oldValue === value) return
   		this.$emit(&amp;#x27;on-change&amp;#x27;, value)
    }
  },
  watch: {
    value(value, oldValue) {
      this.currentValue = value
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;将以上代码放到项目中去运行，你会很神奇地发现，在输入框输入字符串’abc’之后，输入框的值并没有被重置为空，而是保持为’abc’没有变化。在将注释的nextTick取消注释以后，输入框的值被重置为空。真的非常神奇。&lt;/p&gt;
&lt;p&gt;其实之前有好几次同事也碰到了类似的场景：数据层发生了变化，dom并没有随之响应。在数据层发生变化以后，执行一次nextTick，dom就按照预期地更新了。这样几次以后，我们甚至都调侃：遇事不决nextTick。&lt;/p&gt;
&lt;h3&gt;代码执行顺序&lt;/h3&gt;
&lt;p&gt;那么，到底nextTick做了什么呢？这里以上面的代码为例，我们先来理一理我们代码是怎么执行的。具体来说，以上代码执行顺序如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;form.a初始值为null&lt;/li&gt;
&lt;li&gt;用户输入字符串abc&lt;/li&gt;
&lt;li&gt;触发input事件，form.a的值改为abc&lt;/li&gt;
&lt;li&gt;触发on-change事件，form.a的值改为null&lt;/li&gt;
&lt;li&gt;由于form.a的值到这里还是为null&lt;/li&gt;
&lt;li&gt;主线程任务执行完毕，检查watch的回调函数是否需要执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个顺序一理，我们就发现了输入框展示abc不置空的原因：原来form.a的值在主线程中间虽然发生了变化，但是最开始到最后始终为null。也就是说，子组件的props的value没有发生变化。自然，watch的回调函数也就不会执行。&lt;/p&gt;
&lt;p&gt;但是这样一来，我们就有另外一个问题了：为什么触发input事件，form.a的值改为null的时候，没有触发watch的回调呢？为了说明这一点，我们需要深入Vue源码，看看$emit和watch的回调函数分别是在什么时候执行的。&lt;/p&gt;
&lt;h3&gt;$emit做了什么？&lt;/h3&gt;
&lt;p&gt;我们首先看看$emit对应的源码。由于Vue 2.X版本源码是使用flow写的，无形中增加了理解成本。考虑到这一点，我们直接找到Vue的dist包中的vue.js文件，并搜索emit函数&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;Vue.prototype.$emit = function (event) {
  var vm = this;
  {
    var lowerCaseEvent = event.toLowerCase();
    if (lowerCaseEvent !== event &amp;amp;&amp;amp; vm._events[lowerCaseEvent]) {
      tip(
        &amp;quot;Event &amp;#x5C;&amp;quot;&amp;quot; + lowerCaseEvent + &amp;quot;&amp;#x5C;&amp;quot; is emitted in component &amp;quot; +
        (formatComponentName(vm)) + &amp;quot; but the handler is registered for &amp;#x5C;&amp;quot;&amp;quot; + event + &amp;quot;&amp;#x5C;&amp;quot;. &amp;quot; +
        &amp;quot;Note that HTML attributes are case-insensitive and you cannot use &amp;quot; +
        &amp;quot;v-on to listen to camelCase events when using in-DOM templates. &amp;quot; +
        &amp;quot;You should probably use &amp;#x5C;&amp;quot;&amp;quot; + (hyphenate(event)) + &amp;quot;&amp;#x5C;&amp;quot; instead of &amp;#x5C;&amp;quot;&amp;quot; + event + &amp;quot;&amp;#x5C;&amp;quot;.&amp;quot;
      );
    }
  }
  var cbs = vm._events[event];
  if (cbs) {
    cbs = cbs.length &amp;gt; 1 ? toArray(cbs) : cbs;
    var args = toArray(arguments, 1);
    var info = &amp;quot;event handler for &amp;#x5C;&amp;quot;&amp;quot; + event + &amp;quot;&amp;#x5C;&amp;quot;&amp;quot;;
    for (var i = 0, l = cbs.length; i &amp;lt; l; i++) {
      invokeWithErrorHandling(cbs[i], vm, args, vm, info);
    }
  }
  return vm
};

function invokeWithErrorHandling (
  handler,
  context,
  args,
  vm,
  info
) {
  var res;
  try {
    res = args ? handler.apply(context, args) : handler.call(context);
    if (res &amp;amp;&amp;amp; !res._isVue &amp;amp;&amp;amp; isPromise(res) &amp;amp;&amp;amp; !res._handled) {
      res.catch(function (e) { return handleError(e, vm, info + &amp;quot; (Promise&amp;#x2F;async)&amp;quot;); });
      &amp;#x2F;&amp;#x2F; issue #9511
      &amp;#x2F;&amp;#x2F; avoid catch triggering multiple times when nested calls
      res._handled = true;
    }
  } catch (e) {
    handleError(e, vm, info);
  }
  return res
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;源码的内容其实很简单，就是把提前注册（或者说订阅）的函数放到一个数组中，执行$emit函数时就把数组中的函数一一取出并执行。可以看出，这是一个发布-订阅模式的使用。&lt;/p&gt;
&lt;p&gt;也就是说，emit的执行是同步的。那么，watch是怎么执行的呢？相比之下，watch的执行会比较繁琐。理解了watch的流程，也就理解了Vue的核心。&lt;/p&gt;
&lt;p&gt;首先，在初始化Vue组件时，有一个initWatch函数，我们来看看这个函数做了什么。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;function initWatch (vm, watch) {
  for (var key in watch) {
    var handler = watch[key];
    if (Array.isArray(handler)) {
      for (var i = 0; i &amp;lt; handler.length; i++) {
        createWatcher(vm, key, handler[i]);
      }
    } else {
      createWatcher(vm, key, handler);
    }
  }
}

function createWatcher (
  vm,
  expOrFn,
  handler,
  options
) {
  if (isPlainObject(handler)) {
    options = handler;
    handler = handler.handler;
  }
  if (typeof handler === &amp;#x27;string&amp;#x27;) {
    handler = vm[handler];
  }
  return vm.$watch(expOrFn, handler, options)
}

Vue.prototype.$watch = function (
  expOrFn,
  cb,
  options
) {
  var vm = this;
  if (isPlainObject(cb)) {
    return createWatcher(vm, expOrFn, cb, options)
  }
  options = options || {};
  options.user = true;
  var watcher = new Watcher(vm, expOrFn, cb, options);
  if (options.immediate) {
    try {
      cb.call(vm, watcher.value);
    } catch (error) {
      handleError(error, vm, (&amp;quot;callback for immediate watcher &amp;#x5C;&amp;quot;&amp;quot; + (watcher.expression) + &amp;quot;&amp;#x5C;&amp;quot;&amp;quot;));
    }
  }
  return function unwatchFn () {
    watcher.teardown();
  }
}

var Watcher = function Watcher (
  vm,
  expOrFn,
  cb,
  options,
  isRenderWatcher
) {
  this.vm = vm;
  if (isRenderWatcher) {
    vm._watcher = this;
  }
  vm._watchers.push(this);
  &amp;#x2F;&amp;#x2F; options
  if (options) {
    this.deep = !!options.deep;
    this.user = !!options.user;
    this.lazy = !!options.lazy;
    this.sync = !!options.sync;
    this.before = options.before;
  } else {
    this.deep = this.user = this.lazy = this.sync = false;
  }
  this.cb = cb;
  this.id = ++uid$2; &amp;#x2F;&amp;#x2F; uid for batching
  this.active = true;
  this.dirty = this.lazy; &amp;#x2F;&amp;#x2F; for lazy watchers
  this.deps = [];
  this.newDeps = [];
  this.depIds = new _Set();
  this.newDepIds = new _Set();
  this.expression = expOrFn.toString();
  &amp;#x2F;&amp;#x2F; parse expression for getter
  if (typeof expOrFn === &amp;#x27;function&amp;#x27;) {
    this.getter = expOrFn;
  } else {
    this.getter = parsePath(expOrFn);
    if (!this.getter) {
      this.getter = noop;
      warn(
        &amp;quot;Failed watching path: &amp;#x5C;&amp;quot;&amp;quot; + expOrFn + &amp;quot;&amp;#x5C;&amp;quot; &amp;quot; +
        &amp;#x27;Watcher only accepts simple dot-delimited paths. &amp;#x27; +
        &amp;#x27;For full control, use a function instead.&amp;#x27;,
        vm
      );
    }
  }
  this.value = this.lazy
    ? undefined
    : this.get();
}

function parsePath (path) {
  if (bailRE.test(path)) {
    return
  }
  var segments = path.split(&amp;#x27;.&amp;#x27;);
  return function (obj) {
    for (var i = 0; i &amp;lt; segments.length; i++) {
      if (!obj) { return }
      obj = obj[segments[i]];
    }
    return obj
  }
}

Watcher.prototype.get = function get () {
  pushTarget(this);
  var value;
  var vm = this.vm;
  try {
    value = this.getter.call(vm, vm);
  } catch (e) {
    if (this.user) {
      handleError(e, vm, (&amp;quot;getter for watcher &amp;#x5C;&amp;quot;&amp;quot; + (this.expression) + &amp;quot;&amp;#x5C;&amp;quot;&amp;quot;));
    } else {
      throw e
    }
  } finally {
    &amp;#x2F;&amp;#x2F; &amp;quot;touch&amp;quot; every property so they are all tracked as
    &amp;#x2F;&amp;#x2F; dependencies for deep watching
    if (this.deep) {
      traverse(value);
    }
    popTarget();
    this.cleanupDeps();
  }
  return value
}

function defineReactive$$1 (
  obj,
  key,
  val,
  customSetter,
  shallow
) {
  var dep = new Dep();

  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property &amp;amp;&amp;amp; property.configurable === false) {
    return
  }

  &amp;#x2F;&amp;#x2F; cater for pre-defined getter&amp;#x2F;setters
  var getter = property &amp;amp;&amp;amp; property.get;
  var setter = property &amp;amp;&amp;amp; property.set;
  if ((!getter || setter) &amp;amp;&amp;amp; arguments.length === 2) {
    val = obj[key];
  }

  var childOb = !shallow &amp;amp;&amp;amp; observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      &amp;#x2F;* eslint-disable no-self-compare *&amp;#x2F;
      if (newVal === value || (newVal !== newVal &amp;amp;&amp;amp; value !== value)) {
        return
      }
      &amp;#x2F;* eslint-enable no-self-compare *&amp;#x2F;
      if (customSetter) {
        customSetter();
      }
      &amp;#x2F;&amp;#x2F; #7981: for accessor properties without setter
      if (getter &amp;amp;&amp;amp; !setter) { return }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow &amp;amp;&amp;amp; observe(newVal);
      dep.notify();
    }
  });
}

var Dep = function Dep () {
  this.id = uid++;
  this.subs = [];
}

Dep.prototype.addSub = function addSub (sub) {
  this.subs.push(sub);
};

Dep.prototype.removeSub = function removeSub (sub) {
  remove(this.subs, sub);
};

Dep.prototype.depend = function depend () {
  if (Dep.target) {
    Dep.target.addDep(this);
  }
};

Dep.prototype.notify = function notify () {
  &amp;#x2F;&amp;#x2F; stabilize the subscriber list first
  var subs = this.subs.slice();
  if (!config.async) {
    &amp;#x2F;&amp;#x2F; subs aren&amp;#x27;t sorted in scheduler if not running async
    &amp;#x2F;&amp;#x2F; we need to sort them now to make sure they fire in correct
    &amp;#x2F;&amp;#x2F; order
    subs.sort(function (a, b) { return a.id - b.id; });
  }
  for (var i = 0, l = subs.length; i &amp;lt; l; i++) {
    subs[i].update();
  }
}

Watcher.prototype.update = function update () {
  &amp;#x2F;* istanbul ignore else *&amp;#x2F;
  if (this.lazy) {
    this.dirty = true;
  } else if (this.sync) {
    this.run();
  } else {
    queueWatcher(this);
  }
}

Dep.target = null;
var targetStack = [];

function pushTarget (target) {
  targetStack.push(target);
  Dep.target = target;
}

function popTarget () {
  targetStack.pop();
  Dep.target = targetStack[targetStack.length - 1];
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们看到，watch相关联的函数接近20个。这么多函数在来回跳的时候，很容易把逻辑弄丢了。这里我们来讲一讲整个流程。&lt;/p&gt;
&lt;p&gt;在初始化Vue实例时，执行initWatch，initWatch函数往下走，创建了一个watcher实例。watcher实例执行了getter函数，getter函数读取了data某个属性的值，因此触发了Object.defineProperty中的get函数。get函数执行了dep.depend函数，这个函数用于收集依赖。所谓的依赖其实就是回调函数。在我们说的这个例子中，就是value的watch回调函数。&lt;/p&gt;
&lt;p&gt;讲到这里，我们发现watch的回调函数只是在这里进行了注册，还没有执行。那么，watch真正的执行是在哪里呢？我们回到最开始代码的执行顺序来看。在第3步的时候，form.a=abc，这里有一个设置的操作。这个操作触发了Object.defineProperty的set函数，set函数执行了dep.notify函数。执行了update函数，update函数的核心就是queueWatcher函数。为了更好地说明，我们把queueWatcher函数单独列出来看看。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;function queueWatcher (watcher) {
  var id = watcher.id;
  if (has[id] == null) {
    has[id] = true;
    if (!flushing) {
      queue.push(watcher);
    } else {
      &amp;#x2F;&amp;#x2F; if already flushing, splice the watcher based on its id
      &amp;#x2F;&amp;#x2F; if already past its id, it will be run next immediately.
      var i = queue.length - 1;
      while (i &amp;gt; index &amp;amp;&amp;amp; queue[i].id &amp;gt; watcher.id) {
        i--;
      }
      queue.splice(i + 1, 0, watcher);
    }
    &amp;#x2F;&amp;#x2F; queue the flush
    if (!waiting) {
      waiting = true;

      if (!config.async) {
        flushSchedulerQueue();
        return
      }
      nextTick(flushSchedulerQueue);
    }
  }
}

function flushSchedulerQueue () {
  currentFlushTimestamp = getNow();
  flushing = true;
  var watcher, id;

  &amp;#x2F;&amp;#x2F; Sort queue before flush.
  &amp;#x2F;&amp;#x2F; This ensures that:
  &amp;#x2F;&amp;#x2F; 1. Components are updated from parent to child. (because parent is always
  &amp;#x2F;&amp;#x2F;    created before the child)
  &amp;#x2F;&amp;#x2F; 2. A component&amp;#x27;s user watchers are run before its render watcher (because
  &amp;#x2F;&amp;#x2F;    user watchers are created before the render watcher)
  &amp;#x2F;&amp;#x2F; 3. If a component is destroyed during a parent component&amp;#x27;s watcher run,
  &amp;#x2F;&amp;#x2F;    its watchers can be skipped.
  queue.sort(function (a, b) { return a.id - b.id; });

  &amp;#x2F;&amp;#x2F; do not cache length because more watchers might be pushed
  &amp;#x2F;&amp;#x2F; as we run existing watchers
  for (index = 0; index &amp;lt; queue.length; index++) {
    watcher = queue[index];
    if (watcher.before) {
      watcher.before();
    }
    id = watcher.id;
    has[id] = null;
    watcher.run();
    &amp;#x2F;&amp;#x2F; in dev build, check and stop circular updates.
    if (has[id] != null) {
      circular[id] = (circular[id] || 0) + 1;
      if (circular[id] &amp;gt; MAX_UPDATE_COUNT) {
        warn(
          &amp;#x27;You may have an infinite update loop &amp;#x27; + (
            watcher.user
              ? (&amp;quot;in watcher with expression &amp;#x5C;&amp;quot;&amp;quot; + (watcher.expression) + &amp;quot;&amp;#x5C;&amp;quot;&amp;quot;)
              : &amp;quot;in a component render function.&amp;quot;
          ),
          watcher.vm
        );
        break
      }
    }
  }

  &amp;#x2F;&amp;#x2F; keep copies of post queues before resetting state
  var activatedQueue = activatedChildren.slice();
  var updatedQueue = queue.slice();

  resetSchedulerState();

  &amp;#x2F;&amp;#x2F; call component updated and activated hooks
  callActivatedHooks(activatedQueue);
  callUpdatedHooks(updatedQueue);

  &amp;#x2F;&amp;#x2F; devtool hook
  &amp;#x2F;* istanbul ignore if *&amp;#x2F;
  if (devtools &amp;amp;&amp;amp; config.devtools) {
    devtools.emit(&amp;#x27;flush&amp;#x27;);
  }
}

Watcher.prototype.run = function run () {
  if (this.active) {
    var value = this.get();
    if (
      value !== this.value ||
      &amp;#x2F;&amp;#x2F; Deep watchers and watchers on Object&amp;#x2F;Arrays should fire even
      &amp;#x2F;&amp;#x2F; when the value is the same, because the value may
      &amp;#x2F;&amp;#x2F; have mutated.
      isObject(value) ||
      this.deep
    ) {
      &amp;#x2F;&amp;#x2F; set new value
      var oldValue = this.value;
      this.value = value;
      if (this.user) {
        try {
          this.cb.call(this.vm, value, oldValue);
        } catch (e) {
          handleError(e, this.vm, (&amp;quot;callback for watcher &amp;#x5C;&amp;quot;&amp;quot; + (this.expression) + &amp;quot;&amp;#x5C;&amp;quot;&amp;quot;));
        }
      } else {
        this.cb.call(this.vm, value, oldValue);
      }
    }
  }
}

function nextTick (cb, ctx) {
  var _resolve;
  callbacks.push(function () {
    if (cb) {
      try {
        cb.call(ctx);
      } catch (e) {
        handleError(e, ctx, &amp;#x27;nextTick&amp;#x27;);
      }
    } else if (_resolve) {
      _resolve(ctx);
    }
  });
  if (!pending) {
    pending = true;
    timerFunc();
  }
  &amp;#x2F;&amp;#x2F; $flow-disable-line
  if (!cb &amp;amp;&amp;amp; typeof Promise !== &amp;#x27;undefined&amp;#x27;) {
    return new Promise(function (resolve) {
      _resolve = resolve;
    })
  }
}

var timerFunc;

&amp;#x2F;&amp;#x2F; The nextTick behavior leverages the microtask queue, which can be accessed
&amp;#x2F;&amp;#x2F; via either native Promise.then or MutationObserver.
&amp;#x2F;&amp;#x2F; MutationObserver has wider support, however it is seriously bugged in
&amp;#x2F;&amp;#x2F; UIWebView in iOS &amp;gt;= 9.3.3 when triggered in touch event handlers. It
&amp;#x2F;&amp;#x2F; completely stops working after triggering a few times... so, if native
&amp;#x2F;&amp;#x2F; Promise is available, we will use it:
&amp;#x2F;* istanbul ignore next, $flow-disable-line *&amp;#x2F;
if (typeof Promise !== &amp;#x27;undefined&amp;#x27; &amp;amp;&amp;amp; isNative(Promise)) {
  var p = Promise.resolve();
  timerFunc = function () {
    p.then(flushCallbacks);
    &amp;#x2F;&amp;#x2F; In problematic UIWebViews, Promise.then doesn&amp;#x27;t completely break, but
    &amp;#x2F;&amp;#x2F; it can get stuck in a weird state where callbacks are pushed into the
    &amp;#x2F;&amp;#x2F; microtask queue but the queue isn&amp;#x27;t being flushed, until the browser
    &amp;#x2F;&amp;#x2F; needs to do some other work, e.g. handle a timer. Therefore we can
    &amp;#x2F;&amp;#x2F; &amp;quot;force&amp;quot; the microtask queue to be flushed by adding an empty timer.
    if (isIOS) { setTimeout(noop); }
  };
  isUsingMicroTask = true;
} else if (!isIE &amp;amp;&amp;amp; typeof MutationObserver !== &amp;#x27;undefined&amp;#x27; &amp;amp;&amp;amp; (
  isNative(MutationObserver) ||
  &amp;#x2F;&amp;#x2F; PhantomJS and iOS 7.x
  MutationObserver.toString() === &amp;#x27;[object MutationObserverConstructor]&amp;#x27;
)) {
  &amp;#x2F;&amp;#x2F; Use MutationObserver where native Promise is not available,
  &amp;#x2F;&amp;#x2F; e.g. PhantomJS, iOS7, Android 4.4
  &amp;#x2F;&amp;#x2F; (#6466 MutationObserver is unreliable in IE11)
  var counter = 1;
  var observer = new MutationObserver(flushCallbacks);
  var textNode = document.createTextNode(String(counter));
  observer.observe(textNode, {
    characterData: true
  });
  timerFunc = function () {
    counter = (counter + 1) % 2;
    textNode.data = String(counter);
  };
  isUsingMicroTask = true;
} else if (typeof setImmediate !== &amp;#x27;undefined&amp;#x27; &amp;amp;&amp;amp; isNative(setImmediate)) {
  &amp;#x2F;&amp;#x2F; Fallback to setImmediate.
  &amp;#x2F;&amp;#x2F; Technically it leverages the (macro) task queue,
  &amp;#x2F;&amp;#x2F; but it is still a better choice than setTimeout.
  timerFunc = function () {
    setImmediate(flushCallbacks);
  };
} else {
  &amp;#x2F;&amp;#x2F; Fallback to setTimeout.
  timerFunc = function () {
    setTimeout(flushCallbacks, 0);
  };
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在queueWatcher函数中，我们看到了熟悉的面孔：nextTick。我们发现，nextTick就是一个微任务的平稳降级：它将根据所在环境，依次使用Promise、MutationObserver、setImmediate以及setTimeout执行任务。我们看到，执行form.a=abc时，实际上是先注册了一个微任务，在这里我们可以理解为watch回调函数的包裹函数。这个微任务将在主线程任务走完以后执行，因此它将被先挂起。&lt;/p&gt;
&lt;p&gt;随后主线程执行了form.a=null，再次触发了setter。由于都是form.a注册的，在推入微任务队列前会去重，避免watch的回调多次执行。到这里，主线程任务执行完成，微任务队列中watcher回调函数的包裹函数被推出执行，由于form.a的值始终都为null，因此不会执行回调函数。&lt;/p&gt;
&lt;p&gt;在加入$nextTick函数以后，在form.a=null之前先执行了nextTick函数，nextTick函数执行了watcher的回调函数的包裹函数，此时form.a的值为abc，旧的值和新的值不一样，因此执行了watch回调函数。至此，整个逻辑就理顺了。&lt;/p&gt;
&lt;h3&gt;后话&lt;/h3&gt;
&lt;p&gt;没想到，一个简简单单nextTick的使用居然关系到了Vue的核心原理！&lt;p&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
作者: 王先生2022加油
原文链接：&lt;a href=&quot;https://juejin.im/post/6976246978850062367&quot;&gt;https://juejin.im/post/6976246978850062367&lt;/a&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>banyungong</author><pubDate>Wed, 12 Jan 2022 15:13:58 GMT</pubDate></item><item><title>教你用three.js写一个炫酷的3D登陆页面__three.js__Vue.js</title><link>http://vue-js.com/topic/61deb835ad745b003c9df6da</link><guid>http://vue-js.com/topic/61deb835ad745b003c9df6da</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: smartblue&lt;/h2&gt;
&lt;h2&gt;前言：&lt;/h2&gt;
&lt;p&gt;大家好，我是xx传媒严导（xx这两个字请自行脑补） 。&lt;/p&gt;
&lt;p&gt;该篇文章用到的主要技术：vue3、three.js&lt;/p&gt;
&lt;p&gt;我们先看看成品效果：&lt;/p&gt;
&lt;p&gt;高清大图预览（会有些慢）：
&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0353bd6b089b4ab4a583a803ba21b59c~tplv-k3u1fbpfcp-watermark.image?&quot; width=&quot;100%&quot; height=&quot;auto&quot; /&gt;&lt;/p&gt;
&lt;p&gt;座机小图预览：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/79313e7d11014c86a1fc604df323ce60~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;login2.gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;废话不多说，直接进入正题&lt;/p&gt;
&lt;h2&gt;Three.js的基础知识&lt;/h2&gt;
&lt;p&gt;想象一下，在一个虚拟的3D世界中都需要什么？&lt;strong&gt;首先，要有一个立体的空间，其次是有光源，最重要的是要有一双眼睛&lt;/strong&gt;。下面我们就看看在three.js中如何创建一个3D世界吧！&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建一个场景&lt;/li&gt;
&lt;li&gt;设置光源&lt;/li&gt;
&lt;li&gt;创建相机，设置相机位置和相机镜头的朝向&lt;/li&gt;
&lt;li&gt;创建3D渲染器，使用渲染器把创建的场景渲染出来&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此时，你就通过three.js创建出了一个可视化的3D页面，很简单是吧！&lt;/p&gt;
&lt;h3&gt;关于场景&lt;/h3&gt;
&lt;p&gt;你可以为场景添加背景颜色，或创建一个盒模型（球体、立方体），给盒模型的内部贴上图片，再把相机放在这个盒模型内部以达到模拟场景的效果。盒模型的方式多用于360度全景，比如房屋vr展示&lt;/p&gt;
&lt;p&gt;【登陆页面】创建场景的例子：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const scene = new THREE.Scene()
&amp;#x2F;&amp;#x2F; 在场景中添加雾的效果，Fog参数分别代表‘雾的颜色’、‘开始雾化的视线距离’、刚好雾化至看不见的视线距离’
scene.fog = new THREE.Fog(0x000000, 0, 10000)
&amp;#x2F;&amp;#x2F; 盒模型的深度
const depth = 1400
&amp;#x2F;&amp;#x2F; 在场景中添加一个圆球盒模型
&amp;#x2F;&amp;#x2F; 1.创建一个立方体
const geometry = new THREE.BoxGeometry(1000, 800, depth)
&amp;#x2F;&amp;#x2F; 2.加载纹理
const texture = new THREE.TextureLoader().load(&amp;#x27;bg.png&amp;#x27;)
&amp;#x2F;&amp;#x2F; 3.创建网格材质（原料）
const material = new THREE.MeshBasicMaterial({map: texture, side: THREE.BackSide})
&amp;#x2F;&amp;#x2F; 4.生成网格
const mesh = new THREE.Mesh(geometry, material)
&amp;#x2F;&amp;#x2F; 5.把网格放入场景中
scene.add(mesh)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;关于光源&lt;/h3&gt;
&lt;p&gt;为场景设置光源的颜色、强度，同时还可以设置光源的类型（环境光、点光源、平行光等）、光源所在的位置&lt;/p&gt;
&lt;p&gt;【登陆页面】创建光源的例子：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 1.创建环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 1)
&amp;#x2F;&amp;#x2F; 2.创建点光源，位于场景右下角
const light_rightBottom = new THREE.PointLight(0x0655fd, 5, 0)
light_rightBottom.position.set(0, 100, -200)
&amp;#x2F;&amp;#x2F; 3.把光源放入场景中
scene.add(light_rightBottom)
scene.add(ambientLight)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;关于相机（重要）&lt;/h3&gt;
&lt;p&gt;很重要的一步，相机就是你的眼睛。&lt;strong&gt;这里还会着重说明一下使用透视相机时可能会遇到的问题&lt;/strong&gt;，我们最常用到的相机就是正交相机和透视相机了。&lt;/p&gt;
&lt;p&gt;正交相机：无论物体距离相机距离远或者近，在最终渲染的图片中物体的大小都保持不变。&lt;strong&gt;用于渲染2D场景或者UI元素是非常有用的&lt;/strong&gt;。如图：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;图注解：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;图中红色三角锥体是视野的大小&lt;/li&gt;
&lt;li&gt;红色锥体连着的第一个面是摄像机能看到的最近位置&lt;/li&gt;
&lt;li&gt;从该面通过白色辅助线延伸过去的面是摄像机能看到的最远的位置&lt;/li&gt;
&lt;/ol&gt;
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a85e2b77c6154394b83daa561063d7ea~tplv-k3u1fbpfcp-watermark.image?&quot; width=&quot;400&quot; height=&quot;300&quot; /&gt; 
&lt;p&gt;透视相机：被用来模拟人眼所看到的景象。&lt;strong&gt;它是3D场景的渲染中使用得最普遍的投影模式&lt;/strong&gt;。如图：&lt;/p&gt;
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e30d9a2cae7c4847a5603cdbd343e650~tplv-k3u1fbpfcp-watermark.image?&quot; width=&quot;400&quot; height=&quot;300&quot; /&gt; 
&lt;p&gt;我们在使用透视相机时，可能会遇到这种情况：边缘处的物体会产生一定程度上的形变，原因是：&lt;strong&gt;透视相机是鱼眼效果，如果视域越大，边缘变形越大。为了避免边缘变形，可以将fov角度设置小一些，距离拉远一些&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;关于透视相机的几个参数，&lt;code&gt;new THREE.PerspectiveCamera(fov, width / height, near, far)&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fov（field of view） — 摄像机视锥体垂直视野角度&lt;/li&gt;
&lt;li&gt;aspect（width / height） — 摄像机视锥体长宽比&lt;/li&gt;
&lt;li&gt;near — 摄像机视锥体近端面&lt;/li&gt;
&lt;li&gt;far — 摄像机视锥体远端面&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;**
 * 为了避免边缘变形，这里将fov角度设置小一些，距离拉远一些
 * 固定视域角度，求需要多少距离才能满足完整的视野画面
 * 15度等于(Math.PI &amp;#x2F; 12)
 *&amp;#x2F;
const container = document.getElementById(&amp;#x27;login-three-container&amp;#x27;)
const width = container.clientWidth
const height = container.clientHeight
const fov = 15
const distance = width &amp;#x2F; 2 &amp;#x2F; Math.tan(Math.PI &amp;#x2F; 12)
const zAxisNumber = Math.floor(distance - depth &amp;#x2F; 2)
const camera = new THREE.PerspectiveCamera(fov, width &amp;#x2F; height, 1, 30000)
camera.position.set(0, 0, zAxisNumber)
const cameraTarget = new THREE.Vector3(0, 0, 0)
camera.lookAt(cameraTarget)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;关于渲染器&lt;/h3&gt;
&lt;p&gt;用&lt;a href=&quot;https://en.wikipedia.org/wiki/WebGL&quot;&gt;WebGL&lt;/a&gt;渲染出你精心制作的场景。它会创建一个canvas进行渲染&lt;/p&gt;
&lt;p&gt;【登陆页面】创建渲染器的例子：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 获取容器dom
const container = document.getElementById(&amp;#x27;login-three-container&amp;#x27;)
&amp;#x2F;&amp;#x2F; 创建webgl渲染器实例
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
&amp;#x2F;&amp;#x2F; 设置渲染器画布的大小
renderer.setSize(width, height)
&amp;#x2F;&amp;#x2F; 把画布实例（canvas）放入容器中
container.appendChild(renderer.domElement)
&amp;#x2F;&amp;#x2F; 渲染器渲染场景
renderer.render(scene, camera)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;需要注意，这样创建出来的场景并没有动效，原因是这次渲染的仅仅只是这一帧的画面。为了让场景中的物体能动起来，我们需要使用requestAnimationFrame，所以我们可以写一个loop函数&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;动画刷新
const loopAnimate = () =&amp;gt; {
    requestAnimationFrame(loopAnimate)
    scene.rotateY(0.001)
    renderer.render(scene, camera)
}
loopAnimate()
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;完善效果&lt;/h2&gt;
&lt;h3&gt;创建一个左上角的地球&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 加载纹理
const texture = THREE.TextureLoader().load(&amp;#x27;earth_bg.png&amp;#x27;)
&amp;#x2F;&amp;#x2F; 创建网格材质
const material = new THREE.MeshPhongMaterial({map: texture, blendDstAlpha: 1})
&amp;#x2F;&amp;#x2F; 创建几何球体
const sphereGeometry = new THREE.SphereGeometry(50, 64, 32)
&amp;#x2F;&amp;#x2F; 生成网格
const sphere = new THREE.Mesh(sphereGeometry, material)
&amp;#x2F;&amp;#x2F; 为了单独操作球体的运动效果，我们把球体放到一个组中
const Sphere_Group = new THREE.Group()
const Sphere_Group.add(sphere)
&amp;#x2F;&amp;#x2F; 设置该组（球体）在空间坐标中的位置
const Sphere_Group.position.x = -400
const Sphere_Group.position.y = 200
const Sphere_Group.position.z = -200
&amp;#x2F;&amp;#x2F; 加入场景
scene.add(Sphere_Group)
&amp;#x2F;&amp;#x2F; 使球能够自转，需要在loopAnimate中加上
Sphere_Group.rotateY(0.001)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;使地球自转&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 渲染星球的自转
const renderSphereRotate = () =&amp;gt; {
    if (sphere) {
      Sphere_Group.rotateY(0.001)
    }
}
&amp;#x2F;&amp;#x2F; 使球能够自转，需要在loopAnimate中加上
const loopAnimate = () =&amp;gt; {
    requestAnimationFrame(loopAnimate)
    renderSphereRotate()
    renderer.render(scene, camera)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;创建星星&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 初始化星星
const initSceneStar = (initZposition: number): any =&amp;gt; {
    const geometry = new THREE.BufferGeometry()
    const vertices: number[] = []
    const pointsGeometry: any[] = []
    const textureLoader = new THREE.TextureLoader()
    const sprite1 = textureLoader.load(&amp;#x27;starflake1.png&amp;#x27;)
    const sprite2 = textureLoader.load(&amp;#x27;starflake2.png&amp;#x27;)
    parameters = [
      [[0.6, 100, 0.75], sprite1, 50],
      [[0, 0, 1], sprite2, 20]
    ]
    &amp;#x2F;&amp;#x2F; 初始化500个节点
    for (let i = 0; i &amp;lt; 500; i++) {
      &amp;#x2F;**
       * const x: number = Math.random() * 2 * width - width
       * 等价
       * THREE.MathUtils.randFloatSpread(width)
       * _.random使用的是lodash库中的生成随机数
       *&amp;#x2F;
      const x: number = THREE.MathUtils.randFloatSpread(width)
      const y: number = _.random(0, height &amp;#x2F; 2)
      const z: number = _.random(-depth &amp;#x2F; 2, zAxisNumber)
      vertices.push(x, y, z)
    }

    geometry.setAttribute(&amp;#x27;position&amp;#x27;, new THREE.Float32BufferAttribute(vertices, 3))

    &amp;#x2F;&amp;#x2F; 创建2种不同的材质的节点（500 * 2）
    for (let i = 0; i &amp;lt; parameters.length; i++) {
      const color = parameters[i][0]
      const sprite = parameters[i][1]
      const size = parameters[i][2]

      materials[i] = new THREE.PointsMaterial({
        size,
        map: sprite,
        blending: THREE.AdditiveBlending,
        depthTest: true,
        transparent: true
      })
      materials[i].color.setHSL(color[0], color[1], color[2])
      const particles = new THREE.Points(geometry, materials[i])
      particles.rotation.x = Math.random() * 0.2 - 0.15
      particles.rotation.z = Math.random() * 0.2 - 0.15
      particles.rotation.y = Math.random() * 0.2 - 0.15
      particles.position.setZ(initZposition)
      pointsGeometry.push(particles)
      scene.add(particles)
    }
    return pointsGeometry
}
const particles_init_position = -zAxisNumber - depth &amp;#x2F; 2
let zprogress = particles_init_position
let zprogress_second = particles_init_position * 2
const particles_first = initSceneStar(particles_init_position)
const particles_second = initSceneStar(zprogress_second)
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;使星星运动&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 渲染星星的运动
const renderStarMove = () =&amp;gt; {
    const time = Date.now() * 0.00005
    zprogress += 1
    zprogress_second += 1

    if (zprogress &amp;gt;= zAxisNumber + depth &amp;#x2F; 2) {
      zprogress = particles_init_position
    } else {
      particles_first.forEach((item) =&amp;gt; {
        item.position.setZ(zprogress)
      })
    }
    if (zprogress_second &amp;gt;= zAxisNumber + depth &amp;#x2F; 2) {
      zprogress_second = particles_init_position
    } else {
      particles_second.forEach((item) =&amp;gt; {
        item.position.setZ(zprogress_second)
      })
    }

    for (let i = 0; i &amp;lt; materials.length; i++) {
      const color = parameters[i][0]

      const h = ((360 * (color[0] + time)) % 360) &amp;#x2F; 360
      materials[i].color.setHSL(color[0], color[1], parseFloat(h.toFixed(2)))
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;星星的运动效果，实际就是沿着z轴从远处不断朝着相机位置移动，直到移出相机的位置时回到起点，不断重复这个操作。我们使用上帝视角，从x轴的左侧看去，效果如下：&lt;/p&gt;
&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9985a8373aab4c01a4b58f24a2755aa0~tplv-k3u1fbpfcp-watermark.image?&quot; width=&quot;600&quot; height=&quot;150&quot; /&gt; 
&lt;h3&gt;创建云以及运动轨迹&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; 创建曲线路径
const route = [
    new THREE.Vector3(-width &amp;#x2F; 10, 0, -depth &amp;#x2F; 2),
    new THREE.Vector3(-width &amp;#x2F; 4, height &amp;#x2F; 8, 0),
    new THREE.Vector3(-width &amp;#x2F; 4, 0, zAxisNumber)
]
const curve = new THREE.CatmullRomCurve3(route, false)
const tubeGeometry = new THREE.TubeGeometry(curve, 100, 2, 50, false)
const tubeMaterial = new THREE.MeshBasicMaterial({
  opacity: 0,
  transparent: true
})
const tube = new THREE.Mesh(tubeGeometry, tubeMaterial)
&amp;#x2F;&amp;#x2F; 把创建好的路径加入场景中
scene.add(tube)
&amp;#x2F;&amp;#x2F; 创建平面几何
const clondGeometry = new THREE.PlaneGeometry(500, 200)
const textureLoader = new THREE.TextureLoader()
const cloudTexture = textureLoader.load(&amp;#x27;cloud.png&amp;#x27;)
const clondMaterial = new THREE.MeshBasicMaterial({
  map: cloudTexture,
  blending: THREE.AdditiveBlending,
  depthTest: false,
  transparent: true
})
const cloud = new THREE.Mesh(clondGeometry, clondMaterial)
&amp;#x2F;&amp;#x2F; 将云加入场景中
scene.add(cloud)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在有了云和曲线路径，我们需要将二者结合，让云按着路径进行运动&lt;/p&gt;
&lt;h3&gt;使云运动&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;let cloudProgress = 0
let scaleSpeed = 0.0006
let maxScale = 1
let startScale = 0
&amp;#x2F;&amp;#x2F; 初始化云的运动函数
const cloudMove = () =&amp;gt; {
  if (startScale &amp;lt; maxScale) {
    startScale += scaleSpeed
    cloud.scale.setScalar(startScale)
  }
  if (cloudProgress &amp;gt; 1) {
    cloudProgress = 0
    startScale = 0
  } else {
    cloudProgress += speed
    if (cloudParameter.curve) {
      const point = curve.getPoint(cloudProgress)
      if (point &amp;amp;&amp;amp; point.x) {
        cloud.position.set(point.x, point.y, point.z)
      }
    }
  }

}
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;完成three.js有关效果&lt;/h3&gt;
&lt;p&gt;最后，把cloudMove函数放入loopAnimate函数中即可实现云的运动。至此，该登录页所有与three.js有关的部分都介绍完了。剩下的月球地面、登录框、宇航员都是通过定位和层级设置以及css3动画实现的，这里就不进行深入的讨论了。&lt;/p&gt;
&lt;p&gt;上面的每个部分的代码在连贯性并不完整，并且同登录页的完整代码也有些许出入。上面更多是为了介绍每个部分的实现方式。完整代码，我放在github上了，每行注释几乎都打上了，希望能给你入坑three.js带来一些帮助，地址：&lt;a href=&quot;https://github.com/Yanzengyong/threejs-login-view&quot;&gt;https://github.com/Yanzengyong/threejs-login-view&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;之前用react+three.js写过一个3D可视化的知识图谱，如果这篇对大家有帮助并且反响好的话，后续我会写一篇如何使用three.js创建一个3D知识图谱。&lt;/p&gt;
&lt;p&gt;最后，我认为3D可视化的精髓其实在于设计，有好的素材、好的建模，能让你的页面效果瞬间提升N倍&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://threejs.org/&quot;&gt;three.js官网&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: KDDA_
        原文链接：&lt;a href=&quot;https://juejin.im/post/7020571868314730532&quot;&gt;https://juejin.im/post/7020571868314730532&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Wed, 12 Jan 2022 11:15:01 GMT</pubDate></item><item><title>花60秒给Vue3提的PR，竟然被尤大亲自Merge了~__JavaScript__Vue.js__开源</title><link>http://vue-js.com/topic/61deafbead745b003c9df6d9</link><guid>http://vue-js.com/topic/61deafbead745b003c9df6d9</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;ul&gt;
&lt;li&gt;小知识，大挑战！本文正在参与“&lt;a href=&quot;https://juejin.cn/post/7008476801634680869&quot; title=&quot;https://juejin.cn/post/7008476801634680869&quot;&gt;程序员必备小知识&lt;/a&gt;”创作活动。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;先来一个图镇楼！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec1d1b52edb748a8bdf909aa4375cb5d~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;终于等到一个活动可以让我分享在去年定下的目标&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9883d795f5494760b4e82d9329eee6dd~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;0_82ac840b685208ae729eb6b958e22573.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我要成为&lt;code&gt;Vue3&lt;/code&gt;的&lt;code&gt;Contributor&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;然而，在定下目标60秒后，我就完成了它！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;我成为了&lt;code&gt;Vue3&lt;/code&gt;的&lt;code&gt;Contributor&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我这个PR&lt;strong&gt;举世无双&lt;/strong&gt;的水，说是&lt;strong&gt;世界第一水&lt;/strong&gt;也不过分！&lt;/p&gt;
&lt;p&gt;但是不好意思啊，即便是如此水的一个PR，尤大也不得不Merge！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1d5a092265834f08ba4acad6b4ab320d~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;54_91e2cee52fecd67ed6bf892f954400ed.gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;尤大亲手让我成为了&lt;code&gt;Vue3&lt;/code&gt;的&lt;code&gt;Contributor&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为我确实修正了源码中一处错误！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bc1776b7ad9f496ba93eaed12e0b7130~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;哈哈哈，我甚至连那个TODO都没帮忙完成！&lt;/p&gt;
&lt;p&gt;我知道还有很多顶级项目都有这类问题，因为拼错单词是很常见的，手快了、脑抽了、晃神了都有可能拼错，这问题还不太好发现。因为错词的可读性依然很高。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Aoccdrnig to a rscheearch at an Elingsh uinervtisy, it deosn’t mttaer in waht oredr the ltteers in a wrod are, the olny iprmoetnt tihng is taht frist and lsat ltteer is at the rghit pclae. The rset can be a toatl mses and you can sitll raed it wouthit porbelm. Tihs is bcuseae we do not raed ervey lteter by itslef but the wrod as a wlohe.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;对不对？&lt;/p&gt;
&lt;p&gt;那么只要找出开源项目中那些拼错的单词再改掉，岂不是各种顶级项目的&lt;code&gt;Contributor&lt;/code&gt;标记拿到手软？&lt;/p&gt;
&lt;p&gt;下面我来教大家如何对开源项目进行单词拼写检查&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;把代码文件copy到Word里&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;把代码文件copy到Word里&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;把代码文件copy到Word里&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/da10ecc78c6447029fa7cf03e4cf2da3~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;3_a252147afe65161272c362aa89e3cdce.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;就这的话我是不是要被大家的唾沫淹死？&lt;/p&gt;
&lt;p&gt;原理就是这么个简单的原理&lt;/p&gt;
&lt;p&gt;有没有帮我们搞效检查项目代码中拼写错误的仓库呢？&lt;/p&gt;
&lt;p&gt;有的！&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/401fcea35c9345818b0bb83a11d920ba~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;0_8501779.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;请大家自行前往&lt;code&gt;Github&lt;/code&gt;搜索&lt;code&gt;SpellCheck&lt;/code&gt;
或点击下方链接&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/search?q=spellcheck&quot;&gt;https://github.com/search?q=spellcheck&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;One more times and Follow me 大声say!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;大帅是&lt;code&gt;Vue3&lt;/code&gt;的&lt;code&gt;Contributor&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;弱弱的问大家一句，尤大要是看到这篇文章，还有能力撤销这个Merge吗？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ece20490be0b4a848050e25dc144799d~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;6_469436.jpg&quot;&gt;&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 大帅老猿
        原文链接：&lt;a href=&quot;https://juejin.im/post/7012449788255813669&quot;&gt;https://juejin.im/post/7012449788255813669&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Wed, 12 Jan 2022 10:38:54 GMT</pubDate></item><item><title>后端一次给你10万条数据，如何优雅展示，到底考察我什么?__前端__Vue.js__面试</title><link>http://vue-js.com/topic/61deafbecbbfd1003b11f95c</link><guid>http://vue-js.com/topic/61deafbecbbfd1003b11f95c</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: smartblue&lt;/h2&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;大家好，我是林三心，&lt;strong&gt;用最通俗的话讲最难的知识点&lt;/strong&gt;是我的座右铭，&lt;strong&gt;基础是进阶的前提&lt;/strong&gt;是我的初心，今天跟大家来唠唠嗑，如果后端真的返回给前端10万条数据，咱们前端要怎么优雅地展示出来呢？（哈哈假设后端真的能传10万条数据到前端）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4cc34033138345488065107afcc4adf1~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;前置工作&lt;/h2&gt;
&lt;p&gt;先把前置工作给做好，后面才能进行测试&lt;/p&gt;
&lt;h3&gt;后端搭建&lt;/h3&gt;
&lt;p&gt;新建一个&lt;code&gt;server.js&lt;/code&gt;文件，简单起个服务，并返回给前端&lt;code&gt;10w&lt;/code&gt;条数据，并通过&lt;code&gt;nodemon server.js&lt;/code&gt;开启服务&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;没有安装&lt;code&gt;nodemon&lt;/code&gt;的同学可以先全局安装&lt;code&gt;npm i nodemon -g&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; server.js

const http = require(&amp;#x27;http&amp;#x27;)
const port = 8000;

http.createServer(function (req, res) {
  &amp;#x2F;&amp;#x2F; 开启Cors
  res.writeHead(200, {
    &amp;#x2F;&amp;#x2F;设置允许跨域的域名，也可设置*允许所有域名
    &amp;#x27;Access-Control-Allow-Origin&amp;#x27;: &amp;#x27;*&amp;#x27;,
    &amp;#x2F;&amp;#x2F;跨域允许的请求方法，也可设置*允许所有方法
    &amp;quot;Access-Control-Allow-Methods&amp;quot;: &amp;quot;DELETE,PUT,POST,GET,OPTIONS&amp;quot;,
    &amp;#x2F;&amp;#x2F;允许的header类型
    &amp;#x27;Access-Control-Allow-Headers&amp;#x27;: &amp;#x27;Content-Type&amp;#x27;
  })
  let list = []
  let num = 0

  &amp;#x2F;&amp;#x2F; 生成10万条数据的list
  for (let i = 0; i &amp;lt; 100000; i++) {
    num++
    list.push({
      src: &amp;#x27;https:&amp;#x2F;&amp;#x2F;p3-passport.byteacctimg.com&amp;#x2F;img&amp;#x2F;user-avatar&amp;#x2F;d71c38d1682c543b33f8d716b3b734ca~300x300.image&amp;#x27;,
      text: &amp;#96;我是${num}号嘉宾林三心&amp;#96;,
      tid: num
    })
  }
  res.end(JSON.stringify(list));
}).listen(port, function () {
  console.log(&amp;#x27;server is listening on port &amp;#x27; + port);
})
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;前端页面&lt;/h3&gt;
&lt;p&gt;先新建一个&lt;code&gt;index.html&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; index.html

&amp;#x2F;&amp;#x2F; 样式
&amp;lt;style&amp;gt;
    * {
      padding: 0;
      margin: 0;
    }
    #container {
      height: 100vh;
      overflow: auto;
    }
    .sunshine {
      display: flex;
      padding: 10px;
    }
    img {
      width: 150px;
      height: 150px;
    }
  &amp;lt;&amp;#x2F;style&amp;gt;

&amp;#x2F;&amp;#x2F; html部分
&amp;lt;body&amp;gt;
  &amp;lt;div id=&amp;quot;container&amp;quot;&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;script src=&amp;quot;.&amp;#x2F;index.js&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;&amp;#x2F;body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后新建一个&lt;code&gt;index.js&lt;/code&gt;文件，封装一个&lt;code&gt;AJAX&lt;/code&gt;函数，用来请求这&lt;code&gt;10w&lt;/code&gt;条数据&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; index.js

&amp;#x2F;&amp;#x2F; 请求函数
const getList = () =&amp;gt; {
    return new Promise((resolve, reject) =&amp;gt; {
        &amp;#x2F;&amp;#x2F;步骤一:创建异步对象
        var ajax = new XMLHttpRequest();
        &amp;#x2F;&amp;#x2F;步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数
        ajax.open(&amp;#x27;get&amp;#x27;, &amp;#x27;http:&amp;#x2F;&amp;#x2F;127.0.0.1:8000&amp;#x27;);
        &amp;#x2F;&amp;#x2F;步骤三:发送请求
        ajax.send();
        &amp;#x2F;&amp;#x2F;步骤四:注册事件 onreadystatechange 状态改变就会调用
        ajax.onreadystatechange = function () {
            if (ajax.readyState == 4 &amp;amp;&amp;amp; ajax.status == 200) {
                &amp;#x2F;&amp;#x2F;步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的
                resolve(JSON.parse(ajax.responseText))
            }
        }
    })
}

&amp;#x2F;&amp;#x2F; 获取container对象
const container = document.getElementById(&amp;#x27;container&amp;#x27;)
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;直接渲染&lt;/h2&gt;
&lt;p&gt;最直接的方式就是直接渲染出来，但是这样的做法肯定是不可取的，因为一次性渲染出&lt;code&gt;10w&lt;/code&gt;个节点，是非常耗时间的，咱们可以来看一下耗时，差不多要消耗&lt;code&gt;12秒&lt;/code&gt;，非常消耗时间&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9396a045360744e7b73dd626a1456bcd~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;截屏2021-11-18 下午10.07.45.png&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const renderList = async () =&amp;gt; {
    console.time(&amp;#x27;列表时间&amp;#x27;)
    const list = await getList()
    list.forEach(item =&amp;gt; {
        const div = document.createElement(&amp;#x27;div&amp;#x27;)
        div.className = &amp;#x27;sunshine&amp;#x27;
        div.innerHTML = &amp;#96;&amp;lt;img src=&amp;quot;${item.src}&amp;quot; &amp;#x2F;&amp;gt;&amp;lt;span&amp;gt;${item.text}&amp;lt;&amp;#x2F;span&amp;gt;&amp;#96;
        container.appendChild(div)
    })
    console.timeEnd(&amp;#x27;列表时间&amp;#x27;)
}
renderList()
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;setTimeout分页渲染&lt;/h2&gt;
&lt;p&gt;这个方法就是，把&lt;code&gt;10w&lt;/code&gt;按照每页数量&lt;code&gt;limit&lt;/code&gt;分成总共&lt;code&gt;Math.ceil(total / limit)&lt;/code&gt;页，然后利用&lt;code&gt;setTimeout&lt;/code&gt;，每次渲染1页数据，这样的话，渲染出首页数据的时间大大缩减了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/faeac54d664a40ab97ac75325e0984ff~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;截屏2021-11-18 下午10.14.46.png&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const renderList = async () =&amp;gt; {
    console.time(&amp;#x27;列表时间&amp;#x27;)
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total &amp;#x2F; limit)

    const render = (page) =&amp;gt; {
        if (page &amp;gt;= totalPage) return
        setTimeout(() =&amp;gt; {
            for (let i = page * limit; i &amp;lt; page * limit + limit; i++) {
                const item = list[i]
                const div = document.createElement(&amp;#x27;div&amp;#x27;)
                div.className = &amp;#x27;sunshine&amp;#x27;
                div.innerHTML = &amp;#96;&amp;lt;img src=&amp;quot;${item.src}&amp;quot; &amp;#x2F;&amp;gt;&amp;lt;span&amp;gt;${item.text}&amp;lt;&amp;#x2F;span&amp;gt;&amp;#96;
                container.appendChild(div)
            }
            render(page + 1)
        }, 0)
    }
    render(page)
    console.timeEnd(&amp;#x27;列表时间&amp;#x27;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;requestAnimationFrame&lt;/h2&gt;
&lt;p&gt;使用&lt;code&gt;requestAnimationFrame&lt;/code&gt;代替&lt;code&gt;setTimeout&lt;/code&gt;，减少了&lt;code&gt;重排&lt;/code&gt;的次数，极大提高了性能，建议大家在渲染方面多使用&lt;code&gt;requestAnimationFrame&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const renderList = async () =&amp;gt; {
    console.time(&amp;#x27;列表时间&amp;#x27;)
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total &amp;#x2F; limit)

    const render = (page) =&amp;gt; {
        if (page &amp;gt;= totalPage) return
        &amp;#x2F;&amp;#x2F; 使用requestAnimationFrame代替setTimeout
        requestAnimationFrame(() =&amp;gt; {
            for (let i = page * limit; i &amp;lt; page * limit + limit; i++) {
                const item = list[i]
                const div = document.createElement(&amp;#x27;div&amp;#x27;)
                div.className = &amp;#x27;sunshine&amp;#x27;
                div.innerHTML = &amp;#96;&amp;lt;img src=&amp;quot;${item.src}&amp;quot; &amp;#x2F;&amp;gt;&amp;lt;span&amp;gt;${item.text}&amp;lt;&amp;#x2F;span&amp;gt;&amp;#96;
                container.appendChild(div)
            }
            render(page + 1)
        })
    }
    render(page)
    console.timeEnd(&amp;#x27;列表时间&amp;#x27;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;文档碎片 + requestAnimationFrame&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;文档碎片&lt;/code&gt;的好处&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1、之前都是每次创建一个&lt;code&gt;div&lt;/code&gt;标签就&lt;code&gt;appendChild&lt;/code&gt;一次，但是有了&lt;code&gt;文档碎片&lt;/code&gt;可以先把1页的&lt;code&gt;div&lt;/code&gt;标签先放进&lt;code&gt;文档碎片&lt;/code&gt;中，然后一次性&lt;code&gt;appendChild&lt;/code&gt;到&lt;code&gt;container&lt;/code&gt;中，这样减少了&lt;code&gt;appendChild&lt;/code&gt;的次数，极大提高了性能&lt;/li&gt;
&lt;li&gt;2、页面只会渲染&lt;code&gt;文档碎片&lt;/code&gt;包裹着的元素，而不会渲染&lt;code&gt;文档碎片&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;const renderList = async () =&amp;gt; {
    console.time(&amp;#x27;列表时间&amp;#x27;)
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total &amp;#x2F; limit)

    const render = (page) =&amp;gt; {
        if (page &amp;gt;= totalPage) return
        requestAnimationFrame(() =&amp;gt; {
            &amp;#x2F;&amp;#x2F; 创建一个文档碎片
            const fragment = document.createDocumentFragment()
            for (let i = page * limit; i &amp;lt; page * limit + limit; i++) {
                const item = list[i]
                const div = document.createElement(&amp;#x27;div&amp;#x27;)
                div.className = &amp;#x27;sunshine&amp;#x27;
                div.innerHTML = &amp;#96;&amp;lt;img src=&amp;quot;${item.src}&amp;quot; &amp;#x2F;&amp;gt;&amp;lt;span&amp;gt;${item.text}&amp;lt;&amp;#x2F;span&amp;gt;&amp;#96;
                &amp;#x2F;&amp;#x2F; 先塞进文档碎片
                fragment.appendChild(div)
            }
            &amp;#x2F;&amp;#x2F; 一次性appendChild
            container.appendChild(fragment)
            render(page + 1)
        })
    }
    render(page)
    console.timeEnd(&amp;#x27;列表时间&amp;#x27;)
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;懒加载&lt;/h2&gt;
&lt;p&gt;为了比较通俗的讲解，咱们启动一个&lt;code&gt;vue&lt;/code&gt;前端项目，后端服务还是开着&lt;/p&gt;
&lt;p&gt;其实实现原理很简单，咱们通过一张图来展示，就是在列表尾部放一个空节点&lt;code&gt;blank&lt;/code&gt;，然后先渲染第1页数据，向上滚动，等到&lt;code&gt;blank&lt;/code&gt;出现在视图中，就说明到底了，这时候再加载第二页，往后以此类推。&lt;/p&gt;
&lt;p&gt;至于怎么判断&lt;code&gt;blank&lt;/code&gt;出现在视图上，可以使用&lt;code&gt;getBoundingClientRect&lt;/code&gt;方法获取&lt;code&gt;top&lt;/code&gt;属性&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;IntersectionObserver&lt;/code&gt; 性能更好，但是我这里就拿&lt;code&gt;getBoundingClientRect&lt;/code&gt;来举例&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/af9e2974b1d84d3c852c0e7f606794cd~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;截屏2021-11-18 下午10.41.01.png&quot;&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;script setup lang=&amp;quot;ts&amp;quot;&amp;gt;
import { onMounted, ref, computed } from &amp;#x27;vue&amp;#x27;
const getList = () =&amp;gt; {
  &amp;#x2F;&amp;#x2F; 跟上面一样的代码
}

const container = ref&amp;lt;HTMLElement&amp;gt;() &amp;#x2F;&amp;#x2F; container节点
const blank = ref&amp;lt;HTMLElement&amp;gt;() &amp;#x2F;&amp;#x2F; blank节点
const list = ref&amp;lt;any&amp;gt;([]) &amp;#x2F;&amp;#x2F; 列表
const page = ref(1) &amp;#x2F;&amp;#x2F; 当前页数
const limit = 200 &amp;#x2F;&amp;#x2F; 一页展示
&amp;#x2F;&amp;#x2F; 最大页数
const maxPage = computed(() =&amp;gt; Math.ceil(list.value.length &amp;#x2F; limit))
&amp;#x2F;&amp;#x2F; 真实展示的列表
const showList = computed(() =&amp;gt; list.value.slice(0, page.value * limit))
const handleScroll = () =&amp;gt; {
  &amp;#x2F;&amp;#x2F; 当前页数与最大页数的比较
  if (page.value &amp;gt; maxPage.value) return
  const clientHeight = container.value?.clientHeight
  const blankTop = blank.value?.getBoundingClientRect().top
  if (clientHeight === blankTop) {
    &amp;#x2F;&amp;#x2F; blank出现在视图，则当前页数加1
    page.value++
  }
}

onMounted(async () =&amp;gt; {
  const res = await getList()
  list.value = res
})
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div id=&amp;quot;container&amp;quot; @scroll=&amp;quot;handleScroll&amp;quot; ref=&amp;quot;container&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;sunshine&amp;quot; v-for=&amp;quot;(item) in showList&amp;quot; :key=&amp;quot;item.tid&amp;quot;&amp;gt;
      &amp;lt;img :src=&amp;quot;item.src&amp;quot; &amp;#x2F;&amp;gt;
      &amp;lt;span&amp;gt;{{ item.text }}&amp;lt;&amp;#x2F;span&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;div ref=&amp;quot;blank&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;虚拟列表&lt;/h2&gt;
&lt;p&gt;虚拟列表需要讲解的比较多，在这里我分享一下我的一篇&lt;code&gt;虚拟列表&lt;/code&gt;的文章，哈哈我自认为讲的不错吧哈哈哈哈哈哈&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6966179727329460232&quot;&gt;结合“康熙选秀”，给大家讲讲“虚拟列表”&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;如果你觉得此文对你有一丁点帮助，点个赞，鼓励一下林三心哈哈。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你想一起学习前端或者摸鱼，那你可以加我，加入我的摸鱼学习群，点击这里&lt;/strong&gt; —&amp;gt; &lt;a href=&quot;https://juejin.cn/pin/7035153948126216206&quot; title=&quot;https://juejin.cn/pin/7035153948126216206&quot;&gt;摸鱼沸点&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你是有其他目的的，别加我，我不想跟你交朋友，我只想简简单单学习前端，不想搞一些有的没的！！！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: Sunshine_Lin
        原文链接：&lt;a href=&quot;https://juejin.im/post/7031923575044964389&quot;&gt;https://juejin.im/post/7031923575044964389&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Wed, 12 Jan 2022 10:38:54 GMT</pubDate></item><item><title>研究大佬写的倒计时组件(Vue)，学到了不少东西__Vue.js__前端</title><link>http://vue-js.com/topic/61deafbead745b003c9df6d8</link><guid>http://vue-js.com/topic/61deafbead745b003c9df6d8</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0234702e819c4f1c94fce6869e0d88c6~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;file&quot;&gt;&lt;/p&gt;
&lt;p&gt;本文首发于：&lt;a href=&quot;https://github.com/bigo-frontend/blog/&quot;&gt;https://github.com/bigo-frontend/blog/&lt;/a&gt; 欢迎关注、转载。&lt;/p&gt;
&lt;h2&gt;一、前言&lt;/h2&gt;
&lt;p&gt;入职的第一个需求是跟着一位前端大佬一起完成的一个活动项目。&lt;/p&gt;
&lt;p&gt;由于是一起开发，当然不会放过阅读大佬的代码的机会。&lt;/p&gt;
&lt;p&gt;因为我的页面中需要使用到倒计时功能，发现大佬的已经写了个现成的倒计时组件，于是直接就拿过来用了。&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/19cbeea00ba94feda62b720dcca08098~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;传个参数就实现了功能的感觉真是太棒了。项目完成后，就膜拜了一下大佬的倒计时组件的代码。真是让我学到了不少。列举如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计时器为什么要用setTimeout而不用setInterval&lt;/li&gt;
&lt;li&gt;为什么不直接将剩余时间-1。&lt;/li&gt;
&lt;li&gt;如何将所需要的时间返回出去（有可能我只需要分钟和秒数，那就只返回分钟和秒数，也有可能我全都要）。&lt;/li&gt;
&lt;li&gt;不确定接口返回的是剩余时间还是截止日期，该怎么同时兼容这两种情况。&lt;/li&gt;
&lt;li&gt;不确定接口返回的时间是秒还是毫秒单位。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;好了，你可能不太理解这些问题，但是没关系，看完下面的解释，相信你会豁然开朗。
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5313d084d073497a863901a6a907f0ab~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;h2&gt;二、开始手操&lt;/h2&gt;
&lt;h3&gt;1. 先创建一个vue组件&lt;/h3&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
   
  }),
  props: {
    
  },
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2. 实现基本的倒计时组件&lt;/h3&gt;
&lt;p&gt;接下来，假设接口获得的是一个剩余时间。&lt;/p&gt;
&lt;p&gt;将剩余时间&lt;code&gt;time&lt;/code&gt;传入这个倒计时组件，由于time可能是秒为单位的，也有可能是毫秒为单位的，所以我们需要在传入&lt;code&gt;time&lt;/code&gt;的是有也传入一个&lt;code&gt;isMilliSecond&lt;/code&gt;来告诉倒计时组件这个&lt;code&gt;time&lt;/code&gt;是毫秒还是秒为单位的。如下代码中的&lt;code&gt;props&lt;/code&gt;所示。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;computed&lt;/code&gt;中的duration是将time进行转化的结果，不管&lt;code&gt;time&lt;/code&gt;是毫秒还是秒，都转化为秒
不知道你注意到了没有：&lt;code&gt;+this.time&lt;/code&gt;。为什么要在前面加个‘&lt;strong&gt;+&lt;/strong&gt;’号。这点很值得我们学习，因为接口返回的一串数字有时候是字符串的形式，有时候是数字的形式（&lt;s&gt;不能过分相信后端同学，必须自己做好防范&lt;/s&gt;）。所以通过前面加个‘&lt;strong&gt;+&lt;/strong&gt;’号 通通转化为数字。现在的&lt;code&gt;duration&lt;/code&gt;就是转化后的&lt;code&gt;time&lt;/code&gt;啦！&lt;/p&gt;
&lt;p&gt;我们获得duration之后就可以开始倒计时了&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  &amp;#x2F;&amp;#x2F; 新增代码：
  mounted() {
    this.countDown();
  },
  methods: {
    countDown() {
      this.getTime(this.duration);
    },
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在这里创建了一个countDown方法，表示开始倒计时的意思，已进入页面就开始执行countdown方法。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;countDown&lt;/code&gt;方法调用了getTime方法，getTime需要传入duration这个参数，也就是我们获得的剩余时间。&lt;/p&gt;
&lt;p&gt;现在来实现一下这个方法。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
    还剩{{day}}天{{hours}}:{{mins}}:{{seconds}}
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
    days: &amp;#x27;0&amp;#x27;,
    hours: &amp;#x27;00&amp;#x27;,
    mins: &amp;#x27;00&amp;#x27;,
    seconds: &amp;#x27;00&amp;#x27;,
    timer: null,
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  mounted() {
    this.countDown();
  },
  methods: {
    countDown() {
      this.getTime(this.duration);
    },
    &amp;#x2F;&amp;#x2F; 新增代码：
    getTime(duration) {
      this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);
      if (duration &amp;lt; 0) {
        return;
      }
      const { dd, hh, mm, ss } = this.durationFormatter(duration);
      this.days = dd || 0;
      this.hours = hh || 0;
      this.mins = mm || 0;
      this.seconds = ss || 0;
      this.timer = setTimeout(() =&amp;gt; {
        this.getTime(duration - 1);
      }, 1000);
    }
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到，&lt;strong&gt;getTime&lt;/strong&gt;的目的就是获得 days，hours,mins，seconds，然后显示到html上，并且通过定时器实时来刷新days，hours,mins，seconds这个几个值。从而实现了倒计时。很简单，有木有？&lt;/p&gt;
&lt;p&gt;&lt;code&gt;durationFormatter&lt;/code&gt;是一个将&lt;code&gt;duration&lt;/code&gt;转化成天数，小时，分钟，秒数的方法，很简单，可以看下它的具体实现。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;durationFormatter(time) {
  if (!time) return { ss: 0 };
  let t = time;
  const ss = t % 60;
  t = (t - ss) &amp;#x2F; 60;
  if (t &amp;lt; 1) return { ss };
  const mm = t % 60;
  t = (t - mm) &amp;#x2F; 60;
  if (t &amp;lt; 1) return { mm, ss };
  const hh = t % 24;
  t = (t - hh) &amp;#x2F; 24;
  if (t &amp;lt; 1) return { hh, mm, ss };
  const dd = t;
  return { dd, hh, mm, ss };
},
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;好了，问题开始来了！！
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/36794496b1f4436499617fa23eb621e7~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;h3&gt;3. 为什么要&lt;strong&gt;用setTimeout来模拟setInterval的行为&lt;/strong&gt;？&lt;/h3&gt;
&lt;p&gt;这里用setInerval不是更方便吗？&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;setTimeout(function(){··· }, n); &amp;#x2F;&amp;#x2F; n毫秒后执行function
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;setInterval(function(){··· }, n); &amp;#x2F;&amp;#x2F; 每隔n毫秒执行一次function
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看看setInterval有什么缺点：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;再次强调，定时器指定的时间间隔，表示的是何时将定时器的代码添加到消息队列，而不是何时执行代码。所以真正何时执行代码的时间是不能保证的，取决于何时被主线程的事件循环取到，并执行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;setInterval(function, N)  
&amp;#x2F;&amp;#x2F;即：每隔N秒把function事件推到消息队列中
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/39aa93f16e274e499f567cf7e31e33f6~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;上图可见，setInterval每隔100ms往队列中添加一个事件；100ms后，添加T1定时器代码至队列中，主线程中还有任务在执行，所以等待，some event执行结束后执行T1定时器代码；又过了100ms，T2定时器被添加到队列中，主线程还在执行T1代码，所以等待；又过了100ms，理论上又要往队列里推一个定时器代码，但由于此时T2还在队列中，所以T3不会被添加，结果就是此时被跳过；这里我们可以看到，T1定时器执行结束后马上执行了T2代码，所以并没有达到定时器的效果。&lt;/p&gt;
&lt;p&gt;综上所述，setInterval有两个缺点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用setInterval时，某些间隔会被跳过；&lt;/li&gt;
&lt;li&gt;可能多个定时器会连续执行；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可以这么理解：&lt;strong&gt;每个setTimeout产生的任务会直接push到任务队列中；而setInterval在每次把任务push到任务队列前，都要进行一下判断(看上次的任务是否仍在队列中)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因而我们一般用setTimeout模拟setInterval，来规避掉上面的缺点。&lt;/p&gt;
&lt;h3&gt;4. 为什么要clearTimeout(this.timer)&lt;/h3&gt;
&lt;p&gt;第二问：为什么要有&lt;code&gt;this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);&lt;/code&gt;这一句？&lt;/p&gt;
&lt;p&gt;假设一个场景：&lt;/p&gt;
&lt;p&gt;如图所示，在倒计时的父组件中，有两个按钮，点击活动一就会传入活动一的剩余时间，点击活动二，就会传入活动二的时间。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1f5ba9e8f4df440f80df305a0860a4f9~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;如果此时倒计时组件正在做活动一的倒计时，然后点击活动二，就要会马上传入新的time，这个时候就需要重新计时。当然，这里并不会重新计时，因为组件的mounted只会执行一次。也就是说&lt;code&gt;this.countDown();&lt;/code&gt;只会执行一次，也就是说&lt;code&gt;this.getTime(this.duration);&lt;/code&gt;只会执行一次，因此duration还是活动一的时间，怎么办呢？watch派上用场了。&lt;/p&gt;
&lt;p&gt;我们来监听duration，如果发现duration变化，说明新的时间time传入组件，这时就要重新调用this.countDown()。&lt;/p&gt;
&lt;p&gt;代码如下：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
    还剩{{day}}天{{hours}}:{{mins}}:{{seconds}}
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
    days: &amp;#x27;0&amp;#x27;,
    hours: &amp;#x27;00&amp;#x27;,
    mins: &amp;#x27;00&amp;#x27;,
    seconds: &amp;#x27;00&amp;#x27;,
    timer: null,
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  mounted() {
    this.countDown();
  },
  &amp;#x2F;&amp;#x2F; 新增代码：
  watch: {
    duration() {
      this.countDown();
    }
  },
  methods: {
    countDown() {
      this.getTime(this.duration);
    },
    durationFormatter(){...}
    getTime(duration) {
      this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);
      if (duration &amp;lt; 0) {
        return;
      }
      const { dd, hh, mm, ss } = this.durationFormatter(duration);
      this.days = dd || 0;
      this.hours = hh || 0;
      this.mins = mm || 0;
      this.seconds = ss || 0;
      this.timer = setTimeout(() =&amp;gt; {
        this.getTime(duration - 1);
      }, 1000);
    }
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;好了，但是并没有解释上面提出的那个问题：为什么要有&lt;code&gt;this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);&lt;/code&gt;这一句？&lt;/p&gt;
&lt;p&gt;这样，假设现在页面显示的是活动一的时间，这时，执行到setTimeout，在&lt;strong&gt;一秒后&lt;/strong&gt;就会把setTimeout里的回调函数放到任务队列中，&lt;strong&gt;注意是一秒后哦&lt;/strong&gt;！这时，然而，在这一秒的开头，我们点击了活动二按钮，这时候的活动二的时间就会传入倒计时组件中，然后触发&lt;code&gt;countDown()&lt;/code&gt;,也就调用&lt;code&gt;this.getTime(this.duration);&lt;/code&gt;，然后执行到setTimeout，也会一秒后把回调函数放到任务队列中。&lt;/p&gt;
&lt;p&gt;这时，任务队列中就会有两个setTimeout的回调函数了。等待一秒过去，两个回调函数相继执行，我们就会看到页面上的时间一下子背减了2，实际上是很快速地进行了两遍减1的操作。&lt;/p&gt;
&lt;p&gt;这就是为什么要添加上&lt;code&gt;this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);&lt;/code&gt;这一句的原因了。就是要把上一个setTimeout清除掉。&lt;/p&gt;
&lt;h3&gt;5. 使用 diffTime&lt;/h3&gt;
&lt;p&gt;当你认为这是一个完美的组件的时候，你想把这个组件用到项目上，假设你也确实用了，而且还上线了，确发现出现了个大问题：当页面打开的时候，倒计时开始了，时间是 &lt;code&gt;还剩1天12：25：25&lt;/code&gt;，然后有人给你发微信，你马上切换到微信，回复消息后切回浏览器，发现倒计时时间却还是&lt;code&gt;还剩1天12：25：25&lt;/code&gt;。你慌了：&lt;strong&gt;你写的代码出现bug了！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是怎么回事？&lt;/p&gt;
&lt;p&gt;出于节能的考虑, 部分浏览器在进入后台时(或者失去焦点时), &lt;strong&gt;会将 setTimeout 等定时任务暂停
待用户回到浏览器时, 才会重新激活定时任务&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;说是暂停, 其实应该说是延迟, 1s 的任务延迟到 2s, 2s 的延迟到 5s, 实际情况因浏览器而异。&lt;/p&gt;
&lt;p&gt;原来如此，看来不能每次都只是减1这么简单了（毕竟你把浏览器切到后台之后setTimeout就冷却了，等几秒后切回，然后执行setTimeout，只是减了一秒而已）。&lt;/p&gt;
&lt;p&gt;所以我们需要改写一下getTime方法。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down&amp;quot;&amp;gt;
    还剩{{day}}天{{hours}}:{{mins}}:{{seconds}}
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;

export default {
  data: () =&amp;gt; ({
    days: &amp;#x27;0&amp;#x27;,
    hours: &amp;#x27;00&amp;#x27;,
    mins: &amp;#x27;00&amp;#x27;,
    seconds: &amp;#x27;00&amp;#x27;,
    timer: null,
    curTime: 0,&amp;#x2F;&amp;#x2F; 新增代码：
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    isMilliSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  mounted() {
    this.countDown();
  },
  
  watch: {
    duration() {
      this.countDown();
    }
  },
  methods: {
    countDown() {
      &amp;#x2F;&amp;#x2F; 新增代码：
      this.curTime = Date.now();
      this.getTime(this.duration);
    },
    durationFormatter(){...}
    getTime(duration) {
      this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);
      if (duration &amp;lt; 0) {
        return;
      }
      const { dd, hh, mm, ss } = this.durationFormatter(duration);
      this.days = dd || 0;
      this.hours = hh || 0;
      this.mins = mm || 0;
      this.seconds = ss || 0;
      this.timer = setTimeout(() =&amp;gt; {
        &amp;#x2F;&amp;#x2F; 新增代码：
        const now = Date.now();
        const diffTime = Math.floor((now - this.curTime) &amp;#x2F; 1000);
        this.curTime = now;
        this.getTime(duration - diffTime);
      }, 1000);
    }
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;

&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到，我们在三个位置添加了新的代码。&lt;/p&gt;
&lt;p&gt;首先在data了添加了curTime这个变量，然后在执行countDown的时候给&lt;code&gt;curTime&lt;/code&gt;赋值&lt;code&gt;Date.now()&lt;/code&gt;，也就是当前的时刻，也就是显示在页面上的那个时刻。&lt;/p&gt;
&lt;p&gt;然后看修改的第三处代码。可以看到是将&lt;code&gt;-1&lt;/code&gt;改成了&lt;code&gt;-diffTime&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;now 是 setTimeout的回调函数执行的时候的那个时刻。&lt;/p&gt;
&lt;p&gt;因而 diffTime 则 表示 &lt;strong&gt;当前这个setTimeout的回调函数执行的时刻距离上 页面上的剩余时间上一次变化的时间段&lt;/strong&gt;。其实也就是 &lt;strong&gt;当前这个setTimeout的回调函数执行的时刻距离上 一个setTimeout的回调函数执行的时刻时间段。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可能你还是不太能理解diffTime。举个例子：&lt;/p&gt;
&lt;p&gt;你打开了这个倒计时页面，于是执行了countDown，也就是说要执行getTime这个方法了。也就是会马上执行下列的代码。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;执行完这些代码页面上就会出现剩余时间。&lt;/p&gt;
&lt;p&gt;而&lt;code&gt;this.curTime = Date.now();&lt;/code&gt; 就记录下了此刻的时间点。&lt;/p&gt;
&lt;p&gt;然后一秒后执行setTimeout里的回调函数：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;const now = Date.now();&lt;/code&gt; 记录当前这个setTimeout的回调函数执行的时间点。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;const diffTime = Math.floor((now - this.curTime) / 1000);&lt;/code&gt; 记录当前这个setTimeout的回调函数执行的时间点距离页面上开始 渲染 剩余时间的 这一段时间。其实此时的diffTime就是=1。&lt;/p&gt;
&lt;p&gt;然后&lt;code&gt;this.curTime = now;&lt;/code&gt; 将curTime的值变成当前这个setTimeout的回调函数执行的时间点。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;this.getTime(duration - diffTime);&lt;/code&gt; 其实就是&lt;code&gt;this.getTime(duration - 1);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;然后又执行getTime，就会重新执行下面的代码，有渲染了新的剩余时间。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;this.days = dd || 0;
this.hours = hh || 0;
this.mins = mm || 0;
this.seconds = ss || 0;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后一秒后又要执行setTmieout的回调函数，在这一秒还没结束的时候，我们将浏览器切到后台，此时setTimeout冷却了。等5秒后再切回。于是setTmieout的回调函数才得以执行。&lt;/p&gt;
&lt;p&gt;这时&lt;code&gt;const now = Date.now();&lt;/code&gt; 记录当前这个setTimeout的回调函数执行的时间点。&lt;/p&gt;
&lt;p&gt;而curTime是上一个setTimeout的回调函数执行的时间。&lt;/p&gt;
&lt;p&gt;所以&lt;code&gt;const diffTime = Math.floor((now - this.curTime) / 1000);&lt;/code&gt;实际上，diffTime的值就是5秒。&lt;/p&gt;
&lt;p&gt;因而&lt;code&gt;this.getTime(duration - diffTime);&lt;/code&gt; 其实就是&lt;code&gt;this.getTime(duration - 5);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这样就完美解决了因为浏览器切到后台，导致剩余时间不变的问题。&lt;/p&gt;
&lt;h3&gt;6. 添加新功能：可以传入到期时间。&lt;/h3&gt;
&lt;p&gt;之前是只能传入剩余时间的，现在希望也支持传入到期时间。&lt;/p&gt;
&lt;p&gt;只需要改动一下duration就好了。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;  computed: {
    duration() {
      if (this.end) {
        let end = String(this.end).length &amp;gt;= 13 ? +this.end : +this.end * 1000;
        end -= Date.now();
        return end;
      }
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;判断传入的end的长度是否大于13来判断是秒还是毫秒。轻松！&lt;/p&gt;
&lt;h3&gt;7. 添加新功能：可以选择要显示的内容，例如只显示秒，或者只显示小时。&lt;/h3&gt;
&lt;p&gt;只需要改动一下html：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down no-rtl&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
      &amp;lt;slot v-bind=&amp;quot;{
        d: days, h: hours, m: mins, s: seconds,
        hh: &amp;#96;00${hours}&amp;#96;.slice(-2),
        mm: &amp;#96;00${mins}&amp;#96;.slice(-2),
        ss: &amp;#96;00${seconds}&amp;#96;.slice(-2),
      }&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;slot&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;很巧妙有没有，只需要用插槽，就把倒计时组件，也就是把子组件的值传递给父组件了。&lt;/p&gt;
&lt;p&gt;看看父组件是怎么使用这个组件的。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;base-counter v-slot=&amp;quot;timeObj&amp;quot; :time=&amp;quot;countDown&amp;quot;&amp;gt;
  &amp;lt;div class=&amp;quot;count-down&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;icon&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;div&amp;gt;
    {{timeObj.d}}天{{timeObj.hh}}小时{{timeObj.mm}}分钟{{timeObj.ss}}秒
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;base-counter&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;看，如此巧妙又简单。&lt;/p&gt;
&lt;p&gt;发现&lt;code&gt;00${hours}&lt;/code&gt;.slice(-2) 这种写法也很值得学习。以前在获得到分钟的时候，要手动判断获得的分钟是两位数还是一位数，如果是一位数的话就要在前面手动补上0。就像下面的代码：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;var StartMinute = startDate.getMinutes().toString().length &amp;gt;= 2 ? startDate.getMinutes() : &amp;#x27;0&amp;#x27; + startDate.getHours();
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而&lt;code&gt;00${hours}&lt;/code&gt;.slice(-2) 则不用判断，先补上0再说，然后再从后面往前截取两位。
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/72a2f7a957e64e9d96b00a420fa2725b~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;到此。&lt;/p&gt;
&lt;p&gt;一个完美的倒计时组件就完成了。
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f23f102146a1443db651239f5cba83df~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;h2&gt;三、学习总结&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;明白了setInterval的缺点以及用setTimeout代替setInterval。&lt;/li&gt;
&lt;li&gt;学到了&lt;code&gt;“+”&lt;/code&gt;，操作，不管三七二十一，将接口得到的长串数字转化为数字保平安。&lt;/li&gt;
&lt;li&gt;利用clearTimeout来清除掉之前的计时器，以防止造成影响。&lt;/li&gt;
&lt;li&gt;学会使用v-slot来子传父传值&lt;/li&gt;
&lt;li&gt;学会一个倒计时组件，为了以后方便cv操作。把组件完整代码贴上：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;_base-count-down no-rtl&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;content&amp;quot;&amp;gt;
      &amp;lt;slot v-bind=&amp;quot;{
        d: days, h: hours, m: mins, s: seconds,
        hh: &amp;#96;00${hours}&amp;#96;.slice(-2),
        mm: &amp;#96;00${mins}&amp;#96;.slice(-2),
        ss: &amp;#96;00${seconds}&amp;#96;.slice(-2),
      }&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;slot&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
&amp;#x2F;* eslint-disable object-curly-newline *&amp;#x2F;

export default {
  data: () =&amp;gt; ({
    days: &amp;#x27;0&amp;#x27;,
    hours: &amp;#x27;00&amp;#x27;,
    mins: &amp;#x27;00&amp;#x27;,
    seconds: &amp;#x27;00&amp;#x27;,
    timer: null,
    curTime: 0
  }),
  props: {
    time: {
      type: [Number, String],
      default: 0
    },
    refreshCounter: {
      type: [Number, String],
      default: 0
    },
    end: {
      type: [Number, String],
      default: 0
    },
    isMiniSecond: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    duration() {
      if (this.end) {
        let end = String(this.end).length &amp;gt;= 13 ? +this.end : +this.end * 1000;
        end -= Date.now();
        return end;
      }
      const time = this.isMiniSecond ? Math.round(+this.time &amp;#x2F; 1000) : Math.round(+this.time);
      return time;
    }
  },
  mounted() {
    this.countDown();
  },
  watch: {
    duration() {
      this.countDown();
    },
    refreshCounter() {
      this.countDown();
    }
  },
  methods: {
    durationFormatter(time) {
      if (!time) return { ss: 0 };
      let t = time;
      const ss = t % 60;
      t = (t - ss) &amp;#x2F; 60;
      if (t &amp;lt; 1) return { ss };
      const mm = t % 60;
      t = (t - mm) &amp;#x2F; 60;
      if (t &amp;lt; 1) return { mm, ss };
      const hh = t % 24;
      t = (t - hh) &amp;#x2F; 24;
      if (t &amp;lt; 1) return { hh, mm, ss };
      const dd = t;
      return { dd, hh, mm, ss };
    },
    countDown() {
      &amp;#x2F;&amp;#x2F; eslint-disable-next-line no-unused-expressions
      this.curTime = Date.now();
      this.getTime(this.duration);
    },
    getTime(time) {
      &amp;#x2F;&amp;#x2F; eslint-disable-next-line no-unused-expressions
      this.timer &amp;amp;&amp;amp; clearTimeout(this.timer);
      if (time &amp;lt; 0) {
        return;
      }
      &amp;#x2F;&amp;#x2F; eslint-disable-next-line object-curly-newline
      const { dd, hh, mm, ss } = this.durationFormatter(time);
      this.days = dd || 0;
      &amp;#x2F;&amp;#x2F; this.hours = &amp;#96;00${hh || &amp;#x27;&amp;#x27;}&amp;#96;.slice(-2);
      &amp;#x2F;&amp;#x2F; this.mins = &amp;#96;00${mm || &amp;#x27;&amp;#x27;}&amp;#96;.slice(-2);
      &amp;#x2F;&amp;#x2F; this.seconds = &amp;#96;00${ss || &amp;#x27;&amp;#x27;}&amp;#96;.slice(-2);
      this.hours = hh || 0;
      this.mins = mm || 0;
      this.seconds = ss || 0;
      this.timer = setTimeout(() =&amp;gt; {
        const now = Date.now();
        const diffTime = Math.floor((now - this.curTime) &amp;#x2F; 1000);
        const step = diffTime &amp;gt; 1 ? diffTime : 1; &amp;#x2F;&amp;#x2F; 页面退到后台的时候不会计时，对比时间差，大于1s的重置倒计时
        this.curTime = now;
        this.getTime(time - step);
      }, 1000);
    }
  }
};
&amp;lt;&amp;#x2F;script&amp;gt;
&amp;lt;style lang=&amp;#x27;scss&amp;#x27; scoped&amp;gt;
@import &amp;#x27;~@assets&amp;#x2F;css&amp;#x2F;common.scss&amp;#x27;;

._base-count-down {
  color: #fff;
  text-align: left;
  position: relative;
  .content {
    width: auto;
    display: flex;
    align-items: center;
  }
  span {
    display: inline-block;
  }
  .section {
    position: relative;
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;你学到了什么，欢迎补充！！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;欢迎大家留言讨论，祝工作顺利、生活愉快！&lt;/p&gt;
&lt;p&gt;我是bigo前端，下期见。&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: bigo前端
        原文链接：&lt;a href=&quot;https://juejin.im/post/7038405108371030047&quot;&gt;https://juejin.im/post/7038405108371030047&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Wed, 12 Jan 2022 10:38:54 GMT</pubDate></item><item><title>Vue3.2发布了，我激动了，实在学不动了！__Vue.js__Vuex</title><link>http://vue-js.com/topic/61deafbecbbfd1003b11f95b</link><guid>http://vue-js.com/topic/61deafbecbbfd1003b11f95b</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h1&gt;什么？VUE3.2 发布了 ？&lt;/h1&gt;
&lt;p&gt;前两天一早收到尤大微博推送，Vue 3.2 发布了！好家伙，上个版本还没玩儿透，这新版本就来了。评论区也是一片哀嚎&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0e9c8c9f547d4fb1b97e2bf8f8877104~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;\&lt;/p&gt;
&lt;p&gt;但也正如评论区所言，很难不支持。让我们看看这次更新了点儿什么。&lt;/p&gt;
&lt;p&gt;两个新功能已经从实验状态毕业了&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; 与 &lt;code&gt;&amp;lt;style&amp;gt; v-bind&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;-----------------------------华尔兹分割----------------------------&lt;/p&gt;
&lt;h1&gt;第一个：&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; 正式毕业&lt;/h1&gt;
&lt;p&gt;从一开始学习vue3就注意到了实验性的&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;。因为简洁的写法受到许多人追捧（写过&lt;em&gt;setup(){return{}}&lt;/em&gt; 的人都知道），甚至有人说这才是vue3的完全形态。看了更新描述，emmm…官方吐槽最为致命。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bafce637dbc34a0294c99824897c45cf~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; 是一种编译时语法糖，可在 SFC 内使用 Composition API 时**极大地改善工作效率。&lt;/p&gt;
&lt;h1&gt;第二个：新增&lt;code&gt;&amp;lt;style&amp;gt; v-bind&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;而&lt;code&gt;&amp;lt;style&amp;gt; v-bind&lt;/code&gt;呢，简单地来说就是可以在&amp;lt;style&amp;gt;内使用组件定义的动态值。官方给出了例子：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;import { ref } from &amp;#x27;vue&amp;#x27;
​
const color = ref(&amp;#x27;red&amp;#x27;)
&amp;lt;&amp;#x2F;script&amp;gt;
​
&amp;lt;template&amp;gt;
  &amp;lt;button @click=&amp;quot;color = color === &amp;#x27;red&amp;#x27; ? &amp;#x27;green&amp;#x27; : &amp;#x27;red&amp;#x27;&amp;quot;&amp;gt;
    Color is: {{ color }}
  &amp;lt;&amp;#x2F;button&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
​
&amp;lt;style scoped&amp;gt;
button {
  color: v-bind(color);
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;因为vue中文官网暂时没有此次的更新内容，需要的同学可能要到外网啃啃英文文档了。&lt;/p&gt;
&lt;p&gt;文档地址&lt;a href=&quot;https://v3.vuejs.org/api/sfc-script-setup.html&quot;&gt;：https://v3.vuejs.org/api/sfc-script-setup.html&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;第三个：新增 defineCustomElement方法&lt;/h1&gt;
&lt;p&gt;Vue 3.2 引入了一个新的 defineCustomElement 方法，可以使用 Vue 组件 API 轻松创建原生自定义元素：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;import { defineCustomElement } from &amp;#x27;vue&amp;#x27;
​
const MyVueElement = defineCustomElement({
  &amp;#x2F;&amp;#x2F; normal Vue component options here
})
​
&amp;#x2F;&amp;#x2F; Register the custom element.
&amp;#x2F;&amp;#x2F; After registration, all &amp;#96;&amp;lt;my-vue-element&amp;gt;&amp;#96; tags
&amp;#x2F;&amp;#x2F; on the page will be upgraded.
customElements.define(&amp;#x27;my-vue-element&amp;#x27;, MyVueElement)
&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;第四个：性能改进&lt;/h1&gt;
&lt;p&gt;此处有很大篇幅讲述3.2版本的性能升级，其中提到了新的指令&lt;code&gt;v-memo&lt;/code&gt;，简单来说这个指令会记住模板树的一部分，不仅跳过虚拟 DOM 差异，而且完全跳过新 VNode 的创建。可用于复杂的大型页面。&lt;/p&gt;
&lt;h1&gt;第五个：服务器渲染&lt;/h1&gt;
&lt;p&gt;最后提到了服务端渲染与新的Effect Scope API。有兴趣的同学可以仔细看一看更新文档。&lt;a href=&quot;https://blog.vuejs.org/posts/vue-3.2.html%E3%80%82&quot;&gt;https://blog.vuejs.org/posts/vue-3.2.html。&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;第6个：1024Lab 再说点儿&lt;/h1&gt;
&lt;p&gt;相信很多同学已经上手开始使用&amp;lt;scripte setup&amp;gt;了。在文档中可以看到，&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;defineProps、defineEmits、defineExpose、withDefaults***属于***compiler macro，编译器宏&lt;/strong&gt;&lt;/em&gt;。文档中也说到：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;They do not need to be imported, and are compiled away when is processed&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;他们不需要引入，会在编译的时候处理掉。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;然而不引入你用的时候就会报错。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;script setup lang=&amp;quot;ts&amp;quot;&amp;gt;
const props = defineProps&amp;lt;{
  value?: number;
}&amp;gt;();
​
const emit = defineEmits&amp;lt;{
  (e: &amp;#x27;update:value&amp;#x27;, value: number): void;
}&amp;gt;();
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;首先eslint会报错：&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;ESLint: ‘defineEmits’ is not defined.(no-undef)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;此时你需要更改eslint-plugin-vue的配置&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;https:&amp;#x2F;&amp;#x2F;eslint.vuejs.org&amp;#x2F;user-guide&amp;#x2F;#compiler-macros-such-as-defineprops-and-defineemits-are-warned-by-no-undef-rule
module.exports = {
  globals: {
    defineProps: &amp;quot;readonly&amp;quot;,
    defineEmits: &amp;quot;readonly&amp;quot;,
    defineExpose: &amp;quot;readonly&amp;quot;,
    withDefaults: &amp;quot;readonly&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;然后可能编译后浏览器控制台会报错&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;defineEmits is not defined&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;你可能会发现defineEmits等并没有在编译的时候处理掉，通过浏览器看源代码defineEmits还在，且画着红色波浪线。此时你可能需要查看package.json中各个包的版本以及vite的版本2.4.x，更新后重试，此时编译出来的代码应该是这样：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const _sfc_main = _defineComponent({
  props: {
    value: { type: Number, required: false }
  },
  emits: [&amp;quot;update:value&amp;quot;],
  setup(__props, { expose, emit }) {}
 })
&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;第7点：最后让我吐槽一下下&lt;/h1&gt;
&lt;p&gt;尤大在微博上提到的Volar是一款vscode的插件，这让webstorm党…哭惨了…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但是Vue 3.2 + script setup + typescript + volar 真香！真的香！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SmartAdmin2.0 也在用最新的 &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;重构，真香~&lt;/p&gt;
&lt;p&gt;SmartAdmin: &lt;a href=&quot;https://github.com/1024-lab/smart-admin&quot;&gt;https://github.com/1024-lab/smart-admin&lt;/a&gt;&lt;p&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
作者: 1024创新实验室
原文链接：&lt;a href=&quot;https://juejin.im/post/6995777059725246472&quot;&gt;https://juejin.im/post/6995777059725246472&lt;/a&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>banyungong</author><pubDate>Wed, 12 Jan 2022 10:38:54 GMT</pubDate></item><item><title>Element-UI 奇淫技巧第二弹！提升开发效率，延长摸鱼时间~__Element__前端</title><link>http://vue-js.com/topic/61deafbead745b003c9df6d7</link><guid>http://vue-js.com/topic/61deafbead745b003c9df6d7</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: cyanosis
highlight: atom-one-light&lt;/h2&gt;
&lt;h3&gt;主题样式修改&lt;/h3&gt;
&lt;p&gt;曾经为了修改组件风格，一个项目用了上百个样式穿透，后来才发现定制一个主题就够了！&lt;/p&gt;
&lt;h4&gt;第一步，在官网的主题页面，修改背景色、字体颜色及边框颜色&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5389bfb8437f4671a1abcde50e5deccf~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;01.jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;第二步，下载主题&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f83bcb6472c644499dca3c17baa8d57a~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1639302082(1).jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;第三步，用下载的css文件替换掉默认的css文件&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7342d4f166f5486cabaed5ce16a45502~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h4&gt;效果如下：&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5f37ae06ca1f461088fff172422103f2~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;el-backtop 回到顶部组件&lt;/h3&gt;
&lt;p&gt;有时候页面很长，我们需要一个回到顶部的按钮，推荐大家使用 &lt;code&gt;el-backtop&lt;/code&gt;，自带过渡效果。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/12e604091c60439c8034fa6cb2dde77f~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;20211213_211233 00_00_00-00_00_30.gif&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;box&amp;quot;&amp;gt;Scroll down to see the bottom-right button.&amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;el-backtop target=&amp;quot;.container&amp;quot;&amp;gt;
      &amp;lt;i class=&amp;quot;el-icon-caret-top&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;i&amp;gt;
    &amp;lt;&amp;#x2F;el-backtop&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;style lang=&amp;quot;scss&amp;quot; scoped&amp;gt;
.container {
  height: 100vh;
  overflow-x: hidden;
  .box {
    padding: 20px;
    height: 1500px;
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;注意：需要将 body 的 margin 设置为 0，不然会出现两个滚动条。&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;el-form-item label宽度自适应&lt;/h3&gt;
&lt;p&gt;通常我们是给 &lt;code&gt;label&lt;/code&gt; 设置一个固定宽度，但这完全限制了 UI 的发挥，如何让 &lt;code&gt;label&lt;/code&gt; 宽度自适应呢？答案是给控件设置一个固定宽度。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/aa1f4571719548e9b730f548ec1af57a~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;el-form&amp;gt;
    &amp;lt;el-form-item label=&amp;quot;教师&amp;quot;&amp;gt;
      &amp;lt;el-input v-model=&amp;quot;form.teacher&amp;quot; style=&amp;quot;width: 625px;&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-input&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
    &amp;lt;el-form-item label=&amp;quot;请选择在线教学平台名称&amp;quot;&amp;gt;
      &amp;lt;el-select v-model=&amp;quot;form.platform&amp;quot; style=&amp;quot;width: 500px;&amp;quot;&amp;gt;
        &amp;lt;el-option label=&amp;quot;平台一&amp;quot; value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
        &amp;lt;el-option label=&amp;quot;平台二&amp;quot; value=&amp;quot;2&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
      &amp;lt;&amp;#x2F;el-select&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
  &amp;lt;&amp;#x2F;el-form&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      form: {
        teacher: &amp;#x27;&amp;#x27;,
        platform: &amp;#x27;&amp;#x27;
      }
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-form-item 循环校验&lt;/h3&gt;
&lt;p&gt;有时候表单项是一个循环的列表，需要对每一项进行校验。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/09f36b9bde724160bf849cc7747c893e~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1639314939(1).jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;el-form :model=&amp;quot;form&amp;quot; label-width=&amp;quot;100px&amp;quot; ref=&amp;quot;form&amp;quot;&amp;gt;
      &amp;lt;el-form-item
        v-for=&amp;quot;(item, index) in form.list&amp;quot;
        :key=&amp;quot;index&amp;quot;
        :label=&amp;quot;&amp;#x27;评价项目&amp;#x27; + (index + 1)&amp;quot;
        :prop=&amp;quot;&amp;#x27;list.&amp;#x27; + index + &amp;#x27;.evaluateName&amp;#x27;&amp;quot;
        :rules=&amp;quot;rules.evaluateName&amp;quot;
      &amp;gt;
        &amp;lt;el-input v-model=&amp;quot;item.evaluateName&amp;quot; &amp;#x2F;&amp;gt;
      &amp;lt;&amp;#x2F;el-form-item&amp;gt;
      &amp;lt;el-form-item&amp;gt;
        &amp;lt;el-button type=&amp;quot;primary&amp;quot; @click=&amp;quot;submitForm&amp;quot;&amp;gt;提交&amp;lt;&amp;#x2F;el-button&amp;gt;
      &amp;lt;&amp;#x2F;el-form-item&amp;gt;
    &amp;lt;&amp;#x2F;el-form&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      form: {
        list: [
          {
            evaluateName: &amp;#x27;&amp;#x27;
          },
          {
            evaluateName: &amp;#x27;&amp;#x27;
          },
          {
            evaluateName: &amp;#x27;&amp;#x27;
          }
        ]
      },
      rules: {
        evaluateName: [
          {
            required: true,
            message: &amp;#x27;请输入评价项目&amp;#x27;,
            trigger: &amp;#x27;blur&amp;#x27;
          }
        ]
      }
    }
  },
  methods: {
    submitForm() {
      this.$refs.form.validate(valid =&amp;gt; {
        if (valid) {
          alert(&amp;#x27;submit!&amp;#x27;)
        } else {
          console.log(&amp;#x27;error submit!!&amp;#x27;)
          return false
        }
      })
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-form-item 表单嵌套表格校验&lt;/h3&gt;
&lt;p&gt;如果在表单里嵌套表格，那么如何对表格里面的控件进行校验呢？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2f61fb571bba4da18f14bec455d10162~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1639315824(1).jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;el-form :model=&amp;quot;form&amp;quot; ref=&amp;quot;form&amp;quot;&amp;gt;
    &amp;lt;el-table border :data=&amp;quot;form.list&amp;quot; style=&amp;quot;margin-bottom: 10px;&amp;quot;&amp;gt;
      &amp;lt;el-table-column
        label=&amp;quot;评价项目&amp;quot;
        prop=&amp;quot;evaluateName&amp;quot;
        align=&amp;quot;center&amp;quot;
      &amp;gt;&amp;lt;&amp;#x2F;el-table-column&amp;gt;
      &amp;lt;el-table-column label=&amp;quot;评价等级&amp;quot; align=&amp;quot;center&amp;quot;&amp;gt;
        &amp;lt;template slot-scope=&amp;quot;scope&amp;quot;&amp;gt;
          &amp;lt;el-form-item
            :prop=&amp;quot;&amp;#x27;list.&amp;#x27; + scope.$index + &amp;#x27;.evaluateLevel&amp;#x27;&amp;quot;
            :rules=&amp;quot;rules.evaluateLevel&amp;quot;
            class=&amp;quot;evaluate-level&amp;quot;
          &amp;gt;
            &amp;lt;el-radio-group class=&amp;quot;radio-group&amp;quot; v-model=&amp;quot;scope.row.evaluateLevel&amp;quot;&amp;gt;
              &amp;lt;el-radio
                v-for=&amp;quot;evaluationItem in evaluateOptions&amp;quot;
                :key=&amp;quot;evaluationItem.value&amp;quot;
                :label=&amp;quot;evaluationItem.value&amp;quot;
              &amp;gt;
                {{ evaluationItem.label }}
              &amp;lt;&amp;#x2F;el-radio&amp;gt;
            &amp;lt;&amp;#x2F;el-radio-group&amp;gt;
          &amp;lt;&amp;#x2F;el-form-item&amp;gt;
        &amp;lt;&amp;#x2F;template&amp;gt;
      &amp;lt;&amp;#x2F;el-table-column&amp;gt;
    &amp;lt;&amp;#x2F;el-table&amp;gt;
    &amp;lt;el-form-item&amp;gt;
      &amp;lt;el-button type=&amp;quot;primary&amp;quot; class=&amp;quot;submit-btn&amp;quot; @click=&amp;quot;submitOpinionCheck&amp;quot;&amp;gt;
        提交
      &amp;lt;&amp;#x2F;el-button&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
  &amp;lt;&amp;#x2F;el-form&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      form: {
        list: [
          {
            id: &amp;#x27;01&amp;#x27;,
            evaluateName: &amp;#x27;指标一&amp;#x27;,
            evaluateLevel: &amp;#x27;&amp;#x27;
          },
          {
            id: &amp;#x27;02&amp;#x27;,
            evaluateName: &amp;#x27;指标二&amp;#x27;,
            evaluateLevel: &amp;#x27;&amp;#x27;
          },
          {
            id: &amp;#x27;03&amp;#x27;,
            evaluateName: &amp;#x27;指标二&amp;#x27;,
            evaluateLevel: &amp;#x27;&amp;#x27;
          }
        ]
      },
      evaluateOptions: [
        {
          value: &amp;#x27;1&amp;#x27;,
          label: &amp;#x27;优&amp;#x27;
        },
        {
          value: &amp;#x27;2&amp;#x27;,
          label: &amp;#x27;良&amp;#x27;
        },
        {
          value: &amp;#x27;3&amp;#x27;,
          label: &amp;#x27;中&amp;#x27;
        },
        {
          value: &amp;#x27;4&amp;#x27;,
          label: &amp;#x27;差&amp;#x27;
        }
      ],
      rules: {
        evaluateLevel: [{ required: true, message: &amp;#x27;请选择评价等级&amp;#x27;, trigger: &amp;#x27;change&amp;#x27; }]
      }
    }
  },
  methods: {
    submitOpinionCheck() {
      this.$refs.form.validate(valid =&amp;gt; {
        if (valid) {
          alert(&amp;#x27;submit!&amp;#x27;)
        } else {
          console.log(&amp;#x27;error submit!!&amp;#x27;)
          return false
        }
      })
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;style lang=&amp;quot;scss&amp;quot; scoped&amp;gt;
.evaluate-level {
  ::v-deep .el-form-item__error {
    width: 100px;
    &amp;#x2F;* 使校验信息居中 *&amp;#x2F;
    left: calc(50% - 50px);
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-form-item 表单校验 label只保留 *&lt;/h3&gt;
&lt;p&gt;有时候我们只需要表单校验的 &lt;code&gt;*&lt;/code&gt;， 而不需要 lable 文字，像这样：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ae92a837ac804673a130dd41358653cf~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;1639312147(1).jpg&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;el-form :model=&amp;quot;ruleForm&amp;quot; :rules=&amp;quot;rules&amp;quot; ref=&amp;quot;ruleForm&amp;quot; label-width=&amp;quot;30px&amp;quot;&amp;gt;
    &amp;lt;el-form-item label=&amp;quot; &amp;quot; prop=&amp;quot;name&amp;quot;&amp;gt;
      &amp;lt;el-input v-model=&amp;quot;ruleForm.name&amp;quot; placeholder=&amp;quot;请输入活动名称&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-input&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
    &amp;lt;el-form-item label=&amp;quot; &amp;quot; prop=&amp;quot;region&amp;quot;&amp;gt;
      &amp;lt;el-select v-model=&amp;quot;ruleForm.region&amp;quot; placeholder=&amp;quot;请选择活动区域&amp;quot;&amp;gt;
        &amp;lt;el-option label=&amp;quot;区域一&amp;quot; value=&amp;quot;shanghai&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
        &amp;lt;el-option label=&amp;quot;区域二&amp;quot; value=&amp;quot;beijing&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
      &amp;lt;&amp;#x2F;el-select&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
    &amp;lt;el-form-item&amp;gt;
      &amp;lt;el-button type=&amp;quot;primary&amp;quot; @click=&amp;quot;submitForm(&amp;#x27;ruleForm&amp;#x27;)&amp;quot;&amp;gt;提交&amp;lt;&amp;#x2F;el-button&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
  &amp;lt;&amp;#x2F;el-form&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      ruleForm: {
        name: &amp;#x27;&amp;#x27;,
        region: &amp;#x27;&amp;#x27;
      },
      rules: {
        name: [{ required: true, message: &amp;#x27;请输入活动名称&amp;#x27;, trigger: &amp;#x27;blur&amp;#x27; }],
        region: [{ required: true, message: &amp;#x27;请选择活动区域&amp;#x27;, trigger: &amp;#x27;change&amp;#x27; }]
      }
    }
  },
  methods: {
    submitForm(formName) {
      this.$refs[formName].validate(valid =&amp;gt; {
        if (valid) {
          alert(&amp;#x27;submit!&amp;#x27;)
        } else {
          console.log(&amp;#x27;error submit!!&amp;#x27;)
          return false
        }
      })
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-select 下拉框样式修改&lt;/h3&gt;
&lt;p&gt;使用样式穿透修改下拉框样式，你会发现打死都不生效，那是因为下拉框是默认挂载在 body 下面。解决办法：设置 &lt;code&gt;:popper-append-to-body=&amp;quot;false&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/74f10805b5824ca2819709e561451281~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;el-form&amp;gt;
    &amp;lt;el-form-item label=&amp;quot;活动区域&amp;quot;&amp;gt;
      &amp;lt;el-select
        class=&amp;quot;form-select&amp;quot;
        :popper-append-to-body=&amp;quot;false&amp;quot;
        v-model=&amp;quot;form.region&amp;quot;
        placeholder=&amp;quot;请选择活动区域&amp;quot;
      &amp;gt;
        &amp;lt;el-option label=&amp;quot;区域一&amp;quot; value=&amp;quot;shanghai&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
        &amp;lt;el-option label=&amp;quot;区域二&amp;quot; value=&amp;quot;beijing&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-option&amp;gt;
      &amp;lt;&amp;#x2F;el-select&amp;gt;
    &amp;lt;&amp;#x2F;el-form-item&amp;gt;
  &amp;lt;&amp;#x2F;el-form&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      form: {
        region: &amp;#x27;&amp;#x27;
      }
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;

&amp;lt;style lang=&amp;quot;scss&amp;quot; scoped&amp;gt;
.form-select {
  ::v-deep .el-select-dropdown__item.selected {
    background: pink;
  }
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;el-input 打开弹框 自动聚焦&lt;/h3&gt;
&lt;p&gt;很多时候 form 表单是嵌套在 dialog 下面，为了提高用户体验，需要在打开弹框后 input 框自动聚焦。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a32cde61fe7a4e61bae715783be56c13~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;20211213_205355 00_00_00-00_00_30.gif&quot;&gt;&lt;/p&gt;
&lt;h4&gt;代码如下：&lt;/h4&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;el-button type=&amp;quot;text&amp;quot; @click=&amp;quot;showDialog&amp;quot;&amp;gt;
      打开嵌套表单的 Dialog
    &amp;lt;&amp;#x2F;el-button&amp;gt;

    &amp;lt;el-dialog title=&amp;quot;活动&amp;quot; :visible.sync=&amp;quot;dialogVisible&amp;quot;&amp;gt;
      &amp;lt;el-form :model=&amp;quot;form&amp;quot;&amp;gt;
        &amp;lt;el-form-item label=&amp;quot;活动名称&amp;quot; label-width=&amp;quot;80px&amp;quot;&amp;gt;
          &amp;lt;el-input ref=&amp;quot;nameInput&amp;quot; v-model=&amp;quot;form.name&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-input&amp;gt;
        &amp;lt;&amp;#x2F;el-form-item&amp;gt;
        &amp;lt;el-form-item label=&amp;quot;活动内容&amp;quot; label-width=&amp;quot;80px&amp;quot;&amp;gt;
          &amp;lt;el-input v-model=&amp;quot;form.content&amp;quot;&amp;gt;&amp;lt;&amp;#x2F;el-input&amp;gt;
        &amp;lt;&amp;#x2F;el-form-item&amp;gt;
      &amp;lt;&amp;#x2F;el-form&amp;gt;
      &amp;lt;div slot=&amp;quot;footer&amp;quot; class=&amp;quot;dialog-footer&amp;quot;&amp;gt;
        &amp;lt;el-button @click=&amp;quot;dialogVisible = false&amp;quot;&amp;gt;取 消&amp;lt;&amp;#x2F;el-button&amp;gt;
        &amp;lt;el-button type=&amp;quot;primary&amp;quot; @click=&amp;quot;dialogVisible = false&amp;quot;&amp;gt;确 定&amp;lt;&amp;#x2F;el-button&amp;gt;
      &amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;&amp;#x2F;el-dialog&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      dialogVisible: false,
      form: {
        name: &amp;#x27;&amp;#x27;,
        content: &amp;#x27;&amp;#x27;
      }
    }
  },
  methods: {
    showDialog() {
      this.dialogVisible = true
      &amp;#x2F;&amp;#x2F; 如果弹框显示，则自动聚焦
      this.$nextTick(() =&amp;gt; {
        this.$refs.nameInput.focus()
      })
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;今天的分享就到到这里，希望对你有所帮助！如果觉得有用，不要忘了点个赞呦~&lt;/p&gt;
&lt;p&gt;听说喜欢点赞的你，今年年终奖拿到手软&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 前端阿飞
        原文链接：&lt;a href=&quot;https://juejin.im/post/7041320033884045326&quot;&gt;https://juejin.im/post/7041320033884045326&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Wed, 12 Jan 2022 10:38:54 GMT</pubDate></item><item><title>浅析我们为什么要上Vue3？__前端__Vue.js</title><link>http://vue-js.com/topic/61deafbecbbfd1003b11f95a</link><guid>http://vue-js.com/topic/61deafbecbbfd1003b11f95a</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: vue-pro
highlight: a11y-dark&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;专栏地址：&lt;a href=&quot;https://juejin.cn/column/7007233236925415432&quot;&gt;Vue 源码解析与实践&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本文：&lt;a href=&quot;https://juejin.cn/user/870468940732983&quot;&gt;我是970&lt;/a&gt;&lt;br&gt;
原文链接：&lt;a href=&quot;https://juejin.cn/post/7040769191393099783&quot;&gt;https://juejin.cn/post/7040769191393099783&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;✨ 开篇&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;RT，我们为什么要上 Vue3？使用 Vue3 影响我开法拉利吗？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最近在 Vue3 发布了&lt;code&gt;3.2&lt;/code&gt; 大版本之后，掘金上关于 Vue3 的文章越来越多，本来想着让子弹再飞一会，但最近公司上了 Vue3 的项目，自己也跟着学了起来。&lt;/p&gt;
&lt;p&gt;昨天还是 Vue2 的 Options API 的忠实信徒，结果今天搞了 Vue3 的 Composition API 之后，直呼 Vue3 真香！&lt;/p&gt;
&lt;p&gt;接下来，我们从分析 Vue2 优缺点入手，以及结合图片和用例来了解 Vue3 的优势。&lt;/p&gt;
&lt;h2&gt;Vue2 ⚔ Vue3&lt;/h2&gt;
&lt;h3&gt;Vue2&lt;/h3&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;p&gt;不可否认 Vue2 在取得的成功，在 Github 的 Front-End 分类的排行榜上也能看到 Vue 仓库的排名是第一，粉丝足够多，活跃用户足够多&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a8dace12e30945d181ec50023345c6cb~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/46751d216b4847e48714d649f02cd97a~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;比 React 足足多了 12k+ star&lt;/p&gt;
&lt;p&gt;从我的角度来看，最大的功臣莫过于这三个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;响应式数据&lt;/li&gt;
&lt;li&gt;Options API&lt;/li&gt;
&lt;li&gt;SFC&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除去这三位大将，不可或缺 &lt;code&gt;Vuex&lt;/code&gt;/&lt;code&gt;Vue-Router&lt;/code&gt; 这两位功臣，以及丰富的周边插件和活跃的社区。&lt;/p&gt;
&lt;h4&gt;缺点&lt;/h4&gt;
&lt;p&gt;在一个组件仅承担单一逻辑的时候，使用 Options API 来书写组件是很清晰的。&lt;/p&gt;
&lt;p&gt;但是在我们实际的业务场景中，一个父组件总是要包含多个子组件，父组件需要给子组件传值、处理子组件事件、直接操作子组件以及处理各种各样的调接口的逻辑，这时候我们的父组件的逻辑就会变得复杂。&lt;/p&gt;
&lt;p&gt;我们从代码维护者的角度出发，假设这个时候有 10 个函数方法，自然我们要把他们放入到&lt;code&gt;methods&lt;/code&gt;中，而这个 10 个函数方法又分别操作 10 个数据，这个 10 个数据又分别需要进行&lt;code&gt;Watch&lt;/code&gt;操作。&lt;/p&gt;
&lt;p&gt;这时候，我们根据&lt;code&gt;Vue2&lt;/code&gt;的&lt;code&gt;Options API&lt;/code&gt;的写法，就写完了 10 个&lt;code&gt;method&lt;/code&gt;、10 个&lt;code&gt;data&lt;/code&gt;、10 个&lt;code&gt;watch&lt;/code&gt;，我们就将本来 10 个的函数方法，分割在 30 个不同的地方。&lt;/p&gt;
&lt;p&gt;这时候父组件的代码，对代码维护者来说是很不友好的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可能有人说，这么写可以增加代码量，老板夸我牛逼！哈哈哈哈 &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Vue3&lt;/h3&gt;
&lt;h4&gt;优点&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;自由，自由，还是 TM 的自由！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;更强的性能，更好的 tree shaking&lt;/li&gt;
&lt;li&gt;Composition API + setup&lt;/li&gt;
&lt;li&gt;更好地支持 TypeScript&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;可能的缺点&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Vue3: 让你自由过了火&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;使用&lt;code&gt;Composition API&lt;/code&gt;在&lt;code&gt;setup&lt;/code&gt;这个舞台上尽情的表演之后，可能存在一个问题：那就是如何优雅地组织代码？&lt;/p&gt;
&lt;p&gt;代码不能优雅的组织，在代码量上去之后，也一样很难维护。&lt;/p&gt;
&lt;h3&gt;SFC 写法变化&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/19a5449cbc1b45f4b6bb8941fe940180~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;Options API对比Composition API.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Vue2 一个普通的 Vue 文件&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;p&amp;gt;{{ person.name }}&amp;lt;&amp;#x2F;p&amp;gt;
    &amp;lt;p&amp;gt;{{ car.name }}&amp;lt;&amp;#x2F;p&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: &amp;quot;Person&amp;quot;,

  data() {
    return {
      person: {
        name: &amp;quot;小明&amp;quot;,
        sex: &amp;quot;male&amp;quot;,
      },
      car: {
        name: &amp;quot;宝马&amp;quot;,
        price: &amp;quot;40w&amp;quot;,
      }
    };
  },

  watch:{
      &amp;#x27;person.name&amp;#x27;: (value) =&amp;gt; {
          console.log(&amp;#96;名字被修改了, 修改为 ${value}&amp;#96;)
      },
      &amp;#x27;person.sex&amp;#x27;: (value) =&amp;gt; {
          console.log(&amp;#96;性别被修改了, 修改为 ${value}&amp;#96;)
      }
  },

  methods: {
    changePersonName() {
      this.person.name = &amp;quot;小浪&amp;quot;;
    },

    changeCarPrice() {
      this.car.price = &amp;quot;80w&amp;quot;;
    }
  },
};
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;采用 Vue3 Composition API 进行重写&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;p&amp;gt;{{ person.name }}&amp;lt;&amp;#x2F;p&amp;gt;
  &amp;lt;p&amp;gt;{{ car.name }}&amp;lt;&amp;#x2F;p&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script lang=&amp;quot;ts&amp;quot; setup&amp;gt;
import { reactive, watch } from &amp;quot;vue&amp;quot;;

&amp;#x2F;&amp;#x2F; person的逻辑
const person = reactive&amp;lt;{ name: string; sex: string }&amp;gt;({
  name: &amp;quot;小明&amp;quot;,
  sex: &amp;quot;male&amp;quot;,
});
watch(
  () =&amp;gt; [person.name, person.sex],
  ([nameVal, sexVal]) =&amp;gt; {
    console.log(&amp;#96;名字被修改了, 修改为 ${nameVal}&amp;#96;);
    console.log(&amp;#96;名字被修改了, 修改为 ${sexVal}&amp;#96;);
  }
);
function changePersonName() {
  person.name = &amp;quot;小浪&amp;quot;;
}

&amp;#x2F;&amp;#x2F; car的逻辑
const car = reactive&amp;lt;{ name: string; price: string }&amp;gt;({
  name: &amp;quot;宝马&amp;quot;,
  price: &amp;quot;40w&amp;quot;,
});
function changeCarPrice() {
  car.price = &amp;quot;80w&amp;quot;;
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;采用 Vue3 自定义 Hook 的方式，进一步拆分&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-js&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;p&amp;gt;{{ person.name }}&amp;lt;&amp;#x2F;p&amp;gt;
  &amp;lt;p&amp;gt;{{ car.name }}&amp;lt;&amp;#x2F;p&amp;gt;
  &amp;lt;p&amp;gt;{{ animal.name }}&amp;lt;&amp;#x2F;p&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script lang=&amp;quot;ts&amp;quot; setup&amp;gt;
import { usePerson, useCar, useAnimal } from &amp;quot;.&amp;#x2F;hooks&amp;quot;;

const { person, changePersonName } = usePerson();

const { car } = useCar();
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-ts&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; usePerson.ts
import { reactive, watch } from &amp;quot;vue&amp;quot;;

export default function usePerson() {
  const person = reactive&amp;lt;{ name: string; sex: string }&amp;gt;({
    name: &amp;quot;小明&amp;quot;,
    sex: &amp;quot;male&amp;quot;,
  });
  watch(
    () =&amp;gt; [person.name, person.sex],
    ([nameVal, sexVal]) =&amp;gt; {
      console.log(&amp;#96;名字被修改了, 修改为 ${nameVal}&amp;#96;);
      console.log(&amp;#96;名字被修改了, 修改为 ${sexVal}&amp;#96;);
    }
  );
  function changePersonName() {
    person.name = &amp;quot;小浪&amp;quot;;
  }
  return {
    person,
    changePersonName,
  };
}
&lt;/code&gt;&lt;/pre&gt;&lt;pre class=&quot;prettyprint language-ts&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; useCar.ts
import { reactive } from &amp;quot;vue&amp;quot;;

export default function useCar() {
  const car = reactive&amp;lt;{ name: string; price: string }&amp;gt;({
    name: &amp;quot;宝马&amp;quot;,
    price: &amp;quot;40w&amp;quot;,
  });
  function changeCarPrice() {
    car.price = &amp;quot;80w&amp;quot;;
  }
  return {
    car,
    changeCarPrice,
  };
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对比完之后，我们会发现，Vue3 可以让我们更好组织代码。&lt;code&gt;person&lt;/code&gt;和&lt;code&gt;car&lt;/code&gt;的逻辑都被单独放置在一块&lt;/p&gt;
&lt;p&gt;仅仅是代码组织的优势吗？不不不，我们再看看模板中的一些变化&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;标签中起始便签可以不用&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;标签，因为&lt;code&gt;Vue3&lt;/code&gt;提供了&lt;code&gt;片段&lt;/code&gt;的能力，使用&lt;code&gt;Vue-detools&lt;/code&gt;中查看，可以看到有&lt;code&gt;fragment&lt;/code&gt;的标记&lt;br&gt;
&lt;img src=&quot;https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e2d30c1c9db648bea38eeecb4c7d75f1~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;image.png&quot;&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Watch&lt;/code&gt;也不需要罗列多个了，&lt;code&gt;Vue3&lt;/code&gt;中支持侦听多个源&lt;/li&gt;
&lt;li&gt;很自然的使用&lt;code&gt;TypeScript&lt;/code&gt;，类型约束更加简单&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;性能方面&lt;/h3&gt;
&lt;p&gt;Vue3 主要在这几个方面进行了提升&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;编译阶段。对 diff 算法优化、静态提升等等&lt;/li&gt;
&lt;li&gt;响应式系统。&lt;code&gt;Proxy()&lt;/code&gt;替代&lt;code&gt;Object.defineProperty()&lt;/code&gt;监听对象。监听一个对象，不需要再深度遍历，&lt;code&gt;Proxy()&lt;/code&gt;就可以劫持整个对象&lt;/li&gt;
&lt;li&gt;体积包减少。Compostion API 的写法，可以更好的进行 tree shaking，减少上下文没有引入的代码，减少打包后的文件体积&lt;/li&gt;
&lt;li&gt;新增&lt;code&gt;片段&lt;/code&gt;特性。Vue 文件的&lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;标签内，不再需要强制声明一个的&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;标签，节省额外的节点开销&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt; 总结&lt;/h2&gt;
&lt;p&gt;Vue3 的优点和新特性还有很多很多，本篇文章主要从&lt;code&gt;Vue2/3版本的优缺点&lt;/code&gt;和&lt;code&gt;SFC写法变化&lt;/code&gt;这两个角度介入，给大家介绍我们为什么要上 Vue3。&lt;/p&gt;
&lt;p&gt;Vue3 很香，更好地支持 TypeScript，让 Vue3 成为了企业级 Vue 项目的首选，各种新特性也迎合前端技术的发展趋势。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ie 浏览器：那我走？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt; 最后&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;翻译翻译什么是 Vue3 ？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果这篇文章对你有帮助，那麻烦你动动小手点个赞 ，你的每一个点赞都是我继续创作的莫大鼓励！&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://vue3js.cn/interview/vue3/performance.html#%E4%B8%80%E3%80%81%E7%BC%96%E8%AF%91%E9%98%B6%E6%AE%B5&quot; title=&quot;面试官：Vue3.0性能提升主要是通过哪几方面体现的？&quot;&gt;面试官：Vue3.0 性能提升主要是通过哪几方面体现的？&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6903171037211557895&quot; title=&quot;Vue3教程：Vue 3.x快在哪里？&quot;&gt;Vue3 教程：Vue 3.x 快在哪里？&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;其他&lt;/h2&gt;
&lt;p&gt;掘金年度人气创作者打榜最后一天啦，欢迎大家为我们团队投票~ &lt;a href=&quot;https://rank.juejin.cn/rank/2021/3104676565234429?t=team&amp;amp;utm_campaign=annual_2021&amp;amp;utm_medium=self_web_share&amp;amp;utm_source=IDuxFE&quot;&gt;IDuxFE@深信服前端开发团队&lt;/a&gt;
感谢大家的支持~&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: IDuxFE
        原文链接：&lt;a href=&quot;https://juejin.im/post/7046922345864232974&quot;&gt;https://juejin.im/post/7046922345864232974&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Wed, 12 Jan 2022 10:38:54 GMT</pubDate></item><item><title>Vue3.2发布了，我激动了，实在学不动了！__Vue.js__Vuex</title><link>http://vue-js.com/topic/61deafbead745b003c9df6d6</link><guid>http://vue-js.com/topic/61deafbead745b003c9df6d6</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h1&gt;什么？VUE3.2 发布了 ？&lt;/h1&gt;
&lt;p&gt;前两天一早收到尤大微博推送，Vue 3.2 发布了！好家伙，上个版本还没玩儿透，这新版本就来了。评论区也是一片哀嚎&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0e9c8c9f547d4fb1b97e2bf8f8877104~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;\&lt;/p&gt;
&lt;p&gt;但也正如评论区所言，很难不支持。让我们看看这次更新了点儿什么。&lt;/p&gt;
&lt;p&gt;两个新功能已经从实验状态毕业了&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; 与 &lt;code&gt;&amp;lt;style&amp;gt; v-bind&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;-----------------------------华尔兹分割----------------------------&lt;/p&gt;
&lt;h1&gt;第一个：&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; 正式毕业&lt;/h1&gt;
&lt;p&gt;从一开始学习vue3就注意到了实验性的&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;。因为简洁的写法受到许多人追捧（写过&lt;em&gt;setup(){return{}}&lt;/em&gt; 的人都知道），甚至有人说这才是vue3的完全形态。看了更新描述，emmm…官方吐槽最为致命。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bafce637dbc34a0294c99824897c45cf~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; 是一种编译时语法糖，可在 SFC 内使用 Composition API 时**极大地改善工作效率。&lt;/p&gt;
&lt;h1&gt;第二个：新增&lt;code&gt;&amp;lt;style&amp;gt; v-bind&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;而&lt;code&gt;&amp;lt;style&amp;gt; v-bind&lt;/code&gt;呢，简单地来说就是可以在&amp;lt;style&amp;gt;内使用组件定义的动态值。官方给出了例子：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;import { ref } from &amp;#x27;vue&amp;#x27;
​
const color = ref(&amp;#x27;red&amp;#x27;)
&amp;lt;&amp;#x2F;script&amp;gt;
​
&amp;lt;template&amp;gt;
  &amp;lt;button @click=&amp;quot;color = color === &amp;#x27;red&amp;#x27; ? &amp;#x27;green&amp;#x27; : &amp;#x27;red&amp;#x27;&amp;quot;&amp;gt;
    Color is: {{ color }}
  &amp;lt;&amp;#x2F;button&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
​
&amp;lt;style scoped&amp;gt;
button {
  color: v-bind(color);
}
&amp;lt;&amp;#x2F;style&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;因为vue中文官网暂时没有此次的更新内容，需要的同学可能要到外网啃啃英文文档了。&lt;/p&gt;
&lt;p&gt;文档地址&lt;a href=&quot;https://v3.vuejs.org/api/sfc-script-setup.html&quot;&gt;：https://v3.vuejs.org/api/sfc-script-setup.html&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;第三个：新增 defineCustomElement方法&lt;/h1&gt;
&lt;p&gt;Vue 3.2 引入了一个新的 defineCustomElement 方法，可以使用 Vue 组件 API 轻松创建原生自定义元素：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;import { defineCustomElement } from &amp;#x27;vue&amp;#x27;
​
const MyVueElement = defineCustomElement({
  &amp;#x2F;&amp;#x2F; normal Vue component options here
})
​
&amp;#x2F;&amp;#x2F; Register the custom element.
&amp;#x2F;&amp;#x2F; After registration, all &amp;#96;&amp;lt;my-vue-element&amp;gt;&amp;#96; tags
&amp;#x2F;&amp;#x2F; on the page will be upgraded.
customElements.define(&amp;#x27;my-vue-element&amp;#x27;, MyVueElement)
&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;第四个：性能改进&lt;/h1&gt;
&lt;p&gt;此处有很大篇幅讲述3.2版本的性能升级，其中提到了新的指令&lt;code&gt;v-memo&lt;/code&gt;，简单来说这个指令会记住模板树的一部分，不仅跳过虚拟 DOM 差异，而且完全跳过新 VNode 的创建。可用于复杂的大型页面。&lt;/p&gt;
&lt;h1&gt;第五个：服务器渲染&lt;/h1&gt;
&lt;p&gt;最后提到了服务端渲染与新的Effect Scope API。有兴趣的同学可以仔细看一看更新文档。&lt;a href=&quot;https://blog.vuejs.org/posts/vue-3.2.html%E3%80%82&quot;&gt;https://blog.vuejs.org/posts/vue-3.2.html。&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;第6个：1024Lab 再说点儿&lt;/h1&gt;
&lt;p&gt;相信很多同学已经上手开始使用&amp;lt;scripte setup&amp;gt;了。在文档中可以看到，&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;defineProps、defineEmits、defineExpose、withDefaults***属于***compiler macro，编译器宏&lt;/strong&gt;&lt;/em&gt;。文档中也说到：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;They do not need to be imported, and are compiled away when is processed&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;em&gt;他们不需要引入，会在编译的时候处理掉。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;然而不引入你用的时候就会报错。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;script setup lang=&amp;quot;ts&amp;quot;&amp;gt;
const props = defineProps&amp;lt;{
  value?: number;
}&amp;gt;();
​
const emit = defineEmits&amp;lt;{
  (e: &amp;#x27;update:value&amp;#x27;, value: number): void;
}&amp;gt;();
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;首先eslint会报错：&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;ESLint: ‘defineEmits’ is not defined.(no-undef)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;此时你需要更改eslint-plugin-vue的配置&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;https:&amp;#x2F;&amp;#x2F;eslint.vuejs.org&amp;#x2F;user-guide&amp;#x2F;#compiler-macros-such-as-defineprops-and-defineemits-are-warned-by-no-undef-rule
module.exports = {
  globals: {
    defineProps: &amp;quot;readonly&amp;quot;,
    defineEmits: &amp;quot;readonly&amp;quot;,
    defineExpose: &amp;quot;readonly&amp;quot;,
    withDefaults: &amp;quot;readonly&amp;quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;然后可能编译后浏览器控制台会报错&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;defineEmits is not defined&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;你可能会发现defineEmits等并没有在编译的时候处理掉，通过浏览器看源代码defineEmits还在，且画着红色波浪线。此时你可能需要查看package.json中各个包的版本以及vite的版本2.4.x，更新后重试，此时编译出来的代码应该是这样：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const _sfc_main = _defineComponent({
  props: {
    value: { type: Number, required: false }
  },
  emits: [&amp;quot;update:value&amp;quot;],
  setup(__props, { expose, emit }) {}
 })
&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;第7点：最后让我吐槽一下下&lt;/h1&gt;
&lt;p&gt;尤大在微博上提到的Volar是一款vscode的插件，这让webstorm党…哭惨了…&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但是Vue 3.2 + script setup + typescript + volar 真香！真的香！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SmartAdmin2.0 也在用最新的 &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt;重构，真香~&lt;/p&gt;
&lt;p&gt;SmartAdmin: &lt;a href=&quot;https://github.com/1024-lab/smart-admin&quot;&gt;https://github.com/1024-lab/smart-admin&lt;/a&gt;&lt;p&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
作者: 1024创新实验室
原文链接：&lt;a href=&quot;https://juejin.im/post/6995777059725246472&quot;&gt;https://juejin.im/post/6995777059725246472&lt;/a&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>banyungong</author><pubDate>Wed, 12 Jan 2022 10:38:54 GMT</pubDate></item><item><title>Vue3 Ref 语法糖，告别 .value 的写法__Vue.js__源码</title><link>http://vue-js.com/topic/61dd831e03eaf00040d48069</link><guid>http://vue-js.com/topic/61dd831e03eaf00040d48069</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;近期，Vue3 提了一个 &lt;a href=&quot;https://github.com/vuejs/rfcs/discussions/369&quot;&gt;Ref Sugar 的 RFC&lt;/a&gt;，即 &lt;code&gt;ref&lt;/code&gt; 语法糖，目前还处理实验性的（Experimental）阶段。在 RFC 的动机（Motivation）中，Evan You 介绍到在 Composition API 引入后，一个主要未解决的问题是 &lt;code&gt;refs&lt;/code&gt; 和 &lt;code&gt;reactive&lt;/code&gt; 对象的使用。而到处使用 &lt;code&gt;.value&lt;/code&gt; 可能会很麻烦，如果在没使用类型系统的情况下，也会很容易错过：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;let count = ref(1)

function add() {
	count.value++
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;所以，一些用户会更倾向于只使用 &lt;code&gt;reactive&lt;/code&gt;，这样就不用处理使用 &lt;code&gt;refs&lt;/code&gt; 的 &lt;code&gt;.value&lt;/code&gt; 问题。而 &lt;code&gt;ref&lt;/code&gt; 语法糖的作用是让我们在使用 &lt;code&gt;ref&lt;/code&gt; 创建响应式的变量时，可以直接获取和更改变量本身，而不是使用 &lt;code&gt;.value&lt;/code&gt; 来获取和更改对应的值。简单的说，&lt;strong&gt;站在使用层面&lt;/strong&gt;，我们可以告别使用 &lt;code&gt;refs&lt;/code&gt; 时的 &lt;code&gt;.value&lt;/code&gt; 问题：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;let count = $ref(1)

function add() {
	count++
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那么，&lt;code&gt;ref&lt;/code&gt; 语法糖目前要怎么在项目中使用？它又是怎么实现的？这是我第一眼看到这个 RFC 建立的疑问，相信这也是很多同学持有的疑问。所以，下面让我们来一一揭晓。&lt;/p&gt;
&lt;h2&gt;1 Ref 语法糖在项目中的使用&lt;/h2&gt;
&lt;p&gt;由于 &lt;code&gt;ref&lt;/code&gt; 语法糖目前还处于实验性的（Experimental）阶段，所以在 Vue3 中不会默认支持 &lt;code&gt;ref&lt;/code&gt; 语法糖。那么，这里我们以使用 Vite + Vue3 项目开发为例，看一下如何开启对 &lt;code&gt;ref&lt;/code&gt; 语法糖的支持。&lt;/p&gt;
&lt;p&gt;在使用 Vite + Vue3 项目开发时，是由 &lt;code&gt;@vitejs/plugin-vue&lt;/code&gt; 插件来实现对 &lt;code&gt;.vue&lt;/code&gt; 文件的代码转换（Transform）、热更新（HMR）等。所以，我们需要在 &lt;code&gt;vite.config.js&lt;/code&gt; 中给 &lt;code&gt;@vitejs/plugin-vue&lt;/code&gt; 插件的选项（Options）传入 &lt;code&gt;refTransform: true&lt;/code&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; vite.config.js
import { defineConfig } from &amp;#x27;vite&amp;#x27;
import vue from &amp;#x27;@vitejs&amp;#x2F;plugin-vue&amp;#x27;

export default defineConfig({
  plugins: [vue({
    refTransform: true
  })]
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那么，这样一来 &lt;code&gt;@vitejs/plugin-vue&lt;/code&gt; 插件内部会根据传入的选项中 &lt;code&gt;refTransform&lt;/code&gt; 的值判断是否需要对 &lt;code&gt;ref&lt;/code&gt; 语法糖进行特定的代码转换。由于，这里我们设置的是 &lt;code&gt;true&lt;/code&gt;，显然它是会对 &lt;code&gt;ref&lt;/code&gt; 语法糖执行特定的代码转换。&lt;/p&gt;
&lt;p&gt;接着，我们就可以在 &lt;code&gt;.vue&lt;/code&gt; 文件中使用 &lt;code&gt;ref&lt;/code&gt; 语法糖，这里我们看一个简单的例子：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-html&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
	&amp;lt;div&amp;gt;{{count}}&amp;lt;&amp;#x2F;div&amp;gt;
	&amp;lt;button @click=&amp;quot;add&amp;quot;&amp;gt;click me&amp;lt;&amp;#x2F;button&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script setup&amp;gt;
let count = $ref(1)

function add() {
	count++
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对应渲染到页面上：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/719d34930fe94926a14acf1aae7515e8~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;可以看到，我们可以使用 &lt;code&gt;ref&lt;/code&gt; 语法糖的方式创建响应式的变量，而不用思考使用的时候要加 &lt;code&gt;.value&lt;/code&gt; 的问题。此外，&lt;code&gt;ref&lt;/code&gt; 语法糖还支持其他的写法，个人比较推荐的是这里介绍的 &lt;code&gt;$ref&lt;/code&gt; 的方式，有兴趣的同学可以去 RFC 上了解其他的写法。&lt;/p&gt;
&lt;p&gt;那么，在了解完 &lt;code&gt;ref&lt;/code&gt; 语法糖在项目中的使用后，我们算是解答了第一个疑问（怎么在项目中使用）。下面，我们来解答第二个疑问，它又是怎么实现的，也就是在源码中做了哪些处理？&lt;/p&gt;
&lt;h2&gt;2 Ref 语法糖的实现&lt;/h2&gt;
&lt;p&gt;首先，我们通过 &lt;a href=&quot;https://sfc.vuejs.org/#eyJBcHAudnVlIjoiXG48dGVtcGxhdGU+XG5cdDxkaXY+e3tjb3VudH19PC9kaXY+XG5cdDxidXR0b24gQGNsaWNrPVwiYWRkXCI+Y2xpY2sgbWU8L2J1dHRvbj5cbjwvdGVtcGxhdGU+XG5cbjxzY3JpcHQgc2V0dXA+XG5sZXQgY291bnQgPSAkcmVmKDEpXG5cbmZ1bmN0aW9uIGFkZCgpIHtcblx0Y291bnQrK1xufVxuPC9zY3JpcHQ+In0=&quot;&gt;Vue Playground&lt;/a&gt; 来直观地感受一下，前面使用 &lt;code&gt;ref&lt;/code&gt; 语法糖的例子中的 &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; 块（Block）在编译后的结果：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;import { ref as _ref } from &amp;#x27;vue&amp;#x27;

const __sfc__ = {
  setup(__props) {
  let count = _ref(1)

  function add() {
    count.value++
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到，虽然我们在使用 &lt;code&gt;ref&lt;/code&gt; 语法糖的时候不需要处理 &lt;code&gt;.value&lt;/code&gt;，但是它经过编译后&lt;strong&gt;仍然是使用的 &lt;code&gt;.value&lt;/code&gt;&lt;/strong&gt;。那么，这个过程肯定不难免要做很多&lt;strong&gt;编译相关&lt;/strong&gt;的代码转换处理。因为，我们需要找到使用 &lt;code&gt;$ref&lt;/code&gt; 的声明语句和变量，给前者重写为 &lt;code&gt;_ref&lt;/code&gt;，给后者添加 &lt;code&gt;.value&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;而在前面，我们也提及 &lt;code&gt;@vitejs/plugin-vue&lt;/code&gt; 插件会对 &lt;code&gt;.vue&lt;/code&gt; 文件进行代码的转换，这个过程则是使用的 Vue3 提供的 &lt;code&gt;@vue/compiler-sfc&lt;/code&gt; 包（Package），它分别提供了对 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;、&lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt;、&lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; 等块的编译相关的函数。&lt;/p&gt;
&lt;p&gt;那么，显然这里我们需要关注的是 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 块编译相关的函数，这对应的是 &lt;code&gt;@vue/compiler-sfc&lt;/code&gt; 中的 &lt;code&gt;compileScript()&lt;/code&gt; 函数。&lt;/p&gt;
&lt;h3&gt;2.1 compileScript() 函数&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;compileScript()&lt;/code&gt; 函数定义在 &lt;code&gt;vue-next&lt;/code&gt; 的 &lt;code&gt;packages/compiler-sfc/src/compileScript.ts&lt;/code&gt; 文件中，它主要负责对 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 或 &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; 块内容的编译处理，它会接收 2 个参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sfc&lt;/code&gt; 包含 &lt;code&gt;.vue&lt;/code&gt; 文件的代码被解析后的内容，包含 &lt;code&gt;script&lt;/code&gt;、&lt;code&gt;scriptSetup&lt;/code&gt;、&lt;code&gt;source&lt;/code&gt; 等属性&lt;/li&gt;
&lt;li&gt;&lt;code&gt;options&lt;/code&gt; 包含一些可选和必须的属性，例如组件对应的 &lt;code&gt;scopeId&lt;/code&gt; 会作为 &lt;code&gt;options.id&lt;/code&gt;、前面提及的 &lt;code&gt;refTransform&lt;/code&gt; 等&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;compileScript()&lt;/code&gt; 函数的定义（伪代码）：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; packages&amp;#x2F;compiler-sfc&amp;#x2F;src&amp;#x2F;compileScript.ts
export function compileScript(
  sfc: SFCDescriptor,
  options: SFCScriptCompileOptions
): SFCScriptBlock {
  &amp;#x2F;&amp;#x2F; ...
  return {
    ...script,
    content,
    map,
    bindings,
    scriptAst: scriptAst.body
  }
} 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对于 &lt;code&gt;ref&lt;/code&gt; 语法糖而言，&lt;code&gt;compileScript()&lt;/code&gt; 函数首先会获取选项（Option）中 &lt;code&gt;refTransform&lt;/code&gt; 的值，并赋值给 &lt;code&gt;enableRefTransform&lt;/code&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;const enableRefTransform = !!options.refTransform
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;enableRefTransform&lt;/code&gt; 则会用于之后判断是否要调用 &lt;code&gt;ref&lt;/code&gt; 语法糖相关的转换函数。那么，前面我们也提及要使用 &lt;code&gt;ref&lt;/code&gt; 语法糖，需要先给 &lt;code&gt;@vite/plugin-vue&lt;/code&gt; 插件选项的 &lt;code&gt;refTransform&lt;/code&gt; 属性设置为 &lt;code&gt;true&lt;/code&gt;，它会被传入 &lt;code&gt;compileScript()&lt;/code&gt; 函数的 &lt;code&gt;options&lt;/code&gt;，也就是这里的 &lt;code&gt;options.refTransform&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;接着，会从 &lt;code&gt;sfc&lt;/code&gt; 中解构出 &lt;code&gt;scriptSetup&lt;/code&gt;、&lt;code&gt;source&lt;/code&gt;、&lt;code&gt;filename&lt;/code&gt; 等属性。其中，会先用源文件的代码字符串 &lt;code&gt;source&lt;/code&gt; 创建一个 &lt;code&gt;MagicString&lt;/code&gt; 实例 &lt;code&gt;s&lt;/code&gt;，它主要会用于后续代码转换时&lt;strong&gt;对源代码字符串进行替换、添加等操作&lt;/strong&gt;，然后会调用 &lt;code&gt;parse()&lt;/code&gt; 函数来解析 &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; 的内容，即 &lt;code&gt;scriptSetup.content&lt;/code&gt;，从而生成对应的抽象语法树 &lt;code&gt;scriptSetupAst&lt;/code&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;let { script, scriptSetup, source, filename } = sfc
const s = new MagicString(source)
const startOffset = scriptSetup.loc.start.offset
const scriptSetupAst = parse(
  scriptSetup.content,
  {
    plugins: [
      ...plugins,
      &amp;#x27;topLevelAwait&amp;#x27;
    ],
    sourceType: &amp;#x27;module&amp;#x27;
  },
  startOffset
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而 &lt;code&gt;parse()&lt;/code&gt; 函数内部则是使用的 &lt;code&gt;@babel/parser&lt;/code&gt; 提供的 &lt;code&gt;parser&lt;/code&gt; 方法进行代码的解析并生成对应的 AST。对于上面我们这个例子，生成的 AST 会是这样：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;{
  body: [ {...}, {...} ],
  directives: [],
  end: 50,
  interpreter: null,
  loc: {
    start: {...}, 
    end: {...}, 
    filename: undefined, 
    identifierName: undefined
  },
  sourceType: &amp;#x27;module&amp;#x27;,
  start: 0,
  type: &amp;#x27;Program&amp;#x27;
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;注意，这里省略了 &lt;code&gt;body&lt;/code&gt;、&lt;code&gt;start&lt;/code&gt;、&lt;code&gt;end&lt;/code&gt; 中的内容&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;然后，会根据前面定义的 &lt;code&gt;enableRefTransform&lt;/code&gt; 和调用 &lt;code&gt;shouldTransformRef()&lt;/code&gt; 函数的返回值（&lt;code&gt;true&lt;/code&gt; 或 &lt;code&gt;false&lt;/code&gt;）来判断是否进行 &lt;code&gt;ref&lt;/code&gt; 语法糖的代码转换。如果，需要进行相应的转换，则会调用 &lt;code&gt;transformRefAST()&lt;/code&gt; 函数来根据 AST 来进行相应的代码转换操作：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;if (enableRefTransform &amp;amp;&amp;amp; shouldTransformRef(scriptSetup.content)) {
  const { rootVars, importedHelpers } = transformRefAST(
    scriptSetupAst,
    s,
    startOffset,
    refBindings
  )
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在前面，我们已经介绍过了 &lt;code&gt;enableRefTransform&lt;/code&gt;。这里我们来看一下 &lt;code&gt;shouldTransformRef()&lt;/code&gt; 函数，它主要是通过正则匹配代码内容 &lt;code&gt;scriptSetup.content&lt;/code&gt; 来判断是否使用了 &lt;code&gt;ref&lt;/code&gt; 语法糖：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; packages&amp;#x2F;ref-transform&amp;#x2F;src&amp;#x2F;refTransform.ts
const transformCheckRE = &amp;#x2F;[^&amp;#x5C;w]&amp;#x5C;$(?:&amp;#x5C;$|ref|computed|shallowRef)?&amp;#x5C;(&amp;#x2F;

export function shouldTransform(src: string): boolean {
  return transformCheckRE.test(src)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;所以，当你指定了 &lt;code&gt;refTransform&lt;/code&gt; 为 &lt;code&gt;true&lt;/code&gt;，但是你代码中实际并没有使用到 &lt;code&gt;ref&lt;/code&gt; 语法糖，则在编译 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 或 &lt;code&gt;&amp;lt;script setup&amp;gt;&lt;/code&gt; 的过程中也&lt;strong&gt;不会执行&lt;/strong&gt;和 &lt;code&gt;ref&lt;/code&gt; 语法糖相关的代码转换操作，这也是 Vue3 考虑比较细致的地方，避免了不必要的代码转换操作带来性能上的开销。&lt;/p&gt;
&lt;p&gt;那么，对于我们这个例子而言（使用了 &lt;code&gt;ref&lt;/code&gt; 语法糖），则会命中上面的 &lt;code&gt;transformRefAST()&lt;/code&gt; 函数。而 &lt;code&gt;transformRefAST()&lt;/code&gt; 函数则对应的是 &lt;code&gt;packages/ref-transform/src/refTransform.ts&lt;/code&gt; 中的 &lt;code&gt;transformAST()&lt;/code&gt; 函数。&lt;/p&gt;
&lt;p&gt;所以，下面我们来看一下 &lt;code&gt;transformAST()&lt;/code&gt; 函数是如何根据 AST 来对 &lt;code&gt;ref&lt;/code&gt; 语法糖相关代码进行转换操作的。&lt;/p&gt;
&lt;h3&gt;2.2 transformAST() 函数&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;transformAST()&lt;/code&gt; 函数中主要是会遍历传入的原代码对应的 AST，然后通过操作源代码字符串生成的 &lt;code&gt;MagicString&lt;/code&gt; 实例 &lt;code&gt;s&lt;/code&gt; 来对源代码进行特定的转换，例如重写 &lt;code&gt;$ref&lt;/code&gt; 为 &lt;code&gt;_ref&lt;/code&gt;、添加 &lt;code&gt;.value&lt;/code&gt; 等。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;transformAST()&lt;/code&gt; 函数的定义（伪代码）：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; packages&amp;#x2F;ref-transform&amp;#x2F;src&amp;#x2F;refTransform.ts
export function transformAST(
  ast: Program,
  s: MagicString,
  offset: number = 0,
  knownRootVars?: string[]
): {
  &amp;#x2F;&amp;#x2F; ...
  walkScope(ast)
  (walk as any)(ast, {
    enter(node: Node, parent?: Node) {
      if (
        node.type === &amp;#x27;Identifier&amp;#x27; &amp;amp;&amp;amp;
        isReferencedIdentifier(node, parent!, parentStack) &amp;amp;&amp;amp;
        !excludedIds.has(node)
      ) {
        let i = scopeStack.length
        while (i--) {
          if (checkRefId(scopeStack[i], node, parent!, parentStack)) {
            return
          }
        }
      }
    }
  })
  
  return {
    rootVars: Object.keys(rootScope).filter(key =&amp;gt; rootScope[key]),
    importedHelpers: [...importedHelpers]
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到 &lt;code&gt;transformAST()&lt;/code&gt; 会先调用 &lt;code&gt;walkScope()&lt;/code&gt; 来处理根作用域（&lt;code&gt;root scope&lt;/code&gt;），然后调用 &lt;code&gt;walk()&lt;/code&gt; 函数逐层地处理 AST 节点，而这里的 &lt;code&gt;walk()&lt;/code&gt; 函数则是使用的 Rich Haris 写的 &lt;code&gt;estree-walker&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;下面，我们来分别看一下 &lt;code&gt;walkScope()&lt;/code&gt; 和 &lt;code&gt;walk()&lt;/code&gt; 函数做了什么。&lt;/p&gt;
&lt;h4&gt;walkScope() 函数&lt;/h4&gt;
&lt;p&gt;首先，这里我们先来看一下前面使用 &lt;code&gt;ref&lt;/code&gt; 语法糖的声明语句 &lt;code&gt;let count = $ref(1)&lt;/code&gt; 对应的 AST 结构：&lt;/p&gt;
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b7fa3175edc54d79ac9a11b1072a7973~tplv-k3u1fbpfcp-zoom-1.image&quot; height=&quot;500&quot; /&gt;
&lt;p&gt;可以看到 &lt;code&gt;let&lt;/code&gt; 的 AST 节点类型 &lt;code&gt;type&lt;/code&gt; 会是 &lt;code&gt;VariableDeclaration&lt;/code&gt;，其余的代码部分对应的 AST 节点则会被放在 &lt;code&gt;declarations&lt;/code&gt; 中。其中，变量 &lt;code&gt;count&lt;/code&gt; 的 AST 节点会被作为 &lt;code&gt;declarations.id&lt;/code&gt; ，而 &lt;code&gt;$ref(1)&lt;/code&gt; 的 AST 节点会被作为 &lt;code&gt;declarations.init&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;那么，回到 &lt;code&gt;walkScope()&lt;/code&gt; 函数，它会根据 AST 节点的类型 &lt;code&gt;type&lt;/code&gt; 进行特定的处理，对于我们这个例子 &lt;code&gt;let&lt;/code&gt; 对应的 AST 节点 &lt;code&gt;type&lt;/code&gt; 为 &lt;code&gt;VariableDeclaration&lt;/code&gt; 会命中这样的逻辑：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;function walkScope(node: Program | BlockStatement) {
  for (const stmt of node.body) {
    if (stmt.type === &amp;#x27;VariableDeclaration&amp;#x27;) {
      for (const decl of stmt.declarations) {
        let toVarCall
        if (
          decl.init &amp;amp;&amp;amp;
          decl.init.type === &amp;#x27;CallExpression&amp;#x27; &amp;amp;&amp;amp;
          decl.init.callee.type === &amp;#x27;Identifier&amp;#x27; &amp;amp;&amp;amp;
          (toVarCall = isToVarCall(decl.init.callee.name))
        ) {
          processRefDeclaration(
            toVarCall,
            decl.init as CallExpression,
            decl.id,
            stmt
          )
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这里的 &lt;code&gt;stmt&lt;/code&gt; 则是 &lt;code&gt;let&lt;/code&gt; 对应的 AST 节点，然后会遍历 &lt;code&gt;stmt.declarations&lt;/code&gt;，其中 &lt;code&gt;decl.init.callee.name&lt;/code&gt; 指的是 &lt;code&gt;$ref&lt;/code&gt;，接着是调用 &lt;code&gt;isToVarCall()&lt;/code&gt; 函数并赋值给 &lt;code&gt;toVarCall&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;isToVarCall()&lt;/code&gt; 函数的定义：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; packages&amp;#x2F;ref-transform&amp;#x2F;src&amp;#x2F;refTransform.ts
const TO_VAR_SYMBOL = &amp;#x27;$&amp;#x27;
const shorthands = [&amp;#x27;ref&amp;#x27;, &amp;#x27;computed&amp;#x27;, &amp;#x27;shallowRef&amp;#x27;]
function isToVarCall(callee: string): string | false {
  if (callee === TO_VAR_SYMBOL) {
    return TO_VAR_SYMBOL
  }
  if (callee[0] === TO_VAR_SYMBOL &amp;amp;&amp;amp; shorthands.includes(callee.slice(1))) {
    return callee
  }
  return false
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在前面我们也提及 &lt;code&gt;ref&lt;/code&gt; 语法糖可以支持其他写法，由于我们使用的是 &lt;code&gt;$ref&lt;/code&gt; 的方式，所以这里会命中 &lt;code&gt;callee[0] === TO_VAR_SYMBOL &amp;amp;&amp;amp; shorthands.includes(callee.slice(1))&lt;/code&gt; 的逻辑，即 &lt;code&gt;toVarCall&lt;/code&gt; 会被赋值为 &lt;code&gt;$ref&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;然后，会调用 &lt;code&gt;processRefDeclaration()&lt;/code&gt; 函数，它会根据传入的 &lt;code&gt;decl.init&lt;/code&gt; &lt;strong&gt;提供的位置信息&lt;/strong&gt;来对源代码对应的 &lt;code&gt;MagicString&lt;/code&gt; 实例 &lt;code&gt;s&lt;/code&gt; 进行操作，即将 &lt;code&gt;$ref&lt;/code&gt; 重写为 &lt;code&gt;ref&lt;/code&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F; packages&amp;#x2F;ref-transform&amp;#x2F;src&amp;#x2F;refTransform.ts
function processRefDeclaration(
    method: string,
    call: CallExpression,
    id: VariableDeclarator[&amp;#x27;id&amp;#x27;],
    statement: VariableDeclaration
) {
  &amp;#x2F;&amp;#x2F; ...
  if (id.type === &amp;#x27;Identifier&amp;#x27;) {
    registerRefBinding(id)
    s.overwrite(
      call.start! + offset,
      call.start! + method.length + offset,
      helper(method.slice(1))
    )
  } 
  &amp;#x2F;&amp;#x2F; ...
}
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;位置信息指的是该 AST 节点在源代码中的位置，通常会用 &lt;code&gt;start&lt;/code&gt;、&lt;code&gt;end&lt;/code&gt; 表示，例如这里的 &lt;code&gt;let count = $ref(1)&lt;/code&gt;，那么 &lt;code&gt;count&lt;/code&gt; 对应的 AST 节点的 &lt;code&gt;start&lt;/code&gt; 会是 4、&lt;code&gt;end&lt;/code&gt; 会是 9。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;因为，此时传入的 &lt;code&gt;id&lt;/code&gt; 对应的是 &lt;code&gt;count&lt;/code&gt; 的 AST 节点，它会是这样：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;{
  type: &amp;quot;Identifier&amp;quot;,
  start: 4,
  end: 9,
  name: &amp;quot;count&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;所以，这会命中上面的 &lt;code&gt;id.type === &apos;Identifier&apos;&lt;/code&gt; 的逻辑。首先，会调用 &lt;code&gt;registerRefBinding()&lt;/code&gt; 函数，它实际上是调用的是 &lt;code&gt;registerBinding()&lt;/code&gt;，而 &lt;code&gt;registerBinding&lt;/code&gt; 会在&lt;strong&gt;当前作用域&lt;/strong&gt; &lt;code&gt;currentScope&lt;/code&gt; 上绑定该变量 &lt;code&gt;id.name&lt;/code&gt; 并设置为 &lt;code&gt;true&lt;/code&gt; ，它表示这是一个用 &lt;code&gt;ref&lt;/code&gt; 语法糖创建的变量，这会用于后续判断是否给某个变量添加 &lt;code&gt;.value&lt;/code&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;const registerRefBinding = (id: Identifier) =&amp;gt; registerBinding(id, true)
function registerBinding(id: Identifier, isRef = false) {
  excludedIds.add(id)
  if (currentScope) {
    currentScope[id.name] = isRef
  } else {
    error(
      &amp;#x27;registerBinding called without active scope, something is wrong.&amp;#x27;,
      id
    )
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到，在 &lt;code&gt;registerBinding()&lt;/code&gt; 中还会给 &lt;code&gt;excludedIds&lt;/code&gt; 中添加该 AST 节点，而 &lt;code&gt;excludeIds&lt;/code&gt; 它是一个 &lt;code&gt;WeekMap&lt;/code&gt;，它会用于后续跳过不需要进行 &lt;code&gt;ref&lt;/code&gt; 语法糖处理的类型为 &lt;code&gt;Identifier&lt;/code&gt; 的 AST 节点。&lt;/p&gt;
&lt;p&gt;然后，会调用 &lt;code&gt;s.overwrite()&lt;/code&gt; 函数来将 &lt;code&gt;$ref&lt;/code&gt; 重写为 &lt;code&gt;_ref&lt;/code&gt;，它会接收 3 个参数，分别是重写的起始位置、结束位置以及要重写为的字符串。而 &lt;code&gt;call&lt;/code&gt; 则对应着 &lt;code&gt;$ref(1)&lt;/code&gt; 的 AST 节点，它会是这样：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;{
  type: &amp;quot;Identifier&amp;quot;,
  start: 12,
  end: 19,
  callee: {...}
  arguments: {...},
  optional: false
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;并且，我想大家应该注意到了在计算重写的起始位置的时候用到了 &lt;code&gt;offset&lt;/code&gt;，它代表着此时操作的字符串在源字符串中的&lt;strong&gt;偏移位置&lt;/strong&gt;，例如该字符串在源字符串中的开始，那么偏移量则会是 &lt;code&gt;0&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;而 &lt;code&gt;helper()&lt;/code&gt; 函数则会返回字符串 &lt;code&gt;_ref&lt;/code&gt;，并且在这个过程会将 &lt;code&gt;ref&lt;/code&gt; 添加到 &lt;code&gt;importedHelpers&lt;/code&gt; 中，这会在 &lt;code&gt;compileScript()&lt;/code&gt; 时用于生成对应的 &lt;code&gt;import&lt;/code&gt; 语句：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;function helper(msg: string) {
  importedHelpers.add(msg)
  return &amp;#96;_${msg}&amp;#96;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那么，到这里就完成了对 &lt;code&gt;$ref&lt;/code&gt; 到 &lt;code&gt;_ref&lt;/code&gt; 的重写，也就是此时我们代码的会是这样：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;let count = _ref(1)

function add() {
	count++
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;接着，则是通过 &lt;code&gt;walk()&lt;/code&gt; 函数来将 &lt;code&gt;count++&lt;/code&gt; 转换成 &lt;code&gt;count.value++&lt;/code&gt;。下面，我们来看一下 &lt;code&gt;walk()&lt;/code&gt; 函数。&lt;/p&gt;
&lt;h4&gt;walk() 函数&lt;/h4&gt;
&lt;p&gt;前面，我们提及 &lt;code&gt;walk()&lt;/code&gt; 函数使用的是 Rich Haris 写的 &lt;a href=&quot;https://github.com/Rich-Harris/estree-walker&quot;&gt;estree-walker&lt;/a&gt;，它是一个用于遍历符合 &lt;a href=&quot;https://hexdocs.pm/estree/ESTree.html&quot;&gt;ESTree&lt;/a&gt; 规范的 AST 包（Package）。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;walk()&lt;/code&gt; 函数使用起来会是这样：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;import { walk } from &amp;#x27;estree-walker&amp;#x27;

walk(ast, {
  enter(node, parent, prop, index) {
    &amp;#x2F;&amp;#x2F; ...
  },
  leave(node, parent, prop, index) {
    &amp;#x2F;&amp;#x2F; ...
  }
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到，&lt;code&gt;walk()&lt;/code&gt; 函数中可以传入 &lt;code&gt;options&lt;/code&gt;，其中 &lt;code&gt;enter()&lt;/code&gt; 在每次访问 AST 节点的时候会被调用，&lt;code&gt;leave()&lt;/code&gt; 则是在离开 AST 节点的时候被调用。&lt;/p&gt;
&lt;p&gt;那么，回到前面提到的这个例子，&lt;code&gt;walk()&lt;/code&gt; 函数主要做了这 2 件事：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.维护 scopeStack、parentStack 和 currentScope&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;scopeStack&lt;/code&gt; 用于存放此时 AST 节点所处的作用域链，初始情况下栈顶为根作用域 &lt;code&gt;rootScope&lt;/code&gt;；&lt;code&gt;parentStack&lt;/code&gt; 用于存放遍历 AST 节点过程中的祖先 AST 节点（栈顶的 AST 节点是当前 AST 节点的父亲 AST 节点）；&lt;code&gt;currentScope&lt;/code&gt; 指向当前的作用域，初始情况下等于根作用域 &lt;code&gt;rootScope&lt;/code&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;const scopeStack: Scope[] = [rootScope]
const parentStack: Node[] = []
let currentScope: Scope = rootScope
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;所以，在 &lt;code&gt;enter()&lt;/code&gt; 的阶段会判断此时 AST 节点类型是否为函数、块，是则&lt;strong&gt;入栈&lt;/strong&gt; &lt;code&gt;scopeStack&lt;/code&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;parent &amp;amp;&amp;amp; parentStack.push(parent)
if (isFunctionType(node)) {
  scopeStack.push((currentScope = {}))
  &amp;#x2F;&amp;#x2F; ...
  return
}
if (node.type === &amp;#x27;BlockStatement&amp;#x27; &amp;amp;&amp;amp; !isFunctionType(parent!)) {
  scopeStack.push((currentScope = {}))
  &amp;#x2F;&amp;#x2F; ...
  return
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后，在 &lt;code&gt;leave()&lt;/code&gt; 的阶段判断此时 AST 节点类型是否为函数、块，是则&lt;strong&gt;出栈&lt;/strong&gt; &lt;code&gt;scopeStack&lt;/code&gt;，并且更新 &lt;code&gt;currentScope&lt;/code&gt; 为出栈后的 &lt;code&gt;scopeStack&lt;/code&gt; 的栈顶元素：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;parent &amp;amp;&amp;amp; parentStack.pop()
if (
  (node.type === &amp;#x27;BlockStatement&amp;#x27; &amp;amp;&amp;amp; !isFunctionType(parent!)) ||
  isFunctionType(node)
) {
  scopeStack.pop()
  currentScope = scopeStack[scopeStack.length - 1] || null
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;2.处理 Identifier 类型的 AST 节点&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于，在我们的例子中 &lt;code&gt;ref&lt;/code&gt; 语法糖创建 &lt;code&gt;count&lt;/code&gt; 变量的 AST 节点类型是 &lt;code&gt;Identifier&lt;/code&gt;，所以这会在 &lt;code&gt;enter()&lt;/code&gt; 阶段命中这样的逻辑：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;if (
    node.type === &amp;#x27;Identifier&amp;#x27; &amp;amp;&amp;amp;
    isReferencedIdentifier(node, parent!, parentStack) &amp;amp;&amp;amp;
    !excludedIds.has(node)
  ) {
    let i = scopeStack.length
    while (i--) {
      if (checkRefId(scopeStack[i], node, parent!, parentStack)) {
        return
      }
    }
  }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在 &lt;code&gt;if&lt;/code&gt; 的判断中，对于 &lt;code&gt;excludedIds&lt;/code&gt; 我们在前面已经介绍过了，而 &lt;code&gt;isReferencedIdentifier()&lt;/code&gt; 则是通过 &lt;code&gt;parenStack&lt;/code&gt; 来判断当前类型为 &lt;code&gt;Identifier&lt;/code&gt; 的 AST 节点 &lt;code&gt;node&lt;/code&gt; 是否是一个引用了这之前的某个 AST 节点。&lt;/p&gt;
&lt;p&gt;然后，再通过访问 &lt;code&gt;scopeStack&lt;/code&gt; 来沿着作用域链来判断是否某个作用域中有 &lt;code&gt;id.name&lt;/code&gt;（变量名 &lt;code&gt;count&lt;/code&gt;）属性以及属性值为 &lt;code&gt;true&lt;/code&gt;，这代表它是一个使用 &lt;code&gt;ref&lt;/code&gt; 语法糖创建的变量，最后则会通过操作 &lt;code&gt;s&lt;/code&gt;（&lt;code&gt;s.appendLeft&lt;/code&gt;）来给该变量添加 &lt;code&gt;.value&lt;/code&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;function checkRefId(
    scope: Scope,
    id: Identifier,
    parent: Node,
    parentStack: Node[]
): boolean {
  if (id.name in scope) {
    if (scope[id.name]) {
      &amp;#x2F;&amp;#x2F; ...
      s.appendLeft(id.end! + offset, &amp;#x27;.value&amp;#x27;)
    }
    return true
  }
  return false
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;通过了解 &lt;code&gt;ref&lt;/code&gt; 语法糖的实现，我想大家应该会对语法糖这个术语会有不一样的理解，它的本质是在编译阶段通过遍历 AST 来进行特定的代码转换操作。并且，这个实现过程的一些工具包（Package）的配合使用也是非常巧妙的，例如 &lt;code&gt;MagicString&lt;/code&gt; 操作源代码字符串、&lt;code&gt;estree-walker&lt;/code&gt; 遍历 AST 节点和作用域相关处理等。&lt;/p&gt;
&lt;p&gt;最后，如果文中存在表达不当或错误的地方，欢迎各位同学提 Issue ～&lt;/p&gt;
&lt;h2&gt;点赞&lt;/h2&gt;
&lt;p&gt;通过阅读本篇文章，如果有收获的话，可以&lt;strong&gt;点个赞&lt;/strong&gt;，这将会成为我持续分享的动力，感谢～&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;我是五柳，喜欢创新、捣鼓源码，专注于源码（Vue 3、Vite）、前端工程化、跨端等技术学习和分享，欢迎关注我的&lt;strong&gt;微信公众号：&lt;a href=&quot;https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/650eef2e7a0a4f1faf1bd68cd4fd961e~tplv-k3u1fbpfcp-zoom-1.image&quot;&gt;Code center&lt;/a&gt;&lt;/strong&gt;。&lt;p&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
作者: 五柳
原文链接：&lt;a href=&quot;https://juejin.im/post/7007765756221849608&quot;&gt;https://juejin.im/post/7007765756221849608&lt;/a&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/div&gt;</description><author>banyungong</author><pubDate>Tue, 11 Jan 2022 13:16:14 GMT</pubDate></item><item><title>在 Vue 中为什么不推荐用 index 做 key__前端__JavaScript__Vue.js</title><link>http://vue-js.com/topic/61dd74d403eaf00040d48068</link><guid>http://vue-js.com/topic/61dd74d403eaf00040d48068</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d4eb6a01e6564840817b127265b07edd~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8ec9270b418647e4ab27f5ccbd983824~tplv-k3u1fbpfcp-watermark.image?&quot; alt=&quot;谱根.png&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这是第 120 篇不掺水的原创，想获取更多原创好文，请搜索公众号关注我们吧~ 本文首发于政采云前端博客：&lt;a href=&quot;https://zoo.team/article/vue-index&quot;&gt;在 Vue 中为什么不推荐用 index 做 key&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;前言&lt;/h1&gt;
&lt;p&gt;前端开发中，只要涉及到列表渲染，那么无论是 React 还是 Vue 框架，都会提示或要求每个列表项使用唯一的 key，那很多开发者就会直接使用数组的 index 作为 key 的值，而并不知道 key 的原理。那么这篇文章就会讲解 key 的作用以及为什么最好不要使用 index 作为 key 的属性值。&lt;/p&gt;
&lt;h2&gt;key 的作用&lt;/h2&gt;
&lt;p&gt;Vue 中使用虚拟 dom 且根据 diff 算法进行新旧 DOM 对比，从而更新真实 dom ，key 是虚拟 DOM 对象的唯一标识, 在 diff 算法中 key 起着极其重要的作用。&lt;/p&gt;
&lt;h2&gt;key 在 diff 算法中的角色&lt;/h2&gt;
&lt;p&gt;其实在 React，Vue，中 diff 算法大致是差不多，但是 diff 比对方式还是有较大差异的，甚至每个版本 diff 都大有不同。下面我们就以 Vue3.0 diff 算法为切入点，剖析 key 在 diff 算法中的作用&lt;/p&gt;
&lt;p&gt;具体 diff 流程如下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0f3108cefc3e4974acc53b305c42f707~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;未命名表单 (1).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;Vue3.0中 在 patchChildren 方法中有这么一段源码&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;if (patchFlag &amp;gt; 0) {
      if (patchFlag &amp;amp; PatchFlags.KEYED_FRAGMENT) { 
         &amp;#x2F;* 对于存在 key 的情况用于 diff 算法 *&amp;#x2F;
        patchKeyedChildren(
         ...
        )
        return
      } else if (patchFlag &amp;amp; PatchFlags.UNKEYED_FRAGMENT) {
         &amp;#x2F;* 对于不存在 key 的情况,直接 patch  *&amp;#x2F;
        patchUnkeyedChildren( 
          ...
        )
        return
      }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;patchChildren 根据是否存在 key 进行真正的 diff 或者直接 patch。对于 key 不存在的情况我们就不做深入研究了。&lt;/p&gt;
&lt;p&gt;我们先来看看一些声明的变量。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;*  c1 老的 vnode c2 新的vnode  *&amp;#x2F;
let i = 0              &amp;#x2F;* 记录索引 *&amp;#x2F;
const l2 = c2.length   &amp;#x2F;* 新 vnode的数量 *&amp;#x2F;
let e1 = c1.length - 1 &amp;#x2F;* 老 vnode 最后一个节点的索引 *&amp;#x2F;
let e2 = l2 - 1        &amp;#x2F;* 新节点最后一个节点的索引 *&amp;#x2F;
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;同步头部节点&lt;/h3&gt;
&lt;p&gt;第一步的事情就是从头开始寻找相同的 vnode，然后进行 patch ,如果发现不是相同的节点，那么立即跳出循环。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;(a b) c
&amp;#x2F;&amp;#x2F;(a b) d e
&amp;#x2F;* 从头对比找到有相同的节点 patch ，发现不同，立即跳出*&amp;#x2F;
    while (i &amp;lt;= e1 &amp;amp;&amp;amp; i &amp;lt;= e2) {
      const n1 = c1[i]
      const n2 = (c2[i] = optimized
        ? cloneIfMounted(c2[i] as VNode)
        : normalizeVNode(c2[i]))
        &amp;#x2F;* 判断 key ，type 是否相等 *&amp;#x2F;
      if (isSameVNodeType(n1, n2)) {
        patch(
          ...
        )
      } else {
        break
      }
      i++
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;流程如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7c2e234667684ce0b19e085dc96add9f~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;isSameVNodeType 作用就是判断当前 vnode 类型 和 vnode 的 key 是否相等&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;export function isSameVNodeType(n1: VNode, n2: VNode): boolean {
  return n1.type === n2.type &amp;amp;&amp;amp; n1.key === n2.key
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其实看到这，是不是已经知道 key 在 diff 算法的作用，就是用来判断是否是同一个节点。&lt;/p&gt;
&lt;h3&gt;同步尾部节点&lt;/h3&gt;
&lt;p&gt;第二步从尾开始同前 diff&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;a (b c)
&amp;#x2F;&amp;#x2F;d e (b c)
&amp;#x2F;* 如果第一步没有 patch 完，立即，从后往前开始 patch  如果发现不同立即跳出循环 *&amp;#x2F;
    while (i &amp;lt;= e1 &amp;amp;&amp;amp; i &amp;lt;= e2) {
      const n1 = c1[e1]
      const n2 = (c2[e2] = optimized
        ? cloneIfMounted(c2[e2] as VNode)
        : normalizeVNode(c2[e2]))
      if (isSameVNodeType(n1, n2)) {
        patch(
         ...
        )
      } else {
        break
      }
      e1--
      e2--
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;经历第一步操作之后，如果发现没有 patch 完，那么立即进行第二步，从尾部开始遍历依次向前 diff。如果发现不是相同的节点，那么立即跳出循环。
流程如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/86855d1c542f4841993743a5a8a9cd49~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (1).png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;添加新的节点&lt;/h3&gt;
&lt;p&gt;第三步如果老节点是否全部 patch，新节点没有被 patch 完,创建新的 vnode&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;(a b)
&amp;#x2F;&amp;#x2F;(a b) c
&amp;#x2F;&amp;#x2F;i = 2, e1 = 1, e2 = 2
&amp;#x2F;&amp;#x2F;(a b)
&amp;#x2F;&amp;#x2F;c (a b)
&amp;#x2F;&amp;#x2F;i = 0, e1 = -1, e2 = 0
&amp;#x2F;* 如果新的节点大于老的节点数 ，对于剩下的节点全部以新的 vnode 处理（这种情况说明已经 patch 完相同的 vnode ） *&amp;#x2F;
    if (i &amp;gt; e1) {
      if (i &amp;lt;= e2) {
        const nextPos = e2 + 1
        const anchor = nextPos &amp;lt; l2 ? (c2[nextPos] as VNode).el : parentAnchor
        while (i &amp;lt;= e2) {
          patch( &amp;#x2F;* 创建新的节点*&amp;#x2F;
            ...
          )
          i++
        }
      }
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;流程如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e054a7fc8aa8417ea463fd1d8a6ad5a1~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (2).png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;删除多余节点&lt;/h3&gt;
&lt;p&gt;第四步如果新节点全部被 patch，老节点有剩余，那么卸载所有老节点&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;#x2F;&amp;#x2F;i &amp;gt; e2
&amp;#x2F;&amp;#x2F;(a b) c
&amp;#x2F;&amp;#x2F;(a b)
&amp;#x2F;&amp;#x2F;i = 2, e1 = 2, e2 = 1
&amp;#x2F;&amp;#x2F;a (b c)
&amp;#x2F;&amp;#x2F;(b c)
&amp;#x2F;&amp;#x2F;i = 0, e1 = 0, e2 = -1
else if (i &amp;gt; e2) {
   while (i &amp;lt;= e1) {
      unmount(c1[i], parentComponent, parentSuspense, true)
      i++
   }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;流程如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/429c92305790409680f4b7fef4e1c510~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (3).png&quot;&gt;&lt;/p&gt;
&lt;h3&gt;最长递增子序列&lt;/h3&gt;
&lt;p&gt;到了这一步，比较核心的场景还没有出现，如果运气好，可能到这里就结束了，那我们也不能全靠运气。剩下的一个场景是新老节点都还有多个子节点存在的情况。那接下来看看，Vue3 是怎么做的。为了结合 move、新增和卸载的操作&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/77805129d7384dc78922898b3b0e018d~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (4).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;每次在对元素进行移动的时候，我们可以发现一个规律，如果想要移动的次数最少，就意味着需要有一部分元素是稳定不动的，那么究竟能够保持稳定不动的元素有一些什么规律呢？&lt;/p&gt;
&lt;p&gt;可以看一下上面这个例子：c  h  d  e  VS  d  e  i  c，在比对的时候，凭着肉眼可以看出只需要将 c 进行移动到最后，然后卸载 h，新增 i 就好了。d  e 可以保持不动，可以发现 d  e 在新老节点中的顺序都是不变的，d 在 e 的后面，下标处于递增状态。&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-plain&quot;&gt;&lt;code&gt;这里引入一个概念，叫最长递增子序列。
官方解释：在一个给定的数组中，找到一组递增的数值，并且长度尽可能的大。
有点比较难理解，那来看具体例子：

const arr = [10, 9, 2, 5, 3, 7, 101, 18]
=&amp;gt; [2, 3, 7, 18]
这一列数组就是arr的最长递增子序列，其实[2, 3, 7, 101]也是。
所以最长递增子序列符合三个要求：
1、子序列内的数值是递增的
2、子序列内数值的下标在原数组中是递增的
3、这个子序列是能够找到的最长的
但是我们一般会找到数值较小的那一组数列，因为他们可以增长的空间会更多。
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那接下来的思路是：如果能找到老节点在新节点序列中顺序不变的节点们，就知道，哪一些节点不需要移动，然后只需要把不在这里的节点插入进来就可以了。**因为最后要呈现出来的顺序是新节点的顺序，移动是只要老节点移动，所以只要老节点保持最长顺序不变，通过移动个别节点，就能够跟它保持一致。**所以在此之前，先把所有节点都找到，再找对应的序列。最后其实要得到的则是这一个数组：[2, 3, 新增 , 0]。其实这就是 diff 移动的思路了&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/340a3dd032be4da685cf9dfa89f5b1c8~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (5).png&quot;&gt;&lt;/p&gt;
&lt;h1&gt;为什么不要用index&lt;/h1&gt;
&lt;h3&gt;性能消耗&lt;/h3&gt;
&lt;p&gt;使用 index 做 key，破坏顺序操作的时候， 因为每一个节点都找不到对应的 key，导致部分节点不能复用,所有的新 vnode 都需要重新创建。&lt;/p&gt;
&lt;p&gt;例子：&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;hello&amp;quot;&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;li v-for=&amp;quot;(item,index) in studentList&amp;quot; :key=&amp;quot;index&amp;quot;&amp;gt;{{item.name}}&amp;lt;&amp;#x2F;li&amp;gt;
      &amp;lt;br&amp;gt;
      &amp;lt;button @click=&amp;quot;addStudent&amp;quot;&amp;gt;添加一条数据&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;&amp;#x2F;ul&amp;gt;

  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: &amp;#x27;HelloWorld&amp;#x27;,
  data() {
    return {
      studentList: [
        { id: 1, name: &amp;#x27;张三&amp;#x27;, age: 18 },
        { id: 2, name: &amp;#x27;李四&amp;#x27;, age: 19 },
      ],
    };
  },
  methods:{
    addStudent(){
      const studentObj = { id: 3, name: &amp;#x27;王五&amp;#x27;, age: 20 };
      this.studentList=[studentObj,...this.studentList]
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;

&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们先把 Chorme 调试器打开，我们双击把里面文本修改一下&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ad4acbae89724e10bdfccdefbc8b3650~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (6).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们运行以上上面的代码，看下运行结果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e69a1c09aee425ab35b2a0dfbfaf952~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;chrome-capture (2).gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;从上面运行结果可以看出来，我们只是添加了一条数据，但是三条数据都需要重新渲染是不是很惊奇，我明明只是插入了一条数据，怎么三条数据都要重新渲染？而我想要的只是新增的那一条数据新渲染出来就行了。&lt;/p&gt;
&lt;p&gt;上面我们也讲过 diif 比较方式,下面根据 diff 比较绘制一张图，看看具体是怎么比较的吧&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f3346f6ba3c641ef8cf51ebd0505b7b9~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (7).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;当我们在前面加了一条数据时 index 顺序就会被打断，导致新节点 key 全部都改变了，所以导致我们页面上的数据都被重新渲染了。&lt;/p&gt;
&lt;p&gt;下面我们下面生成1000个 DOM 来比较一下采用 index ，和不采用 index 性能比较，为了保证 key 的唯一性我们采用 uuid 作为 key&lt;/p&gt;
&lt;p&gt;我们用 index 做为 key 现执行一遍&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;hello&amp;quot;&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;button @click=&amp;quot;addStudent&amp;quot;&amp;gt;添加一条数据&amp;lt;&amp;#x2F;button&amp;gt;
      &amp;lt;br&amp;gt;
      &amp;lt;li v-for=&amp;quot;(item,index) in studentList&amp;quot; :key=&amp;quot;index&amp;quot;&amp;gt;{{item.id}}&amp;lt;&amp;#x2F;li&amp;gt;
    &amp;lt;&amp;#x2F;ul&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
import uuidv1 from &amp;#x27;uuid&amp;#x2F;v1&amp;#x27;
export default {
  name: &amp;#x27;HelloWorld&amp;#x27;,
  data() {
    return {
      studentList: [{id:uuidv1()}],
    };
  },
  created(){
    for (let i = 0; i &amp;lt; 1000; i++) {
      this.studentList.push({
        id: uuidv1(),
      });
    }
  },
  beforeUpdate(){
    console.time(&amp;#x27;for&amp;#x27;);
  },
  updated(){
    console.timeEnd(&amp;#x27;for&amp;#x27;)&amp;#x2F;&amp;#x2F;for: 75.259033203125 ms
  },
  methods:{
    addStudent(){
      const studentObj = { id: uuidv1() };
      this.studentList=[studentObj,...this.studentList]
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;换成 id 作为 key&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;hello&amp;quot;&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;button @click=&amp;quot;addStudent&amp;quot;&amp;gt;添加一条数据&amp;lt;&amp;#x2F;button&amp;gt;
      &amp;lt;br&amp;gt;
      &amp;lt;li v-for=&amp;quot;(item,index) in studentList&amp;quot; :key=&amp;quot;item.id&amp;quot;&amp;gt;{{item.id}}&amp;lt;&amp;#x2F;li&amp;gt;
    &amp;lt;&amp;#x2F;ul&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
  beforeUpdate(){
    console.time(&amp;#x27;for&amp;#x27;);
  },
  updated(){
    console.timeEnd(&amp;#x27;for&amp;#x27;)&amp;#x2F;&amp;#x2F;for: 42.200927734375 ms
  },
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从上面比较可以看出，用唯一值作为 key 可以节约开销&lt;/p&gt;
&lt;h3&gt;数据错位&lt;/h3&gt;
&lt;p&gt;上述例子可能觉得用 index 做 key 只是影响页面加载的效率，认为少量的数据影响不大，那面下面这种情况，可能用 index 就可能出现一些意想不到的问题了，还是上面的场景，这时我先再每个文本内容后面加一个 input 输入框，并且手动在输入框内填写一些内容，然后通过 button 向前追加一位同学看看&lt;/p&gt;
&lt;pre class=&quot;prettyprint language-javascript&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;hello&amp;quot;&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;li v-for=&amp;quot;(item,index) in studentList&amp;quot; :key=&amp;quot;index&amp;quot;&amp;gt;{{item.name}}&amp;lt;input &amp;#x2F;&amp;gt;&amp;lt;&amp;#x2F;li&amp;gt;
      &amp;lt;br&amp;gt;
      &amp;lt;button @click=&amp;quot;addStudent&amp;quot;&amp;gt;添加一条数据&amp;lt;&amp;#x2F;button&amp;gt;
    &amp;lt;&amp;#x2F;ul&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;

&amp;lt;script&amp;gt;
export default {
  name: &amp;#x27;HelloWorld&amp;#x27;,
  data() {
    return {
      studentList: [
        { id: 1, name: &amp;#x27;张三&amp;#x27;, age: 18 },
        { id: 2, name: &amp;#x27;李四&amp;#x27;, age: 19 },
      ],
    };
  },
  methods:{
    addStudent(){
      const studentObj = { id: 3, name: &amp;#x27;王五&amp;#x27;, age: 20 };
      this.studentList=[studentObj,...this.studentList]
    }
  }
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;我们往 input 里面输入一些值，添加一位同学看下效果：
&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6e9185cea3bb4b5db540848dca67c859~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;chrome-capture (3).gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;这时候我们就会发现，在添加之前输入的数据错位了。添加之后王五的输入框残留着张三的信息，这很显然不是我们想要的结果。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/36be485e696848709de8e7d77a4616da~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (8).png&quot;&gt;&lt;/p&gt;
&lt;p&gt;从上面比对可以看出来这时因为采用 index 作为 key 时，当在比较时，发现虽然文本值变了，但是当继续向下比较时发现 &amp;lt;input /&amp;gt;  DOM 节点还是和原来一摸一样，就复用了，但是没想到 input 输入框残留输入的值，这时候就会出现输入的值出现错位的情况&lt;/p&gt;
&lt;h1&gt;解决方案&lt;/h1&gt;
&lt;p&gt;既然知道用 index 在某些情况下带来很不好的影响，那平时我们在开发当中怎么去解决这种情况呢？其实只要保证 key 唯一不变就行，一般在开发中用的比较多就是下面三种情况。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在开发中最好每条数据使用唯一标识固定的数据作为 key，比如后台返回的 ID，手机号，身份证号等唯一值&lt;/li&gt;
&lt;li&gt;可以采用 Symbol 作为 key，Symbol 是 ES6 引入了一种新的原始数据类型 Symbol ，表示独一无二的值，最大的用法是用来定义对象的唯一属性名。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;prettyprint language-plain&quot;&gt;&lt;code&gt;let a=Symbol(&amp;#x27;测试&amp;#x27;)
let b=Symbol(&amp;#x27;测试&amp;#x27;)
console.log(a===b)&amp;#x2F;&amp;#x2F;false
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;可以采用 uuid 作为 key ，uuid 是 Universally Unique Identifier 的缩写，它是在一定的范围内（从特定的名字空间到全球）唯一的机器生成的标识符&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们采用上面第一种方案作为 key 在看一下上面情况，如图所示。key 相同的节点都做到了复用。起到了diff 算法的真正作用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5b1c1738c0284796b9d0f34e3f36d029~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;chrome-capture (4).gif&quot;&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/81fa5ab929644d9bb3fd5dcf7649446c~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;chrome-capture (5).gif&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3e9bcc175af14a4ab09ab800517293b8~tplv-k3u1fbpfcp-zoom-1.image&quot; alt=&quot;image (9).png&quot;&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;用 index 作为 key 时，在对数据进行，逆序添加，逆序删除等破坏顺序的操作时，会产生没必要的真实 DOM更新，从而导致效率低&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用 index 作为 key 时，如果结构中包含输入类的 DOM，会产生错误的 DOM 更新&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;在开发中最好每条数据使用唯一标识固定的数据作为 key，比如后台返回的 ID，手机号，身份证号等唯一值&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如果不存在对数据逆序添加，逆序删除等破坏顺序的操作时，仅用于渲染展示用时，使用 index 作为 key 也是可以的（但是还是不建议使用，养成良好开发习惯）。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;参考链接&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/zl_Alien/article/details/106595459&quot;&gt;vue3.0 diff算法详解(超详细)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://segmentfault.com/a/1190000038654183&quot;&gt;Vue 3 Virtual Dom Diff源码阅读&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;推荐阅读&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/7002746459456176158&quot;&gt;电商最小存货 - SKU 和 算法实现&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/6997536906967777316&quot;&gt;你需要知道的项目管理知识&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/7013131773756309517&quot;&gt;浏览器渲染之回流重绘&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://juejin.cn/post/7018296556323340324&quot;&gt;防抖节流场景及应用&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;开源作品&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;政采云前端小报&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;开源地址 &lt;a href=&quot;https://www.zoo.team/openweekly/&quot;&gt;www.zoo.team/openweekly/&lt;/a&gt;&lt;/strong&gt; (小报官网首页有微信交流群)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;skuDemo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;开源地址 &lt;a href=&quot;https://github.com/zcy-inc/skuPathFinder-back&quot;&gt;https://github.com/zcy-inc/skuPathFinder-back/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;招贤纳士&lt;/h2&gt;
&lt;p&gt;政采云前端团队（ZooTeam），一个年轻富有激情和创造力的前端团队，隶属于政采云产品研发部，Base 在风景如画的杭州。团队现有 50 余个前端小伙伴，平均年龄 27 岁，近 3 成是全栈工程师，妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵，也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外，还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战，推动并落地了一系列的内部技术产品，持续探索前端技术体系的新边界。&lt;/p&gt;
&lt;p&gt;如果你想改变一直被事折腾，希望开始能折腾事；如果你想改变一直被告诫需要多些想法，却无从破局；如果你想改变你有能力去做成那个结果，却不需要你；如果你想改变你想做成的事需要一个团队去支撑，但没你带人的位置；如果你想改变既定的节奏，将会是“5 年工作时间 3 年工作经验”；如果你想改变本来悟性不错，但总是有那一层窗户纸的模糊… 如果你相信相信的力量，相信平凡人能成就非凡事，相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程，亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程，我觉得我们该聊聊。任何时间，等着你写点什么，发给 &lt;code&gt;ZooTeam@cai-inc.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/98d3aa3d1f8646a8bcda8cfd9e335a4b~tplv-k3u1fbpfcp-zoom-1.image&quot; alt&gt;&lt;/p&gt;
&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 政采云前端团队
        原文链接：&lt;a href=&quot;https://juejin.im/post/7026119446162997261&quot;&gt;https://juejin.im/post/7026119446162997261&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Tue, 11 Jan 2022 12:15:16 GMT</pubDate></item><item><title>『前端优化』—— Vue中避免滥用this去读取data中数据__Vue.js__面试</title><link>http://vue-js.com/topic/61dd66b403eaf00040d48067</link><guid>http://vue-js.com/topic/61dd66b403eaf00040d48067</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h1&gt;主题列表：juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black, awesome-green, qklhk-chocolate&lt;/h1&gt;
&lt;h1&gt;贡献主题：&lt;a href=&quot;https://github.com/xitu/juejin-markdown-themes&quot;&gt;https://github.com/xitu/juejin-markdown-themes&lt;/a&gt;&lt;/h1&gt;
&lt;h2&gt;theme: awesome-green
highlight:&lt;/h2&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在Vue中，data选项是个好东西，把数据往里一丢，在一个Vue组件中任何一个地方都可以通过this来读取data中数据。但是要避免滥用this去读取data中数据，至于在哪里要避免滥用，如果滥用会导致什么后果，本专栏将会一一揭晓。&lt;/p&gt;
&lt;h2&gt;一、用this读取data中数据的过程&lt;/h2&gt;
&lt;p&gt;在Vue源码中会把data中数据添加getter函数和setter函数，将其转成响应式的。getter函数代码如下所示：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;function reactiveGetter() {
    var value = getter ? getter.call(obj) : val;
    if (Dep.target) {
        dep.depend();
        if (childOb) {
            childOb.dep.depend();
            if (Array.isArray(value)) {
                dependArray(value);
            }
        }
    }
    return value
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;用this读取data中数据时，会触发getter函数，在其中通过 &lt;code&gt;var value = getter ? getter.call(obj) : val;&lt;/code&gt; 获取到值后执行 &lt;code&gt;return value&lt;/code&gt;，实现读取数据的目的。&lt;/p&gt;
&lt;p&gt;但是在其间还有一段代码，在这段代码中会经过一系列复杂的逻辑运算来收集依赖。这里不介绍如何收集依赖，如果想了解可以看&lt;a href=&quot;https://juejin.cn/post/6887966751703695374&quot;&gt;这篇专栏&lt;/a&gt;。这里只要知道在Dep.target存在时才会去收集依赖。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里可以得出一个结论，在Dep.target存在时，使用this去读取data中数据时会去收集依赖。如果滥用this去读取data中数据，会多次重复地收集依赖，从而产生性能问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;二、Dep.target什么时候存在&lt;/h2&gt;
&lt;p&gt;Dep.target是由依赖赋值的。依赖又称为Watcher（侦听者）或者订阅者。在Vue中有三种依赖，其中两种是很常见的，就是watch（侦听器）和computed（计算属性）。还有一种隐藏的依赖———渲染Watcher，在模板首次渲染的过程中创建的。&lt;/p&gt;
&lt;p&gt;Dep.target是在依赖创建时被赋值，依赖是用构造函数Watcher创建。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
    &amp;#x2F;&amp;#x2F;...
    if (typeof expOrFn === &amp;#x27;function&amp;#x27;) {
        this.getter = expOrFn;
    } else {
        this.getter = parsePath(expOrFn);
    }
    this.value = this.lazy ? undefined : this.get();
};
Watcher.prototype.get = function get() {
    pushTarget(this);
    try {
        value = this.getter.call(vm, vm);
    } catch (e) {
        
    }
    return value
};
Dep.target = null;
var targetStack = [];
function pushTarget(target) {
    targetStack.push(target);
    Dep.target = target;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在构造函数Watcher最后会执行实例方法&lt;code&gt;get&lt;/code&gt;，在实例方法&lt;code&gt;get&lt;/code&gt;中执行&lt;code&gt;pushTarget(this)&lt;/code&gt;中给Dep.target赋值的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;而依赖是在Vue页面或组件初次渲染时创建，所以产生的性能问题应该是首次渲染过慢的问题。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;三、在何处滥用this去读取data中数据&lt;/h2&gt;
&lt;p&gt;在Dep.target存在时去执行这些滥用this去读取data中数据的代码会产生性能问题，故还要搞清楚这些代码是写在哪里才会被执行到，换句话来说，要搞清楚在哪里滥用this去读取data中数据会产生性能问题。&lt;/p&gt;
&lt;p&gt;在第二小节中介绍了Dep.target被赋值后会执行&lt;code&gt;value = this.getter.call(vm, vm)&lt;/code&gt;，其中&lt;code&gt;this.getter&lt;/code&gt;是一个函数，那么若在其中有用this去读取data数据，就会去收集依赖，假如滥用的话就会产生性能问题。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;this.getter&lt;/code&gt;是在创建依赖过程中赋值的，每种依赖的&lt;code&gt;this.getter&lt;/code&gt;都是不相同的。下面来一一介绍。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;watch（侦听器）依赖的&lt;code&gt;this.getter&lt;/code&gt;是&lt;code&gt;parsePath&lt;/code&gt;函数，其函数参数就是侦听的对象。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;var bailRE = new RegExp((&amp;quot;[^&amp;quot; + (unicodeRegExp.source) + &amp;quot;.$_&amp;#x5C;&amp;#x5C;d]&amp;quot;));
function parsePath(path) {
    if (bailRE.test(path)) {
        return
    }
    var segments = path.split(&amp;#x27;.&amp;#x27;);
    return function(obj) {
        for (var i = 0; i &amp;lt; segments.length; i++) {
            if (!obj) {
                return
            }
            obj = obj[segments[i]];
        }
        return obj
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如下所示的代码中的 &lt;code&gt;a&lt;/code&gt; 和 &lt;code&gt;a.b.c&lt;/code&gt;作为参数传入&lt;code&gt;parsePath&lt;/code&gt;函数会返回一个函数赋值给&lt;code&gt;this.getter&lt;/code&gt;，执行&lt;code&gt;this.getter.call(vm, vm)&lt;/code&gt;会得到&lt;code&gt;this.a&lt;/code&gt;和&lt;code&gt;this.a.b.c&lt;/code&gt;的值。&lt;strong&gt;在这个过程中不会存在滥用this去读取data中数据的场景。&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;watch:{
  a:function(newVal, oldVal){
    &amp;#x2F;&amp;#x2F;做点什么
  }
}
vm.$watch(&amp;#x27;a.b.c&amp;#x27;, function (newVal, oldVal) {
  &amp;#x2F;&amp;#x2F; 做点什么
})
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;computed（计算属性）依赖的&lt;code&gt;this.getter&lt;/code&gt;有两种，如果计算属性的值是个函数，那么&lt;code&gt;this.getter&lt;/code&gt;就是这个函数。如果计算属性的值是个对象，那么&lt;code&gt;this.getter&lt;/code&gt;就是这个对象的get属性值，get属性值也是个函数。在这个函数可能会存在滥用this去读取data中数据的场景，举个例子，代码如下所示。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;computed:{
    d:function(){
        let result = 0;
        for(let key in this.a){
            if(this.a[key].num &amp;gt; 20){
                result += this.a[key].num + this.b + this.c;
            }else{
                result += this.a[key].num + this.e + this.f;
            }
        }
        return result;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在计算属性&lt;code&gt;d&lt;/code&gt;中就存在滥用this去读取data数据。其中&lt;code&gt;this.a&lt;/code&gt;是个数组，此时Dep.target的值为计算属性&lt;code&gt;d&lt;/code&gt;这个依赖，在循环&lt;code&gt;this.a&lt;/code&gt;中使用this去获取中&lt;code&gt;a&lt;/code&gt;、&lt;code&gt;b&lt;/code&gt;、&lt;code&gt;c&lt;/code&gt;、&lt;code&gt;e&lt;/code&gt;、&lt;code&gt;f&lt;/code&gt;的数据，使这些数据进行一系列复杂的逻辑运算来重复地收集计算属性&lt;code&gt;d&lt;/code&gt;这个依赖。导致获取计算属性&lt;code&gt;d&lt;/code&gt;的值的速度变慢，从而产生性能问题。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;渲染Watcher的&lt;code&gt;this.getter&lt;/code&gt;是一个函数如下所示：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;updateComponent = function() {
  vm._update(vm._render(), hydrating);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中&lt;code&gt;vm._render()&lt;/code&gt;会把template模板生成的渲染函数render转成虚拟DOM（VNode）：&lt;code&gt;vnode = render.call(vm._renderProxy, vm.$createElement);&lt;/code&gt;，举一个例子来说明一下渲染函数render是什么。&lt;/p&gt;
&lt;p&gt;例如template模板：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;
    &amp;lt;p&amp;gt;{{a}}&amp;lt;span&amp;gt;{{b}}&amp;lt;&amp;#x2F;span&amp;gt;&amp;lt;&amp;#x2F;p&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;通过vue-loader会生成渲染函数render，如下所示：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;(function anonymous() {
    with(this) {
        return _c(&amp;#x27;div&amp;#x27;, {
            attrs: {
                &amp;quot;class&amp;quot;: &amp;quot;wrap&amp;quot;
            }
        }, [_c(&amp;#x27;p&amp;#x27;, [_v(_s(a)), _c(&amp;#x27;span&amp;#x27;, [_v(_s(b))])])])
    }
})
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中&lt;code&gt;with&lt;/code&gt;语句的作用是为一个或一组语句指定默认对象，例&lt;code&gt;with(this){ a + b }&lt;/code&gt; 等同 &lt;code&gt;this.a + this.b&lt;/code&gt;，那么在template模板中使用&lt;code&gt;{{ a }}&lt;/code&gt;相当使用this去读取data中的&lt;code&gt;a&lt;/code&gt;数据。故在template模板生成的渲染函数render中也可能存在滥用this去读取data中数据的场景。举个例子，代码如下所示：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;
    &amp;lt;div v-for=item in list&amp;gt;
      &amp;lt;div&amp;gt; {{ arr[item.index][&amp;#x27;name&amp;#x27;] }} &amp;lt;&amp;#x2F;div&amp;gt;
      &amp;lt;div&amp;gt; {{ obj[item.id][&amp;#x27;age&amp;#x27;] }} &amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中用&lt;code&gt;v-for&lt;/code&gt;循环&lt;code&gt;list&lt;/code&gt;数组过程中，不断用this去读取data中&lt;code&gt;arr&lt;/code&gt;、&lt;code&gt;obj&lt;/code&gt;的数据，使这些数据进行一系列复杂的逻辑运算来重复收集这个依赖，导致初次渲染的速度变慢，从而产生性能问题。&lt;/p&gt;
&lt;h2&gt;四、如何避免滥用this去读取data中数据&lt;/h2&gt;
&lt;p&gt;综上所述在计算属性和template模板中滥用this去读取data中数据会导致多次重复地收集依赖，从而产生性能问题，那要怎么避免这种情况。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;计算属性中如何避免&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用ES6对象解构赋值来避免，计算属性的值是一个函数，其参数是Vue的实例化&lt;code&gt;this&lt;/code&gt;对象，在上述计算属性中滥用this的例子中可以这样优化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优化前：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;computed:{
    d:function(){
        let result = 0;
        for(let key in this.a){
            if(this.a[key].num &amp;gt; 20){
                result += this.a[key].num + this.b + this.c;
            }else{
                result += this.a[key].num + this.e + this.f;
            }
        }
        return result;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;优化后：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;computed: {
  d({ a, b, c, e, f }) {
    let result = 0;
    for (let key in a) {
      if (a[key].num &amp;gt; 20) {
        result += a[key].num + b + c;
      } else {
        result += a[key].num + e + f;
      }
    }
    return result;
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;以上利用解构赋值提前把data数据中的&lt;code&gt;a&lt;/code&gt;、&lt;code&gt;b&lt;/code&gt;、&lt;code&gt;c&lt;/code&gt;、&lt;code&gt;e&lt;/code&gt;、&lt;code&gt;f&lt;/code&gt;赋值给对应的变量&lt;code&gt;a&lt;/code&gt;、&lt;code&gt;b&lt;/code&gt;、&lt;code&gt;c&lt;/code&gt;、&lt;code&gt;e&lt;/code&gt;、&lt;code&gt;f&lt;/code&gt;，然后在计算属性中可以通过这些变量访问data数据的，且不会触发data中对应数据的依赖收集。这样只用this读取了一次data中的数据，只触发了一次依赖收集，避免了多次重复地依赖收集产生的性能问题。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;template模板中如何避免&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;提前处理&lt;code&gt;v-for&lt;/code&gt;循环所用的数据，不要在&lt;code&gt;v-for&lt;/code&gt;循环中去读取数组、对象类型的数据。在上述template模板中滥用this的例子中可以这样优化。&lt;/p&gt;
&lt;p&gt;假设&lt;code&gt;list&lt;/code&gt;、&lt;code&gt;arr&lt;/code&gt;、&lt;code&gt;obj&lt;/code&gt;皆是服务端返回来的数据，且&lt;code&gt;arr&lt;/code&gt;和&lt;code&gt;obj&lt;/code&gt;没有用到任何模块渲染中，可以这样优化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优化前：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;
    &amp;lt;div v-for=item in list&amp;gt;
      &amp;lt;div&amp;gt; {{ arr[item.index][&amp;#x27;name&amp;#x27;] }} &amp;lt;&amp;#x2F;div&amp;gt;
      &amp;lt;div&amp;gt; {{ obj[item.id][&amp;#x27;age&amp;#x27;] }} &amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;优化后：&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;
    &amp;lt;div v-for=item in listData&amp;gt;
      &amp;lt;div{{item.name}} &amp;lt;&amp;#x2F;div&amp;gt;
        &amp;lt;div&amp;gt;{{item.age}}&amp;lt;&amp;#x2F;div&amp;gt;
    &amp;lt;&amp;#x2F;div&amp;gt;
  &amp;lt;&amp;#x2F;div&amp;gt;
&amp;lt;&amp;#x2F;template&amp;gt;
&amp;lt;script&amp;gt;
export default {
  data() {
    return {
      list: [],
    }
  },
  created(){
    &amp;#x2F;&amp;#x2F; 在这里定义arr和obj避免被转成响应式
    this.arr = [];
    this.obj = {};
  },
  computed: {
    listData: function ({list}) {
      list.forEach(item =&amp;gt; {
        item.name = this.arr[item.index].name;
        item.age = this.obj[item.id].age;
      })
      return list;
    }
  },
}
&amp;lt;&amp;#x2F;script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
        版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
        作者: 红尘炼心
        原文链接：&lt;a href=&quot;https://juejin.im/post/6934911314871451685&quot;&gt;https://juejin.im/post/6934911314871451685&lt;/a&gt;
      &lt;/p&gt;&lt;/div&gt;</description><author>banyungong</author><pubDate>Tue, 11 Jan 2022 11:15:00 GMT</pubDate></item><item><title>你会用ES6，那倒是用啊！__前端__Vue.js__React.js</title><link>http://vue-js.com/topic/61dd58a41369e3003b71b2cf</link><guid>http://vue-js.com/topic/61dd58a41369e3003b71b2cf</guid><description>&lt;div class=&quot;markdown-text&quot;&gt;&lt;hr&gt;
&lt;h2&gt;theme: fancy
highlight: arta&lt;/h2&gt;
&lt;p&gt;本文已参与「&lt;a href=&quot;https://juejin.cn/post/7012210233804079141/&quot; title=&quot;https://juejin.cn/post/7012210233804079141/&quot;&gt;掘力星计划&lt;/a&gt;」，赢取创作大礼包，挑战创作激励金。&lt;/p&gt;
&lt;p&gt;不是标题党，这是一位leader在一次代码评审会对小组成员发出的“怒吼”，原因是在代码评审中发现很多地方还是采用ES5的写法，也不是说用ES5写法不行，会有BUG，只是造成代码量增多，可读性变差而已。&lt;/p&gt;
&lt;p&gt;恰好，这位leader有代码洁癖，面对3~5年经验的成员，还写这种水平的代码，极为不满，不断对代码进行吐槽。不过对于他的吐槽，我感觉还是有很大收获的，故就把leader的吐槽记录下来，分享给掘友们，觉得有收获点个赞，有错误的或者更好的写法，非常欢迎在评论中留言。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ps：ES5之后的JS语法统称ES6！！！&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;一、关于取值的吐槽&lt;/h2&gt;
&lt;p&gt;取值在程序中非常常见，比如从对象&lt;code&gt;obj&lt;/code&gt;中取值。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const obj = {
    a:1,
    b:2,
    c:3,
    d:4,
    e:5,
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = obj.a;
const b = obj.b;
const c = obj.c;
const d = obj.d;
const e = obj.e;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;或者&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const f = obj.a + obj.d;
const g = obj.c + obj.e;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;吐槽：“不会用ES6的解构赋值来取值吗？5行代码用1行代码搞定不香吗？直接用对象名加属性名去取值，要是对象名短还好，很长呢？搞得代码中到处都是这个对象名。”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const {a,b,c,d,e} = obj;
const f = a + d;
const g = c + e;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;反驳&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不是不用ES6的解构赋值，而是服务端返回的数据对象中的属性名不是我想要的，这样取值，不是还得重新创建个遍历赋值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;看来你对ES6的解构赋值掌握的还是不够彻底。如果想创建的变量名和对象的属性名不一致，可以这么写：&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const {a:a1} = obj;
console.log(a1);&amp;#x2F;&amp;#x2F; 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;补充&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6的解构赋值虽然好用。但是要注意解构的对象不能为&lt;code&gt;undefined&lt;/code&gt;、&lt;code&gt;null&lt;/code&gt;。否则会报错，故要给被解构的对象一个默认值。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const {a,b,c,d,e} = obj || {};
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;二、关于合并数据的吐槽&lt;/h2&gt;
&lt;p&gt;比如合并两个数组，合并两个对象。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = [1,2,3];
const b = [1,5,6];
const c = a.concat(b);&amp;#x2F;&amp;#x2F;[1,2,3,1,5,6]

const obj1 = {
  a:1,
}
const obj2 = {
  b:1,
}
const obj = Object.assign({}, obj1, obj2);&amp;#x2F;&amp;#x2F;{a:1,b:1}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6的扩展运算符是不是忘记了，还有数组的合并不考虑去重吗？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = [1,2,3];
const b = [1,5,6];
const c = [...new Set([...a,...b])];&amp;#x2F;&amp;#x2F;[1,2,3,5,6]

const obj1 = {
  a:1,
}
const obj2 = {
  b:1,
}
const obj = {...obj1,...obj2};&amp;#x2F;&amp;#x2F;{a:1,b:1}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;三、关于拼接字符串的吐槽&lt;/h2&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const name = &amp;#x27;小明&amp;#x27;;
const score = 59;
let result = &amp;#x27;&amp;#x27;;
if(score &amp;gt; 60){
  result = &amp;#96;${name}的考试成绩及格&amp;#96;; 
}else{
  result = &amp;#96;${name}的考试成绩不及格&amp;#96;; 
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;像你们这样用ES6字符串模板，还不如不用，你们根本不清楚在&lt;code&gt;${}&lt;/code&gt;中可以做什么操作。在&lt;code&gt;${}&lt;/code&gt;中可以放入任意的JavaScript表达式，可以进行运算，以及引用对象属性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const name = &amp;#x27;小明&amp;#x27;;
const score = 59;
const result = &amp;#96;${name}${score &amp;gt; 60?&amp;#x27;的考试成绩及格&amp;#x27;:&amp;#x27;的考试成绩不及格&amp;#x27;}&amp;#96;;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;四、关于if中判断条件的吐槽&lt;/h2&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;if(
    type == 1 ||
    type == 2 ||
    type == 3 ||
    type == 4 ||
){
   &amp;#x2F;&amp;#x2F;...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6中数组实例方法&lt;code&gt;includes&lt;/code&gt;会不会使用呢？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const condition = [1,2,3,4];

if( condition.includes(type) ){
   &amp;#x2F;&amp;#x2F;...
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;五、关于列表搜索的吐槽&lt;/h2&gt;
&lt;p&gt;在项目中，一些没分页的列表的搜索功能由前端来实现，搜索一般分为精确搜索和模糊搜索。搜索也要叫过滤，一般用&lt;code&gt;filter&lt;/code&gt;来实现。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = [1,2,3,4,5];
const result = a.filter( 
  item =&amp;gt;{
    return item === 3
  }
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果是精确搜索不会用ES6中的&lt;code&gt;find&lt;/code&gt;吗？性能优化懂么，&lt;code&gt;find&lt;/code&gt;方法中找到符合条件的项，就不会继续遍历数组。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const a = [1,2,3,4,5];
const result = a.find( 
  item =&amp;gt;{
    return item === 3
  }
)
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;六、关于扁平化数组的吐槽&lt;/h2&gt;
&lt;p&gt;一个部门JSON数据中，属性名是部门id，属性值是个部门成员id数组集合，现在要把有部门的成员id都提取到一个数组集合中。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const deps = {
&amp;#x27;采购部&amp;#x27;:[1,2,3],
&amp;#x27;人事部&amp;#x27;:[5,8,12],
&amp;#x27;行政部&amp;#x27;:[5,14,79],
&amp;#x27;运输部&amp;#x27;:[3,64,105],
}
let member = [];
for (let item in deps){
    const value = deps[item];
    if(Array.isArray(value)){
        member = [...member,...value]
    }
}
member = [...new Set(member)]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;获取对象的全部属性值还要遍历吗？&lt;code&gt;Object.values&lt;/code&gt;忘记了吗？还有涉及到数组的扁平化处理，为啥不用ES6提供的&lt;code&gt;flat&lt;/code&gt;方法呢，还好这次的数组的深度最多只到2维，还要是遇到4维、5维深度的数组，是不是得循环嵌套循环来扁平化？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const deps = {
    &amp;#x27;采购部&amp;#x27;:[1,2,3],
    &amp;#x27;人事部&amp;#x27;:[5,8,12],
    &amp;#x27;行政部&amp;#x27;:[5,14,79],
    &amp;#x27;运输部&amp;#x27;:[3,64,105],
}
let member = Object.values(deps).flat(Infinity);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中使用&lt;code&gt;Infinity&lt;/code&gt;作为&lt;code&gt;flat&lt;/code&gt;的参数，使得无需知道被扁平化的数组的维度。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;补充&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;flat&lt;/code&gt;方法不支持IE浏览器。&lt;/p&gt;
&lt;h2&gt;七、关于获取对象属性值的吐槽&lt;/h2&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const name = obj &amp;amp;&amp;amp; obj.name;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6中的可选链操作符会使用么？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const name = obj?.name;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;八、关于添加对象属性的吐槽&lt;/h2&gt;
&lt;p&gt;当给对象添加属性时，如果属性名是动态变化的，该怎么处理。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;let obj = {};
let index = 1;
let key = &amp;#96;topic${index}&amp;#96;;
obj[key] = &amp;#x27;话题内容&amp;#x27;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为何要额外创建一个变量。不知道ES6中的对象属性名是可以用表达式吗？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;let obj = {};
let index = 1;
obj[&amp;#96;topic${index}&amp;#96;] = &amp;#x27;话题内容&amp;#x27;;
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;九、关于输入框非空的判断&lt;/h2&gt;
&lt;p&gt;在处理输入框相关业务时，往往会判断输入框未输入值的场景。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;if(value !== null &amp;amp;&amp;amp; value !== undefined &amp;amp;&amp;amp; value !== &amp;#x27;&amp;#x27;){
    &amp;#x2F;&amp;#x2F;...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ES6中新出的空值合并运算符了解过吗，要写那么多条件吗？&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;if((value??&amp;#x27;&amp;#x27;) !== &amp;#x27;&amp;#x27;){
  &amp;#x2F;&amp;#x2F;...
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;十、关于异步函数的吐槽&lt;/h2&gt;
&lt;p&gt;异步函数很常见，经常是用 Promise 来实现。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const fn1 = () =&amp;gt;{
  return new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve(1);
    }, 300);
  });
}
const fn2 = () =&amp;gt;{
  return new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
      resolve(2);
    }, 600);
  });
}
const fn = () =&amp;gt;{
   fn1().then(res1 =&amp;gt;{
      console.log(res1);&amp;#x2F;&amp;#x2F; 1
      fn2().then(res2 =&amp;gt;{
        console.log(res2)
      })
   })
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;吐槽&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果这样调用异步函数，不怕形成地狱回调啊！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;改进&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const fn = async () =&amp;gt;{
  const res1 = await fn1();
  const res2 = await fn2();
  console.log(res1);&amp;#x2F;&amp;#x2F; 1
  console.log(res2);&amp;#x2F;&amp;#x2F; 2
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;补充&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但是要做并发请求时，还是要用到&lt;code&gt;Promise.all()&lt;/code&gt;。&lt;/p&gt;
&lt;pre class=&quot;prettyprint&quot;&gt;&lt;code&gt;const fn = () =&amp;gt;{
   Promise.all([fn1(),fn2()]).then(res =&amp;gt;{
       console.log(res);&amp;#x2F;&amp;#x2F; [1,2]
   }) 
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果并发请求时，只要其中一个异步函数处理完成，就返回结果，要用到&lt;code&gt;Promise.race()&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;十一、后续&lt;/h2&gt;
&lt;p&gt;欢迎来对以上十点leader的吐槽进行反驳，你的反驳如果有道理的，下次代码评审会上，我替你反驳。&lt;/p&gt;
&lt;p&gt;此外以上的整理内容有误的地方，欢迎在评论中指正，万分感谢。&lt;/p&gt;
&lt;p&gt;如果你还有其它想吐槽的，也非常欢迎在评论中留下你的吐槽。&lt;/p&gt;
&lt;p&gt;读完有帮助，不妨关注一下，点个赞支持一下。&lt;p&gt;
版权声明：著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
作者: 红尘炼心
原文链接：&lt;a href=&quot;https://juejin.im/post/7016520448204603423&quot;&gt;https://juejin.im/post/7016520448204603423&lt;/a&gt;
&lt;/p&gt;&lt;/p&gt;
&lt;/div&gt;</description><author>banyungong</author><pubDate>Tue, 11 Jan 2022 10:15:00 GMT</pubDate></item></channel></rss>