vue 1.0源代码重点难点分析
本文分析vue1.0源代碼從入口開(kāi)始到編譯輸出到網(wǎng)頁(yè)顯示生效的整個(gè)過(guò)程,并重點(diǎn)分析一些vue源代碼設(shè)計(jì)原理。
vue初始化根組件的入口代碼:
對(duì)于沒(méi)有路由的單頁(yè)應(yīng)用來(lái)說(shuō),入口就是new Vue(options),options就是根組件代碼,對(duì)于有路由的項(xiàng)目來(lái)說(shuō),入口就是router.start(app),其中app是根組件。
function Router() {} // router構(gòu)造函數(shù)
var router = new Router(); // new router實(shí)例
router.start(app); // 項(xiàng)目真正的入口,app是根組件代碼(對(duì)象)
Router.prototype.start = function start(App, container, cb) {
this._appContainer = container; //把a(bǔ)pp和占位元素container保存在router實(shí)例
var Ctor = this._appConstructor = typeof App === 'function' ? App : Vue.extend(App); // 根組件App繼承Vue類(lèi)(js用構(gòu)造函數(shù)和prototype屬性模仿類(lèi))
this.history.start();
HashHistory.prototype.start = function start() {
self.onChange(path.replace(/^#!?/, '') + query); //當(dāng)瀏覽器地址中hash部分變化時(shí)觸發(fā)執(zhí)行l(wèi)istener(),listener會(huì)格式化瀏覽器地址中hash部分(hash如果沒(méi)有/則加/前綴),再執(zhí)行onChange,傳入path(url地址的hash部分)
onChange: function onChange(path, state, anchor) { // url地址中hask部分變化觸發(fā)執(zhí)行此方法執(zhí)行進(jìn)行路由組件切換,類(lèi)似input元素的輸入觸發(fā)onchange
_this._match(path, state, anchor);
Router.prototype._match = function _match(path, state, anchor) {
var _this3 = this; // this是router實(shí)例
var route = new Route(path, this);? // 這是route實(shí)例,含路由切換信息from/to,不是router實(shí)例。
var transition = new RouteTransition(this, route, currentRoute); //new transition實(shí)例時(shí)傳入router實(shí)例,transition是路由切換from/to相關(guān)處理,還有一個(gè)transition是路由組件切換時(shí)的過(guò)渡效果,別搞混了
if (!this.app) { // 如果是第一次執(zhí)行,還沒(méi)有new根組件實(shí)例
_this3.app = new _this3._appConstructor({ //new根組件實(shí)例,構(gòu)造函數(shù)是function VueComponent (options) { this._init(options) },new根組件實(shí)例保存在router實(shí)例中,router.app就是根組件實(shí)例,router實(shí)例會(huì)保存在根組件實(shí)例中。
在new Vuecomponent()實(shí)例化時(shí)要執(zhí)行Vuecomponent構(gòu)造函數(shù),Vuecomponent是繼承Vue的,就是要執(zhí)行Vue的_init初始化函數(shù),就開(kāi)始初始化根組件,編譯根組件
template。
vue的鉤子函數(shù)created()就是指new組件實(shí)例之后,而ready()就是指編譯組件template之后,先new組件實(shí)例,再編譯組件template,因?yàn)槭窃趧?chuàng)建組件實(shí)例之后才
執(zhí)行構(gòu)造函數(shù)Vue才執(zhí)行_init()開(kāi)始初始化編譯,編譯完成之后插入網(wǎng)頁(yè)生效,是異步過(guò)程,所以執(zhí)行ready()時(shí)可能插入網(wǎng)頁(yè)生效還沒(méi)有真正完成,如果ready()有
代碼需要在網(wǎng)頁(yè)找組件元素,比如初始化輪播的代碼,可能就需要延遲執(zhí)行等插入網(wǎng)頁(yè)生效完成,否則就會(huì)出現(xiàn)無(wú)法理解的異常現(xiàn)象。
router實(shí)例會(huì)保存在根組件實(shí)例中,傳遞給各個(gè)子組件,因此vue的各個(gè)組件訪問(wèn)router實(shí)例非常方便。
Vue.prototype._init就是vue組件入口/初始化函數(shù),這是組件通用的初始化函數(shù),是處理組件的核心函數(shù),因?yàn)関ue是從根組件開(kāi)始遞歸編譯所有的子組件,
所有的子組件都要跑一遍這個(gè)方法,所以這個(gè)方法就是vue的頂層核心方法,在底層有幾十幾百個(gè)方法進(jìn)行各種處理。
先從_init開(kāi)始把組件初始化過(guò)程從頭到尾大概走一遍如下(只列舉主要語(yǔ)句):
Vue.prototype._init = function (options) {
this._initState(); //之后el從id變?yōu)榫W(wǎng)頁(yè)元素DOM(引用網(wǎng)頁(yè)元素)
if (options.el) {
this.$mount(options.el); //如果根組件寫(xiě)了template,el就是根組件占位元素,否則el就是online template元素,從根組件開(kāi)始遞歸編譯所有的子組件子節(jié)點(diǎn)
}
Vue.prototype._initState = function () {
this._initProps();
this._initMeta();
this._initMethods();
this._initData();
this._initComputed();
};
Vue.prototype._initProps = function () {
// make sure to convert string selectors into element now
el = options.el = query(el);
}
Vue.prototype.$mount = function (el) {
this._compile(el); //從根組件開(kāi)始遞歸編譯所有的子組件子節(jié)點(diǎn),然后插入網(wǎng)頁(yè)生效
return this;
};
Vue.prototype._compile = function (el) {
var original = el;
el = transclude(el, options);
var rootLinker = compileRoot(el, options, contextOptions); // compile產(chǎn)生link函數(shù)
var rootUnlinkFn = rootLinker(this, el, this._scope); // 執(zhí)行l(wèi)ink函數(shù),插入網(wǎng)頁(yè)生效
var contentUnlinkFn = contentLinkFn ? contentLinkFn(this, el) : compile(el, options)(this, el); //編譯產(chǎn)生link()再執(zhí)行l(wèi)ink()
if (options.replace) {
replace(original, el); //可能需要替換更新
}
function compileRoot(el, options, contextOptions) {
replacerLinkFn = compileDirectives(el.attributes, options); //root元素<div id=? 沒(méi)什么要編譯的
return function rootLinkFn(vm, el, scope) {
}
function compileDirectives(attrs, options) {}
function compile(el, options, partial) {? // 編譯組件的template,el是組件占位元素,options是組件代碼
var nodeLinkFn = compileNode(el, options) //編譯根節(jié)點(diǎn)
var childLinkFn = compileNodeList(el.childNodes, options) // 遞歸編譯子節(jié)點(diǎn)
return function compositeLinkFn(vm, el, host, scope, frag) {? // 編譯返回的通用link函數(shù)
function makeChildLinkFn(linkFns) { // 產(chǎn)生子節(jié)點(diǎn)link函數(shù)
return function childLinkFn(vm, nodes, host, scope, frag) { // 子節(jié)點(diǎn)link函數(shù)返回給var childLinkFn
function compileNodeList(nodeList, options) {? // 會(huì)遞歸調(diào)用自己,層層遞歸所有子節(jié)點(diǎn)
nodeLinkFn = compileNode(node, options); //編譯一個(gè)節(jié)點(diǎn)
childLinkFn = compileNodeList(node.childNodes, options) //編譯一個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn),遞歸編譯子節(jié)點(diǎn)
return linkFns.length ? makeChildLinkFn(linkFns) : null; //nodeLinkFn和childLinkFn在linkFns中
function compileNode(node, options) {? // 編譯一個(gè)節(jié)點(diǎn)
var type = node.nodeType;
if (type === 1 && !isScript(node)) {
return compileElement(node, options);
} else if (type === 3 && node.data.trim()) {
return compileTextNode(node, options);
function compileElement(el, options) { // 到這里要解析處理頁(yè)面元素表達(dá)式中的指令屬性包括標(biāo)簽組件,細(xì)節(jié)本文忽略
// check terminal directives (for & if)
if (hasAttrs) {
linkFn = checkTerminalDirectives(el, attrs, options);
}
// check element directives
if (!linkFn) {
linkFn = checkElementDirectives(el, options);
}
// check component
if (!linkFn) {
linkFn = checkComponent(el, options);
}
// normal directives
if (!linkFn && hasAttrs) {
linkFn = compileDirectives(attrs, options);
}
return linkFn;
function checkTerminalDirectives(el, attrs, options) {? // 編譯處理指令
def = resolveAsset(options, 'directives', matched[1]); //每種每個(gè)屬性指令都有一套方法找出來(lái)
return makeTerminalNodeLinkFn(el, dirName, value, options, termDef, rawName, arg, modifiers);
function makeTerminalNodeLinkFn(el, dirName, value, options, def, rawName, arg, modifiers) {
var parsed = parseDirective(value); //指令表達(dá)式字符串如果沒(méi)有|,就無(wú)需解析,直接返回指令表達(dá)式字符串
var fn = function terminalNodeLinkFn(vm, el, host, scope, frag) {
if (descriptor.ref) {
defineReactive((scope || vm).$refs, descriptor.ref, null);
}
vm._bindDir(descriptor, el, host, scope, frag);
};
return fn;
Vue.prototype._bindDir = function (descriptor, node, host, scope, frag) {
this._directives.push(new Directive(descriptor, this, node, host, scope, frag));
};
至此解析編譯節(jié)點(diǎn)的指令表達(dá)式結(jié)束,然后執(zhí)行l(wèi)ink函數(shù):
function compositeLinkFn(vm, el, host, scope, frag) {
var dirs = linkAndCapture(function compositeLinkCapturer() {
if (nodeLinkFn) nodeLinkFn(vm, el, host, scope, frag);
if (childLinkFn) childLinkFn(vm, childNodes, host, scope, frag);
}, vm);
function linkAndCapture(linker, vm) {
linker(); //增加新解析的directive實(shí)例存儲(chǔ)到vm._directives中
var dirs = vm._directives.slice(originalDirCount); //取最新增加的directive實(shí)例
sortDirectives(dirs);
for (var i = 0, l = dirs.length; i < l; i++) {
dirs[i]._bind(); //執(zhí)行每個(gè)directive實(shí)例的_bind方法,比如v-for是一個(gè)directive實(shí)例,每個(gè)循環(huán)項(xiàng)又是一個(gè)directive實(shí)例,組件標(biāo)簽也是一個(gè)directive實(shí)例
}
return dirs;
}
linker()就是要執(zhí)行編譯階段產(chǎn)生的link函數(shù):
function childLinkFn(vm, nodes, host, scope, frag) {
nodeLinkFn(vm, node, host, scope, frag);
childrenLinkFn(vm, childNodes, host, scope, frag); //childrenLinkFn就是childLinkFn,子節(jié)點(diǎn)遞歸調(diào)用
nodeLinkFn()就是編譯階段產(chǎn)生的那個(gè)fn:
var fn = function terminalNodeLinkFn(vm, el, host, scope, frag) {
if (descriptor.ref) {
defineReactive((scope || vm).$refs, descriptor.ref, null);? // 如果有v-ref="xxx" 引用組件對(duì)象,這也是一種指令屬性
}
vm._bindDir(descriptor, el, host, scope, frag);? // bind就是初始化方法,bindDir就是初始化指令的方法
};
要執(zhí)行new Directive(descriptor, this, node, host, scope, frag),然后把directive實(shí)例保存到vm._directives中,
new Directive時(shí)只是把之前compile/link階段產(chǎn)生的數(shù)據(jù)方法保存到directive實(shí)例中,并無(wú)其它處理,因此只是準(zhǔn)備數(shù)據(jù),保存到實(shí)例,后面再執(zhí)行實(shí)例中的初始化方法初始化指令。
Directive.prototype._bind = function () { // 這就是dirs[i]._bind()方法代碼,元素節(jié)點(diǎn)每個(gè)屬性指令都有一個(gè)directive實(shí)例都有一個(gè)通用的bind初始化方法
// setup directive params
this._setupParams();
this.bind(); //每個(gè)指令實(shí)例中的bind方法是specific bind方法,通用_bind方法調(diào)用specific bind方法,每個(gè)指令都不一樣。
var watcher = this._watcher = new Watcher(this.vm, this.expression, this._update, // 凡是要獲取表達(dá)式/變量值都要?jiǎng)?chuàng)建watcher,會(huì)把watcher加入到在求值過(guò)程中依賴(lài)的變量屬性的dep中。
this.update(watcher.value); //更新屬性指令網(wǎng)頁(yè)數(shù)據(jù),比如id="$index"要把變量值插入,比如{{item.name}}要把變量值插入,網(wǎng)頁(yè)數(shù)據(jù)更新之后就處理完了,更新網(wǎng)頁(yè)是最后一步,每種屬性指令的update方法都不一樣。
可見(jiàn)vue處理指令的關(guān)鍵成分是watcher和指令實(shí)例中的bind/update初始化函數(shù)方法,在編譯階段處理所有的指令屬性,new directive實(shí)例,保存起來(lái),在link階段只要把所有保存的指令實(shí)例都執(zhí)行一遍bind方法進(jìn)行初始化就完成了所有的指令初始化工作,包括標(biāo)簽組件也是要走指令流程的,只不過(guò)標(biāo)簽組件還要走一遍component處理流程,要new Vuecomponent()實(shí)例執(zhí)行構(gòu)造函數(shù)從_init()開(kāi)始把compile()流程再走一遍完成組件template的編譯,組件template中可能又有指令屬性,就又要把上述流程走一遍完成對(duì)指令屬性的編譯處理,比如v-for="item in items"。
所以vue的通用指令處理程序和組件處理程序會(huì)從編譯根組件開(kāi)始被反復(fù)執(zhí)行多次,遞歸所有的子組件,所有的子節(jié)點(diǎn),層層嵌套,每個(gè)指令每個(gè)子組件都要執(zhí)行一遍核心編譯處理程序,如果在核心編譯程序debug看,會(huì)有幾十幾百次輸出,能看到所有的指令/組件的數(shù)據(jù),還是非常復(fù)雜的。
以v-for指令為例來(lái)看一段指令的源代碼:
var vFor = { //
bind: function bind() {
if ('development' !== 'production' && this.el.hasAttribute('v-if')) {
warn('<' + this.el.tagName.toLowerCase() + ' v-for="' + this.expression + '" v-if="' + this.el.getAttribute('v-if') + '">: ' + 'Using v-if and v-for on the same element is not recommended - ' + 'consider filtering the source Array instead.', this.vm);
} //不推薦同時(shí)使用v-for和v-if是在這兒檢查告警的
this.start = createAnchor('v-for-start'); //text節(jié)點(diǎn)
this.end = createAnchor('v-for-end');
replace(this.el, this.end); //先用占位元素替換掉v-for元素,等編譯好v-for元素之后再插入網(wǎng)頁(yè)到占位元素位置生效
before(this.start, this.end);
this.factory = new FragmentFactory(this.vm, this.el); //此過(guò)程會(huì)compile(this.el)生成linker保存在fragment實(shí)例中
function FragmentFactory(vm, el) {
linker = compile(template, vm.$options, true); // 編譯指令template,v-for指令是一個(gè)數(shù)組循環(huán)產(chǎn)生一個(gè)列表
router切換路由組件時(shí)涉及transition過(guò)渡效果,所以相關(guān)代碼是封裝在transition中:
var startTransition = function startTransition() {
transition.start(function () {
_this3._postTransition(route, state, anchor);
RouteTransition.prototype.start = function start(cb) {
var view = this.router._rootView; //_rootView是<router-view>指令實(shí)例,里面有app組件構(gòu)造器
activate(_view, transition, depth, cb); //transition里面有要切換的子頁(yè)面路由和component構(gòu)造函數(shù)對(duì)象,里面有options(組件代碼)
function activate(view, transition, depth, cb, reuse) { //depth對(duì)應(yīng)子路由
var handler = transition.activateQueue[depth];
if (!handler) {
view.setComponent(null);
component = view.build({ //view就是占位標(biāo)簽router-view指令實(shí)例,build方法就是var component={}中的build方法
build: function build(extraOptions) {
var child = new this.Component(options); //options就是之前準(zhǔn)備好的組件構(gòu)造器含組件代碼,這就是new 路由組件實(shí)例化
return child;
Vue[type] = function (id, definition) { //這就是vue component的構(gòu)造函數(shù)的構(gòu)造函數(shù)
definition = Vue.extend(definition);
this.options[type + 's'][id] = definition;
return definition;
view.transition(component);
component.$before(view.anchor, null, false);
//Actually swap the components
transition: function transition(target, cb) { //var component={}中的transition方法,target是組件實(shí)例,實(shí)例中el是已經(jīng)編譯產(chǎn)生的DOM對(duì)象,可以直接插入網(wǎng)頁(yè)生效。
self.remove(current);
target.$before(self.anchor, cb);
Vue.prototype.$before = function (target, cb, withTransition) { //此時(shí)target變?yōu)閍nchor占位元素(text節(jié)點(diǎn))
return insert(this, target, cb, withTransition, beforeWithCb, beforeWithTransition);
function insert(vm, target, cb, withTransition, op1, op2) {
function beforeWithCb(el, target, vm, cb) {
before(el, target);
if (cb) cb();
}
function beforeWithTransition(el, target, vm, cb) {
applyTransition(el, 1, function () {
before(el, target);
}, vm, cb);
有兩個(gè)細(xì)節(jié)提一下:
function View (Vue) {
Vue.elementDirective('router-view', viewDef);
在View構(gòu)造函數(shù)中定義了router-view這個(gè)指令/組件。
下面是vue的on方法,是用底層js事件方法:
function on(el, event, cb, useCapture) {
el.addEventListener(event, cb, useCapture);
}
vue沒(méi)有再定義一套事件機(jī)制,就是從上層封裝直接調(diào)用底層js方法來(lái)定義事件綁定,比肩簡(jiǎn)單。
要注意,cb中已經(jīng)引用組件實(shí)例,所有的組件實(shí)例都是保存在根組件實(shí)例中,按id索引。當(dāng)點(diǎn)擊頁(yè)面執(zhí)行方法時(shí),頁(yè)面并沒(méi)有作用域指引,方法也不在全局空間,
就是在js的events[]中找handler執(zhí)行,關(guān)鍵關(guān)鍵關(guān)鍵是handler方法代碼中已經(jīng)引用組件實(shí)例,這就是作用域。
它如何引用組件實(shí)例呢?估計(jì)很少有人想過(guò)這個(gè)問(wèn)題。
先new router實(shí)例,要么在全局空間創(chuàng)建router實(shí)例,要么把router實(shí)例保存在全局,再new 根組件實(shí)例,保存在router實(shí)例,再new 組件實(shí)例,保存在根組件,組件實(shí)例的方法用this引用組件實(shí)例,因此引用組件實(shí)例就是引用保存在全局的router實(shí)例中的屬性,js對(duì)象引用機(jī)制會(huì)導(dǎo)致能引用
到組件實(shí)例,這個(gè)可是很關(guān)鍵的,否則就全亂套了。
其它的框架大都自己又定義了一套事件機(jī)制,就復(fù)雜了。
組件構(gòu)造函數(shù)Vuecomponent用Vue.extend繼承vue構(gòu)造函數(shù),每個(gè)組件只是options不同,源代碼中組件的初始化方法:
var component = {
bind: function bind() {
this.setComponent(this.expression);
build: function build(extraOptions) { //入口參數(shù)就是組件的代碼
extend(options, extraOptions);
var child = new this.Component(options); //就是new VueComponent(),就是new Vue()實(shí)例,只不過(guò)是子實(shí)例。
setComponent: function setComponent(value, cb) {
self.mountComponent(cb);
mountComponent: function mountComponent(cb) {
var newComponent = this.build();
路由組件與頁(yè)面組件不同,頁(yè)面組件在編譯template時(shí)會(huì)創(chuàng)建一個(gè)directve指令實(shí)例,里面有組件的id和descriptor,而所有的組件定義代碼都在components[]中可以找到。
路由組件是new Vuecomponent()創(chuàng)建組件實(shí)例時(shí)執(zhí)行_init() -> $mount(el)開(kāi)始編譯template,指令組件是在編譯template過(guò)程中層層遞歸掃描html元素節(jié)點(diǎn)找到指令標(biāo)簽再編譯處理指令標(biāo)簽。
?
根組件實(shí)例只在執(zhí)行入口初始化時(shí)創(chuàng)建一次,每次路由變化時(shí),會(huì)執(zhí)行一次_match,this.app指根組件/主模塊,根組件實(shí)例創(chuàng)建之后一直存在,不會(huì)再執(zhí)行new _appConstructor()初始化根組件。
vue處理的對(duì)象主要是組件和指令,主要用vue構(gòu)造函數(shù)和directive構(gòu)造函數(shù)來(lái)處理組件和指令,new實(shí)例時(shí)就是初始化組件和指令,編譯組件時(shí)從根節(jié)點(diǎn)開(kāi)始遞歸循環(huán)
掃描所有的子節(jié)點(diǎn),只要有組件/指令都是用同樣的通用代碼處理,所以只要上述兩個(gè)構(gòu)造函數(shù)對(duì)象寫(xiě)ok了,把編譯循環(huán)遞歸寫(xiě)ok了,就基本成功了,不管template有
多復(fù)雜有多少層節(jié)點(diǎn)嵌套,有多少層組件嵌套,都是用通用方法和循環(huán)遞歸處理。
還有watcher構(gòu)造函數(shù),是為了實(shí)現(xiàn)數(shù)據(jù)更新同步。
Vue內(nèi)置指令router-view,slot,v-for,v-if,v-model,是重要指令,每個(gè)指令有一套方法比如bind,update,都已經(jīng)設(shè)計(jì)好,如果自定義指令,需要自己寫(xiě)方法,最起碼
要寫(xiě)bind方法(初始化方法)。
從初始化根組件開(kāi)始執(zhí)行vue._init,如果根元素頁(yè)面有調(diào)用組件,則每個(gè)組件都再執(zhí)行一遍vue._init,從根組件開(kāi)始每個(gè)組件
都初始化一個(gè)組件實(shí)例,從根元素開(kāi)始每個(gè)節(jié)點(diǎn)都要編譯處理一遍,編譯之后產(chǎn)生link函數(shù)再執(zhí)行l(wèi)ink函數(shù)。對(duì)于每個(gè)屬性指令
則執(zhí)行一遍new Directive()創(chuàng)建一個(gè)directive實(shí)例,相關(guān)數(shù)據(jù)方法都保存在實(shí)例中,主要是el和update方法,
如果屬性指令寫(xiě)在組件標(biāo)簽元素內(nèi),則el是編譯之后插入網(wǎng)頁(yè)的<div>元素,執(zhí)行update方法更新網(wǎng)頁(yè)時(shí)是針對(duì)網(wǎng)頁(yè)中這個(gè)<div>
進(jìn)行屬性修改,指令實(shí)例保存在所屬的組件實(shí)例中,路由組件中定義/調(diào)用的子組件都保存在組件實(shí)例的options.components下。
頁(yè)面每個(gè)表達(dá)式都都會(huì)創(chuàng)建watcher,組件中被頁(yè)面綁定的每個(gè)屬性變量都會(huì)創(chuàng)建set/get方法,包含所有依賴(lài)自己的watcher,
watcher中的update方法會(huì)調(diào)用指令的update方法更新頁(yè)面,比如:class指令的update方法就是修改所在元素的class屬性,
class="{{}}"也是指令,更新時(shí)也是要修改所在元素的class屬性。
vue自帶指令保存在options.directives中,對(duì)于<test>這樣的自定義標(biāo)簽,是已經(jīng)用component定義的組件,它是指令組件,
既是指令也是組件,在當(dāng)前頁(yè)面組件實(shí)例的options.components下有,在_directives下也有,指令組件要把component流程
和directive流程都走一遍。
編譯一個(gè)節(jié)點(diǎn)/屬性,只是準(zhǔn)備數(shù)據(jù),產(chǎn)生link函數(shù),執(zhí)行l(wèi)ink函數(shù)才初始化生效,比如執(zhí)行指令的初始化方法,
對(duì)于組件指令,初始化方法最后要執(zhí)行mountComponent才把template元素插入網(wǎng)頁(yè)生效,之前的所有處理都是在做數(shù)據(jù)準(zhǔn)備
每個(gè)指令都是事先定義好的,有bind/update方法,自定義指令也有方法,在頁(yè)面調(diào)用指令時(shí),
編譯時(shí)查指令集合,獲取指令的相關(guān)數(shù)據(jù),然后new directive創(chuàng)建一個(gè)指令實(shí)例,保存在當(dāng)前子頁(yè)面組件實(shí)例中,編譯就
大功告成了,然后執(zhí)行l(wèi)ink函數(shù),把當(dāng)前頁(yè)面組件中調(diào)用的所有指令_directives[]都執(zhí)行一遍初始化方法即可,指令的初始化
方法也是更新方法,就是是設(shè)置/修改指令所在的元素的屬性,對(duì)于組件指令,就更復(fù)雜,要new component()創(chuàng)建組件實(shí)例,
再執(zhí)行組件指令的初始化方法,執(zhí)行mountComponent完成最后一個(gè)環(huán)節(jié),把組件template元素插入網(wǎng)頁(yè)生效,所以組件指令
既是組件又是指令,component和directive兩種流程都要走一遍,非常復(fù)雜。在組件指令寫(xiě)屬性,處理起來(lái)是最復(fù)雜的,
要把<test>變成<div>,標(biāo)簽原始屬性復(fù)制到<div>,但標(biāo)簽原始屬性的作用域是調(diào)用標(biāo)簽的頁(yè)面組件,也就是標(biāo)簽組件的父組件,
而<div>中replacer屬性,也就是寫(xiě)在template中的<div>中的屬性,作用域是標(biāo)簽組件本身,除此之外,所有組件/指令處理
流程都是一樣的,只是不同的組件/指令,它們的數(shù)據(jù)方法不同。
再小結(jié)一下:
組件走Vue構(gòu)造器流程,指令走Directive構(gòu)造器流程,組件指令兩個(gè)流程都走,看Vue和Directive代碼,要清楚每個(gè)組件,每個(gè)指令,都要執(zhí)行‘
一遍同樣的代碼,代碼會(huì)被多次執(zhí)行,每次執(zhí)行都是針對(duì)一個(gè)不同的組件/指令。編譯方法中compileNodesList要遞歸所有的子節(jié)點(diǎn),包括看不見(jiàn)的文本節(jié)點(diǎn),
compileNode編譯每個(gè)節(jié)點(diǎn)時(shí)要遍歷所有的屬性,html屬性,指令屬性,props屬性,處理方法是不同的。組件指令既是組件又是指令,兩種流程都要走一遍。
組件/指令都是事先定義好的,數(shù)據(jù)方法都已經(jīng)有了,都保存在根組件Vue實(shí)例中,再繼承到各個(gè)子組件實(shí)例中,編譯時(shí)就是要判斷標(biāo)簽/屬性是不是已知指令,
如果是,就把數(shù)據(jù)準(zhǔn)備一下,然后new directive實(shí)例保存在當(dāng)前組件實(shí)例中,再執(zhí)行l(wèi)ink函數(shù),link函數(shù)會(huì)執(zhí)行指令的初始化函數(shù),初始化函數(shù)會(huì)最后完成
網(wǎng)頁(yè)更新,對(duì)于組件指令就是要把template元素插入網(wǎng)頁(yè)生效。
為了實(shí)現(xiàn)數(shù)據(jù)同步,還要針對(duì)組件的屬性和頁(yè)面綁定的變量包括方法創(chuàng)建watcher,在初始化指令時(shí)要進(jìn)行watcher相關(guān)處理,這是另外一種主要的流程,貫穿在
所有主要流程中。
vuex/store是vue作者參考flux/mutation實(shí)現(xiàn)原理自己寫(xiě)了一套代碼實(shí)現(xiàn)了緩存數(shù)據(jù)方法,并不是第三方插件,調(diào)用
vuex/store初始化緩存的方式就是:
new Vuex.Store({
modules: {
userInfo: {
state: {
userInfo : {
username: ''
}
}
},
mutations: {
['SETUSERINFO'] (state, data) {
state.userInfo.username = data.username;
}
}
}
},
組件調(diào)用vuex/store數(shù)據(jù)方法的方式是:
import {getMenus} from '../vuex/getters/user_getters'
import {setUserInfo} from '../vuex/actions/user_actions'
store : store,
vuex : {
getters : {
getMenus : (state) => {
return state.userInfo.globle.menus;
}
},
actions : {
setUserInfo : ({dispatch},data) => {
dispatch('SETUSERINFO',data);
}
}
}
vuex安裝到vue的相關(guān)源代碼:
function vuexInit() {
var actions = vuex.actions;
options.methods[_key] = makeBoundAction(this.$store, actions[_key], _key); // 給action方法增加一層封裝,所以你debug看組件實(shí)例里面的action方法并不是你自己寫(xiě)的原始方法,而是一個(gè)封裝方法,任何框架只要訪問(wèn)全局store數(shù)據(jù)本質(zhì)上都是加一層封裝,由封裝方法再調(diào)用真正的action方法,由于封裝方法在store相關(guān)的程序空間,可以訪問(wèn)store/dispatch,可以傳遞給原始action方法,因此你寫(xiě)的原始action方法無(wú)需涉及store/state,可以寫(xiě)形參dispatch,實(shí)際調(diào)用時(shí)會(huì)自動(dòng)傳遞dispatch,這就是action方法的奧秘,react的reducer/action方法本質(zhì)原理上也是一樣的。
store.js:
Vue.use(Vuex); //初始化時(shí)會(huì)安裝vuex插件到vue,也就是執(zhí)行vuex的install程序。
vuex.js:
function install(_Vue) {
override(Vue); //改寫(xiě)vue
}
function override (Vue) {
var _init = Vue.prototype._init;
Vue.prototype._init = function () { //改寫(xiě)vue原來(lái)的_init方法,其實(shí)還是調(diào)用原來(lái)的_init,但給options增加init=vuexinit方法
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
options.init = options.init ? [vuexInit].concat(options.init) : vuexInit;
_init.call(this, options);
Vue.prototype._init = function (options) {
this._callHook('init'); //會(huì)調(diào)用options.init,也就是調(diào)用vuexinit方法
Vue.prototype._callHook = function (hook) {
this.$emit('pre-hook:' + hook);
var handlers = this.$options[hook];
if (handlers) {
for (var i = 0, j = handlers.length; i < j; i++) {
handlers[i].call(this);
因此vuex擴(kuò)展到vue之后,在new Vue(options)創(chuàng)建組件實(shí)例時(shí),會(huì)執(zhí)行vuexinit方法處理options中的vuex:{}表達(dá)式,會(huì)在組件中創(chuàng)建setUserInfo方法,
創(chuàng)建的方法代碼是在原始action方法加了一層封裝。
vue用set/get方法實(shí)現(xiàn)數(shù)據(jù)響應(yīng),其關(guān)鍵代碼在于:
function defineReactive (obj, key, val) {
var dep = new Dep()
var childOb = observe(val) //如果屬性有層次,要遞歸處理每一層屬性
Object.defineProperty(obj, key, { //這是針對(duì)數(shù)據(jù)對(duì)象和里面的屬性創(chuàng)建set/get,是為了實(shí)現(xiàn)同步功能,
//組件定義的數(shù)據(jù)屬性在new Vue()時(shí)復(fù)制到this._data。
enumerable: true,
configurable: true,
get: function(){
if(Dep.target){ // 在針對(duì)頁(yè)面表達(dá)式創(chuàng)建new watcher()時(shí)會(huì)調(diào)用get方法取值,并把new watcher實(shí)例保存在Dep.target,Dep.target就是當(dāng)前正在創(chuàng)建的watcher實(shí)例,這個(gè)地方挺不容易看懂的,因?yàn)橐w設(shè)計(jì)邏輯才能看懂,
dep.addSub(Dep.target); //把watcher實(shí)例保存到屬性對(duì)應(yīng)的dep中,因此依賴(lài)obj的屬性的watcher都會(huì)保存到屬性中
Dep.target = null;
}
return val
},
set: function(newVal){
var value = val
if (newVal === value) {
return
}
val = newVal
childOb = observe(newVal)
dep.notify() // 執(zhí)行屬性中保存的watcher的update方法更新組件/指令的頁(yè)面
}
})
}
其中dep的方法層層封裝非常繞,詳細(xì)代碼本文忽略。
vuex/store也是用get/set方法實(shí)現(xiàn)數(shù)據(jù)響應(yīng),是通過(guò)加了一層computed方法實(shí)現(xiàn)的,computed方法再調(diào)用getter方法訪問(wèn)store的屬性數(shù)據(jù),computed方法會(huì)創(chuàng)建
watcher,當(dāng)調(diào)用vuex getter方法獲取屬性的值時(shí)會(huì)把自身watcher實(shí)例保存在屬性中,當(dāng)set屬性時(shí)就會(huì)執(zhí)行watcher的update方法更新data屬性,頁(yè)面可以綁定
data屬性,當(dāng)data屬性變化時(shí),頁(yè)面會(huì)更新,因?yàn)関ue已經(jīng)針對(duì)頁(yè)面綁定表達(dá)式創(chuàng)建了watcher,針對(duì)data屬性創(chuàng)建了get/set方法,已經(jīng)建立了數(shù)據(jù)響應(yīng)機(jī)制。
所以當(dāng)使用computed方法時(shí),還是很復(fù)雜很繞的,當(dāng)數(shù)據(jù)變化時(shí),從store到頁(yè)面是通過(guò)好幾層watcher實(shí)現(xiàn)數(shù)據(jù)響應(yīng)的,頁(yè)面表達(dá)式有一層watcher,cb是指令的
update方法,比如對(duì)于{{title}}這樣最簡(jiǎn)單的表達(dá)式其實(shí)就是一個(gè)文本指令,其update方法就是要取title的值插入做為文本內(nèi)容,computed方法又有一層watcher,
cb=null,因?yàn)閏omputed方法只是執(zhí)行方法代碼獲取值,不同于指令,沒(méi)有cb,無(wú)需處理網(wǎng)頁(yè)元素。
下面是vue構(gòu)造comptedGetter方法的代碼:
function makeComputedGetter(store, getter) {
var id = store._getterCacheId;
if (getter[id]) {
return getter[id];
}
var vm = store._vm;
var Watcher = getWatcher(vm);
var Dep = getDep(vm);
var watcher = new Watcher(vm, function (vm) { // 創(chuàng)建watcher,在執(zhí)行g(shù)etter方法取值時(shí)把自身保存到屬性中
return getter(vm.state);
}, null, { lazy: true });
var computedGetter = function computedGetter() { //computedGetter
if (watcher.dirty) {
watcher.evaluate();
}
if (Dep.target) {
watcher.depend();
}
return watcher.value;
};
getter[id] = computedGetter;
return computedGetter;
}
watcher實(shí)例里面,proto里面有一個(gè)update方法,這是watcher通用update方法,還有一個(gè)cb,這是指令通用update方法,
指令通用update方法會(huì)調(diào)用指令specific update方法(如果有的話)。
下面是關(guān)于在組件標(biāo)簽用v-on:綁定事件的代碼細(xì)節(jié):
//Register v-on events on a child component
function registerComponentEvents(vm, el) {
name = attrs[i].name;
if (eventRE.test(name)) {
handler = (vm._scope || vm._context).$eval(value, true); //v-on=后面的寫(xiě)法需要解析處理一下
handler._fromParent = true; //事件在當(dāng)前初始化的組件,方法在父組件
vm.$on(name.replace(eventRE), handler); // 綁定邏輯事件,邏輯事件用$emit觸發(fā)
事件用addeventlistener和$on兩種方法綁定,如果是物理事件,比如物理click事件,前者有效,如果是邏輯事件,前者
無(wú)效,后者有效,后者是vue自己建立的_events[]數(shù)據(jù),handler方法在父組件。
在標(biāo)簽寫(xiě)v-on只能綁定組件里面的邏輯事件,組件里面用$emit觸發(fā)邏輯事件,執(zhí)行父組件里面的handler方法,不能監(jiān)聽(tīng)子組件里面的物理click事件,
在子組件里面才能寫(xiě)v-on綁定物理click事件。
下面是關(guān)于checkbox元素用v-model雙向綁定的源代碼分析,checkbox handler代碼:
var checkbox = {
bind: function bind() {
this.listener = function () {
var model = self._watcher.get(); //獲取v-model=表達(dá)式的值,也就是變量屬性值option.checked,涉及v-for循環(huán)變量/scope
if (isArray(model)) {
} else {
self.set(getBooleanValue()); //獲取元素checked屬性值,傳遞給set方法
}
Directive.prototype.set = function (value) {
if (this.twoWay) {
this._withLock(function () {
this._watcher.set(value);
Watcher.prototype.set = function (value) {
this.setter.call(scope, scope, value); //調(diào)用變量屬性的setter方法設(shè)置值
function (scope, val) { //watcher的setter
setPath(scope, path, val);
function setPath(obj, path, val) {
obj[key] = val;
set(obj, key, val);//如果屬性不存在,就調(diào)Vue.set添加屬性?
this.getValue = function () {
return el.hasOwnProperty('_value') ? el._value : self.params.number ? toNumber(el.value) : el.value;
};
function getBooleanValue() {
var val = el.checked;
if (val && el.hasOwnProperty('_trueValue')) {
return el._trueValue;
}
if (!val && el.hasOwnProperty('_falseValue')) {
return el._falseValue;
}
return val;
}
this.on('change', this.listener);
可見(jiàn)v-model底層就是綁定'change'事件,就是獲取元素checked屬性值,可以重新定義checked屬性值,那就取定義的值,一般不。
當(dāng)點(diǎn)擊checkbox元素時(shí),其checked屬性會(huì)變化,v-model獲取元素checked屬性值同步到變量,反之則是根據(jù)變量值設(shè)置元素
的checked屬性值,從而實(shí)現(xiàn)兩個(gè)方向的數(shù)據(jù)同步。
vue初始化組件時(shí)處理template的代碼:
function transcludeTemplate(el, options) {
var template = options.template;
var frag = parseTemplate(template, true);
function parseTemplate(template, shouldClone, raw) {
if (typeof template === 'string') {
//template如果寫(xiě)#id,則去網(wǎng)頁(yè)按id找元素,這已經(jīng)是DOM對(duì)象了
//如果template是html字符串,則用innerHTML編譯為DOM對(duì)象再插入到frag
frag = stringToFragment(template, raw);
function stringToFragment(templateString, raw) {
node.innerHTML = prefix + templateString + suffix; //前后綴是有可能包裹一層標(biāo)簽
while (child = node.firstChild) {
frag.appendChild(child);
//這是移動(dòng)子元素,循環(huán)結(jié)果把所有子元素都移動(dòng)插入到frag中去,template可以寫(xiě)多個(gè)<div>
function nodeToFragment(node) {
// script template,template可以寫(xiě)在網(wǎng)頁(yè)的<script>標(biāo)簽中
if (node.tagName === 'SCRIPT') {
return stringToFragment(node.textContent);
}
因此在組件中寫(xiě)template=只能是html代碼字符串,或者#id指向網(wǎng)頁(yè)中的元素,沒(méi)有其它寫(xiě)法,不能寫(xiě)js表達(dá)式,
也不能用js代碼修改template,按id找元素可以把template寫(xiě)在網(wǎng)頁(yè)的<script>中,類(lèi)似angular,也可以寫(xiě)在網(wǎng)頁(yè)中的
<template>標(biāo)簽元素中:
<template id="t1">
<div>{{context}}</div>
</template>
如果構(gòu)造<template>插入網(wǎng)頁(yè),<template>類(lèi)似frag,本身是虛擬節(jié)點(diǎn),在網(wǎng)頁(yè)不占節(jié)點(diǎn)。
?
本文到此就差不多結(jié)束了,本文寫(xiě)的比較粗糙,因?yàn)関ue 1.0源碼是以前研究的,當(dāng)時(shí)沒(méi)有很好地整理出來(lái),后來(lái)復(fù)制了幾段源代碼分析記錄,大概編輯了一下就此發(fā)表,總比爛在肚子里強(qiáng),本人對(duì)vue 1.0和2.0進(jìn)行了深入的研究,進(jìn)行了無(wú)數(shù)次debug看數(shù)據(jù),因?yàn)楸救苏J(rèn)為vue非常優(yōu)秀實(shí)用,值得學(xué)習(xí),而angular和react太高大上,一般人沒(méi)法學(xué)習(xí),文中錯(cuò)誤歡迎批評(píng)指正交流。
?
轉(zhuǎn)載于:https://www.cnblogs.com/pzhu1/p/9007441.html
新人創(chuàng)作打卡挑戰(zhàn)賽發(fā)博客就能抽獎(jiǎng)!定制產(chǎn)品紅包拿不停!總結(jié)
以上是生活随笔為你收集整理的vue 1.0源代码重点难点分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: RBAC权限模型及数据权限扩展的实践
- 下一篇: 如何运行vue项目