Vue响应式原理深入分析

Vue响应式原理深入分析

Vue.js是一个流行的JavaScript框架,它的核心包括Vue.js库和Vue.js运行时,能够让我们构建用户交互的Web应用程序。Vue.js的根本原理就是响应式,下面将详细讲解Vue响应式的原理及其实现方式。

Vue响应式的原理

Vue.js的响应式原理是基于ES5中的Object.defineProperty()方法(ES6中的Proxy也可以实现)。当通过Vue.js的$set()方法或者直接对Vue.js的data属性进行修改时,Vue.js会触发一系列的响应式过程,即首先会检测当前修改的属性是否已经存在,如果存在,则会直接更新属性的值;否则会通过Observer类将新增的属性进行响应式化处理,然后再将新增的属性添加到响应式系统中。

Observer类是Vue.js实现响应式的核心类,其作用是将对象转化为响应式对象,即在对象的属性值发生变化时能够自动更新。Observer类主要通过递归遍历对象上的属性,将对象的每个属性都转化成响应式对象。同时,Observer类中结合Dep类进行订阅发布操作,即在属性值发生变化后自动通知相关的订阅者进行更新。

Dep类是Vue.js实现双向绑定的核心类,其作用是建立属性与Watcher之间的关系,即当属性发生变化时,自动通知Watcher进行更新。Dep类主要通过添加观察者(即Watcher对象)实现,当属性值发生变化时,Dep会通知添加到Dep中的所有Watcher对象进行更新。

Watcher类是Vue.js实现响应式的关键类,其作用是建立Dep与更新UI的关系,即将Dep中的更新操作同步到UI上。Watcher类主要通过调用update()方法进行更新。

Vue响应式的实现方式

Vue.js的响应式实现方式与其它响应式框架的实现方式有所不同,在Vue.js中采用了Observer、Dep和Watcher三个类相互协作的方式实现。

示例一

下面是一个简单的Vue.js响应式实现的示例,主要通过Object.defineProperty()方法实现Vue.js的响应式。

let data = { name: "Lena", age: 20 };
observe(data);

function observe(obj) {
    if (!obj || typeof obj !== "object") {
        return;
    }

    Object.keys(obj).forEach(key => {
        defineReactive(obj, key, obj[key]);
    });
}

function defineReactive(obj, key, val) {
    let dep = new Dep();

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            if (Dep.target) {
                dep.depend();
            }
            return val;
        },
        set: function reactiveSetter(newVal) {
            if (newVal === val) {
                return;
            }
            val = newVal;
            dep.notify();
        }
    });
}

class Dep {
    constructor() {
        this.subs = [];
    }

    addSub(sub) {
        this.subs.push(sub);
    }

    depend() {
        if (Dep.target) {
            Dep.target.addDep(this);
        }
    }

    notify() {
        this.subs.forEach(sub => {
            sub.update();
        });
    }
}

Dep.target = null;

class Watcher {
    constructor(vm, expOrFn, cb) {
        this.vm = vm;
        this.getter = parsePath(expOrFn);
        this.cb = cb;
        this.value = this.get();
    }

    get() {
        Dep.target = this;
        let value = this.getter.call(this.vm);
        Dep.target = null;
        return value;
    }

    update() {
        const oldValue = this.value;
        this.value = this.get();
        this.cb.call(this.vm, this.value, oldValue);
    }

    addDep(dep) {
        dep.addSub(this);
    }
}

function parsePath(exp) {
    const segments = exp.split('.');
    return function getter(obj) {
        for (let i = 0; i < segments.length; i++) {
            if (!obj) {
                return;
            }
            obj = obj[segments[i]];
        }
        return obj;
    }
}

let vm = { data };
new Watcher(vm, "data.name", function(val, oldVal) {
    console.log(`name: ${oldVal} -> ${val}`);
})
vm.data.name = "Amy";

示例二

下面是Vue.js的伪代码,展示了如何使用Observer、Dep和Watcher类实现对象属性的响应式操作。

class Observer {
    constructor(value) {
        this.value = value;
        this.dep = new Dep();
        def(value, "__ob__", this);
        if (Array.isArray(value)) {
            //...
        } else {
            this.walk(value);
        }
    }

    walk(obj) {
        const keys = Object.keys(obj);
        for (let i = 0; i < keys.length; i++) {
            defineReactive(obj, keys[i], obj[keys[i]]);
        }
    }
}

function defineReactive(obj, key, val) {
    const dep = new Dep();
    let childOb = observe(val);
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            if (Dep.target) {
                dep.depend();
                if (childOb) {
                    childOb.dep.depend();
                }
            }
            return val;
        },
        set: function reactiveSetter(newVal) {
            if (newVal === val || (newVal !== newVal && val !== val)) {
                return;
            }
            val = newVal;
            childOb = observe(newVal);
            dep.notify();
        }
    });
}

function observe(value) {
    if (!isObject(value)) {
        return;
    }
    let ob;
    if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) {
        ob = value.__ob__;
    } else {
        ob = new Observer(value);
    }
    return ob;
}

class Watcher {
    constructor(vm, expOrFn, cb, options, isRenderWatcher) {
        //...
        this.depIds = new Set();
        this.newDepIds = new Set();
        this.vm = vm;
        this.expression = expOrFn;
        this.cb = cb;
        this.value = this.get();
    }

    get() {
        //...
        Dep.target = this;
        let value = this.getter.call(this.vm, this.vm);
        Dep.target = null;
        return value;
    }

    addDep(dep) {
        const id = dep.id;
        if (!this.newDepIds.has(id)) {
            this.newDepIds.add(id);
            this.depIds.add(id);
            dep.addSub(this);
        }
    }

    update() {
        //...
        this.run();
    }

    run() {
        const value = this.get();
        const oldValue = this.value;
        this.value = value;
        this.cb.call(this.vm, value, oldValue);
    }
}

class Dep {
    constructor() {
        this.id = uid++;
        this.subs = [];
    }

    addSub(sub) {
        this.subs.push(sub);
    }

    removeSub(sub) {
        remove(this.subs, sub)
    }

    depend() {
        if (Dep.target) {
            Dep.target.addDep(this);
        }
    }

    notify() {
        const subs = this.subs.slice();
        for (let i = 0, l = subs.length; i < l; i++) {
            subs[i].update();
        }
    }
}

Dep.target = null;

function noop() {}

function makeMap(str) {
    const map = {};
    const list = str.split(",");
    for (let i = 0; i < list.length; i++) {
        map[list[i]] = true;
    }
    return val => !!map[val];
}

function remove(arr, item) {
    if (arr.length) {
        const index = arr.indexOf(item);
        if (index > -1) {
            return arr.splice(index, 1);
        }
    }
}

function isNative(Ctor) {
    return typeof Ctor === "function" && /native code/.test(Ctor.toString());
}

const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
    return hasOwnProperty.call(obj, key);
}

function isObject(obj) {
    return obj !== null && typeof obj === "object";
}

function def(obj, key, value, enumerable) {
    Object.defineProperty(obj, key, {
        value: value,
        enumerable: !!enumerable,
        writable: true,
        configurable: true
    });
}

总结

本文通过详细讲解Vue.js的响应式原理及其实现方式,包括Observer、Dep和Watcher三个核心类的作用及相互协作的关系。同时,通过两个示例,说明了如何使用Object.defineProperty()方法实现Vue.js的响应式操作,以及Vue.js伪代码中Observer、Dep和Watcher类的使用方式。对于Vue.js的深入了解,掌握其响应式原理是非常关键的,希望本文的介绍对大家有所帮助。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Vue响应式原理深入分析 - Python技术站

(0)
上一篇 2023年5月27日
下一篇 2023年5月27日

相关文章

  • Vite使用Esbuild提升性能详解

    Vite使用Esbuild提升性能详解 背景 Vite是一个轻量级的前端工具,它的设计初衷是为了让开发者更快地启动和构建项目,提供了一些开箱即用的特性和优化,例如快速启动、模块热更新等。其中,Vite的关键特性之一就是使用了类似于Snowpack的“零配置”的模式进行快速的开发体验。 然而,在一些情况下,Vite的构建速度还是无法满足一些开发者的需求。因此,…

    Vue 2023年5月28日
    00
  • 利用vue对比两组数据差异的可视化组件详解

    你好,下面是对“利用vue对比两组数据差异的可视化组件详解”的完整攻略的详细讲解: 利用vue对比两组数据差异的可视化组件详解 什么是数据差异可视化组件? 数据差异可视化组件可以让用户直观地看到两组数据之间的区别,通常用于比较历史数据和最新数据或两个数据集之间的差异。利用数据差异可视化组件,用户可以快速了解两组数据之间的变化情况,从而更好地进行决策和分析。 …

    Vue 2023年5月29日
    00
  • vue实现打卡功能

    下面是vue实现打卡功能的完整攻略。 1. 确定需求和功能点 在开始实现打卡功能之前,我们需要先明确需求和功能点。一般来说,一个打卡功能至少包含以下几个方面: 打卡地点的定位和显示 打卡时间的记录和展示 打卡成功/失败的反馈提示 打卡数据的保存和更新 根据实际业务需求,我们可以在此基础之上进行扩展和优化。 2. 实现地理位置定位 首先,我们需要实现打卡地点的…

    Vue 2023年5月27日
    00
  • 使用Vite2+Vue3渲染Markdown文档的方法实践

    使用Vite2+Vue3渲染Markdown文档的方法实践,可以按照以下步骤进行: 准备工作 安装Node.js,下载地址:https://nodejs.org/en/download/。 在终端中执行以下命令安装Vite:npm install -g vite。 创建一个新的Vue3项目:npm init vite@latest project-name …

    Vue 2023年5月28日
    00
  • Vue 项目的成功发布和部署的实现

    Vue 项目的成功发布和部署,可以分为以下几个步骤: 1. 准备工作 在开始部署之前,需要确保你的项目已经完成开发,并成功运行在本地环境中。同时需要确保已经安装必要的环境和工具,例如:Node.js、Git、npm 或 yarn。 2. 打包 Vue 项目 使用 npm run build 或 yarn build 命令来打包 Vue 项目并生成可部署的静态…

    Vue 2023年5月28日
    00
  • 解析vue data不可以使用箭头函数问题

    解析vue中的data不可以使用箭头函数问题,主要是因为箭头函数没有自己的上下文,而且 Vue 中传递给 data 的对象必须是可扩展的,以便在数据更新时进行响应。下面是该问题的解决攻略: 方法1:使用传统的函数 在Vue组件中,如果要解析data对象,应该在声明周期的created或mounted函数中使用传统的函数来定义data。如下所示: <te…

    Vue 2023年5月28日
    00
  • vue源码中的检测方法的实现

    Vue源码中的检测方法的实现使用的是JavaScript提供的Object.defineProperty()方法,它可以拦截对对象属性的访问和修改。Vue将此方法用于Vue实例的data对象和组件实例的props对象上,以便在属性值变化时可以感知到,并及时更新视图。 具体实现步骤如下: 实现一个观察者,用来监听对象的变化,当对象的某个属性发生变化时,观察者会…

    Vue 2023年5月27日
    00
  • Vue项目判断开发、测试、正式环境过程

    要在Vue项目中区分开发、测试和正式环境,我们通常需要在构建和打包阶段添加相应的标记,例如process.env.NODE_ENV可以告诉我们当前的环境变量。下面是一个完整的攻略,讲解了如何实现在Vue项目中进行环境标记,并根据标记执行不同的操作。 环境标记的配置 在Vue项目中,我们可以通过webpack中的DefinePlugin插件来定义环境变量。这个…

    Vue 2023年5月28日
    00
合作推广
合作推广
分享本页
返回顶部