模仿vue自己动手写响应式框架(四) - Vue对象构建

字节航标
• 阅读 1078

概述

之前的章节中,我们创建了一个没有任何逻辑的vue对象,仅仅只是保证了var app = new Vue({...})不报错而已,这一篇我们将构建一个真正的vue对象,实现真正的值绑定。

build(构建)

这是html中创建vue的代码

var app = new Vue({
            el: '#app',
            data: {
                newTodo: '',
                todos: []
            },
            methods: {
                addTodo: function () {
                    this.todos.push({ text: this.newTodo });
                    this.newTodo = '';
                },
                deleteTodo: function (index) {
                    this.todos.splice(index, 1);
                }
            }
        })

思路是

  • 创建vue对象
  • 将data数据直接挂载到对象上,这样可以实现vue.newTodo的访问效果
  • 将method直接挂载到桂香上,可以实现vue.addTodo的效果

当然,实际上vue并不是这么实现的,vue通过proxy方式实现直接访问的效果,我们的目的是能用就行(大家真正实现一个框架不要报这种想法,本系列是为了让所有人都能理解,都能入门才用该方式实现)。

var vue = {};
function build() {
        for (let k in options.data) {
            let v = options.data[k];
            defineProperty(k, v);
        }
        for (let key in options.methods) {
            vue[key] = options.methods[key];
        }
    }

defineProperty

这个是实现绑定的核心步骤,代码如下:

function defineProperty(name, value) {
        Object.defineProperty(vue, name, {
            get: function() {
                return value;
            },
            set: function(newValue) {
                value = newValue;
                let items = subscriber[name];
                if (items) {
                    for (let i = 0; i < items.length; ++i) {
                        items[i].change(name, newValue);
                    }
                }
            }
        })
    }

当我们给一个对象新增一个属性或者修改属性值的时候可以直接通过user.name='张三'实现,也可以通过函数Object.defineProperty进行定义,明显后者更为麻烦,但是Object.defineProperty可以监听值的变化,获取值和设置值都可以监听到,这就是为实现值变化更新dom的功能打下了基础。我们在set中监听值变化,从订阅者里面根据变量名称取出订阅者的指令,依次调用指令change方法。defineProperty其他用法参考这里

我们的data除了newTodo这个字符串变量外还有todos这个变量,其中新增代办的代码如下:

addTodo: function () {
            this.todos.push({ text: this.newTodo });
            this.newTodo = '';
        }

todos是通过调用push方法进行插入,那这样会触发set事件吗,答案是不会的,只有当this.todos=[]这种todos赋予新值才会触发值改变,这个肯定是不行的,这个要求开发者必须构建一个push后的数组再赋值给todos,可以用原型解决这个问题,原型最大的好处就是可以重新定义js的标准方法。

function defineArrayProperty() {
        var method=['push','splice'];
        for(let i=0;i<method.length;++i) {
            var origin = Array.prototype[method[i]];
            var fn = function () {
                origin.apply(this, arguments);
                let names = Object.getOwnPropertyNames(vue);
                for (let i = 0; i < names.length; ++i) {
                    if (vue[names[i]] === this) {
                        let items = subscriber[names[i]];
                        if (items) {
                            for (let j = 0; j < items.length; ++j) {
                                items[j].change(names[i], this);
                            }
                        }
                    }
                }
            }
            Object.defineProperty(Array.prototype, method[i], {
                value: fn
            });
        }
    }

通过apply方法调用原来的方法,再找到订阅者通知订阅者,对于变量可以一个个定义属性,这样可以获取到名称,但是对于修改js基本对象的属性,这里无法获取变量名,只能通过遍历vue属性找到变量名。

valueTrigger

完成了以上工作,我们上一篇中的valueTrigger逻辑就可以实现

function valueTrigger(name, value) {
    if (vue[name] != undefined) {
        vue[name] = value;
    }
}

当值发生变化,由于变量都直接绑定到vue对象本身,因此可以直接通过属性名找到并赋值。

事件

我们将配置中methods的方法也挂载到了vue对象上,那么事件响应就可以做了,我们稍微修改下上一篇中compile函数,增加事件响应

function compile(node) {
        let element = node.cloneNode(false);
        for (let i = 0; i < node.childNodes.length; ++i) {
            element.appendChild(compile(node.childNodes[i]));
        }
        if (element.nodeType == 3) {
            //文本类型解析
            let vars = parseVariable(element.textContent);
            for (let i = 0; i < vars.length; ++i) {
                let directive = Directive(element, vars[i], element.textContent);
                addSubscriber(vars[i], directive);
            }
        } else if (element.nodeType == 1 && element.attributes) {
            //元素类型解析
            let attrs = element.attributes;
            for (let i = 0; i < attrs.length; ++i) {
                let name = attrs[i].name;
                if (name.startsWith("v-bind") || name.startsWith(":") || name.startsWith("v-model")) {
                    let vars = parseVariable(attrs[i].value);
                    if (vars.length == 0) {
                        let directive = Directive(element, name, attrs[i].value);
                        addSubscriber(attrs[i].value, directive);
                    } else {
                        for (let i = 0; i < vars.length; ++i) {
                            let directive = Directive(element, name, attrs[i].value);
                            addSubscriber(vars[i], directive);
                        }
                    }
                }
                //事件响应
                if (name.startsWith("v-on:")) {
                    let event = name.substr(5);
                    addEvent(element, event, parseMethod(attrs[i].value));
                }
            }
        }
        return element;
    }
  • parseMethod通过正则表达式进行解析
function parseMethod(exp) {
        var method = {};
        let m;
        let methodRegExp = /([^\(]+)\(([^\)]*)\)/g;
        if (m = methodRegExp.exec(exp)) {
            method.name = m[1];
            let params = m[2];
            params = params.replace(/\s+/g, '');
            if (params && params.length > 0) {
                method.params = params.split(",");
            } else {
                method.params = [];
            }
        }
        return method;
    }
  • addEvent代码:
function addEvent(element, event, method) {
        element.addEventListener(event, function(e) {
            let params = [];
            let paramNames = method.params;
            if (paramNames) {
                for (let i = 0; i < paramNames.length; ++i) {
                    params.push(vue[paramNames[i]]);
                }
            }
            vue[method.name].apply(vue, params);
        })
    }

通过调用addEventListener给节点增加事件,比如v-on:click就会增加click事件,通过apply方法动态调用定义在vue中的方法,完成事件的响应,

效果

可以点击这里查看效果,为了用上style属性,修改了下html,增加了style属性,效果如下:

模仿vue自己动手写响应式框架(四) - Vue对象构建

完整的js代码可以点击这里查看

参考

点击余下链接,查看该系列其他文章

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Peter20 Peter20
4年前
mysql中like用法
like的通配符有两种%(百分号):代表零个、一个或者多个字符。\(下划线):代表一个数字或者字符。1\.name以"李"开头wherenamelike'李%'2\.name中包含"云",“云”可以在任何位置wherenamelike'%云%'3\.第二个和第三个字符是0的值wheresalarylike'\00%'4\
Wesley13 Wesley13
3年前
VBox 启动虚拟机失败
在Vbox(5.0.8版本)启动Ubuntu的虚拟机时,遇到错误信息:NtCreateFile(\\Device\\VBoxDrvStub)failed:0xc000000034STATUS\_OBJECT\_NAME\_NOT\_FOUND(0retries) (rc101)Makesurethekern
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这