2019高級前端之框架Vue篇

1. nextTick

在下次dom更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)蒲稳,可用于獲取更新后的dom狀態(tài)

  • 新版本中默認是mincrotasks, v-on中會使用macrotasks

  • macrotasks任務的實現(xiàn):

    • setImmediate / MessageChannel / setTimeout

2. 生命周期

  • _init_

    • initLifecycle/Event佛掖,往vm上掛載各種屬性
    • callHook: beforeCreated: 實例剛創(chuàng)建
    • initInjection/initState: 初始化注入和 data 響應性
    • created: 創(chuàng)建完成绘闷,屬性已經(jīng)綁定夺衍, 但還未生成真實dom
    • 進行元素的掛載: $el / vm.$mount()
    • 是否有template: 解析成render function
      • *.vue文件: vue-loader會將<template>編譯成render function
    • beforeMount: 模板編譯/掛載之前
    • 執(zhí)行render function浙芙,生成真實的dom捐迫,并替換到dom tree
    • mounted: 組件已掛載
  • update:

    • 執(zhí)行diff算法乾翔,比對改變是否需要觸發(fā)UI更新
    • flushScheduleQueue
      • watcher.before: 觸發(fā)beforeUpdate鉤子 - watcher.run(): 執(zhí)行watcher中的 notify,通知所有依賴項更新UI
    • 觸發(fā)updated鉤子: 組件已更新
  • actived / deactivated(keep-alive): 不銷毀施戴,緩存反浓,組件激活與失活

  • destroy:

    • beforeDestroy: 銷毀開始
    • 銷毀自身且遞歸銷毀子組件以及事件監(jiān)聽
      • remove(): 刪除節(jié)點
      • watcher.teardown(): 清空依賴
      • vm.$off(): 解綁監(jiān)聽
    • destroyed: 完成后觸發(fā)鉤子

上面是vue的聲明周期的簡單梳理,接下來我們直接以代碼的形式來完成vue的初始化


new Vue({})

// 初始化Vue實例
function _init() {
     // 掛載屬性
    initLifeCycle(vm) 
    // 初始化事件系統(tǒng)赞哗,鉤子函數(shù)等
    initEvent(vm) 
    // 編譯slot雷则、vnode
    initRender(vm) 
    // 觸發(fā)鉤子
    callHook(vm, 'beforeCreate')
    // 添加inject功能
    initInjection(vm)
    // 完成數(shù)據(jù)響應性 props/data/watch/computed/methods
    initState(vm)
    // 添加 provide 功能
    initProvide(vm)
    // 觸發(fā)鉤子
    callHook(vm, 'created')
        
     // 掛載節(jié)點
    if (vm.$options.el) {
        vm.$mount(vm.$options.el)
    }
}

// 掛載節(jié)點實現(xiàn)
function mountComponent(vm) {
     // 獲取 render function
    if (!this.options.render) {
        // template to render
        // Vue.compile = compileToFunctions
        let { render } = compileToFunctions() 
        this.options.render = render
    }
    // 觸發(fā)鉤子
    callHook('beforeMounte')
    // 初始化觀察者
    // render 渲染 vdom, 
    vdom = vm.render()
    // update: 根據(jù) diff 出的 patchs 掛載成真實的 dom 
    vm._update(vdom)
    // 觸發(fā)鉤子  
    callHook(vm, 'mounted')
}

// 更新節(jié)點實現(xiàn)
funtion queueWatcher(watcher) {
    nextTick(flushScheduleQueue)
}

// 清空隊列
function flushScheduleQueue() {
     // 遍歷隊列中所有修改
    for(){
        // beforeUpdate
        watcher.before()
         
        // 依賴局部更新節(jié)點
        watcher.update() 
        callHook('updated')
    }
}

// 銷毀實例實現(xiàn)
Vue.prototype.$destory = function() {
     // 觸發(fā)鉤子
    callHook(vm, 'beforeDestory')
    // 自身及子節(jié)點
    remove() 
    // 刪除依賴
    watcher.teardown() 
    // 刪除監(jiān)聽
    vm.$off() 
    // 觸發(fā)鉤子
    callHook(vm, 'destoryed')
}

3. 數(shù)據(jù)響應(數(shù)據(jù)劫持)

看完生命周期后肪笋,里面的watcher等內(nèi)容其實是數(shù)據(jù)響應中的一部分月劈。數(shù)據(jù)響應的實現(xiàn)由兩部分構(gòu)成: 觀察者( watcher )依賴收集器( Dep ),其核心是 defineProperty這個方法藤乙,它可以 重寫屬性的 get 與 set 方法猜揪,從而完成監(jiān)聽數(shù)據(jù)的改變。

  • Observe (觀察者)觀察 props 與 state
    • 遍歷 props 與 state坛梁,對每個屬性創(chuàng)建獨立的監(jiān)聽器( watcher )
  • 使用 defineProperty 重寫每個屬性的 get/set(defineReactive
    • get: 收集依賴
      • Dep.depend()
        • watcher.addDep()
    • set: 派發(fā)更新
      • Dep.notify()
      • watcher.update()
      • queenWatcher()
      • nextTick
      • flushScheduleQueue
      • watcher.run()
      • updateComponent()

大家可以先看下面的數(shù)據(jù)相應的代碼實現(xiàn)后而姐,理解后就比較容易看懂上面的簡單脈絡了。

let data = {a: 1}
// 數(shù)據(jù)響應性
observe(data)

// 初始化觀察者
new Watcher(data, 'name', updateComponent)
data.a = 2

// 簡單表示用于數(shù)據(jù)更新后的操作
function updateComponent() {
    vm._update() // patchs
}

// 監(jiān)視對象
function observe(obj) {
     // 遍歷對象划咐,使用 get/set 重新定義對象的每個屬性值
    Object.keys(obj).map(key => {
        defineReactive(obj, key, obj[key])
    })
}

function defineReactive(obj, k, v) {
    // 遞歸子屬性
    if (type(v) == 'object') observe(v)
    
    // 新建依賴收集器
    let dep = new Dep()
    // 定義get/set
    Object.defineProperty(obj, k, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
              // 當有獲取該屬性時拴念,證明依賴于該對象,因此被添加進收集器中
            if (Dep.target) {
                dep.addSub(Dep.target)
            }
            return v
        },
        // 重新設置值時尖殃,觸發(fā)收集器的通知機制
        set: function reactiveSetter(nV) {
            v = nV
            dep.nofify()
        },
    })
}

// 依賴收集器
class Dep {
    constructor() {
        this.subs = []
    }
    addSub(sub) {
        this.subs.push(sub)
    }
    notify() {
        this.subs.map(sub => {
            sub.update()
        })
    }
}

Dep.target = null

// 觀察者
class Watcher {
    constructor(obj, key, cb) {
        Dep.target = this
        this.cb = cb
        this.obj = obj
        this.key = key
        this.value = obj[key]
        Dep.target = null
    }
    addDep(Dep) {
        Dep.addSub(this)
    }
    update() {
        this.value = this.obj[this.key]
        this.cb(this.value)
    }
    before() {
        callHook('beforeUpdate')
    }
}

4. virtual dom 原理實現(xiàn)

  • 創(chuàng)建 dom 樹

  • 樹的diff丈莺,同層對比,輸出patchs(listDiff/diffChildren/diffProps)

    • 沒有新的節(jié)點送丰,返回
    • 新的節(jié)點tagNamekey不變缔俄, 對比props,繼續(xù)遞歸遍歷子樹
      • 對比屬性(對比新舊屬性列表):
        • 舊屬性是否存在與新屬性列表中
        • 都存在的是否有變化
        • 是否出現(xiàn)舊列表中沒有的新屬性
    • tagNamekey值變化了,則直接替換成新節(jié)點
  • 渲染差異

    • 遍歷patchs俐载, 把需要更改的節(jié)點取出來
    • 局部更新dom
// diff算法的實現(xiàn)
function diff(oldTree, newTree) {
     // 差異收集
    let pathchs = {}
    dfs(oldTree, newTree, 0, pathchs)
    return pathchs
}

function dfs(oldNode, newNode, index, pathchs) {
    let curPathchs = []
    if (newNode) {
        // 當新舊節(jié)點的 tagName 和 key 值完全一致時
        if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
              // 繼續(xù)比對屬性差異
            let props = diffProps(oldNode.props, newNode.props)
            curPathchs.push({ type: 'changeProps', props })
            // 遞歸進入下一層級的比較
            diffChildrens(oldNode.children, newNode.children, index, pathchs)
        } else {
              // 當 tagName 或者 key 修改了后蟹略,表示已經(jīng)是全新節(jié)點,無需再比
            curPathchs.push({ type: 'replaceNode', node: newNode })
        }
    }

     // 構(gòu)建出整顆差異樹
    if (curPathchs.length) {
            if(pathchs[index]){
                pathchs[index] = pathchs[index].concat(curPathchs)
            } else {
                pathchs[index] = curPathchs
            }
    }
}

// 屬性對比實現(xiàn)
function diffProps(oldProps, newProps) {
    let propsPathchs = []
    // 遍歷新舊屬性列表
    // 查找刪除項
    // 查找修改項
    // 查找新增項
    forin(olaProps, (k, v) => {
        if (!newProps.hasOwnProperty(k)) {
            propsPathchs.push({ type: 'remove', prop: k })
        } else {
            if (v !== newProps[k]) {
                propsPathchs.push({ type: 'change', prop: k , value: newProps[k] })
            }
        }
    })
    forin(newProps, (k, v) => {
        if (!oldProps.hasOwnProperty(k)) {
            propsPathchs.push({ type: 'add', prop: k, value: v })
        }
    })
    return propsPathchs
}

// 對比子級差異
function diffChildrens(oldChild, newChild, index, pathchs) {
        // 標記子級的刪除/新增/移動
    let { change, list } = diffList(oldChild, newChild, index, pathchs)
    if (change.length) {
        if (pathchs[index]) {
            pathchs[index] = pathchs[index].concat(change)
        } else {
            pathchs[index] = change
        }
    }

     // 根據(jù) key 獲取原本匹配的節(jié)點遏佣,進一步遞歸從頭開始對比
    oldChild.map((item, i) => {
        let keyIndex = list.indexOf(item.key)
        if (keyIndex) {
            let node = newChild[keyIndex]
            // 進一步遞歸對比
            dfs(item, node, index, pathchs)
        }
    })
}

// 列表對比挖炬,主要也是根據(jù) key 值查找匹配項
// 對比出新舊列表的新增/刪除/移動
function diffList(oldList, newList, index, pathchs) {
    let change = []
    let list = []
    const newKeys = getKey(newList)
    oldList.map(v => {
        if (newKeys.indexOf(v.key) > -1) {
            list.push(v.key)
        } else {
            list.push(null)
        }
    })

    // 標記刪除
    for (let i = list.length - 1; i>= 0; i--) {
        if (!list[i]) {
            list.splice(i, 1)
            change.push({ type: 'remove', index: i })
        }
    }

    // 標記新增和移動
    newList.map((item, i) => {
        const key = item.key
        const index = list.indexOf(key)
        if (index === -1 || key == null) {
            // 新增
            change.push({ type: 'add', node: item, index: i })
            list.splice(i, 0, key)
        } else {
            // 移動
            if (index !== i) {
                change.push({
                    type: 'move',
                    form: index,
                    to: i,
                })
                move(list, index, i)
            }
        }
    })

    return { change, list }
}

5. Proxy 相比于 defineProperty 的優(yōu)勢

  • 數(shù)組變化也能監(jiān)聽到
  • 不需要深度遍歷監(jiān)聽
let data = { a: 1 }
let reactiveData = new Proxy(data, {
    get: function(target, name){
        // ...
    },
    // ...
})

6. vue-router

  • mode
    • hash
    • history
  • 跳轉(zhuǎn)
    • this.$router.push()
    • <router-link to=""></router-link>
  • 占位
    • <router-view></router-view>

7. vuex

  • state: 狀態(tài)中心
  • mutations: 更改狀態(tài)
  • actions: 異步更改狀態(tài)
  • getters: 獲取狀態(tài)
  • modules: 將state分成多個modules,便于管理

作者:郭東東
鏈接:https://juejin.im/post/5c64d15d6fb9a049d37f9c20
來源:掘金
著作權歸作者所有状婶。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權意敛,非商業(yè)轉(zhuǎn)載請注明出處。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末膛虫,一起剝皮案震驚了整個濱河市草姻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌稍刀,老刑警劉巖撩独,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異账月,居然都是意外死亡综膀,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門局齿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剧劝,“玉大人,你說我怎么就攤上這事项炼〉F剑” “怎么了示绊?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵锭部,是天一觀的道長。 經(jīng)常有香客問我面褐,道長拌禾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任展哭,我火速辦了婚禮湃窍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘匪傍。我一直安慰自己您市,他們只是感情好,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布役衡。 她就那樣靜靜地躺著茵休,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上榕莺,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天俐芯,我揣著相機與錄音,去河邊找鬼钉鸯。 笑死吧史,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的唠雕。 我是一名探鬼主播贸营,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼岩睁!你這毒婦竟也來了莽使?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤笙僚,失蹤者是張志新(化名)和其女友劉穎芳肌,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肋层,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡亿笤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了栋猖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片净薛。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蒲拉,靈堂內(nèi)的尸體忽然破棺而出肃拜,到底是詐尸還是另有隱情,我是刑警寧澤雌团,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布燃领,位于F島的核電站,受9級特大地震影響锦援,放射性物質(zhì)發(fā)生泄漏猛蔽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一灵寺、第九天 我趴在偏房一處隱蔽的房頂上張望曼库。 院中可真熱鬧,春花似錦略板、人聲如沸毁枯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽种玛。三九已至胀糜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蒂誉,已是汗流浹背教藻。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留右锨,地道東北人括堤。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像绍移,于是被迫代替她去往敵國和親悄窃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容

  • vue理解淺談 一 理解vue的核心理念 使用vue會讓人感到身心愉悅,它同時具備angular和react的優(yōu)點...
    ambeer閱讀 24,138評論 2 18
  • 前言 Vue.js 的核心包括一套“響應式系統(tǒng)”蹂窖。 “響應式”轧抗,是指當數(shù)據(jù)改變后,Vue 會通知到使用該數(shù)據(jù)的代碼...
    NARUTO_86閱讀 37,525評論 8 86
  • 1瞬测、active-class是哪個組件的屬性横媚?嵌套路由怎么定義?答:vue-router模塊的router-lin...
    jane819閱讀 1,756評論 0 15
  • vue是什么月趟? vue是構(gòu)建數(shù)據(jù)驅(qū)動的web界面的漸進式框架灯蝴。Vue.js 的目標是通過盡可能簡單的 API 實現(xiàn)...
    九四年的風閱讀 8,713評論 2 131
  • 第五章 旅行路上的人生百態(tài)(2) 人與跳蚤臭蟲大戰(zhàn) 天黑的時候,鴻漸他們投住在一家旅館名叫“歐亞大旅...
    小河邊的依依楊柳閱讀 2,564評論 15 51