VNode有哪些属性?
Vue内部定义的Vnode对象包含了以下属性:
__v_isVNode: true,内部属性,有该属性表示为Vnode
__v_skip: true,内部属性,表示跳过响应式转换,reactive转换时会根据此属性进行判断
isCompatRoot?: true,用于是否做了兼容处理的判断
type: VNodeTypes,虚拟节点的类型
props: (VNodeProps & ExtraProps) | null,虚拟节点的props
key: string | number | null,虚拟阶段的key,可用于diff
ref: VNodeNormalizedRef | null,虚拟阶段的引用
scopeId: string | null,仅限于SFC(单文件组件),在设置currentRenderingInstance当前渲染实例时,一期设置
slotScopeIds: string[] | null,仅限于单文件组件,与单文件组件的插槽有关
children: VNodeNormalizedChildren,子节点
component: ComponentInternalInstance | null,组件实例
dirs: DirectiveBinding[] | null,当前Vnode绑定的指令
transition: TransitionHooks | null,TransitionHooks
Vue2.0为什么不能检查数组的变化,该怎么解决?
我们都知道Vue2.0对于响应式数据的实现有一些不足:
- 无法检测数组/对象的新增
- 无法检测通过索引改变数组的操作
无法检测数组/对象的新增?
Vue检测数据的变动是通过Object.defineProperty实现的,所以无法监听数组的操作时可以理解的,因为是在构造函数中就已经为所有属性做了这个检测绑定操作。
无法检测通过索引改变数组的操作。即vm.items[indexOfItem] = newValue?
官方文档中对应这两点都是简要的概括为:由于JavaScript的限制无法实现,而Object.defineProperty是实现检测数据改变的方案,这个限制是指Object.defineProperty
小结:是出于对性能原因的考虑,没有去实现它。而不是不能实现。
对于对象而言,每一次的数据变更都会对对象的属性进行一次枚举,一般对象本身的属性数量有限,所以对于遍历枚举等方式产生的性能损耗可以忽略不计,但是对于数组而言,数组包含的元素是可能达到成千上万,假设对于每一次数组元素的更新都触发了枚举/遍历,其带来的性能损耗将与获得的用户体验不成正比,故vue无法检测数组的变动。
不过Vue3.0用proxy代替了defineProperty之后就解决了这个问题。
解决方案
数组:
this.$set(array, index, data)
//这是个深度的修改,某些情况下可能导致你不希望的结果,因此最好还是慎用
this.dataArr = this.originArr
this.$set(this.dataArr, 0, {data: '修改第一个元素'})
console.log(this.dataArr)
console.log(this.originArr) //同样的 源数组也会被修改 在某些情况下会导致你不希望的结果
splice
//因为splice会被监听有响应式,而splice又可以做到增删改。
利用临时变量进行中转
let tempArr = [...this.targetArr]
tempArr[0] = {data: 'test'}
this.targetArr = tempArr
对象:
1、this.$set(obj, key ,value) - 可实现增、改
2、watch时添加deep:true深度监听,只能监听到属性值的变化,新增、删除属性无法监听
this.$watch('blog', this.getCatalog, {
deep: true
// immediate: true // 是否第一次触发
});
3、watch时直接监听某个key
watch: {
'obj.name'(curVal, oldVal) {
// TODO
}
}
说说Vue页面的渲染流程?
在Vue核心中除了响应式原理外,视图渲染也是重中之重。我们都知道每次更新数据,都会走视图渲染的逻辑,而这当中牵扯的逻辑也是十分繁琐。
本题主要解析的是初始化视图渲染流程,将会了解到从挂载组件开始,Vue是如何构建VNode,又是如何将VNode转为真实节点并挂载到页面。
初始化调用$mount挂载组件。
初始化调用$mount挂载组件。
_render开始构建VNode,核心方法为createElement,一般会创建普通的VNode,遇到组件就创建组件类型的VNode,否则就是未知标签的VNode,构建完成传递给_update。
patch阶段根据VNode创建真实节点树,核心方法为createElm,首先遇到组件类型的VNode,内部会执行$mount,再走一遍相同的流程。普通节点类型则创建一个真实节点,如果他有子节点开始递归调用createElm,使用insert插入子节点,直到没有子节点就填充内容节点。最后递归完成后,同样也是使用insert将整个节点树插入到页面中,再将旧的根节点移除。
react和vue有什么区别?
React是由Facebook创建的JavaScript UI框架,React推广了Virtual DOM(虚拟DOM)并创造了JSX语法。JSX语法的出现允许我们在JavaScript中书写html代码。
Vue是尤雨溪开发的,Vue使用了模版系统而不是JSX,因其实模版系统都是使用普通的HTML,所以对应用的升级更方便、容易,而不需要整体重构。
Vue相较于React更容易上手,如果是一个有一定开发经验的开发者,甚至都不需要花额外的时间去学习,直接一遍开发一遍查文档即可。
VUE 与 React 区别
React的思路是HTML in JavaScript也可以说是All in JavaScript,通过JavaScript来生成HTML,所以设计了JSX语法,还有通过JS来操作CSS,社区的styled-component、JSS等。
Vue是把HTML,CSS,JavaScript组合到一起,用各自的处理方式,Vue有单文件组件,可以把HTML、CSS、JS写到一个文件中,HTML提供了模版引擎来处理。
React整体是函数式的思想,在React中是单向数据流,推崇结合immutable来实现数据不可变。
而Vue的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立Watcher来监听,当属性变化的时候,响应式的更新对应的虚拟DOM。
如上,所以React的性能优化需要手动去做,而Vue的性能优化是自动的,但是Vue的响应式机制也有问题,就是当state特别多的时候,Watcher会很多,会导致卡顿。
React 与 VUE 共同点
React 与 VUE 存在很多共同点,例如他们都是JavaScript的UI框架,专注于创造前端的富应用。不同于早期的JavaScript框架功能齐全,React与Vue只有框架的骨架,其他的功能如路由、状态管理等是框架分离的组件。
优势
React
- 灵活性和响应性:它提供最大的灵活性和响应能力。
- 丰富的JavaScript库:来自世界各地的贡献者正在努力添加更多功能。
- 可扩展性:由于其灵活的结果和可扩展性,React已被证明对大型应用程序更好。
- 不断发展:React得到了Facebook专业开发人员的支持,他们不断寻求改进方法。
- Web或移动平台:React提供React Native平台,可通过相同的React组件模型为IOS和Android开发本机呈现的应用程序。
Vue
- 易于使用:Vue.js包含基于HTML的标准模版,可以更轻松的使用和修改现有程序。
- 更顺畅的集成:无论是单页应用程序还是复杂的Web界面,Vue.js都可以更平滑地集成更小的部件,而不会对整个系统产生任何影响。
- 更好的性能,更小的尺寸:它占用更少的空间,并且往往比其他框架提供更好的性能。
- 精心编写的文档:通过详细的文档提供简单的学习曲线,无需额外的知识;HTML和JavaScript将完成工作。
- 适应性:整体声音设计和架构使其成为一种流行的JavaScript框架。
- 他提供无障碍的迁移,简单有效的结构和可重用的模版。
vue中computed和watch的区别
computed 计算属性依赖其他属性,由其他属性计算而来的。
在官方文档中,还强调了 computed 一个重要的特点,就是 computed 带有缓存功能。
computed 内定义的 function 只执行一次,仅当初始化显示或者相关的 data、props 等属性数据发生变化的时候调用;
而 computed 属性值默认会缓存计算结果,计算属性是基于它们的响应式依赖进行缓存的;
只有当 computed 属性被使用后,才会执行 computed 的代码,在重复的调用中,只要依赖数据不变,直接取缓存中的计算结果。只有依赖型数据发生改变,computed 才会重新计算。
watch 监听属性通过 vm 对象的 $watch() 或 watch 配置来监听 Vue 实例上的属性变化,或某些特定数据的变化,然后执行某些具体的业务逻辑操作。当属性变化时,回调函数自动调用,在函数内部进行计算。其可以监听的数据来源:data,props,computed 内的数据。
注: 监听函数有两个参数,第一个参数是最新的值,第二个参数是输入之前的值,顺序一定是新值,旧值,如果只写一个参数,那就是最新属性值。
在使用时选择 watch 还是 computed,还有一个参考点就是官网说的:当需要在数据变化时执行异步或开销较大的操作时,watch方式是最有用的。所以 watch 一定是支持异步的。
上面仅限监听简单数据类型,监听复杂数据类型就需要用到深度监听 deep。
**deep:**为了发现对象内部值的变化,可以在选项参数中指定 deep: true。注意监听数组的变更不需要这么做。
深度监听虽然可以监听到对象的变化,但是无法监听到对象里面哪个具体属性的变化。这是因为它们的引用指向同一个对象/数组。Vue 不会保留变更之前值的副本。
若果要监听对象的单个属性的变化,有两种方法:
1.直接监听对象的属性
若果要监听对象的单个属性的变化,有两种方法:
1.直接监听对象的属性
2.与 computed 属性配合使用,computed 返回想要监听的属性值,watch 用来监听
computed: {
firstNameChange() {
return this.fullName.firstName
}
},
watch: {
firstNameChange() {
console.log(this.fullName)
}
}
总结:
watch和computed都是以vue的依赖追踪机制为基础的,当某一个依赖型数据(简单理解为放在data等对象下的示例数据)发生变化时,所以依赖这个数据的相关数据会自动发生变化,即自动调用相关的函数,来实现数据的变动。
当依赖的值变化时,在watch中,是可以做一些复杂的操作的,而computed中的依赖,仅仅是一个值依赖于另一个值,是值上的依赖。
应用场景:
computed:用于处理复杂的逻辑运算;一个数据受一个或多个数据影响;用来处理watch和methods无法处理的,或处理起来不方便的情况。例如处理模版中的复杂表达式、购物车里面的商品数量和总金额之间的变化关系等。
watch:用来处理当一个属性发生变化时,需要执行某些具体的业务逻辑操作,或要在数据变化时执行异步或开销较大的操作;一个数据改变影响多个数据。例如用了监控路由、input输入框值的特殊处理等。
区别:
computed
- 初始化显示或者相关的data、props等属性数据发生变化的时候调用;
- 计算属性不在data中,它是基于data或者props中的数据通过计算得到的一个新值,这个新值根据已知值的变化而变化。
- 在computed属性对象中定义计算属性的方法,和取data对象里的数据属性一样,以属性访问的形式调用
- 如果computed属性值是函数,那么默认会走get方法,必须要有一个返回值,函数的返回值就是属性的属性值
- computed属性值默认会缓存计算结果,在重复的调用中,只要依赖数据不变,直接取缓存中的计算结果,只有依赖数据发生改变,computed才会重新计算。
- 在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
watch
- 主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作,可以看做是computed和methods的结合体
- 可以监听的数据来源:data、props、computed内的数据
- watch支持异步
- 不支持缓存,监听的数据改变,直接会触发相应的操作;
- 监听函数有两个参数,第一个参数是最新的值,第二个参数是输入之前的值,顺序一定是新值,旧值。
computed怎么实现的缓存?
- 初始化data和computed,分别代理其set及get方法,对data中的所有属性生产唯一的dep实例。
- 对computed中的sum生成唯一watcher,并保存在vm._computedWatchers中
- 执行render函数时会访问sum属性,从而执行initComputed时定义的getter方法,会将Dep.target指向sum的watcher,并调用该属性具体方法sum
- sum方法中访问this.count,即会调用this.count代理的get方法,将this.count的dep加入sum的watcher,同时该dep中的subs添加这个watcher。
- 设置vm.count=2,调用count代理的set方法触发dep的notify方法,因为是computed属性,只是将watcher中的dirty设置为true。
- 最后一步vm.sum,访问其get方法时,得知sum的watcher.dirty为true,调用其watcher.evaluate()方法获取新的值。