# 存储watcher






































 
 
 
 
 
 
 
 
 
 
 
 
 












<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 
    模板编译原理 通过AST语法树描述出html语法结构 ,在把这个语法转换成JS语法,包上with 生成render方法 ,调用这个方法返回虚拟dom, 再把虚拟dom变成真实dom渲染到页面上。

    注意:虚拟dom和ast语法树不一样,虚拟dom是表示dom对象的,ast语法树这里表示html结构 

    1. 将模板变成一个render函数,
    2. 去当前的的实例上取值
    3. 虚拟dom(说白了就是一个对象)它的好处是可以描述DOM结构,diff算法,可以比对哪些属性变化了哪些属性没有变化,把需要变化的进行更新
    4. 生成真实dom
   -->
  <div id="app" a=1 b=2 style="color:red;font-size:12px;">
    <span style="color:red;">{{name}} aa {{age}} haha<a>hello</a></span>
  </div>
  <script src="/dist/vue.js"></script>
  <script>
    const vm = new Vue({
      el: '#app',
      data() {
        return {
          name: 'f',
          age: 18,
          address: ''
        }
      }
    })

    console.clear()

    // 修改了数据后,手动调用_update方法更新dom
    vm.name = 'dd'
    vm._update(vm._render()) // 强制更新的方法,但是我们希望数据变化后可以自动更新视图,而不是手动触发

    // 在这里取值,没有依赖模板,所以不需要收集依赖
    vm.address

    setTimeout(() => {
      vm.name = 'ff'
    }, 1000)

    // console.log(vm)
    /* 
    1. 默认渲染时会将渲染watcher放到Dep.target上
    2. 当我们调用this.getter时就会对属性进行取值操作
    3. 让Dep.target值为空,不在模板中访问的值不记录watcher
    4. 修改name值,会找到name对应的dep,通知dep中的watcher执行
    */
  </script>
</body>

</html>

src/lifeCycle.js









 
 
 
 
 
 





















import { patch } from './observer/patch';
import Watcher from './observer/watcher';

export function lifecycleMixin(Vue) {
	Vue.prototype._update = function(vnode) {
		console.log('_udpate', vnode);
		// 将虚拟节点转换成真实dom
		const vm = this;
		console.log('vm.$options.el', vm.$options.el);
		// 首次渲染需要用虚拟节点更新真实dom
		// vm.$el = patch(vm.$options.el, vnode);

		// 第一次渲染的完毕后,拿到新的节点,下次再次渲染时替换上次渲染的结果
		vm.$options.el = patch(vm.$options.el, vnode);
	};
}
export function mountComponent(vm, el) {
	// 默认 vue 通过 watcher 来渲染,这个watcher可以叫做渲染watcher,每个组件都有一个渲染watcher
	vm.$el = el;
	let updateComponent = () => {
		// 将虚拟节点 渲染到页面上
		// vm._render() 返回虚拟节点
		// vm._update() 将虚拟节点转换成真实节点
		vm._update(vm._render());
	};
	new Watcher(
		vm,
		updateComponent,
		() => {
			// 回调函数
		},
		true
	); // true 表示这是一个渲染watcher
}

src/observer/watcher.js

 














 
 
 


 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 





import { popTarget, pushTarget } from './dep';

// id用于标识不同组件不同的watcher
let id = 0;
class Watcher {
	constructor(vm, exprOrFn, cb, options) {
		this.vm = vm;
		this.exprOrFn = exprOrFn;
		if (typeof exprOrFn == 'function') {
			this.getter = exprOrFn;
		}
		this.cb = cb;
		this.options = options;
		// 每创建一个watcher就让id自增
		this.id = id++;
		// 让watcher记住dep
		this.deps = [];
		this.depsId = new Set();
		this.get();
	}
	get() {
		// 渲染之前,存储当前的watcher,这个watcher里有一个get方法
		pushTarget(this); // 给 Dep.target 赋值为当前的 watcher,也就是将watcher放到全局上
		this.getter(); // 这个方法会取data中的值,这个方法调用了render函数,会对模板的数据进行取值,触发Object.defineProperty的get方法

		// 清除当前watcher,为什么要清除当前的watcher呢,这是因为当我们没有在模板中使用数据,而是在其他地方使用的数据,不需要收集依赖
		popTarget(); // Dep.target = null
	}
	// 当属性取值时,需要记住这个watcher,等数据发生了变化,去执行自己记住的watcher,这个watcher会重新调用_update方法去更新

	// 让watcher记住deep
	addDep(dep) {
		// 不能直接这么写,因为页面中可能多次调用同一个数据, 如:{{name}} {{name}} {{name}},这样当前的watcher就存放了多个相同的deep了,不合理
		// this.deps.push(dep)

		let id = dep.id;
		// 使用set集合去重,过滤重复的dep
		if (!this.depsId.has(id)) {
			// dep是非重复的,watcher肯定也不会重
			this.depsId.add(id);
			// 让当前的watcher记住deep
			this.deps.push(dep);
			// 让dep记住当前watcher
			dep.addSub(this);
		}
	}
	update() {
		this.get();
	}
}

// 在取值之前把watcher暴露到全局上,让所有属性(这个属性必须在模板中使用到)的deep都记住这个watcher,等数据变了,就可以让属性记住的watcher去执行
export default Watcher;

src/observer/dep.js

// 可以把当前的watcher放到一个全局变量上

// id 用于表示dep的唯一性
let id = 0;
class Dep {
	constructor() {
		this.id = id++;
		// 属性记住watcher
		this.subs = [];
	}
	depend() {
		// Dep.target指的是当前的watcher
		if (Dep.target) {
			// 调用watcher的addDep方法实现dep记住watcher,watcher记住dep的功能
			Dep.target.addDep(this); // 让watcher,去存放dep,this指代的是当前dep
		}
	}
	notify() {
		this.subs.forEach((watcher) => watcher.update());
	}
	addSub(watcher) {
		// 属性deep记住当前的watcher
		this.subs.push(watcher);
	}
}

Dep.target = null;

export function pushTarget(watcher) {
	Dep.target = watcher;
}
export function popTarget() {
	Dep.target = null;
}
export default Dep;

# 对象依赖收集


 


























































 
 
 
 
 
 
 



 
 









 
 














import { arrayMethods } from './array';
import Dep from './dep';

class Observer {
	// value 观测的数据
	constructor(value) {
		// 为了方便数组拿到 observeArray 方法,对新增的每一项进行观测
		// 不要这样写,会造成死循环
		// value.__ob__ = this
		Object.defineProperty(value, '__ob__', {
			value: this,
			enumerable: false, // 不能被枚举,表示不能被循环
			configurable: false // 不能删除此属性
		});

		// value 可能是对象,可能是数组,需要分类来处理
		if (Array.isArray(value)) {
			// 数组不用 Object.defineProperty 进行代理,性能不好

			// 有些浏览器不支持 __proto__, 所以使用 Object.setPrototypeOf
			// value.__proto__ = arrayMethods // 当value是数组时,调用的数组方法为改写后的方法
			Object.setPrototypeOf(value, arrayMethods);

			// 处理原有数组中元素为对象的情况,将每一项变为响应式的,但是还处理不了后续加入的对象
			this.observeArray(value);
		} else {
			this.walk(value);
		}
	}
	observeArray(value) {
		value &&
			value.forEach((item) => {
				observe(item);
			});
	}
	walk(data) {
		// 将对象中所有 key 重新用 Object.defineProperty 定义成响应式的
		Object.keys(data).forEach((key) => {
			defineReactive(data, key, data[key]);
		});
	}
}

// 没有放到原型上的原因是因为这个方法不一定是通过实例调用的,如Vue.util.defineReactive
export function defineReactive(data, key, value) {
	// value有可能也是一个对象,需要递归遍历,所以vue2中数据尽量不要嵌套过深,会浪费性能
	/* 
  data() {
        return {
          name: 'f',
          obj: {
            name: 1,
            age: 2
          }
        }
      }
  */
	// Object.defineProperty只是重写了对象的get和set,Proxy是给对象设置代理不用改写对象性能高些,两者不一样
	observe(value);

	let dep = new Dep(); // 每次都会给属性创建deep,也就是每个属性都有一个deep属性,可以去记录当前的watcher
	Object.defineProperty(data, key, {
		get() {
			// 给每个属性增加一个deep,Dep.target指的是当前的watcher
			if (Dep.target) {
				dep.depend(); // 让当前属性的dep记住当前的watcher,也要让当前的watcher记住这个dep,注意只是让模板中使用到属性记住watcher,模板中没有使用到的属性不用记录watcher,防止无用更新
			}
			return value;
		},
		set(newValue) {
			// 取值时会打印输出当前dep
			console.log(dep);
			// 如果新值和旧值相等,则不做任何操作
			if (newValue === value) return;
			/* 
      如果用户重重新给数据赋值成新值且这个新值是对象,要将这个对象设置成响应式的,如:
      vm.obj = {name:'d'}
      */
			observe(newValue);
			value = newValue;

			// 通知dep中记录的watcher让它去执行,更新页面
			dep.notify();
		}
	});
}
export function observe(data) {
	// 只对对象类型进行观测,非对象类型无法观测
	if (typeof data !== 'object' || data === null) return;

	// 如果对象包含__ob__属性,说明已经被观测过了,可以防止循环引用
	if (data.__ob__) return;

	// 通过类实现对数据的观测,用类的方便扩展,会产生一个实例作为唯一标识,可以用这个实例来判断data是否被观测了
	return new Observer(data);
}

# 批量更新

// src/observer/scheduler.js
import { nextTick } from '../util/next-tick';

let has = {};
let queue = [];
let pending = false;
function flushSchedulerQueue() {
	for (let i = 0; i < queue.length; i++) {
		// 获取watcher
		let watcher = queue[i];
		// 执行watcher
		watcher.run();
	}
	queue = [];
	has = {};
	pending = false;
}

// 多次调用 queueWatcher ,如果watcher不是同一个,会多次调用nextTick,所以也需要加锁
export function queueWatcher(watcher) {
	// 更新时对watcher进行去重操作
	const id = watcher.id;
	if (has[id] == null) {
		has[id] = true;
		queue.push(watcher);
		if (!pending) {
			nextTick(flushSchedulerQueue);
			pending = true;
		}
	}
}


 













































 
 
 
 
 
 
 
 




// src/observer/watcher.js
import { popTarget, pushTarget } from './dep';
import { queueWatcher } from './scheduler';

// id用于标识不同组件不同的watcher
let id = 0;
class Watcher {
	constructor(vm, exprOrFn, cb, options) {
		this.vm = vm;
		this.exprOrFn = exprOrFn;
		if (typeof exprOrFn == 'function') {
			this.getter = exprOrFn;
		}
		this.cb = cb;
		this.options = options;
		// 每创建一个watcher就让id自增
		this.id = id++;
		// 让watcher记住dep
		this.deps = [];
		this.depsId = new Set();
		this.get();
	}
	get() {
		// 渲染之前,存储当前的watcher,这个watcher里有一个get方法
		pushTarget(this); // 给 Dep.target 赋值为当前的 watcher,也就是将watcher放到全局上
		this.getter(); // 这个方法会取data中的值,这个方法调用了render函数,会对模板的数据进行取值,触发Object.defineProperty的get方法

		// 清除当前watcher,为什么要清除当前的watcher呢,这是因为当我们没有在模板中使用数据,而是在其他地方使用的数据,不需要收集依赖
		popTarget(); // Dep.target = null
	}
	// 当属性取值时,需要记住这个watcher,等数据发生了变化,去执行自己记住的watcher,这个watcher会重新调用_update方法去更新

	// 让watcher记住deep
	addDep(dep) {
		// 不能直接这么写,因为页面中可能多次调用同一个数据, 如:{{name}} {{name}} {{name}},这样当前的watcher就存放了多个相同的deep了,不合理
		// this.deps.push(dep)

		let id = dep.id;
		// 使用set集合去重,过滤重复的dep
		if (!this.depsId.has(id)) {
			// dep是非重复的,watcher肯定也不会重
			this.depsId.add(id);
			// 让当前的watcher记住deep
			this.deps.push(dep);
			// 让dep记住当前watcher
			dep.addSub(this);
		}
	}
	// 如果多次同时更新一个数据,希望合并成一次去更新这个数据
	update() {
		// this.get();
		queueWatcher(this);
	}
  run() {
    this.get()
  }
}
// 在取值之前把watcher暴露到全局上,让所有属性(这个属性必须在模板中使用到)的deep都记住这个watcher,等数据变了,就可以让属性记住的watcher去执行
export default Watcher;

# nexTick

let callbacks = [];
let pending = false;
function flushCallbacks() {
	callbacks.forEach((cb) => cb());
	pending = false;
	callbacks = [];
}
let timerFunc;
if (Promise) {
	// then方法是异步的
	timerFunc = () => {
		Promise.resolve().then(flushCallbacks);
	};
} else if (MutationObserver) {
	// MutationObserver 也是一个异步方法
	let observe = new MutationObserver(flushCallbacks); // H5的api
	let textNode = document.createTextNode(1);
	observe.observe(textNode, {
		characterData: true
	});
	timerFunc = () => {
		textNode.textContent = 2;
	};
} else if (setImmediate) {
	timerFunc = () => {
		setImmediate(flushCallbacks);
	};
} else {
	timerFunc = () => {
		setTimeout(flushCallbacks, 0);
	};
}
// 批处理, 第一次开定时器,后续只更新列表,最后执行清空逻辑
/* 
举例: 第一次cb是渲染watcher(渲染watcher执行的过程是同步,更新是异步的),第二次cb是用户传入的回调
*/
export function nextTick(cb) {
	callbacks.push(cb); // cb默认是渲染逻辑,将用户的逻辑放到渲染逻辑之后即可
	if (!pending) {
		pending = true;
		timerFunc();
	}
}




 














 























// src/init.js
import { compileToFunctions } from './compiler/index';
import { mountComponent } from './lifeCycle';
import { initState } from './state';
import { nextTick } from './util/next-tick';

export function initMixin(Vue) {
	Vue.prototype._init = function(options) {
		const vm = this;
		vm.$options = options; // 实例上有个属性$options表示的是用户传入的属性,可以通过vm.$options获取到用户传入的所有属性

		// 初始化状态
		initState(vm);

		// 有el属性的话,说明数据可以挂在到页面上
		if (vm.$options.el) {
			vm.$mount(vm.$options.el);
		}
	};
  Vue.prototype.$nextTick = nextTick
	Vue.prototype.$mount = function(el) {
		const vm = this;
		const options = vm.$options;
		el = document.querySelector(el);
    vm.$options.el = el;
		// 如果有render就直接用render,没有render,看看有没有template属性,如果也没有template属性的话,就直接找外部模板
		// 如果没有render方法
		if (!options.render) {
			let template = options.template;
			// 如果没有模板但是有el
			if (!template && el) {
				template = el.outerHTML;
			}
			// 将模板编译成render函数
			const render = compileToFunctions(template);
			options.render = render;
			console.log('render', render);
		}

		mountComponent(vm, el); // 组件挂载
	};
}

nextTick的简单使用




























































 
 
 
















<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 
    模板编译原理 通过AST语法树描述出html语法结构 ,在把这个语法转换成JS语法,包上with 生成render方法 ,调用这个方法返回虚拟dom, 再把虚拟dom变成真实dom渲染到页面上。

    注意:虚拟dom和ast语法树不一样,虚拟dom是表示dom对象的,ast语法树这里表示html结构 

    1. 将模板变成一个render函数,
    2. 去当前的的实例上取值
    3. 虚拟dom(说白了就是一个对象)它的好处是可以描述DOM结构,diff算法,可以比对哪些属性变化了哪些属性没有变化,把需要变化的进行更新
    4. 生成真实dom
   -->
  <div id="app" a=1 b=2 style="color:red;font-size:12px;">
    <span style="color:red;">{{name}} aa {{age}} haha<a>hello</a></span>
  </div>
  <script src="/dist/vue.js"></script>
  <script>
    const vm = new Vue({
      el: '#app',
      data() {
        return {
          name: 'f',
          age: 18,
          address: ''
        }
      }
    })

    // console.clear()

    // 修改了数据后,手动调用_update方法更新dom
    vm.name = 'dd'
    vm._update(vm._render()) // 强制更新的方法,但是我们希望数据变化后可以自动更新视图,而不是手动触发

    // 在这里取值,没有依赖模板,所以不需要收集依赖
    vm.address

    setTimeout(() => {
      console.clear()
      // 1.测试批量更新
      vm.name = '1'
      vm.name = '2'
      vm.name = '3'
      // 2.修改完成后立刻获取真实dom,结果和我们想象的不一样  因为我们更新的操作是异步的
      // console.log(vm.$options.el.innerHTML)
      // 使用setTimeout来获取更新后的结果
      // setTimeout(() => {
      //   console.log(vm.$options.el.innerHTML)
      // })
      // 使用nextTick获取更新后的结果
      vm.$nextTick(() => {
        console.log(vm.$options.el.innerHTML)
      })
      vm.name = '4'
      vm.name = '5'
    }, 5000)

    // console.log(vm)
    /* 
    1. 默认渲染时会将渲染watcher放到Dep.target上
    2. 当我们调用this.getter时就会对属性进行取值操作
    3. 让Dep.target值为空,不在模板中访问的值不记录watcher
    4. 修改name值,会找到name对应的dep,通知dep中的watcher执行
    */
  </script>
</body>

</html>

# 数组的依赖收集

Vue的更新逻辑 通过nextTick异步执行更新视图逻辑。

默认当我们在模板中去取数组值的时候,需要让这个数组去收集依赖,数组怎么去收集依赖呢,就是给数组增加一个dep,如果我们取值了,就让这个数组通过这个dep记住这个依赖,当数组发生变化的时候,就调用notify去更新视图。

vue中的watcher分为渲染watcher、computed watcher和用户watcher。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- 
    模板编译原理 通过AST语法树描述出html语法结构 ,在把这个语法转换成JS语法,包上with 生成render方法 ,调用这个方法返回虚拟dom, 再把虚拟dom变成真实dom渲染到页面上。

    注意:虚拟dom和ast语法树不一样,虚拟dom是表示dom对象的,ast语法树这里表示html结构 

    1. 将模板变成一个render函数,
    2. 去当前的的实例上取值
    3. 虚拟dom(说白了就是一个对象)它的好处是可以描述DOM结构,diff算法,可以比对哪些属性变化了哪些属性没有变化,把需要变化的进行更新
    4. 生成真实dom
   -->
  <div id="app" a=1 b=2 style="color:red;font-size:12px;">
    {{arr1}}
  </div>
  <!-- {{arr2[0].name}}{{arr2}} -->
  <script src="/dist/vue.js"></script>
  <script>
    const vm = new Vue({
      el: '#app',
      data() {
        return {
          // 情况一 内部会给arr1这个属性增加依赖,但是我们更改数组时不会去通知更新
          // arr1: [1, 2, 3],
          // 情况二 外层数组进行了依赖收集,但是里层数组还没有进行依赖收集
          arr1:[[1, 2, 3]]
          // arr2: [{name: 'f'}]
        }
      }
    })
    setTimeout(() => {
      // vm.arr1.push(4)
      vm.arr1[0].push(4)
      // _s(xx)里使用了JSON.stringify,所以改变页面会变化
      // vm.arr2[0].name = 'dd'
    }, 1000);
  </script>
</body>

</html>







 























 













 
 
 
 
 
 
 
 
 
 
 
















 
 
 
 








 
 
 
 
 
 
 
 
 































// src/observer/index.js
import { arrayMethods } from './array';
import Dep from './dep';

class Observer {
	// value 观测的数据
	constructor(value) {
		this.dep = new Dep(); // 给数组本身和对象本身增加一个dep属性
		// 为了方便数组拿到 observeArray 方法,对新增的每一项进行观测
		// 不要这样写,会造成死循环
		// value.__ob__ = this
		Object.defineProperty(value, '__ob__', {
			value: this,
			enumerable: false, // 不能被枚举,表示不能被循环
			configurable: false // 不能删除此属性
		});

		// value 可能是对象,可能是数组,需要分类来处理
		if (Array.isArray(value)) {
			// 数组不用 Object.defineProperty 进行代理,性能不好

			// 有些浏览器不支持 __proto__, 所以使用 Object.setPrototypeOf
			// value.__proto__ = arrayMethods // 当value是数组时,调用的数组方法为改写后的方法
			Object.setPrototypeOf(value, arrayMethods);

			// 处理原有数组中元素为对象的情况,将每一项变为响应式的,但是还处理不了后续加入的对象
			this.observeArray(value);
		} else {
			this.walk(value);
		}
	}
  // 处理数组中的元素是对象的情况,注意:数组也是对象,就会递归观测,观测的时候回增加__ob__属性 Object.defineProperty(value, '__ob__', {...}
	observeArray(value) {
		value &&
			value.forEach((item) => {
				observe(item);
			});
	}
	walk(data) {
		// 将对象中所有 key 重新用 Object.defineProperty 定义成响应式的
		Object.keys(data).forEach((key) => {
			defineReactive(data, key, data[key]);
		});
	}
}
// value指代的是数组
// 让里层数组收集外层数组的依赖(因为收集的都是同一个watcher),这样修改里层数组也可以更新视图
function dependArray(value) {
	for (let i = 0; i < value.length; i++) {
		let current = value[i];
		current.__ob__ && current.__ob__.dep.depend(); // 让里层的数组和外层的数组收集的都是同一个watcher
		if (Array.isArray(current)) {
			dependArray(current);
		}
	}
}
// 没有放到原型上的原因是因为这个方法不一定是通过实例调用的,如Vue.util.defineReactive
export function defineReactive(data, key, value) {
	// value有可能也是一个对象,需要递归遍历,所以vue2中数据尽量不要嵌套过深,会浪费性能
	/* 
  data() {
        return {
          name: 'f',
          obj: {
            name: 1,
            age: 2
          }
        }
      }
  */
	// Object.defineProperty只是重写了对象的get和set,Proxy是给对象设置代理不用改写对象性能高些,两者不一样

	// 相当于给数组本身添加了一个dep属性,让数组通过childOb.dep.depend()收集了watcher,等到数组中的内容发生变化去通知watcher更新
	let childOb = observe(value);
	// 在这个例子中,这个dep是给数组加的,仅限于这个例子
	console.log('childOb.dep', childOb.dep); // Dep {id: 1, subs: Array(0)}

	let dep = new Dep(); // 每次都会给属性创建deep,也就是每个属性都有一个deep属性,可以去记录当前的watcher
	Object.defineProperty(data, key, {
		get() {
			// 给每个属性增加一个deep,Dep.target指的是当前的watcher
			if (Dep.target) {
				dep.depend(); // 让当前属性的dep记住当前的watcher,也要让当前的watcher记住这个dep,注意只是让模板中使用到属性记住watcher,模板中没有使用到的属性不用记录watcher,防止无用更新

				// childOb 可能是对象也可能是数组,比如我们给对象新增了一个属性,需要出发对象的更新,如我们有对象 {a:1} ,我们使用$set(obj, b, 2)给这个对象新增了属性
				if (childOb) {
					// 如果对数组取值,会将当前的watcher和数组进行关联
					childOb.dep.depend();
					if (Array.isArray(value)) {
						// 如果内部还是数组
						dependArray(value); // 不停的进行依赖收集
					}
				}
			}
			return value;
		},
		set(newValue) {
			// 取值时会打印输出当前dep
			console.log(dep);
			// 如果新值和旧值相等,则不做任何操作
			if (newValue === value) return;
			/* 
      如果用户重重新给数据赋值成新值且这个新值是对象,要将这个对象设置成响应式的,如:
      vm.obj = {name:'d'}
      */
			observe(newValue);
			value = newValue;

			// 通知dep中记录的watcher让它去执行,更新页面
			dep.notify();
		}
	});
}
export function observe(data) {
	// 只对对象类型进行观测,非对象类型无法观测
	if (typeof data !== 'object' || data === null) return;

	// 如果对象包含__ob__属性,说明已经被观测过了,可以防止循环引用
	if (data.__ob__) return;

	// 通过类实现对数据的观测,用类的方便扩展,会产生一个实例作为唯一标识,可以用这个实例来判断data是否被观测了
	return new Observer(data);
}





























 
 





// src/observer/array.js
// 注意不能直接改写数组原有方法,只需要改写被 vue 控制的数组,因为代码中的数组有可能没被data直接使用
const oldArrayProtoMethods = Array.prototype;

export let arrayMethods = Object.create(oldArrayProtoMethods);

// 下面这些方法会改变原数组
let methods = [ 'push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice' ];
methods.forEach((method) => {
	// 重写数组方法
	arrayMethods[method] = function(...args) {
		// 调用数组原来方法
		const result = oldArrayProtoMethods[method].apply(this, args);

		const ob = this.__ob__;
    // 有可能用户新增的数据是对象,这个时候需要做特殊处理
		// inserted 保存插入的值
    let inserted;
		switch (method) {
			case 'push':
			case 'unshift':
				inserted = args;
				break;
			case 'splice':
				inserted = args.slice(2);
			default:
				break;
		}
		if (inserted) ob.observeArray(inserted); // 对新增的每一项进行观测
    // 数组变化后通知watcher更新
    ob.dep.notify()

		return result;
	};
});