vue2响应式原理解析

文章详细阐述了Vue.js中数据响应式的工作原理,包括`defineReactive`函数用于创建响应式数据,`Observer`类遍历并转化对象属性为响应式,以及`Watcher`类负责依赖收集和更新。同时,文章提到了数组的特殊处理,如何通过重写数组方法实现响应式,并介绍了Dep类在依赖管理和通知更新中的作用。
摘要由CSDN通过智能技术生成

源码:点击跳转

认识defineReactive 数据->响应式

let aa = { a: 1 };

// defineReactive 形成闭包环境 
function defineReactive(data, key, value) {
    // value 闭包
    Object.defineProperty(data, key, {
        // value: 2, 
        get() {
            console.log("访问了2");
            return value;
        },
        set(newValue) {
            console.log("设置新值");
            value = newValue;
        }
    })
}

// 在这里对当前的对象属性  进行一个初始化  将其设置为响历式数据
defineReactive(aa, "b", 2)

console.log(aa.b); //打印: 访问了2 

这样的话, 只能检测到对象中的第一层属性. 下面继续

observe (obj) 将obj所有属性彻底变为响应式

这里设计到observe函数 Observer类 defineReactive函数
手绘关系图:

在这里插入图片描述

observe函数


import Observer from "./Observer";

/**
 * 将对象所有属性变为响应式
 * @param {object} obj 要监测的对象
 * @returns {void}
 */
export default function observe(obj) {
    // 如果obj不是对象 什么都不做
    if (typeof obj !== 'object' || obj === null) {
        return;
    }

    let ob;
    // obj身上有没有__ob__属性(observer类的实例)
    // 有的话就赋值给ob
    if (typeof obj.__ob__ !== 'undefined') {
        ob = obj.__ob__;
    }
    // 没有new一个Observer实例 赋值给ob
    else {
        ob = new Observer(obj);
    }
    return ob;
}

在这里, 首先检查对象有没有__ob__属性, 第一次肯定是没有了,
直接new一个Observer实例,赋值给ob返回

Observer实例是什么 ?做了什么?
在里面将对象每一级都转换为响应式(通过defineReactive)
在里面给当前对象通过Object.defineProperty 将__ob__属性添加到当前属性上, (def函数) , 然后调用walk函数,将当前属性的每一个子级属性传入defineReactive()

Observer类

import { def } from "./utils"
import defineReactive from "./defineReactive"

/**
 * 将一个正常的obj 转换为每个层级的属性都是响应式的obj
 */
export default class Observer {
    /**
     * 将对象传进来,返回一个每个层级都被设置setter和getter的对象
     * @param {object} value 要劫持属性的对象
     */
    constructor(value) {
        console.log("传进来的对象是:\n", value);
        // 给传进来的value添加__ob__属性  
        // 构造函数中的this  -->  是实例
        def(value, '__ob__', this, false);
        // 给实例添加了__ob__属性, 值是this(new的结果)
        this.walk(value)
    }
    /**
     * 遍历属性中的obj
     * @param {object} value 进行遍历的obj
     */
    walk(value) {
        for (let k in value) {
            defineReactive(value, k);
        }

    }
}

defineReactive函数

import observe from "./observe";

// defineReactive 形成闭包环境 
/**
 * 将传进来的obj上的第一层属性, 添加getter和setter
 * @author 李哈哈 <lwb010522@126.com>
 * @param {Object} data 要变为响应式的对象
 * @param {string} key 对象的属性
 * @param {any|undefined} value 属性的值(不传即为key值)
 * @returns {void}
 */
export default function defineReactive(data, key, value) {
    // 如果只传进来了2个参数 说明是子属性
    if (arguments.length === 2) {
        value = data[key];
    }
    // 因为在Observer的walk中  会传进来obj的后代属性  所以这些后代属性也要监听
    let childObj = observe(value);
    // value 闭包
    Object.defineProperty(data, key, {
        get() {
            console.log("访问了", value);
            return value;
        },
        set(newValue) {
            console.log("设置新值", newValue);
            value = newValue;
            // 设置新值的时候 还要再监听 因为设置新值也可能被设置为了对象
            childObj = observe(value);
        }
    })
}

数组的响应式处理

在这里插入图片描述
在这里插入图片描述

Object.setPrototypeOf(o,ArrayMethods)

定义某个对象的原型是ArrayMethods
就等于 : o.__proto__=ArrayMethods

对象属性是数组

专门创建一个对象, 将数组的prototype指向这个新对象
对这个新对象的处理如下:

import { def } from './utils'

// 得到Array的prototype
const arrayPrototype = Array.prototype;
// 然后以Array的prototype为原型 创建一个对象arrayMethods  然后数组的原型改到这个创建的对象上

// 这个对象要暴露出去  在Observer中 用来将对象的原型指向此对象
export const arrayMethods = Object.create(arrayPrototype);



const methodsNeedChange = [
    "push",
    "pop",
    "shift",
    "unshift",
    "splice",
    "sort",
    "resolve"
]

// 遍历 重写方法
methodsNeedChange.forEach(method => {
    // 先备份原来的方法
    const original = arrayMethods[method];
    // 再重写方法 
    // 给这个对象 通过definePrototype添加属性
    // 添加的属性为上面那7个方法
    def(arrayMethods, method, function () {
        // 这里确保方法执行有原本的功能
        original.apply(this, arguments);
    }, false)
})

此时的Observer类


    constructor(obj) {
        console.log("传进来的对象是:\n", obj);
        // 给传进来的obj添加__ob__属性  
        // 构造函数中的this  -->  是实例
        def(obj, '__ob__', this, false);
        // 给实例添加了__ob__属性, 值是this(new的结果)
        if (Array.isArray(obj)) {
            // 属性是数组,需要单独处理 将数组原型 指向自己定义的对象arrayMethods
            Object.setPrototypeOf(obj, arrayMethods);
            console.log("数组原型已经被修改为了:", arrayMethods);
        } else {
            this.walk(obj)
        }
    }

到此 实现通过自定义的数组的原型, 实现了调用数组7个方法, 走的是自己定义的原型中的方法.
但是,新增元素依然不是响应式

对数组进一步处理

数组中的3个方法 push unshift splice 是插入数据, 所以插入的数据也有可能是数组, 所以,这3个方法,要单独处理一下,

其中,pushunshift一样,都是直接插入, 直接整合,得到插入的数据, 传入observe()就好.

splice不一样—> splice(开始位置, 删除长度, 插入的值),要得到arguments中的slice(2) 序号为2的参数 才是插入的值, 将得到的插入值传入observe()

此时的数组处理方法

import { def } from './utils'

// 得到Array的prototype
const arrayPrototype = Array.prototype;
// 然后以Array的prototype为原型 创建一个对象arrayMethods  然后数组的原型改到这个创建的对象上
// 这个对象要暴露出去  在Observer中 用来将对象的原型指向此对象
export const arrayMethods = Object.create(arrayPrototype);

const methodsNeedChange = [
    "push",
    "pop",
    "shift",
    "unshift",
    "splice",
    "sort",
    "reverse"
]

// 遍历 重写方法
methodsNeedChange.forEach(method => {
    // 先备份原来的方法
    const original = arrayMethods[method];
    // 再重写方法 
    // 给这个对象 通过definePrototype添加属性
    // 添加的属性为上面那7个方法
    def(arrayMethods, method, function () {
        // 把数组上的ob(Observer实例)拿出来
        const ob = this.__ob__;
        // 因为数组方法中有3个方法可以插入新项 要确保插入的新项也要被监测到
        let inserted = [];
        switch (method) {
            case "push":
                inserted = arguments;
                console.log("监测到push方法插入元素~");
                break;
            case "unshift":
                inserted = arguments;
                console.log("监测到unshift方法插入元素~");
                break;
            case "splice":
                // splice(下标,数量,插入值)
                inserted = Array.from(arguments).slice(2);
                console.log("监测到splice方法插入元素~");
                break;
            case "pop":
                console.log("监测到pop方法移除元素~");
                break;
            case "shift":
                console.log("监测到shift方法移除元素~");
                break;
            case "sort":
                console.log("监测到sort方法排序元素~");
                break;
            case "reverse":
                console.log("监测到reverse方法反转元素~");
                break;
        }

        // 判断有没有要插入的新值 让新项也变为响应的
        if (inserted) {
            ob.observerArray(inserted);
        }
        // 这里确保方法执行有原本的功能
        let res = original.apply(this, arguments);
        console.log(res);
        return res;
    }, false)
})

此时的Observer类

增加了对数组元素的处理

import { def } from "./utils"
import defineReactive from "./defineReactive"
import { arrayMethods } from "./array"
import observe from "./observe";

/**
 * 将一个正常的obj 转换为每个层级的属性都是响应式的obj
 */
export default class Observer {
    /**
     * 将对象传进来,返回一个每个层级都被设置setter和getter的对象
     * @param {object} obj 要劫持属性的对象
     */
    constructor(obj) {
        console.log("传进来的对象是:\n", obj);
        // 给传进来的obj添加__ob__属性  
        // 构造函数中的this  -->  是实例
        def(obj, '__ob__', this, false);
        // 给实例添加了__ob__属性, 值是this(new的结果)
        if (Array.isArray(obj)) {
            // 属性是数组,需要单独处理 将数组原型 指向自己定义的对象arrayMethods
            Object.setPrototypeOf(obj, arrayMethods);
            // console.log("数组原型已经被修改为了:", arrayMethods);
            // 执行数组的observer
            this.observerArray(obj);
        } else {
            this.walk(obj)
        }
    }
    /**
     * 遍历属性中的obj
     * @param {object} obj 进行遍历的obj
     */
    walk(obj) {
        for (let k in obj) {
            defineReactive(obj, k);
        }

    }
    observerArray(arr) {
        for (let i = 0; i < arr.length; i++) {
            // 数组每一项都要被监听到
            observe(arr[i]);
        }
    }
}

数据劫持原理 流程图

在这里插入图片描述

双向绑定

在getter中收集依赖,
在setter中触发依赖

在这里插入图片描述
在这里插入图片描述

Dep类

let uid = 0;
// 完成依赖收集
export default class Dep {
    constructor() {
        // console.log("**********************************依赖");
        // 用数组来存储自己的订阅者(watcher的实例)
        this.subs = [];
    }


    /**
     * 添加订阅者到订阅者数组
     * @param {Object} sub watcher实例
     */
    addSub(sub) {
        this.id = uid++;
        this.subs.push(sub);
    }


    /**
     * 添加依赖  如果有订阅者实例需要被添加到订阅者数组   (触发添加订阅者数组方法)
     */
    depend() {
        // Dep.target是一个全局变量, 里面存放的是Watcher实例,
        if (Dep.target) {
            // 有watcher实例就push到订阅者数组里面
            this.addSub(Dep.target);
        }
    }


    /**
     * 通知订阅者更新
     */
    notify() {
        console.log("收到了更新通知~~~");
        // 浅拷贝一份
        let subs = this.subs.slice();
        // 遍历,执行update方法
        for (let i = 0, l = subs.length; i < l; i++) {
            subs[i].update();
        }
    }
}

Watcher类

import Dep from "./Dep";

let uid;
export default class Watcher {

    constructor(target, expression, callback) {
        // watcher 构造的时候, 就要传进来 
        // 要监听哪个对象(target)  
        // 监听对象中的哪个属性(expression)
        this.id = uid++;
        this.target = target;
        this.getter = parsePath(expression);//getter就是一个获得器  因为得到了一个函数
        this.callback = callback;
        this.value = this.get();

    }
    update() {
        // 更新视图
        console.log("更新视图!!!!!!!!!!!!!更新视图!!!!!!!!!!!更新视图!!!!!!!!!!!!!!更新视图!!!!!!!!!!!!更新视图!!!!!!!!");
        this.run();
    }
    get() {
        // 进入依赖收集阶段
        // 这样 全局变量就有了watcher实例作为值 可以触发Dep的depend()方法,然后通过addSubs方法push进入订阅者数组中
        Dep.target = this;
        const obj = this.target;
        let value;
        try {
            value = this.getter(obj) //通过执行器函数拿到最终得到的值所要监听的属性值
        } catch {

        } finally {
            // 收集完毕 就把这个变量置为null
            Dep.target = null;
        }

        return value;
    }
    run() {
        // update一执行  调用这个方法
        this.getAndInvoke(this.callback);
    }
    // 唤起回调函数
    getAndInvoke(cb) {
        // 首先调用get得到监听的值
        const value = this.get();
        // 判断这个值是否发生了变化
        if (value !== this.value || typeof value === 'object') {
            // 保存并替换旧值 
            const oldValue = this.value;
            this.value = value;

            // 执行回调 并且将旧值和新值传入
            cb.call(this.target, value, oldValue);
        };
    }
}

/**
 * 将打点形式的字符串 如:a.b.c  改为打点运算符
 * @param {string} str 要监听哪个属性 a.b.c
 * @returns {function} 返回可以读取a.b.c属性的函数
 */
function parsePath(str) {
    let segment = str.split('.');
    return (obj) => {
        for (let i = 0; i < segment.length; i++) {
            if (!obj) return;
            obj = obj[segment[i]];
        }
        return obj;
    }
}

收集触发依赖的getter和setter

import Dep from "./Dep";
import observe from "./observe";

// defineReactive 形成闭包环境 
/**
 * 将传进来的obj上的第一层属性, 添加getter和setter
 * @author 李哈哈 <lwb010522@126.com>
 * @param {Object} data 要变为响应式的对象
 * @param {string} key 对象的属性
 * @param {any|undefined} value 属性的值(不传即为key值)
 * @returns {void}
 */
export default function defineReactive(data, key, value) {
    const dep = new Dep()  //这里的dep是给闭包内使用的dep 因为要使用它上面的notify方法
    // 如果只传进来了2个参数
    if (arguments.length === 2) {
        value = data[key];
    }
    // 因为在Observer的walk中  会传进来obj的后代属性  所以这些后代属性也要监听
    let childObj = observe(value);
    // value 闭包
    Object.defineProperty(data, key, {
        get() {
            console.log("访问了", value);
            // 如果现在处于依赖的收集阶段
            if (Dep.target) {
                // 就触发收集依赖  将订阅者加入数组
                dep.depend();
                // 如果子元素存在, 子元素也需要收集
                if (childObj) {
                    childObj.dep.depend();
                }
            }
            return value;
        },
        set(newValue) {
            console.log("设置新值", newValue);
            value = newValue;
            // 设置新值的时候 还要再监听 因为设置新值也可能被设置为了对象
            childObj = observe(value);

            // 发布订阅模式 通知dep
            dep.notify();
        }
    })
}

使用watcher 数据改变时页面更新

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值