深入理解Vue官方文件梳理之全域性API

深入理解Vue官方文件梳理之全域性API

Vue.extend

配置項data必須為function,否則配置無效。data的合併規則(可以看《Vue官方文件梳理-全域性配置》)原始碼如下:

傳入非function型別的data(上圖中data配置為{a:1}),在合併options時,如果data不是function型別,開發版會發出警告,然後直接返回了parentVal,這意味著extend傳入的data選項被無視了。

我們知道例項化Vue的時候,data可以是物件,這裡的合併規則不是通用的嗎?注意上面有個if(!vm)的判斷,例項化的時候vm是有值的,因此不同於Vue.extend,其實下面的註釋也做了說明(in a Vue.extend merge, both should be function),這也是官方文件為何說data是個特例。

另外官方文件所說的“子類”,是因為Vue.extend返回的是一個“繼承”Vue的函式,原始碼結構如下:


Vue.extend = function (extendOptions) {
//***
var Super = this;
var SuperId = Super.cid;
//***
var Sub = function VueComponent(options) {
this._init(options);
};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
//***
return Sub
};

Vue.nextTick

既然使用vue,當然要沿著資料驅動的方式思考,所謂資料驅動,就是不要直接去操作dom,dom的所有操作完全可以利用vue的各種指令來完成,指令將資料和dom進行了“繫結”,運算元據不僅能實現dom的更新,而且更方便。

如果瀏覽器支援Promise,或者用了Promise庫(但是對外暴露的必須叫Promise,因為原始碼中的判斷為typeof Promise !== ‘undefined’),nextTick返回的就是Promise物件。


Vue.nextTick().then(() => {
// do sth
})

Vue執行nextTick的回撥採用call的方式cb.call(ctx);ctx就是當前Vue例項,因此在回撥中可以直接使用this呼叫例項的配置。
nextTick可以簡單理解為將回撥放到末尾執行,原始碼中如果當前不支援Promise和MutationObserver,那麼會採用setTimeout的方式來執行回撥,這不就是我們常用的延後執行程式碼的方式。


if (typeof Promise !== 'undefined' && isNative(Promise)) {
} else if (typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
} else {
// fallback to setTimeout
/* istanbul ignore next */
timerFunc = function () {
setTimeout(nextTickHandler, 0);
};
}

舉個例子來實際看下:


<div id="app">
<div ref="dom">{{a}}</div>
</div>
new Vue({
el: '#app',
data: {
a: 1
},
mounted: function name(params) {
console.log('start');
this.$nextTick(function () {
console.log('beforeChange', this.$refs.dom.textContent)
})
this.a = 2;
console.log('change');
this.$nextTick(function () {
console.log('afterChange', this.$refs.dom.textContent)
})
console.log('end');
}
})
// 控制檯依次列印
// start
// change
// end
// beforeChange 1
// afterChange 2

你估計會有些納悶,既然都是最後才執行,那為什麼beforeChange輸出的是1而不是2,這是因為this.a=2背後觸發dom更新也是採用nextTick的方式,上面的程式碼實際執行的順序是:beforeChange>更新dom>afterChange。

Vue.set

Vue.set( target, key, value ),target不能是 Vue 例項,或者 Vue 例項的根資料物件,因為原始碼中做了如下判斷:


var ob = (target).__ob__;
if (target._isVue || (ob && ob.vmCount)) {
"development" !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data '  
'at runtime - declare it upfront in the data option.'
);
return val
}

target._isVue阻止了給Vue例項新增屬性,ob && ob.vmCount阻止了給Vue例項的根資料物件新增屬性。

Vue.delete

如果Vue能檢測到delete操作,那麼就不會出現這個api。如果一定要用delete來刪除$data的屬性,那就用Vue.delete,否則不會觸發dom的更新。

同Vue.set,Vue.delete( target, key )的target不能是一個 Vue 示例或 Vue 示例的根資料物件。原始碼中的阻止方式和Vue.set相同。

在2.2.0 版本中target若為陣列,key則是陣列下標。因為Vue.delete刪除陣列實際是用splice來刪除,delete雖然能用於刪除陣列,但位置還在,不能算真正的刪除。


var a = [1, 2, 3];
delete a[0];
console.log(a); // [undefined, 2, 3]

Vue.use

Vue.use 原始碼比較簡單,可以全部貼出來。


Vue.use = function (plugin) {
var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
var args = toArray(arguments, 1);
args.unshift(this);
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args);
} else if (typeof plugin === 'function') {
plugin.apply(null, args);
}
installedPlugins.push(plugin);
return this
};

安裝的外掛放到了 installedPlugins ,安裝外掛前通過installedPlugins.indexOf(plugin)來判斷外掛是否被安裝過,進而阻止註冊相同外掛多次。

外掛型別為 object,必須指定 install 屬性來安裝外掛(typeof plugin.install === ‘function’),另外外掛執行採用plugin.install.apply(plugin, args);,因此 this 訪問 object 的其他屬性。此處的 args 是由 Vue(args.unshift(this);) 和 Vue.use 傳入的除了 plugin 的其他引數(toArray(arguments, 1),1 表示從 arguments[1] 開始擷取)。


Vue.use({
a: 1,
install: function (Vue) {
console.log(this.a) // 1
console.log(arguments) // [function Vue(options),"a", "b", "c"]
}
}, 'a', 'b', 'c')

外掛型別為 function,安裝呼叫plugin.apply(null, args);,因此在嚴格模式下外掛執行時上下文 this 為 null,非嚴格模式為 Window。


'use strict'
Vue.use(function plugin() {
console.log(this) // null
console.log(arguments) // [function Vue(options),"a", "b", "c"]
}, 'a', 'b', 'c')

Vue.compile

和眾多 JS 模板引擎的原理一樣,預先會把模板轉化成一個 render 函式,Vue.compile 就是來完成這個工作的,目標是將模板(template 或 el)轉化成 render 函式。
Vue.compile 返回了{render:Function,staticRenderFns:Array},render 可直接應用於 Vue 的配置項 render,而 staticRenderFns 是怎麼來的,而且按照官網的例子,Vue 還有個隱藏的配置項 staticRenderFns,先來個例子看看。


var compiled = Vue.compile(
'<div>'  
'<header><h1>no data binding</h1></header>'  
'<section>{{prop}}</section>'  
'</div>'
)
console.log(compiled.render.toString())
console.log(compiled.staticRenderFns.toString())
// render
function anonymous() {
with(this) {
return _c('div', [_m(0), _c('section', [_v(_s(prop))])])
}
}
// staticRenderFns
function anonymous() {
with(this) {
return _c('header', [_c('h1', [_v("no data binding")])])
}
}

原來沒有和資料繫結的 dom 會放到 staticRenderFns 中,然後在 render 中以_m(0)來呼叫。但是並不盡然,比如上述模板去掉<h1>,staticRenderFns 長度為 0,header 直接放到了 render 函式中。


function anonymous() {
with(this) {
return _c('div', [_c('header', [_v("no data binding")]), _c('section', [_v(_s(prop))])])
}
}

Vue.compile 對應的原始碼比較複雜,上述渲染 <header> 沒有放到 staticRenderFns 對應原始碼的核心判斷如下:


// For a node to qualify as a static root, it should have children that
// are not just static text. Otherwise the cost of hoisting out will
// outweigh the benefits and it's better off to just always render it fresh.
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
)) {
node.staticRoot = true;
return
} else {
node.staticRoot = false;
}

<header> 不符判斷條件 !(node.children.length === 1 && node.children[0].type === 3), <header> 有一個子節點 TextNode(nodeType=3)。 註釋也說明了一個 node 符合靜態根節點的條件。

另外官網說明了此方法只在獨立構建時有效,什麼是獨立構建?這個官網做了詳細的介紹,不再贅述。對應官網地址:對不同構建版本的解釋

仔細觀察編譯後的 render 方法,和我們自己寫的 render 方法有很大區別。但是仍然可以直接配置到 render 配置選項上。那麼裡面的那些 _c()、_m() 、_v()、_s() 能呼叫?隨便看一個 Vue 的例項的 __proto__ 就會發現:


// internal render helpers.
// these are exposed on the instance prototype to reduce generated render
// code size.
Vue.prototype._o = markOnce;
Vue.prototype._n = toNumber;
Vue.prototype._s = toString;
Vue.prototype._l = renderList;
Vue.prototype._t = renderSlot;
Vue.prototype._q = looseEqual;
Vue.prototype._i = looseIndexOf;
Vue.prototype._m = renderStatic;
Vue.prototype._f = resolveFilter;
Vue.prototype._k = checkKeyCodes;
Vue.prototype._b = bindObjectProps;
Vue.prototype._v = createTextVNode;
Vue.prototype._e = createEmptyVNode;
Vue.prototype._u = resolveScopedSlots;
Vue.prototype._g = bindObjectListeners;

正如註釋所說,這些方法是為了減少生成的 render 函式的體積。

全域性 API 還剩 directive、filter、component、mixin,這幾個比較類似,而且都對應著配置項,會在「選項」中再詳細介紹。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援指令碼之家。

您可能感興趣的文章:

使用FileReader API建立Vue檔案閱讀器元件詳解Vue.js專案API、Router配置拆分實踐Nginx 解決WebApi跨域二次請求以及Vue單頁面的問題詳解Vue2 SSR 快取 Api 資料Vue2 配置 Axios api 介面呼叫檔案的方法詳解vue-cli本地環境API代理設定和解決跨域vue.js全域性API之nextTick全面解析Vue.js使用$.ajax和vue-resource實現OAuth的註冊、登入、登出和API呼叫Vue 2.x教程之基礎APIvue專案中api介面管理總結