浅谈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 相关文章推荐
JS+CSS实现模仿浏览器网页字符查找功能的方法
Feb 26 Javascript
JavaScript中利用各种循环进行遍历的方式总结
Nov 10 Javascript
angularjs创建弹出框实现拖动效果
Aug 25 Javascript
js检测离开或刷新页面时表单数据是否更改的方法
Aug 02 Javascript
Javascript中的对象和原型(二)
Aug 12 Javascript
BootstrapValidator不触发校验的实现代码
Sep 28 Javascript
老生常谈javascript变量的命名规范和注释
Sep 29 Javascript
解决Angular.Js与Django标签冲突的方案
Dec 20 Javascript
Angular2使用Guard和Resolve进行验证和权限控制
Apr 24 Javascript
详解JS获取HTML DOM元素的8种方法
Jun 17 Javascript
vue中使用echarts的示例
Jan 03 Vue.js
uniapp开发打包多端应用完整方法指南
Dec 24 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
四月新番又没了,《Re:从零开始的异世界生活》第二季延期至7月播出
2020/05/06 日漫
在JavaScript中调用php程序
2009/03/09 PHP
CI框架中zip类应用示例
2014/06/17 PHP
跟我学Laravel之安装Laravel
2014/10/15 PHP
PHP使用PDO抽象层获取查询结果的方法示例
2018/05/10 PHP
Yii框架日志记录Logging操作示例
2018/07/12 PHP
JXTree对象,读取外部xml文件数据,生成树的函数
2007/04/02 Javascript
js 创建快捷方式的代码(fso)
2010/11/19 Javascript
JavaScript flash复制库类 Zero Clipboard
2011/01/17 Javascript
innerHTML与jquery里的html()区别介绍
2012/10/12 Javascript
JS原型对象通俗&quot;唱法&quot;
2012/12/27 Javascript
js单词形式的运算符
2014/05/06 Javascript
PHP+MySQL+jQuery随意拖动层并即时保存拖动位置实例讲解
2015/10/09 Javascript
Bootstrap4一次重大更新 几乎涉及每行代码
2016/05/16 Javascript
js实现小窗口拖拽效果
2016/12/03 Javascript
浅谈JS获取元素的N种方法及其动静态讨论
2017/08/25 Javascript
React Native AsyncStorage本地存储工具类
2017/10/24 Javascript
[01:04:48]VGJ.S vs TNC Supermajor 败者组 BO3 第一场 6.6
2018/06/07 DOTA
python 获取本机ip地址的两个方法
2013/02/25 Python
Python复数属性和方法运算操作示例
2017/07/21 Python
Python使用回溯法子集树模板解决爬楼梯问题示例
2017/09/08 Python
python hbase读取数据发送kafka的方法
2018/12/27 Python
python递归法实现简易连连看小游戏
2020/03/25 Python
Python爬取365好书中小说代码实例
2020/02/28 Python
Django ORM filter() 的运用详解
2020/05/14 Python
python如何获得list或numpy数组中最大元素对应的索引
2020/11/16 Python
CSS3实现的炫酷菜单代码分享
2015/03/12 HTML / CSS
Javascript 高级手势使用介绍
2013/04/21 HTML / CSS
介绍一下write命令
2014/08/10 面试题
为什么需要版本控制
2016/10/28 面试题
外贸销售员求职的自我评价
2013/11/23 职场文书
人力资源管理系自荐信
2014/05/31 职场文书
党员三严三实对照检查材料
2014/10/13 职场文书
2014年宣传工作总结
2014/11/18 职场文书
先进基层党组织材料
2014/12/25 职场文书
2015年高考寄语或鼓励的话
2015/03/23 职场文书