浅谈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 相关文章推荐
jquery ajax提交表单数据的两种方式
Nov 24 Javascript
对 lightbox JS 图片控件进行了一下改造, 使其他支持复杂的图片说明
Mar 20 Javascript
js操作模态窗口及父子窗口间相互传值示例
Jun 09 Javascript
AngularJS实现数据列表的增加、删除和上移下移等功能实例
Sep 05 Javascript
chrome下判断点击input上标签还是其余标签的实现方法
Sep 18 Javascript
javascript 中设置window.location.href跳转无效问题解决办法
Feb 09 Javascript
JS sort方法基于数组对象属性值排序
Jul 10 Javascript
解决vue无法侦听数组及对象属性的变化问题
Jul 17 Javascript
js实现滑动进度条效果
Aug 21 Javascript
JavaScript经典案例之简易计算器
Aug 24 Javascript
javascript函数式编程基础
Sep 15 Javascript
Javascript之datagrid查询详解
Sep 15 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+dbfile开发小型留言本
2006/10/09 PHP
PHP数据类型之布尔型的介绍
2013/04/28 PHP
PHP合并数组+号和array_merge的区别
2015/06/25 PHP
Ajax+PHP实现的删除数据功能示例
2019/02/12 PHP
基于PHP实现邮箱验证激活过程详解
2020/10/28 PHP
用jQuery扩展自写的 UI导航
2010/01/13 Javascript
node.js中的fs.closeSync方法使用说明
2014/12/17 Javascript
javascript表格隔行变色加鼠标移入移出及点击效果的方法
2015/04/10 Javascript
JavaScript操作XML/HTML比较常用的对象属性集锦
2015/10/30 Javascript
完美JQuery图片切换效果的简单实现
2016/07/21 Javascript
BootStrap实现轮播图效果(收藏)
2016/12/30 Javascript
详解vue父子模版嵌套案例
2017/03/04 Javascript
jQuery 中msgTips 顶部弹窗效果实现代码
2017/08/14 jQuery
Three.js实现浏览器变动时进行自适应的方法
2017/09/26 Javascript
JavaScript实现短信倒计时60s
2017/10/09 Javascript
js判断文件类型大小并给出提示的实现方法
2018/01/03 Javascript
基于D3.js实现时钟效果
2018/07/17 Javascript
jQuery实现表单动态添加数据并提交的方法
2018/07/19 jQuery
vue-cli3自动消除console.log()的调试信息方式
2020/10/21 Javascript
JavaScript实现鼠标移入随机变换颜色
2020/11/24 Javascript
[39:02]DOTA2亚洲邀请赛 3.31 小组赛 B组 Mineski vs VGJ.T
2018/04/01 DOTA
[01:01:14]完美世界DOTA2联赛PWL S2 SZ vs Rebirth 第一场 11.21
2020/11/23 DOTA
浅谈MySQL中的触发器
2015/05/05 Python
python对json的相关操作实例详解
2017/01/04 Python
python SQLAlchemy的Mapping与Declarative详解
2019/07/04 Python
django 实现简单的插入视频
2020/04/07 Python
来自美国主售篮球鞋的零售商店:KICKSUSA
2017/11/28 全球购物
英国最大的在线照明商店:Litecraft
2020/08/31 全球购物
机电专业毕业生推荐信
2013/11/10 职场文书
生物科学专业职业规划书范文
2014/02/11 职场文书
《雕塑之美》教学反思
2014/04/24 职场文书
助人为乐好少年事迹材料
2014/08/18 职场文书
2014年社区卫生工作总结
2014/12/18 职场文书
事业单位考察材料范文
2014/12/25 职场文书
生日赠语
2015/06/23 职场文书
培训学校2015年度工作总结
2015/07/20 职场文书