一个简化版Vue助你理解Vue原理

好多人看完我的这个文章对它的理解还是只是知道了大概原理,但是对具体的Vue双向绑定的实现很模糊,因此就出了这篇文章,供大家参考希望可以得到收获,以下是主要代码逻辑,先陈述一下这一过程都需要什么:

需要有一个接收Vue实例配置项的构造函数SimpleVue,给他加两个原型方法分别是observe()和compile(),再构造出一个订阅器watcher,给他加一个更新视图方法

  • observe():用来劫持并监听数据变化的数据监听器,有变化就会通知下文中的订阅器watcher
  • compile():节点DOM解析器,用来获取和解析每一个节点及其指令,根据初始化的模板数据来创建订阅器watcher
  • watcher():订阅器watcher,用来接收属性值的相关数据的变化通知,调用自身原型方法update从而更新视图

由于Vue就是一个MVVM的框架理念,所以就要通过Object.defineProperty()方法来劫持并监听所有属性值相关的数据,看看它是否变化,如有变化则通知订阅器watcher看是否需要视图更新,这一过程就是我们的数据监听器observe的工作任务,由于数据和订阅器是一对多的关系,所以通知订阅器的时候需要把数据对应的订阅器的集合都放在一个oWatcherObj对象中,接下来需要一个节点DOM解析器compile,主要用来迭代递归获取和解析每一个节点及其指令,根据初始化的模板数据来创建订阅器watcher,实例化watcher就会接到数据变化的通知,进而实现VM更新视图

template:

<div id="simpleVue">
    <button yf-on:click="copy">戳我</button>
    <div>
        <textarea yf-model="name"></textarea>
        <div yf-text="name"></div>
    </div>
    <hr>
    <button yf-on:click="show">显示/隐藏</button>
    <div yf-if="isShow">
        <input type="text" yf-model="webSite">
        <div yf-text="webSite"></div>
    </div>
</div>

SimpleVue构造:

class SimpleVue { // 简化版Vue实例的构造 用来接收实例的配置项
    constructor(options) {
        this.$el = document.querySelector(options.el);
        this.$data = options.data;
        this.$methods = options.methods;
        this.oWatcherObj = {}; // 所有属性值相关的数据对应的订阅器的集合都放在该对象中
        this.observe(); // 调用数据监听器对属性值相关的数据进行劫持并监听
        this.compile(this.$el); // 对该DOM节点进行解析
    }
    observe() { // 数据监听器 用来劫持并监听属性值相关数据的变化 如有变化则通知订阅器watcher
        for (let key in this.$data) {
            let value = this.$data[key];
            this.oWatcherObj[key] = []; // 初始化该数据的订阅器 数据和订阅器的关系是一对多
            let oWatcherObj = this.oWatcherObj[key];
            Object.defineProperty(this.$data, key, { // 关键方法 可以修改对象身上的默认属性值的ES5方法 下面用到的是ES中两大属性中的访问器属性,有以下四种描述符对象
                configurable: false, // 该状态下的属性描述符不能被修改和删除
                enumerable: false, // 该状态下的属性描述符中的属性不可被枚举
                get() { // 属性值相关的数据读取函数
                    return value;
                },
                set(newVal) { // 属性值相关的数据写入函数
                    if (newVal !== value) {
                        value = newVal;
                        oWatcherObj.forEach((obj) => {
                            obj.update(); // 通知和该数据相关的所有订阅器
                        });
                    }
                }
            });
        }
    }
    compile(el) { // 节点DOM解析器 用来获取和解析每一个节点及其指令 根据初始化的模板数据来创建订阅器watcher
        let nodes = el.children;
        for (let i = 0; i < nodes.length; i++) { // 迭代同级所有节点
            let node = nodes[i];
            if (node.children.length > 0) {
                this.compile(node); // 递归所有子节点
            }
            if (node.hasAttribute('yf-on:click')) { // 节点中如存在该指令则执行以下操作
                let eventAttrVal = node.getAttribute('yf-on:click');
                node.addEventListener('click', this.$methods[eventAttrVal].bind(this.$data)); // 绑定获取到的指令对应的数据所触发的方法
            }
            if (node.hasAttribute('yf-if')) {
                let ifAttrVal = node.getAttribute('yf-if');
                this.oWatcherObj[ifAttrVal].push(new Watcher(this, node, "", ifAttrVal)); // 给该指令对应的数据创建订阅器放在该数据对应的订阅器数组里
            }
            if (node.hasAttribute('yf-model')) {
                let modelAttrVal = node.getAttribute('yf-model');
                node.addEventListener('input', ((i) => { // 前方高能:此处有闭包请绕行!!! i的问题
                    this.oWatcherObj[modelAttrVal].push(new Watcher(this, node, "value", modelAttrVal));
                    return () => {
                        this.$data[modelAttrVal] = nodes[i].value; // 将该指令所在节点的值扔给该指令的数据
                    }
                })(i));
            }
            if (node.hasAttribute('yf-text')) {
                let textAttrVal = node.getAttribute('yf-text');
                this.oWatcherObj[textAttrVal].push(new Watcher(this, node, "innerText", textAttrVal));
            }
        }
    }
}

订阅器构造:

class Watcher { // 订阅器构造 用来接收属性值的相关数据的变化通知 从而更新视图
    constructor(...arg) {
        this.vm = arg[0];
        this.el = arg[1];
        this.attr = arg[2];
        this.val = arg[3];
        this.update(); // 初始化订阅器时更新一下视图
    }
    update() { // 将收到的新的数据更新在视图中从而实现真正的VM
        if (this.vm.$data[this.val] === true) {
            this.el.style.display = 'block';
        } else if (this.vm.$data[this.val] === false) {
            this.el.style.display = 'none';
        } else {
            this.el[this.attr] = this.vm.$data[this.val];
        }
    }
}

Shortcuts

希望大家阅读完本文可以有所收获,因为能力有限,掌握的知识也是不够全面,欢迎大家提出来一起分享!谢谢O(∩_∩)O~

欢迎访问我的GitHub,喜欢的可以star,项目随意fork,支持转载但要下标注,同时恭候:个人博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345