Vue中key的作用
vue 中 key 值的作用能够分为两种状况来思考:
- 第一种状况是 v-if 中应用 key。因为 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。因而当应用 v-if 来实现元素切换的时候,如果切换前后含有雷同类型的元素,那么这个元素就会被复用。如果是雷同的 input 元素,那么切换前后用户的输出不会被革除掉,这样是不合乎需要的。因而能够通过应用 key 来惟一的标识一个元素,这个状况下,应用 key 的元素不会被复用。这个时候 key 的作用是用来标识一个独立的元素。
- 第二种状况是 v-for 中应用 key。用 v-for 更新已渲染过的元素列表时,它默认应用“就地复用”的策略。如果数据项的程序产生了扭转,Vue 不会挪动 DOM 元素来匹配数据项的程序,而是简略复用此处的每个元素。因而通过为每个列表项提供一个 key 值,来以便 Vue 跟踪元素的身份,从而高效的实现复用。这个时候 key 的作用是为了高效的更新渲染虚构 DOM。
key 是为 Vue 中 vnode 的惟一标记,通过这个 key,diff 操作能够更精确、更疾速
- 更精确:因为带 key 就不是就地复用了,在 sameNode 函数a.key === b.key比照中能够防止就地复用的状况。所以会更加精确。
- 更疾速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历形式更快
为什么vue组件中data必须是一个函数?
对象为援用类型,当复用组件时,因为数据对象都指向同一个data对象,当在一个组件中批改data时,其余重用的组件中的data会同时被批改;而应用返回对象的函数,因为每次返回的都是一个新对象(Object的实例),援用地址不同,则不会呈现这个问题。
Computed 和 Watch 的区别
对于Computed:
- 它反对缓存,只有依赖的数据产生了变动,才会从新计算
- 不反对异步,当Computed中有异步操作时,无奈监听数据的变动
- computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data申明过,或者父组件传递过去的props中的数据进行计算的。
- 如果一个属性是由其余属性计算而来的,这个属性依赖其余的属性,个别会应用computed
- 如果computed属性的属性值是函数,那么默认应用get办法,函数的返回值就是属性的属性值;在computed中,属性有一个get办法和一个set办法,当数据发生变化时,会调用set办法。
对于Watch:
- 它不反对缓存,数据变动时,它就会触发相应的操作
- 反对异步监听
- 监听的函数接管两个参数,第一个参数是最新的值,第二个是变动之前的值
- 当一个属性发生变化时,就须要执行相应的操作
监听数据必须是data中申明的或者父组件传递过去的props中的数据,当发生变化时,会触发其余操作,函数有两个的参数:
- immediate:组件加载立刻触发回调函数
- deep:深度监听,发现数据外部的变动,在简单数据类型中应用,例如数组中的对象发生变化。须要留神的是,deep无奈监听到数组和对象外部的变动。
当想要执行异步或者低廉的操作以响应一直的变动时,就须要应用watch。
总结:
- computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值产生扭转,下一次获取 computed 的值时才会从新计算 computed 的值。
- watch 侦听器 : 更多的是察看的作用,无缓存性,相似于某些数据的监听回调,每当监听的数据变动时都会执行回调进行后续操作。
使用场景:
- 当须要进行数值计算,并且依赖于其它数据时,应该应用 computed,因为能够利用 computed 的缓存个性,防止每次获取值时都要从新计算。
- 当须要在数据变动时执行异步或开销较大的操作时,应该应用 watch,应用 watch 选项容许执行异步操作 ( 拜访一个 API ),限度执行该操作的频率,并在失去最终后果前,设置中间状态。这些都是计算属性无奈做到的。
v-show 与 v-if 有什么区别?
v-if 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简略得多——不论初始条件是什么,元素总是会被渲染,并且只是简略地基于 CSS 的 “display” 属性进行切换。
所以,v-if 实用于在运行时很少扭转条件,不须要频繁切换条件的场景;v-show 则实用于须要十分频繁切换条件的场景。
Vue 的生命周期办法有哪些 个别在哪一步发申请
beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。在以后阶段 data、methods、computed 以及 watch 上的数据和办法都不能被拜访
created 实例曾经创立实现之后被调用。在这一步,实例已实现以下的配置:数据观测(data observer),属性和办法的运算, watch/event 事件回调。这里没有$el,如果非要想与 Dom 进行交互,能够通过 vm.$nextTick 来拜访 Dom
beforeMount 在挂载开始之前被调用:相干的 render 函数首次被调用。
mounted 在挂载实现后产生,在以后阶段,实在的 Dom 挂载结束,数据实现双向绑定,能够拜访到 Dom 节点
beforeUpdate 数据更新时调用,产生在虚构 DOM 从新渲染和打补丁(patch)之前。能够在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程
updated 产生在更新实现之后,以后阶段组件 Dom 已实现更新。要留神的是防止在此期间更改数据,因为这可能会导致有限循环的更新,该钩子在服务器端渲染期间不被调用。
beforeDestroy 实例销毁之前调用。在这一步,实例依然齐全可用。咱们能够在这时进行善后收尾工作,比方革除计时器。
destroyed Vue 实例销毁后调用。调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
activated keep-alive 专属,组件被激活时调用
deactivated keep-alive 专属,组件被销毁时调用
异步申请在哪一步发动?
能够在钩子函数 created、beforeMount、mounted 中进行异步申请,因为在这三个钩子函数中,data 曾经创立,能够将服务端端返回的数据进行赋值。
如果异步申请不须要依赖 Dom 举荐在 created 钩子函数中调用异步申请,因为在 created 钩子函数中调用异步申请有以下长处:
- 能更快获取到服务端数据,缩小页面 loading 工夫;
- ssr 不反对 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
Vue.js的template编译
简而言之,就是先转化成AST树,再失去的render函数返回VNode(Vue的虚构DOM节点),具体步骤如下:
首先,通过compile编译器把template编译成AST语法树(abstract syntax tree 即 源代码的形象语法结构的树状表现形式),compile是createCompiler的返回值,createCompiler是用以创立编译器的。另外compile还负责合并option。
而后,AST会通过generate(将AST语法树转化成render funtion字符串的过程)失去render函数,render的返回值是VNode,VNode是Vue的虚构DOM节点,外面有(标签名、子节点、文本等等)
参考 前端进阶面试题具体解答
MVC 和 MVVM 区别
MVC
MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计榜样
- Model(模型):是应用程序中用于解决应用程序数据逻辑的局部。通常模型对象负责在数据库中存取数据
- View(视图):是应用程序中解决数据显示的局部。通常视图是根据模型数据创立的
- Controller(控制器):是应用程序中解决用户交互的局部。通常控制器负责从视图读取数据,管制用户输出,并向模型发送数据
MVC 的思维:一句话形容就是 Controller 负责将 Model 的数据用 View 显示进去,换句话说就是在 Controller 外面把 Model 的数据赋值给 View。
MVVM
MVVM 新增了 VM 类
- ViewModel 层:做了两件事达到了数据的双向绑定 一是将【模型】转化成【视图】,行将后端传递的数据转化成所看到的页面。实现的形式是:数据绑定。二是将【视图】转化成【模型】,行将所看到的页面转化成后端的数据。实现的形式是:DOM 事件监听。
MVVM 与 MVC 最大的区别就是:它实现了 View 和 Model 的主动同步,也就是当 Model 的属性扭转时,咱们不必再本人手动操作 Dom 元素,来扭转 View 的显示,而是扭转属性后该属性对应 View 层显示会主动扭转(对应Vue数据驱动的思维)
整体看来,MVVM 比 MVC 精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不必再用选择器操作 DOM 元素。因为在 MVVM 中,View 不晓得 Model 的存在,Model 和 ViewModel 也察看不到 View,这种低耦合模式进步代码的可重用性
留神:Vue 并没有齐全遵循 MVVM 的思维 这一点官网本人也有阐明
那么问题来了 为什么官网要说 Vue 没有齐全遵循 MVVM 思维呢?
- 严格的 MVVM 要求 View 不能和 Model 间接通信,而 Vue 提供了$refs 这个属性,让 Model 能够间接操作 View,违反了这一规定,所以说 Vue 没有齐全遵循 MVVM。
v-if 和 v-show 的区别
v-if 在编译过程中会被转化成三元表达式,条件不满足时不渲染此节点。
v-show 会被编译成指令,条件不满足时管制款式将对应节点暗藏 (display:none)
写过自定义指令吗 原理是什么
指令实质上是装璜器,是 vue 对 HTML 元素的扩大,给 HTML 元素减少自定义性能。vue 编译 DOM 时,会找到指令对象,执行指令的相干办法。
自定义指令有五个生命周期(也叫钩子函数),别离是 bind、inserted、update、componentUpdated、unbind
1. bind:只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。2. inserted:被绑定元素插入父节点时调用 (仅保障父节点存在,但不肯定已被插入文档中)。3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变动。通过比拟更新前后的绑定值,能够疏忽不必要的模板更新。4. componentUpdated:被绑定元素所在模板实现一次更新周期时调用。5. unbind:只调用一次,指令与元素解绑时调用。原理
1.在生成 ast 语法树时,遇到指令会给以后元素增加 directives 属性
2.通过 genDirectives 生成指令代码
3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子
4.当执行指令对应钩子函数时,调用对应指令定义的办法
vue初始化页面闪动问题
应用vue开发时,在vue初始化之前,因为div是不归vue管的,所以咱们写的代码在还没有解析的状况下会容易呈现花屏景象,看到相似于{{message}}的字样,尽管个别状况下这个工夫很短暂,然而还是有必要让解决这个问题的。
首先:在css里加上以下代码:
[v-cloak] { display: none;}如果没有彻底解决问题,则在根元素加上style="display: none;" :style="{display: 'block'}"
如何从实在DOM到虚构DOM
波及到Vue中的模板编译原理,次要过程:
- 将模板转换成
ast树,ast用对象来形容实在的JS语法(将实在DOM转换成虚构DOM) - 优化树
- 将
ast树生成代码
Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题 ?你能说说如下代码的实现原理么?
1)Vue为什么要用vm.$set() 解决对象新增属性不能响应的问题
- Vue应用了Object.defineProperty实现双向数据绑定
- 在初始化实例时对属性执行 getter/setter 转化
- 属性必须在data对象上存在能力让Vue将它转换为响应式的(这也就造成了Vue无奈检测到对象属性的增加或删除)
所以Vue提供了Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)
2)接下来咱们看看框架自身是如何实现的呢?
Vue 源码地位:vue/src/core/instance/index.js
export function set (target: Array<any> | Object, key: any, val: any): any { // target 为数组 if (Array.isArray(target) && isValidArrayIndex(key)) { // 批改数组的长度, 防止索引>数组长度导致splcie()执行有误 target.length = Math.max(target.length, key) // 利用数组的splice变异办法触发响应式 target.splice(key, 1, val) return val } // key 曾经存在,间接批改属性值 if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ // target 自身就不是响应式数据, 间接赋值 if (!ob) { target[key] = val return val } // 对属性进行响应式解决 defineReactive(ob.value, key, val) ob.dep.notify() return val}咱们浏览以上源码可知,vm.$set 的实现原理是:
- 如果指标是数组,间接应用数组的 splice 办法触发相应式;
- 如果指标是对象,会先判读属性是否存在、对象是否是响应式,
- 最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决
defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法
生命周期钩子是如何实现的
Vue 的生命周期钩子外围实现是利用公布订阅模式先把用户传入的的生命周期钩子订阅好(外部采纳数组的形式存储)而后在创立组件实例的过程中会一次执行对应的钩子办法(公布)
相干代码如下
export function callHook(vm, hook) { // 顺次执行生命周期对应的办法 const handlers = vm.$options[hook]; if (handlers) { for (let i = 0; i < handlers.length; i++) { handlers[i].call(vm); //生命周期外面的this指向以后实例 } }}// 调用的时候Vue.prototype._init = function (options) { const vm = this; vm.$options = mergeOptions(vm.constructor.options, options); callHook(vm, "beforeCreate"); //初始化数据之前 // 初始化状态 initState(vm); callHook(vm, "created"); //初始化数据之后 if (vm.$options.el) { vm.$mount(vm.$options.el); }};Vue 组件间通信有哪几种形式?
Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信。
(1)props / $emit 实用 父子组件通信 这种办法是 Vue 组件的根底,置信大部分同学耳闻能详,所以此处就不举例开展介绍。
(2)ref 与 $parent / $children 实用 父子组件通信
ref:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例$parent / $children:拜访父 / 子实例
(3)EventBus ($emit / $on) 实用于 父子、隔代、兄弟组件通信 这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件。
(4)$attrs/$listeners 实用于 隔代组件通信
$attrs:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 ( class 和 style 除外 )。当一个组件没有申明任何prop时,这里会蕴含所有父作用域的绑定 ( class 和 style 除外 ),并且能够通过v-bind="$attrs"传入外部组件。通常配合inheritAttrs选项一起应用。$listeners:蕴含了父作用域中的 (不含 .native 润饰器的)v-on事件监听器。它能够通过v-on="$listeners"传入外部组件
(5)provide / inject 实用于 隔代组件通信 先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量。 provide / inject API 次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系。 (6)Vuex 实用于 父子、隔代、兄弟组件通信 Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store” 基本上就是一个容器,它蕴含着你的利用中大部分的状态 ( state )。
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
- 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。
Vue template 到 render 的过程
vue的模版编译过程次要如下:template -> ast -> render函数
vue 在模版编译版本的码中会执行 compileToFunctions 将template转化为render函数:
// 将模板编译为render函数const { render, staticRenderFns } = compileToFunctions(template,options//省略}, this)CompileToFunctions中的次要逻辑如下∶ (1)调用parse办法将template转化为ast(形象语法树)
constast = parse(template.trim(), options)- parse的指标:把tamplate转换为AST树,它是一种用 JavaScript对象的模式来形容整个模板。
- 解析过程:利用正则表达式程序解析模板,当解析到开始标签、闭合标签、文本的时候都会别离执行对应的 回调函数,来达到结构AST树的目标。
AST元素节点总共三种类型:type为1示意一般元素、2为表达式、3为纯文本
(2)对动态节点做优化
optimize(ast,options)这个过程次要剖析出哪些是动态节点,给其打一个标记,为后续更新渲染能够间接跳过动态节点做优化
深度遍历AST,查看每个子树的节点元素是否为动态节点或者动态节点根。如果为动态节点,他们生成的DOM永远不会扭转,这对运行时模板更新起到了极大的优化作用。
(3)生成代码
const code = generate(ast, options)generate将ast形象语法树编译成 render字符串并将动态局部放到 staticRenderFns 中,最初通过 new Function(` render`) 生成render函数。
说说Vue的生命周期吧
什么时候被调用?
- beforeCreate :实例初始化之后,数据观测之前调用
- created:实例创立万之后调用。实例实现:数据观测、属性和办法的运算、
watch/event事件回调。无$el. - beforeMount:在挂载之前调用,相干
render函数首次被调用 - mounted:了被新创建的
vm.$el替换,并挂载到实例下来之后调用改钩子。 - beforeUpdate:数据更新前调用,产生在虚构DOM从新渲染和打补丁,在这之后会调用改钩子。
- updated:因为数据更改导致的虚构DOM从新渲染和打补丁,在这之后会调用改钩子。
- beforeDestroy:实例销毁前调用,实例依然可用。
- destroyed:实例销毁之后调用,调用后,Vue实例批示的所有货色都会解绑,所有事件监听器和所有子实例都会被移除
每个生命周期外部能够做什么?
- created:实例曾经创立实现,因为他是最早触发的,所以能够进行一些数据、资源的申请。
- mounted:实例曾经挂载实现,能够进行一些DOM操作。
- beforeUpdate:能够在这个钩子中进一步的更改状态,不会触发重渲染。
- updated:能够执行依赖于DOM的操作,然而要防止更改状态,可能会导致更新无线循环。
- destroyed:能够执行一些优化操作,清空计时器,解除绑定事件。
ajax放在哪个生命周期?:个别放在 mounted 中,保障逻辑统一性,因为生命周期是同步执行的, ajax 是异步执行的。复数服务端渲染 ssr 同一放在 created 中,因为服务端渲染不反对 mounted 办法。 什么时候应用beforeDestroy?:以后页面应用 $on ,须要解绑事件。分明定时器。解除事件绑定, scroll mousemove 。
了解Vue运行机制全局概览
全局概览
首先咱们来看一下笔者画的外部流程图。
大家第一次看到这个图肯定是一头雾水的,没有关系,咱们来一一讲一下这些模块的作用以及调用关系。置信讲完之后大家对Vue.js外部运行机制会有一个大略的意识。
初始化及挂载
在
new Vue()之后。 Vue 会调用_init函数进行初始化,也就是这里的init过程,它会初始化生命周期、事件、 props、 methods、 data、 computed 与 watch 等。其中最重要的是通过Object.defineProperty设置setter与getter函数,用来实现「 响应式 」以及「 依赖收集 」,前面会具体讲到,这里只有有一个印象即可。初始化之后调用
$mount会挂载组件,如果是运行时编译,即不存在 render function 然而存在 template 的状况,须要进行「 编译 」步骤。
编译
compile编译能够分成 parse、optimize 与 generate 三个阶段,最终须要失去 render function。
1. parse
parse 会用正则等形式解析 template 模板中的指令、class、style等数据,造成AST。
2. optimize
optimize的次要作用是标记 static 动态节点,这是 Vue 在编译过程中的一处优化,前面当update更新界面时,会有一个patch的过程, diff 算法会间接跳过动态节点,从而缩小了比拟的过程,优化了patch的性能。
3. generate
generate是将 AST 转化成render function字符串的过程,失去后果是render的字符串以及 staticRenderFns 字符串。
- 在经验过
parse、optimize与generate这三个阶段当前,组件中就会存在渲染VNode所需的render function了。
响应式
接下来也就是 Vue.js 响应式外围局部。
这里的getter跟setter曾经在之前介绍过了,在init的时候通过Object.defineProperty进行了绑定,它使得当被设置的对象被读取的时候会执行getter函数,而在当被赋值的时候会执行setter函数。
- 当
render function被渲染的时候,因为会读取所需对象的值,所以会触发getter函数进行「 依赖收集 」,「 依赖收集 」的目标是将观察者Watcher对象寄存到以后闭包中的订阅者Dep的subs中。造成如下所示的这样一个关系。
在批改对象的值的时候,会触发对应的setter,setter告诉之前「 依赖收集 」失去的 Dep 中的每一个 Watcher,通知它们本人的值扭转了,须要从新渲染视图。这时候这些 Watcher 就会开始调用update来更新视图,当然这两头还有一个patch的过程以及应用队列来异步更新的策略,这个咱们前面再讲。
Virtual DOM
咱们晓得,render function会被转化成VNode节点。Virtual DOM其实就是一棵以 JavaScript 对象( VNode 节点)作为根底的树,用对象属性来形容节点,实际上它只是一层对实在 DOM 的形象。最终能够通过一系列操作使这棵树映射到实在环境上。因为 Virtual DOM 是以 JavaScript 对象为根底而不依赖实在平台环境,所以使它具备了跨平台的能力,比如说浏览器平台、Weex、Node 等。
比如说上面这样一个例子:
{ tag: 'div', /*阐明这是一个div标签*/ children: [ /*寄存该标签的子节点*/ { tag: 'a', /*阐明这是一个a标签*/ text: 'click me' /*标签的内容*/ } ]}渲染后能够失去
<div> <a>click me</a></div>这只是一个简略的例子,实际上的节点有更多的属性来标记节点,比方 isStatic (代表是否为动态节点)、 isComment (代表是否为正文节点)等。
更新视图
- 后面咱们说到,在批改一个对象值的时候,会通过
setter -> Watcher -> update的流程来批改对应的视图,那么最终是如何更新视图的呢? - 当数据变动后,执行 render function 就能够失去一个新的 VNode 节点,咱们如果想要失去新的视图,最简略粗犷的办法就是间接解析这个新的
VNode节点,而后用innerHTML间接全副渲染到实在DOM中。然而其实咱们只对其中的一小块内容进行了批改,这样做仿佛有些「 节约 」。 - 那么咱们为什么不能只批改那些「扭转了的中央」呢?这个时候就要介绍咱们的「
patch」了。咱们会将新的VNode与旧的VNode一起传入patch进行比拟,通过 diff 算法得出它们的「 差别 」。最初咱们只须要将这些「 差别 」的对应 DOM 进行批改即可。
再看全局
回过头再来看看这张图,是不是大脑中曾经有一个大略的脉络了呢?
用VNode来形容一个DOM构造
虚构节点就是用一个对象来形容一个实在的DOM元素。首先将 template (实在DOM)先转成 ast , ast 树通过 codegen 生成 render 函数, render 函数里的 _c 办法将它转为虚构dom
理解nextTick吗?
异步办法,异步渲染最初一步,与JS事件循环分割严密。次要应用了宏工作微工作(setTimeout、promise那些),定义了一个异步办法,屡次调用nextTick会将办法存入队列,通过异步办法清空以后队列。
computed 的实现原理
computed 实质是一个惰性求值的观察者。
computed 外部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立即求值,同时持有一个 dep 实例。
其外部通过 this.dirty 属性标记计算属性是否须要从新求值。
当 computed 的依赖状态产生扭转时,就会告诉这个惰性的 watcher,
computed watcher 通过 this.dep.subs.length 判断有没有订阅者,
有的话,会从新计算,而后比照新旧值,如果变动了,会从新渲染。 (Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 从新渲染,实质上是一种优化。)
没有的话,仅仅把 this.dirty = true。 (当计算属性依赖于其余数据时,属性并不会立刻从新计算,只有之后其余中央须要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)个性。)