Skip to content

0x036-Vue源码解读-defineReactive

前置知识

对象属性的特性

JavaScript权威指南(原书第6版)

对象除了名字和之外,每个属性还有一些与之相关的值,称为“属性特性”

  • 可写(writable attribute),表明是否可以设置该属性的值。
  • 枚举(enumerable attribute),表明是否可以通过for/in循环返回该属性。
  • 可配置(configurable attribute),表明是否可以删除或修改该属性。

在ECMAScript 5之前,通过代码给对象创建的所有属性都是可写的、可枚举的和可配置的。在ECMAScript 5中则可以对这些特性加以配置。6.7节讲述如何操作。

数据属性:

  • value 值
  • writable 可写入
  • enumerable 可枚举
  • configurable 可配置

对象特性

除了包含属性之外,每个对象还拥有三个相关的对象特性(object attribute)

  • 对象的原型(prototype)指向另外一个对象,本对象的属性继承自它的原型对象。
    • 每个JavaScript对象都有一个原型属性,指向另一个对象。这个原型对象可以包含属性和方法。
    • 当你访问一个对象的属性时,如果该对象本身没有这个属性,JavaScript会查找它的原型对象,直到找到该属性或到达原型链的顶端(通常是 Object.prototype)。
    • 通过这种机制,对象可以继承其他对象的属性和方法,实现代码重用。
  • 对象的类(class)是一个标识对象类型的字符串
  • 对象的扩展标记(extensible flag)指明了(在ECMAScript 5中)是否可以向该对象添加新属性
    • 这个标记指示一个对象是否可以添加新属性。在ECMAScript 5中,可以通过 Object.isExtensible(obj) 方法来检查一个对象是否可扩展。
    • 默认情况下,所有对象都是可扩展的,但可以通过 Object.preventExtensions(obj) 方法将对象标记为不可扩展,一旦对象被标记为不可扩展,就不能再向其添加新属性。
js
Object.isExtensible({}) // true
Object.isExtensible(Object.seal({})) // false
Object.isExtensible(Object.freeze({})) // false
Object.isExtensible({}) // true
Object.isExtensible(Object.seal({})) // false
Object.isExtensible(Object.freeze({})) // false

getter / settter

Note

我们知道,对象属性是由名字、值和一组特性(attribute)构成的。在ECMAScript 5中,属性值可以用一个或两个方法替代,这两个方法就是getter和setter。由getter和setter定义的属性称做“存取器属性”(accessor property),它不同于“数据属性”(data property),数据属性只有一个简单的值。

  • 当程序查询存取器属性的值时,JavaScript调用getter方法(无参数)​
  • 设置一个存取器属性的值时,JavaScript调用setter方法,将赋值表达式右侧的值当做参数传入setter
js
let obj = {  
    _value: 0,  
    get() {  
        return this._value  
    },  
    set(val) {  
        if (typeof val !== 'number') {  
            throw new Error('value must be a number')  
        }  
        return this._value = val;  
    }  
}    
console.log(obj.get())  
obj.set(1)  
console.log(obj.get())  
obj.set("1") // Uncaught Error: value must be a number
let obj = {  
    _value: 0,  
    get() {  
        return this._value  
    },  
    set(val) {  
        if (typeof val !== 'number') {  
            throw new Error('value must be a number')  
        }  
        return this._value = val;  
    }  
}    
console.log(obj.get())  
obj.set(1)  
console.log(obj.get())  
obj.set("1") // Uncaught Error: value must be a number

Object.defineProperty

Object.definePeoperty() 用于设置属性的特性、或者让新建属性具有某种特性。

入参:

  • 目标对象
  • 创建或修改的属性名称
  • 属性描述符对象
js
/*
// 对于新创建的属性来说,默认的特性值是false或undefined
interface PropertyDescriptor {
    configurable?: boolean;
    enumerable?: boolean;
    value?: any;
    writable?: boolean;
    get?(): any;
    set?(v: any): void;
}*/
let obj = {};
Object.defineProperty(obj, 'name', {
    value: 'linghuchong', 
    configurable: true, 
    enumerable: false, // 影响 JSON.stringify 。 false的找不到
    writable: false
})
console.log(Object.keys(obj)) // 因为设置了enumerable=false,这里是 []
console.log(JSON.stringify(obj)) // 因为设置了enumerable=false,这里是 {}
console.log(obj.name) // linghuchong
obj.name = 'b' // typeError: Cannot assign to read only property 'name' of object
/*
// 对于新创建的属性来说,默认的特性值是false或undefined
interface PropertyDescriptor {
    configurable?: boolean;
    enumerable?: boolean;
    value?: any;
    writable?: boolean;
    get?(): any;
    set?(v: any): void;
}*/
let obj = {};
Object.defineProperty(obj, 'name', {
    value: 'linghuchong', 
    configurable: true, 
    enumerable: false, // 影响 JSON.stringify 。 false的找不到
    writable: false
})
console.log(Object.keys(obj)) // 因为设置了enumerable=false,这里是 []
console.log(JSON.stringify(obj)) // 因为设置了enumerable=false,这里是 {}
console.log(obj.name) // linghuchong
obj.name = 'b' // typeError: Cannot assign to read only property 'name' of object

需要注意:get / set 和 value / writable 是相互独立的属性,不能同时出现在同一个属性定义中。

Object.getOwnPropertyDescriptor

Object.getOwnPropertyDescriptor() 是 JavaScript 中的一个内置方法,用于获取对象自身某个属性的描述符。这个方法可以帮助我们了解一个属性的详细信息,比如它是否可枚举、是否可写、是否可配置,以及它的值等。

语法

Object.getOwnPropertyDescriptor(obj, prop)

obj: 目标对象,即我们想要查询属性的对象。
prop: 字符串,表示我们想要获取描述符的属性名。

返回值

该方法返回一个对象,包含以下属性:

  • value: 属性的值。
  • writable: 一个布尔值,表示属性是否可以被赋值(即是否可写)。
  • enumerable: 一个布尔值,表示属性是否可以在 for...in 循环中被枚举。
  • configurable: 一个布尔值,表示属性是否可以被删除,或其特性是否可以被修改。

如果指定的属性不存在,返回 undefined。

示例

js
const obj = {
    name: 'Alice',
    age: 25
};

// 获取 'name' 属性的描述符
const descriptor = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(descriptor);
/*
输出:
{
  value: 'Alice',
  writable: true,
  enumerable: true,
  configurable: true
}
*/
const obj = {
    name: 'Alice',
    age: 25
};

// 获取 'name' 属性的描述符
const descriptor = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(descriptor);
/*
输出:
{
  value: 'Alice',
  writable: true,
  enumerable: true,
  configurable: true
}
*/

Object.seal

Object.seal()方法封闭一个对象,具有以下效果:

• 阻止添加新属性
• 将现有属性标记为不可配置(configurable:false) 不可删除属性
但允许修改现有属性的值

代码示例:

js
let obj = { a: 1, b: 2 };
Object.seal(obj);
obj.a = 3; // 可修改现有属性的值
delete obj.b; // 无法删除属性
obj.c = 4; // 无法新增属性
let obj = { a: 1, b: 2 };
Object.seal(obj);
obj.a = 3; // 可修改现有属性的值
delete obj.b; // 无法删除属性
obj.c = 4; // 无法新增属性

Object.preventExtensions

防止对象被扩展,也就是说,不能向该对象添加新的属性,但是支持修改或删除

js
const obj = { name: "Alice" };
Object.preventExtensions(obj);
obj.age = 25; // 这个操作将会失败,age 属性不会被添加
console.log(obj); // 输出: { name: "Alice" }
obj.name = "Bob"; // 这个操作是允许的,修改现有属性
console.log(obj); // 输出: { name: "Bob" }
delete obj.name; // 这个操作也是允许的,可以删除现有属性
console.log(obj); // 输出: {}
console.log(Object.isExtensible(obj)); // 输出: false
const obj = { name: "Alice" };
Object.preventExtensions(obj);
obj.age = 25; // 这个操作将会失败,age 属性不会被添加
console.log(obj); // 输出: { name: "Alice" }
obj.name = "Bob"; // 这个操作是允许的,修改现有属性
console.log(obj); // 输出: { name: "Bob" }
delete obj.name; // 这个操作也是允许的,可以删除现有属性
console.log(obj); // 输出: {}
console.log(Object.isExtensible(obj)); // 输出: false

Object.propertyIsEnumerable

• Object.propertyIsEnumerable() 方法可以用来判断一个对象的指定属性是否可枚举
• 对于字符串类型的属性, Object.propertyIsEnumerable() 会返回 true, 表示该字符串属性是可枚举的。
• 可枚举的属性可以通过 for...in 循环遍历到, 但不包括原型链上的属性和 Symbol 命名的属性。 ??

js
// 字符串属性
const obj = {
  name: 'Alice',
  age: 25
};

console.log(obj.propertyIsEnumerable('name')); // true
console.log(obj.propertyIsEnumerable('age')); // true

// 原型链上的属性
Object.prototype.hobby = 'reading';
console.log(obj.propertyIsEnumerable('hobby')); // false

// Symbol 命名的属性
const sym = Symbol('id');
obj[sym] = 1234;
console.log(obj.propertyIsEnumerable(sym)); // true
// 字符串属性
const obj = {
  name: 'Alice',
  age: 25
};

console.log(obj.propertyIsEnumerable('name')); // true
console.log(obj.propertyIsEnumerable('age')); // true

// 原型链上的属性
Object.prototype.hobby = 'reading';
console.log(obj.propertyIsEnumerable('hobby')); // false

// Symbol 命名的属性
const sym = Symbol('id');
obj[sym] = 1234;
console.log(obj.propertyIsEnumerable(sym)); // true

Object.hasOwnProperty

对象的hasOwnProperty()方法用来检测给定的名字是否是对象的自有属性。对于继承属性它将返回false:

js
const obj = {
  name: 'Alice',
  age: 25
};
console.log(obj.hasOwnProperty('name')); // 输出: true
console.log(obj.hasOwnProperty('age'));  // 输出: true
console.log(obj.hasOwnProperty('gender')); // 输出: false
const obj = {
  name: 'Alice',
  age: 25
};
console.log(obj.hasOwnProperty('name')); // 输出: true
console.log(obj.hasOwnProperty('age'));  // 输出: true
console.log(obj.hasOwnProperty('gender')); // 输出: false

vue3为什么改用了Proxy

[参考](https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/90)

  1. Object.defineProperty无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应;
  2. Object.defineProperty只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。Proxy可以劫持整个对象,并返回一个新的对象。
  3. Proxy不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。

Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue 2.x里,是通过 递归 + 遍历 data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象是才是更好的选择。

使用Object.defineProperty监听

赋值时打印log

js
function defineReactive(obj, key) {
    let val = obj[key];
    Object.defineProperty(obj, key, {
        enumerable: true, configurable: true, get: function () {
            return val
        }, set: function (newVal) {
            if (val === newVal) {
                return
            }
            console.log(`${val} updated to ${newVal}`)
            val = newVal
        }
    })
}
function observerObject(obj) {
    const keys = Object.keys(obj);
    for (const key of keys) {
        defineReactive(obj, key)
    }
}

let obj = {
    a: 20, b: 2, c: 3,
}
observerObject(obj)

obj.a = 21  // 20 updated to 21
obj.b = 24  // 2 updated to 24
obj.f = 'panda' // 无log
function defineReactive(obj, key) {
    let val = obj[key];
    Object.defineProperty(obj, key, {
        enumerable: true, configurable: true, get: function () {
            return val
        }, set: function (newVal) {
            if (val === newVal) {
                return
            }
            console.log(`${val} updated to ${newVal}`)
            val = newVal
        }
    })
}
function observerObject(obj) {
    const keys = Object.keys(obj);
    for (const key of keys) {
        defineReactive(obj, key)
    }
}

let obj = {
    a: 20, b: 2, c: 3,
}
observerObject(obj)

obj.a = 21  // 20 updated to 21
obj.b = 24  // 2 updated to 24
obj.f = 'panda' // 无log

新增属性时无log,因为没有对新增加的属性添加监听,下面增加一个$set方法,监听新增的属性:

js
obj.$set = function (key, value) {  
    this[key] = value 
    defineReactive(this, key) 
} 
obj.$set('f', 'cat')
obj.f = 'dog' // cat updated to dog
obj.$set = function (key, value) {  
    this[key] = value 
    defineReactive(this, key) 
} 
obj.$set('f', 'cat')
obj.f = 'dog' // cat updated to dog

动态监听属性

js
function defineReactive(obj, key) {  
    let val = obj[key];  
    let dep = new Dep()  
    Object.defineProperty(obj, key, {  
        enumerable: true, configurable: true, get: function () {  
            dep.depend()  
            return val  
        }, set: function (newVal) {  
            if (val === newVal) {  
                return  
            }  
            // console.log(`${val} updated to ${newVal}`)  
            val = newVal  
            dep.notify(newVal, val)  
        }  
    })  
}  
  
class Dep {  
    constructor() {  
        // 需要支持多个watcher  
        this.subs = []  
    }  
  
    depend() {  
        Dep.target && this.subs.push(Dep.target /*watcher*/)  
    }  
  
    notify(newVal, oldVal) {  
        for (let i = 0; i < this.subs.length; i++) {  
            const watcher = this.subs[i]  
            watcher.fn.call(watcher.vm, newVal, oldVal)  
        }  
    }  
}  
  
function observerObject(obj) {  
    const keys = Object.keys(obj);  
    for (const key of keys) {  
        defineReactive(obj, key)  
    }  
}  
  
Dep.target = null  
  
class Watch {  
    constructor(data, prop, fn) {  
        this.vm = data  
        this.prop = prop  
        this.fn = fn  
        Dep.target = this  
        // 用于触发getter,将watcher添加到Dep中  
        data[prop]  
        Dep.target = null  
    }  
}  
  
let obj = {  
    a: 20, b: 2, c: 3,  
}  
observerObject(obj)  
  
// 注意这里的func不要使用箭头函数,否则获取不到this  
new Watch(obj, 'a', function (val, oldVal) {  
    console.log('update a changed1:', val, oldVal, this)  
})  
new Watch(obj, 'a', function (val, oldVal) {  
    console.log('update a changed2:', val, oldVal, this)  
})  
  
new Watch(obj, 'b', function (val, oldVal) {  
    console.log('update b changed:', val, oldVal, this)  
})
function defineReactive(obj, key) {  
    let val = obj[key];  
    let dep = new Dep()  
    Object.defineProperty(obj, key, {  
        enumerable: true, configurable: true, get: function () {  
            dep.depend()  
            return val  
        }, set: function (newVal) {  
            if (val === newVal) {  
                return  
            }  
            // console.log(`${val} updated to ${newVal}`)  
            val = newVal  
            dep.notify(newVal, val)  
        }  
    })  
}  
  
class Dep {  
    constructor() {  
        // 需要支持多个watcher  
        this.subs = []  
    }  
  
    depend() {  
        Dep.target && this.subs.push(Dep.target /*watcher*/)  
    }  
  
    notify(newVal, oldVal) {  
        for (let i = 0; i < this.subs.length; i++) {  
            const watcher = this.subs[i]  
            watcher.fn.call(watcher.vm, newVal, oldVal)  
        }  
    }  
}  
  
function observerObject(obj) {  
    const keys = Object.keys(obj);  
    for (const key of keys) {  
        defineReactive(obj, key)  
    }  
}  
  
Dep.target = null  
  
class Watch {  
    constructor(data, prop, fn) {  
        this.vm = data  
        this.prop = prop  
        this.fn = fn  
        Dep.target = this  
        // 用于触发getter,将watcher添加到Dep中  
        data[prop]  
        Dep.target = null  
    }  
}  
  
let obj = {  
    a: 20, b: 2, c: 3,  
}  
observerObject(obj)  
  
// 注意这里的func不要使用箭头函数,否则获取不到this  
new Watch(obj, 'a', function (val, oldVal) {  
    console.log('update a changed1:', val, oldVal, this)  
})  
new Watch(obj, 'a', function (val, oldVal) {  
    console.log('update a changed2:', val, oldVal, this)  
})  
  
new Watch(obj, 'b', function (val, oldVal) {  
    console.log('update b changed:', val, oldVal, this)  
})
  • 需要遍历object所有自有属性
  • 全局定义了一个Dep.target,用于getter/setter中获取Dep.target
  • Dep中的subs是一个数组,因为可能有n个Watch监听object的某个字段
  • 如果是object嵌套,需要再循环调用一下,代码略

如何监听数组属性

Vue2中的做法是对Array进行了扩展:

|430

defineReactive方法

defineReactive

js
export function defineReactive (  
  obj: Object,  
  key: string,  
  val: any,  
  customSetter?: ?Function,  
  shallow?: boolean  
) {  
  const dep = new Dep()  
  // 获取属性描述符,只操作可配置属性
  const property = Object.getOwnPropertyDescriptor(obj, key)  
  if (property && property.configurable === false) {  
    return  
  }  
  
  // 获取getter / setter 
  const getter = property && property.get  
  const setter = property && property.set  
  if ((!getter || setter) && arguments.length === 2) {  
    val = obj[key]  
  }  
  
  let childOb = !shallow && observe(val)  
  Object.defineProperty(obj, key, {  
    enumerable: true,  
    configurable: true,  
    get: function reactiveGetter () {  
      // 如果原本有getter,调用getter方法。否则取val
      const value = getter ? getter.call(obj) : val  
      // 使对象后续有可被监听的能力
      if (Dep.target) {  
        dep.depend()  
        if (childOb) {  
          childOb.dep.depend()  
          if (Array.isArray(value)) {  
            dependArray(value)  
          }  
        }  
      }  
      return value  
    },  
    set: function reactiveSetter (newVal) {  
      // 如果原本有getter,调用getter方法。否则取val
      const value = getter ? getter.call(obj) : val  
	  /**
	   *  newVal !== newVal && value !== value 这里看着很奇怪,但是想到 NaN !== NaN 就恍然了
	   */
      if (newVal === value || (newVal !== newVal && value !== value)) {  
        return  
      }  
      /* eslint-enable no-self-compare */  
      if (process.env.NODE_ENV !== 'production' && customSetter) {  
        customSetter()  
      }  
      // #7981: for accessor properties without setter  
      // 因为getter/setter和value不是同时存在的。只有getter也没必要执行后面的操作了
      if (getter && !setter) return  
      if (setter) {  
        setter.call(obj, newVal)
      } else {  
        val = newVal  
      }  
      childOb = !shallow && observe(newVal)  
      dep.notify()  
    }  
  })  
}
export function defineReactive (  
  obj: Object,  
  key: string,  
  val: any,  
  customSetter?: ?Function,  
  shallow?: boolean  
) {  
  const dep = new Dep()  
  // 获取属性描述符,只操作可配置属性
  const property = Object.getOwnPropertyDescriptor(obj, key)  
  if (property && property.configurable === false) {  
    return  
  }  
  
  // 获取getter / setter 
  const getter = property && property.get  
  const setter = property && property.set  
  if ((!getter || setter) && arguments.length === 2) {  
    val = obj[key]  
  }  
  
  let childOb = !shallow && observe(val)  
  Object.defineProperty(obj, key, {  
    enumerable: true,  
    configurable: true,  
    get: function reactiveGetter () {  
      // 如果原本有getter,调用getter方法。否则取val
      const value = getter ? getter.call(obj) : val  
      // 使对象后续有可被监听的能力
      if (Dep.target) {  
        dep.depend()  
        if (childOb) {  
          childOb.dep.depend()  
          if (Array.isArray(value)) {  
            dependArray(value)  
          }  
        }  
      }  
      return value  
    },  
    set: function reactiveSetter (newVal) {  
      // 如果原本有getter,调用getter方法。否则取val
      const value = getter ? getter.call(obj) : val  
	  /**
	   *  newVal !== newVal && value !== value 这里看着很奇怪,但是想到 NaN !== NaN 就恍然了
	   */
      if (newVal === value || (newVal !== newVal && value !== value)) {  
        return  
      }  
      /* eslint-enable no-self-compare */  
      if (process.env.NODE_ENV !== 'production' && customSetter) {  
        customSetter()  
      }  
      // #7981: for accessor properties without setter  
      // 因为getter/setter和value不是同时存在的。只有getter也没必要执行后面的操作了
      if (getter && !setter) return  
      if (setter) {  
        setter.call(obj, newVal)
      } else {  
        val = newVal  
      }  
      childOb = !shallow && observe(newVal)  
      dep.notify()  
    }  
  })  
}

接下来看Dep

Dep

depend

dep.depend: 将Dep.target/Watcher添加到subs中

js
export default class Dep {  
	static target: ?Watcher;  
	id: number;  
	subs: Array<Watcher>;
	constructor () {  
	  this.id = uid++  
	  this.subs = []  
	}
	...
	depend () {  
	  if (Dep.target) {  
	    Dep.target.addDep(this)   // 1
	  }  
	}
	addSub (sub: Watcher) {   // 4
	  this.subs.push(sub)  
	}
}

export default class Watcher {
	depIds: SimpleSet;   
	newDeps: Array<Dep>; 
	newDepIds: SimpleSet; 
	....
	addDep (dep: Dep) {     // 2
	  const id = dep.id  
	  if (!this.newDepIds.has(id)) {   
		this.newDepIds.add(id)  
		this.newDeps.push(dep)  
		if (!this.depIds.has(id)) {  
		  dep.addSub(this)   // 3 
		}  
	  }  
	}
    ...
	update () {   // b
	  /* istanbul ignore else */  
	  if (this.lazy) {  
	    this.dirty = true  
	  } else if (this.sync) {  
	    this.run()  
	  } else {  
	    queueWatcher(this)  
	  }  
	}    
}
export default class Dep {  
	static target: ?Watcher;  
	id: number;  
	subs: Array<Watcher>;
	constructor () {  
	  this.id = uid++  
	  this.subs = []  
	}
	...
	depend () {  
	  if (Dep.target) {  
	    Dep.target.addDep(this)   // 1
	  }  
	}
	addSub (sub: Watcher) {   // 4
	  this.subs.push(sub)  
	}
}

export default class Watcher {
	depIds: SimpleSet;   
	newDeps: Array<Dep>; 
	newDepIds: SimpleSet; 
	....
	addDep (dep: Dep) {     // 2
	  const id = dep.id  
	  if (!this.newDepIds.has(id)) {   
		this.newDepIds.add(id)  
		this.newDeps.push(dep)  
		if (!this.depIds.has(id)) {  
		  dep.addSub(this)   // 3 
		}  
	  }  
	}
    ...
	update () {   // b
	  /* istanbul ignore else */  
	  if (this.lazy) {  
	    this.dirty = true  
	  } else if (this.sync) {  
	    this.run()  
	  } else {  
	    queueWatcher(this)  
	  }  
	}    
}

notify

js
export default class Dep {  
	static target: ?Watcher; 
	notify () {  
	  // stabilize the subscriber list first  
	  const subs = this.subs.slice()  
	  if (process.env.NODE_ENV !== 'production' && !config.async) {  
	    // subs aren't sorted in scheduler if not running async  
	    // we need to sort them now to make sure they fire in correct    // order    
	    subs.sort((a, b) => a.id - b.id)  
	  }  
	  for (let i = 0, l = subs.length; i < l; i++) {  
	    subs[i].update()   // a
	  }  
	}
}

export default class Watcher {
	depIds: SimpleSet;   
	newDeps: Array<Dep>; 
	newDepIds: SimpleSet; 
	....
	update () {   // b
	  /* istanbul ignore else */  
	  if (this.lazy) {  
		// 如果 watcher 是懒加载模式(lazy),
		// 它只需将 dirty 标志设置为 true。
		// 这意味着当下次访问这个值时,它会触发重新计算	    
	    this.dirty = true  
	  } else if (this.sync) {  
	    // 同步模式例如 <xx :dd.sync="value">
	    this.run()  
	  } else {  
	    queueWatcher(this)  
	  }  
	}    
}

export function queueWatcher (watcher: Watcher) {  
  const id = watcher.id  
  if (has[id] == null) {  
    has[id] = true  
    if (!flushing) {  
      queue.push(watcher)  
    } else {  
      // if already flushing, splice the watcher based on its id  
      // if already past its id, it will be run next immediately.      let i = queue.length - 1  
      while (i > index && queue[i].id > watcher.id) {  
        i--  
      }  
      queue.splice(i + 1, 0, watcher)  
    }  
    // queue the flush  
    if (!waiting) {  
      waiting = true  
  
      if (process.env.NODE_ENV !== 'production' && !config.async) {  
        flushSchedulerQueue()  
        return  
      }  
      nextTick(flushSchedulerQueue)  
    }  
  }  
}
export default class Dep {  
	static target: ?Watcher; 
	notify () {  
	  // stabilize the subscriber list first  
	  const subs = this.subs.slice()  
	  if (process.env.NODE_ENV !== 'production' && !config.async) {  
	    // subs aren't sorted in scheduler if not running async  
	    // we need to sort them now to make sure they fire in correct    // order    
	    subs.sort((a, b) => a.id - b.id)  
	  }  
	  for (let i = 0, l = subs.length; i < l; i++) {  
	    subs[i].update()   // a
	  }  
	}
}

export default class Watcher {
	depIds: SimpleSet;   
	newDeps: Array<Dep>; 
	newDepIds: SimpleSet; 
	....
	update () {   // b
	  /* istanbul ignore else */  
	  if (this.lazy) {  
		// 如果 watcher 是懒加载模式(lazy),
		// 它只需将 dirty 标志设置为 true。
		// 这意味着当下次访问这个值时,它会触发重新计算	    
	    this.dirty = true  
	  } else if (this.sync) {  
	    // 同步模式例如 <xx :dd.sync="value">
	    this.run()  
	  } else {  
	    queueWatcher(this)  
	  }  
	}    
}

export function queueWatcher (watcher: Watcher) {  
  const id = watcher.id  
  if (has[id] == null) {  
    has[id] = true  
    if (!flushing) {  
      queue.push(watcher)  
    } else {  
      // if already flushing, splice the watcher based on its id  
      // if already past its id, it will be run next immediately.      let i = queue.length - 1  
      while (i > index && queue[i].id > watcher.id) {  
        i--  
      }  
      queue.splice(i + 1, 0, watcher)  
    }  
    // queue the flush  
    if (!waiting) {  
      waiting = true  
  
      if (process.env.NODE_ENV !== 'production' && !config.async) {  
        flushSchedulerQueue()  
        return  
      }  
      nextTick(flushSchedulerQueue)  
    }  
  }  
}