浅谈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 相关文章推荐
对google个性主页的拖拽效果的js的完整注释[转]
Apr 10 Javascript
MC Dialog js弹出层 完美兼容多浏览器(5.6更新)
May 06 Javascript
关于跨站脚本攻击问题
Dec 22 Javascript
parentElement,srcElement的使用小结
Jan 13 Javascript
javascript setinterval 的正确语法如何书写
Jun 17 Javascript
Angularjs注入拦截器实现Loading效果
Dec 28 Javascript
AngularJS用户选择器指令实例分析
Nov 04 Javascript
移动端js图片查看器
Nov 17 Javascript
脚本div实现拖放功能(两种)
Feb 13 Javascript
jQuery中DOM操作原则实例分析
Aug 01 jQuery
浅析JavaScript预编译和暗示全局变量
Sep 03 Javascript
SpringBoot在yml配置文件中配置druid的操作
Nov 16 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数据备份
2014/05/19 PHP
php导入大量数据到mysql性能优化技巧
2014/12/29 PHP
PHP和MySql中32位和64位的整形范围是多少
2016/02/18 PHP
使用IE的地址栏来辅助调试Web页脚本
2007/03/08 Javascript
Js数组排序函数sort()介绍
2015/06/08 Javascript
jQuery AjaxUpload 上传图片代码
2016/02/02 Javascript
jQuery配合coin-slider插件制作幻灯片效果的流程解析
2016/05/13 Javascript
Vuejs第六篇之Vuejs与form元素实例解析
2016/09/05 Javascript
微信小程序 倒计时组件实现代码
2016/10/24 Javascript
百度搜索框智能提示案例jsonp
2016/11/28 Javascript
JavaScript转换数据库DateTime字段类型方法
2017/06/27 Javascript
微信小程序 获取javascript 里的数据
2017/08/17 Javascript
浅谈Node 调试工具入门教程
2018/03/20 Javascript
js中int和string数据类型互相转化实例
2019/01/16 Javascript
js实现select下拉框选择
2020/01/11 Javascript
js面向对象封装级联下拉菜单列表的实现步骤
2021/02/08 Javascript
详解ES6实现类的私有变量的几种写法
2021/02/10 Javascript
[19:14]DOTA2 HEROS教学视频教你分分钟做大人-维萨吉
2014/06/24 DOTA
[00:27]DOTA2荣耀之路2:Patience from zhou!
2018/05/24 DOTA
Python求一批字符串的最长公共前缀算法示例
2019/03/02 Python
django框架基于模板 生成 excel(xls) 文件操作示例
2019/06/19 Python
canvas拼图功能实现代码示例
2018/11/21 HTML / CSS
美国最大的珠宝首饰网上商城:Jewelry.com
2016/07/22 全球购物
南威尔士家居商店:Leekes
2016/10/25 全球购物
ECCO爱步官方旗舰店:丹麦鞋履品牌
2018/01/02 全球购物
Urban Outfitters德国官网:美国跨国生活方式零售公司
2018/05/21 全球购物
Onzie官网:美国时尚瑜伽品牌
2019/08/21 全球购物
复核员上岗演讲稿
2014/01/05 职场文书
大型车展策划方案
2014/02/01 职场文书
二年级学生期末评语
2014/12/26 职场文书
2015小学音乐教师个人工作总结
2015/07/21 职场文书
村主任当选感言
2015/08/01 职场文书
Python使用openpyxl批量处理数据
2021/06/23 Python
关于python类SortedList详解
2021/09/04 Python
Python OpenCV实现图形检测示例详解
2022/04/08 Python
openstack云计算keystone组件工作介绍
2022/04/20 Servers