1、Vue是如何实现数据双向绑定的

1.1、实现双向绑定的基本原理

数据驱动:Vue会通过Dircetives指令,对DOM做一层封装,当数据发生改变会通知指令去修改对应的DOM,数据驱动DOM变化,DOM是数据的一种自然映射。Vue还会对操作进行监听,当视图发生改变时,vue监听到这些变化,从而改变数据,这样就形成了数据的双向绑定。
数据响应原理:当你把一个普通的 JavaScript 对象传给 Vue 实例的 data选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter,在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。

正如上面所说,vue实现数据双向绑定主要是采用数据劫持结合发布者-订阅者模式的方式。

数据劫持是通过Object.defineProperty()实现的,该函数为每个属性添加setter,getter 的方法,在数据发生改变时 setter 方法会被触发,然后发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 为每个属性添加 setter,getter 的方法。

vue的数据双向绑定主要通过三个模块完成:监听者Observer、订阅者Watcher、Compile解析模板指令。

(1)Observer监听 model 的数据变化,如果有变动的,就通知订阅者

(2)初始渲染页面、为节点绑定函数。通过 Compile 扫描和解析每个节点的相关指令,将模板中的变量替换成数据,并根据初始数据渲染页面视图。并且将每个指令对应的节点绑定函数,一旦视图发生交互,绑定的函数就被触发,改变数据。

(3)watcher 搭起了 observer 和 Compile 之间的通信桥梁,达到数据变化 —>视图更新,视图交互变化(input)—>数据 model 发生变更的双向绑定效果。

var vm = new Vue({
  data: {
    obj: { a: 1 }
  },
  created: function () {
    console.log(this.obj);
  }
});

打印Vue实例的data里的某个数据的某个属性,可以看到该属性含有 setter、getter 方法,由此可以得知,每个属性都被添加了setter、getter 方法。

实现mvvm主要包含两个方面,数据变化更新视图,视图变化更新数据:

view更新data通过事件监听即,比如 input 标签监听 'input' 事件就可以实现。关键点在于 data 如何更新view,当数据改变,如何更新视图的。重点是如何知道数据变了,而这可以由Observer实现,通过Object.defineProperty( )对属性设置一个set函数,当数据改变了就会来触发这个函数,然后只要将一些需要更新视图的方法放在这里面就可以实现data更新view了。

1.2、observe 的实现

利用Obeject.defineProperty()来为每个属性添加setter,getter 的方法实现监听属性变动。将需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter,这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。示例代码:

var data = {
name: 'kindeng'
};
observe(data);
data.name = 'dmq'; // 哈哈哈,监听到值变化了 kindeng --> dmq
function observe(data) {
if (!data || typeof data !== 'object') {
return;
}
// 取出所有属性遍历
Object.keys(data).forEach(function (key) {
defineReactive(data, key, data[key]);
});
}; function defineReactive(data, key, val) {
observe(val); // 监听子属性
Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, // 不能再define
get: function () {
return val;
},
set: function (newVal) {
console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal);
val = newVal;
}
});
}

通过上面代码就可以实现对每个属性进行监听。

1.3、通过JS实现简单的双向绑定

<body>
<div id="app">
<input type="text" id="txt">
<p id="show"></p>
</div>
</body>
<script type="text/javascript">
var obj = {}
Object.defineProperty(obj, 'txt', {
get: function () {
return obj
},
set: function (newValue) {
document.getElementById('txt').value = newValue
document.getElementById('show').innerHTML = newValue
}
})
document.addEventListener('keyup', function (e) {
obj.txt = e.target.value
})
</script>

上面代码中,首先 defineProperty 为每个属性添加 getter、setter 方法,当数据发生改变, setter 方法被触发,视图也发生改变。setter 里面的执行命令可以看做是一个订阅者 Watcher,将视图和数据连接起来了。最下面的代码为节点绑定方法可以看做是Compile的作用,为指令节点绑定方法,当发生视图交互时,函数被触发,数据被改变,Watcher 收到通知,视图也将发生改变。

2、浏览器渲染页面过程

(浏览器渲染引擎的渲染流程)

2.1、关键渲染路径

关键渲染路径是指浏览器从最初接收请求来的HTML、CSS、javascript等资源,然后解析、构建树、渲染布局、绘制,最后呈现给客户能看到的界面这整个过程。

所以浏览器的渲染过程主要包括以下几步:

  1. 解析HTML生成DOM树。
  2. 解析CSS生成CSSOM规则树。
  3. 将DOM树与CSSOM规则树合并在一起生成渲染树。
  4. 遍历渲染树开始布局,计算每个节点的位置大小信息。
  5. 将渲染树每个节点绘制到屏幕。

3、JS操作真实DOM的代价!

用我们传统的开发模式,原生JS或JQ操作DOM时,浏览器会从生成DOM树开始从头到尾执行一遍流程。在一次操作中,我需要更新10个DOM节点,浏览器收到第一个DOM请求后并不知道还有9次更新操作,因此会马上执行流程,最终执行10次。例如,第一次计算完,紧接着下一个DOM更新请求,这个节点的坐标值就变了,前一次计算为无用功。计算DOM节点坐标值等都是白白浪费的性能。即使计算机硬件一直在迭代更新,操作DOM的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户体验。

4、虚拟DOM的作用

虚拟DOM就是为了解决浏览器性能问题而被设计出来的。假如像上面所说的,若一次操作中有10次更新DOM的动作,会生成一个新的虚拟DOM,将新的虚拟DOM和旧的进行比较,然后将10次更新的 diff 内容保存到一个JS对象中,最终通过这个JS对象来更新真实DOM,由此只进行了一次操作真实DOM,避免大量无谓的计算量。所以,虚拟DOM的作用是将多个DOM操作合并成一个,并且将DOM操作先全部反映在JS对象中(操作内存中的JS对象比操作DOM的速度要更快),再将最终的JS对象映射成真实的DOM,交由浏览器去绘制。

5、实现虚拟DOM

虚拟DOM就是是用JS对象来代表节点,每次渲染都会生成一个VNode。当数据发生改变时,生成一个新的的VNode,通过 diff 算法和上一次渲染时用的VNode进行对比,生成一个对象记录差异,然后根据该对象来更新真实的DOM。原本要操作的DOM在vue这边还是要操作的,不过是统一计算出所有变化后统一更新一次DOM,进行浏览器DOM的一次性更新。

参考:https://baijiahao.baidu.com/s?id=1593097105869520145&wfr=spider&for=pchttps://www.jianshu.com/p/af0b398602bc

6、Vue 中路由的hash模式和history模式

Vue 中路由有 hash 模式和 history 模式,hash 模式带 # 号,history 没有这个 # 号,就是普通的 url 。可以通过在 router 中配置 mode 选项来切换模式。

Vue 中的路由是怎么实现的可以参考:https://segmentfault.com/a/1190000011967786

Vue 中路由的实现是通过监听 url 的改变,然后通过解析 url ,匹配上对应的组件进行渲染实现的。

在 hash 模式下,跳转路由导致后面 hash 值的变化,但这并不会导致浏览器向服务器发出请求。另外每次 hash 值的变化,还会触发 hashchange 这个事件,通过监听这个事件就能知道 hash 值的改变,并能解析出 url。在 hash 模式下,刷新页面和直接输入链接都不会导致浏览器发出请求。

history 模式的实现原理是通过HTML5中的两个方法:pushState 和 replaceState,这两个方法可以改变 url 地址且不会发送请求,由此可以跳转路由而不刷新页面,不发出请求。但是在 history 模式下,用户如果直接输入链接或者手动刷新时,浏览器还是会发出请求,而会导致服务器寻找该 url 路径下的对应的文件,而该路径下的文件往往不存在,所以会返回 404。为了避免这种情况,在使用 history 模式时,需要后端进行配合使用,配置在URL 匹配不到任何静态资源返回什么东西,比如可以配置在找不到文件时返回项目的主页面。

参考:https://www.cnblogs.com/xufeimei/p/10745353.html

Vue的思考扩展的更多相关文章

  1. 第3章-Vue.js 指令扩展 和 todoList练习

    一.学习目标 了解Vue.js指令的实现原理 理解v-model指令的高级用法 能够使用Vue.js 指令完成 todoList 练习(重点+难点) 二.todoList练习效果展示 2.1.效果图展 ...

  2. 2、Vue构造器和扩展

    1.VUE构造器简介 VUE构造器是一个非常重要的语法. 每个Vue.js应用都是通过构造函数Vue创建一个根实例. New了Vue对象,然后调用了这个vue对象的构造器,并向构造器传入了数据. 在实 ...

  3. vue 安装sass扩展

    1.创建一个基于 webpack 模板的新项目 $ vue init webpack myvue 1 2.在当前目录下,安装依赖 $ cd myvue $ npm install 1 2 3.安装sa ...

  4. Vue使用的扩展

    1.Bus(总线)实现非父子组件通信 Vue2.0提供了Vuex进行非父子组件之间的通信,但在简单的场景下,可以使用一个空的Vue实例作为中央事件总线. 实现代码示例: <div id=&quo ...

  5. vue开发chrome扩展,数据通过storage对象获取

    开发chrome插件时遇到一个问题,那就是单文件组件的data数据需要从chrome提供的storage对象中获取,但是 chrome.storage.sync.get 方法是异步获取数据的,需要通过 ...

  6. vue - Vue路由(扩展)

    忙里偷闲,还在学校,趁机把后面的路由多出来的知识点学完 十.缓存路由组件 让不展示的路由组件保持挂载,不被销毁 在我们的前面案例有一个问题,都知道vue的路由当我们切换一个路由后,另一个路由就会被销毁 ...

  7. Vue 向下扩展后就类似于 jQuery

    https://cn.vuejs.org/v2/guide/comparison.html

  8. Vue数据双向绑定探究

    前面的啰嗦话,写一点吧,或许就有点用呢 使用过vue的小伙伴都会感觉,哇,这个框架对开发者这么友好,简直都要笑出声了. 确实,使用过vue的框架做开发的人都会感觉到,以前写一大堆操作dom,bom的东 ...

  9. Vue.js——60分钟组件快速入门(上篇)

    组件简介 组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树: 那么什么是组件呢?组件可以扩展HTML ...

随机推荐

  1. web.xml 通过contextConfigLocation配置spring 的方式

    部署到tomcat后,src目录下的配置文件会和class文件一样,自动copy到应用的 classes目录下 spring的 配置文件在启动时,加载的是web-info目录下的application ...

  2. 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_07 缓冲流_2_BufferedOutputStream_字节缓冲

    子类 继承父类,这些方法都可以使用 必须写上flush,刷新数据数据才能写入到文件内

  3. 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_06 Properties集合_2_Properties集合中的方法store

    第一行是注释,第二行是时间,时间是自动加的 使用FileOutputStream. 写入中文会乱码

  4. iview在项目中遇到的坑

    1.下拉框选中某一项搜索发现总是搜不到,最后发现是选中后选中值后边莫名多了很长的空格,原因很简单,在代码中opction闭合标签和主体没有在一行. 2.iview+vue项目中,用百分比或者displ ...

  5. 双系统(win10+ubuntu)卸载Ubuntu系统

    之前装的双系统,Win10 和Ubuntu ,系统引导使用的是Ubuntu的Grup的引导, 直接删除Ubuntu会导致引导丢失,会很麻烦,win10直接会挂掉,后期恢复需要重建引导 安全删除思路,先 ...

  6. 20190902 On Java8 第十六章 代码校验

    第十六章 代码校验 你永远不能保证你的代码是正确的,你只能证明它是错的. 测试 测试覆盖率的幻觉 测试覆盖率,同样也称为代码覆盖率,度量代码的测试百分比.百分比越高,测试的覆盖率越大. 当分析一个未知 ...

  7. 14 (H5*) JS第4天 函数、作用域、预解析

    目录 1:函数的其他定义 2:函数作为参数 3:函数作为返回值 4:作用域 5:作用域链 6:预解析 7:预解析分段 复习 <script> /* * 复习: * 函数:把一些重复的代码封 ...

  8. linux 截取变量字符串

    STR=123456abc FINAL=`echo ${STR: -1}` 或者 FINAL=${STR: -1} 都可以让FINAL获得c这个最后一个字符   Linux 的字符串截取很有用.有八种 ...

  9. 移动端的设备提供了一个事件:orientationChange事件

    移动端的设备提供了一个事件:orientationChange事件:https://blog.csdn.net/gong1422425666/article/details/79001836

  10. HDU 5945 题解(DP)(单调队列)

    题面: Fxx and game Time Limit: 3000/1500 MS (Java/Others) Memory Limit: 131072/65536 K (Java/Others) T ...