Vue源码解析系列课程--Vue数据响应式原理笔记(对象、数组)

目录

 

一、defineReactive函数:

二、递归侦测对象全部属性:

1. 根据传入的值为对象类型 , 修改defineReactive.js函数

2.创建observe.js函数辅助Observer.js函数:

3.创建一个工具函数utils.js

4. 创建Observer.js函数写Observer类

5. 对象测试: 

三、数组的响应式处理:

1. array.js函数:

 

2. Observer.js类:

3. push、unshift、splice这三种方法比较特殊(完善array.js函数):

4. 数组测试:


一、defineReactive函数:

函数功能:创建闭包环境,完善Object.defineProperty(){}里面的getter()和setter()方法。

不用闭包的情况(需要我们创建临时变量进行新旧值的周转):

var obj = {};
var temp;

Object.defineProperty(obj,'a',{
    //getter
    get(){
        console.log("试图访问obj的"+a+"属性");
        return temp;
    },
    //setter
    set(newValue){
        console.log("试图改变obj的a属性",newValue);
        temp = newValue;
    }
});

console.log(obj.a);
obj.a = 9;
obj.a++;
console.log(obj.a);

利用闭包情况:

var obj = {};
function defineReactive(data,key,val){
    Object.defineProperty(data,key,{
        //可枚举
        enumerable:true,
        //可被配置
        configurable:true,
        //getter
        get(){
            console.log('你试图访问obj的a属性');
            return val;
        }
        //setter
        set(newValue){
            console.log('你试图修改obj的a属性',newValue);
            if(val == newValue){
                return;
            }
            val = newValue;
        }
    });
}

defineReactive(obj,'a',10);

console.log(obj.a);
obj.a = 69;
obj.a++;
console.log(obj.a);

二、递归侦测对象全部属性:

图示:

Vue源码解析系列课程--Vue数据响应式原理笔记(对象、数组)_第1张图片

1. 根据传入的值为对象类型 , 修改defineReactive.js函数

如果此时obj的值如下形式则不能使全部值得到响应式。

var obj = {
    a:{
        m:{
            n:5
        }
    }
}

那么defineReactive.js函数要发生改变。

  • 单独摘出来并向外暴露一个函数。
  • 因为值如果是对象,我们要加入 arguments.length 用来判断传入参数的个数。如果是两个参数,那么就直接取其值为val。
export default function defineReactive(data,key,val){

    if(arguments.length == 2){
        val = obj[key];
    }
    Object.defineProperty(data,key,{
        //可枚举
        enumerable:true,
        //可被配置
        configurable:true,
        //getter
        get(){
            console.log('你试图访问obj的a属性');
            return val;
        }
        //setter
        set(newValue){
            console.log('你试图修改obj的a属性',newValue);
            if(val == newValue){
                return;
            }
            val = newValue;
        }
    });
}

2.创建observe.js函数辅助Observer.js函数:

  • 接收参数判断是不是对象,如果不是对象则什么也不做。
  • 定义ob一会存储Observer的实例,因为Observer为类。
  • 判断value的__ob__是不是undefined,如果是则给其new一个Observer实例,如果不是则将其__ob__的值赋给ob;
import Observer from "./Observer";
export default function(value){
    //如果value不是对象,什么都不做
    if(typeof value != 'object'){
        return;
    }
//    定义ob
    var ob;
    if(typeof value.__ob__ != 'undefined'){
        ob = value.__ob__;
    }else{
        ob = new Observer(value);
    }
    return ob;
};

3.创建一个工具函数utils.js

功能:用来添加 __ob__属性,因为其是不可枚举属性。

  • 定义obj对象的key属性
//定义obj对象的key属性
export const def = function(obj,key,value,enumerable){
    Object.defineProperty(obj,key,{
        value,
        enumerable,
        writable:true,
        configurable
    })
}

4. 创建Observer.js函数写Observer类

函数功能:将一个正常的对象转换为每个层级的属性都是响应式(可被侦测到的)对象。

  • 首先通过utils函数给传进来的值添加一个__ob__属性(就是Observer的实例)
  • 需要注意的是在构造函数中的this不是表示类本身,而是表示实例。
  • walk函数遍历value对象的每一个值,给其设置一个defineReactive,在defineReactive里调用observe形成三者之间互相调用,给对象里面的所有元素都添加上Observer实例

Observer.js函数:

        /*给实例(this,一定要注意,构造函数中的this不是表示类本身,而是表示实例)
        添加了__ob__属性,值是这次new的实例
         *这里value是传过来的obj
         * '__ob__'是传过去的key
         * this,是传过去的value
         * false是不可枚举的
         * */

def(value,'__ob__',this,false);

 

/*
* 功能:将一个正常的object转换为每个层级的属性都是响应式
* (可以被侦测的object)
* */

import {def} from './utils.js'
import defineReactive from './defineReactive'
import {arrayMethods} from './array.js'
import observe from "./observe";
import Dep from './Dep.js'
import Watcher from './Watcher.js'


//一个类如何被实例化
export default class Observer{
    //构造器
    constructor(value){
        //给实例(this,一定要注意,构造函数中的this不是表示类本身,而是表示实例)
        //添加了__ob__属性,值是这次new的实例
        def(value,'__ob__',this,false);
        /*
         *这里value是传过来的obj
         * '__ob__'是传过去的key
         * this,是传过去的value
         * false是不可枚举的
         * */

        console.log(value);

        this.walk(value);
    }
    //    遍历
    walk(value){

       // 嵌套的子属性在此处遍历

       for(let k in value){
            defineReactive(value,k);
       }
    }

}

defineReactive.js函数:

  • defineReactive.js中我们注意要在set函数里面将修改后的值也要observe一下。
import observe from './observe.js'
export default function defineReactive(data,key,val){

    if(arguments.length == 2){
        val = obj[key];
    }

    let childOb = observe(val);
    Object.defineProperty(data,key,{
        //可枚举
        enumerable:true,
        //可被配置
        configurable:true,
        //getter
        get(){
            console.log("你试图访问obj的"+key+"属性");
            return val;
        }
        //setter
        set(newValue){
            console.log('你试图修改obj的'+key+'属性',newValue);
            if(val == newValue){
                return;
            }
            val = newValue;
            childOb = observe(newValue); 
        }
    });
}

5. 对象测试: 

index.js文件:

import defineReactive from './defineReactive.js'
import Observer from './Observer.js'
import observe from './observe.js'


var obj = {
    a:{
        m:{
            n:5
        }
    },
    b:10,
    c:{
        d:{
            e:{
                f:6666
            }
        }
    },
    g:[22,33,12,44,55]
};

observe(obj);
//对象测试
console.log(obj.a.m.n);
obj.a.m.n = 88;
console.log(obj.a.m.n);

 

Vue源码解析系列课程--Vue数据响应式原理笔记(对象、数组)_第2张图片

 

三、数组的响应式处理:

以Array.prototype为原型创建了一个arrayMethods对象,此时通过setPrototypeOf使新数组的隐式原型指向arrayMethods

Vue源码解析系列课程--Vue数据响应式原理笔记(对象、数组)_第3张图片

改写数组的七种方法:这七中方法都在Array的原型上(Array.prototype

push

pop

shift

unshift

splice

sort

resever

1array.js函数

  • 得到Array.prototype
  • 将对象arrayMethods的隐式原型指向Array.prototype ( Object.create(arrayPrototype)
  • 将数组的隐式原型指向对象arrayMethods (通过 def() )

    //定义新的方法
    def(arrayMethods,methodName ,function(){
        console.log("");
    },false);

  • 此时只是调用没有添加上值,所以我们要在def里面调用原来的方法。( original.apply(this,arguments)
  • 以Array.prototype为原型创建了一个arrayMethods对象,此时通过setPrototypeOf使数组的隐式原型指向arrayMethods
import {def} from './utils.js'

//得到Array.prototype
const arrayPrototype = Array.ptototype;

//以Array.prototype为原型,创建了一个ArrayMethods对象,并暴露
export const arrayMethods = Object.create(arrayPrototype);

//要被改写的七个数组方法
const methodsNeedChange = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
]

methodsNeedChange.forEach(methodName => {
    //备份原来的
    const original = arrayPrototype[methodName];
    //定义新的方法
    def(arrayMethods,methodName ,function(){
        console.log("123");
        original.apply(this,arguments);
    },false);

})

 

2Observer.js类

  • 要加入数据的判断,如果是对象则执行walk方法,如果是数组则强制改变其原型,将其原型改到arrayMethods上。
  • 如果是数组那么数组的元素也要遍历并observe
/*
* 功能:将一个正常的object转换为每个层级的属性都是响应式
* (可以被侦测的object)
* */

import {def} from './utils.js'
import defineReactive from './defineReactive'
import {arrayMethods} from './array.js'
import observe from "./observe";
import Dep from './Dep.js'
import Watcher from './Watcher.js'


//一个类如何被实例化
export default class Observer{
    //构造器
    constructor(value){

        //给实例(this,一定要注意,构造函数中的this不是表示类本身,而是表示实例)
        //添加了__ob__属性,值是这次new的实例
        def(value,'__ob__',this,false);
        /*
         *这里value是传过来的obj
         * '__ob__'是传过去的key
         * this,是传过去的value
         * false是不可枚举的
         * */

        console.log(value);

        //检查是数组还是对象
        if(Array.isArray(value)){
            /*如果是数组,要将数组的原型指向arrayMethods*/
            Object.setPrototypeOf(value,arrayMethods);
        //    让这个数组变的observe
            this.observeArray(value);
        }else{
            this.walk(value);
        }
    }

//    对象遍历
    walk(value){

       // 嵌套的子属性在此处遍历

       for(let k in value){
            defineReactive(value,k);
       }
    }

//    数组的特殊遍历
    observeArray(arr){
        for(let i= 0,l = arr.length;i

3. push、unshift、splice这三种方法比较特殊(完善array.js函数):

这三种方法可以插入新项,需要将插入的新项也变为observe

  • 首先要有返回值。
  • 要把类数组转换为数组。
  • switch里面判断插入的新项,并将新项添加observe也变为响应式。
import {def} from './utils.js'
/*
* push、pop、shift、unshift、splice、sort、reverse
* 这7中方法被改写了
* **/
//Vue当中数组的响应式是如何实现的?
/*
* 它实际上是以Array.prototype为原型,
* 创建了一个ArrayMethods对象,此时利用Es6中的setPrototypeOf方法
* 来强制使数组的隐式原型指向ArrayMethods
*
*
*
* **/
//得到Array.prototype
const arrayPrototype = Array.prototype;


//以Array.prototype为原型,创建了一个ArrayMethods对象,并暴露
export const arrayMethods = Object.create(arrayPrototype);

//要被改写的七个数组方法
const methodsNeedChange = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
];
methodsNeedChange.forEach(methodName => {
//    备份原来的方法
    const original = arrayPrototype[methodName];

//    定义新的方法
    def(arrayMethods,methodName,function(){

        //    恢复原来的功能,定义返回值
        const result = original.apply(this,arguments);

        //把类数组对象变为数组
        const args = [...arguments];

        //把这个数组身上的__ob__取出来,__ob__已经被添加了,为什么被添加了?因为数组肯定不是最高层,比如obj.g
//属性是数组,obj不能是数组,第一次遍历obj这个对象的第一层的时候,已经给g属性(就是这个数组)添加了
// __ob__属性
        const ob = this.__ob__;

//    有三种方法 push、unshift、splice能够插入新项,现在要把插入的新项也要变为observe
        let inserted = [];
        switch(methodName){
            case 'push':
            case 'unshift':
                inserted = arguments;
                break;
            case 'splice':
                //        splice格式是splice(下标,数量,插入的新项)
                inserted = arguments.splice(2);
                break;
        }

//    判断有没有要插入的新项,让新项也变为响应的
        if(inserted){
            //ob的原型上有observerArray方法
            ob.observeArray(inserted);
        }
        return result;
    },false);

});

4. 数组测试:

index.js(入口文件夹):

import defineReactive from './defineReactive.js'
import Observer from './Observer.js'
import observe from './observe.js'
import Watcher from './Watcher.js'

var obj = {
    a:{
        m:{
            n:5
        }
    },
    b:10,
    c:{
        d:{
            e:{
                f:6666
            }
        }
    },
    g:[22,33,12,44,55]
};

observe(obj);
//对象测试
// console.log(obj.a.m.n);
// obj.a.m.n = 88;
// console.log(obj.a.m.n);
//数组测试
obj.g.push(11);
console.log(obj.g);

// new Watcher(obj,'a.m.n',(val)=>{
//     console.log('!');
// });
// obj.a.m.n = 88;
// console.log(obj);

Vue源码解析系列课程--Vue数据响应式原理笔记(对象、数组)_第4张图片

注:资料学习《Vue尚硅谷源码解析系列课程》

代码地址:https://gitee.com/c-fff/DataResponsive 

你可能感兴趣的:(web前端,#,Vue)