Vue.js的HelloWorld舉例分析

這篇文章主要介紹“Vue.js的Hello World舉例分析”,在日常操作中,相信很多人在Vue.js的Hello World舉例分析問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Vue.js的Hello World舉例分析”的疑惑有所幫助!接下來,請(qǐng)跟著小編一起來學(xué)習(xí)吧!

創(chuàng)新互聯(lián)主要從事網(wǎng)站設(shè)計(jì)制作、網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)靜海,十多年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18980820575

構(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()中,主要做了兩件事:

  1. 代理data中的屬性

  2. 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過程。在_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的條件判斷,這些條件有:

  1. 沒有被observe過(observe過的對(duì)象都會(huì)被添加__ob__屬性)

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

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

Vue.js的Hello World舉例分析

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

  1. new Observer({message: 'Hello World'}) 

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

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

  4. 將屬性變成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收集依賴的代碼里,并沒有看到watcher被添加到subs中,什么時(shí)候添加進(jìn)去的呢?這個(gè)問題在講到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過程是分析給定元素(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元素,大致過程如下:

  1. compileRoot:由于root node(<div  id="mountNode"></div>)本身沒有任何指令,所以這里compile不出什么東西

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

  3. compileTextNode:

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

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

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

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

processTextToken之后,其實(shí)就得到了創(chuàng)建指令需要的所有信息:指令類型v-text,表達(dá)式"message",過濾器無,并且該指令負(fù)責(zé)跟進(jìn)的DOM是新創(chuàng)建的TextNode。接下來就是實(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)部屬性的賦值,真正的綁定過程還需要調(diào)用Directive.prototype._bind,它是在Vue實(shí)例方法_bindDir()中被調(diào)用的。

在_bind里面,會(huì)創(chuàng)建watcher,并***次通過watcher去獲得表達(dá)式"message"的計(jì)算值,更新到之前新建的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&rsquo;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。一直說的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ù)訪問路徑(simplePath)。simplePath的值如何計(jì)算,怎么通過"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里說到的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á)式的值分三步:

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

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

  3. 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

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

到此,關(guān)于“Vue.js的Hello World舉例分析”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

網(wǎng)頁標(biāo)題:Vue.js的HelloWorld舉例分析
當(dāng)前路徑:http://muchs.cn/article38/ghjisp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站、品牌網(wǎng)站制作面包屑導(dǎo)航、手機(jī)網(wǎng)站建設(shè)網(wǎng)站維護(hù)、服務(wù)器托管

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

商城網(wǎng)站建設(shè)