Vue2使用的是 options api,无法tree-shaking。

导出vue构造函数

import { initMixin } from './init';

function Vue(options) {
	this._init(options); // 当用户 new Vue 时,会调用 init 方法进行 vue 的初始化
}

// Vue.prototype._init = function(options) {
//   const vm = this;
//   vm.$options = options; // 实例上有个属性$options 表示是用户传入的属性
// };

// 这样写的好处是可以查分逻辑,便于代码维护
initMixin(Vue); // 给原型上新增_init方法


export default Vue;

init方法中初始化vue状态

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

根据不同属性进行初始化操作

// 作用:将所有数据定义在vm属性上,并且后续更改触发视图更新
export function initState(vm) {
	const opts = vm.$options; // 获取用户传入属性

	if (opts.props) {
		initProps(vm);
	}
	if (opts.methods) {
		initMethod(vm);
	}
	if (opts.data) {
		// 初始化data
		initData(vm);
	}
	if (opts.computed) {
		initComputed(vm);
	}
	if (opts.watch) {
		initWatch(vm);
	}
}

# 初始化数据

function initData(vm) {
	let data = vm.$options.data;
	// 对data类型进行判断,如果是函数获取函数返回值作为对象,使用call是保证当前的this指向实例,因为我们可以在data中调用this.xxx
	data = vm._data = typeof data === 'function' ? data.call(vm) : data;
	// 通过 vm._data获取劫持后的数据,用户就可以通过拿到_data了,要不虽然我们对数据进行了劫持,但是用户无法拿到劫持后的data,因为如果data是一个函数,函数执行完返回的对象外部无法拿到

	// 将_data中的数据全部放到vm上,让用户可以通过vm.xx直接拿到数据,而不用像vm._data.xxx这么麻烦
	for (let key in data) {
		proxy(vm, '_data', key);
	}

	// 观察数据
	observe(data);
}

# 递归属性劫持

class Observer {
	// value 观测的数据
	constructor(value) {
		this.walk(value);
	}
  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)

  Object.defineProperty(data, key, {
    get() {
      return value
    },
    set(newValue) {
      // 如果新值和旧值相等,则不做任何操作
      if (newValue ===  value) return
      /* 
      如果用户重重新给数据赋值成新值且这个新值是对象,要将这个对象设置成响应式的,如:
      vm.obj = {name:'d'}
      */
      observe(newValue)
      value = newValue
    }
  })
}
export function observe(data) {
	// 只对对象类型进行观测,非对象类型无法观测
	if (typeof data !== 'object' || data === null) return;

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

# 数组

上面我们实现了对对象的劫持,但是对数组呢?

const vm = new Vue({
  data() {
    return {
      arr: [1, 2, 3, 4]
    } 
  }
})
console.log(vm)

控制台输出如下:

可以看到对数组中的每一项都进行了代理,如果数组中的数据很大,那性能会很不好。所以在vue中对数组,不使用 Object.defineProperty 。

我们需要重写 Array 中能改变数组的方法:

// 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); // 对新增的每一项进行观测
		return result;
	};
});

 



 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 




 
 
 
 
 
 













































 
 





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

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);

	Object.defineProperty(data, key, {
		get() {
			return value;
		},
		set(newValue) {
			// 如果新值和旧值相等,则不做任何操作
			if (newValue === value) return;
			/* 
      如果用户重重新给数据赋值成新值且这个新值是对象,要将这个对象设置成响应式的,如:
      vm.obj = {name:'d'}
      */
			observe(newValue);
			value = newValue;
		}
	});
}
export function observe(data) {
	// 只对对象类型进行观测,非对象类型无法观测
	if (typeof data !== 'object' || data === null) return;

	// 如果对象包含__ob__属性,说明已经被观测过了,可以防止循环引用,给所有响应式数据增加标识,并且可以在响应式上获取Observer实例上的方法
	if (data.__ob__) return;

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

# 数据代理

function proxy(vm, source, key) {
	Object.defineProperty(vm, key, {
		get() {
			return vm[source][key];
		},
		set(newValue) {
			vm[source][key] = newValue;
		}
	});
}