Vue2.0源碼學(xué)習(xí)1:開(kāi)發(fā)環(huán)境的搭建和響應(yīng)式原理的實(shí)現(xiàn)

前言

最近參與一次關(guān)于Vue2.0的集中學(xué)習(xí)。主要學(xué)習(xí)了以下內(nèi)容。

  • 響應(yīng)式原理的實(shí)現(xiàn)
  • vue的模板編譯
  • 依賴(lài)收集和異步更新機(jī)制
  • Vue dom算法的實(shí)現(xiàn)

現(xiàn)在對(duì)學(xué)習(xí)內(nèi)容進(jìn)行一次集中總結(jié)整理,方便以后的學(xué)習(xí)。

開(kāi)發(fā)環(huán)境的搭建

rollup

Rollup 是一個(gè) JavaScript 模塊打包器,可以將小塊代碼編譯成大塊復(fù)雜的代碼, rollup.js更專(zhuān)注于Javascript類(lèi)庫(kù)打包。

  • 安裝rollup

    npm install @babel/preset-env @babel/core rollup rollup-plugin-babel rollup-plugin-serve cross-env -D

  • 配置文件rollup.config.js

/**
 * rollup 的配置文件
 */
import babel from 'rollup-plugin-babel';
import serve from 'rollup-plugin-serve';
export default {
    //入口
    input: './src/index.js',
    output: {
        format: 'umd', // 模塊化類(lèi)型
        file: 'dist/vue.js', 
        name: 'Vue', // 打包后的全局變量的名字
        sourcemap: true //源碼映射
    },
    plugins: [
        babel({
            exclude: 'node_modules/**' //忽略打包文件
        }),
        process.env.ENV === 'development'?serve({
            open: true,
            openPage: '/public/index.html',
            port: 8000,
            contentBase: ''
        }):null
    ]
}
  • 腳本文件
    在package.json 中添加
 "scripts": {
    "build:dev": "rollup -c",//打包
    "serve": "rollup -c -w" //啟動(dòng)
  },
  • 啟動(dòng)
    在控制臺(tái)執(zhí)行npm run serve
image

響應(yīng)式原理的實(shí)現(xiàn)

原理

Vue 2.0依賴(lài)Object.defineProperty 數(shù)據(jù)劫持來(lái)實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式的。

var obj={
  a:1,
  b:2,
  c:3,
  d:[1],
}
Object.keys(obj).forEach(key=>{
  let value=obj[key];
  
  Object.defineProperty(obj,key,{

    get(){
       console.log(`獲取obj${key}值${value}`); //獲取obja值1
       return value;
    },
    set(newValue){
        console.log(`obj 中${key} 被賦值 ${newValue}`) //obj 中b 被賦值 3

    }
})

})
obj.a;
obj.b=3
obj.d.push(2); //不能對(duì)push 方法進(jìn)行數(shù)據(jù)劫持

在上述實(shí)例 我們發(fā)現(xiàn)可以通過(guò)Object.defineProperty對(duì)數(shù)據(jù)進(jìn)行劫持,進(jìn)行響應(yīng)式操作梳理,但也有如下問(wèn)題。

  • 如果對(duì)象事多層嵌套,需要進(jìn)行遞歸處理,否則不能監(jiān)聽(tīng)到數(shù)據(jù)的更新。所以我們?cè)陂_(kāi)發(fā)中避免在 data中數(shù)據(jù)層級(jí)太深,影響性能。

  • 對(duì)數(shù)組中的方法不能進(jìn)行數(shù)據(jù)劫持。push,pop,unshift,shift,splice,reverse,sort,需要進(jìn)行特殊處理。

實(shí)現(xiàn)

Observer

/**
 * 數(shù)據(jù)觀測(cè)
 */
import {isObject} from '../util/index.js'
import {newArrayProto} from './array'
import Dep from './dep.js'
 class Observer{
       constructor(data)
       {
            //將 this 掛載在data 上 可以使用ob 調(diào)用方法
             Object.defineProperty(data,'_ob_',{
                   enumerable:false,
                   configurable:false,
                   value:this
             })
             if(data instanceof Array)
             {
                 //數(shù)組是 [].__proto__=Array.prototype
                 //更改需要觀測(cè)數(shù)組的原型鏈
                 data.__proto__=newArrayProto;
                 this.observeArray(data);
             }
             else{
                 //監(jiān)測(cè)對(duì)象
                  this.walk(data);
             }
             
       }
       /**
        * 觀測(cè)數(shù)組
        * @param {*} data 
        */
       observeArray(data){

          for(let i=0;i<data.length;i++)
          {
              observe(data[i])
          }

       }
       /**
        * 遍歷監(jiān)測(cè)對(duì)象
        * @param {*} data 
        */
       walk(data){
          //  Object.keys 不可遍歷不可枚舉類(lèi)型 所以 _ob_ 不會(huì)被遍歷
         Object.keys(data).forEach(key=>{
             defineReactive(data,key,data[key])
         })
       }
 }
 /**
  * 數(shù)據(jù)監(jiān)測(cè)
  * @param {*} data 
  * @param {*} key 
  * @param {*} value 
  */
 function defineReactive(data,key,value){
      let dep=new Dep();
       observe(value);// value 還是對(duì)象,遞歸
       Object.defineProperty(data,key,{
           get(){
               return value;

           },
           set(newValue){
               if(newValue===value) return;
               //對(duì)于賦值的如果是對(duì)象 進(jìn)行響應(yīng)式監(jiān)測(cè)
               observe(newValue);
               value=newValue;
           }
       })

 }
 /**
  * 觀測(cè)數(shù)據(jù)方法
  * @param {*} data 
  */

 export function observe(data)
 {
       //不是對(duì)象
       if(!isObject(data)) return;
       //說(shuō)明已經(jīng)被觀測(cè)
       if(data._ob_ instanceof Observer) return;
       return new Observer(data);
 }


注意

  • 如果觀察對(duì)象還是一個(gè)對(duì)象,需要遞歸進(jìn)行observe。
  • 觀測(cè)后該對(duì)象加上ob標(biāo)記,指向當(dāng)前class Observer的實(shí)例。既可以方便調(diào)用實(shí)例中的方法,又可以標(biāo)識(shí)當(dāng)前對(duì)象已經(jīng)被觀測(cè),避免重復(fù)觀測(cè)。
  • 如果檢測(cè)到數(shù)據(jù)類(lèi)型是數(shù)組,修改被觀測(cè)數(shù)組上的原型鏈,具體方法詳看如下代碼

數(shù)組響應(yīng)式處理

export let newArrayProto=Object.create(Array.prototype);
 let oldMethods=Array.prototype;
 //需要重寫(xiě)數(shù)組的方法 
 let methods=[
       'push',
       'pop',
       'unshift',
       'shift',
       'splice',
       'sort',
       'reverce'
 ];
 methods.forEach((method)=>{
       newArrayProto[method]=function(...args){
              //依然執(zhí)行原數(shù)組原型上的方法
              let result= oldMethods[method].call(this,...args);
              let insered=null,//新增的元素
                  ob=this._ob_
              switch(method){
                   case 'push':
                   case 'unshift':
                       insered=args;
                       break;
                    case 'splice':
                        insered=args.splice(2);
                        break;
                    default:
                        break;
                   
              }
              //對(duì)于新增元素進(jìn)行觀測(cè)
              insered && ob.observeArray(insered);
              return result;
       }
 })

思路

  • 創(chuàng)建新的數(shù)組原型對(duì)象。

    let newArrayProto=Object.create(Array.prototype);

  • 重寫(xiě)原型對(duì)象的7個(gè)方法( push,pop,unshift,shift,splice,reverse,sort),這七個(gè)方法執(zhí)行時(shí)還是要調(diào)用數(shù)組原型上的方法,同時(shí)
    也可以實(shí)現(xiàn)觸發(fā)這七個(gè)方法,觸發(fā)更新。需要注意的是push,unshift,splice三個(gè)方法會(huì)新增數(shù)組數(shù)據(jù),因此也要對(duì)新增數(shù)據(jù)進(jìn)行響應(yīng)式觀測(cè)。

  • 修改數(shù)組原型鏈

         if(data instanceof Array)
             {
                 //數(shù)組是 [].__proto__=Array.prototype
                 //更改需要觀測(cè)數(shù)組的原型鏈
                 data.__proto__=newArrayProto;
                 this.observeArray(data);
             }

調(diào)用 initState

在是生命周期 beforeCreate 和 created之間調(diào)用,進(jìn)行數(shù)據(jù)響應(yīng)式處理,然后再進(jìn)行模板編譯和掛載($mount),下一節(jié)總結(jié)在Vue.prototype.mount 實(shí)現(xiàn)的功能。

 Vue.prototype._init=function(options){
             const vm=this;
             //將參數(shù)掛載到 vm 上
             vm.$options = mergeOptions(vm.constructor.options,options);
             callHook(vm,'beforeCreate');
             initState(vm);
             callHook(vm,'created');
            if(vm.$options.el)
            {
                 this.$mount(vm.$options.el);
            }
        }

github地址

https://github.com/yuxuewen/vue2.0_source.git

結(jié)語(yǔ)

以上是總結(jié)的vue2.0響應(yīng)式原理的實(shí)現(xiàn),熟悉源碼并非是真正自己去實(shí)現(xiàn)一個(gè)Vue,而是通過(guò)作者的設(shè)計(jì)思路來(lái)拓展我們的視野,對(duì)平時(shí)開(kāi)發(fā)有很大意義。本人前端小白,如果錯(cuò)誤,請(qǐng)諒解,并歡迎批評(píng)指正。
掘金地址:https://juejin.im/user/5efd45a1f265da22f511c7f3/posts

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容