成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Vue.js源碼(1):Hello World的背后

jlanglang / 1418人閱讀

摘要:構(gòu)造函數(shù)文件路徑初始化這里只拿對(duì)例子理解最關(guān)鍵的步驟分析。在最后,調(diào)用了對(duì)數(shù)據(jù)進(jìn)行。每個(gè)函數(shù)之后都會(huì)返回一個(gè)。就是去實(shí)例化指令,將指令和新建的元素在一起,然后將元素替換到中去。

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

    
{{message}}
var vm = new Vue({
    el: "#mountNode",
    data: function () {
        return {
            message: "Hello World"
        };
    }
});

這篇文章將要解決幾個(gè)問(wèn)題:

new Vue()的過(guò)程中,內(nèi)部到底有哪些步驟

如何收集依賴

如何計(jì)算表達(dá)式

如何表達(dá)式的值如何反應(yīng)在DOM上的

簡(jiǎn)單來(lái)說(shuō)過(guò)程是這樣的:

observe: 把{message: "Hello World"}變成是reactive的

compile: compileTextNode "{{message}}",解析出指令(directive = v-text)和表達(dá)式(expression = message),創(chuàng)建fragment(new TextNode)準(zhǔn)備替換

link:實(shí)例化directive,將創(chuàng)建的fragment和directive鏈接起來(lái),將fragment替換在DOM上

bind: 通過(guò)directive對(duì)應(yīng)的watcher獲取依賴(message)的值("Hello World"),v-text去update值到fragment上

詳細(xì)過(guò)程,接著往下看。

構(gòu)造函數(shù)

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

function Vue (options) {
  this._init(options)
}
初始化

這里只拿對(duì)例子理解最關(guān)鍵的步驟分析。
文件路徑: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的默認(rèn)算法(strategy),這些strategy都可以配置的,參考Custom Option Merge Strategy

在本文的例子中,主要是data選項(xiàng)的merge,在merge之后,放到$options.data中,基本相當(dāng)于下面這樣:

vm.$options.data = function mergedInstanceDataFn () {
      var parentVal = undefined
      
      // 這里就是在我們定義的options中的data
      var childVal = function () {
          return {
              message: "Hello World"
          }
      }
      
      // data function綁定vm實(shí)例后執(zhí)行,執(zhí)行結(jié)果: {message: "Hello World"}
      var instanceData = childVal.call(vm)
      
      // 對(duì)象之間的merge,類似$.extend,結(jié)果肯定就是:{message: "Hello World"}
      return mergeData(instanceData, parentVal)
}
init data

_initData()發(fā)生在_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的結(jié)果賦值給內(nèi)部屬性:
文件路徑:src/instance/internal/state.js

var dataFn = this.$options.data // 上面我們得到的mergedInstanceDataFn函數(shù)
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

這里是我們的第一個(gè)重點(diǎn),observe過(guò)程。在_initData()最后,調(diào)用了observe(data, this)對(duì)數(shù)據(jù)進(jìn)行observe。在hello world例子里,observe()函數(shù)主要是針對(duì){message: "Hello World"}創(chuàng)建了Observer對(duì)象。
文件路徑:src/observer/index.js

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

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

沒(méi)有被observe過(guò)(observe過(guò)的對(duì)象都會(huì)被添加__ob__屬性)

只能是plain object(toString.call(ob) === "[object Object]")或者數(shù)組

不能是Vue實(shí)例(obj._isVue !== true

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就是干這個(gè)事情的,使data變成“發(fā)布者”,watcher是訂閱者,訂閱data的變化。

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

new Observer({message: "Hello World"})

實(shí)例化一個(gè)Dep對(duì)象,用來(lái)收集依賴

walk(Observer.prototype.walk())數(shù)據(jù)的每一個(gè)屬性,這里只有message

將屬性變成reactive的(Observer.protoype.convert())

convert()里調(diào)用了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觀察這個(gè)數(shù)據(jù)的依賴(watcher)
    }
  })
}

關(guān)于依賴收集和notify,主要是Dep
文件路徑:src/observer/dep.js

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

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

但是在getter收集依賴的代碼里,并沒(méi)有看到watcher被添加到subs中,什么時(shí)候添加進(jìn)去的呢?這個(gè)問(wèn)題在講到Watcher的時(shí)候再回答。

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過(guò)程是分析給定元素(el)或者模版(template),提取指令(directive)和創(chuàng)建對(duì)應(yīng)離線的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元素,大致過(guò)程如下:

compileRoot:由于root node(

)本身沒(méi)有任何指令,所以這里compile不出什么東西

compileChildNode:mountNode的子node,即內(nèi)容為"{{message}}"的TextNode

compileTextNode:
3.1 parseText:其實(shí)就是tokenization(標(biāo)記化:從字符串中提取符號(hào),語(yǔ)句等有意義的元素),得到的結(jié)果是tokens
3.2 processTextToken:從tokens中分析出指令類型,表達(dá)式和過(guò)濾器,并創(chuàng)建新的空的TextNode
3.3 創(chuàng)建fragment,將新的TextNode append進(jìn)去

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

processTextToken之后,其實(shí)就得到了創(chuàng)建指令需要的所有信息:指令類型v-text,表達(dá)式"message",過(guò)濾器無(wú),并且該指令負(fù)責(zé)跟進(jìn)的DOM是新創(chuàng)建的TextNode。接下來(lái)就是實(shí)例化指令了。

link

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

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

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

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()構(gòu)造函數(shù)里面只是一些內(nèi)部屬性的賦值,真正的綁定過(guò)程還需要調(diào)用Directive.prototype._bind,它是在Vue實(shí)例方法_bindDir()中被調(diào)用的。
在_bind里面,會(huì)創(chuàng)建watcher,并第一次通過(guò)watcher去獲得表達(dá)式"message"的計(jì)算值,更新到之前新建的TextNode中去,完成在頁(yè)面上渲染"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.

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

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

Watcher會(huì)去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",單一變量,被認(rèn)為是簡(jiǎn)單的數(shù)據(jù)訪問(wèn)路徑(simplePath)。simplePath的值如何計(jì)算,怎么通過(guò)"message"字符串獲得data.message的值呢?
獲取字符串對(duì)應(yīng)的變量的值,除了用eval,還可以用Function。上面的makeGetterFn("scope." + exp)返回:

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

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

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

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

值拿到了,那什么時(shí)候?qū)essage設(shè)為依賴的呢?這就要結(jié)合前面observe data里說(shuō)到的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獲取表達(dá)式的值分三步:

beforeGet:設(shè)置Dep.target = this

調(diào)用表達(dá)式的getter,讀取(getter)vm.message的值,進(jìn)入了message的reactiveGetter,由于Dep.target有值,因此執(zhí)行了dep.depend()將target,即當(dāng)前watcher,收入dep.subs數(shù)組里

afterGet:設(shè)置Dep.target = null

這里值得注意的是Dep.target,由于JS的單線程特性,同一時(shí)刻只能有一個(gè)watcher去get數(shù)據(jù)的值,所以target在全局下只需要有一個(gè)就可以了。
文件路徑: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

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

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

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/90930.html

相關(guān)文章

  • Vue.js源碼(2):初探List Rendering

    摘要:最后舉兩個(gè)例子,回顧上面的內(nèi)容例一改變的是數(shù)組元素中屬性,由于創(chuàng)建的的指令,因此這里直接由指令更新對(duì)應(yīng)元素的內(nèi)容。 下面例子來(lái)自官網(wǎng),雖然看上去就比Hello World多了一個(gè)v-for,但是內(nèi)部多了好多的處理過(guò)程。但是這就是框架,只給你留下最美妙的東西,讓生活變得簡(jiǎn)單。 {{ todo.text }} ...

    shiyang6017 評(píng)論0 收藏0
  • Vue.js起手式+Vue小作品實(shí)戰(zhàn)

    摘要:本文是小羊根據(jù)文檔進(jìn)行解讀的第一篇文章,主要內(nèi)容涵蓋的基礎(chǔ)部分的知識(shí)的,文章順序基本按照官方文檔的順序,每個(gè)知識(shí)點(diǎn)現(xiàn)附上代碼,然后根據(jù)代碼給予個(gè)人的一些理解,最后還放上在線編輯的代碼以供練習(xí)和測(cè)試之用在最后,我參考上的一篇技博,對(duì)進(jìn)行初入的 本文是小羊根據(jù)Vue.js文檔進(jìn)行解讀的第一篇文章,主要內(nèi)容涵蓋Vue.js的基礎(chǔ)部分的知識(shí)的,文章順序基本按照官方文檔的順序,每個(gè)知識(shí)點(diǎn)現(xiàn)附上代...

    CompileYouth 評(píng)論0 收藏0
  • Vue.js起手式+Vue小作品實(shí)戰(zhàn)

    摘要:本文是小羊根據(jù)文檔進(jìn)行解讀的第一篇文章,主要內(nèi)容涵蓋的基礎(chǔ)部分的知識(shí)的,文章順序基本按照官方文檔的順序,每個(gè)知識(shí)點(diǎn)現(xiàn)附上代碼,然后根據(jù)代碼給予個(gè)人的一些理解,最后還放上在線編輯的代碼以供練習(xí)和測(cè)試之用在最后,我參考上的一篇技博,對(duì)進(jìn)行初入的 本文是小羊根據(jù)Vue.js文檔進(jìn)行解讀的第一篇文章,主要內(nèi)容涵蓋Vue.js的基礎(chǔ)部分的知識(shí)的,文章順序基本按照官方文檔的順序,每個(gè)知識(shí)點(diǎn)現(xiàn)附上代...

    付倫 評(píng)論0 收藏0
  • 【翻譯】Next.js背后哲學(xué)和設(shè)計(jì)

    摘要:無(wú)數(shù)的模板語(yǔ)言和框架應(yīng)運(yùn)而生但是技術(shù)始終被分割為前端和后端。這意味著一個(gè)頁(yè)面可以有很多的這并不會(huì)對(duì)其余的頁(yè)面有任何影響。提前綁定和編譯預(yù)測(cè)是一個(gè)非常有效的部署方式。最后,這是我們對(duì)于這個(gè)特定問(wèn)題的貢獻(xiàn)。 Next.js 原文地址 Naoyuki Kanezawa (@nkzawa), Guillermo Rauch (@rauchg) 和 Tony Kovanen (@tonykova...

    plokmju88 評(píng)論0 收藏0
  • 你所不知道HelloWorld背后原理

    摘要:今日最佳對(duì)于程序員而言,所謂的二八定律指的是花百分之八十的時(shí)間去學(xué)習(xí)日常研發(fā)中不常見的那百分之二十的原理。 【今日最佳】對(duì)于程序員而言,所謂的二八定律指的是 花百分之八十的時(shí)間去學(xué)習(xí)日常研發(fā)中不常見的那百分之二十的原理。 據(jù)說(shuō)阿里某程序員對(duì)書法十分感興趣,退休后決定在這方面有所建樹。于是花重金購(gòu)買了上等的文房四寶。 一日,飯后突生雅興,一番磨墨擬紙,并點(diǎn)上了上好的檀香,頗有王羲之風(fēng)范,...

    lavor 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<