共计 12729 个字符,预计需要花费 32 分钟才能阅读完成。
虚构 DOM 的优缺点?
长处:
- 保障性能上限: 框架的虚构 DOM 须要适配任何下层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;然而比起粗犷的 DOM 操作性能要好很多,因而框架的虚构 DOM 至多能够保障在你不须要手动优化的状况下,仍然能够提供还不错的性能,即保障性能的上限;
- 无需手动操作 DOM: 咱们不再须要手动去操作 DOM,只须要写好 View-Model 的代码逻辑,框架会依据虚构 DOM 和 数据双向绑定,帮咱们以可预期的形式更新视图,极大进步咱们的开发效率;
- 跨平台: 虚构 DOM 实质上是 JavaScript 对象, 而 DOM 与平台强相干,相比之下虚构 DOM 能够进行更不便地跨平台操作,例如服务器渲染、weex 开发等等。
毛病:
- 无奈进行极致优化: 尽管虚构 DOM + 正当的优化,足以应答绝大部分利用的性能需求,但在一些性能要求极高的利用中虚构 DOM 无奈进行针对性的极致优化。
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
是对象的话,对象属于援用类型,会影响到所有的实例。所以为了保障组件不同的实例之间data
不抵触,data
必须是一个函数,
简版了解
// 1. 组件的渲染流程 调用 Vue.component -> Vue.extend -> 子类 -> new 子类
// Vue.extend 依据用户定义产生一个新的类
function Vue() {}
function Sub() { // 会将 data 存起来
this.data = this.constructor.options.data();}
Vue.extend = function(options) {
Sub.options = options; // 动态属性
return Sub;
}
let Child = Vue.extend({data:()=>({ name: 'zf'})
});
// 两个组件就是两个实例, 心愿数据互不感化
let child1 = new Child();
let child2 = new Child();
console.log(child1.data.name);
child1.data.name = 'poetry';
console.log(child2.data.name);
// 根不须要 任何的合并操作 根才有 vm 属性 所以他能够是函数和对象 然而组件 mixin 他们都没有 vm 所以我就能够判断 以后 data 是不是个函数
相干源码
// 源码地位 src/core/global-api/extend.js
export function initExtend (Vue: GlobalAPI) {Vue.extend = function (extendOptions: Object): Function {extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {validateComponentName(name)
}
const Sub = function VueComponent (options) {this._init(options)
}
// 子类继承大 Vue 父类的原型
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
if (Sub.options.props) {initProps(Sub)
}
if (Sub.options.computed) {initComputed(Sub)
}
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// create asset registers, so extended classes
// can have their private assets too.
ASSET_TYPES.forEach(function (type) {Sub[type] = Super[type]
})
// enable recursive self-lookup
if (name) {Sub.options.components[name] = Sub // 记录本人 在组件中递归本人 -> jsx
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// cache constructor
cachedCtors[SuperId] = Sub
return Sub
}
}
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 中有助于一致性;
v-show 与 v-if 有什么区别?
v-if 是 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是 惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简略得多——不论初始条件是什么,元素总是会被渲染,并且只是简略地基于 CSS 的“display”属性进行切换。
所以,v-if 实用于在运行时很少扭转条件,不须要频繁切换条件的场景;v-show 则实用于须要十分频繁切换条件的场景。
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),限度执行该操作的频率,并在失去最终后果前,设置中间状态。这些都是计算属性无奈做到的。
参考 前端进阶面试题具体解答
个别在哪个生命周期申请异步数据
咱们能够在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 曾经创立,能够将服务端端返回的数据进行赋值。
举荐在 created 钩子函数中调用异步申请,因为在 created 钩子函数中调用异步申请有以下长处:
- 能更快获取到服务端数据,缩小页面加载工夫,用户体验更好;
- SSR 不反对 beforeMount、mounted 钩子函数,放在 created 中有助于一致性。
diff 算法
工夫复杂度: 个树的齐全 diff
算法是一个工夫复杂度为 O(n*3)
,vue 进行优化转化成 O(n)
。
了解:
-
最小量更新,
key
很重要。这个能够是这个节点的惟一标识,通知diff
算法,在更改前后它们是同一个 DOM 节点- 扩大
v-for
为什么要有key
,没有key
会暴力复用,举例子的话轻易说一个比方挪动节点或者减少节点(批改 DOM),加key
只会挪动缩小操作 DOM。
- 扩大
- 只有是同一个虚构节点才会进行精细化比拟,否则就是暴力删除旧的,插入新的。
- 只进行同层比拟,不会进行跨层比拟。
diff 算法的优化策略:四种命中查找,四个指针
- 旧前与新前(先比结尾,后插入和删除节点的这种状况)
- 旧后与新后(比结尾,前插入或删除的状况)
- 旧前与新后(头与尾比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧后之后)
- 旧后与新前(尾与头比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧前之前)
过滤器的作用,如何实现一个过滤器
依据过滤器的名称,过滤器是用来过滤数据的,在 Vue 中应用 filters
来过滤数据,filters
不会批改数据,而是过滤数据,扭转用户看到的输入(计算属性 computed
,办法 methods
都是通过批改数据来解决数据格式的输入显示)。
应用场景:
- 须要格式化数据的状况,比方须要解决工夫、价格等数据格式的输入 / 显示。
- 比方后端返回一个 年月日的日期字符串 ,前端须要展现为 多少天前 的数据格式,此时就能够用
fliters
过滤器来解决数据。
过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。过滤器用在 插值表达式 {{}}
和 v-bind
表达式 中,而后放在操作符“|
”前面进行批示。
例如,在显示金额,给商品价格增加单位:
<li> 商品价格:{{item.price | filterPrice}}</li>
filters: {filterPrice (price) {return price ? ('¥' + price) : '--'
}
}
v-model 的原理?
咱们在 vue 我的项目中次要应用 v-model 指令在表单 input、textarea、select 等元素上创立双向数据绑定,咱们晓得 v-model 实质上不过是语法糖,v-model 在外部为不同的输出元素应用不同的属性并抛出不同的事件:
- text 和 textarea 元素应用 value 属性和 input 事件;
- checkbox 和 radio 应用 checked 属性和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件。
以 input 表单元素为例:
<input v-model='something'>
相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">
如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:
父组件:<ModelChild v-model="message"></ModelChild>
子组件:<div>{{value}}</div>
props:{value: String},
methods: {test1(){this.$emit('input', '小红')
},
},
双向数据绑定的原理
Vue.js 是采纳 数据劫持 联合 发布者 - 订阅者模式 的形式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时公布音讯给订阅者,触发相应的监听回调。次要分为以下几个步骤:
- 须要 observe 的数据对象进行递归遍历,包含子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变动
- compile 解析模板指令,将模板中的变量替换成数据,而后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,增加监听数据的订阅者,一旦数据有变动,收到告诉,更新视图
- Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,次要做的事件是: ①在本身实例化时往属性订阅器 (dep) 外面增加本人 ②本身必须有一个 update()办法 ③待属性变动 dep.notice()告诉时,能调用本身的 update()办法,并触发 Compile 中绑定的回调,则功成身退。
- MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听本人的 model 数据变动,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变动 -> 视图更新;视图交互变动(input) -> 数据 model 变更的双向绑定成果。
对 SSR 的了解
SSR 也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端实现,而后再把 html 间接返回给客户端
SSR 的劣势:
- 更好的 SEO
- 首屏加载速度更快
SSR 的毛病:
- 开发条件会受到限制,服务器端渲染只反对 beforeCreate 和 created 两个钩子;
- 当须要一些内部扩大库时须要非凡解决,服务端渲染应用程序也须要处于 Node.js 的运行环境;
- 更多的服务端负载。
Vue 中 computed 和 watch 有什么区别?
计算属性 computed:
(1)** 反对缓存 **,只有依赖数据发生变化时,才会从新进行计算函数;(2)计算属性内 ** 不反对异步操作 **;(3)计算属性的函数中 ** 都有一个 get**(默认具备,获取计算属性)** 和 set**(手动增加,设置计算属性)办法;(4)计算属性是主动监听依赖值的变动,从而动静返回内容。
侦听属性 watch:
(1)** 不反对缓存 **,只有数据发生变化,就会执行侦听函数;(2)侦听属性内 ** 反对异步操作 **;(3)侦听属性的值 ** 能够是一个对象,接管 handler 回调,deep,immediate 三个属性 **;(3)监听是一个过程,在监听的值变动时,能够触发一个回调,并 ** 做一些其余事件 **。
Vue 模版编译原理晓得吗,能简略说一下吗?
简略说,Vue 的编译过程就是将 template
转化为 render
函数的过程。会经验以下阶段:
- 生成 AST 树
- 优化
- codegen
首先解析模版,生成AST 语法树
(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。
Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对
,对运行时的模板起到很大的优化作用。
编译的最初一步是 将优化后的 AST 树转换为可执行的代码
。
怎么缓存以后的组件?缓存后怎么更新
缓存组件应用 keep-alive
组件,这是一个十分常见且有用的优化伎俩,vue3
中 keep-alive
有比拟大的更新,能说的点比拟多
思路
- 缓存用
keep-alive
,它的作用与用法 - 应用细节,例如缓存指定 / 排除、联合
router
和transition
- 组件缓存后更新能够利用
activated
或者beforeRouteEnter
- 原理论述
答复范例
- 开发中缓存组件应用
keep-alive
组件,keep-alive
是vue
内置组件,keep-alive
包裹动静组件component
时,会缓存不流动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,避免反复渲染DOM
<keep-alive>
<component :is="view"></component>
</keep-alive>
- 联合属性
include
和exclude
能够明确指定缓存哪些组件或排除缓存指定组件。vue3
中联合vue-router
时变动较大,之前是keep-alive
包裹router-view
,当初须要反过来用router-view
包裹keep-alive
<router-view v-slot="{Component}">
<keep-alive>
<component :is="Component"></component>
</keep-alive>
</router-view>
- 缓存后如果要获取数据,解决方案能够有以下两种
beforeRouteEnter
:在有vue-router 的
我的项目,每次进入路由的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){
next(vm=>{console.log(vm)
// 每次进入路由执行
vm.getData() // 获取数据})
},
actived
:在keep-alive
缓存的组件被激活的时候,都会执行actived
钩子
activated(){this.getData() // 获取数据
},
keep-alive
是一个通用组件,它外部定义了一个map
,缓存创立过的组件实例,它返回的渲染函数外部会查找内嵌的component
组件对应组件的vnode
,如果该组件在map
中存在就间接返回它。因为component
的is
属性是个响应式数据,因而只有它变动,keep-alive
的render
函数就会从新执行
双向绑定的原理是什么
咱们都晓得 Vue
是数据双向绑定的框架,双向绑定由三个重要局部形成
- 数据层(Model):利用的数据及业务逻辑
- 视图层(View):利用的展现成果,各类 UI 组件
- 业务逻辑层(ViewModel):框架封装的外围,它负责将数据与视图关联起来
而下面的这个分层的架构计划,能够用一个专业术语进行称说:MVVM
这里的管制层的外围性能便是“数据双向绑定”。天然,咱们只需弄懂它是什么,便能够进一步理解数据绑定的原理
了解 ViewModel
它的主要职责就是:
- 数据变动后更新视图
- 视图变动后更新数据
当然,它还有两个次要局部组成
- 监听器(
Observer
):对所有数据的属性进行监听 - 解析器(
Compiler
):对每个元素节点的指令进行扫描跟解析, 依据指令模板替换数据, 以及绑定相应的更新函数
能说下 vue-router 中罕用的 hash 和 history 路由模式实现原理吗?
(1)hash 模式的实现原理
晚期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简略,location.hash 的值就是 URL 中 # 前面的内容。比方上面这个网站,它的 location.hash 的值为 ‘#search’:
https://www.word.com#search
hash 路由模式的实现次要是基于上面几个个性:
- URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 局部不会被发送;
hash 值的扭转,都会在浏览器的拜访历史中减少一个记录。因而咱们能通过浏览器的回退、后退按钮管制 hash 的切换;
- 能够通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会产生扭转;或者应用 JavaScript 来对 loaction.hash 进行赋值,扭转 URL 的 hash 值;
- 咱们能够应用 hashchange 事件来监听 hash 值的变动,从而对页面进行跳转(渲染)。
(2)history 模式的实现原理
HTML5 提供了 History API 来实现 URL 的变动。其中做最次要的 API 有以下两个:history.pushState() 和 history.repalceState()。这两个 API 能够在不进行刷新的状况下,操作浏览器的历史纪录。惟一不同的是,前者是新增一个历史记录,后者是间接替换以后的历史记录,如下所示:
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
history 路由模式的实现次要基于存在上面几个个性:
- pushState 和 repalceState 两个 API 来操作实现 URL 的变动;
- 咱们能够应用 popstate 事件来监听 url 的变动,从而对页面进行跳转(渲染);
- history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时咱们须要手动触发页面跳转(渲染)。
函数式组件劣势和原理
函数组件的特点
- 函数式组件须要在申明组件是指定
functional:true
- 不须要实例化,所以没有
this
,this
通过render
函数的第二个参数context
来代替 - 没有生命周期钩子函数,不能应用计算属性,
watch
- 不能通过
$emit
对外裸露事件,调用事件只能通过context.listeners.click
的形式调用内部传入的事件 - 因为函数式组件是没有实例化的,所以在内部通过
ref
去援用组件时,理论援用的是HTMLElement
- 函数式组件的
props
能够不必显示申明,所以没有在props
外面申明的属性都会被主动隐式解析为prop
, 而一般组件所有未声明的属性都解析到$attrs
外面,并主动挂载到组件根元素下面 (能够通过inheritAttrs
属性禁止)
长处
- 因为函数式组件不须要实例化,无状态,没有生命周期,所以渲染性能要好于一般组件
- 函数式组件构造比较简单,代码构造更清晰
应用场景:
- 一个简略的展现组件,作为容器组件应用 比方
router-view
就是一个函数式组件 - “高阶组件”——用于接管一个组件作为参数,返回一个被包装过的组件
例子
Vue.component('functional',{ // 构造函数产生虚构节点的
functional:true, // 函数式组件 // data={attrs:{}}
render(h){return h('div','test')
}
})
const vm = new Vue({el: '#app'})
源码相干
// functional component
if (isTrue(Ctor.options.functional)) { // 带有 functional 的属性的就是函数式组件
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on // 处理事件
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn // 解决原生事件
// install component management hooks onto the placeholder node
installComponentHooks(data) // 装置组件相干钩子(函数式组件没有调用此办法,从而性能高于一般组件)
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 的性能所调用的办法
delete 和 Vue.delete 删除数组的区别
delete
只是被删除的元素变成了empty/undefined
其余的元素的键值还是不变。Vue.delete
间接删除了数组 扭转了数组的键值。