浅谈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实现很酷的EMAIL地址添加功能实例
Feb 28 Javascript
浅析AngularJs HTTP响应拦截器
Dec 28 Javascript
AngularJS ng-blur 指令详解及简单实例
Jul 30 Javascript
JavaScript定义数组的三种方法(new Array(),new Array('x','y')
Oct 04 Javascript
浅谈Vue的基本应用
Dec 27 Javascript
使用base64对图片的二进制进行编码并用ajax进行显示
Jan 03 Javascript
JS判断指定dom元素是否在屏幕内的方法实例
Jan 23 Javascript
基于jQuery实现弹幕APP
Feb 10 Javascript
vue使用better-scroll实现下拉刷新、上拉加载
Nov 23 Javascript
微信小程序实现时间预约功能
Nov 27 Javascript
vue组件开发props验证的实现
Feb 12 Javascript
在js文件中引入(调用)另一个js文件的三种方法
Sep 11 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
MYSQL环境变量设置方法
2007/01/15 PHP
PHP+jquery实时显示网站在线人数的方法
2015/01/04 PHP
php实现购物车功能(上)
2020/07/23 PHP
PHP 与 UTF-8 的最佳实践详细介绍
2017/01/04 PHP
Win7环境下Apache连接MySQL提示连接已重置的解决办法
2017/05/09 PHP
PHP curl批处理及多请求并发实现方法分析
2018/08/15 PHP
细品javascript 寻址,闭包,对象模型和相关问题
2009/04/27 Javascript
ajax处理php返回json数据的实例代码
2013/01/24 Javascript
动态加载JS文件的三种方法
2013/11/08 Javascript
Bootstrap实现input控件失去焦点时验证
2016/08/04 Javascript
原生JS实现轮播效果+学前端的感受(防止走火入魔)
2016/08/21 Javascript
easyui-combobox 实现简单的自动补全功能示例
2016/11/08 Javascript
无阻塞加载js,防止因js加载不了影响页面显示的问题
2016/12/18 Javascript
vue组件父子间通信之综合练习(聊天室)
2017/11/07 Javascript
利用vue+elementUI实现部分引入组件的方法详解
2017/11/22 Javascript
js+canvas实现验证码功能
2020/09/21 Javascript
vue.js的状态管理vuex中store的使用详解
2019/11/08 Javascript
浅谈Vue 自动化部署打包上线
2020/06/14 Javascript
微信小程序抽奖组件的使用步骤
2021/01/11 Javascript
Python基于Tkinter实现的记事本实例
2015/06/17 Python
django js实现部分页面刷新的示例代码
2018/05/28 Python
Python实现的生产者、消费者问题完整实例
2018/05/30 Python
Python hashlib模块加密过程解析
2019/11/05 Python
Python Tornado核心及相关原理详解
2020/06/24 Python
python合并多个excel文件的示例
2020/09/23 Python
python 写一个性能测试工具(一)
2020/10/24 Python
python 使用paramiko模块进行封装,远程操作linux主机的示例代码
2020/12/03 Python
CSS3教程(10):CSS3 HSL声明设置颜色
2009/04/02 HTML / CSS
马德里著名的运动鞋商店:NOIRFONCE
2019/04/12 全球购物
新年主持词
2014/03/27 职场文书
《从现在开始》教学反思
2014/04/15 职场文书
法制宣传标语集锦
2014/06/25 职场文书
新学期感想
2015/08/10 职场文书
幼儿园保教工作总结2015
2015/10/15 职场文书
JS + HTML 罗盘式时钟的实现
2021/05/21 Javascript
Python如何利用pandas读取csv数据并绘图
2022/07/07 Python