浅谈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动态调整iframe高度的代码
Apr 10 Javascript
Jquery中给animation加更多的运作效果实例
Sep 05 Javascript
JS 有趣的eval优化输入验证实例代码
Sep 22 Javascript
javascript判断chrome浏览器的方法
Mar 26 Javascript
jQuery 中国省市两级联动选择附图
May 14 Javascript
JavaScript实现找出字符串中第一个不重复的字符
Sep 03 Javascript
jQuery选择器querySelector的使用指南
Jan 23 Javascript
js简单实现调整网页字体大小的方法
Jul 23 Javascript
JS实现的文件拖拽上传功能示例
May 21 Javascript
react在安卓中输入框被手机键盘遮挡问题的解决方法
Sep 03 Javascript
Jquery如何使用animation动画效果改变背景色的代码
Jul 20 jQuery
element 动态合并表格的步骤
Dec 31 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
全国FM电台频率大全 - 24 贵州省
2020/03/11 无线电
完美解决dedecms中的[html][/html]和[code][/code]问题
2007/03/20 PHP
php计算title标题相似比的方法
2015/07/29 PHP
PHP入门教程之表单与验证实例详解
2016/09/11 PHP
详解php中的implements 使用
2017/06/13 PHP
PHP实现基本留言板功能原理与步骤详解
2020/03/26 PHP
JS等比例缩小图片尺寸的实例
2013/02/27 Javascript
JQUERY 实现窗口滚动搜索框停靠效果(类似滚动停靠)
2013/03/27 Javascript
网站繁简切换的JS遇到页面卡死的解决方法
2014/03/12 Javascript
jquery使用正则表达式验证email地址的方法
2015/01/22 Javascript
javascript 动态创建表格的2种方法总结
2015/03/04 Javascript
jQuery入门基础知识学习指南
2015/08/14 Javascript
Bootstrap模态框禁用空白处点击关闭
2016/10/20 Javascript
JavaScript装饰器函数(Decorator)实例详解
2017/03/30 Javascript
AngularJS学习笔记之表单验证功能实例详解
2017/07/06 Javascript
jquery动态赋值id与动态取id方法示例
2017/08/21 jQuery
jQuery+ajax读取json数据并按照价格排序示例
2018/03/28 jQuery
详解js跨域请求的两种方式,支持post请求
2018/05/05 Javascript
electron-vue利用webpack打包实现多页面的入口文件问题
2019/05/12 Javascript
layui清除radio的选中状态实例
2019/11/14 Javascript
[01:02:25]2014 DOTA2华西杯精英邀请赛5 24 NewBee VS VG
2014/05/25 DOTA
python递归计算N!的方法
2015/05/05 Python
MySQL最常见的操作语句小结
2015/05/07 Python
Python函数可变参数定义及其参数传递方式实例详解
2015/05/25 Python
Python实现自动为照片添加日期并分类的方法
2017/09/30 Python
对TensorFlow中的variables_to_restore函数详解
2018/07/30 Python
Django 接收Post请求数据,并保存到数据库的实现方法
2019/07/12 Python
django-filter和普通查询的例子
2019/08/12 Python
Python数据相关系数矩阵和热力图轻松实现教程
2020/06/16 Python
《王二小》教学反思
2014/02/27 职场文书
房产代理公证处委托书
2014/04/04 职场文书
学生上课看漫画的检讨书
2014/09/26 职场文书
温馨祝福晨语:美丽的一天从我的问候开始
2019/11/28 职场文书
Django开发RESTful API实现增删改查(入门级)
2021/05/10 Python
Python实现查询剪贴板自动匹配信息的思路详解
2021/07/09 Python
Spring中的@Transactional的工作原理
2022/06/05 Java/Android