浅谈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 相关文章推荐
IE6 弹出Iframe层中的文本框“经常”无法获得输入焦点
Dec 27 Javascript
用JS控制回车事件的代码
Feb 20 Javascript
js获取图片大小的函数代码
Sep 20 Javascript
js获取网页高度(详细整理)
Dec 28 Javascript
jquery实现炫酷的叠加层自动切换特效
Feb 01 Javascript
jQuery往textarea中光标所在位置插入文本的方法
Jun 26 Javascript
浅谈js图片前端预览之filereader和window.URL.createObjectURL
Jun 30 Javascript
JavaScript 闭包机制详解及实例代码
Oct 10 Javascript
jQuery层级选择器实例代码
Feb 06 Javascript
jQuery轻量级表单模型验证插件
Oct 15 jQuery
Javascript中绑定click事件的四种方式介绍
Oct 26 Javascript
JS拖拽排序插件Sortable.js用法实例分析
Feb 20 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
php将mysql数据库整库导出生成sql文件的具体实现
2014/01/08 PHP
CodeIgniter配置之autoload.php自动加载用法分析
2016/01/20 PHP
简单三步,搞掂内存泄漏
2007/03/10 Javascript
一个cssQuery对象 javascript脚本实现代码
2009/07/21 Javascript
jquery获取被勾选的checked(选中)的那一行的3列和4列的值
2013/07/04 Javascript
jquery获得keycode的示例代码
2013/12/30 Javascript
extjs 时间范围选择自动判断的实现代码
2014/06/24 Javascript
javascript实现鼠标拖动改变层大小的方法
2015/04/30 Javascript
zTree插件下拉树使用入门教程
2016/04/11 Javascript
JavaScript实现设计模式中的单例模式的一些技巧总结
2016/05/17 Javascript
JS实现上传图片的三种方法并实现预览图片功能
2017/07/14 Javascript
解决vue路由后界面没有变化,但是链接有的问题
2018/09/01 Javascript
Vue数据双向绑定的深入探究
2018/11/27 Javascript
详解vuex commit保存数据技巧
2018/12/25 Javascript
详解vue-cli+element-ui树形表格(多级表格折腾小计)
2019/04/17 Javascript
jquery多级树形下拉菜单的实例代码
2019/07/09 jQuery
微信小程序上传图片并等比列压缩到指定大小的实例代码
2019/10/24 Javascript
Vue 构造选项 - 进阶使用说明
2020/08/14 Javascript
[01:03:37]Secret vs VGJ.S Supermajor小组赛C组 BO3 第二场 6.3
2018/06/04 DOTA
python批量修改文件后缀示例代码分享
2013/12/24 Python
python实现按任意键继续执行程序
2016/12/30 Python
视觉直观感受若干常用排序算法
2017/04/13 Python
python实现微信机器人: 登录微信、消息接收、自动回复功能
2019/04/29 Python
Pytorch 实现sobel算子的卷积操作详解
2020/01/10 Python
基于python实现上传文件到OSS代码实例
2020/05/09 Python
python利用 keyboard 库记录键盘事件
2020/10/16 Python
HTML5新增的Css选择器、伪类介绍
2013/08/07 HTML / CSS
类的返射机制中的包及核心类
2016/09/12 面试题
业务经理岗位职责
2013/11/11 职场文书
博士研究生自我鉴定范文
2013/12/04 职场文书
煤矿班组长的职责
2013/12/25 职场文书
综合内勤岗位职责
2014/04/14 职场文书
医院党建工作总结2015
2015/05/26 职场文书
贫困生证明范文
2015/06/16 职场文书
《一面五星红旗》教学反思
2016/02/23 职场文书
Redis sentinel哨兵集群的实现步骤
2022/07/15 Redis