vue響應式原理源碼:帶你一步精通vue

學過 vue 如果不瞭解響應式的原理,怎麼能說自己熟練使用 vue,要是沒有寫過一個簡易版的 vue 怎麼能說自己精通vue,這篇文章通過300多行代碼,帶你寫一個簡易版的 vue,主要實現 vue 數據響應式 (數據劫持結合發佈者-訂閱者)、數組的變異方法編譯指令數據的雙向綁定的功能。

本文需要有一定 vue 基礎,並不適合新手學習。在文章最後附有vue學習鏈接。因爲本文用到了很多知識點,在文章最後也有相關知識點鏈接和 vue 源碼地址,大家自己到文章最後看哦~

文章較長,且有些難度,建議大家,找一個安靜的環境,並在看之前沐浴更衣,保持編程的神聖感。下面是實現的簡易版 vue 的源碼地址,一定要先下載下來!因爲文章中的並非全部的代碼。

github 源碼地址:https://github.com/young-monk/myvue.git

在開始學習之前,我們先來了解一下什麼是 MVVM ,什麼是數據響應式。

我們都知道 vue 是一個典型的 MVVM 思想,由數據驅動視圖。

那麼什麼是 MVVM 思想呢?

MVVM 是 Model-View-ViewModel,是把一個系統分爲了模型( model )、視圖( view )和 view-model 三個部分。

vue 在 MVVM 思想下,view 和model 之間沒有直接的聯繫,但是 view 和 view-model 、model 和 view-model之間時交互的,當 view 視圖進行 dom 操作等使數據發生變化時,可以通過 view-model 同步到 model 中,同樣的 model 數據變化也會同步到 view 中。

那麼實現數據響應式都有什麼方法呢?

1、發佈者-訂閱者模式:當一個對象(發佈者)狀態發生改變時,所有依賴它的對象(訂閱者)都會得到通知。通俗點來講,發佈者就相當於報紙,而訂閱者相當於讀報紙的人。

2、髒值檢查:通過存儲舊的數據,和當前新的數據進行對比,觀察是否有變更,來決定是否更新視圖。angular.js就是通過髒值檢查的方式。最簡單的實現方式就是通過 setInterval() 定時輪詢檢測數據變動,但這樣無疑會增加性能,所以,angular 只有在指定的事件觸發時進入髒值檢測。

3、數據劫持:通過Object.defineProperty()來劫持各個屬性的settergetter,在數據變動時觸發相應的方法。

vue 是如何實現數據響應式的呢?

vue.js 則是通過數據劫持結合發佈者-訂閱者模式的方式。

(圖片來自網絡)

當執行 new Vue() 時,Vue 就進入了初始化階段,vue會對指令進行解析(初始化視圖,增加訂閱者,綁定更新函數),同時通過Obserber 會遍歷數據並通過Object.defineProperty 的 getter 和 setter 實現對的監聽, 當數據發生變化的時候,Observer 中的 setter 方法被觸發,setter 會立即調用Dep.notify(),Dep 開始遍歷所有的訂閱者,並調用訂閱者的 update 方法,訂閱者收到通知後對視圖進行相應的更新。

我來依次介紹一下圖中的重要的名詞

Observer :數據監聽器,能夠對數據對象的所有屬性進行監聽,如有變動可拿到最新值並通知訂閱者,內部採用Object.definePropertygettersetter 來實現

Compile :指令解析器,它的作用對每個元素節點的指令進行掃描和解析,根據指令模板替換數據,以及綁定相應的更新函數

Dep :訂閱者收集器或者叫消息訂閱器都可以,它在內部維護了一個數組,用來收集訂閱者,當數據改變觸發 notify 函數,再調用訂閱者的 update 方法

Watcher :訂閱者,它是連接 Observer 和 Compile 的橋樑,收到消息訂閱器的通知,更新視圖

Updater:視圖更新

所以我們想要實現一個vue響應式,需要完成 數據劫持依賴收集發佈者訂閱者模式

下面我來介紹我模仿源碼實現的功能

  1. 數據的響應式、雙向綁定,能夠對數據對象的所有屬性進行監聽,如有變動可拿到最新值並通知訂閱者
  2. 解析 vue 常用的指令 v-html,v-text,v-bind,v-on,v-model,包括( @:
  3. 數組變異方法的處理
  4. 在 vue 中使用 this 訪問或改變 data 中的數據

我們想要完成以上的功能,需要實現如下類和方法

  1. 實現Observer類:對所有的數據進行監聽
  2. 實現 array 工具方法:對變異方法的處理
  3. 實現 Dep 類:維護訂閱者
  4. 實現 Watcher 類:接收 Dep 的更新通知,用於更新視圖
  5. 實現 Compile 類:用於對指令進行解析
  6. 實現一個 compileUtils 工具方法,實現通過指令更新視圖、綁定更新函數Watcher
  7. 實現this.datathis.data代理:實現對 this.data 代理,可以直接在vue中使用 this 獲取當前數據

我是使用了webpack作爲構建工具來協同開發的,所以在我實現的vue響應式中會用到ES6模塊化,webpack的相關知識。知識鏈接在文章最後。

1、實現 Observer 類

我們都知道要用 Obeject.defineProperty() 來監聽屬性的數據變化,我們需要對 Observer 的數據對象進行遞歸遍歷,包括子屬性對象的屬性,都加上 settergetter ,這樣的話,當給這個對象的某個值賦值,就會觸發 setter,那麼就能監聽到了數據變化。當然我們在新增加數據的時候,也要對新的數據對象進行遞歸遍歷,加上 settergetter

但我們要注意數組,在處理數組時並不是把數組中的每一個元素都加上 settergetter ,我們試想一下,一個從後端返回的數組數據是非常龐大的,如果爲每個屬性都加上 settergetter ,性能消耗是十分巨大的。我們想要得到的效果和所消耗的性能不成正比,所以在數組方面,我們通過對數組的7 個變異方法來實現數據的響應式。只有通過數組變異方法來修改和刪除數組時纔會重新渲染頁面。

那麼監聽到變化之後是如何通知訂閱者來更新視圖的呢?我們需要實現一個Dep(消息訂閱器),其中有一個 notify()方法,是通知訂閱者數據發生了變化,再讓訂閱者來更新視圖。

我們怎麼添加訂閱者呢?我們可以通過 new Dep(),通過 Dep 中的 addSaubs() 方法來添加訂閱者。我們來看一下具體代碼。

我們首先需要聲明一個Observer類,在創建類的時候,我們需要創建一個消息訂閱器,判斷一下是否是數組,如果是數組,我們便改造數組,如果是對象,我們便需要爲對象的每一個屬性都加入 settergetter

import { arrayMethods } from './array' //數組變異方法處理 
class Observer {
  constructor(data) {
    //用於對數組進行處理,存放數組的觀察者watcher
    this.dep = new Dep()
    if (Array.isArray(data)) {
      //如果是數組,使用數組的變異方法
      data.__proto__ = arrayMethods
      //把數組數據添加 __ob__ 一個Observer,當使用數組變異方法時,可以更新視圖
      data.__ob__ = this
      //給數組的每一項添加數據劫持(setter/getter處理)
      this.observerArray(data)
    } else {
      //非數組數據添加數據劫持(setter/getter處理)
      this.walk(data)
    }
  }
}

在上面,我們給 data 的__proto__原型鏈重新賦值,我們來看一下 arrayMethods 是什麼,arrayMethods 是 array.js 文件中,拋出的一個新的 Array 原型

// 獲取Array的原型鏈
const arrayProto = Array.prototype;
// 重新創建一個含有對應原型的對象,在下面稱爲新Array
const arrayMethods = Object.create(arrayProto);
// 處理7個數組變異方法
['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'].forEach(ele => {
    //修改新Array的對應的方法
    arrayMethods[ele] = function () {
        // 執行數組的原生方法,完成其需要完成的內容
        arrayProto[ele].call(this, ...arguments)
        // 獲取Observer對象
        const ob = this.__ob__
        // 更新視圖
        ob.dep.notify()
    }
})
export {
    arrayMethods
}

此時呢,我們就擁有了數組的變異方法,我們還需要通過 observerArray 方法爲數組的每一項添加 getter 和setter ,注意,此時的每一項只是最外面的一層,並非遞歸遍歷。

//循環遍歷數組,爲數組每一項設置setter/getter
observerArray(items) {
    for (let i = 0; i < items.length; i++) {
      this.observer(items[i])
    }
}

如果是一個對象的話,我們就要對 對象 的每一個屬性遞歸遍歷,通過walk() 方法

walk(data) {
	//數據劫持
    if (data && typeof data === "object") {
      for (const key in data) {
        //綁定setter和getter
        this.defineReactive(data, key, data[key])
      }
    }
}

在上面的調用了 defineReactive() ,我們來看看這個方法是幹什麼的?這個方法就是設置數據劫持的,每一行都有註釋。

//數據劫持,設置 setter/getteer
  defineReactive(data, key, value) {
    //如果是數組的話,需要接受返回的Observer對象
    let arrayOb = this.observer(value)
    //創建訂閱者/收集依賴
    const dep = new Dep()
    //setter和getter處理
    Object.defineProperty(data, key, {
      //可枚舉的
      enumerable: true,
      //可修改的
      configurable: false,
      get() {
        //當 Dep 有 watcher 時, 添加 watcher
        Dep.target && dep.addSubs(Dep.target)
        //如果是數組,則添加上數組的觀察者
        Dep.target && arrayOb && arrayOb.dep.addSubs(Dep.target)
        return value
      },
      set: (newVal) => {
        //新舊數據不相等時更改
        if (value !== newVal) {
          //爲新設置的數據添加setter/getter
          arrayOb = this.observer(newVal);
          value = newVal
          //通知 dep 數據發送了變化
          dep.notify()
        }
      }
    })
  }
}

我們需要注意的是,在上面的圖解中,在 Observer 中,如果數據發生變化,會通知消息訂閱器,那麼在何時綁定消息訂閱器呢?就是在設置 settergetter 的時候,創建一個Dep,併爲Dep添加訂閱者,Dep.target && dep.addSubs(Dep.target),通過調用 dep 的 addSubs 方法添加訂閱者

2、實現 Dep

Dep是消息訂閱器,它的作用就是維護一個訂閱者數組,當數據發送變化是,通知對應的訂閱者,Dep中有一個notify()方法,作用就是通知訂閱者,數據發送了變化

// 訂閱者收集器
export default class Dep {
    constructor() {
        //管理的watcher的數組
        this.subs = []
    }
    addSubs(watcher) {
        //添加watcher
        this.subs.push(watcher)
    }
    notify() {
        //通知watcher更新dom
        this.subs.forEach(w => w.update())
    }
}

3、實現 watcher

Watcher就是訂閱者,watcher是 Observer 和 Compile 之間通信的橋樑,當數據改變時,接收到 Dep 的通知(Dep 的notify()方法),來調用自己的update()方法,觸發 Compile 中綁定的回調,達到更新視圖的目的。

import Dep from './dep'
import { complieUtils } from './utils'
export default class Watcher {
    constructor(vm, expr, cb) {
        //當前的vue實例
        this.vm = vm;
        //表達式
        this.expr = expr;
        //回調函數,更新dom
        this.cb = cb
        //獲取舊的數據,此時獲取舊值的時候,Dep.target會綁定上當前的this
        this.oldVal = this.getOldVal()
    }
    getOldVal() {
        //將當前的watcher綁定起來
        Dep.target = this
        //獲取舊數據
        const oldVal = complieUtils.getValue(this.expr, this.vm)
        //綁定完成後,將綁定的置空,防止多次綁定
        Dep.target = null
        return oldVal
    }
    update() {
        //更新函數
        const newVal = complieUtils.getValue(this.expr, this.vm)
        if (newVal !== this.oldVal || Array.isArray(newVal)) {
            //條用更新在compile中創建watcher時傳入的回調函數
            this.cb(newVal)
        }
    }
}

上面中用到了 complieUtils 中的 getValue() 方法,會在下面講,主要作用是獲取到指定表達式的值。

我們把整個流程分成兩條路線的話:

new Vue() ==> Observer數據劫持 ==> 綁定Dep ==> 通知watcher ==> 更新視圖

new Vue() ==> Compile解析模板指令 ==> 初始化視圖 和 綁定watcher

此時,我們第一條線的內容已經實現了,我們再來實現一下第二條線。

4、實現 Compile

compile主要做的事情是解析模板指令,將模板中的變量替換成數據,初始化渲染頁面視圖。同時也要綁定更新函數,添加訂閱者。

因爲在解析的過程中,會多次的操作dom,爲提高性能和效率,會先將vue實例根節點的 el 轉換成文檔碎片fragment進行解析編譯操作,解析完成,再將fragment添加回原來的真實dom節點中,文檔碎片知識點在文章最後面的知識點鏈接中。

class Complie {
    constructor(el, vm) {
        this.el = this.isNodeElement(el) ? el : document.querySelector(el);
        this.vm = vm;
        // 1、將所有的dom對象放到fragement文檔碎片中,防止重複操作dom,消耗性能
        const fragments = this.nodeTofragments(this.el)
        // 2、編譯模板
        this.complie(fragments)
        // 3、追加子元素到根元素
        this.el.appendChild(fragments)
    }  
}

我們可以看到,Complie 中主要進行了三步,第一步 nodeTofragments 是講所有的dom節點放到文檔碎片中操作,最後一步,是把解析好的dom元素,從文檔碎片重新加入到頁面中,這兩步的具體方法,大家去下載我的源碼,看一下就明白了,有註釋。我就不再解釋 了。

我們來看一下第二步,編譯模板:

 complie(fragments) {
    //獲取所有節點
    const nodes = fragments.childNodes;
    [...nodes].forEach(ele => {
        if (this.isNodeElement(ele)) {
            //1. 編譯元素節點
            this.complieElement(ele)
        } else {
            //編譯文本節點
            this.complieText(ele)
        }
        //如果有子節點,循環遍歷,編譯指令
        if (ele.childNodes && ele.childNodes.length) {
            this.complie(ele)
        }
    })
}

我們要知道,模板可能有兩種情況,一種是文本節點(含有雙大括號的插值表達式)和元素節點(含有指令)。我們獲取所有節點後對每個節點進行判斷,如果是元素節點,則用解析元素節點的方法,如果是文本節點,則調用解析文本的方法。

complieElement(node) {
    //1.獲取所有的屬性
    const attrs = node.attributes;
    //2.篩選出是屬性的
    [...attrs].forEach(attr => {
        //attr是一個對象,name是屬性名,value是屬性值
        const {name,value} = attr
        //判斷是否含有v-開頭 如:v-html
        if (name.startsWith("v-")) {
            //將指令分離  text, html, on:click
            const [, directive] = name.split("-")
            //處理on:click或bind:name的情況 on,click
            const [dirName, paramName] = directive.split(":") 
            //編譯模板
            complieUtils[dirName](node, value, this.vm, paramName)
            //刪除屬性,在頁面中的dom中不會再顯示v-html這種指令的屬性
            node.removeAttribute(name)
        } else if (name.startsWith("@")) {
            // 如果是事件處理 @click='handleClick'
            let [, paramName] = name.split('@');
            complieUtils['on'](node, value, this.vm, paramName);
            node.removeAttribute(name);
        } else if (name.startsWith(":")) {
            // 如果是事件處理 :href='...'
            let [, paramName] = name.split(':');
            complieUtils['bind'](node, value, this.vm, paramName);
            node.removeAttribute(name);
        }
    })

}

我們在編譯模板中調用了 complieUtils[dirName](node, value, this.vm, paramName)方法,這是工具類中的一個方法,用於處理指令

我們再來看看文本節點,文本節點就相對比較簡單,只需要匹配{{}}形式的插值表達式就可以了,同樣的調用工具方法,來解析。

complieText(node) {
    //1.獲取所有的文本內容
    const text = node.textContent
    //匹配{{}}
    if (/\{\{(.+?)\}\}/.test(text)) {
        //編譯模板
        complieUtils['text'](node, text, this.vm)
    }
}

上面用來這麼多工具方法,我們來看看到底是什麼

5、實現 complieUtils 工具方法

這個方法主要是對指令進行處理,獲取指令中的值,並在頁面中更新相應的值,同時我們在這裏要綁定watcher的回調函數。

我來以v-text指令來解釋,其他指令都有註釋,大家自己看。

import Watcher from './watcher'
export const complieUtils = {
    //處理text指令
    text(node, expr, vm) {
        let value;
        if (/\{\{.+?\}\}/.test(expr)) {
            //處理 {{}}
            value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
                //綁定觀察者/更新函數
                new Watcher(vm, args[1], () => {
                    //第二個參數,傳入回調函數
                    this.updater.updaterText(node, this.getContentVal(expr, vm))
                })
                return this.getValue(args[1], vm)
            })
        } else {
            //v-text
            new Watcher(vm, expr, (newVal) => {
                this.updater.updaterText(node, newVal)
            })
            //獲取到value值
            value = this.getValue(expr, vm)
        }
        //調用更新函數
        this.updater.updaterText(node, value)
    },
}

text處理函數是對dom元素的textContent進行操作的,所以有兩種情況,一種是使用v-text指令,會更新元素的textContent,另一種情況是{{}} 的插值表達式,也是更新元素的 textContent。

在此方法中我們先判斷是哪一種情況,如果是v-text指令,那麼就綁定一個watcher的回調,獲取到textContent的值,調用updater.updaterText在下面講,是更新元素的方法。如果是雙大括號的話,我們就要對其進行特殊處理,首先是將雙大括號替換成指定的變量的值,同時爲其綁定 watcher 的回調。

//通過表達式, vm獲取data中的值, person.name
getValue(expr, vm) {
    return expr.split(".").reduce((data, currentVal) => {
        return data[currentVal]
    }, vm.$data)
},

獲取 textContent 的值是用一個 reduce 函數,用法在最後面的鏈接中,因爲數據可能是 person.name 我們需要獲取到最深的對象的值。

 //更新dom元素的方法
updater: {
    //更新文本
    updaterText(node, value) {
        node.textContent = value
    }
}

updater.updaterText更新dom的方法,其實就是對 textContent 重新賦值。

我們再來將一下v-model指令,實現雙向的數據綁定,我們都知道,v-model其實實現的是 input 事件和 value 之間的語法糖。所以我們這裏同樣的監聽一下當前dom元素的 input 事件,當數據改變時,調用設置新值的方法

//處理model指令
model(node, expr, vm) {
    const value = this.getValue(expr, vm)
    //綁定watcher
    new Watcher(vm, expr, (newVal) => {
        this.updater.updaterModel(node, newVal)
    })
    //雙向數據綁定
    node.addEventListener("input", (e) => {
        //設值方法
        this.setVal(expr, vm, e.target.value)
    })
    this.updater.updaterModel(node, value)
},

這個方法同樣是通過 reduce 方法,爲對應的變量設置成新的值,此時數據改變了,會自動調用更新視圖的方法,我們在之前已經實現了。

//通過表達式,vm,輸入框的值,實現設置值,input中v-model雙向數據綁定
setVal(expr, vm, inputVal) {
    expr.split(".").reduce((data, currentVal) => {
        data[currentVal] = inputVal
    }, vm.$data)
},

6、實現vue

最後呢,我們就要來整合這些類和工具方法,在創建一個vue實例的時候,我們先獲取options中的參數,然後對起進行數據劫持和編譯模板

class Vue {
    constructor(options) {
        //獲取模板
        this.$el = options.el;
        //獲取data中的數據
        this.$data = options.data;
        //將對象中的屬性存起來,以便後續使用
        this.$options = options
        //1.數據劫持,設置setter/getter
        new Observer(this.$data)
        //2.編譯模板,解析指令
        new Complie(this.$el, this)
    }
}

此時我們想要使用 vue 中的數據,比如我們想要在 vm 對象中使用person.name, 必須用 this.$data.person.name 才能獲取到,如果我們想在vm對象中使用 this.person.name 直接修改數據,就需要代理一下 this.$data 。其實就是將當前的 this.$data 中的數據放到全局中進行監聽。

export default class Vue {
    constructor(options) {
        //...
        //1.數據劫持,設置setter/getter
        //2.編譯模板,解析指令
        if (this.$el) { //如果有模板
            //代理this
            this.proxyData(this.$data)
        }
    }
    proxyData(data) {
        for (const key in data) {
            //將當前的數據放到全局指向中
            Object.defineProperty(this, key, {
                get() {
                    return data[key];
                },
                set(newVal) {
                    data[key] = newVal
                }
            })
        }
    }
}

文章到了這裏,就實現了一個簡易版的vue,建議大家反覆學習,仔細體驗,細細品味。

在文章的最後,我通過的形式,來解答一些常見的面試題:

:什麼時候頁面會重新渲染?

:數據發生改變,頁面就會重新渲染,但數據驅動視圖,數據必須先存在,然後才能實現數據綁定,改變數據,頁面纔會重新渲染。


:什麼時候頁面不會重新渲染?

:有3種情況不會重新渲染

  1. 未經聲明和未使用的變量,修改他們,都不會重新渲染頁面

  2. 通過索引的方式和更改長度的方式更改數組,都不會重新渲染頁面

  3. 增加和刪除對象的屬性,不會重新渲染頁面


:如何使 未聲明/未使用的變量、增加/刪除對象屬性可以使頁面重新渲染?

:添加利用vm.$set/Vue.set,刪除利用vm.$delete/Vue.delete方法


:如何更改數組可以使頁面重新渲染?

:可以使用數組的變異方法(共 7 個):pushpopunshiftshiftsplicesortreverse


:數據更新後,頁面會立刻重新渲染麼?

:更改數據後,頁面不會立刻重新渲染,頁面渲染的操作是異步執行的,執行完同步任務後,纔會執行異步的

同步隊列,異步隊列(宏任務、微任務 )


:如果更改了數據,想要在頁面重新渲染後再做操作,怎麼辦?

:可以使用 vm.$nextTickVue.nextTick


:來介紹一下vm.$nextTickVue.nextTick

:我們來看個小例子就明白啦

<div id="app">{{ name }}</div>
<script>
    const vm = new Vue({
      el: '#app',
      data: {
        name: 'monk'
      }
    })
    vm.name = 'the young monk';
    console.log(vm.name); // the young monk   此時數據已更改
    console.log(vm.$el.innerHTML); // monk    此時頁面還未重新渲染
     // 1. 使用vm.$nextTick
    vm.$nextTick(() => {
        console.log(vm.$el.innerHTML); // the young monk  此時數據已更改
    })
    // 2. 使用Vue.nextTick
    Vue.nextTick(() => {
        console.log(vm.$el.innerHTML); // the young monk  此時數據已更改
    })
</script>

vm.$nextTickVue.nextTick 有什麼區別呢 ?

Vue.nextTick內部函數的this指向windowvm.$nextTick內部函數的this指向Vue實例對象。

Vue.nextTick(function () {
	console.log(this); // window
})
vm.$nextTick(function () {
	console.log(this); // vm實例
})

vm.$nextTickVue.nextTick 是通過什麼實現的呢 ?

:二者都是等頁面渲染後執行的任務,都是使用微任務。

  if(typeof Promise !== 'undefined') {
    // 微任務
    // 首先看一下瀏覽器中有沒有promise
    // 因爲IE瀏覽器中不能執行Promise
    const p = Promise.resolve();

  } else if(typeof MutationObserver !== 'undefined') {
    // 微任務
    // 突變觀察
    // 監聽文檔中文字的變化,如果文字有變化,就會執行回調
    // vue的具體做法是:創建一個假節點,然後讓這個假節點稍微改動一下,就會執行對應的函數
  } else if(typeof setImmediate !== 'undefined') {
    // 宏任務
    // 只在IE下有
  } else {
    // 宏任務
    // 如果上面都不能執行,那麼則會調用setTimeout
  }

同樣的這也是vue的一個小缺點:vue一直是等主線程執行完以後再執行渲染任務,如果主線程卡死,則永遠渲染不出來。


:利用 Object.defineProperty 實現響應式有什麼缺點?

  1. 天生就需要進行遞歸
  2. 監聽不到數組不存在的索引的改變
  3. 監聽不到數組長度的改變
  4. 監聽不到對象的增刪

vue前置知識學習鏈接:

vue源碼地址:https://github.com/vuejs/vue-devtools

webpack 知識點鏈接:https://blog.csdn.net/Newbie___/article/details/104782796

es6 模塊化相關知識鏈接:https://blog.csdn.net/Newbie___/article/details/104523097

es6 類的相關知識:https://blog.csdn.net/Newbie___/article/details/104450311

DocumentFragment 文檔碎片知識點:https://developer.mozilla.org/zh-CN/docs/Web/API/DocumentFragment

Obeject.defineProperty方法的使用:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

Array.reduce() 方法使用:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

事件循環相關知識:https://blog.csdn.net/Newbie___/article/details/105450311

vue基礎知識學習鏈接:

好似大家都在用vue,vue好在哪?https://blog.csdn.net/Newbie___/article/details/105028758

vue中插值表達式和14個vue指令詳解 https://blog.csdn.net/Newbie___/article/details/105179351

一篇文章帶你“深入”瞭解vue的計算屬性computed和偵聽屬器watch https://blog.csdn.net/Newbie___/article/details/105224887

axios在企業級vue項目中的應用(附帶axios正確打開方式)https://blog.csdn.net/Newbie___/article/details/105238226

vue的生命週期詳解:從入門到精通 https://blog.csdn.net/Newbie___/article/details/105347733

vue組件和父子組件間通信的13種方式(包含動態、異步組件和常用的prop、$emit、插槽等)https://blog.csdn.net/Newbie___/article/details/105480827

vueRouter路由詳解:從入門到精通 https://blog.csdn.net/Newbie___/article/details/105766504

vue中的vuex狀態管理:從入門到精通 https://blog.csdn.net/Newbie___/article/details/105766702
5450311

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章