摘要:你可以使用的方法傳入指令和定義對象來注冊一個全局自定義指令。深度數(shù)據(jù)觀察如果你希望在一個對象上使用自定義指令,并且當(dāng)對象內(nèi)部嵌套的屬性發(fā)生變化時也能夠觸發(fā)指令的函數(shù),那么你就要在指令的定義中傳入。
Vue簡介
數(shù)據(jù)綁定
/** *假設(shè)有這么兩個鐘東西 **/ //數(shù)據(jù) var object = { message: "Hello World!" } //DOM{{ message }}/** *我們可以這么寫 **/
new Vue({ el: "#example", data: object }) /** * 如果有個數(shù)據(jù) **/ var object1 = { message: "Hello World!" } var object2 = { message: "Hello World!" } //DOM{{ message }}{{ message }}/** *我們還可以這么寫 **/ var vm1 = new Vue({el: "#example1",data: object}) //改變vm1的數(shù)據(jù)DOM隨之改變 vm2.message = "oliver" var vm2 = new Vue({el: "#example2",data: object}) vm2.message = "lisa"
組件化
var Example = Vue.extend({ template: "{{ message }}", data: function () { return { message: "Hello Vue.js!" } } }) // 將該組件注冊為標(biāo)簽 Vue.component("example", Example) Vue 在組件化上和 React 類似:一切都是組件。 組件使用上也和React一致:
組件之間數(shù)據(jù)傳遞:
1.用 props 來定義如何接收外部數(shù)據(jù); Vue.component("child", { // 聲明 props props: ["msg"], // prop 可以用在模板內(nèi) // 可以用 `this.msg` 設(shè)置 template: "{{ msg }}" })2.用自定義事件來向外傳遞消息; 使用 $on() 監(jiān)聽事件; 使用 $emit() 在它上面觸發(fā)事件; 使用 $dispatch() 派發(fā)事件,事件沿著父鏈冒泡; 使用 $broadcast() 廣播事件,事件向下傳導(dǎo)給所有的后代。 3.用 API 來將外部動態(tài)傳入的內(nèi)容(其他組件或是 HTML)和自身模板進(jìn)行組合;
模塊化
Webpack 或者 Browserify,然后再加上 ES2015配合 vue-loader 或是 vueify,就可以把Vue的每一個組件變成 Web Components
路由
使用Vue重構(gòu)的Angular項(xiàng)目 www.songxuemeng.com/diary 個人感覺vue-router煩的問題是組件之間的數(shù)據(jù)交互,rootRouter的數(shù)據(jù)很難向其他組件傳遞. /** *解決方法 **/ var app = Vue.extend({ data:function(){ return { data:"", }; }, }); router.map({ "/": { component: Vue.extend({ mixins: [calendar.mixin], data:function(){ return { data:data } } }) }, }) router.start(app, "#app");Vue源碼分析
http://img2.tbcdn.cn/L1/461/1...
Vue.js是一個典型的MVVM的程序結(jié)構(gòu),程序大體可以分為:
全局設(shè)計(jì):包括全局接口、默認(rèn)選項(xiàng)等;
vm實(shí)例設(shè)計(jì):包括接口(vm原形)、實(shí)例初始化過程(vm構(gòu)造函數(shù))
下面是構(gòu)造函數(shù)最核心的工作內(nèi)容。
http://img3.tbcdn.cn/L1/461/1...
整個實(shí)例初始化的過程中,重中之重就是把數(shù)據(jù) (Model) 和視圖 (View) 建立起關(guān)聯(lián)關(guān)系。Vue.js 和諸多 MVVM 的思路是類似的,主要做了三件事:
通過 observer 對 data 進(jìn)行了監(jiān)聽,并且提供訂閱某個數(shù)據(jù)項(xiàng)的變化的能力
把 template 解析成一段 document fragment,然后解析其中的 directive,得到每一個 directive 所依賴的數(shù)據(jù)項(xiàng)及其更新方法。比如 v-text="message" 被解析之后;
所依賴的數(shù)據(jù)項(xiàng) this.$data.message,以及
相應(yīng)的視圖更新方法 node.textContent = this.$data.message
通過 watcher 把上述兩部分結(jié)合起來,即把 directive 中的數(shù)據(jù)依賴訂閱在對應(yīng)數(shù)據(jù)的 observer 上,這樣當(dāng)數(shù)據(jù)變化的時候,就會觸發(fā) observer,進(jìn)而觸發(fā)相關(guān)依賴對應(yīng)的視圖更新方法,最后達(dá)到模板原本的關(guān)聯(lián)效果。
所以整個 vm 的核心,就是如何實(shí)現(xiàn) observer, directive (parser), watcher 這三樣?xùn)|西
http://img4.tbcdn.cn/L1/461/1...
數(shù)據(jù)列表的更新視圖更新效率的焦點(diǎn)問題主要在于大列表的更新和深層數(shù)據(jù)更新這兩方面.
但是工作中經(jīng)常用的主要是前者
首先 diff(data, oldVms) 這個函數(shù)的注釋對整個比對更新機(jī)制做了個簡要的闡述,大概意思是先比較新舊兩個列表的 vm 的數(shù)據(jù)的狀態(tài),然后差量更新 DOM。
第一步:便利新列表里的每一項(xiàng),如果該項(xiàng)的 vm 之前就存在,則打一個 _reused 的標(biāo),如果不存在對應(yīng)的 vm,則創(chuàng)建一個新的。
for (i = 0, l = data.length; i < l; i++) { item = data[i]; key = convertedFromObject ? item.$key : null; value = convertedFromObject ? item.$value : item; primitive = !isObject(value); frag = !init && this.getCachedFrag(value, i, key); if (frag) { // reusable fragment如果存在打上usered frag.reused = true; // update $index frag.scope.$index = i; // update $key if (key) { frag.scope.$key = key; } // update iterator if (iterator) { frag.scope[iterator] = key !== null ? key : i; } // update data for track-by, object repeat & // primitive values. if (trackByKey || convertedFromObject || primitive) { frag.scope[alias] = value; } } else { // new isntance如果不存在就新建一個 frag = this.create(value, alias, i, key); frag.fresh = !init; } frags[i] = frag; if (init) { frag.before(end); } }
第二步:便利舊列表里的每一項(xiàng),如果 _reused 的標(biāo)沒有被打上,則說明新列表里已經(jīng)沒有它了,就地銷毀該 vm。
for (i = 0, l = oldFrags.length; i < l; i++) { frag = oldFrags[i]; if (!frag.reused) { //如果沒有used說明不存在,就地銷毀 this.deleteCachedFrag(frag); this.remove(frag, removalIndex++, totalRemoved, inDocument); } }
第三步:整理新的 vm 在視圖里的順序,同時還原之前打上的 _reused 標(biāo)。就此列表更新完成
for (i = 0, l = frags.length; i < l; i++) { frag = frags[i]; // this is the frag that we should be after targetPrev = frags[i - 1]; prevEl = targetPrev ? targetPrev.staggerCb ? targetPrev.staggerAnchor : targetPrev.end || targetPrev.node : start; if (frag.reused && !frag.staggerCb) { currentPrev = findPrevFrag(frag, start, this.id); if (currentPrev !== targetPrev && (!currentPrev || // optimization for moving a single item. // thanks to suggestions by @livoras in #1807 findPrevFrag(currentPrev, start, this.id) !== targetPrev)) { this.move(frag, prevEl); } } else { // new instance, or still in stagger. // insert with updated stagger index. this.insert(frag, insertionIndex++, prevEl, inDocument); } //還原打上的used frag.reused = frag.fresh = false; }
keep-alive
Vue.js 為其組件設(shè)計(jì)了一個 [keep-alive] 的特性,如果這個特性存在,那么在組件被重復(fù)創(chuàng)建的時候,會通過緩存機(jī)制快速創(chuàng)建組件,以提升視圖更新的性能。 bind: function bind() { if (!this.el.__vue__) { // keep-alive cache this.keepAlive = this.params.keepAlive; if (this.keepAlive) { this.cache = {}; } ..... }數(shù)據(jù)監(jiān)聽機(jī)制 對象數(shù)據(jù)監(jiān)聽
"Vue"使用"Object.defineProperty"這個"API"為想要監(jiān)聽的屬性增加了對應(yīng)的"getter"和"setter",每次數(shù)據(jù)改變的時候在setter中觸發(fā)函數(shù)"dep.notify()",來達(dá)到數(shù)據(jù)監(jiān)聽的效果
//對要監(jiān)聽的屬性使用Object.defineProperty重寫get和set函數(shù),增加setter和getter方法 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { //增加getter var value = getter ? getter.call(obj) : val; if (Dep.target) { dep.depend(); if (childOb) { childOb.dep.depend(); } if (isArray(value)) { for (var e, i = 0, l = value.length; i < l; i++) { e = value[i]; e && e.__ob__ && e.__ob__.dep.depend(); } } } return value; }, set: function reactiveSetter(newVal) { var value = getter ? getter.call(obj) : val; //在屬性set value的時候調(diào)用!!! if (newVal === value) { return; } //增加setter if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = observe(newVal); //最后調(diào)用一個自己的函數(shù) dep.notify(); } }); 然后dep.notify()都做了什么呢?
Dep.prototype.notify = function () { // stablize the subscriber list first var subs = toArray(this.subs) for (var i = 0, l = subs.length; i < l; i++) { //對相應(yīng)的數(shù)據(jù)進(jìn)行更新 subs[i].update() } }
dep在文檔里面定義是:
//A dep is an observable that can have multiple //directives subscribing to it. export default function Dep () { this.id = uid++ this.subs = [] }
"dep"是維護(hù)數(shù)據(jù)的一個數(shù)組,對應(yīng)著一個"watcher"對象
所以整個數(shù)據(jù)監(jiān)聽的完成是靠set給屬性提供一個setter然后當(dāng)數(shù)據(jù)更新時,dep會觸發(fā)watcher對象,返回新值.
之后會有更詳細(xì)解釋
數(shù)組可能會有點(diǎn)麻煩,Vue.js 采取的是對幾乎每一個可能改變數(shù)據(jù)的方法進(jìn)行 prototype 更改:
;["push", "pop", "shift", "unshift", "splice", "sort", "reverse"].forEach(function (method) { // cache original method var original = arrayProto[method]; def(arrayMethods, method, function mutator() { // avoid leaking arguments: // http://jsperf.com/closure-with-arguments var i = arguments.length; var args = new Array(i); while (i--) { args[i] = arguments[i]; } var result = original.apply(this, args); var ob = this.__ob__; var inserted; switch (method) { case "push": inserted = args; break; case "unshift": inserted = args; break; case "splice": inserted = args.slice(2); break; } if (inserted) ob.observeArray(inserted); // notify change ob.dep.notify(); return result; }); });
同時 Vue.js 提供了兩個額外的“糖方法” $set 和 $remove 來彌補(bǔ)這方面限制帶來的不便。
但這個策略主要面臨兩個問題:
無法監(jiān)聽數(shù)據(jù)的 length,導(dǎo)致 arr.length 這樣的數(shù)據(jù)改變無法被監(jiān)聽
通過角標(biāo)更改數(shù)據(jù),即類似 arr[2] = 1 這樣的賦值操作,也無法被監(jiān)聽
為此 Vue.js 在文檔中明確提示不建議直接角標(biāo)修改數(shù)據(jù)
"實(shí)例計(jì)算屬性。getter 和 setter 的 this 自動地綁定到實(shí)例。"
舉個栗子:
var vm = new Vue({ data: { a: 1 }, computed: { // 僅讀取,值只須為函數(shù) b: function () { return this.a * 2 }, // 讀取和設(shè)置 c: { get: function () { return this.a + 1 }, set: function (v) { this.a = v - 1 } } } })
可以看出來computed可以提供自定義一個屬性c的getter和setter/b的getter,問題是c和b怎么維護(hù)和a的關(guān)系
下面是computed怎么提供屬性setter和getter的代碼:
//初始化computed ... var userDef = computed[key]; //userDef指的是computed屬性,this -> computed def.get = makeComputedGetter(userDef, this); //或者makeComputedGetter(userDef.get, this) ... function makeComputedGetter(getter, owner) { var watcher = new Watcher(owner, getter, null, { lazy: true }); return function computedGetter() { if (watcher.dirty) { watcher.evaluate(); } if (Dep.target) { watcher.depend(); } return watcher.value; }; }
computed在建立的時候綁定一個對應(yīng)的 watcher 對象,在計(jì)算過程中它把屬性記錄為依賴。之后當(dāng)依賴的 setter 被調(diào)用時,會觸發(fā) watcher 重新計(jì)算 ,也就會導(dǎo)致它的關(guān)聯(lián)指令更新 DOM。
視圖解析過程 解析器parsers/path.js 主要的職責(zé)是可以把一個 JSON 數(shù)據(jù)里的某一個“路徑”下的數(shù)據(jù)取出來,比如:
var path = "a.b[1].v" var obj = { a: { b: [ {v: 1}, {v: 2}, {v: 3} ] } } parse(obj, path) // 2 var pathStateMachine = [] pathStateMachine[BEFORE_PATH] = { "ws": [BEFORE_PATH], "ident": [IN_IDENT, APPEND], "[": [IN_SUB_PATH], "eof": [AFTER_PATH] } pathStateMachine[IN_PATH] = { "ws": [IN_PATH], ".": [BEFORE_IDENT], "[": [IN_SUB_PATH], "eof": [AFTER_PATH] } pathStateMachine[BEFORE_IDENT] = { "ws": [BEFORE_IDENT], "ident": [IN_IDENT, APPEND] } pathStateMachine[IN_IDENT] = { "ident": [IN_IDENT, APPEND], "0": [IN_IDENT, APPEND], "number": [IN_IDENT, APPEND], "ws": [IN_PATH, PUSH], ".": [BEFORE_IDENT, PUSH], "[": [IN_SUB_PATH, PUSH], "eof": [AFTER_PATH, PUSH] } pathStateMachine[IN_SUB_PATH] = { """: [IN_SINGLE_QUOTE, APPEND], """: [IN_DOUBLE_QUOTE, APPEND], "[": [IN_SUB_PATH, INC_SUB_PATH_DEPTH], "]": [IN_PATH, PUSH_SUB_PATH], "eof": ERROR, "else": [IN_SUB_PATH, APPEND] } pathStateMachine[IN_SINGLE_QUOTE] = { """: [IN_SUB_PATH, APPEND], "eof": ERROR, "else": [IN_SINGLE_QUOTE, APPEND] } pathStateMachine[IN_DOUBLE_QUOTE] = { """: [IN_SUB_PATH, APPEND], "eof": ERROR, "else": [IN_DOUBLE_QUOTE, APPEND] }
狀態(tài)機(jī)可以完成
1.dom結(jié)構(gòu)中{{data.someObj}}的解析; 2.對字符型json的取值;
可惜大學(xué)里面的編譯原理我給忘記了,否則可以給大家解析一下.
視圖解析過程視圖的解析過程,Vue.js 的策略是把 element 或 template string 先統(tǒng)一轉(zhuǎn)換成 document fragment,然后再分解和解析其中的子組件和 directives。
相比React的visual DOM有一定的性能優(yōu)化空間,畢竟 DOM 操作相比純 JavaScript 運(yùn)算還是會慢一些。
Vue擴(kuò)展 MixinMixin (混入) 是一種可以在多個 Vue 組件之間靈活復(fù)用特性的機(jī)制。你可以像寫一個普通 Vue 組件的選項(xiàng)對象一樣編寫一個 mixin:
module.exports = { created: function () { this.hello() }, methods: { hello: function () { console.log("hello from mixin!") } } }
// test.js var myMixin = require("./mixin") var Component = Vue.extend({ mixins: [myMixin] }) var component = new Component() // -> "hello from mixin!"Vue插件
Vue插件類型分為以下幾種: 1.添加一個或幾個全局方法。比如 vue-element 2.添加一個或幾個全局資源:指令、過濾器、動畫效果等。比如 vue-touch 3.通過綁定到 Vue.prototype 的方式添加一些 Vue 實(shí)例方法。這里有個約定,就是 Vue 的實(shí)例方法應(yīng)該帶有 $ 前綴,這樣就不會和用戶的數(shù)據(jù)和方法產(chǎn)生沖突了。
MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或?qū)傩?Vue.myGlobalMethod = ... // 2. 添加全局資源 Vue.directive("my-directive", {}) // 3. 添加實(shí)例方法 Vue.prototype.$myMethod = ... }
var vueTouch = require("vue-touch") // use the plugin globally Vue.use(vueTouch) 你也可以向插件里傳遞額外的選項(xiàng): Vue.use(require("my-plugin"), { /* pass in additional options */ }) 全局方法: Vue.fun() 局部方法: vm.$fun()Vue指令
Vue.js 允許注冊自定義指令,實(shí)質(zhì)上是開放 Vue 一些技巧:怎樣將數(shù)據(jù)的變化映射到 DOM 的行為。你可以使用 Vue.directive(id, definition) 的方法傳入指令 id 和定義對象來注冊一個全局自定義指令。定義對象需要提供一些鉤子函數(shù):
bind: 僅調(diào)用一次,當(dāng)指令第一次綁定元素的時候。
update: 第一次是緊跟在 bind 之后調(diào)用,獲得的參數(shù)是綁定的初始值;以后每當(dāng)綁定的值發(fā)生變化就會被調(diào)用,獲得新值與舊值兩個參數(shù)。
unbind:僅調(diào)用一次,當(dāng)指令解綁元素的時候。
一旦注冊好自定義指令,你就可以在 Vue.js 模板中像這樣來使用它(需要添加 Vue.js 的指令前綴,默認(rèn)為 v-):
如果你只需要 update 函數(shù),你可以只傳入一個函數(shù),而不用傳定義對象:
// 這個函數(shù)會被作為 update() 函數(shù)使用 })``` 所有的鉤子函數(shù)會被復(fù)制到實(shí)際的**指令對象**中,而這個指令對象將會是所有鉤子函數(shù)的 `this` 上下文環(huán)境。指令對象上暴露了一些有用的公開屬性: - **el**: 指令綁定的元素 - **vm**: 擁有該指令的上下文 ViewModel - **expression**: 指令的表達(dá)式,不包括參數(shù)和過濾器 - **arg**: 指令的參數(shù) - **raw**: 未被解析的原始表達(dá)式 - **name**: 不帶前綴的指令名 >這些屬性是只讀的,不要修改它們。你也可以給指令對象附加自定義的屬性,但是注意不要覆蓋已有的內(nèi)部屬性。 使用指令對象屬性的示例: ``
bind: function () {
this.el.style.color = "#fff" this.el.style.backgroundColor = this.arg
},
update: function (value) {
this.el.innerHTML = "name - " + this.name + "
" + "raw - " + this.raw + "
" + "expression - " + this.expression + "
" + "argument - " + this.arg + "
" + "value - " + value
}
})
var demo = new Vue({
el: "#demo",
data: {
msg: "hello!"
}
})`
Result
name - demo
raw - LightSlateGray:msg
expression - msg
argument - LightSlateGray
value - hello!
多重從句同一個特性內(nèi)部,逗號分隔的多個從句將被綁定為多個指令實(shí)例。在下面的例子中,指令會被創(chuàng)建和調(diào)用兩次:
如果想要用單個指令實(shí)例處理多個參數(shù),可以利用字面量對象作為表達(dá)式:
console.log(value) // Object {color: "white", text: "hello!"} })``` ## 字面指令 如果在創(chuàng)建自定義指令的時候傳入 `isLiteral: true`,那么特性值就會被看成直接字符串,并被賦值給該指令的 `expression`。字面指令不會試圖建立數(shù)據(jù)監(jiān)視。 **Example**: ``
isLiteral: true,
bind: function () {
console.log(this.expression) // "foo"
}
})`
然而,在字面指令含有 Mustache 標(biāo)簽的情形下,指令的行為如下:
指令實(shí)例會有一個屬性,this._isDynamicLiteral 被設(shè)為 true;
如果沒有提供 update 函數(shù),Mustache 表達(dá)式只會被求值一次,并將該值賦給 this.expression 。不會對表達(dá)式進(jìn)行數(shù)據(jù)監(jiān)視。
如果提供了 update 函數(shù),指令將會為表達(dá)式建立一個數(shù)據(jù)監(jiān)視,并且在計(jì)算結(jié)果變化的時候調(diào)用 update。
雙向指令如果你的指令想向 Vue 實(shí)例寫回?cái)?shù)據(jù),你需要傳入 twoWay: true 。該選項(xiàng)允許在指令中使用 this.set(value)。
twoWay: true, bind: function () { this.handler = function () { // 把數(shù)據(jù)寫回 vm // 如果指令這樣綁定 v-example="a.b.c", // 這里將會給 `vm.a.b.c` 賦值 this.set(this.el.value) }.bind(this) this.el.addEventListener("input", this.handler) }, unbind: function () { this.el.removeEventListener("input", this.handler) } })``` ## 內(nèi)聯(lián)語句 傳入 `acceptStatement: true` 可以讓自定義指令像 `v-on` 一樣接受內(nèi)聯(lián)語句: ``
acceptStatement: true,
update: function (fn) {
// the passed in value is a function which when called, // will execute the "a++" statement in the owner vm"s // scope.
}
})`
但是請明智地使用此功能,因?yàn)橥ǔN覀兿M苊庠谀0逯挟a(chǎn)生副作用。
深度數(shù)據(jù)觀察如果你希望在一個對象上使用自定義指令,并且當(dāng)對象內(nèi)部嵌套的屬性發(fā)生變化時也能夠觸發(fā)指令的 update 函數(shù),那么你就要在指令的定義中傳入 deep: true。
deep: true, update: function (obj) { // 當(dāng) obj 內(nèi)部嵌套的屬性變化時也會調(diào)用此函數(shù) } })``` ## 指令優(yōu)先級 你可以選擇給指令提供一個優(yōu)先級數(shù)(默認(rèn)是 0)。同一個元素上優(yōu)先級越高的指令會比其他的指令處理得早一些。優(yōu)先級一樣的指令會按照其在元素特性列表中出現(xiàn)的順序依次處理,但是不能保證這個順序在不同的瀏覽器中是一致的。 通常來說作為用戶,你并不需要關(guān)心內(nèi)置指令的優(yōu)先級,如果你感興趣的話,可以參閱源碼。邏輯控制指令 `v-repeat`, `v-if` 被視為 “終結(jié)性指令”,它們在編譯過程中始終擁有最高的優(yōu)先級。 ## 元素指令 有時候,我們可能想要我們的指令可以以自定義元素的形式被使用,而不是作為一個特性。這與 `Angular` 的 `E` 類指令的概念非常相似。元素指令可以看做是一個輕量的自定義組件(后面會講到)。你可以像下面這樣注冊一個自定義的元素指令:
// 和普通指令的 API 一致
bind: function () {
// 對 this.el 進(jìn)行操作...
}
})
Mixin (混入) 是一種可以在多個 Vue 組件之間靈活復(fù)用特性的機(jī)制。你可以像寫一個普通 Vue 組件的選項(xiàng)對象一樣編寫一個 mixin:
module.exports = { created: function () { this.hello() }, methods: { hello: function () { console.log("hello from mixin!") } } }
// test.js var myMixin = require("./mixin") var Component = Vue.extend({ mixins: [myMixin] }) var component = new Component() // -> "hello from mixin!"Vue插件
Vue插件類型分為以下幾種: 1.添加一個或幾個全局方法。比如 vue-element 2.添加一個或幾個全局資源:指令、過濾器、動畫效果等。比如 vue-touch 3.通過綁定到 Vue.prototype 的方式添加一些 Vue 實(shí)例方法。這里有個約定,就是 Vue 的實(shí)例方法應(yīng)該帶有 $ 前綴,這樣就不會和用戶的數(shù)據(jù)和方法產(chǎn)生沖突了。
MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或?qū)傩?Vue.myGlobalMethod = ... // 2. 添加全局資源 Vue.directive("my-directive", {}) // 3. 添加實(shí)例方法 Vue.prototype.$myMethod = ... }
var vueTouch = require("vue-touch") // use the plugin globally Vue.use(vueTouch) 你也可以向插件里傳遞額外的選項(xiàng): Vue.use(require("my-plugin"), { /* pass in additional options */ }) 全局方法: Vue.fun() 局部方法: vm.$fun()Vue指令
Vue.js 允許注冊自定義指令,實(shí)質(zhì)上是開放 Vue 一些技巧:怎樣將數(shù)據(jù)的變化映射到 DOM 的行為。你可以使用 Vue.directive(id, definition) 的方法傳入指令 id 和定義對象來注冊一個全局自定義指令。定義對象需要提供一些鉤子函數(shù):
bind: 僅調(diào)用一次,當(dāng)指令第一次綁定元素的時候。
update: 第一次是緊跟在 bind 之后調(diào)用,獲得的參數(shù)是綁定的初始值;以后每當(dāng)綁定的值發(fā)生變化就會被調(diào)用,獲得新值與舊值兩個參數(shù)。
unbind:僅調(diào)用一次,當(dāng)指令解綁元素的時候。
一旦注冊好自定義指令,你就可以在 Vue.js 模板中像這樣來使用它(需要添加 Vue.js 的指令前綴,默認(rèn)為 v-):
如果你只需要 update 函數(shù),你可以只傳入一個函數(shù),而不用傳定義對象:
// 這個函數(shù)會被作為 update() 函數(shù)使用 })``` 所有的鉤子函數(shù)會被復(fù)制到實(shí)際的**指令對象**中,而這個指令對象將會是所有鉤子函數(shù)的 `this` 上下文環(huán)境。指令對象上暴露了一些有用的公開屬性: - **el**: 指令綁定的元素 - **vm**: 擁有該指令的上下文 ViewModel - **expression**: 指令的表達(dá)式,不包括參數(shù)和過濾器 - **arg**: 指令的參數(shù) - **raw**: 未被解析的原始表達(dá)式 - **name**: 不帶前綴的指令名 >這些屬性是只讀的,不要修改它們。你也可以給指令對象附加自定義的屬性,但是注意不要覆蓋已有的內(nèi)部屬性。 使用指令對象屬性的示例: ``
bind: function () {
this.el.style.color = "#fff" this.el.style.backgroundColor = this.arg
},
update: function (value) {
this.el.innerHTML = "name - " + this.name + "
" + "raw - " + this.raw + "
" + "expression - " + this.expression + "
" + "argument - " + this.arg + "
" + "value - " + value
}
})
var demo = new Vue({
el: "#demo",
data: {
msg: "hello!"
}
})`
Result
name - demo
raw - LightSlateGray:msg
expression - msg
argument - LightSlateGray
value - hello!
多重從句同一個特性內(nèi)部,逗號分隔的多個從句將被綁定為多個指令實(shí)例。在下面的例子中,指令會被創(chuàng)建和調(diào)用兩次:
如果想要用單個指令實(shí)例處理多個參數(shù),可以利用字面量對象作為表達(dá)式:
console.log(value) // Object {color: "white", text: "hello!"} })``` ## 字面指令 如果在創(chuàng)建自定義指令的時候傳入 `isLiteral: true`,那么特性值就會被看成直接字符串,并被賦值給該指令的 `expression`。字面指令不會試圖建立數(shù)據(jù)監(jiān)視。 **Example**: ``
isLiteral: true,
bind: function () {
console.log(this.expression) // "foo"
}
})`
然而,在字面指令含有 Mustache 標(biāo)簽的情形下,指令的行為如下:
指令實(shí)例會有一個屬性,this._isDynamicLiteral 被設(shè)為 true;
如果沒有提供 update 函數(shù),Mustache 表達(dá)式只會被求值一次,并將該值賦給 this.expression 。不會對表達(dá)式進(jìn)行數(shù)據(jù)監(jiān)視。
如果提供了 update 函數(shù),指令將會為表達(dá)式建立一個數(shù)據(jù)監(jiān)視,并且在計(jì)算結(jié)果變化的時候調(diào)用 update。
雙向指令如果你的指令想向 Vue 實(shí)例寫回?cái)?shù)據(jù),你需要傳入 twoWay: true 。該選項(xiàng)允許在指令中使用 this.set(value)。
twoWay: true, bind: function () { this.handler = function () { // 把數(shù)據(jù)寫回 vm // 如果指令這樣綁定 v-example="a.b.c", // 這里將會給 `vm.a.b.c` 賦值 this.set(this.el.value) }.bind(this) this.el.addEventListener("input", this.handler) }, unbind: function () { this.el.removeEventListener("input", this.handler) } })``` ## 內(nèi)聯(lián)語句 傳入 `acceptStatement: true` 可以讓自定義指令像 `v-on` 一樣接受內(nèi)聯(lián)語句: ``
acceptStatement: true,
update: function (fn) {
// the passed in value is a function which when called, // will execute the "a++" statement in the owner vm"s // scope.
}
})`
但是請明智地使用此功能,因?yàn)橥ǔN覀兿M苊庠谀0逯挟a(chǎn)生副作用。
深度數(shù)據(jù)觀察如果你希望在一個對象上使用自定義指令,并且當(dāng)對象內(nèi)部嵌套的屬性發(fā)生變化時也能夠觸發(fā)指令的 update 函數(shù),那么你就要在指令的定義中傳入 deep: true。
deep: true, update: function (obj) { // 當(dāng) obj 內(nèi)部嵌套的屬性變化時也會調(diào)用此函數(shù) } })``` ## 指令優(yōu)先級 你可以選擇給指令提供一個優(yōu)先級數(shù)(默認(rèn)是 0)。同一個元素上優(yōu)先級越高的指令會比其他的指令處理得早一些。優(yōu)先級一樣的指令會按照其在元素特性列表中出現(xiàn)的順序依次處理,但是不能保證這個順序在不同的瀏覽器中是一致的。 通常來說作為用戶,你并不需要關(guān)心內(nèi)置指令的優(yōu)先級,如果你感興趣的話,可以參閱源碼。邏輯控制指令 `v-repeat`, `v-if` 被視為 “終結(jié)性指令”,它們在編譯過程中始終擁有最高的優(yōu)先級。 ## 元素指令 有時候,我們可能想要我們的指令可以以自定義元素的形式被使用,而不是作為一個特性。這與 `Angular` 的 `E` 類指令的概念非常相似。元素指令可以看做是一個輕量的自定義組件(后面會講到)。你可以像下面這樣注冊一個自定義的元素指令:
// 和普通指令的 API 一致
bind: function () {
// 對 this.el 進(jìn)行操作...
}
})
Angular Modules angular.module("myModule", [...]); Components Vue.extend({ data: function(){ return {...} }, created: function() {...}, ready: function() {...}, components: {...}, methods: {...},
總體來說
對于Angular來說module就是一個容器,而對Vue來說一個component里面會有邏輯代碼
在Vue里面會放進(jìn)許多代碼細(xì)節(jié),并且有固定的屬性
Directives Angular myModule.directive("directiveName", function (injectables) { return { restrict: "A", template: "", controller: function() { ... }, compile: function() {...}, link: function() { ... } //(other props excluded) }; }); Vue Vue.directive("my-directive", { bind: function () {...}, update: function (newValue, oldValue) {...}, unbind: function () {...} });
Vue的指令比Angular的簡單,而Angular的指令類似Vue的component
Filters Angular myModule.angular.module(‘filterName", []) .filter("reverse", function() { return function(input) {...}; }); Vue Vue.filter("reverse", function (value) { return function(value){...}; });
filters都是類似的,但是Vue提供了read/wirte功能
Templating Interpolation {{myVariable}} Interpolation {{myVariable}}
當(dāng)輸出是一個對象的時候
Vue:[Object]
Angular :{[attr:value]}
Vue可以使用filters得到正常輸出 {{someObject|json}}
Model binding Angular Vue Loops Angular
Vue也可以這樣寫v-repeat="item: items"
Event binding Angular Vue
通用v-on指令使事件更加一致
臟值檢查一個電話列表應(yīng)用的例子,在其中我們會將一個phones數(shù)組中的值(在JavaScript中定義)綁定到一個列表項(xiàng)目中以便于我們的數(shù)據(jù)和UI保持同步:
...
- {{phone.name}}
{{phone.snippet}}
var phonecatApp = angular.module("phonecatApp", []); phonecatApp.controller("PhoneListCtrl", function($scope) { $scope.phones = [ {"name": "Nexus S", "snippet": "Fast just got faster with Nexus S."}, {"name": "Motorola XOOM with Wi-Fi", "snippet": "The Next, Next Generation tablet."}, {"name": "MOTOROLA XOOM", "snippet": "The Next, Next Generation tablet."} ]; });
任何時候只要是底層的model數(shù)據(jù)發(fā)生了變化,我們在DOM中的列表也會跟著更新。
臟值檢查的基本原理就是只要任何時候數(shù)據(jù)發(fā)生了變化,這個庫都會通過一個digest或者change cycle去檢查變化是否發(fā)生了。在Angular中,一個digest循環(huán)意味著所有所有被監(jiān)視的表達(dá)式都會被循環(huán)一遍以便查看其中是否有變化發(fā)生。它智斗一個模型之前的值因此當(dāng)變化發(fā)生時,一個change事件將會被觸發(fā)。對于開發(fā)者來說,這帶來的一大好處就是你可以使用原生的JavaScript對象數(shù)據(jù),它易于使用及整合。下面的圖片展示的是一個非常糟糕的算法,它的開銷非常大。
這個操作的開銷和被監(jiān)視的對象的數(shù)量是成正比的。我可能需要做很多的臟治檢查。同時當(dāng)數(shù)據(jù)發(fā)生改變時,我也需要一種方式去觸發(fā)臟值檢查.
相比Angular的臟值檢查,Vue的setter/getter方案使數(shù)據(jù)和DOM更新的時間復(fù)雜度降低,數(shù)據(jù)的更新只發(fā)生在數(shù)據(jù)發(fā)生改變時,數(shù)據(jù)更新的時間復(fù)雜度只和數(shù)據(jù)的觀察者有關(guān),"它們擁有一些存取器去獲取數(shù)據(jù)并且能夠在你設(shè)置或者獲取對象時捕獲到這些行為并在內(nèi)部進(jìn)行廣播".
vue的約束的模型系統(tǒng)而且相比Object.observer()[在es7標(biāo)準(zhǔn)中],Vue的存取方式可以做到比較好的兼容性.
Vue實(shí)現(xiàn)簡單的watcher1.實(shí)現(xiàn)observer 2.Vue消息-訂閱器 3.Watcher的實(shí)現(xiàn) 4.實(shí)現(xiàn)一個Vue
實(shí)現(xiàn)一個 $wacth
const v = new Vue({ data:{ a:1, b:2 } }) v.$watch("a",()=>console.log("哈哈,$watch成功")) setTimeout(()=>{ v.a = 5 },2000) //打印 哈哈,$watch成功
為了幫助大家理清思路。。我們就做最簡單的實(shí)現(xiàn)。。只考慮對象不考慮數(shù)組
將要observe的對象, 通過遞歸,將它所有的屬性,包括子屬性的屬性,都給加上set和get, 這樣的話,給這個對象的某個屬性賦值,就會觸發(fā)set。就給每個屬性(包括子屬性)都加上get/set, 這樣的話,這個對象的,有任何賦值,就會觸發(fā)set方法。
export default class Observer{ constructor(value) { this.value = value this.walk(value) } //遞歸。。讓每個字屬性可以observe walk(value){ Object.keys(value).forEach(key=>this.convert(key,value[key])) } convert(key, val){ defineReactive(this.value, key, val) } }
export function defineReactive (obj, key, val) { var childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: ()=>val, set:newVal=> { childOb = observe(newVal)//如果新賦值的值是個復(fù)雜類型。再遞歸它,加上set/get。。 } }) }
export function observe (value, vm) { if (!value || typeof value !== "object") { return } return new Observer(value) }
維護(hù)一個數(shù)組,,這個數(shù)組,就放訂閱著,一旦觸發(fā)notify, 訂閱者就調(diào)用自己的update方法
export default class Dep { constructor() { this.subs = [] } addSub(sub){ this.subs.push(sub) } notify(){ this.subs.forEach(sub=>sub.update()) } }
每次set函數(shù),調(diào)用的時候,我們是不是應(yīng)該,觸發(fā)notify,對吧。所以 我們把代碼補(bǔ)充完整
export function defineReactive (obj, key, val) { var dep = new Dep() var childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: ()=>val, set:newVal=> { var value = val if (newVal === value) { return } val = newVal childOb = observe(newVal) dep.notify() } }) }
v.$watch("a",()=>console.log("哈哈,$watch成功"))
我們想象這個Watcher,應(yīng)該用什么東西。update方法,嗯這個毋庸置疑, 還有呢,
對表達(dá)式(就是那個“a”) 和 回調(diào)函數(shù),這是最基本的,所以我們簡單寫寫
export default class Watcher { constructor(vm, expOrFn, cb) { this.cb = cb this.vm = vm //此處簡化.要區(qū)分fuction還是expression,只考慮最簡單的expression this.expOrFn = expOrFn this.value = this.get() } update(){ this.run() } run(){ const value = this.get() if(value !==this.value){ this.value = value this.cb.call(this.vm) } } get(){ //此處簡化。。要區(qū)分fuction還是expression const value = this.vm._data[this.expOrFn] return value } }
怎樣將通過addSub(),將Watcher加進(jìn)去呢。 我們發(fā)現(xiàn)var dep = new Dep() 處于閉包當(dāng)中, 我們又發(fā)現(xiàn)Watcher的構(gòu)造函數(shù)里會調(diào)用this.get 所以,我們可以在上面動動手腳, 修改一下Object.defineProperty的get要調(diào)用的函數(shù), 判斷是不是Watcher的構(gòu)造函數(shù)調(diào)用,如果是,說明他就是這個屬性的訂閱者 果斷將他addSub()中去,那問題來了, 我怎樣判斷他是Watcher的this.get調(diào)用的,而不是我們普通調(diào)用的呢
export default class Watcher { ....省略未改動代碼.... get(){ Dep.target = this //此處簡化。。要區(qū)分fuction還是expression const value = this.vm._data[this.expOrFn] Dep.target = null return value } }
這樣的話,我們只需要在Object.defineProperty的get要調(diào)用的函數(shù)里, 判斷有沒有值,就知道到底是Watcher 在get,還是我們自己在查看賦值,如果 是Watcher的話就addSub(),代碼補(bǔ)充一下
export function defineReactive (obj, key, val) { var dep = new Dep() var childOb = observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: ()=>{ // 說明這是watch 引起的 if(Dep.target){ dep.addSub(Dep.target) } return val }, set:newVal=> { var value = val if (newVal === value) { return } val = newVal childOb = observe(newVal) dep.notify() } }) }
最后不要忘記,在Dep.js中加上這么一句
Dep.target = null
我們要把以上代碼配合Vue的$watch方法來用, 要watch Vue實(shí)例的屬性:
import Watcher from "../watcher" import {observe} from "../observer" export default class Vue { constructor (options={}) { //這里簡化了。。其實(shí)要merge this.$options=options //這里簡化了。。其實(shí)要區(qū)分的 let data = this._data=this.$options.data Object.keys(data).forEach(key=>this._proxy(key)) observe(data,this) }
$watch(expOrFn, cb, options){ new Watcher(this, expOrFn, cb) } _proxy(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自己的data,代理自己的data, 使訪問自己的屬性,就是訪問子data的屬性。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/84624.html
摘要:當(dāng)前正在處理的節(jié)點(diǎn),以及該節(jié)點(diǎn)的和等信息。源碼解析之一整體分析源碼解析之三寫作中源碼解析之四寫作中作者博客作者作者微博 筆者系 vue-loader 貢獻(xiàn)者之一(#16) 前言 vue-loader 源碼解析系列之一,閱讀該文章之前,請大家首先參考大綱 vue-loader 源碼解析系列之 整體分析 selector 做了什么 const path = require(path) co...
摘要:寫文章不容易,點(diǎn)個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于版本如果你覺得排版難看,請點(diǎn)擊下面鏈接或者拉到下面關(guān)注公眾號也可以吧原理源碼版今天繼續(xù)探索源碼,廢話不 寫文章不容易,點(diǎn)個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內(nèi)部詳情,讓我們一起學(xué)習(xí)吧研究基于...
摘要:假如你通過閱讀源碼,掌握了對的實(shí)現(xiàn)原理,對生態(tài)系統(tǒng)有了充分的認(rèn)識,那你會在面試環(huán)節(jié)游刃有余,達(dá)到晉級阿里的技術(shù)功底,從而提高個人競爭力,面試加分更容易拿。 前言 一年一度緊張刺激的高考開始了,與此同時,我也沒閑著,奔走在各大公司的前端面試環(huán)節(jié),不斷積累著經(jīng)驗(yàn),一路升級打怪。 最近兩年,太原作為一個準(zhǔn)二線城市,各大互聯(lián)網(wǎng)公司的技術(shù)棧也在升級換代,假如你在太原面試前端崗位,而你的技術(shù)庫里若...
摘要:五六月份推薦集合查看最新的請點(diǎn)擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風(fēng)荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點(diǎn)擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...
閱讀 1683·2023-04-26 00:30
閱讀 3155·2021-11-25 09:43
閱讀 2884·2021-11-22 14:56
閱讀 3194·2021-11-04 16:15
閱讀 1155·2021-09-07 09:58
閱讀 2028·2019-08-29 13:14
閱讀 3113·2019-08-29 12:55
閱讀 993·2019-08-29 10:57