日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Vue.js源碼(1):HelloWorld的背后

下面的代碼會在頁面上輸出Hello World,但是在這個new Vue()到頁面渲染之間,到底發(fā)生了什么。這篇文章希望通過最簡單的例子,去了解Vue源碼過程。這里分析的源碼版本是Vue.version = '1.0.20'

成都創(chuàng)新互聯(lián)公司是一家專業(yè)提供鄂托克企業(yè)網(wǎng)站建設,專注與成都網(wǎng)站設計、網(wǎng)站建設、HTML5、小程序制作等業(yè)務。10年已為鄂托克眾多企業(yè)、政府機構等服務。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站建設公司優(yōu)惠進行中。

 
 
  1. {{message}}
 
 
 
  1. var vm = new Vue({
  2.     el: '#mountNode',
  3.     data: function () {
  4.         return {
  5.             message: 'Hello World'
  6.         };
  7.     }
  8. });

 這篇文章將要解決幾個問題:

  1. new Vue()的過程中,內(nèi)部到底有哪些步驟
  2. 如何收集依賴
  3. 如何計算表達式
  4. 如何表達式的值如何反應在DOM上的

簡單來說過程是這樣的:

  1. observe: 把{message: 'Hello World'}變成是reactive的
  2. compile: compileTextNode "{{message}}",解析出指令(directive = v-text)和表達式(expression = message),創(chuàng)建fragment(new TextNode)準備替換
  3. link:實例化directive,將創(chuàng)建的fragment和directive鏈接起來,將fragment替換在DOM上
  4. bind: 通過directive對應的watcher獲取依賴(message)的值("Hello World"),v-text去update值到fragment上

詳細過程,接著往下看。

構造函數(shù)

文件路徑:src/instance/vue.js

 
 
  1. function Vue (options) {
  2.   this._init(options)

初始化

這里只拿對例子理解最關鍵的步驟分析。文件路徑:src/instance/internal/init.js

 
 
  1. Vue.prototype._init = function (options) {
  2.     ...
  3.     // merge options.
  4.     options = this.$options = mergeOptions(
  5.       this.constructor.options,
  6.       options,
  7.       this
  8.     )
  9.     ...
  10.     // initialize data observation and scope inheritance.
  11.     this._initState()
  12.     ...
  13.     // if `el` option is passed, start compilation.
  14.     if (options.el) {
  15.       this.$mount(options.el)
  16.     }
  17. }

 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中,基本相當于下面這樣:

 
 
  1. vm.$options.data = function mergedInstanceDataFn () {
  2.       var parentVal = undefined
  3.       
  4.       // 這里就是在我們定義的options中的data
  5.       var childVal = function () {
  6.           return {
  7.               message: 'Hello World'
  8.           }
  9.       }
  10.       
  11.       // data function綁定vm實例后執(zhí)行,執(zhí)行結果: {message: 'Hello World'}
  12.       var instanceData = childVal.call(vm)
  13.       
  14.       // 對象之間的merge,類似$.extend,結果肯定就是:{message: 'Hello World'}
  15.       return mergeData(instanceData, parentVal)

init data

_initData()發(fā)生在_initState()中,主要做了兩件事:

  1. 代理data中的屬性
  2. observe data

文件路徑:src/instance/internal/state.js

 
 
  1. Vue.prototype._initState = function () {
  2.     this._initProps()
  3.     this._initMeta()
  4.     this._initMethods()
  5.     this._initData() // 這里
  6.     this._initComputed()
  7.   } 

屬性代理(proxy)

把data的結果賦值給內(nèi)部屬性:文件路徑:src/instance/internal/state.js

 
 
  1. var dataFn = this.$options.data // 上面我們得到的mergedInstanceDataFn函數(shù)
  2. var data = this._data = dataFn ? dataFn() : {} 

代理(proxy)data中的屬性到_data,使得vm.message === vm._data.message:

文件路徑:src/instance/internal/state.js

 
 
  1. /**
  2.   * Proxy a property, so that
  3.   * vm.prop === vm._data.prop
  4.   */
  5. Vue.prototype._proxy = function (key) {
  6.     if (!isReserved(key)) {
  7.       var self = this
  8.       Object.defineProperty(self, key, {
  9.         configurable: true,
  10.         enumerable: true,
  11.         get: function proxyGetter () {
  12.           return self._data[key]
  13.         },
  14.         set: function proxySetter (val) {
  15.           self._data[key] = val
  16.         }
  17.       })
  18.     }
  19.   }

 observe

這里是我們的***個重點,observe過程。在_initData()***,調(diào)用了observe(data, this)對數(shù)據(jù)進行observe。在hello world例子里,observe()函數(shù)主要是針對{message: 'Hello World'}創(chuàng)建了Observer對象。

文件路徑:src/observer/index.js

 
 
  1. var ob = new Observer(value) // value = data = {message:'Hello World'}

在observe()函數(shù)中還做了些能否observe的條件判斷,這些條件有:

  1. 沒有被observe過(observe過的對象都會被添加__ob__屬性)
  2. 只能是plain object(toString.call(ob) === "[object Object]")或者數(shù)組
  3. 不能是Vue實例(obj._isVue !== true)
  4. object是extensible的(Object.isExtensible(obj) === true)

Observer

官網(wǎng)的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變成“發(fā)布者”,watcher是訂閱者,訂閱data的變化。

 在例子中,創(chuàng)建observer的過程是:

  1. new Observer({message: 'Hello World'})
  2. 實例化一個Dep對象,用來收集依賴
  3. walk(Observer.prototype.walk())數(shù)據(jù)的每一個屬性,這里只有message
  4. 將屬性變成reactive的(Observer.protoype.convert())

convert()里調(diào)用了defineReactive(),給data的message屬性添加reactiveGetter和reactiveSetter

文件路徑:src/observer/index.js

 
 
  1. export function defineReactive (obj, key, value) {
  2.     ...
  3.     Object.defineProperty(obj, key, {
  4.     enumerable: true,
  5.     configurable: true,
  6.     get: function reactiveGetter () {
  7.       ...
  8.       if (Dep.target) {
  9.         dep.depend() // 這里是收集依賴
  10.         ...
  11.       }
  12.       return value
  13.     },
  14.     set: function reactiveSetter (newVal) {
  15.       ...
  16.       if (setter) {
  17.         setter.call(obj, newVal)
  18.       } else {
  19.         val = newVal
  20.       }
  21.       ...
  22.       dep.notify() // 這里是notify觀察這個數(shù)據(jù)的依賴(watcher)
  23.     }
  24.   })
  25. }

 關于依賴收集和notify,主要是Dep類

文件路徑:src/observer/dep.js

 
 
  1. export default function Dep () {
  2.   this.id = uid++
  3.   this.subs = []
  4. }

 這里的subs是保存著訂閱者(即watcher)的數(shù)組,當被觀察數(shù)據(jù)發(fā)生變化時,即被調(diào)用setter,那么dep.notify()就循環(huán)這里的訂閱者,分別調(diào)用他們的update方法。

但是在getter收集依賴的代碼里,并沒有看到watcher被添加到subs中,什么時候添加進去的呢?這個問題在講到Watcher的時候再回答。

mount node

按照生命周期圖上,observe data和一些init之后,就是$mount了,最主要的就是_compile。

文件路徑:src/instance/api/lifecycle.js

 
 
  1. Vue.prototype.$mount = function (el) {
  2.     ...
  3.     this._compile(el)
  4.     ...
  5.   } 

_compile里分兩步:compile和link

compile

compile過程是分析給定元素(el)或者模版(template),提取指令(directive)和創(chuàng)建對應離線的DOM元素(document fragment)。

文件路徑:src/instance/internal/lifecycle.js

 
 
  1. Vue.prototype._compile = function (el) {
  2.     ...
  3.     var rootLinker = compileRoot(el, options, contextOptions)
  4.     ...
  5.     var rootUnlinkFn = rootLinker(this, el, this._scope)
  6.     ...
  7.     var contentUnlinkFn = compile(el, options)(this, el)
  8.     ...
  9. }

 例子中compile #mountNode元素,大致過程如下:

  1. compileRoot:由于root node(
    )本身沒有任何指令,所以這里compile不出什么東西
  2. compileChildNode:mountNode的子node,即內(nèi)容為"{{message}}"的TextNode
  3. compileTextNode:

3.1 parseText:其實就是tokenization(標記化:從字符串中提取符號,語句等有意義的元素),得到的結果是tokens

3.2 processTextToken:從tokens中分析出指令類型,表達式和過濾器,并創(chuàng)建新的空的TextNode

3.3 創(chuàng)建fragment,將新的TextNode append進去

parseText的時候,通過正則表達式(/\{\{\{(.+?)\}\}\}|\{\{(.+?)\}\}/g)匹配字符串"{{message}}",得出的token包含這些信息:“這是個tag,而且是文本(text)而非HTML的tag,不是一次性的插值(one-time interpolation),tag的內(nèi)容是"message"”。這里用來做匹配的正則表達式是會根據(jù)delimiters和unsafeDelimiters的配置動態(tài)生成的。

processTextToken之后,其實就得到了創(chuàng)建指令需要的所有信息:指令類型v-text,表達式"message",過濾器無,并且該指令負責跟進的DOM是新創(chuàng)建的TextNode。接下來就是實例化指令了。

link

每個compile函數(shù)之后都會返回一個link function(linkFn)。linkFn就是去實例化指令,將指令和新建的元素link在一起,然后將元素替換到DOM tree中去。每個linkFn函數(shù)都會返回一個unlink function(unlinkFn)。unlinkFn是在vm銷毀的時候用的,這里不介紹。

實例化directive:new Directive(description, vm, el)

description是compile結果token中保存的信息,內(nèi)容如下:

 
 
  1. description = {
  2.     name: 'text', // text指令
  3.     expression: 'message',
  4.     filters: undefined,
  5.     def: vTextDefinition
  6. }

 def屬性上的是text指令的定義(definition),和Custome Directive一樣,text指令也有bind和update方法,其定義如下:

文件路徑:src/directives/public/text.js

 
 
  1. export default {
  2.   bind () {
  3.     this.attr = this.el.nodeType === 3
  4.       ? 'data'
  5.       : 'textContent'
  6.   },
  7.   update (value) {
  8.     this.el[this.attr] = _toString(value)
  9.   }
  10. }

 new Directive()構造函數(shù)里面只是一些內(nèi)部屬性的賦值,真正的綁定過程還需要調(diào)用Directive.prototype._bind,它是在Vue實例方法_bindDir()中被調(diào)用的。

在_bind里面,會創(chuàng)建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.

每個與數(shù)據(jù)綁定的directive都有一個watcher,幫它監(jiān)聽表達式的值,如果發(fā)生變化,則通知它update自己負責的DOM。一直說的dependency collection就在這里發(fā)生。

Directive.prototype._bind()里面,會new Watcher(expression, update),把表達式和directive的update方法傳進去。

Watcher會去parseExpression:

文件路徑:src/parsers/expression.js

 
 
  1. export function parseExpression (exp, needSet) {
  2.   exp = exp.trim()
  3.   // try cache
  4.   var hit = expressionCache.get(exp)
  5.   if (hit) {
  6.     if (needSet && !hit.set) {
  7.       hit.set = compileSetter(hit.exp)
  8.     }
  9.     return hit
  10.   }
  11.   var res = { exp: exp }
  12.   res.get = isSimplePath(exp) && exp.indexOf('[') < 0
  13.     // optimized super simple getter
  14.     ? makeGetterFn('scope.' + exp)
  15.     // dynamic getter
  16.     : compileGetter(exp)
  17.   if (needSet) {
  18.     res.set = compileSetter(exp)
  19.   }
  20.   expressionCache.put(exp, res)
  21.   return res

這里的expression是"message",單一變量,被認為是簡單的數(shù)據(jù)訪問路徑(simplePath)。simplePath的值如何計算,怎么通過"message"字符串獲得data.message的值呢?

獲取字符串對應的變量的值,除了用eval,還可以用Function。上面的makeGetterFn('scope.' + exp)返回:

 
 
  1. var getter = new Function('scope', 'return ' + body + ';') // new Function('scope', 'return scope.message;')

Watch.prototype.get()獲取表達式值的時候,

 
 
  1. var scope = this.vm
  2. getter.call(scope, scope) // 即執(zhí)行vm.message 

由于initState時對數(shù)據(jù)進行了代理(proxy),這里的vm.message即為vm._data.message,即是data選項中定義的"Hello World"。

值拿到了,那什么時候?qū)essage設為依賴的呢?這就要結合前面observe data里說到的reactiveGetter了。

文件路徑:src/watcher.js

 
 
  1. Watcher.prototype.get = function () {
  2.   this.beforeGet()        // -> Dep.target = this
  3.   var scope = this.scope || this.vm
  4.   ...
  5.   var value value = this.getter.call(scope, scope)
  6.   ...
  7.   this.afterGet()         // -> Dep.target = null
  8.   return value

watcher獲取表達式的值分三步:

  1. beforeGet:設置Dep.target = this
  2. 調(diào)用表達式的getter,讀取(getter)vm.message的值,進入了message的reactiveGetter,由于Dep.target有值,因此執(zhí)行了dep.depend()將target,即當前watcher,收入dep.subs數(shù)組里
  3. afterGet:設置Dep.target = null

這里值得注意的是Dep.target,由于JS的單線程特性,同一時刻只能有一個watcher去get數(shù)據(jù)的值,所以target在全局下只需要有一個就可以了。

文件路徑:src/observer/dep.js

 
 
  1. // the current target watcher being evaluated.
  2. // this is globally unique because there could be only one
  3. // watcher being evaluated at any time.
  4. Dep.target = null 

就這樣,指令通過watcher,去touch了表達式中涉及到的數(shù)據(jù),同時被該數(shù)據(jù)(reactive data)保存為其變化的訂閱者(subscriber),數(shù)據(jù)變化時,通過dep.notify() -> watcher.update() -> directive.update() -> textDirective.update(),完成DOM的更新。

到這里,“Hello World”怎么渲染到頁面上的過程基本就結束了。這里針對最簡單的使用,挑選了最核心的步驟進行分析,更多內(nèi)部細節(jié),后面慢慢分享。


當前題目:Vue.js源碼(1):HelloWorld的背后
網(wǎng)頁URL:http://www.5511xx.com/article/dpccdjp.html