博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
手写MVVM框架 之vue双向数据绑定原理剖析
阅读量:4974 次
发布时间:2019-06-12

本文共 6451 字,大约阅读时间需要 21 分钟。

            
自定义MVVM框架,这是比较牛逼的v-text,v-model和数据绑定原理介绍

{

{message}}

//创建一个MVVM框架类class MVVM { //类构造器(创造实例模板代码)    constructor(options) { //相当于函数的参数 {el:...,data:...}        //缓存重要属性        this.$vm = this;        this.$el = options.el;        this.$data = options.data;        //判断视图是否存在        if(this.$el) {            //添加属性观察对象(实现属性挟持)            new Observer(this.$data);            //创建模板编译器,来解析视图            this.$compiler = new TemepCompiler(this.$el, this.$vm);        }    }}
class TemepCompiler { //类构造器(创造实例模板代码)    constructor(el, vm) { //相当于函数的参数 (this.$el,this.$vm)        //缓存重要属性        this.vm = vm;        this.el = this.isElemetNode(el) ? el : document.querySelector(el);        //判断视图是否存在        if(this.el) {            //1.把模板内容放入内存(片段)            var fragment = this.node2fragment(this.el);            //2.解析模板            this.compile(fragment);            //3.把内存的结果返回页面            this.el.appendChild(fragment);        }    }    //工具方法    isElemetNode(node) {        return node.nodeType === 1 //1.元素节点 2.属性节点 3.文本节点    }    isTextNode(node) {        return node.nodeType === 3 //1.元素节点 2.属性节点 3.文本节点    }    toArray(arr) {        return [].slice.call(arr); //假数组转为数组;    }    isDerective(attrName) {        return attrName.indexOf("v-") >= 0; //判断属性名中是否有v-开头的属性    }    //核心方法(节省内存)把模板放入内存,等待解析    node2fragment(node) {        //创建内存片段        var fragment = document.createDocumentFragment(),            child;        //模板内容丢到内存        while(child = node.firstChild) {            fragment.appendChild(child);        }        //返回        return fragment;    }    compile(parentNode) {        //获取子节点        var childNodes = parentNode.childNodes, //类数组            compiler = this;        //遍历每一个节点        this.toArray(childNodes).forEach((node) => {            //判断节点类型            if(compiler.isElemetNode(node)) {                //1.属性节点(解析指令)                compiler.compileElement(node);            } else {                //2.文本节点(解析指令)                var textReg = /\{\{(.+)\}\}/; //(\转义)文本表达式验证规则                var expr = node.textContent;                //var expr = node.innerText; //谷歌支持                //按照规则验证内容                if(textReg.test(expr)) {                    expr = RegExp.$1 //缓存最近一次的正则里面的值;                    //调用方法编译                    compiler.compileText(node, expr); //如果还有子节点,继续解析;                }            }        });    }    //解析元素节点的指令的方法    compileElement(node) {        //获取当前元素节点的所有属性        var arrs = node.attributes;        self = this;        //遍历当前元素所有属性        this.toArray(arrs).forEach(attr => {            var attrName = attr.name;            //判断属性是否是指令            if(self.isDerective(attrName)) {                var type = attrName.substr(2); //v-text,v-model...                var expr = attr.value; //指令的值就是表达式                //找帮手                CompilerUntils[type](node, self.vm, expr);            }        })    }    //解析表达式的方法    compileText(node, expr) {        CompilerUntils.text(node, this.vm, expr);    }}//解析指令帮手CompilerUntils = {    //解析text指令    text(node, vm, expr) {        //第一次观察        //1.找到更新规则对象的更新方法        var updaterFn = this.updater["textUpdater"];        //2.执行方法        updaterFn && updaterFn(node, vm.$data[expr]) //等价if(updaterFn){updaterFn(node,vm.$data[expr])}        //第n+1次观察        new Watcher(vm, expr, (newVaule) => {            //触发订阅时按之前的规则对节点进行更新;v-model的也一样;            updaterFn && updaterFn(node, newVaule);        })    },    //解析model指令    model(node, vm, expr) {        //1.找到更新规则对象的更新方法        var updaterFn = this.updater["modelUpdater"];        //2.执行方法        updaterFn && updaterFn(node, vm.$data[expr]) //等价if(updaterFn){updaterFn(node,vm.$data[expr])}        //第n+1次观察        new Watcher(vm, expr, (newVaule) => {            //触发订阅时按之前的规则对节点进行更新;v-model的也一样;            updaterFn && updaterFn(node, newVaule);        })        //视图到模型变化        node.addEventListener("input", (e) => {            var newValue = e.target.value;            //把值放进数据            console.log(newValue+'新值');            vm.$data[expr] = newValue;        })    },    updater: {        //文本更新方法        textUpdater(node, value) {            node.textContent;            //node.innerText;  //谷歌支持        },        //输入框值更新方法        modelUpdater(node, value) {            node.value = value;        }    }}
class Observer {    //构造函数    constructor(data) {        //提供一个解析方法,完成属性的分析,和挟持        this.observe(data);    }    observe(data) {        //判断数据的有效性 必须是对象        if(!data || typeof data !== "object") {            return;        }        var keys = Object.keys(data); //拿到所有的属性(key)转为数组        keys.forEach((key) => {            //重新定义key            this.defineReactive(data, key, data[key]); //动态属性需要中括号不能.        })    }    //针对当前对象属性的重新定义(挟持)    defineReactive(obj, key, val) {        var dep = new Dep();        //重新定义         Object.defineProperty(obj, key, {            enumerable: true, //是否可以遍历            configurable: false, //是否可以重新配置            get() { //getter取值                Dep.target && dep.addSub(Dep.target); //拿到订阅者                //返回属性                return val;            },            set(newValue) { //setter修改值                val = newValue; //新值覆盖旧值                dep.notify(); //通知,触发update操作;            }        })    }}//创建发布者//1.管理订阅者//2.通知class Dep {    constructor() {        this.subs = [];    }    //添加订阅    addSub(sub) { //其实就是Watcher实例        this.subs.push(sub);    }    //集体通知    notify() {        this.subs.forEach((sub) => {            sub.update();        })    }}
//定义一个订阅者class Watcher {    //构造函数    //1.需要使用订阅功能的节点    //2.全局vm对象,用于获取数据    //3.需要使用订阅功能的节点    constructor(vm, expr, cb) {        //缓存重要属性        this.vm = vm;        this.expr = expr;        this.cb = cb;        //缓存当前值        this.value = this.get();    }    //获取当前值    get() {        //把当前订阅者添加到全局        Dep.target = this; //watcher实例        //获取当前值        var value = this.vm.$data[this.expr];        //清空全局        Dep.target = null;        //返回        return value;    }    //提供一个更新    update() {        //获取新值        var newValue = this.vm.$data[this.expr];        //获取老值        var oldValue = this.value;        //执行回调        if(newValue !== oldValue) {            this.cb(newValue); //效果一样关键需不需要改变this指向this.cb.call(this.vm,newValue)        }    }}

 

转载于:https://www.cnblogs.com/lhl66/p/8974281.html

你可能感兴趣的文章
程序员常去的6个头条分享站点
查看>>
作业三3
查看>>
SQL语句优化技术分析
查看>>
开通博客
查看>>
github 快速部署
查看>>
Python内置函数
查看>>
------ 比较二位数组大小-----
查看>>
canvas绘制经典折线图(一)
查看>>
项目职责
查看>>
对空间时钟的调查报告
查看>>
初步体验javascript try catch机制
查看>>
javaweb学习——session和Cookie实现购物车功能
查看>>
Android系统是目前最为流行的手机系统之一
查看>>
洛谷 2615 神奇的幻方——模拟
查看>>
【bzoj1085】【 [SCOI2005]骑士精神】启发式剪枝+迭代加深搜索
查看>>
C# 获取运行时间
查看>>
TraceView简单使用方法
查看>>
git config and options core.bare hard
查看>>
win10自动更新彻底关闭
查看>>
转:研读代码必须掌握的Eclipse快捷键
查看>>