浅谈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 控制 html元素 显示/隐藏实现代码
Sep 01 Javascript
Mootools 1.2教程 事件处理
Sep 15 Javascript
js 编写规范
Mar 03 Javascript
JavaScript 变量作用域分析
Jul 04 Javascript
网站接入QQ登录的两种方法
Jul 22 Javascript
javascript动态添加checkbox复选框的方法
Dec 23 Javascript
Ajax分页插件Pagination从前台jQuery到后端java总结
Jul 22 Javascript
详谈AngularJs 控制器、数据绑定、作用域
Jul 09 Javascript
Angular4实现动态添加删除表单输入框功能
Aug 11 Javascript
BootstrapTable加载按钮功能实例代码详解
Sep 22 Javascript
详解JSONObject和JSONArray区别及基本用法
Oct 25 Javascript
从零开始用electron手撸一个截屏工具的示例代码
Oct 10 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将Unicode 转化为UTF-8的实现方法(推荐)
2017/02/08 PHP
PHP封装返回Ajax字符串和JSON数组的方法
2017/02/17 PHP
php中时间函数date及常用的时间计算
2017/05/12 PHP
PHP运用foreach神奇的转换数组(实例讲解)
2018/02/01 PHP
js 事件截取enter按键页面提交事件示例代码
2014/03/04 Javascript
JavaScript之Object类型介绍
2015/04/01 Javascript
在JavaScript中使用开平方根的sqrt()方法
2015/06/15 Javascript
jQuery animate easing使用方法图文详解
2016/06/17 Javascript
全面介绍javascript实用技巧及单竖杠
2016/07/18 Javascript
JS禁止查看网页源代码的实现方法
2016/10/12 Javascript
JS数组去重(4种方法)
2017/03/27 Javascript
angular.js + require.js构建模块化单页面应用的方法步骤
2017/07/19 Javascript
Three.js利用Detector.js插件如何实现兼容性检测详解
2017/09/26 Javascript
vue2.0 自定义 饼状图 (Echarts)组件的方法
2018/03/02 Javascript
解决vue 路由变化页面数据不刷新的问题
2018/03/13 Javascript
基于VuePress 轻量级静态网站生成器的实现方法
2018/04/17 Javascript
AngularJS发送异步Get/Post请求方法
2018/08/13 Javascript
详解Vue 单文件组件的三种写法
2020/02/19 Javascript
Jquery+AJAX实现无刷新上传并重命名文件操作示例【PHP后台接收】
2020/05/29 jQuery
[01:03:22]LGD vs OG 2018国际邀请赛淘汰赛BO3 第一场 8.25
2018/08/29 DOTA
[01:46]2018完美盛典章节片——坚守
2018/12/17 DOTA
python删除不需要的python文件方法
2018/04/24 Python
Python实现模拟浏览器请求及会话保持操作示例
2018/07/30 Python
如何通过Python实现标签云算法
2019/07/02 Python
Python 依赖库太多了该如何管理
2019/11/08 Python
PyTorch加载自己的数据集实例详解
2020/03/18 Python
python 第三方库paramiko的常用方式
2021/02/20 Python
解决HTML5手机端页面缩放的问题
2017/10/27 HTML / CSS
普通员工辞职信
2014/01/17 职场文书
暑期研修感言
2014/02/17 职场文书
《桂花雨》教学反思
2014/04/12 职场文书
基层党员对照检查材料
2014/09/24 职场文书
详解PHP设计模式之依赖注入模式
2021/05/25 PHP
攻略丨滑雪究竟该选哪款对讲机?
2022/02/18 无线电
使用Redis实现分布式锁的方法
2022/06/16 Redis
mysql sock文件存储了什么信息
2022/07/15 MySQL