<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【Vuejs】952- 一文帶你了解vue2之響應(yīng)式原理

          共 10439字,需瀏覽 21分鐘

           ·

          2021-05-11 10:47

          在面試的過程中也會問到:請闡述vue2的響應(yīng)式原理?,凡是出現(xiàn)闡述或者理解,一般都是知無不言言無不盡,知道多少說多少。接下來,我來談?wù)勛约旱睦斫猓杏洸灰ケ常欢ㄒ斫庵螅米约旱恼Z言來描述出來。

          那什么是響應(yīng)式呢?響應(yīng)式就是當(dāng)對象本身(對象的增刪值)或者對象屬性(重新賦值)發(fā)生變化時,將會運行一些函數(shù),最常見的就是render函數(shù)。

          在具體實現(xiàn)上,vue用到了幾個核心部件,每一個部件都解決一個問題:

          1. Observer
          2. Dep
          3. Watcher
          4. Scheduler

          ?? Observer

          Observer要實現(xiàn)的目標非常簡單,就是把一個普通的對象轉(zhuǎn)換為響應(yīng)式的對象。

          為了實現(xiàn)這一點,Observer把對象的每個屬性通過Object.defineProperty轉(zhuǎn)換為帶有gettersetter的屬性,這樣一來,我們訪問或設(shè)置屬性時,會分別調(diào)用getter和setter方法,vue就有機會做一些別的事情。

          image.png

          Observer是vue內(nèi)部的構(gòu)造器,我們可以通過Vue提供的靜態(tài)方法Vue.observable(object)間接的使用該功能。

          「示例」

          <body>
              <script src="./vue.min.js"></script>
              <script>
                let obj = {
                  name:"法醫(yī)",
                  age:100,
                  like:{
                    a:"琴",
                    b:"棋"
                  },
                  character:[{
                    c:"性格好"
                  },{
                    d:"真帥哦"
                  }]
                }
                Vue.observable(obj);
              
          </script>

            </body>

          「運行結(jié)果」

          輸出結(jié)果中的...代表數(shù)據(jù)是響應(yīng)式的,Invoke property getter表示調(diào)用了屬性的getter方法。如果對象中還存在對象,那么它會深度遞歸遍歷,讓所有的數(shù)據(jù)都是響應(yīng)式的數(shù)據(jù)。

          image.png

          如果說在組件當(dāng)中,配置中的data也會返回一個響應(yīng)式數(shù)據(jù),這一過程在組件生命周期中發(fā)生在beforeCreate之后,created之后

          Observer在具體實現(xiàn)上,它會遞歸遍歷對象的所有屬性,以完成數(shù)據(jù)響應(yīng)式的轉(zhuǎn)換。如果說一個屬性一開始并不存在于對象中,是后面添加上的,那么這種屬性是檢測不到的,所以像之前使用obj.e = 3新增一個e:3也是檢測不到的,因為之前對象中沒有。但是到了vue3,使用了proxy,那就可以檢測到了。因此在vue2中提供了$set$delete兩個實例方法,我們可以通過這兩個實例方法對已有響應(yīng)式對象添加或刪除屬性。

          「示例」

          通過對昵稱的刪除和年齡的添加,對比$set$deletedeleteset


          <body>
              <div id="app">
                <p>昵稱:{{obj.name}}</p>
                <p>年齡:{{obj.age}}</p>
                   <!-- 首先通過 delete obj.name 方式進行昵稱的刪除-->
                <button @click="delete obj.name">刪除昵稱</button>
                <button>添加年齡</button>
              </div>


              <script src="./vue.min.js"></script>
              <script>
               let vm = new Vue({
                  el:"#app",
                  data(){
                    return{
                      obj:{
                        name:"法醫(yī)"
                      }
                    }
                  }
                })
              
          </script>

            </body>

          「運行結(jié)果」

          GIF.gif

          從運行結(jié)果來看,在沒有點擊刪除昵稱按鈕之前vm.obj輸出的name是響應(yīng)式數(shù)據(jù),點擊刪除昵稱按鈕之后再次打印vm.obj此時數(shù)據(jù)已經(jīng)被刪除,但是頁面上昵稱法醫(yī)并未刪除,vue收不到屬性被刪除的通知,因為delete obj.name是不會被檢測到的

          接下來使用$delete進行昵稱的刪除操作:

          <body>
              <div id="app">
                <p>昵稱:{{obj.name}}</p>
                <p>年齡:{{obj.age}}</p>
                <!-- 使用 $delete 刪除昵稱-->
                <button @click="$delete(obj,'name')">刪除昵稱</button>
                <button>添加年齡</button>
              </div>


              <script src="./vue.min.js"></script>
              <script>
               let vm = new Vue({
                  el:"#app",
                  data(){
                    return{
                      obj:{
                        name:"法醫(yī)"
                      }
                    }
                  }
                })
              
          </script>

            </body>

          「運行結(jié)果」

          GIF222.gif

          當(dāng)使用$delete的時候vue就會收到通知了,進行昵稱刪除操作,頁面也會及時響應(yīng)。

          同理,我們來看看$setset

           <body>
              <div id="app">
                <p>昵稱:{{obj.name}}</p>
                <p>年齡:{{obj.age}}</p>
                <!--  -->
                <button @click="$delete(obj,'name')">刪除昵稱</button>
                 <!-- 使用 obj.age=100 添加年齡-->
                <button @click="obj.age=100">添加年齡</button>
              </div>


              <script src="./vue.min.js"></script>
              <script>
               let vm = new Vue({
                  el:"#app",
                  data(){
                    return{
                      obj:{
                        name:"法醫(yī)"
                      }
                    }
                  }
                })
              
          </script>

            </body>

          「運行結(jié)果」

          GIF 2021-4-19 23-37-22.gif

          當(dāng)使用傳統(tǒng)方式obj.age=100向?qū)ο筇砑訉傩缘臅r候,其實可以添加成功的,只是數(shù)據(jù)并不是響應(yīng)式的,頁面上沒有顯示年齡。

          接下來就使用$set添加屬性:

          <body>
              <div id="app">
                <p>昵稱:{{obj.name}}</p>
                <p>年齡:{{obj.age}}</p>
                <!-- 使用 $set 添加年齡 -->
                <button @click="$delete(obj,'name')">刪除昵稱</button>
                <button @click="$set(obj,'age','100')">添加年齡</button>
              </div>


              <script src="./vue.min.js"></script>
              <script>
               let vm = new Vue({
                  el:"#app",
                  data(){
                    return{
                      obj:{
                        name:"法醫(yī)"
                      }
                    }
                  }
                })
              
          </script>

            </body>

          「運行結(jié)果」

          GIF 2021-4-19 23-41-57.gif

          一目了然,使用$set添加的屬性是響應(yīng)式的,age:(...)三個點很明顯。

          以上就是針對對象的檢測,那么數(shù)組呢?數(shù)組又是怎樣檢測的呢?Object和Array的變化檢測處理方式是不同的。

          對于數(shù)組,vue會更改它的隱式原型,之所以這樣做,是因為vue需要監(jiān)聽那些可能改變數(shù)組內(nèi)容的 方法。

          image.png

          總之,Observer的目標,就是要讓一個對象,它的屬性的讀取、賦值,內(nèi)部數(shù)組的變化都要能夠被vue檢測到,這樣才能讓數(shù)據(jù)轉(zhuǎn)換為響應(yīng)式數(shù)據(jù)。

          ?? Dep

          現(xiàn)在有兩個問題沒有解決,就是讀取屬性時要做什么事情?屬性變化時要做什么事情?這個問題就需要Dep來解決。

          Dep的全稱是Dependency,表示依賴的意思,

          Vue會為響應(yīng)式對象中的每個屬性、對象本身、數(shù)組本身創(chuàng)建一個Dep實例,每個Dep實例都有能力做以下兩件事:

          • 記錄依賴:是誰在用我
          • 派發(fā)更新:我變了,我要通知那些用到我的人

          當(dāng)讀取響應(yīng)式對象的某個屬性時,它會進行依賴收集:有人用到了我

          當(dāng)改變某個屬性時,它會派發(fā)更新:那些用我的人聽好了,我變了

          image.png

          Watcher

          現(xiàn)在又有一個問題,就是Dep如何知道是誰在用我?

          要解決這個問題,需要依靠另一個東西,就是Watcher

          當(dāng)某個函數(shù)執(zhí)行的過程中,用到了響應(yīng)式數(shù)據(jù),響應(yīng)式數(shù)據(jù)是無法知道是哪個函數(shù)在用自己,因此,vue通過一種巧妙的辦法來解決這個問題:

          我們不要直接執(zhí)行函數(shù),而是把函數(shù)交給一個叫做watcher的東西去執(zhí)行,watcher是一個對象,每個這樣的函數(shù)執(zhí)行時都應(yīng)該創(chuàng)建一個watcher,通過watcher去執(zhí)行。watcher會創(chuàng)建一個全局變量,讓全局變量記錄當(dāng)前負責(zé)執(zhí)行的watcher等于自己,然后再去執(zhí)行函數(shù),在函數(shù)執(zhí)行的過程中,如果發(fā)生了依賴記錄dep.depend(),那么Dep就會把這個全局變量記錄下來,表示:有一個watcher用到了我這個屬性

          當(dāng)Dep進行派發(fā)更新時,它會通知之前記錄的所有watcher:我變了

          image.png

          每一個vue組件實例,都至少對應(yīng)一個watcher,該watcher中記錄了該組件的render函數(shù)。

          watcher首先會把render函數(shù)運行一次以收集依賴,于是那些在render中用到的響應(yīng)式數(shù)據(jù)就會記錄這個watcher。

          當(dāng)數(shù)據(jù)變化時,dep就會通知該watcher,而watcher將重新運行render函數(shù),從而讓界面重新渲染,同時重新記錄當(dāng)前的依賴。

          ?? Scheduler

          現(xiàn)在還剩最后一個問題,就是Dep通知watcher之后,響應(yīng)數(shù)據(jù)又多次改變,造成watcher執(zhí)行重復(fù)運行對應(yīng)函數(shù),就有可能導(dǎo)致函數(shù)頻繁運行,從而導(dǎo)致效率低下

          試想,如果一個交給watcher的函數(shù),它里面用到了屬性a、b、c、d,那么a、b、c、d屬性都會記錄依賴,于是下面的代碼將會觸發(fā)4次更新:

          state.a = "new data";
          state.b = "new data";
          state.c = "new data";
          state.d = "new data";

          這樣肯定是不行的,因此,watcher收到派發(fā)更新的通知后,它不會立即執(zhí)行對應(yīng)render函數(shù),當(dāng)然不僅僅是render函數(shù),還有可能是其它的函數(shù),而是把自己交給一個叫調(diào)度器的東西,在調(diào)度器里面有個隊列,可以認為是一個數(shù)組,這個隊列數(shù)組中記錄了當(dāng)前要運行哪些watcher,調(diào)度器維護一個執(zhí)行隊列,在隊列中同一個watcher只會存在一次,隊列中的watcher不是立即執(zhí)行,它會通過一個叫做nextTick的工具方法,把這些需要執(zhí)行的watcher放入到事件循環(huán)的微隊列中,nextTick的具體做法是通過Promise完成的,nextTick其實就是一個函數(shù)

          nextTick((fn)=>{
              Promise.resolve().then(fn);//通過這種方式就跑到微隊列中去了
          })

          也就是說,當(dāng)響應(yīng)式數(shù)據(jù)變化時,render函數(shù)的執(zhí)行是異步的,并且在微隊列中

          ?? 總體流程圖

          image.png

          我們簡單過一遍這個流程圖:

          1. 原始對象通過Observer將轉(zhuǎn)換成一個響應(yīng)式的對象,具有gettersetter方法,然后就靜靜等待著。
          2. 突然有一天,雷雨交加,有一個render函數(shù)要執(zhí)行,但不是直接就執(zhí)行了,而是交給watcher來執(zhí)行,watcher通過設(shè)置全局變量的方式讀取數(shù)據(jù),因為讀取了數(shù)據(jù),所以會觸發(fā)響應(yīng)式對象的getter,隨后getter會從全局變量的位置讀取到當(dāng)前正在讀取的watcher并把watcher收集到Dep中。
          3. 通過以上步驟頁面就會被渲染出來了。
          4. 又是突然的一天哈,風(fēng)和日麗,我觸發(fā)了一個按鈕或者事件,不管干了什么,反正是數(shù)據(jù)改變了,進行新的步驟——派發(fā)更新,隨后通知watcher,我變了哦,你給我馬上搞定這件事情,但是watcher并不是立即就執(zhí)行的,因為數(shù)據(jù)變動有時候不是一個,而是很多,立即執(zhí)行的話會重復(fù)執(zhí)行很多render函數(shù)或者其它數(shù)據(jù)變動的函數(shù),執(zhí)行效率會變低。然而watcher把自己交給調(diào)度器Scheduler
          5. 調(diào)度器會把watcher添加到隊列中,當(dāng)然在隊列中也不會執(zhí)行的,而是將隊列交給nextTick隊列,nextTick里面的函數(shù)全是在微隊列的,等同步代碼執(zhí)行完成后,會異步地執(zhí)行函數(shù)fn1、fn2、watcher等等,這一步相當(dāng)于重新執(zhí)行了watcher,然后又重新執(zhí)行了render函數(shù),就這樣地循環(huán)往復(fù)。

          ?? 好了, 以上就是我今天的分享,大家對于vue2響應(yīng)式原理還有什么問題可以在評論區(qū)討論鴨~

          記得點贊 ?? 支持一下哦~ ??

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計模式 重溫系列(9篇全)
          4. 正則 / 框架 / 算法等 重溫系列(16篇全)
          5. Webpack4 入門(上)|| Webpack4 入門(下)
          6. MobX 入門(上) ||  MobX 入門(下)
          7. 120+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點擊“閱讀原文”查看 120+ 篇原創(chuàng)文章

          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大香蕉视频伊人在线 | 免费性爱视频网站 | 人人免费人人摸 | 天天噪天天射天天拍 | 黄色视频直接看 |