Vue之双向绑定

双向数据绑定

将DOM与Vue实例的data数据绑定到一起,彼此间互相影响。

  • 数据的变化会引起DOM的改变
  • DOM的改变也会引起数据的变化

原理

当把一个普通的Javascript对象传入Vue实例作为data选项时,Vue会遍历这个对象的所有属性,并使用Object.defineProperty( )来把每个属性转为setter和getter。这些setter和getter对用户来说不可见,但是在内部会让Vue能够追踪依赖,在属性被访问和修改时通知变更。
每个组件实例都对应一个watcher实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后,当依赖项的sette触发时,会通知watcher,从而使它关联的组件重新渲染。

什么是响应式:简单来说,当你的数据发生变化时,视图也进行相应的更新。

响应式数据

只有data中的数据(程序创建时data里的属性)才是响应式的,动态添加进来的数据默认为非响应式的。

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <div id ="app">
        <p>{{stu}}</p>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el:"#app",
            data: {
                stu: {
                    name:"Jona",
                    age:19
                }
            }
        })
        vm.stu.width = "666";
        Vue.set(vm.stu,"gender","male");
    </script>
</body>
</html>

此时,如果在console中修改stu.width的值,视图不会发生更新。

这主要因为,data中的属性,Vue会把它通过Object.defineProperty()修改转成setter和getter,因此当修改时,会点用属性的setter方法,实现视图立即更新。而对于非响应式数据,即动态添加的,由于在Vue中,DOM的更新是异步的,因此视图不会立即更新。

添加响应式数据的方式

  • 使用Vue.set(object, key, value)
    • 主要适用于添加单个属性
    • 也可以使用vm.$set()实例方法
  • 使用Object.assign()
    • 主要使用添加多个
    • eg: vm.stu = Object.assign({}, vm.stu, {width:120, height:100})

异步DOM更新

Vue在更新DOM时是异步更新的。只要侦听到数据变化,Vue会开启一个队列,并缓冲在同一事件循环中所有的数据变更。如果一个watcher被多次触发,只会被推入队列中一次。这样会去除重复数据以避免不必要的计算和DOM操作。在下一个事件循环“tick”中,Vue会刷新队列并执行实际(已去重的)工作。Vue在内部对异步队列尝试使用原生的Promise.then( ), MutationObserver和 setImmediate , 如果执行环境不支持,则会采用setTimeout(fn , o)代替。

例如,当设置vm.someData = "new value",该组件不会立即进行重新渲染。当刷新队列时,组件会在下一个事件循环"tick"中更新。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)。在组件内使用 vm.$nextTick() 实例方法也特别方便。因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上。

Vue.component('example', {
  template: '<span>{{ message }}</span>',
  data: function () {
    return {
      message: '未更新'
    }
  },
  methods: {
    updateMessage: function () {
      this.message = '已更新'
      console.log(this.$el.textContent) // => '未更新'
      this.$nextTick(function () {
        console.log(this.$el.textContent) // => '已更新'
      })
    }
  }
})

Event Loop

js是单线程语言,即所有任务需要排队,一个任务只有等到前一个任务执行完毕之后才能执行。如果钱一个任务需要花费大量时间,后一个任务就不得不一直等待。
一些时候,当执行IO操作等这些耗时,但却未占用CPU的任务时,如果等当前耗时等待的任务执行完之后再执行下一个任务,会带来效率上的问题,而且也不友好。因此,设计者把这些等待中的任务挂起,从而可以执行后续任务。等挂起的任务有结果了,再把挂起的任务执行下去。
于是,js中的任务分为两种:同步任务异步任务
- 同步任务:在主线程上排列执行的任务,按顺序排列执行,一个任务只有前一个任务执行完毕之后才会执行。
- 异步任务:不进入主线程,而进入“任务队列”的任务。只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
异步任务执行机制

  • 所有同步任务都在主线程上执行,形成一个执行栈。
  • 主线程之外还有一个“任务队列”。只要异步任务有了结果,就在任务队列中放置一个事件。
  • 执行栈中的同步任务执行完毕后,系统就会读取任务队列。那些对应的异步任务,就结束了等待状态,进入执行栈,开始执行。
  • 主线程不断重复以上过程。

事件和回调函数

任务队列是一个事件的队列,IO设备完成一项任务就会在任务队列中添加一个事件,表示相应的任务可以进入执行栈了。这些事件,除了IO事件,还包括用户事件。
只要指定过回调函数,这些事件发生时就会进入任务队列,等待主线程读取。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
在任务队列中的任务进入主线程时,主线程首先要检查执行时间,因为对于setTimeout(),setTimeIntervel()这类定时器,只有到了规定时间才能返回主线程。

            console.log("1");
            setTimeout(function(){
                console.log("2")
            });
            (function(){
                console.log("3");
                setTimeout(function(){
                    console.log("4");
                },1000)
            })();
            console.log("5");
            console.log("6");
            (function(){
                console.log("7");
                setTimeout(function(){
                    console.log("8");
                })
            })();
在这里插入图片描述

这里的2, 4, 8 均在任务队列中,当执行栈中的任务执行完之后,由于对2没有设置推迟时间,且它在队列的最前端,因此先打印2。队列中第二个,由于设置了1000ms,而8没有设置推迟时间,因此先打印8,等到了1000ms时才会打印4。

任务队列

任务队列分为microtask 和 macortask,通常称为微任务和宏任务。

  • 执行的优先级: 主线程任务 > micortask > macrotask
  • macrotask有:setTimeout, setTimeInterval , setImmediate等
  • microtask有:Promise, MutationObserver等

Vue的nextTick

Vue在内部尝试对异步队列使用原生的setImmediate Promise.then和MessageChannel,如果当前执行环境不支持,就采用setTimeout(fn, 0)代替。


在这里插入图片描述

每次执行栈的同步任务执行完毕,就会去任务队列中取出完成的异步任务,队列中又分为microtasks queues和宏任务队列等到把microtasks queues所有的microtasks都执行完毕,注意是所有的,他才会从宏任务队列中取事件。等到把队列中的事件取出一个,放入执行栈执行完成,就算一次循环结束,之后event loop还会继续循环,他会再去microtasks queues执行所有的任务,然后再从宏任务队列里面取一个,如此反复循环。

console.log(1);
            setTimeout(function(){
                console.log(2)
            });

            console.log(3);
            new Promise((resolve, reject) => {
                console.log(4);
                resolve()
            }).then(() => {
                console.log(5)
            }).then(() => {
                console.log(8)
                show()
            })

            function show()
            {
             console.log(9);
             setTimeout(function(){
                console.log(10)
             });
             console.log(11);
             new Promise((resolve, reject) => {
                console.log(12);
                resolve()
             }).then(() => {
                console.log(13)
             })
            }

            setTimeout(function() {
                console.log(6)
            })
            console.log(7)
            }
在这里插入图片描述

也就是说,Vue会把里面的数据变更(非data中的属性)通过Promise.then等方法加入到任务队列中, vue中的nextTick 也会加入到任务队列中,因此,在 nextTick之前的数据变更可以在nextTick中访问到变更后的数据

参考文献

  1. node基础面试事件环?微任务、宏任务?一篇带你飞
  2. Event Loop是个什么玩意:从 Vue 的 nextTick 说起
  3. JavaScript 运行机制详解:再谈Event Loop
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353