摘要:整體概覽源碼最終是向外部拋出一個的構(gòu)造函數(shù),見源碼在源碼最開始,通過方法見源碼向構(gòu)造函數(shù)添加全局方法,如等,主要初始化一些全局使用的方法變量和配置實(shí)例化當(dāng)使用時,最基本使用方式如下此時,會調(diào)用構(gòu)造函數(shù)實(shí)例化一個對象,而在構(gòu)造函數(shù)中只有這句代
整體概覽
Vue源碼最終是向外部拋出一個Vue的構(gòu)造函數(shù),見源碼:
function Vue (options) { this._init(options) }
在源碼最開始,通過installGlobalAPI方法(見源碼)向Vue構(gòu)造函數(shù)添加全局方法,如Vue.extend、Vue.nextTick、Vue.delete等,主要初始化Vue一些全局使用的方法、變量和配置;
export default function (Vue){ Vue.options = { ..... } Vue.extend = function (extendOptions){ ...... } Vue.use = function (plugin){ ...... } Vue.mixin = function (mixin){ ...... } Vue.extend = function (extendOptions){ ...... } }實(shí)例化Vue
當(dāng)使用vue時,最基本使用方式如下:
var app = new Vue({ el: "#app", data: { message: "Hello Vue!" } })
此時,會調(diào)用構(gòu)造函數(shù)實(shí)例化一個vue對象,而在構(gòu)造函數(shù)中只有這句代碼this.init(options);而在init中(源碼),主要進(jìn)行一些變量的初始化、option重組、各種狀態(tài)、事件初始化;如下:
Vue.prototype._init = function (options) { options = options || {} this.$el = null this.$parent = options.parent this.$root = this.$parent ? this.$parent.$root : this this.$children = [] this.$refs = {} // child vm references this.$els = {} // element references this._watchers = [] // all watchers as an array this._directives = [] // all directives ...... // 更多見源碼 options = this.$options = mergeOptions( this.constructor.options, options, this ) // set ref this._updateRef() // initialize data as empty object. // it will be filled up in _initData(). this._data = {} // call init hook this._callHook("init") // initialize data observation and scope inheritance. this._initState() // setup event system and option events. this._initEvents() // call created hook this._callHook("created") // if `el` option is passed, start compilation. if (options.el) { this.$mount(options.el) } }
在其中通過mergeOptions方法,將全局this.constructor.options與傳入的options及實(shí)例化的對象進(jìn)行合并;而this.constructor.options則是上面初始化vue時進(jìn)行配置的,其中主要包括一些全局使用的指令、過濾器,如經(jīng)常使用的"v-if"、"v-for"、"v-show"、"currency":
this.constructor.options = { directives: { bind: {}, // v-bind cloak: {}, // v-cloak el: {}, // v-el for: {}, // v-for html: {}, // v-html if: {}, // v-if for: {}, // v-for text: {}, // v-text model: {}, // v-model on: {}, // v-on show: {} // v-show }, elementDirectives: { partial: {}, //api: https://v1.vuejs.org/api/#partial slot: {} // }, filters: { // api: https://v1.vuejs.org/api/#Filters capitalize: function() {}, // {{ msg | capitalize }} ‘a(chǎn)bc’ => ‘Abc’ currency: funnction() {}, debounce: function() {}, filterBy: function() {}, json: function() {}, limitBy: function() {}, lowercase: function() {}, orderBy: function() {}, pluralize: function() {}, uppercase: function() {} } }
然后,會觸發(fā)初始化一些狀態(tài)、事件、觸發(fā)init、create鉤子;然后隨后,會觸發(fā)this.$mount(options.el);進(jìn)行實(shí)例掛載,將dom添加到頁面;而this.$mount()方法則包含了絕大部分頁面渲染的代碼量,包括模板的嵌入、編譯、link、指令和watcher的生成、批處理的執(zhí)行等等,后續(xù)會詳細(xì)進(jìn)行說明;
_compile函數(shù)之transclude在上面說了下,在Vue.prototype.$mount完成了大部分工作,而在$mount方法里面,最主要的工作量由this._compile(el)承擔(dān);其主要包括transclude(嵌入)、compileRoot(根節(jié)點(diǎn)編譯)、compile(頁面其他的編譯);而在這兒主要說明transclude方法;
通過對transclude進(jìn)行網(wǎng)絡(luò)翻譯結(jié)果是"嵌入";其主要目的是將頁面中自定義的節(jié)點(diǎn)轉(zhuǎn)化為真實(shí)的html節(jié)點(diǎn);如一個組件hello {{message}}
hello {{message}}
那transclude具體干了什么呢,我們先看它的源碼:
export function transclude (el, options) { // extract container attributes to pass them down // to compiler, because they need to be compiled in // parent scope. we are mutating the options object here // assuming the same object will be used for compile // right after this. if (options) { // 把el(虛擬節(jié)點(diǎn),如)元素上的所有attributes抽取出來存放在了選項(xiàng)對象的_containerAttrs屬性上 // 使用el.attributes 方法獲取el上面,并使用toArray方法,將類數(shù)組轉(zhuǎn)換為真實(shí)數(shù)組 options._containerAttrs = extractAttrs(el) } // for template tags, what we want is its content as // a documentFragment (for fragment instances) // 判斷是否為 template 標(biāo)簽 if (isTemplate(el)) { // 得到一段存放在documentFragment里的真實(shí)dom el = parseTemplate(el) } if (options) { if (options._asComponent && !options.template) { options.template = " " } if (options.template) { // 將el的內(nèi)容(子元素和文本節(jié)點(diǎn))抽取出來 options._content = extractContent(el) // 使用options.template 將虛擬節(jié)點(diǎn)轉(zhuǎn)化為真實(shí)html, => // 但不包括未綁定數(shù)據(jù), 則上面轉(zhuǎn)化為 =>hello {{ msg }}
el = transcludeTemplate(el, options) } } // isFragment: node is a DocumentFragment // 使用nodeType 為 11 進(jìn)行判斷是非為文檔片段 if (isFragment(el)) { // anchors for fragment instance // passing in `persist: true` to avoid them being // discarded by IE during template cloning prepend(createAnchor("v-start", true), el) el.appendChild(createAnchor("v-end", true)) } return el }hello
首先先看如下代碼:
if (options) { // 把el(虛擬節(jié)點(diǎn),如)元素上的所有attributes抽取出來存放在了選項(xiàng)對象的_containerAttrs屬性上 // 使用el.attributes 方法獲取el上面,并使用toArray方法,將類數(shù)組轉(zhuǎn)換為真實(shí)數(shù)組 options._containerAttrs = extractAttrs(el) }
而extractAttrs方法如下,其主要根據(jù)元素nodeType去判斷是否為元素節(jié)點(diǎn),如果為元素節(jié)點(diǎn),且元素有相關(guān)屬性,則將屬性值取出之后,再轉(zhuǎn)為屬性數(shù)組;最后將屬性數(shù)組放到options._containerAttrs中,為什么要這么做呢?因?yàn)楝F(xiàn)在的el可能不是真實(shí)的元素,而是諸如
function extractAttrs (el) { // 只查找元素節(jié)點(diǎn)及有屬性 if (el.nodeType === 1 && el.hasAttributes()) { // attributes 屬性返回指定節(jié)點(diǎn)的屬性集合,即 NamedNodeMap, 類數(shù)組 return toArray(el.attributes) } }
下一句,根據(jù)元素nodeName是否為“template”去判斷是否為元素;如果是,則走parseTemplate(el)方法,并覆蓋當(dāng)前el對象
if (isTemplate(el)) { // 得到一段存放在documentFragment里的真實(shí)dom el = parseTemplate(el) } function isTemplate (el) { return el.tagName && el.tagName.toLowerCase() === "template" }
而parseTemplate則主要是將傳入內(nèi)容生成一段存放在documentFragment里的真實(shí)dom;進(jìn)入函數(shù),首先判斷傳入是否已經(jīng)是一個文檔片段,如果已經(jīng)是,則直接返回;否則,判斷傳入是否為字符串,如果為字符串, 先判斷是否是"#test"這種選擇器類型,如果是,通過document.getElementById方法取出元素,如果文檔中有此元素,將通過nodeToFragment方式,將其放入一個新的節(jié)點(diǎn)片段中并賦給frag,最后返回到外面;如果不是選擇器類型字符串,則使用stringToFragment將其生成一個新的節(jié)點(diǎn)片段,并返回;如果傳入非字符串而是節(jié)點(diǎn)(不管是什么節(jié)點(diǎn),可以是元素節(jié)點(diǎn)、文本節(jié)點(diǎn)、甚至Comment節(jié)點(diǎn)等);則直接通過nodeToFragment生成節(jié)點(diǎn)片段并返回;
export function parseTemplate (template, shouldClone, raw) { var node, frag // if the template is already a document fragment, // do nothing // 是否為文檔片段, nodetype是否為11 // https://developer.mozilla.org/zh-CN/docs/Web/API/DocumentFragment // 判斷傳入是否已經(jīng)是一個文檔片段,如果已經(jīng)是,則直接返回 if (isFragment(template)) { trimNode(template) return shouldClone ? cloneNode(template) : template } // 判斷傳入是否為字符串 if (typeof template === "string") { // id selector if (!raw && template.charAt(0) === "#") { // id selector can be cached too frag = idSelectorCache.get(template) if (!frag) { node = document.getElementById(template.slice(1)) if (node) { frag = nodeToFragment(node) // save selector to cache idSelectorCache.put(template, frag) } } } else { // normal string template frag = stringToFragment(template, raw) } } else if (template.nodeType) { // a direct node frag = nodeToFragment(template) } return frag && shouldClone ? cloneNode(frag) : frag }
從上面可見,在parseTemplate里面最重要的是nodeToFragment和stringToFragment;那么,它們又是如何將傳入內(nèi)容轉(zhuǎn)化為新的文檔片段呢?首先看nodeToFragment:
function nodeToFragment (node) { // if its a template tag and the browser supports it, // its content is already a document fragment. However, iOS Safari has // bug when using directly cloned template content with touch // events and can cause crashes when the nodes are removed from DOM, so we // have to treat template elements as string templates. (#2805) /* istanbul ignore if */ // 是template元素或者documentFragment,使用stringToFragment轉(zhuǎn)化并保存節(jié)點(diǎn)內(nèi)容 if (isRealTemplate(node)) { return stringToFragment(node.innerHTML) } // script template if (node.tagName === "SCRIPT") { return stringToFragment(node.textContent) } // normal node, clone it to avoid mutating the original var clonedNode = cloneNode(node) var frag = document.createDocumentFragment() var child /* eslint-disable no-cond-assign */ while (child = clonedNode.firstChild) { /* eslint-enable no-cond-assign */ frag.appendChild(child) } trimNode(frag) return frag }
其實(shí)看源碼,很容易理解,首先判斷傳入內(nèi)容是否為template元素或者documentFragment或者script標(biāo)簽,如果是,都直接走stringToFragment;后面就是先使用document.createDocumentFragment創(chuàng)建一個文檔片段,然后將節(jié)點(diǎn)進(jìn)行循環(huán)appendChild到創(chuàng)建的文檔片段中,并返回新的片段;
那么,stringToFragment呢?這個就相對復(fù)雜一點(diǎn)了,如下:
function stringToFragment (templateString, raw) { // try a cache hit first var cacheKey = raw ? templateString : templateString.trim() //trim() 方法會從一個字符串的兩端刪除空白字符 var hit = templateCache.get(cacheKey) if (hit) { return hit } // 創(chuàng)建一個文檔片段 var frag = document.createDocumentFragment() // tagRE: /<([w:-]+)/ // 匹配標(biāo)簽 // "".match(/<([w:-]+)/) => [" "] var tagMatch = templateString.match(tagRE) // entityRE: /?w+?;/ var entityMatch = entityRE.test(templateString) // commentRE: /
它對應(yīng)的descriptor就是:
descriptor = { arg: undefined, attr: "v-demo", def: { bind: function() {}, // 上面定義的bind update: function() {} // 上面定義的update }, expression:"demo", filters: undefined, modifiers: {}, name: "demo" }
接著上面的,使用extend(this, def)就將def中定義的方法或?qū)傩跃蛷?fù)制到實(shí)例化指令對象上面;好供后面使用;
// initial bind if (this.bind) { this.bind() }
這就是執(zhí)行上面剛剛保存的bind方法;當(dāng)執(zhí)行此方法時,上面就會執(zhí)行
this.el.setAttribute("style", "color: green");
將字體顏色改為綠色;
// 下面這些判斷是因?yàn)樵S多指令比如slot component之類的并不是響應(yīng)式的, // 他們只需要在bind里處理好dom的分發(fā)和編譯/link即可然后他們的使命就結(jié)束了,生成watcher和收集依賴等步驟根本沒有 // 所以根本不用執(zhí)行下面的處理 if (this.literal) { } else if ( (this.expression || this.modifiers) && (this.update || this.twoWay) && !this._checkStatement() ) { var watcher = this._watcher = new Watcher( this.vm, this.expression, this._update, // callback { filters: this.filters, twoWay: this.twoWay, deep: this.deep, preProcess: preProcess, postProcess: postProcess, scope: this._scope } ) }
而這兒就是對需要添加雙向綁定的指令添加watcher;對應(yīng)watcher后面再進(jìn)行詳細(xì)說明; 可以從上看出,傳入了this._update方法,其實(shí)也就是當(dāng)數(shù)據(jù)變化時,就會執(zhí)行this._update方法,而:
var dir = this if (this.update) { // 處理一下原本的update函數(shù),加入lock判斷 this._update = function (val, oldVal) { if (!dir._locked) { dir.update(val, oldVal) } } } else { this._update = function() {} }
其實(shí)也就是執(zhí)行上面的descriptor.def.update方法,所以當(dāng)值變化時,會觸發(fā)我們自定義指令時定義的update方法,而發(fā)生顏色變化;
這是指令最主要的代碼部分;其他的如下:
// 獲取指令的參數(shù), 對于一些指令, 指令的元素上可能存在其他的attr來作為指令運(yùn)行的參數(shù) // 比如v-for指令,那么元素上的attr: track-by="..." 就是參數(shù) // 比如組件指令,那么元素上可能寫了transition-mode="out-in", 諸如此類 this._setupParams(); // 當(dāng)一個指令需要銷毀時,對其進(jìn)行銷毀處理;此時,如果定義了unbind方法,也會在此刻調(diào)用 this._teardown(); 而對于每個指令的處理原理,可以看其對應(yīng)源碼;如v-show源碼: // src/directives/public/show.js import { getAttr, inDoc } from "../../util/index" import { applyTransition } from "../../transition/index" export default { bind () { // check else block var next = this.el.nextElementSibling if (next && getAttr(next, "v-else") !== null) { this.elseEl = next } }, update (value) { this.apply(this.el, value) if (this.elseEl) { this.apply(this.elseEl, !value) } }, apply (el, value) { if (inDoc(el)) { applyTransition(el, value ? 1 : -1, toggle, this.vm) } else { toggle() } function toggle () { el.style.display = value ? "" : "none" } } }
可以從上面看出在初始化頁面綁定時,主要獲取后面兄弟元素是否使用v-else; 如果使用,將元素保存到this.elseEl中,而當(dāng)值變化執(zhí)行update時,主要執(zhí)行了this.apply;而最終只是執(zhí)行了下面代碼:
el.style.display = value ? "" : "none"
從而達(dá)到隱藏或者展示元素的效果;
未完待續(xù),后續(xù)會持續(xù)完善......
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/93224.html
摘要:當(dāng)我們的視圖和數(shù)據(jù)任何一方發(fā)生變化的時候,我們希望能夠通知對方也更新,這就是所謂的數(shù)據(jù)雙向綁定。返回值返回傳入函數(shù)的對象,即第一個參數(shù)該方法重點(diǎn)是描述,對象里目前存在的屬性描述符有兩種主要形式數(shù)據(jù)描述符和存取描述符。 前言 談起當(dāng)前前端最熱門的 js 框架,必少不了 Vue、React、Angular,對于大多數(shù)人來說,我們更多的是在使用框架,對于框架解決痛點(diǎn)背后使用的基本原理往往關(guān)注...
摘要:當(dāng)前正在處理的節(jié)點(diǎn),以及該節(jié)點(diǎn)的和等信息。源碼解析之一整體分析源碼解析之三寫作中源碼解析之四寫作中作者博客作者作者微博 筆者系 vue-loader 貢獻(xiàn)者之一(#16) 前言 vue-loader 源碼解析系列之一,閱讀該文章之前,請大家首先參考大綱 vue-loader 源碼解析系列之 整體分析 selector 做了什么 const path = require(path) co...
摘要:源碼解析這邊解析的是從樹轉(zhuǎn)換成函數(shù)部分的源碼,由于第一次提交的源碼這部分不全,故做了部分更新,代碼全在文件夾中。入口整個語法樹轉(zhuǎn)函數(shù)的起點(diǎn)是文件中的函數(shù)明顯看到,函數(shù)傳入?yún)?shù)為語法樹,內(nèi)部調(diào)用函數(shù)開始解析根節(jié)點(diǎn)容器節(jié)點(diǎn)。 通過對 Vue2.0 源碼閱讀,想寫一寫自己的理解,能力有限故從尤大佬2016.4.11第一次提交開始讀,準(zhǔn)備陸續(xù)寫: 模版字符串轉(zhuǎn)AST語法樹 AST語法樹轉(zhuǎn)r...
摘要:源碼解析這邊解析的是從樹轉(zhuǎn)換成函數(shù)部分的源碼,由于第一次提交的源碼這部分不全,故做了部分更新,代碼全在文件夾中。入口整個語法樹轉(zhuǎn)函數(shù)的起點(diǎn)是文件中的函數(shù)明顯看到,函數(shù)傳入?yún)?shù)為語法樹,內(nèi)部調(diào)用函數(shù)開始解析根節(jié)點(diǎn)容器節(jié)點(diǎn)。 通過對 Vue2.0 源碼閱讀,想寫一寫自己的理解,能力有限故從尤大佬2016.4.11第一次提交開始讀,準(zhǔn)備陸續(xù)寫: 模版字符串轉(zhuǎn)AST語法樹 AST語法樹轉(zhuǎn)r...
閱讀 1241·2021-11-08 13:25
閱讀 1440·2021-10-13 09:40
閱讀 2774·2021-09-28 09:35
閱讀 736·2021-09-23 11:54
閱讀 1123·2021-09-02 15:11
閱讀 2431·2019-08-30 13:18
閱讀 1668·2019-08-30 12:51
閱讀 2686·2019-08-29 18:39