深刻解析Vue源碼

Vue簡介

數據綁定css

/**
*假設有這麼兩個鍾東西
**/
//數據
var object = {
  message: 'Hello World!'
}
//DOM
<div id="example">
  {{ message }}
</div>

/**
*咱們能夠這麼寫
**/
new Vue({
  el: '#example',
  data: object
})

/**
* 若是有個數據
**/

var object1 = {
  message: 'Hello World!'
}

var object2 = {
  message: 'Hello World!'
}

//DOM
<div id="example1">
  {{ message }}
</div>

<div id="example2">
  {{ message }}
</div>

/**
*咱們還能夠這麼寫
**/

var vm1 = new Vue({el: '#example1',data: object})
//改變vm1的數據DOM隨之改變
vm2.message = 'oliver'

var vm2 = new Vue({el: '#example2',data: object})

vm2.message = 'lisa'

組件化html

var Example = Vue.extend({
template: '<div>{{ message }}</div>',
data: function () {
return {
  message: 'Hello Vue.js!'
}
}
})

// 將該組件註冊爲 <example> 標籤
Vue.component('example', Example)

Vue 在組件化上和 React 相似:一切都是組件。
組件使用上也和React一致:

<example></example>

組件之間數據傳遞:vue

1.用 props 來定義如何接收外部數據;
Vue.component('child', {
  // 聲明 props
  props: ['msg'],
  // prop 能夠用在模板內
  // 能夠用 `this.msg` 設置
  template: '<span>{{ msg }}</span>'
})
<child msg="hello!"></child>

2.用自定義事件來向外傳遞消息;
使用 $on() 監聽事件;
使用 $emit() 在它上面觸發事件;
使用 $dispatch() 派發事件,事件沿着父鏈冒泡;
使用 $broadcast() 廣播事件,事件向下傳導給全部的後代。

3.用 <slot> API 來將外部動態傳入的內容(其餘組件或是 HTML)和自身模板進行組合;

模塊化node

Webpack 或者 Browserify,而後再加上 ES2015配合 vue-loader 或是 vueify,就能夠把Vue的每個組件變成
Web Components

<!-- MyComponent.vue -->

<!-- css -->
<style>
.message {
  color: red;
}
</style>

<!-- template -->
<template>
  <div class="message">{{ message }}</div>
</template>

<!-- js -->
<script>
export default {
  props: ['message'],
  created() {
    console.log('MyComponent created!')
  }
}
</script>

路由react

使用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...angularjs

Vue.js是一個典型的MVVM的程序結構,程序大致能夠分爲:
全局設計:包括全局接口、默認選項等;
vm實例設計:包括接口(vm原形)、實例初始化過程(vm構造函數)算法

下面是構造函數最核心的工做內容。vue-router

http://img3.tbcdn.cn/L1/461/1...express

整個實例初始化的過程當中,重中之重就是把數據 (Model) 和視圖 (View) 創建起關聯關係。Vue.js 和諸多 MVVM 的思路是相似的,主要作了三件事:json

經過 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 這三樣東西

vue文件結構

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擴展

Mixin

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 的實例方法應該帶有 $ 前綴,這樣就不會和用戶的數據和方法產生衝突了。
開發Vue插件
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或屬性
Vue.myGlobalMethod = ...
// 2. 添加全局資源
Vue.directive('my-directive', {})
// 3. 添加實例方法
Vue.prototype.$myMethod = ...
}
使用Vue插件
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-):

<div v-my-directive="someValue"></div>

若是你只須要 update 函數,你能夠只傳入一個函數,而不用傳定義對象:

// 這個函數會被做爲 update() 函數使用
})```

全部的鉤子函數會被複制到實際的**指令對象**中,而這個指令對象將會是全部鉤子函數的 `this` 上下文環境。指令對象上暴露了一些有用的公開屬性:

- **el**: 指令綁定的元素
- **vm**: 擁有該指令的上下文 ViewModel
- **expression**: 指令的表達式,不包括參數和過濾器
- **arg**: 指令的參數
- **raw**: 未被解析的原始表達式
- **name**: 不帶前綴的指令名

>這些屬性是隻讀的,不要修改它們。你也能夠給指令對象附加自定義的屬性,可是注意不要覆蓋已有的內部屬性。

使用指令對象屬性的示例:

`<div id="demo" v-demo="LightSlateGray : msg"></div>`

bind: function () {

this.el.style.color = '#fff'
this.el.style.backgroundColor = this.arg

},
update: function (value) {

this.el.innerHTML =
  'name - '       + this.name + '<br>' +
  'raw - '        + this.raw + '<br>' +
  'expression - ' + this.expression + '<br>' +
  'argument - '   + this.arg + '<br>' +
  'value - '      + value

}
})
var demo = new Vue({
el: '#demo',
data: {

msg: 'hello!'

}
})`

Result

  • name - demo
  • raw - LightSlateGray:msg
  • expression - msg
  • argument - LightSlateGray
  • value - hello!

多重從句

同一個特性內部,逗號分隔的多個從句將被綁定爲多個指令實例。在下面的例子中,指令會被建立和調用兩次:

<div v-demo="color: 'white', text: 'hello!'"></div>

若是想要用單個指令實例處理多個參數,能夠利用字面量對象做爲表達式:

<div v-demo="{color: 'white', text: 'hello!'}"></div>

console.log(value) // Object {color: 'white', text: 'hello!'}
})```

## 字面指令

若是在建立自定義指令的時候傳入 `isLiteral: true`,那麼特性值就會被當作直接字符串,並被賦值給該指令的 `expression`。字面指令不會試圖創建數據監視。

**Example**:

`<div v-literal-dir="foo"></div>`

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` 同樣接受內聯語句:

`<div v-my-directive="a++"></div>`

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

<div v-my-directive="obj"></div>

deep: true,
  update: function (obj) {
    // 當 obj 內部嵌套的屬性變化時也會調用此函數
  }
})```

## 指令優先級

你能夠選擇給指令提供一個優先級數(默認是 0)。同一個元素上優先級越高的指令會比其餘的指令處理得早一些。優先級同樣的指令會按照其在元素特性列表中出現的順序依次處理,可是不能保證這個順序在不一樣的瀏覽器中是一致的。

一般來講做爲用戶,你並不須要關心內置指令的優先級,若是你感興趣的話,能夠參閱源碼。邏輯控制指令 `v-repeat`, `v-if` 被視爲 「終結性指令」,它們在編譯過程當中始終擁有最高的優先級。

## 元素指令

有時候,咱們可能想要咱們的指令能夠以自定義元素的形式被使用,而不是做爲一個特性。這與 `Angular` 的 `E` 類指令的概念很是類似。元素指令能夠看作是一個輕量的自定義組件(後面會講到)。你能夠像下面這樣註冊一個自定義的元素指令:

// 和普通指令的 API 一致
bind: function () {

// 對 this.el 進行操做...

}
})

Vue擴展

Mixin

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 的實例方法應該帶有 $ 前綴,這樣就不會和用戶的數據和方法產生衝突了。
開發Vue插件
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或屬性
Vue.myGlobalMethod = ...
// 2. 添加全局資源
Vue.directive('my-directive', {})
// 3. 添加實例方法
Vue.prototype.$myMethod = ...
}
使用Vue插件
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-):

<div v-my-directive="someValue"></div>

若是你只須要 update 函數,你能夠只傳入一個函數,而不用傳定義對象:

// 這個函數會被做爲 update() 函數使用
})```

全部的鉤子函數會被複制到實際的**指令對象**中,而這個指令對象將會是全部鉤子函數的 `this` 上下文環境。指令對象上暴露了一些有用的公開屬性:

- **el**: 指令綁定的元素
- **vm**: 擁有該指令的上下文 ViewModel
- **expression**: 指令的表達式,不包括參數和過濾器
- **arg**: 指令的參數
- **raw**: 未被解析的原始表達式
- **name**: 不帶前綴的指令名

>這些屬性是隻讀的,不要修改它們。你也能夠給指令對象附加自定義的屬性,可是注意不要覆蓋已有的內部屬性。

使用指令對象屬性的示例:

`<div id="demo" v-demo="LightSlateGray : msg"></div>`

bind: function () {

this.el.style.color = '#fff'
this.el.style.backgroundColor = this.arg

},
update: function (value) {

this.el.innerHTML =
  'name - '       + this.name + '<br>' +
  'raw - '        + this.raw + '<br>' +
  'expression - ' + this.expression + '<br>' +
  'argument - '   + this.arg + '<br>' +
  'value - '      + value

}
})
var demo = new Vue({
el: '#demo',
data: {

msg: 'hello!'

}
})`

Result

  • name - demo
  • raw - LightSlateGray:msg
  • expression - msg
  • argument - LightSlateGray
  • value - hello!

多重從句

同一個特性內部,逗號分隔的多個從句將被綁定爲多個指令實例。在下面的例子中,指令會被建立和調用兩次:

<div v-demo="color: 'white', text: 'hello!'"></div>

若是想要用單個指令實例處理多個參數,能夠利用字面量對象做爲表達式:

<div v-demo="{color: 'white', text: 'hello!'}"></div>

console.log(value) // Object {color: 'white', text: 'hello!'}
})```

## 字面指令

若是在建立自定義指令的時候傳入 `isLiteral: true`,那麼特性值就會被當作直接字符串,並被賦值給該指令的 `expression`。字面指令不會試圖創建數據監視。

**Example**:

`<div v-literal-dir="foo"></div>`

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` 同樣接受內聯語句:

`<div v-my-directive="a++"></div>`

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

<div v-my-directive="obj"></div>

deep: true,
  update: function (obj) {
    // 當 obj 內部嵌套的屬性變化時也會調用此函數
  }
})```

## 指令優先級

你能夠選擇給指令提供一個優先級數(默認是 0)。同一個元素上優先級越高的指令會比其餘的指令處理得早一些。優先級同樣的指令會按照其在元素特性列表中出現的順序依次處理,可是不能保證這個順序在不一樣的瀏覽器中是一致的。

一般來講做爲用戶,你並不須要關心內置指令的優先級,若是你感興趣的話,能夠參閱源碼。邏輯控制指令 `v-repeat`, `v-if` 被視爲 「終結性指令」,它們在編譯過程當中始終擁有最高的優先級。

## 元素指令

有時候,咱們可能想要咱們的指令能夠以自定義元素的形式被使用,而不是做爲一個特性。這與 `Angular` 的 `E` 類指令的概念很是類似。元素指令能夠看作是一個輕量的自定義組件(後面會講到)。你能夠像下面這樣註冊一個自定義的元素指令:

// 和普通指令的 API 一致
bind: function () {

// 對 this.el 進行操做...

}
})

vuejs vs angularjs

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: '<div></div>',
    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
<input type="text" ng-model="myVar">
<p ng-bind="myVar"></p>
Vue
<input type="text" v-model="myVar">
<p v-model="myVar"></p>

Loops
Angular
<li ng-repeat="item in items" class="item-{{$index}}">
  {{item.myProperty}}
</li>
Vue
<li v-for="items" class="item-{{$index}}">
  {{myProperty}}
</li>

Conditionals
Angular
<div ng-if="myVar"></div>
<div ng-show="myVar"></div>
Vue
<div v-if="myVar"></div>
<div v-show="myVar"></div>

Conditional classes
Angular
<div ng-class="{‘active’: myVar}"></div>
Vue
<div v-class="active: myVar"></div>

Vue也能夠這樣寫v-repeat='item: items'

Event binding
Angular
<div ng-click="myMethod($event)"></div>
Vue
<div v-on="click: myMethod($event)"></div>

通用v-on指令使事件更加一致

髒值檢查

一個電話列表應用的例子,在其中咱們會將一個phones數組中的值(在JavaScript中定義)綁定到一個列表項目中以便於咱們的數據和UI保持同步:

<code><html ng-app>
  <head>
...
<script src="angular.js"></script>
<script src="controller.js"></script>
  </head>
  <body ng-controller="PhoneListCtrl">
<ul>
  <li ng-repeat="phone in phones">
{{phone.name}}
<p>{{phone.snippet}}</p>
  </li>
</ul>
  </body>
</html>
</code>

<code>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.'}
  ];
});  
</code>

任什麼時候候只要是底層的model數據發生了變化,咱們在DOM中的列表也會跟着更新。

髒值檢查的基本原理就是隻要任什麼時候候數據發生了變化,這個庫都會經過一個digest或者change cycle去檢查變化是否發生了。在Angular中,一個digest循環意味着全部全部被監視的表達式都會被循環一遍以便查看其中是否有變化發生。它智鬥一個模型以前的值所以當變化發生時,一個change事件將會被觸發。對於開發者來講,這帶來的一大好處就是你可使用原生的JavaScript對象數據,它易於使用及整合。下面的圖片展現的是一個很是糟糕的算法,它的開銷很是大。

這個操做的開銷和被監視的對象的數量是成正比的。我可能須要作不少的髒治檢查。同時當數據發生改變時,我也須要一種方式去觸發髒值檢查.

相比Angular的髒值檢查,Vue的setter/getter方案使數據和DOM更新的時間複雜度下降,數據的更新只發生在數據發生改變時,數據更新的時間複雜度只和數據的觀察者有關,"它們擁有一些存取器去獲取數據而且可以在你設置或者獲取對象時捕獲到這些行爲並在內部進行廣播".

vue的約束的模型系統

並且相比Object.observer()[在es7標準中],Vue的存取方式能夠作到比較好的兼容性.

Vue實現簡單的watcher

1.實現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成功

爲了幫助你們理清思路。。咱們就作最簡單的實現。。只考慮對象不考慮數組

實現obserer

將要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()
        }
      })
    }
實現一個Watcher
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

咱們要把以上代碼配合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的屬性。

相關文章
相關標籤/搜索