vue的第一個commit分析_如何寫文案


別再煩惱如何寫文案,掌握八大原則!

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。


為什麼寫這篇vue的分析文章?


對於天資愚鈍的前端(我)來說,閱讀源碼是件不容易的事情,畢竟有時候看源碼分析的文章都看不懂。每次看到大佬們用了1~2年的vue就能掌握原理,甚至精通源碼,再看看自己用了好幾年都還在基本的使用階段,心中總是羞愧不已。如果一直滿足於基本的業務開發,怕是得在初級水平一直待下去了吧。所以希望在學習源碼的同時記錄知識點,可以讓自己的理解和記憶更加深刻,也方便將來查閱。


目錄結構


本文以vue的第一次 commit a879ec06 作為分析版本


├── build
│   └── build.js // `rollup` 打包配置
├── dist
│   └── vue.js
├── package.json
├── src // vue源碼目錄
│   ├── compiler // 將vue-template轉化為render函數
│   │   ├── codegen.js // 遞歸ast提取指令,分類attr,style,class,並生成render函數
│   │   ├── html-parser.js // 通過正則匹配將html字符串轉化為ast
│   │   ├── index.js // compile主入口
│   │   └── text-parser.js // 編譯{{}}
│   ├── config.js // 對於vue的全局配置文件
│   ├── index.js // 主入口
│   ├── index.umd.js // 未知(應該是umd格式的主入口)
│   ├── instance // vue實例函數
│   │   └── index.js // 包含了vue實例的初始化,compile,data代理,methods代理,watch數據,執行渲染
│   ├── observer // 數據訂閱發布的實現
│   │   ├── array.js // 實現array變異方法,$set $remove 實現
│   │   ├── batcher.js // watch執行隊列的收集,執行
│   │   ├── dep.js // 訂閱中心實現
│   │   ├── index.js // 數據劫持的實現,收集訂閱者
│   │   └── watcher.js // watch實現,訂閱者
│   ├── util // 工具函數
│   │   ├── component.js
│   │   ├── debug.js
│   │   ├── dom.js
│   │   ├── env.js // nexttick實現
│   │   ├── index.js
│   │   ├── lang.js
│   │   └── options.js
│   └── vdom
│   ├── dom.js // dom操作的封裝
│   ├── h.js // 節點數據分析(元素節點,文本節點)
│   ├── index.js // vdom主入口
│   ├── modules // 不同屬性處理函數
│   │   ├── attrs.js // 普通attr屬性處理
│   │   ├── class.js // class處理
│   │   ├── events.js // event處理
│   │   ├── props.js // props處理
│   │   └── style.js // style處理
│   ├── patch.js // node樹的渲染,包括節點的加減更新處理,及對應attr的處理
│   └── vnode.js // 返回最終的節點數據
└── webpack.config.js // webpack配置

從template到html的過程分析


我們的代碼是從new Vue()開始的,Vue的構造函數如下:


constructor (options) {
// options就是我們對於vue的配置
this.$options = options
this._data = options.data
// 獲取元素html,即template
const el = this._el = document.querySelector(options.el)
// 編譯模板 -> render函數
const render = compile(getOuterHTML(el))
this._el.innerHTML = ''
// 實例代理data數據
Object.keys(options.data).forEach(key => this._proxy(key))
// 將method的this指向實例
if (options.methods) {
Object.keys(options.methods).forEach(key => {
this[key] = options.methods[key].bind(this)
})
}
// 數據觀察
this._ob = observe(options.data)
this._watchers = []
// watch數據及更新
this._watcher = new Watcher(this, render, this._update)
// 渲染函數
this._update(this._watcher.value)
}

當我們初始化項目的時候,即會執行構造函數,該函數向我們展示了vue初始化的主線:編譯template字符串 => 代理data數據/methods的this綁定 => 數據觀察 => 建立watch及更新渲染


1. 編譯template字符串


const render = compile(getOuterHTML(el))


其中compile的實現如下:


export function compile (html) {
html = html.trim()
// 對編譯結果緩存
const hit = cache[html]
// parse函數在parse-html中定義,其作用是把我們獲取的html字符串通過正則匹配轉化為ast,輸出如下 {tag: 'div', attrs: {}, children: []}
return hit || (cache[html] = generate(parse(html)))
}

接下來看看generate函數,ast通過genElement的轉化生成了構建節點html的函數,在genElement將對if for 等進行判斷並轉化( 指令的具體處理將在後面做分析,先關注主流程代碼),最後都會執行genData函數


// 生成節點主函數
export function generate (ast) {
const code = genElement(ast)
// 執行code代碼,並將this作為code的global對象。所以我們在template中的變量將指向為實例的屬性 {{name}} -> this.name
return new Function (`with (this) { return ${code}}`)
}

// 解析單個節點 -> genData
function genElement (el, key) {
let exp
// 指令的實現,實際就是在模板編譯時實現的
if (exp = getAttr(el, 'v-for')) {
return genFor(el, exp)
} else if (exp = getAttr(el, 'v-if')) {
return genIf(el, exp)
} else if (el.tag === 'template') {
return genChildren(el)
} else {
// 分別為 tag 自身屬性 子節點數據
return `__h__('${ el.tag }', ${ genData(el, key) }, ${ genChildren(el) })`
}
}

我們可以看看在genData中都做了什麼。上面的parse函數將html字符串轉化為ast,而在genData中則將節點的attrs數據進一步處理,例如class -> renderClass style class props attr 分類。在這裏可以看到 bind 指令的實現,即通過正則匹配 : 和 bind,如果匹配則把相應的 value值轉化為 (value) 的形式,而不匹配的則通過JSON.stringify()轉化為字符串('value')。最後輸出attrs(key-value),在這裏得到的對象是字符串形式的,例如(value)等也僅僅是將變量名,而在generate中通過new Function進一步通過(this.value)得到變量值。


function genData (el, key) {
// 沒有屬性返回空對象
if (!el.attrs.length) {
return '{}'
}
// key
let data = key ? `{key:${ key },` : `{`
// class處理
if (el.attrsMap[':class'] || el.attrsMap['class']) {
data += `class: _renderClass(${ el.attrsMap[':class'] }, "${ el.attrsMap['class'] || '' }"),`
}
// attrs
let attrs = `attrs:{`
let props = `props:{`
let hasAttrs = false
let hasProps = false
for (let i = 0, l = el.attrs.length; i < l; i++) {
let attr = el.attrs[i]
let name = attr.name
// bind屬性
if (bindRE.test(name)) {
name = name.replace(bindRE, '')
if (name === 'class') {
continue
// style處理
} else if (name === 'style') {
data += `style: ${ attr.value },`
// props屬性處理
} else if (mustUsePropsRE.test(name)) {
hasProps = true
props += `"${ name }": (${ attr.value }),`
// 其他屬性
} else {
hasAttrs = true
attrs += `"${ name }": (${ attr.value }),`
}
// on指令,未實現
} else if (onRE.test(name)) {
name = name.replace(onRE, '')
// 普通屬性
} else if (name !== 'class') {
hasAttrs = true
attrs += `"${ name }": (${ JSON.stringify(attr.value) }),`
}
}
if (hasAttrs) {
data += attrs.slice(0, -1) + '},'
}
if (hasProps) {
data += props.slice(0, -1) + '},'
}
return data.replace(/,$/, '') + '}'
}

而對於genChildren,我們可以猜到就是對ast中的children進行遍歷調用genElement,實際上在這裏還包括了對文本節點的處理。


// 遍歷子節點 -> genNode
function genChildren (el) {
if (!el.children.length) {
return 'undefined'
}
// 對children扁平化處理
return '__flatten__([' + el.children.map(genNode).join(',') + '])'
}

function genNode (node) {
if (node.tag) {
return genElement(node)
} else {
return genText(node)
}
}

// 解析{{}}
function genText (text) {
if (text === ' ') {
return '" "'
} else {
const exp = parseText(text)
if (exp) {
return 'String(' + escapeNewlines(exp) + ')'
} else {
return escapeNewlines(JSON.stringify(text))
}
}
}

genText處理了text及換行,在parseText函數中利用正則解析{{}},輸出字符串(value)形式的字符串。


現在我們再看看__h__('${ el.tag }', ${ genData(el, key) }, ${ genChildren(el) })__h__函數


// h 函數利用上面得到的節點數據得到 vNode對象 => 虛擬dom
export default function h (tag, b, c) {
var data = {}, children, text, i
if (arguments.length === 3) {
data = b
if (isArray(c)) { children = c }
else if (isPrimitive(c)) { text = c }
} else if (arguments.length === 2) {
if (isArray(b)) { children = b }
else if (isPrimitive(b)) { text = b }
else { data = b }
}
if (isArray(children)) {
// 子節點遞歸處理
for (i = 0; i < children.length; ++i) {
if (isPrimitive(children[i])) children[i] = VNode(undefined, undefined, undefined, children[i])
}
}
// svg處理
if (tag === 'svg') {
addNS(data, children)
}
// 子節點為文本節點
return VNode(tag, data, children, text, undefined)
}

到此為止,我們分析了const render = compile(getOuterHTML(el)),從elhtml字符串到render函數都是怎麼處理的。


教你寫出一流的 銷售文案?

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。



2. 代理data數據/methods的this綁定


// 實例代理data數據
Object.keys(options.data).forEach(key => this._proxy(key))
// 將method的this指向實例
if (options.methods) {
Object.keys(options.methods).forEach(key => {
this[key] = options.methods[key].bind(this)
})
}

實例代理data數據的實現比較簡單,就是利用了對象的setter和getter,讀取this數據時返回data數據,在設置this數據時同步設置data數據


_proxy (key) {
if (!isReserved(key)) {
// need to store ref to self here
// because these getter/setters might
// be called by child scopes via
// prototype inheritance.
var self = this
Object.defineProperty(self, key, {
configurable: true,
enumerable: true,
get: function proxyGetter () {
return self._data[key]
},
set: function proxySetter (val) {
self._data[key] = val
}
})
}
}

3. Obaerve的實現


Observe的實現原理在很多地方都有分析,主要是利用了Object.defineProperty()來建立對數據更改的訂閱,在很多地方也稱之為數據劫持。下面我們來學習從零開始建立這樣一個數據的訂閱發布體系。


從簡單處開始,我們希望有個函數可以幫我們監聽數據的改變,每當數據改變時執行特定回調函數


function observe(data, callback) {
if (!data || typeof data !== 'object') {
return
}

// 遍歷key
Object.keys(data).forEach((key) => {
let value = data[key];

// 遞歸遍歷監聽深度變化
observe(value, callback);

// 監聽單個可以的變化
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
return value;
},
set(val) {
if (val === value) {
return
}

value = val;

// 監聽新的數據
observe(value, callback);

// 數據改變的回調
callback();
}
});
});
}

// 使用observe函數監聽data
const data = {};
observe(data, () => {
console.log('data修改');
})

上面我們實現了一個簡單的observe函數,只要我們將編譯函數作為callback傳入,那麼每次數據更改時都會觸發回調函數。但是我們現在不能為單獨的key設置監聽及回調函數,只能監聽整個對象的變化執行回調。下面我們對函數進行改進,達到為某個key設置監聽及回調。同時建立調度中心,讓整個訂閱發布模式更加清晰。


// 首先是訂閱中心
class Dep {
constructor() {
this.subs = []; // 訂閱者數組
}

addSub(sub) {
// 添加訂閱者
this.subs.push(sub);
}

notify() {
// 發布通知
this.subs.forEach((sub) => {
sub.update();
});
}
}

// 當前訂閱者,在getter中標記
Dep.target = null;

// 訂閱者
class Watch {
constructor(express, cb) {
this.cb = cb;
if (typeof express === 'function') {
this.expressFn = express;
} else {
this.expressFn = () => {
return new Function(express)();
}
}

this.get();
}

get() {
// 利用Dep.target存當前訂閱者
Dep.target = this;
// 執行表達式 -> 觸發getter -> 在getter中添加訂閱者
this.expressFn();
// 及時置空
Dep.taget = null;
}

update() {
// 更新
this.cb();
}

addDep(dep) {
// 添加訂閱
dep.addSub(this);
}
}

// 觀察者 建立觀察
class Observe {
constructor(data) {
if (!data || typeof data !== 'object') {
return
}

// 遍歷key
Object.keys(data).forEach((key) => {
// key => dep 對應
const dep = new Dep();
let value = data[key];

// 遞歸遍歷監聽深度變化
const observe = new Observe(value);

// 監聽單個可以的變化
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
if (Dep.target) {
const watch = Dep.target;
watch.addDep(dep);
}
return value;
},
set(val) {
if (val === value) {
return
}

value = val;

// 監聽新的數據
new Observe(value);

// 數據改變的回調
dep.notify();
}
});
});
}
}

// 監聽數據中某個key的更改
const data = {
name: 'xiaoming',
age: 26
};

const observe = new Observe(data);

const watch = new Watch('data.age', () => {
console.log('age update');
});

data.age = 22

現在我們實現了訂閱中心訂閱者觀察者。觀察者監測數據的更新,訂閱者通過訂閱中心訂閱數據的更新,當數據更新時,觀察者會告訴訂閱中心,訂閱中心再逐個通知所有的訂閱者執行更新函數。到現在為止,我們可以大概猜出vue的實現原理:



  1. 建立觀察者觀察data數據的更改 (new Observe)


  2. 在編譯的時候,當某個代碼片段或節點依賴data數據,為該節點建議訂閱者,訂閱data中某些數據的更新(new Watch)


  3. 當dada數據更新時,通過訂閱中心通知數據更新,執行節點更新函數,新建或更新節點(dep.notify())



上面是我們對vue實現原理訂閱發布模式的基本實現,及編譯到更新過程的猜想,現在我們接着分析vue源碼的實現:


在實例的初始化中


// ...
// 為數據建立數據觀察
this._ob = observe(options.data)
this._watchers = []
// 添加訂閱者 執行render 會觸發 getter 訂閱者訂閱更新,數據改變觸發 setter 訂閱中心通知訂閱者執行 update
this._watcher = new Watcher(this, render, this._update)
// ...

vue中數據觀察的實現


// observe函數
export function observe (value, vm) {
if (!value || typeof value !== 'object') {
return
}
if (
hasOwn(value, '__ob__') &&
value.__ob__ instanceof Observer
) {
ob = value.__ob__
} else if (
shouldConvert &&
(isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 為數據建立觀察者
ob = new Observer(value)
}
// 存儲關聯的vm
if (ob && vm) {
ob.addVm(vm)
}
return ob
}

// => Observe 函數
export function Observer (value) {
this.value = value
// 在數組變異方法中有用
this.dep = new Dep()
// observer實例存在__ob__中
def(value, '__ob__', this)
if (isArray(value)) {
var augment = hasProto
? protoAugment
: copyAugment
// 數組遍歷,添加變異的數組方法
augment(value, arrayMethods, arrayKeys)
// 對數組的每個選項調用observe函數
this.observeArray(value)
} else {
// walk -> convert -> defineReactive -> setter/getter
this.walk(value)
}
}

// => walk
Observer.prototype.walk = function (obj) {
var keys = Object.keys(obj)
for (var i = 0, l = keys.length; i < l; i++) {
this.convert(keys[i], obj[keys[i]])
}
}

// => convert
Observer.prototype.convert = function (key, val) {
defineReactive(this.value, key, val)
}

// 重點看看defineReactive
export function defineReactive (obj, key, val) {
// key對應的的訂閱中心
var dep = new Dep()

var property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}

// 兼容原有setter/getter
// cater for pre-defined getter/setters
var getter = property && property.get
var setter = property && property.set

// 實現遞歸監聽屬性 val = obj[key]
// 深度優先遍歷 先為子屬性設置 reactive
var childOb = observe(val)
// 設置 getter/setter
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val
// Dep.target 為當前 watch 實例
if (Dep.target) {
// dep 為 obj[key] 對應的調度中心 dep.depend 將當前 wtcher 實例添加到調度中心
dep.depend()
if (childOb) {
// childOb.dep 為 obj[key] 值 val 對應的 observer 實例的 dep
// 實現array的變異方法和$set方法訂閱
childOb.dep.depend()
}

// TODO: 此處作用未知?
if (isArray(value)) {
for (var e, i = 0, l = value.length; i < l; i++) {
e = value[i]
e && e.__ob__ && e.__ob__.dep.depend()
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val
// 通過 getter 獲取 val 判斷是否改變
if (newVal === value) {
return
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// 為新值設置 reactive
childOb = observe(newVal)
// 通知key對應的訂閱中心更新
dep.notify()
}
})
}

訂閱中心的實現


let uid = 0

export default function Dep () {
this.id = uid++
// 訂閱調度中心的watch數組
this.subs = []
}

// 當前watch實例
Dep.target = null

// 添加訂閱者
Dep.prototype.addSub = function (sub) {
this.subs.push(sub)
}

// 移除訂閱者
Dep.prototype.removeSub = function (sub) {
this.subs.$remove(sub)
}

// 訂閱
Dep.prototype.depend = function () {
// Dep.target.addDep(this) => this.addSub(Dep.target) => this.subs.push(Dep.target)
Dep.target.addDep(this)
}

// 通知更新
Dep.prototype.notify = function () {
// stablize the subscriber list first
var subs = this.subs.slice()
for (var i = 0, l = subs.length; i < l; i++) {
// subs[i].update() => watch.update()
subs[i].update()
}
}

訂閱者的實現


export default function Watcher (vm, expOrFn, cb, options) {
// mix in options
if (options) {
extend(this, options)
}
var isFn = typeof expOrFn === 'function'
this.vm = vm
// vm 的 _watchers 包含了所有 watch
vm._watchers.push(this)
this.expression = expOrFn
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
// deps 一個 watch 實例可以對應多個 dep
this.deps = []
this.newDeps = []
this.depIds = Object.create(null)
this.newDepIds = null
this.prevError = null // for async error stacks
// parse expression for getter/setter
if (isFn) {
this.getter = expOrFn
this.setter = undefined
} else {
warn('vue-lite only supports watching functions.')
}
this.value = this.lazy
? undefined
: this.get()
this.queued = this.shallow = false
}

Watcher.prototype.get = function () {
this.beforeGet()
var scope = this.scope || this.vm
var value
try {
// 執行 expOrFn,此時會觸發 getter => dep.depend() 將watch實例添加到對應 obj[key] 的 dep
value = this.getter.call(scope, scope)
}
if (this.deep) {
// 深度watch
// 觸發每個key的getter watch實例將對應多個dep
traverse(value)
}
// ...
this.afterGet()
return value
}

// 觸發getter,實現訂閱
Watcher.prototype.beforeGet = function () {
Dep.target = this
this.newDepIds = Object.create(null)
this.newDeps.length = 0
}

// 添加訂閱
Watcher.prototype.addDep = function (dep) {
var id = dep.id
if (!this.newDepIds[id]) {
// 將新出現的dep添加到newDeps中
this.newDepIds[id] = true
this.newDeps.push(dep)
// 如果已在調度中心,不再重複添加
if (!this.depIds[id]) {
// 將watch添加到調度中心的數組中
dep.addSub(this)
}
}
}

Watcher.prototype.afterGet = function () {
// 切除key的getter聯繫
Dep.target = null
var i = this.deps.length
while (i--) {
var dep = this.deps[i]
if (!this.newDepIds[dep.id]) {
// 移除不在expOrFn表達式中關聯的dep中watch的訂閱
dep.removeSub(this)
}
}
this.depIds = this.newDepIds
var tmp = this.deps
this.deps = this.newDeps
// TODO: 既然newDeps最終會被置空,這邊賦值的意義在於?
this.newDeps = tmp
}

// 訂閱中心通知消息更新
Watcher.prototype.update = function (shallow) {
if (this.lazy) {
this.dirty = true
} else if (this.sync || !config.async) {
this.run()
} else {
// if queued, only overwrite shallow with non-shallow,
// but not the other way around.
this.shallow = this.queued
? shallow
? this.shallow
: false
: !!shallow
this.queued = true
// record before-push error stack in debug mode
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.debug) {
this.prevError = new Error('[vue] async stack trace')
}
// 添加到待執行池
pushWatcher(this)
}
}

// 執行更新回調
Watcher.prototype.run = function () {
if (this.active) {
var value = this.get()
if (
((isObject(value) || this.deep) && !this.shallow)
) {
// set new value
var oldValue = this.value
this.value = value
var prevError = this.prevError
// ...
this.cb.call(this.vm, value, oldValue)
}
this.queued = this.shallow = false
}
}

Watcher.prototype.depend = function () {
var i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}

wtach回調執行隊列


在上面我們可以發現,watch在收到信息更新執行update時。如果非同步情況下會執行pushWatcher(this)將實例推入執行池中,那麼在何時會執行回調函數,如何執行呢?我們一起看看pushWatcher的實現。


// batch.js
var queueIndex
var queue = []
var userQueue = []
var has = {}
var circular = {}
var waiting = false
var internalQueueDepleted = false

// 重置執行池
function resetBatcherState () {
queue = []
userQueue = []
// has 避免重複
has = {}
circular = {}
waiting = internalQueueDepleted = false
}

// 執行執行隊列
function flushBatcherQueue () {
runBatcherQueue(queue)
internalQueueDepleted = true
runBatcherQueue(userQueue)
resetBatcherState()
}

// 批量執行
function runBatcherQueue (queue) {
for (queueIndex = 0; queueIndex < queue.length; queueIndex++) {
var watcher = queue[queueIndex]
var id = watcher.id
// 執行後置為null
has[id] = null
watcher.run()
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > config._maxUpdateCount) {
warn(
'You may have an infinite update loop for watcher ' +
'with expression "' + watcher.expression + '"',
watcher.vm
)
break
}
}
}
}

// 添加到執行池
export function pushWatcher (watcher) {
var id = watcher.id
if (has[id] == null) {
if (internalQueueDepleted && !watcher.user) {
// an internal watcher triggered by a user watcher...
// let's run it immediately after current user watcher is done.
userQueue.splice(queueIndex + 1, 0, watcher)
} else {
// push watcher into appropriate queue
var q = watcher.user
? userQueue
: queue
has[id] = q.length
q.push(watcher)
// queue the flush
if (!waiting) {
waiting = true
// 在nextick中執行
nextTick(flushBatcherQueue)
}
}
}
}


4. patch實現


上面便是vue中數據驅動的實現原理,下面我們接着回到主流程中,在執行完watch后,便執行this._update(this._watcher.value)開始節點渲染


// _update => createPatchFunction => patch => patchVnode => (dom api)

// vtree是通過compile函數編譯的render函數執行的結果,返回了當前表示當前dom結構的對象(虛擬節點樹)
_update (vtree) {
if (!this._tree) {
// 第一次渲染
patch(this._el, vtree)
} else {
patch(this._tree, vtree)
}
this._tree = vtree
}

// 在處理節點時,需要針對class,props,style,attrs,events做不同處理
// 在這裏注入針對不同屬性的處理函數
const patch = createPatchFunction([
_class, // makes it easy to toggle classes
props,
style,
attrs,
events
])

// => createPatchFunction返回patch函數,patch函數通過對比虛擬節點的差異,對節點進行增刪更新
// 最後調用原生的dom api更新html
return function patch (oldVnode, vnode) {
var i, elm, parent
var insertedVnodeQueue = []
// pre hook
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]()

if (isUndef(oldVnode.sel)) {
oldVnode = emptyNodeAt(oldVnode)
}

if (sameVnode(oldVnode, vnode)) {
// someNode can patch
patchVnode(oldVnode, vnode, insertedVnodeQueue)
} else {
// 正常的不復用 remove insert
elm = oldVnode.elm
parent = api.parentNode(elm)

createElm(vnode, insertedVnodeQueue)

if (parent !== null) {
api.insertBefore(parent, vnode.elm, api.nextSibling(elm))
removeVnodes(parent, [oldVnode], 0, 0)
}
}

for (i = 0; i < insertedVnodeQueue.length; ++i) {
insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i])
}

// hook post
for (i = 0; i < cbs.post.length; ++i) cbs.post[i]()
return vnode
}

結尾


以上分析了vue從template 到節點渲染的大致實現,當然也有某些地方沒有全面分析的地方,其中template解析為ast主要通過正則匹配實現,及節點渲染及更新的patch過程主要通過節點操作對比來實現。但是我們對編譯template字符串 => 代理data數據/methods的this綁定 => 數據觀察 => 建立watch及更新渲染的大致流程有了個比較完整的認知。


歡迎到前端學習打卡群一起學習~516913974

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。





Orignal From: vue的第一個commit分析_如何寫文案

留言