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

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

内容简介:本文只做简单介绍,结合代码食用更佳:效果预览: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 ?你能解释这个死循环吗? 。

为自己的填坑喝彩~


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

查看所有标签

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

Numerical Methods and Methods of Approximation in Science and En

Numerical Methods and Methods of Approximation in Science and En

Karan Surana / CRC Press / 2018-10-31

ABOUT THIS BOOK Numerical Methods and Methods of Approximation in Science and Engineering prepares students and other readers for advanced studies involving applied numerical and computational anal......一起来看看 《Numerical Methods and Methods of Approximation in Science and En》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

SHA 加密
SHA 加密

SHA 加密工具

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

HSV CMYK互换工具