Vue 源码(一):响应式原理

栏目: JavaScript · 发布时间: 5年前

内容简介:本文只做简单介绍,结合代码食用更佳:效果预览:github pagesdefineProperty 让我们可以劫持某个属性的 getter 和 setter,举个例子:

本文只做简单介绍,结合代码食用更佳: github/vue-learn-source-code

效果预览:github pages

Object.defineProperty

defineProperty 让我们可以劫持某个属性的 getter 和 setter,举个例子:

var person = {
    firstName: 'meimei',
    lastName: 'han'
};
Object.defineProperty(person, 'fullName', {
    get() {
        return this.lastName + ' ' + this.firstName;
    },
    set(val) {
        let arr = val.split(' ');
        this.lastName = arr[0];
        this.firstName = arr[1];
    }
});
复制代码

劫持 fullName 后,改变 firstName 或 lastName 会更新 fullName,反之亦然

目标

本文的目标是仿造 vue 实现改变数据后更新 dom,让以下代码能够 work:

<div id="app">
    <p>firstName: {{firstName}}</p>
    <p>lastName: {{lastName}}</p>
    <p>fullName: {{fullName}}</p>
</div>
<script src="./vue.js"></script>
<script>
    let vm = new Vue({
        el: '#app',
        data() {
            return {
                firstName: 'meimei',
                lastName: 'han'
            };
        },
        computed: {
            fullName: {
                get: function() {
                    return this.lastName + ' ' + this.firstName;
                },
                set: function(val) {
                    let arr = val.split(' ');
                    this.lastName = arr[0];
                    this.firstName = arr[1];
                }
            }
        }
    });
</script>
复制代码

观察者模式

我们要做的是数据变化后去更新 dom,观察者模式很适合

数据只需要收集依赖,当数据变化通知依赖更新即可,先建一个类描述这件事:

class Dep {
    constructor() {
        this.subs=[]
    }
    addSub(item) {
        this.subs.push(item);
    }
    notify() {
        this.subs.forEach(item => {
            item.update();
        });
    }
}
复制代码

再细想一下,dom 依赖 data,则在获取 dom 的过程中需要用到 data 的 get,在 data get 时收集依赖即可,set data 时执行 dom 的 update

get data 时需要记录依赖 data 的数据,给 class Dep 增加一个属性 target 作为记录工具,结合 defineProperty 实现如下:

Dep.target = undefined;
function defineReactive(obj, key) {
    let dep = new Dep();
    let val = obj[key];
    Object.defineProperty(obj, key, {
        get: function() {
            if (Dep.target) {
                // get 中收集依赖
                dep.addSub(Dep.target);
            }
            return val;
        },
        set: function(value) {
            val = value;
            // set 中触发更新
            dep.notify();
        }
    })
}
复制代码

工具已备齐,接下来就是遍历 data 的属性,用 defineReactive 走一遍

data, computed, dom 的依赖关系

解析 dom 会用到 data 和 computed,computed 的 get 会用到 data

  1. 遍历 data
function initData(vm) {
    let data = vm.$options.data;
    data = typeof data === 'function' ? data() : data;
    Object.keys(data).forEach(key => {
        defineReactive(data, key);
    });
    // 把 data 的属性代理到 vm 实例
    proxy(data, vm);
}
复制代码
  1. 遍历 computed
function initComputed(vm) {
    let computed = vm.$options.computed;
    let defaultSetter = function(key) {
        console.error(this, ' has no setter for ', key)
    }
    Object.keys(computed).forEach(key => {
        let getter = typeof computed[key] === 'function' ? computed[key] : computed[key].get;
        let setter = typeof computed[key] === 'function' ? defaultSetter.bind(computed) : computed[key].set;
        Object.defineProperty(computed, key, {
            get: getter.bind(vm),
            set: setter.bind(vm)
        })
    })
    // 把 computed 的属性代理到 vm 实例
    proxy(computed, vm);
}
复制代码
  1. 解析 dom
function mount(vm) {
    let update = compile(vm);
    let watcher = new Watcher(update);
    // 把 target 标为 dom
    Dep.target = watcher;
    update();
    Dep.target = undefined;
}

function compile(vm) {
    let el = vm.$options.el;
    el = document.querySelector(el);
    vm.$el = el;
    let innerHTML = el.innerHTML;
    let getter = function() {
        return innerHTML.replace(/{{(.*?)}}/g, function() {
            // 这里用到了 data computed 的 get,收集了依赖
            return vm[arguments[1]]
        });
    };
    let update = function() {
        let iHTML = getter();
        el.innerHTML = iHTML;
    }
    return update;
}
复制代码

多说一句

在收集依赖时,我们给 Dep 这个 class 增加一个属性 target,在 vue 中还结合了 targetStack。这种收集方式稍微管理不慎就可能存在 bug,在另一篇文章有提过: 熟悉 Vue ?你能解释这个死循环吗?

为自己的填坑喝彩~


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

代码里的世界观——通往架构师之路

代码里的世界观——通往架构师之路

余叶 / 人民邮电出版社 / 2018-11 / 59.00元

本书分为两大部分,第一部分讲述程序员在编写程序和组织代码时遇到的很多通用概念和共同问题,比如程序里的基本元素,如何面向对象,如何面向抽象编程,什么是耦合,如何进行单元测试等。第二部分讲述程序员在编写代码时都会遇到的思考和选择,比如程序员的两种工作模式,如何坚持技术成长,程序员的组织生产方法,程序员的职业生涯规划等。一起来看看 《代码里的世界观——通往架构师之路》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具