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 发生变更的双向绑定效果。

  1. var vm = new Vue({
  2.   data: {
  3.     obj: { a: 1 }
  4.   },
  5.   created: function () {
  6.     console.log(this.obj);
  7.   }
  8. });

打印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,那么就能监听到了数据变化。示例代码:

  1. var data = {
  2. name: 'kindeng'
  3. };
  4. observe(data);
  5. data.name = 'dmq'; // 哈哈哈,监听到值变化了 kindeng --> dmq
  6. function observe(data) {
  7. if (!data || typeof data !== 'object') {
  8. return;
  9. }
  10. // 取出所有属性遍历
  11. Object.keys(data).forEach(function (key) {
  12. defineReactive(data, key, data[key]);
  13. });
  14. };
  15.  
  16. function defineReactive(data, key, val) {
  17. observe(val); // 监听子属性
  18. Object.defineProperty(data, key, {
  19. enumerable: true, // 可枚举
  20. configurable: false, // 不能再define
  21. get: function () {
  22. return val;
  23. },
  24. set: function (newVal) {
  25. console.log('哈哈哈,监听到值变化了 ', val, ' --> ', newVal);
  26. val = newVal;
  27. }
  28. });
  29. }

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

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

  1. <body>
  2. <div id="app">
  3. <input type="text" id="txt">
  4. <p id="show"></p>
  5. </div>
  6. </body>
  7. <script type="text/javascript">
  8. var obj = {}
  9. Object.defineProperty(obj, 'txt', {
  10. get: function () {
  11. return obj
  12. },
  13. set: function (newValue) {
  14. document.getElementById('txt').value = newValue
  15. document.getElementById('show').innerHTML = newValue
  16. }
  17. })
  18. document.addEventListener('keyup', function (e) {
  19. obj.txt = e.target.value
  20. })
  21. </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. jsonify返回中文乱码的问题

    参考博客: 解决flask中文乱码的问题 返回内容如图: 实际填写的内容: 从网上找答案,结果很多都是说配置app.config,如下: app.config['JSON_AS_ASCII'] 但我试 ...

  2. VMware vSphere 虚拟化简介

    目录 目录 vSphere 简介 vSphere 提供的工具 vCenter vCenter 的功能 vCenter 管理界面上提供的操作功能 HOST CLUSTER TEMPLATE Virtua ...

  3. APlayer 媒体播放引擎

    APlayer媒体播放引擎 Windows平台的播放内核 封闭式 DirectShow 架构,不受系统解码环境干扰 全媒体文件格式支持 丰富的媒体文件传输协议(http/https/ftp/mms/r ...

  4. maximize_window()最大化浏览器和刷新当前页面refresh()

    from selenium import webdriverdriver = webdriver.Firefox()driver.get("https://www.baidu.com&quo ...

  5. oracle--序列&视图&索引&视图&可视化操作&分页&数据库备份

    --oracle学习内容--oracle的管理系统学习--oracle的数据管理学习--oracle的用户管理--oracle二维表管理--oracle的其他知识 --oracle的序列.视图.索引 ...

  6. 实现atoi

    1. 去掉首位空格 2. 判断首位是否有正负号 3. 判断各位是否是0~9,有其他字符直接返回当前结果   public class Solution { public int atoi(String ...

  7. 一次特殊的“VARCHAR转numeric失败”错误记录

    今天接触到一个很有意思的问题.当我在执行一条INSERT的sql语句时,他总是报字符串转数字类型失败. 问题 首先,该表中的所有数字类型的字段都是非必填,其次,每个数字类型的字段都有默认值.最令我感到 ...

  8. Java初始化块及执行顺序

    理解 初始化块又称为代码块.属于类中的第四大成员.本质上是一个方法,它也有方法体,但没有方法名,没有参数,没有返回,而且也不是通过对象或类名显式调用,而是通过隐式调用 是构造器的补充 语法 [修饰符] ...

  9. [2019杭电多校第二场][hdu6602]Longest Subarray(线段树)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6602 题目大意为求最长的区间,满足C种数字在区间内要么不出现,要么出现的次数都不小于K. 大致的分析一 ...

  10. HNUSTOJ-1696 简单验证码识别(模拟)

    1696: 简单验证码识别 时间限制: 2 Sec  内存限制: 128 MB 提交: 148  解决: 44 [提交][状态][讨论版] 题目描述 验证码是Web系统中一种防止暴力破解的重要手段.其 ...