您好,登錄后才能下訂單哦!
這篇文章主要介紹“Vue.js的Hello World舉例分析”,在日常操作中,相信很多人在Vue.js的Hello World舉例分析問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Vue.js的Hello World舉例分析”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
構造函數
文件路徑:src/instance/vue.js
function Vue (options) { this._init(options) }
初始化
這里只拿對例子理解最關鍵的步驟分析。文件路徑:src/instance/internal/init.js
Vue.prototype._init = function (options) { ... // merge options. options = this.$options = mergeOptions( this.constructor.options, options, this ) ... // initialize data observation and scope inheritance. this._initState() ... // if `el` option is passed, start compilation. if (options.el) { this.$mount(options.el) } }
merge options
mergeOptions()定義在src/util/options.js文件中,這里主要定義options中各種屬性的合并(merge),例如:props, methods, computed, watch等。另外,這里還定義了每種屬性merge的默認算法(strategy),這些strategy都可以配置的,參考Custom Option Merge Strategy
在本文的例子中,主要是data選項的merge,在merge之后,放到$options.data中,基本相當于下面這樣:
vm.$options.data = function mergedInstanceDataFn () { var parentVal = undefined // 這里就是在我們定義的options中的data var childVal = function () { return { message: 'Hello World' } } // data function綁定vm實例后執行,執行結果: {message: 'Hello World'} var instanceData = childVal.call(vm) // 對象之間的merge,類似$.extend,結果肯定就是:{message: 'Hello World'} return mergeData(instanceData, parentVal) }
init data
_initData()發生在_initState()中,主要做了兩件事:
代理data中的屬性
observe data
文件路徑:src/instance/internal/state.js
Vue.prototype._initState = function () { this._initProps() this._initMeta() this._initMethods() this._initData() // 這里 this._initComputed() }
屬性代理(proxy)
把data的結果賦值給內部屬性:文件路徑:src/instance/internal/state.js
var dataFn = this.$options.data // 上面我們得到的mergedInstanceDataFn函數 var data = this._data = dataFn ? dataFn() : {}
代理(proxy)data中的屬性到_data,使得vm.message === vm._data.message:
文件路徑:src/instance/internal/state.js
/** * Proxy a property, so that * vm.prop === vm._data.prop */ Vue.prototype._proxy = function (key) { if (!isReserved(key)) { 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 } }) } }
observe
這里是我們的***個重點,observe過程。在_initData()***,調用了observe(data, this)對數據進行observe。在hello world例子里,observe()函數主要是針對{message: 'Hello World'}創建了Observer對象。
文件路徑:src/observer/index.js
var ob = new Observer(value) // value = data = {message:'Hello World'}
在observe()函數中還做了些能否observe的條件判斷,這些條件有:
沒有被observe過(observe過的對象都會被添加__ob__屬性)
只能是plain object(toString.call(ob) === "[object Object]")或者數組
不能是Vue實例(obj._isVue !== true)
object是extensible的(Object.isExtensible(obj) === true)
Observer
官網的Reactivity in Depth上有這么句話:
When you pass a plain JavaScript object to a Vue instance as its data option, Vue.js will walk through all of its properties and convert them to getter/setters
The getter/setters are invisible to the user, but under the hood they enable Vue.js to perform dependency-tracking and change-notification when properties are accessed or modified
Observer就是干這個事情的,使data變成“發布者”,watcher是訂閱者,訂閱data的變化。
在例子中,創建observer的過程是:
new Observer({message: 'Hello World'})
實例化一個Dep對象,用來收集依賴
walk(Observer.prototype.walk())數據的每一個屬性,這里只有message
將屬性變成reactive的(Observer.protoype.convert())
convert()里調用了defineReactive(),給data的message屬性添加reactiveGetter和reactiveSetter
文件路徑:src/observer/index.js
export function defineReactive (obj, key, value) { ... Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { ... if (Dep.target) { dep.depend() // 這里是收集依賴 ... } return value }, set: function reactiveSetter (newVal) { ... if (setter) { setter.call(obj, newVal) } else { val = newVal } ... dep.notify() // 這里是notify觀察這個數據的依賴(watcher) } }) }
關于依賴收集和notify,主要是Dep類
文件路徑:src/observer/dep.js
export default function Dep () { this.id = uid++ this.subs = [] }
這里的subs是保存著訂閱者(即watcher)的數組,當被觀察數據發生變化時,即被調用setter,那么dep.notify()就循環這里的訂閱者,分別調用他們的update方法。
但是在getter收集依賴的代碼里,并沒有看到watcher被添加到subs中,什么時候添加進去的呢?這個問題在講到Watcher的時候再回答。
mount node
按照生命周期圖上,observe data和一些init之后,就是$mount了,最主要的就是_compile。
文件路徑:src/instance/api/lifecycle.js
Vue.prototype.$mount = function (el) { ... this._compile(el) ... }
_compile里分兩步:compile和link
compile
compile過程是分析給定元素(el)或者模版(template),提取指令(directive)和創建對應離線的DOM元素(document fragment)。
文件路徑:src/instance/internal/lifecycle.js
Vue.prototype._compile = function (el) { ... var rootLinker = compileRoot(el, options, contextOptions) ... var rootUnlinkFn = rootLinker(this, el, this._scope) ... var contentUnlinkFn = compile(el, options)(this, el) ... }
例子中compile #mountNode元素,大致過程如下:
compileRoot:由于root node(<div id="mountNode"></div>)本身沒有任何指令,所以這里compile不出什么東西
compileChildNode:mountNode的子node,即內容為"{{message}}"的TextNode
compileTextNode:
3.1 parseText:其實就是tokenization(標記化:從字符串中提取符號,語句等有意義的元素),得到的結果是tokens
3.2 processTextToken:從tokens中分析出指令類型,表達式和過濾器,并創建新的空的TextNode
3.3 創建fragment,將新的TextNode append進去
parseText的時候,通過正則表達式(/\{\{\{(.+?)\}\}\}|\{\{(.+?)\}\}/g)匹配字符串"{{message}}",得出的token包含這些信息:“這是個tag,而且是文本(text)而非HTML的tag,不是一次性的插值(one-time interpolation),tag的內容是"message"”。這里用來做匹配的正則表達式是會根據delimiters和unsafeDelimiters的配置動態生成的。
processTextToken之后,其實就得到了創建指令需要的所有信息:指令類型v-text,表達式"message",過濾器無,并且該指令負責跟進的DOM是新創建的TextNode。接下來就是實例化指令了。
link
每個compile函數之后都會返回一個link function(linkFn)。linkFn就是去實例化指令,將指令和新建的元素link在一起,然后將元素替換到DOM tree中去。每個linkFn函數都會返回一個unlink function(unlinkFn)。unlinkFn是在vm銷毀的時候用的,這里不介紹。
實例化directive:new Directive(description, vm, el)
description是compile結果token中保存的信息,內容如下:
description = { name: 'text', // text指令 expression: 'message', filters: undefined, def: vTextDefinition }
def屬性上的是text指令的定義(definition),和Custome Directive一樣,text指令也有bind和update方法,其定義如下:
文件路徑:src/directives/public/text.js
export default { bind () { this.attr = this.el.nodeType === 3 ? 'data' : 'textContent' }, update (value) { this.el[this.attr] = _toString(value) } }
new Directive()構造函數里面只是一些內部屬性的賦值,真正的綁定過程還需要調用Directive.prototype._bind,它是在Vue實例方法_bindDir()中被調用的。
在_bind里面,會創建watcher,并***次通過watcher去獲得表達式"message"的計算值,更新到之前新建的TextNode中去,完成在頁面上渲染"Hello World"。
watcher
For every directive / data binding in the template, there will be a corresponding watcher object, which records any properties “touched” during its evaluation as dependencies. Later on when a dependency’s setter is called, it triggers the watcher to re-evaluate, and in turn causes its associated directive to perform DOM updates.
每個與數據綁定的directive都有一個watcher,幫它監聽表達式的值,如果發生變化,則通知它update自己負責的DOM。一直說的dependency collection就在這里發生。
Directive.prototype._bind()里面,會new Watcher(expression, update),把表達式和directive的update方法傳進去。
Watcher會去parseExpression:
文件路徑:src/parsers/expression.js
export function parseExpression (exp, needSet) { exp = exp.trim() // try cache var hit = expressionCache.get(exp) if (hit) { if (needSet && !hit.set) { hit.set = compileSetter(hit.exp) } return hit } var res = { exp: exp } res.get = isSimplePath(exp) && exp.indexOf('[') < 0 // optimized super simple getter ? makeGetterFn('scope.' + exp) // dynamic getter : compileGetter(exp) if (needSet) { res.set = compileSetter(exp) } expressionCache.put(exp, res) return res }
這里的expression是"message",單一變量,被認為是簡單的數據訪問路徑(simplePath)。simplePath的值如何計算,怎么通過"message"字符串獲得data.message的值呢?
獲取字符串對應的變量的值,除了用eval,還可以用Function。上面的makeGetterFn('scope.' + exp)返回:
var getter = new Function('scope', 'return ' + body + ';') // new Function('scope', 'return scope.message;')
Watch.prototype.get()獲取表達式值的時候,
var scope = this.vm getter.call(scope, scope) // 即執行vm.message
由于initState時對數據進行了代理(proxy),這里的vm.message即為vm._data.message,即是data選項中定義的"Hello World"。
值拿到了,那什么時候將message設為依賴的呢?這就要結合前面observe data里說到的reactiveGetter了。
文件路徑:src/watcher.js
Watcher.prototype.get = function () { this.beforeGet() // -> Dep.target = this var scope = this.scope || this.vm ... var value value = this.getter.call(scope, scope) ... this.afterGet() // -> Dep.target = null return value }
watcher獲取表達式的值分三步:
beforeGet:設置Dep.target = this
調用表達式的getter,讀取(getter)vm.message的值,進入了message的reactiveGetter,由于Dep.target有值,因此執行了dep.depend()將target,即當前watcher,收入dep.subs數組里
afterGet:設置Dep.target = null
這里值得注意的是Dep.target,由于JS的單線程特性,同一時刻只能有一個watcher去get數據的值,所以target在全局下只需要有一個就可以了。
文件路徑:src/observer/dep.js
// the current target watcher being evaluated. // this is globally unique because there could be only one // watcher being evaluated at any time. Dep.target = null
就這樣,指令通過watcher,去touch了表達式中涉及到的數據,同時被該數據(reactive data)保存為其變化的訂閱者(subscriber),數據變化時,通過dep.notify() -> watcher.update() -> directive.update() -> textDirective.update(),完成DOM的更新。
到此,關于“Vue.js的Hello World舉例分析”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。