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日

相关文章

  • Delegate IDE build/run actions to maven 配置会影响程序运行吗?

    “Delegate IDE build/run actions to maven”这个选项是IntelliJ IDEA中的一个功能,意味着让IDEA通过Maven来进行项目构建、测试以及运行。在Maven的配置文件中,可以进行配置来指定构建、测试和运行项目时所需的依赖和其他配置。 配置会影响程序的构建过程 选择”Delegate IDE build/run …

    Vue 2023年5月28日
    00
  • 浅谈Vue.set实际上是什么

    浅谈Vue.set实际上是什么 在Vue.js中,我们通常使用双向数据绑定的方式更新视图,但是,在某些情况下,我们需要手动更改对象或数组的元素来更新视图,此时就需要用到Vue.set方法。本文将详细讲解Vue.set的实际用法和含义,帮助您更好地了解Vue.js的数据绑定机制。 Vue.set的作用 Vue.set是Vue.js框架中用来改变被Vue.js监…

    Vue 2023年5月29日
    00
  • 详解从vue-loader源码分析CSS Scoped的实现

    标题:详解从vue-loader源码分析CSS Scoped的实现 文章内容: 简介 在Vue项目中,使用作用域CSS是非常常见的需求。Vue.js官方提供了一个vue-loader插件,可以帮助我们快捷实现CSS作用域。本文将详细讲解vue-loader源码分析CSS Scoped的实现过程。 CSS Scoped实现原理 CSS Scoped即为CSS作…

    Vue 2023年5月27日
    00
  • vue实现横向时间轴组件方式

    下面是关于如何使用Vue实现横向时间轴组件的详细攻略: 1. 确定组件的结构和样式 根据需求确定时间轴的结构和样式,例如需要横向展示一段时间内的事件,每个事件分为左和右两侧,左侧显示具体时间,右侧显示事件内容。横向时间轴通常需要使用CSS flexbox和grid等布局技术来实现。 2. 使用Vue创建组件 可以使用Vue的单文件组件(SFC)或render…

    Vue 2023年5月29日
    00
  • VSCode创建Vue项目的完整步骤教程

    下面是创建Vue项目的完整步骤教程: 准备工作 首先,你需要安装一些软件,包括: Node.js(可以在官网上下载安装包) Visual Studio Code(可以在官网上下载安装包) 安装好Node.js后,你可以在命令行界面输入以下命令,查看其版本号,以确认是否安装成功: node -v 安装好Visual Studio Code后,你需要安装Vue.…

    Vue 2023年5月27日
    00
  • Vue过滤器(filter)实现及应用场景详解

    Vue过滤器(filter)是一种用于格式化或转换数据的技术,它可以在视图中简化数据的渲染过程,并且提高代码的可读性和可维护性。在本文中,我们将讲解如何实现Vue过滤器,以及它们在不同场景中的应用。 1. Vue过滤器的实现 1.1 基本语法 Vue过滤器是一个全局的函数,可以在模板中通过管道符号 (|) 使用。下面是Vue过滤器的基本语法: Vue.fil…

    Vue 2023年5月27日
    00
  • vue-resource 获取本地json数据404问题的解决

    Vue-resource 是Vue.js官方提供的一个数据请求插件,可以方便地进行数据的请求和响应。在使用vue-resource时,经常会遇到获取本地json数据时出现404错误的问题,接下来我将详细讲解如何解决该问题。 问题描述 使用vue-resource请求本地json数据时,页面在访问数据时会报404错误,无法正常获取数据。 解决方案 步骤一:使用…

    Vue 2023年5月27日
    00
  • Vue3插槽Slot实现原理详解

    下面我将为你详细讲解“Vue3插槽Slot实现原理详解”的完整攻略。 什么是插槽(Slot) 在Vue开发中,有时候我们需要在父组件中定义子组件的模板结构,但是子组件的内容是不确定的。这种情况下,我们可以使用插槽(Slot)来解决问题。 插槽允许我们定义一个承载子组件内容的挂载点,然后在子组件中使用具名插槽(Named Slot)或默认插槽(Default …

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