稍微學一下 Vue 的數(shù)據響應式(Vue2 及 Vue3)

什么是數(shù)據響應式

從一開始使用 Vue 時,對于之前的 jq 開發(fā)而言秧饮,一個很大的區(qū)別就是基本不用手動操作 dom,data 中聲明的數(shù)據狀態(tài)改變后會自動重新渲染相關的 dom。
換句話說就是 Vue 自己知道哪個數(shù)據狀態(tài)發(fā)生了變化及哪里有用到這個數(shù)據需要隨之修改。

因此實現(xiàn)數(shù)據響應式有兩個重點問題:

  1. 如何知道數(shù)據發(fā)生了變化赃阀?
  2. 如何知道數(shù)據變化后哪里需要修改?

對于第一個問題擎颖,如何知道數(shù)據發(fā)生了變化榛斯,Vue3 之前使用了 ES5 的一個 API Object.defineProperty Vue3 中使用了 ES6 的 Proxy,都是對需要偵測的數(shù)據進行 變化偵測 肠仪,添加 getter 和 setter 肖抱,這樣就可以知道數(shù)據何時被讀取和修改备典。

第二個問題异旧,如何知道數(shù)據變化后哪里需要修改,Vue 對于每個數(shù)據都收集了與之相關的 依賴 提佣,這里的依賴其實就是一個對象吮蛹,保存有該數(shù)據的舊值及數(shù)據變化后需要執(zhí)行的函數(shù)。每個響應式的數(shù)據變化時會遍歷通知其對應的每個依賴拌屏,依賴收到通知后會判斷一下新舊值有沒有發(fā)生變化潮针,如果變化則執(zhí)行回調函數(shù)響應數(shù)據變化(比如修改 dom)。

下面詳細分別介紹 Vue2 及 Vue3 的數(shù)據變化偵測及依賴收集倚喂。

Vue2

變化偵測

Object 的變化偵測

轉化響應式數(shù)據需要將 Vue 實例上 data 屬性中定義的數(shù)據通過遞歸將所有屬性都轉化為 getter/setter 的形式每篷,Vue 中定義了一個 Observer 類來做這個事情。

function def(obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

class Observer {
  constructor(value) {
    this.value = value;
    def(value, '__ob__', this);
    if (!Array.isArray(value)) {
      this.walk(value);
    }
  }
  walk(obj) {
    for (const [key, value] of Object.entries(obj)) {
      defineReactive(obj, key, value);
    }
  }
}

直接將一個對象傳入 new Observer() 后就對每項屬性都調用 defineReactive 函數(shù)添加變化偵測端圈,下面定義這個函數(shù):

function defineReactive(data, key, val) {
  let childOb = observe(val);
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function () {
      // 讀取 data[key] 時觸發(fā)
      console.log('getter', val);
      return val;
    },
    set: function (newVal) {
      // 修改 data[key] 時觸發(fā)
      console.log('setter', newVal);
      if (val === newVal) {
        return;
      }
      val = newVal;
    }
  })
}

function observe(value, asRootData) {
  if (typeof val !== 'object') {
    return;
  }
  let ob;
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else {
    ob = new Observer(val);
  }
  return ob;
}

函數(shù)中判斷如果是對象則遞歸調用 Observer 來實現(xiàn)所有屬性的變化偵測焦读,根據 __ob__ 屬性判斷是否已處理過,防止多次重復處理舱权,Observer 處理過后會給數(shù)據添加這個屬性矗晃,下面寫一個對象試一下:

const people = {
  name: 'c',
  age: 12,
  parents: {
    dad: 'a',
    mom: 'b'
  },
  mates: ['d', 'e']
};
new Observer(people);
people.name; // getter c
people.age++; // getter 12  setter 13
people.parents.dad; // getter {}  getter a

打印 people 可以看到所有屬性添加了 getter/setter 方法,讀取 name 屬性時打印了 people.age++ 修改 age 時打印了 getter 12 setter 13 說明 people 的屬性已經被全部成功代理監(jiān)聽宴倍。

Array 的變化偵測

可以看到前面 Observer 中僅對 Object 類型個數(shù)據做了處理张症,為每個屬性添加了 getter/setter,處理后如果屬性值中有數(shù)組鸵贬,通過 屬性名 + 索引 的方式(如:this.people.mates[0])獲取也是會觸發(fā) getter 的俗他。但是如果通過數(shù)組原型方法修改數(shù)組的值,如 this.people.mates.push('f')阔逼,這樣是無法通過 setter 偵測到的兆衅,因此,在 Observer 中需要對 Object 和 Array 分別進行單獨的處理。

為偵測到數(shù)組原型方法的操作涯保,Vue 中是通過創(chuàng)建一個攔截器 arrayMethods诉濒,并將攔截器重新掛載到數(shù)組的原型對象上。

下面是攔截器的定義:

const ArrayProto = Array.prototype;
const arrayMethods = Object.create(ArrayProto);
;[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
].forEach(method => {
  const original = ArrayProto[method];
  Object.defineProperty(arrayMethods, method, {
    value: function mutator(...args) {
      console.log('mutator:', this, args);
      return original.apply(this, args);
    },
    enumerable: false,
    writable: true,
    configurable: true
  })
})

這里 arrayMethods 繼承了 Array 的原型對象 Array.prototype夕春,并給它添加了 push pop shift unshift splice sort reverse 這些方法未荒,因為數(shù)組是可以通過這些方法進行修改的。添加的 push pop... 方法中重新調用 original(緩存的數(shù)組原型方法)及志,這樣就不會影響數(shù)組本身的操作片排。

最后給 Observer 中添加數(shù)組的修改:直接將攔截器掛載到數(shù)組原型對象上

class Observer {
  constructor(value) {
    this.value = value;
    def(value, '__ob__', this);
    if (Array.isArray(value)) {
      value.__proto__ = arrayMethods;
    } else {
      this.walk(value);
    }
  }
  walk(obj) {
    for (const [key, value] of Object.entries(obj)) {
      defineReactive(obj, key, value);
    }
  }
}

再來驗證一下:

const people = {
  name: 'c',
  age: 12,
  parents: {
    dad: 'a',
    mom: 'b'
  },
  mates: ['d', 'e']
};
new Observer(people);
people.mates[0]; // getter (2) ["d", "e"]
people.mates.push('f'); // mutator: (2) ["d", "e"] ["f"]

現(xiàn)在數(shù)組的修改也能被偵測到了。

依賴收集

目前已經可以對 ObjectArray 數(shù)據的變化進行截獲速侈,那么開始考慮一開始提到的 Vue 響應式數(shù)據的第二個問題:如何知道數(shù)據變化后哪里需要修改率寡?

最開始已經說過,Vue 中每個數(shù)據都需要收集與之相關的依賴倚搬,用來表示該數(shù)據變化時需要進行的操作行為冶共。

通過數(shù)據的變化偵測我們可以知道數(shù)據何時被讀取或修改,因此可以在數(shù)據讀取時收集依賴每界,修改時通知依賴更新捅僵,這樣就可以實現(xiàn)數(shù)據響應式了。

依賴收集在哪

為每個數(shù)據都創(chuàng)建一個收集依賴的對象 dep眨层,對外暴露 depend(收集依賴)庙楚、notify(通知依賴更新)的兩個方法,內部維護了一個數(shù)組用來保存該數(shù)據的每項依賴趴樱。

對于 Object馒闷,可以在 getter 中收集,setter 中通知更新叁征,對 defineReactive 函數(shù)修改如下:

function defineReactive(data, key, val) {
  let childOb = observe(val);
  // 處理每個響應式數(shù)據時都創(chuàng)建一個對象用來收集依賴
  let dep = new Dep();
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function () {
      // 收集依賴
      dep.depend();
      return val;
    },
    set: function (newVal) {
      if (val === newVal) {
        return;
      }
      val = newVal;
      // 通知依賴更新
      dep.notify();
    }
  })
}

上面代碼中依賴是收集在一個 Dep 實例對象上的纳账,下面看一下 Dep 這個類。

class Dep {
  constructor() {
    this.subs = [];
  }
  addSub(sub) {
    this.subs.push(sub);
  }
  removeSub(sub) {
    if (this.subs.length) {
      const index = this.subs.indexOf(sub);
      this.subs.splice(index, 1);
    }
  }
  depend() {
    if (window.target) {
      this.addSub(window.target);
    }
  }
  notify() {
    const subs = this.subs.slice();
    for (let i = 0; i < subs.length; i++) {
      subs[i].update();
    }
  }
}

Dep 的每個實例都有一個保存依賴的數(shù)組 subs航揉,收集依賴時是從全局的一個變量上獲取到并插入 subs塞祈,通知依賴時就遍歷所有 subs 成員并調用其 update 方法。

Object 的依賴收集和觸發(fā)都是在 defineProperty 中進行的帅涂,因此 Dep 實例定義在 defineReactive 函數(shù)中就可以讓 getter 和 setter 都拿到议薪。

而對于 Array 來說,依賴可以在 getter 中收集媳友,但觸發(fā)卻是在攔截器中斯议,為了保證 getter 和 攔截器中都能訪問到 Dep 實例,Vue 中給 Observer 實例上添加了 dep 屬性醇锚。

class Observer {
  constructor(value) {
    this.value = value;
    this.dep = new Dep();
    def(value, '__ob__', this);
    if (Array.isArray(value)) {
      value.__proto__ = arrayMethods;
    } else {
      this.walk(value);
    }
  }
  walk(obj) {
    for (const [key, value] of Object.entries(obj)) {
      defineReactive(obj, key, value);
    }
  }
}

Observer 在處理數(shù)據響應式時也將自身實例添加到了數(shù)據的 __ob__ 屬性上哼御,因此在 getter 和攔截器中都能通過響應式數(shù)據本身的 __ob__.dep 拿到其對應的依賴坯临。修改 defineReactive 和 攔截器如下:

function defineReactive(data, key, val) {
  let childOb = observe(val);
  let dep = new Dep();
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function () {
      dep.depend();
      // 給 Observer 實例上的 dep 屬性收集依賴
      if (childOb) {
        childOb.dep.depend();
      }
      return val;
    },
    ...
  })
}

;[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
].forEach(method => {
  const original = ArrayProto[method];
  def(arrayMethods, method, (...args) => {
    const result = original.apply(this, args);
    const ob = this.__ob__;
    ob.dep.notify();
    return result;
  })
})

依賴長什么樣

現(xiàn)在已經知道了依賴保存在每個響應式數(shù)據對應的 Dep 實例中的 subs 中,通過上面 Dep 的代碼可以知道恋昼,收集的依賴是一個全局對象看靠,且該對象對外暴露了一個 update 方法,記錄了數(shù)據變化時需要進行的更新操作(如修改 dom 或 Vue 的 Watch)液肌。

首先這個依賴對象的功能主要有兩點:

  1. 需要主動將自己收集到對應響應式數(shù)據的 Dep 實例中挟炬;
  2. 保存數(shù)據變化時要進行的操作并在 update 方法中調用;

其實就是一個中介角色嗦哆,Vue 中起名為 Watcher谤祖。

class Watcher {
  constructor(vm, expOrFn, cb) {
    this.vm = vm;
    // 保存通過表達式獲取數(shù)據的方法
    this.getter = parsePath(expOrFn);
    this.cb = cb;
    this.value = this.get();
  }
  get() {
    // 將自身 Watcher 實例掛到全局對象上
    window.target = this;
    // 獲取表達式對應的數(shù)據
    // 會自動觸發(fā)該數(shù)據的 getter
    // getter 中收集依賴時從全局對象上拿到這個 Watcher 實例
    let value = this.getter.call(this.vm, this.vm);
    window.target = undefined;
    return value;
  }
  update() {
    const oldValue = this.value;
    this.value = this.get();
    // 將舊值與新值傳遞給回調函數(shù)
    this.cb.call(this.vm, this.value, oldValue);
  }
}

對于第一點,主動將自己收集到 Dep 實例中老速,Watcher 中設計的非常巧妙粥喜,在 get 中將自身 Watcher 實例掛到全局對象上,然后通過獲取數(shù)據觸發(fā) getter 來實現(xiàn)依賴收集橘券。

第二點實現(xiàn)很簡單额湘,只需要將構造函數(shù)參數(shù)中的回調函數(shù)保存并在 update 方法中調用即可。

構造函數(shù)中的 parsePath 方法就是從 Vue 實例的 data 上通過表達式獲取數(shù)據约郁,比如表達式為 "user.name" 則需要解析該字符串然后獲取 data.user.name 數(shù)據缩挑。

總結

  • 數(shù)據先通過調用 new Observer() 為每項屬性添加變化偵測,并創(chuàng)建一個 Dep 實例用來保存相關依賴鬓梅。在讀取屬性值時保存依賴,修改屬性值時通知依賴谨湘;
  • Dep 實例的 subs 屬性為一個數(shù)組绽快,保存依賴是向數(shù)組中添加,通知依賴時遍歷數(shù)組一次調用依賴的 update 方法紧阔;
  • 依賴是一個 Watcher 實例坊罢,保存了數(shù)據變化時需要進行的操作,并將實例自身放到全局的一個位置擅耽,然后讀取數(shù)據觸發(fā)數(shù)據的 getter活孩,getter 中從全局指定的位置獲取到該 Watcher 實例并收集在 Dep 實例中。

以上就是 Vue2 中的響應式原理乖仇,在 Observer 處理完后憾儒,外界只需要通過創(chuàng)建 Watcher 傳入需要監(jiān)聽的數(shù)據及數(shù)據變化時的響應回調函數(shù)即可。

Vue3

Vue3 中每個功能單獨為一個模塊乃沙,并可以單獨打包使用起趾,本文僅簡單討論 Vue3 中與數(shù)據響應式相關的 Reactive 模塊,了解其內部原理警儒,與 Vue2 相比又有何不同训裆。

因為該模塊可以單獨使用,先來看一下這個模塊的用法示例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>vue3 demo</title>
</head>
<body>
  <div id="app">
    <div id="count"></div>
    <button id="btn">+1</button>
  </div>
  <script src="./vue3.js"></script>
  <script>
    const countEl = document.querySelector('#count')
    const btnEl = document.querySelector('#btn')
    // 定義響應式數(shù)據
    const state = reactive({
      count: 0,
      man: {
        name: 'pan'
      }
    })
    // 定義計算屬性
    let double = computed(() => {
      return state.count * 2
    })
    // 回調函數(shù)立即執(zhí)行一次,內部使用到的數(shù)據更新時會重新執(zhí)行回調函數(shù)
    effect(() => {
      countEl.innerHTML = `count is ${state.count}, double is ${double.value}, man's name is ${state.man.name}`
    })
    // 修改響應式數(shù)據觸發(fā)更新
    btnEl.addEventListener('click', () => {
      state.count++
    }, false)
  </script>
</body>
</html>

通過示例可以看到實現(xiàn) Vue3 這個數(shù)據響應式需要有 reactive边琉、computed属百、effect 這幾個函數(shù),下面仍然通過從變化偵測及依賴收集兩個方面介紹变姨,簡單實現(xiàn)這幾個函數(shù)诸老。

變化偵測

示例中的 reactive 函數(shù)是對數(shù)據進行響應式化的,因此該函數(shù)的功能就類似于 Vue2 中的 defineReactive 函數(shù)的 getter/setter 處理钳恕,處理后能夠對數(shù)據的獲取及修改操作進行捕獲别伏。

const toProxy = new WeakMap()
const toRaw = new WeakMap()

const baseHandler = {
  get(target, key) {
    console.log('Get', target, key)
    const res = Reflect.get(target, key)
    // 遞歸尋找
    return typeof res == 'object' ? reactive(res) : res
  },
  set(target, key, val) {
    console.log('Set', target, key, val)
    const res = Reflect.set(target, key, val)
    return res
  }
}
function reactive(target) {
  console.log('reactive', target)
  // 查詢緩存
  let observed = toProxy.get(target)
  if (observed) {
    return observed
  }
  if (toRaw.get(target)) {
    return target
  }
  observed = new Proxy(target, baseHandler)
  // 設置緩存
  toProxy.set(target, observed)
  toRaw.set(observed, target)
  return observed
}

reactive 中使用 Proxy 對目標進行代理,代理的行為是 baseHander 忧额,然后對目標對象及代理后的對象進行緩存厘肮,防止多次代理。

baseHandler 中就是對數(shù)據的獲取及修改進行攔截睦番,并通過 Reflect 執(zhí)行 get/set 的原本操作类茂,并在獲取值為 Object 時遞歸進行響應式處理。很簡單地就完成了數(shù)據的響應式處理托嚣。

依賴收集

依賴收集與 Vue2 類似巩检,在 getter 中收集依賴,setter 中觸發(fā)依賴示启,修改 baseHandler 如下:

const baseHandler = {
  get(target, key) {
    const res = Reflect.get(target, key)
    // 收集依賴
    track(target, key)
    return typeof res == 'object' ? reactive(res) : res
  },
  set(target, key, val) {
    const info = {
      oldValue: target[key],
      newValue: val
    }
    const res = Reflect.set(target, key, val)
    // 觸發(fā)更新
    trigger(target, key, info)
    return res
  }
}

track 函數(shù)收集依賴兢哭,trigger 函數(shù)觸發(fā)依賴更新。

首先需要兩個全局變量夫嗓,用于保存當前待收集的依賴對象的 effectStack 及一個記錄所有數(shù)據及其對應依賴的表 targetMap 迟螺。

const effectStack = []
const targetMap = new WeakMap()

接下來定義這收集依賴及觸發(fā)依賴更新這兩個函數(shù):

function track(target, key) {
  // 從棧中拿到待收集的依賴對象
  let effect = effectStack[effectStack.length - 1]
  if (effect) {
    // 通過 target 及 key 從依賴映射表中拿到對應的依賴列表(Set類型)
    // 首次需要對依賴映射表初始化
    let depsMap = targetMap.get(target)
    if (depsMap === undefined) {
      depsMap = new Map()
      targetMap.set(target, depsMap)
    }
    let dep = depsMap.get(key)
    if (dep === undefined) {
      dep = new Set()
      depsMap.set(key, dep)
    }
    // 若 target.key 對應的依賴列表中不存在該依賴則收集
    if (!dep.has(effect)) {
      dep.add(effect)
    }
  }
}
function trigger(target, key, info) {
  // 依賴映射表中取出 target 相關數(shù)據
  const depsMap = targetMap.get(target)
  if (depsMap === undefined) {
    return
  }
  // 普通依賴對象的列表
  const effects = new Set()
  // 計算屬性依賴對象的列表
  const computedRunners = new Set()
  if (key) {
    // 取出 key 相關的依賴列表遍歷分類存入 effects 及 computedRunners
    let deps = depsMap.get(key)
    deps.forEach(effect => {
      if (effect.computed) {
        computedRunners.add(effect)
      } else {
        effects.add(effect)
      }
    })
  }
  // 遍歷執(zhí)行所有依賴對象
  const run = effect=> effect()
  effects.forEach(run)
  computedRunners.forEach(run)
}

track 及 trigger 的大致代碼也很簡單,track 是拿到待收集的依賴對象 effect 后收集到 effectStack舍咖,trigger 是從 effectStack 拿到對應的依賴列表遍歷執(zhí)行矩父。

到現(xiàn)在就差這個依賴對象了,根據上面 trigger 函數(shù)可以知道排霉,這個依賴 effect 首先是個函數(shù)可以執(zhí)行窍株,并且還有自身屬性,如 computed 表示其為一個計算屬性的依賴攻柠,有時會根據該標識進行寫特殊處理球订。

下面開始介紹這個依賴對象是如何產生的:

// 創(chuàng)建依賴對象
function createReactiveEffect(fn, options) {
  const effect = function effect(...args) {
    return run(effect, fn, args)
  }
  effect.computed = options.computed
  effect.lazy = options.lazy
  return effect
}

function run(effect, fn, args) {
  if (!effectStack.includes(effect)) {
    try {
      effectStack.push(effect)
      return fn(...args)
    } finally {
      effectStack.pop()
    }
  }
}

createReactiveEffect 是一個高階函數(shù),內部創(chuàng)建了一個名為 effect 的函數(shù)辙诞,函數(shù)內部返回的是一個 run 函數(shù)辙售,run 函數(shù)中將依賴 effect 對象存入全局的待收集依賴棧 effectStack 中,并執(zhí)行傳入的回調函數(shù)飞涂,該回調函數(shù)其實就是一開始示例中 effect 函數(shù)傳入的修改 Dom 的函數(shù)旦部。也就是說依賴對象作為函數(shù)直接執(zhí)行就會添加依賴到全局棧并執(zhí)行回調函數(shù)祈搜。

回調函數(shù)中如果有讀取了響應式數(shù)據的話則會觸發(fā) proxy 的 get 收集依賴,這時就能從 effectStack 上拿到該依賴對象了士八。

然后給 effect 增加了 computed lazy 屬性后返回容燕。

最后就是對外暴露的 effect 及 computed 函數(shù)了:

// 創(chuàng)建依賴對象并判斷非計算屬性則立即執(zhí)行
function effect(fn, options = {}) {
  let e = createReactiveEffect(fn, options)
  if (!options.lazy) {
    e()
  }
  return e
}

// computed 內部調用 effect 并添加計算屬性相關的 options
function computed(fn) {
  const runner = effect(fn, {
    computed: true,
    lazy: true
  })
  return {
    effect: runner,
    get value() {
      return runner()
    }
  }
}

computed 就不多說了,effect 就是將傳入的回調函數(shù)傳給 createReactiveEffect 創(chuàng)建依賴對象婚度,然后執(zhí)行依賴對象就會執(zhí)行回調函數(shù)并收集該依賴對象蘸秘。

總結

  • reactive 將傳入的數(shù)據對象使用 proxy 包裝,通過 proxy 的 get set 攔截數(shù)據的獲取及修改蝗茁,與 Vue2 的 defineProperty 一樣醋虏,在 get 中收集依賴,在 set 中觸發(fā)依賴哮翘;
  • effect 函數(shù)接受一個回調函數(shù)作為參數(shù)颈嚼,將回調函數(shù)包裝一下作為依賴對象后執(zhí)行回調函數(shù),回調函數(shù)執(zhí)行時觸發(fā)相關數(shù)據的 get 后進行依賴收集饭寺;

到此 Vue2 及 Vue3 中的數(shù)據響應式原理都分析完了阻课。

Vue2 及 Vue3 數(shù)據響應式的對比

本次 Vue 對于數(shù)據響應式的升級主要在變化偵測部分。

Vue2 中的變化偵測實現(xiàn)對 Object 及 Array 分別進行了不同的處理艰匙,Objcet 使用了
Object.defineProperty API 限煞,Array 使用了攔截器對 Array 原型上的能夠改變數(shù)據的方法進行攔截。雖然也實現(xiàn)了數(shù)據的變化偵測员凝,但存在很多局限 署驻,比如對象新增屬性無法被偵測,以及通過數(shù)組下邊修改數(shù)組內容绊序,也因此在 Vue2 中經常會使用到 $set 這個方法對數(shù)據修改硕舆,以保證依賴更新。

Vue3 中使用了 es6 的 Proxy API 對數(shù)據代理骤公,沒有像 Vue2 中對原數(shù)據進行修改,只是加了代理包裝扬跋,因此首先性能上會有所改善阶捆。其次解決了 Vue2 中變化偵測的局限性,可以不使用 $set 新增的對象屬性及通過下標修改數(shù)組都能被偵測到钦听。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末洒试,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子朴上,更是在濱河造成了極大的恐慌垒棋,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痪宰,死亡現(xiàn)場離奇詭異叼架,居然都是意外死亡畔裕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門乖订,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扮饶,“玉大人,你說我怎么就攤上這事乍构√鹞蓿” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵哥遮,是天一觀的道長岂丘。 經常有香客問我,道長眠饮,這世上最難降的妖魔是什么奥帘? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮君仆,結果婚禮上翩概,老公的妹妹穿的比我還像新娘。我一直安慰自己返咱,他們只是感情好钥庇,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咖摹,像睡著了一般评姨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上萤晴,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天吐句,我揣著相機與錄音,去河邊找鬼店读。 笑死嗦枢,一個胖子當著我的面吹牛,可吹牛的內容都是我干的屯断。 我是一名探鬼主播文虏,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼殖演!你這毒婦竟也來了氧秘?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤趴久,失蹤者是張志新(化名)和其女友劉穎丸相,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彼棍,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡灭忠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年膳算,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片更舞。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡畦幢,死狀恐怖,靈堂內的尸體忽然破棺而出缆蝉,到底是詐尸還是另有隱情宇葱,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布刊头,位于F島的核電站黍瞧,受9級特大地震影響,放射性物質發(fā)生泄漏原杂。R本人自食惡果不足惜印颤,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望穿肄。 院中可真熱鬧年局,春花似錦、人聲如沸咸产。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脑溢。三九已至僵朗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屑彻,已是汗流浹背验庙。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留社牲,地道東北人粪薛。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像搏恤,于是被迫代替她去往敵國和親汗菜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容

  • 前言 Vue 最獨特的特性之一挑社,是其非侵入性的響應式系統(tǒng)。數(shù)據模型僅僅是普通的 JavaScript 對象巡揍。而當你...
    浪里行舟閱讀 1,959評論 0 16
  • 這方面的文章很多痛阻,但是我感覺很多寫的比較抽象,本文會通過舉例更詳細的解釋腮敌。(此文面向的Vue新手們阱当,如果你是個大牛...
    Ivy_2016閱讀 15,372評論 8 64
  • 前言 Vue.js 的核心包括一套“響應式系統(tǒng)”弊添。 “響應式”录淡,是指當數(shù)據改變后,Vue 會通知到使用該數(shù)據的代碼...
    world_7735閱讀 950評論 0 2
  • 前言 Vue.js 的核心包括一套“響應式系統(tǒng)”油坝。 “響應式”嫉戚,是指當數(shù)據改變后,Vue 會通知到使用該數(shù)據的代碼...
    NARUTO_86閱讀 37,393評論 8 86
  • 摘要: 搞懂Vue響應式原理澈圈! 作者:浪里行舟 原文:深入淺出Vue響應式原理 Fundebug經授權轉載彬檀,版權歸...
    Fundebug閱讀 5,486評論 0 9