关于前端:模仿vue自己动手写响应式框架四-Vue对象构建

6次阅读

共计 3973 个字符,预计需要花费 10 分钟才能阅读完成。

概述

在之前的章节中,咱们创立了一个没有任何逻辑的 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 属性,成果如下:

残缺的 js 代码能够点击这里查看

参考

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

  • 模拟 vue 本人入手写响应式框架(一) – Vue 实现 todo 利用
  • 模拟 vue 本人入手写响应式框架(二) – Vue 对象创立用
  • 模拟 vue 本人入手写响应式框架(三) – dom 解析
  • 模拟 vue 本人入手写响应式框架(四) – Vue 对象构建
正文完
 0