摘要:你可以使用的方法傳入指令和定義對象來注冊一個全局自定義指令。深度數據觀察如果你希望在一個對象上使用自定義指令,并且當對象內部嵌套的屬性發生變化時也能夠觸發指令的函數,那么你就要在指令的定義中傳入。
Vue簡介
數據綁定
/** *假設有這么兩個鐘東西 **/ //數據 var object = { message: "Hello World!" } //DOM{{ message }}/** *我們可以這么寫 **/
new Vue({ el: "#example", data: object }) /** * 如果有個數據 **/ var object1 = { message: "Hello World!" } var object2 = { message: "Hello World!" } //DOM{{ message }}{{ message }}/** *我們還可以這么寫 **/ var vm1 = new Vue({el: "#example1",data: object}) //改變vm1的數據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!" } } }) // 將該組件注冊為標簽 Vue.component("example", Example) Vue 在組件化上和 React 類似:一切都是組件。 組件使用上也和React一致:
組件之間數據傳遞:
1.用 props 來定義如何接收外部數據; Vue.component("child", { // 聲明 props props: ["msg"], // prop 可以用在模板內 // 可以用 `this.msg` 設置 template: "{{ msg }}" })2.用自定義事件來向外傳遞消息; 使用 $on() 監聽事件; 使用 $emit() 在它上面觸發事件; 使用 $dispatch() 派發事件,事件沿著父鏈冒泡; 使用 $broadcast() 廣播事件,事件向下傳導給所有的后代。 3.用 API 來將外部動態傳入的內容(其他組件或是 HTML)和自身模板進行組合;
模塊化
Webpack 或者 Browserify,然后再加上 ES2015配合 vue-loader 或是 vueify,就可以把Vue的每一個組件變成 Web Components
路由
使用Vue重構的Angular項目 www.songxuemeng.com/diary 個人感覺vue-router煩的問題是組件之間的數據交互,rootRouter的數據很難向其他組件傳遞. /** *解決方法 **/ 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的程序結構,程序大體可以分為:
全局設計:包括全局接口、默認選項等;
vm實例設計:包括接口(vm原形)、實例初始化過程(vm構造函數)
下面是構造函數最核心的工作內容。
http://img3.tbcdn.cn/L1/461/1...
整個實例初始化的過程中,重中之重就是把數據 (Model) 和視圖 (View) 建立起關聯關系。Vue.js 和諸多 MVVM 的思路是類似的,主要做了三件事:
通過 observer 對 data 進行了監聽,并且提供訂閱某個數據項的變化的能力
把 template 解析成一段 document fragment,然后解析其中的 directive,得到每一個 directive 所依賴的數據項及其更新方法。比如 v-text="message" 被解析之后;
所依賴的數據項 this.$data.message,以及
相應的視圖更新方法 node.textContent = this.$data.message
通過 watcher 把上述兩部分結合起來,即把 directive 中的數據依賴訂閱在對應數據的 observer 上,這樣當數據變化的時候,就會觸發 observer,進而觸發相關依賴對應的視圖更新方法,最后達到模板原本的關聯效果。
所以整個 vm 的核心,就是如何實現 observer, directive (parser), watcher 這三樣東西
http://img4.tbcdn.cn/L1/461/1...
數據列表的更新視圖更新效率的焦點問題主要在于大列表的更新和深層數據更新這兩方面.
但是工作中經常用的主要是前者
首先 diff(data, oldVms) 這個函數的注釋對整個比對更新機制做了個簡要的闡述,大概意思是先比較新舊兩個列表的 vm 的數據的狀態,然后差量更新 DOM。
第一步:便利新列表里的每一項,如果該項的 vm 之前就存在,則打一個 _reused 的標,如果不存在對應的 vm,則創建一個新的。
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); } }
第二步:便利舊列表里的每一項,如果 _reused 的標沒有被打上,則說明新列表里已經沒有它了,就地銷毀該 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 標。就此列表更新完成
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 為其組件設計了一個 [keep-alive] 的特性,如果這個特性存在,那么在組件被重復創建的時候,會通過緩存機制快速創建組件,以提升視圖更新的性能。 bind: function bind() { if (!this.el.__vue__) { // keep-alive cache this.keepAlive = this.params.keepAlive; if (this.keepAlive) { this.cache = {}; } ..... }數據監聽機制 對象數據監聽
"Vue"使用"Object.defineProperty"這個"API"為想要監聽的屬性增加了對應的"getter"和"setter",每次數據改變的時候在setter中觸發函數"dep.notify()",來達到數據監聽的效果
//對要監聽的屬性使用Object.defineProperty重寫get和set函數,增加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的時候調用!!! if (newVal === value) { return; } //增加setter if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = observe(newVal); //最后調用一個自己的函數 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++) { //對相應的數據進行更新 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"是維護數據的一個數組,對應著一個"watcher"對象
所以整個數據監聽的完成是靠set給屬性提供一個setter然后當數據更新時,dep會觸發watcher對象,返回新值.
之后會有更詳細解釋
數組可能會有點麻煩,Vue.js 采取的是對幾乎每一個可能改變數據的方法進行 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 來彌補這方面限制帶來的不便。
但這個策略主要面臨兩個問題:
無法監聽數據的 length,導致 arr.length 這樣的數據改變無法被監聽
通過角標更改數據,即類似 arr[2] = 1 這樣的賦值操作,也無法被監聽
為此 Vue.js 在文檔中明確提示不建議直接角標修改數據
"實例計算屬性。getter 和 setter 的 this 自動地綁定到實例。"
舉個栗子:
var vm = new Vue({ data: { a: 1 }, computed: { // 僅讀取,值只須為函數 b: function () { return this.a * 2 }, // 讀取和設置 c: { get: function () { return this.a + 1 }, set: function (v) { this.a = v - 1 } } } })
可以看出來computed可以提供自定義一個屬性c的getter和setter/b的getter,問題是c和b怎么維護和a的關系
下面是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在建立的時候綁定一個對應的 watcher 對象,在計算過程中它把屬性記錄為依賴。之后當依賴的 setter 被調用時,會觸發 watcher 重新計算 ,也就會導致它的關聯指令更新 DOM。
視圖解析過程 解析器parsers/path.js 主要的職責是可以把一個 JSON 數據里的某一個“路徑”下的數據取出來,比如:
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] }
狀態機可以完成
1.dom結構中{{data.someObj}}的解析; 2.對字符型json的取值;
可惜大學里面的編譯原理我給忘記了,否則可以給大家解析一下.
視圖解析過程視圖的解析過程,Vue.js 的策略是把 element 或 template string 先統一轉換成 document fragment,然后再分解和解析其中的子組件和 directives。
相比React的visual DOM有一定的性能優化空間,畢竟 DOM 操作相比純 JavaScript 運算還是會慢一些。
Vue擴展 MixinMixin (混入) 是一種可以在多個 Vue 組件之間靈活復用特性的機制。你可以像寫一個普通 Vue 組件的選項對象一樣編寫一個 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 實例方法。這里有個約定,就是 Vue 的實例方法應該帶有 $ 前綴,這樣就不會和用戶的數據和方法產生沖突了。
MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或屬性 Vue.myGlobalMethod = ... // 2. 添加全局資源 Vue.directive("my-directive", {}) // 3. 添加實例方法 Vue.prototype.$myMethod = ... }
var vueTouch = require("vue-touch") // use the plugin globally Vue.use(vueTouch) 你也可以向插件里傳遞額外的選項: Vue.use(require("my-plugin"), { /* pass in additional options */ }) 全局方法: Vue.fun() 局部方法: vm.$fun()Vue指令
Vue.js 允許注冊自定義指令,實質上是開放 Vue 一些技巧:怎樣將數據的變化映射到 DOM 的行為。你可以使用 Vue.directive(id, definition) 的方法傳入指令 id 和定義對象來注冊一個全局自定義指令。定義對象需要提供一些鉤子函數:
bind: 僅調用一次,當指令第一次綁定元素的時候。
update: 第一次是緊跟在 bind 之后調用,獲得的參數是綁定的初始值;以后每當綁定的值發生變化就會被調用,獲得新值與舊值兩個參數。
unbind:僅調用一次,當指令解綁元素的時候。
一旦注冊好自定義指令,你就可以在 Vue.js 模板中像這樣來使用它(需要添加 Vue.js 的指令前綴,默認為 v-):
如果你只需要 update 函數,你可以只傳入一個函數,而不用傳定義對象:
// 這個函數會被作為 update() 函數使用 })``` 所有的鉤子函數會被復制到實際的**指令對象**中,而這個指令對象將會是所有鉤子函數的 `this` 上下文環境。指令對象上暴露了一些有用的公開屬性: - **el**: 指令綁定的元素 - **vm**: 擁有該指令的上下文 ViewModel - **expression**: 指令的表達式,不包括參數和過濾器 - **arg**: 指令的參數 - **raw**: 未被解析的原始表達式 - **name**: 不帶前綴的指令名 >這些屬性是只讀的,不要修改它們。你也可以給指令對象附加自定義的屬性,但是注意不要覆蓋已有的內部屬性。 使用指令對象屬性的示例: ``
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!
多重從句同一個特性內部,逗號分隔的多個從句將被綁定為多個指令實例。在下面的例子中,指令會被創建和調用兩次:
如果想要用單個指令實例處理多個參數,可以利用字面量對象作為表達式:
console.log(value) // Object {color: "white", text: "hello!"} })``` ## 字面指令 如果在創建自定義指令的時候傳入 `isLiteral: true`,那么特性值就會被看成直接字符串,并被賦值給該指令的 `expression`。字面指令不會試圖建立數據監視。 **Example**: ``
isLiteral: true,
bind: function () {
console.log(this.expression) // "foo"
}
})`
然而,在字面指令含有 Mustache 標簽的情形下,指令的行為如下:
指令實例會有一個屬性,this._isDynamicLiteral 被設為 true;
如果沒有提供 update 函數,Mustache 表達式只會被求值一次,并將該值賦給 this.expression 。不會對表達式進行數據監視。
如果提供了 update 函數,指令將會為表達式建立一個數據監視,并且在計算結果變化的時候調用 update。
雙向指令如果你的指令想向 Vue 實例寫回數據,你需要傳入 twoWay: true 。該選項允許在指令中使用 this.set(value)。
twoWay: true, bind: function () { this.handler = function () { // 把數據寫回 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) } })``` ## 內聯語句 傳入 `acceptStatement: true` 可以讓自定義指令像 `v-on` 一樣接受內聯語句: ``
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.
}
})`
但是請明智地使用此功能,因為通常我們希望避免在模板中產生副作用。
深度數據觀察如果你希望在一個對象上使用自定義指令,并且當對象內部嵌套的屬性發生變化時也能夠觸發指令的 update 函數,那么你就要在指令的定義中傳入 deep: true。
deep: true, update: function (obj) { // 當 obj 內部嵌套的屬性變化時也會調用此函數 } })``` ## 指令優先級 你可以選擇給指令提供一個優先級數(默認是 0)。同一個元素上優先級越高的指令會比其他的指令處理得早一些。優先級一樣的指令會按照其在元素特性列表中出現的順序依次處理,但是不能保證這個順序在不同的瀏覽器中是一致的。 通常來說作為用戶,你并不需要關心內置指令的優先級,如果你感興趣的話,可以參閱源碼。邏輯控制指令 `v-repeat`, `v-if` 被視為 “終結性指令”,它們在編譯過程中始終擁有最高的優先級。 ## 元素指令 有時候,我們可能想要我們的指令可以以自定義元素的形式被使用,而不是作為一個特性。這與 `Angular` 的 `E` 類指令的概念非常相似。元素指令可以看做是一個輕量的自定義組件(后面會講到)。你可以像下面這樣注冊一個自定義的元素指令:
// 和普通指令的 API 一致
bind: function () {
// 對 this.el 進行操作...
}
})
Mixin (混入) 是一種可以在多個 Vue 組件之間靈活復用特性的機制。你可以像寫一個普通 Vue 組件的選項對象一樣編寫一個 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 實例方法。這里有個約定,就是 Vue 的實例方法應該帶有 $ 前綴,這樣就不會和用戶的數據和方法產生沖突了。
MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或屬性 Vue.myGlobalMethod = ... // 2. 添加全局資源 Vue.directive("my-directive", {}) // 3. 添加實例方法 Vue.prototype.$myMethod = ... }
var vueTouch = require("vue-touch") // use the plugin globally Vue.use(vueTouch) 你也可以向插件里傳遞額外的選項: Vue.use(require("my-plugin"), { /* pass in additional options */ }) 全局方法: Vue.fun() 局部方法: vm.$fun()Vue指令
Vue.js 允許注冊自定義指令,實質上是開放 Vue 一些技巧:怎樣將數據的變化映射到 DOM 的行為。你可以使用 Vue.directive(id, definition) 的方法傳入指令 id 和定義對象來注冊一個全局自定義指令。定義對象需要提供一些鉤子函數:
bind: 僅調用一次,當指令第一次綁定元素的時候。
update: 第一次是緊跟在 bind 之后調用,獲得的參數是綁定的初始值;以后每當綁定的值發生變化就會被調用,獲得新值與舊值兩個參數。
unbind:僅調用一次,當指令解綁元素的時候。
一旦注冊好自定義指令,你就可以在 Vue.js 模板中像這樣來使用它(需要添加 Vue.js 的指令前綴,默認為 v-):
如果你只需要 update 函數,你可以只傳入一個函數,而不用傳定義對象:
// 這個函數會被作為 update() 函數使用 })``` 所有的鉤子函數會被復制到實際的**指令對象**中,而這個指令對象將會是所有鉤子函數的 `this` 上下文環境。指令對象上暴露了一些有用的公開屬性: - **el**: 指令綁定的元素 - **vm**: 擁有該指令的上下文 ViewModel - **expression**: 指令的表達式,不包括參數和過濾器 - **arg**: 指令的參數 - **raw**: 未被解析的原始表達式 - **name**: 不帶前綴的指令名 >這些屬性是只讀的,不要修改它們。你也可以給指令對象附加自定義的屬性,但是注意不要覆蓋已有的內部屬性。 使用指令對象屬性的示例: ``
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!
多重從句同一個特性內部,逗號分隔的多個從句將被綁定為多個指令實例。在下面的例子中,指令會被創建和調用兩次:
如果想要用單個指令實例處理多個參數,可以利用字面量對象作為表達式:
console.log(value) // Object {color: "white", text: "hello!"} })``` ## 字面指令 如果在創建自定義指令的時候傳入 `isLiteral: true`,那么特性值就會被看成直接字符串,并被賦值給該指令的 `expression`。字面指令不會試圖建立數據監視。 **Example**: ``
isLiteral: true,
bind: function () {
console.log(this.expression) // "foo"
}
})`
然而,在字面指令含有 Mustache 標簽的情形下,指令的行為如下:
指令實例會有一個屬性,this._isDynamicLiteral 被設為 true;
如果沒有提供 update 函數,Mustache 表達式只會被求值一次,并將該值賦給 this.expression 。不會對表達式進行數據監視。
如果提供了 update 函數,指令將會為表達式建立一個數據監視,并且在計算結果變化的時候調用 update。
雙向指令如果你的指令想向 Vue 實例寫回數據,你需要傳入 twoWay: true 。該選項允許在指令中使用 this.set(value)。
twoWay: true, bind: function () { this.handler = function () { // 把數據寫回 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) } })``` ## 內聯語句 傳入 `acceptStatement: true` 可以讓自定義指令像 `v-on` 一樣接受內聯語句: ``
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.
}
})`
但是請明智地使用此功能,因為通常我們希望避免在模板中產生副作用。
深度數據觀察如果你希望在一個對象上使用自定義指令,并且當對象內部嵌套的屬性發生變化時也能夠觸發指令的 update 函數,那么你就要在指令的定義中傳入 deep: true。
deep: true, update: function (obj) { // 當 obj 內部嵌套的屬性變化時也會調用此函數 } })``` ## 指令優先級 你可以選擇給指令提供一個優先級數(默認是 0)。同一個元素上優先級越高的指令會比其他的指令處理得早一些。優先級一樣的指令會按照其在元素特性列表中出現的順序依次處理,但是不能保證這個順序在不同的瀏覽器中是一致的。 通常來說作為用戶,你并不需要關心內置指令的優先級,如果你感興趣的話,可以參閱源碼。邏輯控制指令 `v-repeat`, `v-if` 被視為 “終結性指令”,它們在編譯過程中始終擁有最高的優先級。 ## 元素指令 有時候,我們可能想要我們的指令可以以自定義元素的形式被使用,而不是作為一個特性。這與 `Angular` 的 `E` 類指令的概念非常相似。元素指令可以看做是一個輕量的自定義組件(后面會講到)。你可以像下面這樣注冊一個自定義的元素指令:
// 和普通指令的 API 一致
bind: function () {
// 對 this.el 進行操作...
}
})
Angular Modules angular.module("myModule", [...]); Components Vue.extend({ data: function(){ return {...} }, created: function() {...}, ready: function() {...}, components: {...}, methods: {...},
總體來說
對于Angular來說module就是一個容器,而對Vue來說一個component里面會有邏輯代碼
在Vue里面會放進許多代碼細節,并且有固定的屬性
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}}
當輸出是一個對象的時候
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指令使事件更加一致
臟值檢查一個電話列表應用的例子,在其中我們會將一個phones數組中的值(在JavaScript中定義)綁定到一個列表項目中以便于我們的數據和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數據發生了變化,我們在DOM中的列表也會跟著更新。
臟值檢查的基本原理就是只要任何時候數據發生了變化,這個庫都會通過一個digest或者change cycle去檢查變化是否發生了。在Angular中,一個digest循環意味著所有所有被監視的表達式都會被循環一遍以便查看其中是否有變化發生。它智斗一個模型之前的值因此當變化發生時,一個change事件將會被觸發。對于開發者來說,這帶來的一大好處就是你可以使用原生的JavaScript對象數據,它易于使用及整合。下面的圖片展示的是一個非常糟糕的算法,它的開銷非常大。
這個操作的開銷和被監視的對象的數量是成正比的。我可能需要做很多的臟治檢查。同時當數據發生改變時,我也需要一種方式去觸發臟值檢查.
相比Angular的臟值檢查,Vue的setter/getter方案使數據和DOM更新的時間復雜度降低,數據的更新只發生在數據發生改變時,數據更新的時間復雜度只和數據的觀察者有關,"它們擁有一些存取器去獲取數據并且能夠在你設置或者獲取對象時捕獲到這些行為并在內部進行廣播".
vue的約束的模型系統而且相比Object.observer()[在es7標準中],Vue的存取方式可以做到比較好的兼容性.
Vue實現簡單的watcher1.實現observer 2.Vue消息-訂閱器 3.Watcher的實現 4.實現一個Vue
實現一個 $wacth
const v = new Vue({ data:{ a:1, b:2 } }) v.$watch("a",()=>console.log("哈哈,$watch成功")) setTimeout(()=>{ v.a = 5 },2000) //打印 哈哈,$watch成功
為了幫助大家理清思路。。我們就做最簡單的實現。。只考慮對象不考慮數組
將要observe的對象, 通過遞歸,將它所有的屬性,包括子屬性的屬性,都給加上set和get, 這樣的話,給這個對象的某個屬性賦值,就會觸發set。就給每個屬性(包括子屬性)都加上get/set, 這樣的話,這個對象的,有任何賦值,就會觸發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)//如果新賦值的值是個復雜類型。再遞歸它,加上set/get。。 } }) }
export function observe (value, vm) { if (!value || typeof value !== "object") { return } return new Observer(value) }
維護一個數組,,這個數組,就放訂閱著,一旦觸發notify, 訂閱者就調用自己的update方法
export default class Dep { constructor() { this.subs = [] } addSub(sub){ this.subs.push(sub) } notify(){ this.subs.forEach(sub=>sub.update()) } }
每次set函數,調用的時候,我們是不是應該,觸發notify,對吧。所以 我們把代碼補充完整
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,應該用什么東西。update方法,嗯這個毋庸置疑, 還有呢,
對表達式(就是那個“a”) 和 回調函數,這是最基本的,所以我們簡單寫寫
export default class Watcher { constructor(vm, expOrFn, cb) { this.cb = cb this.vm = vm //此處簡化.要區分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(){ //此處簡化。。要區分fuction還是expression const value = this.vm._data[this.expOrFn] return value } }
怎樣將通過addSub(),將Watcher加進去呢。 我們發現var dep = new Dep() 處于閉包當中, 我們又發現Watcher的構造函數里會調用this.get 所以,我們可以在上面動動手腳, 修改一下Object.defineProperty的get要調用的函數, 判斷是不是Watcher的構造函數調用,如果是,說明他就是這個屬性的訂閱者 果斷將他addSub()中去,那問題來了, 我怎樣判斷他是Watcher的this.get調用的,而不是我們普通調用的呢
export default class Watcher { ....省略未改動代碼.... get(){ Dep.target = this //此處簡化。。要區分fuction還是expression const value = this.vm._data[this.expOrFn] Dep.target = null return value } }
這樣的話,我們只需要在Object.defineProperty的get要調用的函數里, 判斷有沒有值,就知道到底是Watcher 在get,還是我們自己在查看賦值,如果 是Watcher的話就addSub(),代碼補充一下
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實例的屬性:
import Watcher from "../watcher" import {observe} from "../observer" export default class Vue { constructor (options={}) { //這里簡化了。。其實要merge this.$options=options //這里簡化了。。其實要區分的 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的屬性。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84624.html
摘要:當前正在處理的節點,以及該節點的和等信息。源碼解析之一整體分析源碼解析之三寫作中源碼解析之四寫作中作者博客作者作者微博 筆者系 vue-loader 貢獻者之一(#16) 前言 vue-loader 源碼解析系列之一,閱讀該文章之前,請大家首先參考大綱 vue-loader 源碼解析系列之 整體分析 selector 做了什么 const path = require(path) co...
摘要:寫文章不容易,點個贊唄兄弟專注源碼分享,文章分為白話版和源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于版本如果你覺得排版難看,請點擊下面鏈接或者拉到下面關注公眾號也可以吧原理源碼版今天繼續探索源碼,廢話不 寫文章不容易,點個贊唄兄弟專注 Vue 源碼分享,文章分為白話版和 源碼版,白話版助于理解工作原理,源碼版助于了解內部詳情,讓我們一起學習吧研究基于...
摘要:假如你通過閱讀源碼,掌握了對的實現原理,對生態系統有了充分的認識,那你會在面試環節游刃有余,達到晉級阿里的技術功底,從而提高個人競爭力,面試加分更容易拿。 前言 一年一度緊張刺激的高考開始了,與此同時,我也沒閑著,奔走在各大公司的前端面試環節,不斷積累著經驗,一路升級打怪。 最近兩年,太原作為一個準二線城市,各大互聯網公司的技術棧也在升級換代,假如你在太原面試前端崗位,而你的技術庫里若...
摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...
閱讀 1668·2023-04-26 00:30
閱讀 3145·2021-11-25 09:43
閱讀 2868·2021-11-22 14:56
閱讀 3183·2021-11-04 16:15
閱讀 1137·2021-09-07 09:58
閱讀 2013·2019-08-29 13:14
閱讀 3101·2019-08-29 12:55
閱讀 982·2019-08-29 10:57