该方法统一初始化options中的内容
// src/instance/initState.js
// 初始化options
export function initState(vm){
let options = vm.$options
// 初始化data
if(options.data)initData(vm)
// 初始化computed
if(options.computed)initComputed(vm, options.computed)
// 初始化watch
if(options.watch)initWatch(vm)
}
该方法为初始化data入口方法。
先判断data是否是一个函数,如果是一个函数就调用返回对象,获取对象赋值给Vue._data。
将data传给observer方法实现数据监听,下面详细介绍。
遍历data属性代理给Vue,实现可以在method,computed等option中使用this.属性。
// src/instance/initState.js
/**
* 初始化data
* @param Vue vm
*/
function initData(vm){
let data = vm.$options.data
data = vm._data = typeof data === 'function' ? data.call(this) : data //如果是一个函数就执行函数
// 监听data
observer.call(vm,data)
// 将data中的属性挂载到Vue
let keys = Object.keys(data)
for(let i = 0; i < keys.length; i++){
Object.defineProperty(vm, keys[i],{
get(){
return vm._data[keys[i]]
},
set(value){
vm._data[keys[i]] = value
}
})
}
}
实现数据监听入口方法。
判断data是否是一个对象,如果是一个对象就将该对象传给Observer类进行处理。
如果不是对象,是基本类型或者函数就直接返回,因为后期会递归调用该方法,作为递归结束条件所以进行类型判断。
// src/observer/index.js
import newArrayProperty from "./arr"
import { Dep } from "./Dep"
// 监听data
export function observer(data){
// 判断data是否是一个对象或者数组
if(typeof data !== 'object' || data == null) return data
return new Observer(data)
}
该类是数据监听核心类,每一个对象上都会有一个__ob__属性代理到Observer实例,方便后面通过__ob__属性执行Observer上的一些方法。
Observer实例上也绑定了data与dep,data就是源对象,dep就是一个Dep类的实例,可以理解为依赖,每个对象可以通过他身上的依赖也就是dep通知渲染watcher更新渲染页面。后面另开章节详细说明。这里知道有这么个东西就行了。
接下来就是实现数据监听了,Vue2中有两种情况,一种是数组,另一种是对象。
数组监听:先判断data是不是数组,如果是数组就改变改数组的原型,让它执行我们劫持数组的那几个方法,然后递归处理数组中的数据。下面子章节做详细说明。
对象监听:如果不是数组类型的话那就只能是对象了,然后就执行walk方法遍历对象属性执行defineReactive方法进行对象数据监听。下面子章节做详细说明。
// src/observer/index.js
class Observer {
constructor(data){
this.value = data
this.dep = new Dep()
// 给每个对象代理一个ob属性,ob为Observer实例,用于方便执行Observer中的方法
Object.defineProperty(data,'__ob__',{
enumerable: false,
value: this
})
// 如果data是一个数组
if(Array.isArray(data)){
data.__proto__ = newArrayProperty // 让数组原型指向我们的对象
this.observerArray(data) // 对数据里面的数据判断劫持
}else{
this.walk(data)
}
}
// 处理对象里面的数据
walk(data){
// 获取data的所有属性
let keys = Object.keys(data)
for(let i = 0; i < keys.length; i++){
let key = keys[i]
let value = data[key]
defineReactive(data, key, value)
}
}
// 处理数组里面的数据
observerArray(array){
for(let i = 0; i < array.length; i++){
let value = array[i]
// 如果是一个对象就递归
if(typeof value === 'object') observer(value)
}
}
}
该方法是对象数据类型实现数据监听的核心方法。
上面通过walk方法遍历data中的属性,每个属性执行了defineReactive方法。
Object.defineProperty中的get方法收集依赖,当首次加载页面时render函数被watcher托管,也就是渲染watcher。这时渲染watcher托管的render函数会读取模板中的data属性,也就会执行下面的get方法,此时data中的dep就会与渲染watcher绑定。
当用户修改data时就会执行set方法,set中先判断修改的值和原来的是否一样,一样的话就直接返回,不一样就执行observer方法,加工新的data然后进行赋值,最后dep通知依赖的渲染watcher更新视图渲染页面。
Dep和Watcher类比较复杂,另外开一个章节进行说明。
// src/observer/index.js
function defineReactive(data, key, value){
let dep = data.__ob__.dep
let childOb = observer(value) // 对value进行递归监听
Object.defineProperty(data,key,{
// enumerable: true,
// configurable: true,
get(){
if(Dep.target){ // 首次获取数据时才将添加依赖,其他时候调取数据时不添加依赖
if(Array.isArray(childOb.value)){ // 如果是数组给数组添加依赖
childOb.dep.depend() // 绑定watcher添加依赖
dependArray(childOb.value) // 给数组里面的数组添加依赖
}
dep.depend() // 对象添加依赖
}
return value
},
set(newValue){
if(newValue === value)return // 如果更改的值与原来的值相等就直接返回
observer(newValue) // 如果newValue是一个对象也需要进行监听
value = newValue
dep.notify() // 告诉Dep更新了数据,重新渲染页面
}
})
}
采用策略模式劫持数组常用的7个方法,在内部判断并获取增数据,新增数据重新做数据劫持。
将操作参数交给原来数组方法进行执行。
通知watcher更新视图,这里notify可以写前面是因为notify是通过nextTick实现的,notify会在数组原方法执行后执行。为什么会这样,等到nextTick实现的时候在详细说明。
// src/observer/arr.js
let olderArrayProperty = Array.prototype
let newArrayProperty = Object.create(olderArrayProperty)
let methods = [
'push',
'pop',
'shift',
'unshift',
'sort',
'splice',
'reverse'
]
for(let i = 0; i < methods.length; i++){
newArrayProperty[methods[i]] = function(...args){
// console.log(methods[i],"被劫持了")
// 如果是在数组插入数据,劫持插入的数据
let inserted
switch (methods[i]) {
case "push":
case "unshift":
inserted = args
break;
case "splice":
inserted = args.slice(2)
break;
}
let ob = this.__ob__
// 将插入的数据重新交给Observer进行响应式处理
if(inserted){
ob.observerArray(inserted)
}
ob.dep.notify() // 通知更新视图
return olderArrayProperty[methods[i]].apply(this, args) // 调用数组原方法
}
}
export default newArrayProperty