浅谈mvvm-simple双向绑定简单实现


Posted in Javascript onApril 18, 2018

mvvm模式解放DOM枷锁

浅谈mvvm-simple双向绑定简单实现

mvvm原理分析

JavaScript在浏览器中操作HTML经历了几个不同阶段

第一阶段 直接用浏览器提供的原生API操作DOM元素

var dom = document.getElementById('id');
dom.innerHTML = 'hello mvvm';

第二阶段 jQuery的出现解决了原生API的复杂性和浏览器间的兼容性等问题,提供了更加简易方便的API

$('#id').text('hello mvvm')

第三阶段 MVC模式使前端可以和后端配合,修改服务端渲染后的页面内容

而随着产品对于用户体验的重视,交互体验越来越重要,仅用jQuery远远不够。 MVVM模型解决了频繁操作的痛点,Model-View-ViewModel模式将数据与视图的同步交由ViewModel完成

jQuery修改节点内容:

<p>name: <span id="name">vist</span>!</p>
<p>age: <span id="age">25</span>.</p>

var name = 'bestvist';
var age = 26;
$('#name').text(name);
$('#age').text(age);

MVVM模式下只需要关注数据结构:

var me = {
  name: 'vist',
  age: 25
}

修改相应属性就好

me.name = 'bestvist';
me.age = 26;

mvvm实现

mvvm实现数据绑定的几种方式:

  1. 发布-订阅模式
  2. 脏值检查
  3. 数据劫持

比较流行的vue采用的就是数据劫持和发布-订阅模式,通过劫持es5提供的Object.defineProperty()中各个属性的get,set方法, 数据更新时触发消息给订阅者,实现数据绑定功能。

Object.defineProperty(obj, prop, descriptor)方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性,并返回这个对象。 该方法接受3个参数:

  • obj 定义属性的对象。
  • prop 被定义或修改的属性名。
  • descriptor 被定义或修改的属性的描述符。

一般情况通过直接给对象属性赋值来创建属性或者修改对应属性,而使用Object.defineProperty可以修改对象属性的一些额外默认配置。 如:

const obj = {name: 'Tom'};
Object.defineProperty(obj, 'name', {
 get: function(val) {
   return 'Jerry'; 
 }
})
console.log(obj.name);
//输出: Jerry

Object.defineProperty详细解释,请戳这里

mvvm实现的主要流程:

  • 数据代理,访问实例上的属性时直接返回对应data里的属性
  • 数据监听,对实例上的属性监听,如果数据改变通知订阅者更新
  • 指令解析,对每个元素节点进行解析,替换数据并绑定更新函数
  • 链接数据监听和指令解析,保证每个数据的更新,指令解析都可以获取并更新视图

实例化类:

new MVVM({
  el: '#app',
  data() {
    return {
      message: 'hello mvvm'
    }
  }
})

数据代理:

class MVVM {
  constructor(options) {
    this.$options = options || {};
    let data = this._data = this.$options.data();

    // 数据代理 vm.xxx => vm._data.xxx
    Object.keys(data).forEach(key => {
      this._proxyData(key);
    });

    // observe(data, this);
    // this.$compile = new Compile(options.el || document.body, this);

  }

  _proxyData(key) {
    Object.defineProperty(this, key, {
      configurable: true,
      enumerable: true,
      get: () => {
        return this._data[key];
      },
      set: newVal => {
        this._data[key] = newVal;
      }
    });
  }

}

数据监听,劫持实例属性更新

class Observer {
  constructor(data) {
    this.data = data;
    Object.keys(this.data).forEach(key => {
      this.defineReactive(key, this.data[key]);
    })
  }

  // 定义反应
  defineReactive(key, val) {
    let dep = new Dep();
    Object.defineProperty(this.data, key, {
      enumerable: true,
      configurable: false,
      get: () => {
        return val;
      },
      set: newVal => {
        if (val === newVal) {
          return;
        }
        val = newVal;
        // 赋值对象再进行劫持
        observe(val);
        ... // 数据修改通知
      }
    })
  }

}

function observe(val) {
  if (!val || typeof val !== 'object') {
    return;
  }
  return new Observer(val);
}

指令解析部分代码

class Compile {
  constructor(el, vm) {
    this.$vm = vm;
    this.$el = this.isElementNode(el) ? el : document.querySelector(el);
    if (this.$el) {
      this.$fragment = this.node2Fragment(this.$el);
      this.init();
      this.$el.appendChild(this.$fragment);
    }
  }

  init() {
    this.compileElement(this.$fragment);
  }

  node2Fragment(el) {
    let fragment = document.createDocumentFragment(), child;

    // 原生节点拷贝到fragment
    while (child = el.firstChild) {
      // appendChild将元素从dom上移到fragment
      fragment.appendChild(child);
    }
    return fragment;
  }

  compileElement(el) {
    let childNodes = el.childNodes;

    [].slice.call(childNodes).forEach(node => {
      let text = node.textContent;
      let reg = /\{\{(.*)\}\}/;

      if (this.isElementNode(node)) {
        this.compile(node);
      } else if (this.isTextNode(node) && reg.test(text)) {
        this.compileText(node, RegExp.$1);
      }

      if (node.childNodes && node.childNodes.length) {
        this.compileElement(node);
      }
    })
  }

}

其中

while (child = el.firstChild) {
  // appendChild将元素从dom上移到fragment
  fragment.appendChild(child);
}

通过appendChild改变原dom结构特点,逐步把dom元素节点移到fragment中。

完整代码 Vue源码

总结

以数据流为导向的mvvm模式极大的简化前端对于dom的操作,加快前端开发速度,同时也提高了用户体验。

参考:

剖析Vue原理&实现双向绑定MVVM mvvm廖雪峰

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
开发跨浏览器javascript常见注意事项
Jan 01 Javascript
javascript真的不难-回顾一下基础知识
Jan 15 Javascript
js判断当前页面用什么浏览器打开的方法
Jan 06 Javascript
学习使用AngularJS文件上传控件
Feb 16 Javascript
js判断图片加载完成后获取图片实际宽高的方法
Feb 25 Javascript
JavaScript中三种异步上传文件方式
Mar 06 Javascript
原生js仿淘宝网商品放大镜效果
Feb 28 Javascript
JavaScript实现全选取消效果
Dec 14 Javascript
vue 设置路由的登录权限的方法
Jul 03 Javascript
vue.js添加一些触摸事件以及安装fastclick的实例
Aug 28 Javascript
详细分析vue响应式原理
Jun 22 Javascript
JavaScript实现多球运动效果
Sep 07 Javascript
JS点击动态添加标签、删除指定标签的代码
Apr 18 #Javascript
jQuery实现的手动拖动控制进度条效果示例【测试可用】
Apr 18 #jQuery
浅谈vuepress 踩坑记
Apr 18 #Javascript
使用webpack-dev-server处理跨域请求的方法
Apr 18 #Javascript
详解webpack-dev-server 设置反向代理解决跨域问题
Apr 18 #Javascript
jQuery实现的两种简单弹窗效果示例
Apr 18 #jQuery
vue双向数据绑定知识点总结
Apr 18 #Javascript
You might like
各种战术和打法的原创者
2020/03/04 星际争霸
解析PHP跳出循环的方法以及continue、break、exit的区别介绍
2013/07/01 PHP
微信自定义菜单的处理开发示例
2015/04/16 PHP
ECSHOP在PHP5.5及高版本上报错的解决方法
2015/08/31 PHP
PHP实现二维数组根据key进行排序的方法
2016/12/30 PHP
laravel框架如何设置公共头和公共尾
2019/10/22 PHP
jQuery DOM操作小结与实例
2010/01/07 Javascript
jquery DIV撑大让滚动条滚到最底部代码
2013/06/06 Javascript
jQueryMobile之Helloworld与页面切换的方法
2015/02/04 Javascript
jquery获取form表单input元素值的简单实例
2016/05/30 Javascript
javascript宿主对象之window.navigator详解
2016/09/07 Javascript
详解Vue组件实现tips的总结
2017/11/01 Javascript
vue项目中v-model父子组件通信的实现详解
2017/12/10 Javascript
karma+webpack搭建vue单元测试环境的方法示例
2018/05/24 Javascript
使用form-create动态生成vue自定义组件和嵌套表单组件
2019/01/18 Javascript
eslint 的三大通用规则详解
2019/05/16 Javascript
vue使用svg文件补充-svg放大缩小操作(使用d3.js)
2020/09/22 Javascript
[40:50]2014 DOTA2国际邀请赛中国区预选赛 5 23 CIS VS LGD第四场
2014/05/24 DOTA
Python实现向QQ群成员自动发邮件的方法
2014/11/19 Python
Python删除空文件和空文件夹的方法
2015/07/14 Python
详解如何在python中读写和存储matlab的数据文件(*.mat)
2018/02/24 Python
python 接收处理外带的参数方法
2018/12/03 Python
对Python协程之异步同步的区别详解
2019/02/19 Python
Django实现学生管理系统
2019/02/26 Python
sklearn中的交叉验证的实现(Cross-Validation)
2021/02/22 Python
非常震撼的纯CSS3人物行走动画
2016/02/24 HTML / CSS
canvas实现圆绘制的示例代码
2019/09/11 HTML / CSS
雅诗兰黛旗下专业男士保养领导品牌:Lab Series
2017/05/15 全球购物
Daisy London官网:英国最大的首饰集团IBB旗下
2019/02/28 全球购物
JSP和EJB可以共享HttpSession么?EJB里面可以改变session里面的内容
2013/06/05 面试题
高中生毕业自我鉴定范文
2013/12/22 职场文书
国际经济与贸易专业大学生职业规划书
2014/03/01 职场文书
幼儿园见习报告范文
2014/10/30 职场文书
2014年大学生工作总结
2014/11/20 职场文书
幼师自荐信范文
2015/03/06 职场文书
mysql5.5中文乱码问题解决的有用方法
2022/05/30 MySQL