Vue响应式原理深入分析

yizhihongxing

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日

相关文章

  • Vue脚手架搭建及创建Vue项目流程的详细教程

    下面是关于Vue脚手架搭建及创建Vue项目的详细教程攻略: 1. 什么是Vue脚手架? Vue脚手架是Vue.js的官方脚手架工具,提供了快速搭建Vue.js开发环境的方法,包含了常用的插件和构建工具,方便开发者快速地进行Vue项目的开发与调试。 2. Vue脚手架搭建 2.1 环境准备 Vue脚手架需要依赖Node.js和npm包管理器,因此需要先安装No…

    Vue 2023年5月27日
    00
  • vuejs实现ready函数加载完之后执行某个函数的方法

    Vue.js是一种流行的JavaScript框架,用于构建高度可交互的页面。实现在Vue.js的ready函数加载完之后执行某个函数的方法比较简单,以下是详细攻略: 在Vue.js的实例化中定义“mounted”生命周期钩子函数。这个函数会在Vue.js的实例化加载到DOM之后立即执行,因此是最佳场所来执行要在Vue.js的ready函数之后执行的函数。例如…

    Vue 2023年5月28日
    00
  • 解决Echarts 显示隐藏后宽度高度变小的问题

    针对Echarts显示隐藏后宽度高度变小的问题,解决方法如下: 问题分析 Echarts在隐藏和显示时,并没有对宽度和高度进行重新计算,导致当图表重新显示时,图表大小会变小,显示不完整的问题。 解决方法 可以手动计算图表容器的宽度和高度,并调用Echarts的resize方法实现图表大小的更新。 步骤 具体步骤如下: 获取图表容器的宽度和高度 var cha…

    Vue 2023年5月28日
    00
  • Vue组件更新数据v-model不生效的解决

    好的。在Vue组件中,如果v-model绑定的值没有随着组件数据的更新而更新,很可能是因为v-model绑定的值没有遵循单向数据流的规则,即修改了组件外部数据的值,而没有通过emit方法通知组件。下面是详细的攻略: 1. 检查v-model中绑定的值 首先需要确认v-model中绑定的值,它是一个props属性,还是组件内部的data数据。如果v-model…

    Vue 2023年5月27日
    00
  • vue中的dom节点和window对象

    Vue中的DOM节点和Window对象 在Vue的开发过程中,我们会频繁地涉及到操作DOM节点或者Window对象。因此,了解这两个概念是非常重要的。 DOM节点 DOM(Document Object Model)是指文档对象模型,是一种表示和操作HTML, XHTML和XML文档的标准编程接口。DOM节点是文档对象的基本组成部分,通俗地说就是HTML页面…

    Vue 2023年5月28日
    00
  • vue配置多页面的实现方法

    关于Vue配置多页面的实现方法,下面是一个完整的攻略。 1. 安装依赖 在开始前,需要安装vue-loader和vue-template-compiler这两个依赖。 npm install vue-loader vue-template-compiler –save-dev 2. 配置webpack 在webpack配置文件中,需要做如下修改。 在ent…

    Vue 2023年5月27日
    00
  • 在vue中axios设置timeout超时的操作

    当使用axios在Vue中进行数据请求时,可能会遇到服务器响应非常缓慢或者出现网络问题等情况,由此导致前端请求一直在等待响应,造成用户体验不佳。为了解决这类问题,我们可以通过设置axios的timeout超时时间来规定前端在等待响应的最大时间,如果超过这个时间则取消请求,并且返回一个错误提示。 下面是设置axios timeout的完整攻略和两条示例说明: …

    Vue 2023年5月29日
    00
  • vue中常见的问题及解决方法总结(推荐)

    Vue中常见问题及解决方法总结 1. Vue中常见问题 1.1. Vue组件之间通信 在Vue中,通信是组件之间的一个重要问题。通信包括父子组件之间的通信、兄弟组件之间的通信,还有隔代组件之间的通信等。通信方式有很多种,包括props/$emit、$parent/$children、事件总线、Vuex等。 1.1.1. Props/$emit Props/$…

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