浅谈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 相关文章推荐
让焦点自动跳转
Jul 01 Javascript
JavaScript方法和技巧大全
Dec 27 Javascript
JavaScript CSS菜单功能 改进版
Dec 20 Javascript
angularJS中$apply()方法详解
Jan 07 Javascript
基于JS实现的笛卡尔乘积之商品发布
May 13 Javascript
动态生成的DOM不会触发onclick事件的原因及解决方法
Aug 06 Javascript
JQuery Ajax WebService传递参数的简单实例
Nov 02 Javascript
AngularJs点击状态值改变背景色的实例
Dec 18 Javascript
vue使用laydate时间插件的方法
Nov 14 Javascript
详解vue路由篇(动态路由、路由嵌套)
Jan 27 Javascript
最简单的vue消息提示全局组件的方法
Jun 16 Javascript
vue-cli3 引入 font-awesome的操作
Aug 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
PHP Memcached + APC + 文件缓存封装实现代码
2010/03/11 PHP
iframe 父窗口和子窗口相互的调用方法集锦
2010/12/15 Javascript
Javascript this 的一些学习总结
2012/08/02 Javascript
javascript常见用法总结
2014/05/22 Javascript
JavaScript操作cookie类实例
2015/03/31 Javascript
基于jquery实现复选框全选,反选,全不选等功能
2015/10/16 Javascript
JS判断图片是否加载完成方法汇总(最新版)
2016/05/13 Javascript
jQuery实现的checkbox级联选择下拉菜单效果示例
2016/12/26 Javascript
如何提高Dom访问速度
2017/01/05 Javascript
JavaScript实现翻页功能(附效果图)
2017/02/16 Javascript
BootStrap中jQuery插件Carousel实现轮播广告效果
2017/03/27 jQuery
基于代数方程库Algebra.js解二元一次方程功能示例
2017/06/09 Javascript
Nodejs进阶之服务端字符编解码和乱码处理
2017/09/04 NodeJs
基于vue+axios+lrz.js微信端图片压缩上传方法
2019/06/25 Javascript
解决layer.open后laydate失效的问题
2019/09/06 Javascript
nodejs实现聊天机器人功能
2019/09/19 NodeJs
微信小程序自定义tabBar在uni-app的适配详解
2019/09/30 Javascript
VUE注册全局组件和局部组件过程解析
2019/10/10 Javascript
react+antd 递归实现树状目录操作
2020/11/02 Javascript
用python实现的可以拷贝或剪切一个文件列表中的所有文件
2009/04/30 Python
Python对象转JSON字符串的方法
2016/04/27 Python
Django卸载之后重新安装的方法
2017/03/15 Python
python解决pandas处理缺失值为空字符串的问题
2018/04/08 Python
Python简单过滤字母和数字的方法小结
2019/01/09 Python
解决Django no such table: django_session的问题
2020/04/07 Python
python文件及目录操作代码汇总
2020/07/08 Python
基于python tkinter的点名小程序功能的实例代码
2020/08/22 Python
香港草莓网土耳其网站:Strawberrynet TR
2017/03/02 全球购物
外贸主管求职简历的自我评价
2013/10/23 职场文书
元旦晚会感言
2014/03/12 职场文书
梅花魂教学反思
2014/04/25 职场文书
80后婚前协议书范本
2014/10/24 职场文书
2014司机年终工作总结
2014/12/05 职场文书
学校党员干部承诺书
2015/05/04 职场文书
css height属性中的calc方法详解
2021/06/03 HTML / CSS
简单聊聊TypeScript只读修饰符
2022/04/06 Javascript