1.通过options获取vue实例的参数
new Vue({...})
function Vue(options) {
this._init(options); // 入口方法,初始化操作
}
2.初始化方法initMixin
- Vue原型上添加_init方法
- Vue中的状态进行初始化initState(双向绑定)
function initMixin(Vue) {
Vue.prototype._init = function(options) {
const vm = this;
vm.$options = options;
// 初始化状态(将数据做一个初始化劫持,当我改变数据时应该更新视图)
// vue组件中又很多的状态 data props watch computed
initState(vm)
}
3.初始化状态
initProps/initMethods/initData/initComputed/initWatch
4.initData数据劫持方案
function initData(vm) {
let data = vm.$options.data;
// 执行data函数 返回对象
vm._data = data = typeof data == 'function' ? data.call(vm) : data;
// 数据的劫持方案
observe(data)
// 属性代理 当我去vm上取值时,帮我将属性的取值代理到vm._data上
for(let key in data) {
proxy(vm,'_data',key)
}
function proxy(vm,data,key) {
Object.defineProperty(vm,key,{
get() {
return vm[data][key];
},
set(newValue) {
vm[data][key] = newValue;
}
})
}
}
5.observe函数
function observe(data) {
// 判断data是不是object 如果不是就不需要做数据劫持了
if (typeof data !== 'object' || data == null) {
return;
}
// __ob__对象观测标志
if(data.__ob__) return data;
return new Observer(data)
}
6.Observer数据观测核心类
class Observer {
constructor(value) {
// 判断一个对象是不是被观测过,看他是否有__ob__这个属性
Object.defineProperty(value,'__ob__',{
enumerable:false, // Object.keys获取不到这个key值 就不会导致死循环
configurable:false,
value: this
})
// 数组和对象采用不同的观测方式
if(Array.isArray(value)) {
// 开发功能时很少对数组索引进行操作,为了性能考虑不对数组进行拦截
// 拦截可以改变数组的方法进行操作(push shift unshift splice sort reverse pop)
// 函数劫持/切片编程
value.__proto__ = arrayMethods // 重写了数组方法
// 观测数组中的对象
this.observeArray(value) // 数组中如果存在对象 也需要被观测
}else {
this.walk(value) // 对象观测
}
}
observeArray(value) {
value.forEach(item => {
observe(item) // 观测数组中的对象类型
})
}
walk(data) {
let keys = Object.keys(data) // 获取对象的key
keys.forEach(key=>{
defineReactive(data,key,data[key]) // Vue.util.defineReactive 对象数据劫持方法
})
}
}
function defineReactive(data,key,value) {
observe(value) // 如果值是对象再进行观测(递归)
Object.defineProperty(data,key,{
get() {
console.log('取值时数据响应');
return value
},
set(newValue) {
console.log('设置值时数据响应');
if(newValue == value) return;
observe(newValue) // 如果用户将值设置为对象仍然需要进行观测
value = newValue;
}
})
}
7.arrayMethods数组方法重写
// 获取数组原型上的方法
let oldArrayProtoMethods = Array.prototype;
// 继承数组原型方法 arrayMethods.__proto__ = oldArrayProtoMethods
export let arrayMethods = Object.create(oldArrayProtoMethods);
let methods = [
'push',
'shift',
'unshift',
'splice',
'sort',
'reverse',
'pop'
] // 这其中方法会改变原数组的值
// 函数劫持/切片编程
methods.forEach(method => {
arrayMethods[method] = function (...args) {
console.log('数组方法被调用了');
const result = oldArrayProtoMethods[method].apply(this,args); // this 就是observer里的value
let inserted;
let ob = this.__ob__;
switch(method) {
case 'push': // arr.push({a:1},{b:2})
case 'unshift': // 追加数据 可能是对象 应该再次被劫持
inserted = args;
break;
case 'splice': // vue.$set原理
inserted = args.slice(2);
default:
break;
}
if(inserted) ob.observeArray(inserted) // 新增对象也被观测了
return result;
}
})
总结
几个重点:
- 对象和数组的数据劫持分开处理
因为数组很少通过索引去修改值,更常见的是使用方法(push等),因此需要重写数组的原型方法
- 用户添加的数据可能是对象或者数组,需要额外进行观测
在对象进行赋值时,在set里对新的值继续进行观测;数组添加值时,进行数组的观测
- Object.defineProperty在进行对象数据观测时,深层级的对象无法实现观测
因此在defineReactive函数内部继续观测value(observe(value)),通过递归的方式实现数据观测---深度遍历
- 数组中可能存在对象,仍然需要进行数据观测
observeArray(value) {
value.forEach(item => {
observe(item) // 观测数组中的对象类型
})
}
- 被观测数据上的__ob__属性有着重要的作用
- 1)防止数据进行重复的观测
- 2)__ob__属性指向Observer实例本身,因此在数组劫持的模块里就可以通过被劫持对象的__ob__属性获取Observer实例,从而调取Observer实例上的observeArray方法