手撸vue2 - 2.初始化data实现数据监听

1. initState方法

该方法统一初始化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)
    
}

2. initData方法

该方法为初始化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
            }
        })
    }
}

3. observer方法实现

实现数据监听入口方法。

判断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)
}

3.1 Observer类

该类是数据监听核心类,每一个对象上都会有一个__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)
        }
    }
}

3.1.1 defineReactive方法实现

该方法是对象数据类型实现数据监听的核心方法。

上面通过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更新了数据,重新渲染页面
        }
    })
}

3.1.2 实现数组监听

采用策略模式劫持数组常用的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

你可能感兴趣的:(手撸Vue2,vue.js,前端,javascript)