浅谈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分析、压缩工具 JavaScript Analyser
Nov 30 Javascript
js调用webservice中的方法实现思路及代码
Feb 25 Javascript
javascrip关于继承的小例子
May 10 Javascript
简体中文转换繁体中文(实现代码)
Dec 25 Javascript
JQuery选择器绑定事件及修改内容的方法
Jan 23 Javascript
浅析JavaScript动画
Jun 10 Javascript
Jquery中巧用Ajax的beforeSend方法
Jan 20 Javascript
zTree插件下拉树使用入门教程
Apr 11 Javascript
Bootstrap每天必学之警告框插件
Apr 26 Javascript
全面解析多种Bootstrap图片轮播效果
May 27 Javascript
原生js获取浏览器窗口及元素宽高常用方法集合
Jan 18 Javascript
vue打包使用Nginx代理解决跨域问题
Aug 27 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
Adodb的十个实例(清晰版)
2006/12/31 PHP
php新浪微博登录接口用法实例
2014/12/23 PHP
PHP中$_SERVER使用说明
2015/07/05 PHP
PHP获取远程http或ftp文件的md5值的方法
2019/04/15 PHP
jMessageBox 基于jQuery的窗口插件
2009/12/09 Javascript
Jquery实战_读书笔记1—选择jQuery
2010/01/22 Javascript
JavaScript 设计模式 安全沙箱模式
2010/09/24 Javascript
formvalidator验证插件中有关ajax验证问题
2013/01/04 Javascript
HTML Color Picker(js拾色器效果)
2013/08/27 Javascript
javascript模拟订火车票和退票示例
2014/04/24 Javascript
javascript+canvas制作九宫格小程序
2014/12/28 Javascript
jQuery ready()和onload的加载耗时分析
2016/09/08 Javascript
jQuery 特性操作详解及实例代码
2016/09/29 Javascript
Vue.js实现多条件筛选、搜索、排序及分页的表格功能
2020/11/24 Javascript
EasyUI的TreeGrid的过滤功能的解决思路
2017/08/08 Javascript
JavaScript递归算法生成树形菜单
2017/08/15 Javascript
浅谈javascript中的prototype和__proto__的理解
2019/04/07 Javascript
深入解读Python解析XML的几种方式
2016/02/16 Python
python实现排序算法解析
2018/09/08 Python
Python3.7 基于 pycryptodome 的AES加密解密、RSA加密解密、加签验签
2019/12/04 Python
Python 自由定制表格的实现示例
2020/03/20 Python
java字符串格式化输出实例讲解
2021/01/06 Python
详解HTML5布局和HTML5标签
2020/10/26 HTML / CSS
智利最大的网上商店:Linio智利
2016/11/24 全球购物
美国网上花店:JustFlowers
2017/02/12 全球购物
小米旗下精品生活电商平台:小米有品
2018/12/18 全球购物
Book Depository欧盟:一家领先的国际图书零售商
2019/05/21 全球购物
Myprotein中国网站:欧洲畅销运动营养品牌
2021/02/11 全球购物
C语言编程题
2015/03/09 面试题
控制工程专业个人求职信
2013/09/25 职场文书
医师定期考核实施方案
2014/05/07 职场文书
演讲稿开场白台词
2014/08/25 职场文书
三严三实学习心得体会
2014/10/13 职场文书
2014年银行员工工作总结
2014/11/12 职场文书
Golang中channel的原理解读(推荐)
2021/10/16 Golang
java如何实现获取客户端ip地址的示例代码
2022/04/07 Java/Android