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
鉤子: 組件已更新
- 執(zhí)行
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é)點
tagName
與key
不變缔俄, 對比props
,繼續(xù)遞歸遍歷子樹- 對比屬性(對比新舊屬性列表):
- 舊屬性是否存在與新屬性列表中
- 都存在的是否有變化
- 是否出現(xiàn)舊列表中沒有的新屬性
- 對比屬性(對比新舊屬性列表):
-
tagName
和key
值變化了,則直接替換成新節(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)載請注明出處。