【vue2原理】-第二篇,对象的劫持

52 阅读3分钟

1. 对象的单层劫持

  • data 为函数和对象的处理,及当 data 为函数时的 this 指向问题
  • Observer 类:对数据进行观测
  • walk 方法:遍历 data 属性(对象属性遍历)
  • defineReactive 方法:利用 Object.defineProperty 实现数据劫持

1,initData 中获取 data

data 在 options 中,而 options 已被 vm 实例共享( vm.$options = options )

function initData(vm){    
    let data = vm.$options.data;// 拿到 vue 初始化时,用户传入的data数据    
    console.log("进入 state.js - initData,数据初始化操作", data)
}

2,处理 data 的两种情况

上篇说了,data 有可能是函数,也有可能是对象,因此后续逻辑需要对此进行处理

export function isFunction(val){  
    return typeof val == 'function'
}

如果 data 是函数,则需要让 data 函数执行,拿到它返回的对象

// 如果 data 是函数,需要执行 data 拿到它的返回值
if(isFunction(data)){  
    data = data(); // 这样执行,this 不是 vm,而是window
}

测试

let vm = new Vue({  
    el: '#app',  
    data() {    
        console.log("打印 data函数执行时,this的指向")    
        console.log(this)    
        return { message: 'Hello Vue' } // data 返回一个对象  
    }
 });

此时,data 函数执行时,this 指向 window

3,处理 this 的指向问题

在 Vue 中,data 函数执行时 this 应指向当前 vm 实例

所以,在 data 执行时绑定 this 为当前 vm 实例

if(isFunction(data)){  
    data = data.call(vm);// data 执行时,绑定this 为 vm
}

测试:

将 data 是对象和函数的两种情况进行逻辑简化:

// data 可能是函数或对象
// 如果 data 是函数,则需要让 data 函数执行,拿到它返回的对象
// 如果 data 是对象,不做处理
data = isFunction(data) ? data.call(vm) : data;

注意:只有根实例的 data 可以是对象,组件必须为函数

4,核心模块 observe:对数据进行观测

data 数据的响应式原理是:

通过 Object.defineProperty,重写 data 上的所有属性

这就需要遍历 data 对象拿到每一个属性,再逐一通过 Object.defineProperty 重新定义

调用 observe 方法观测数据,实现 data 数据的响应式

import { observe } from "./observe";
import { isFunction } from "./utils";

export function initState(vm) {    
    const opts = vm.$options;    
    if (opts.data) {        
        initData(vm);    
    }
}

function initData(vm) {    
    let data = vm.$options.data;    
    data = isFunction(data) ? data.call(vm) : data;    
    observe(data);  // 使用 observe 实现 data 数据的响应式
}

经过 data = isFunction(data) ? data.call(vm) : data;处理后,此时 data 一定是一个对象

所以,对处理后的 data 进行检测,如果不是对象就结束

// 判断是否为对象:类型是object,且不能为 null 
export function isObject(val) {  
    return typeof val == 'object' && val !== null
}
import { isObject } from "../utils";

export function observe(value) {
  // 如果 value 不是对象,就不需要观测了,直接 return  
  if(!isObject(value)){    
      return;  
  }
}

5,Observer 类:对【对象】进行观测

完成的逻辑是这样的:

export function observe(value) {  
    if(!isObject(value)){    
        return;  
    }
  // 观测 value 对象,实现数据响应式  
  return new Observer(value);
}

Observer 类:

遍历对象属性,使用 Object.defineProperty 重新定义 data 对象中的属性

class Observer {
  constructor(value){    // 如果value是对象,遍历对象中的属性,使用 Object.defineProperty 重新定义    
      this.walk(value); // 循环对象属性  
  }
  
  // 循环 data 对象,使用 Object.keys 不循环原型方法  
  walk(data){    
      Object.keys(data).forEach(key => {       
          // 使用 Object.defineProperty 重新定义 data 对象中的属性      
          defineReactive(data, key, data[key]);    
      });  
  }
}

function defineReactive(obj, key, value) {  
    Object.defineProperty(obj, key, {    
        get(){ // 闭包      
             return value;	    
        },    
        set(newValue) {      
            if (newValue === value) return      
            value = newValue;    
        }  
    })
}

至此,obj 中的所有属性都通过 defineProperty 重新定义,具有 get、set 方法

2. 对象的深层劫持

1,实现思路

当对象属性 obj 即将被 Object.defineProperty 劫持时,

再对 obj 对象做一次“对象的单层劫持”

更深层的对象属性劫持,就是在递归执行“对象的单层劫持”

即:当属性为对象类型时(非 null)

继续调用 observe 进行观测,observe 的递归实现了对象属性的深层劫持

2,代码逻辑

function defineReactive(obj, key, value) {  
    observe(value);// 递归实现深层观测  
    Object.defineProperty(obj, key, {    
        get() {      
            return value;    
        },    
        set(newValue) {      
            if (newValue === value) return      
            value = newValue;    
        } 
    })
}