浅谈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 日期时间 转换的方法
Feb 21 Javascript
js对象内部访问this修饰的成员函数示例
Apr 27 Javascript
javascript实现淡蓝色的鼠标拖动选择框实例
May 09 Javascript
jquery判断复选框是否被选中的方法
Oct 16 Javascript
JQuery EasyUI Layout 在from布局自适应窗口大小的实现方法
May 28 Javascript
深入理解JS中的Function.prototype.bind()方法
Oct 11 Javascript
Bootstrap模态框禁用空白处点击关闭
Oct 20 Javascript
数组Array的一些方法(总结)
Feb 17 Javascript
Javascript实现数组中的元素上下移动
Apr 28 Javascript
Angular 2 利用Router事件和Title实现动态页面标题的方法
Aug 23 Javascript
vue解决使用$http获取数据时报错的问题
Oct 30 Javascript
解决antd 下拉框 input [defaultValue] 的值的问题
Oct 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
Memcache 在PHP中的使用技巧
2010/02/08 PHP
header中Content-Disposition的作用与使用方法
2012/06/13 PHP
php获取远程图片体积大小的实例
2013/11/12 PHP
php实现生成带二维码图片并强制下载功能
2018/02/24 PHP
TP5(thinkPHP5)框架基于ajax与后台数据交互操作简单示例
2018/09/03 PHP
PHP使用pdo实现事务处理操作示例
2018/09/05 PHP
Javascript 的addEventListener()及attachEvent()区别分析
2009/05/21 Javascript
jQuery源码分析-03构造jQuery对象-工具函数
2011/11/14 Javascript
使用Jquery获取带特殊符号的ID 标签的方法
2014/04/30 Javascript
使用GruntJS构建Web程序之Tasks(任务)篇
2014/06/06 Javascript
jquery动感漂浮导航菜单代码分享
2020/04/15 Javascript
jquery Ajax实现Select动态添加数据
2017/06/08 jQuery
JS对象与JSON互转换、New Function()、 forEach()、DOM事件流等js开发基础小结
2017/08/10 Javascript
代码详解javascript模块加载器
2018/03/04 Javascript
微信小程序实现多选框全选与取消全选功能示例
2019/05/14 Javascript
localstorage实现带过期时间的缓存功能
2019/06/28 Javascript
[59:26]DOTA2上海特级锦标赛D组资格赛#1 EG VS VP第二局
2016/02/28 DOTA
python正则匹配查询港澳通行证办理进度示例分享
2013/12/27 Python
python类和函数中使用静态变量的方法
2015/05/09 Python
Python内置数据结构与操作符的练习题集锦
2016/07/01 Python
Python 爬虫之超链接 url中含有中文出错及解决办法
2017/08/03 Python
对Python闭包与延迟绑定的方法详解
2019/01/07 Python
python 操作hive pyhs2方式
2019/12/21 Python
python日期与时间戳的各种转换示例
2020/02/12 Python
python 实现朴素贝叶斯算法的示例
2020/09/30 Python
python 利用opencv实现图像网络传输
2020/11/12 Python
用python对excel进行操作(读,写,修改)
2020/12/25 Python
Python使用paramiko连接远程服务器执行Shell命令的实现
2021/03/04 Python
纯CSS实现菜单、导航栏的3D翻转动画效果
2014/04/23 HTML / CSS
农民入党思想汇报
2014/01/03 职场文书
《世界多美呀》教学反思
2014/03/02 职场文书
党支部公开承诺践诺书
2014/03/28 职场文书
护士工作失误检讨书
2014/09/14 职场文书
2015年办公室主任工作总结
2015/04/09 职场文书
新入职员工工作总结
2015/10/15 职场文书
python实现自动清理文件夹旧文件
2021/05/10 Python