Vue 面试题
Vue
Vue
MVVM
MVVM是Model-View-ViewModal的缩写,是一种脱胎于 MVC 模式的设计模式 - 核心是数据双向绑定
Model 代表数据层,负责存放业务相关的数据;
View 代表视图层,负责在页面上展示数据;
ViewModel 业务逻辑层,作用是同步 View 和 Model 之间的关联;
其实现同步关联的核心是DOM Listeners和 Data Bindings两个工具。DOMListeners 工具用于监听 View 中 DOM 的变化,并会选择性的传给 Model;Data Bindings 工具用于监听 Model 数据变化,并将其更新给 View。
SPA
- SPA(single-page application):单页应用 - 通过动态_重写当前页面_来与用户交互
- MPA(MultiPage-page application):多页应用 - 访问另一个页面的时候,都需要重新加载html、css、js文件
优点:
- 具有桌面应用的即时性、网站的可移植性和可访问性
- 用户体验好、快,内容的改变不需要重新加载整个页面
- 良好的前后端分离,分工更明确
缺点:
- 不利于搜索引擎的抓取
- 首次渲染速度相对较慢
什么是vue的响应式
vue数据响应式设计的初衷是为了实现数据和函数(渲染也就是函数)的联动;
vue的双向绑定原理是什么?关键点?
vue 双向数据绑定主要讲的是数据和视图的双向绑定:数据和视图同步,数据发生变化 视图跟着变化,视图变化 数据也随之发生改变;
是通过 数据劫持 结合 发布订阅模式的方式来实现的;
数据劫持:核心是通过 **Object.defineProperty(**obj, prop, descriptor**)**** **方法(ES6新增)对数据进行劫持,可以监听到数据的变化。当数据发生变化时,可以触发相应的处理函数(getter\setter方法),通知订阅者更新视图;当视图中的元素发生变化时 会触发相应的事件,然后同步新的数据。这样就完成了双向绑定
- obj(要定义其上属性的对象)
- prop(要定义或修改的属性)
- descriptor(具体的改变方法)
就是用这个方法定义一个值,当调用时我们使用了它里面的 **get **方法,当我们给这个属性赋值时,同时又调用了里面的 **set **方法
**发布订阅模式:**Vue通过发布订阅模式来实现双向数据绑定。当数据发生变化时,Vue会发布一个事件,订阅该事件的组件会收到通知,从而更新视图。
SPA首屏加载速度慢的怎么解决
分成两大部分:资源加载优化 和 页面渲染优化
- 减小入口文件积
- 静态资源本地缓存
- UI框架按需加载
- 图片资源的压缩
- 组件重复打包
- 开启GZip压缩
- 使用SSR
vue优缺点
| 优点 | 缺点 |
|---|---|
| + 无刷新体验; + 组件化开发思想; + 数据双向绑定,数据视图结构分离 + 轻量级框架:只关注视图层 + 虚拟 DOM加载 HTML 节点,运行效率高。 | + 不支持低版本的浏览器,最低只支持到IE9; + 不利于SEO的优化(如果要支持SEO,建议通过服务端来进行渲染组件); + 第一次加载首页耗时相对长一些; + 不可以使用浏览器的导航按钮需要自行实现前进、后退。 |
vue的两个核心点
数据驱动、组件系统
- 数据驱动:ViewModel,保证数据和视图的一致性。
- 组件系统:应用类UI可以看作全部是由组件树构成的。
Vue中的diff算法
diff 算法是一种通过同层的树节点进行比较的高效算法:作用于虚拟 dom 渲染成真实 dom 的新旧 VNode 节点比较
其有两个特点:
- 比较只会在同层级进行, 不会跨层级比较
- 在diff比较的过程中,循环从两边向中间比较
[组件通信(传值)](https://www.yuque.com/hutaoao/blog/fele98?singleDoc# 《Vue组件之间的通信方式》)
- 通过 props 传递:父组件向子组件传递数据
- 通过 $emit 触发自定义事件:子组件向父组件传递数据
- 使用 ref:父组件在使用子组件的时候设置ref,通过设置子组件ref来获取数据
- EventBus:兄弟组件传值
- $parent 或 **$root:**通过共同祖辈$parent或者$root搭建通信桥连
- $attrs 与 $listeners:祖先传递数据给子孙
- Provide 与 Inject:在祖先组件定义provide属性,返回传递的值,在后代组件通过inject接收组件传递过来的值
- Vuex:
vue2 生命周期
Vue实例从创建到销毁的过程,就是生命周期。总共分为8个阶段 第一次页面加载时会触发
beforeCreate,created,beforeMount,mounted
1)创建阶段
beforeCreate(创建前)实例刚被创建,此时不能访问 data、methods、refcreated(创建后)data数据、methods中的方法已可访问,dom未创建,ref 仍为 undefined,$el 尚不可用。
2)挂载阶段
beforeMount(载入前)完成了模版的编译,但未挂载到页面中 - dom未创建mounted— (载入后)dom已创建,能获取到 $ref 属性,可用于获取访问数据和dom元素
注意!!! mounted 不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 vm.$nextTick:
3)更新阶段
beforeUpdate(更新前)此时data 中的状态只是最新的,当时页面上显示的数据还是旧的updated(更新后)此时data中的状态值和界面上显示的数据都是最新的
4)卸载阶段
beforeDestroy(卸载前**)准备销毁,实例属性方法仍可使用 - **用于一些定时器或订阅的取消destroyed(卸载后)所有内容均不可使用
针对 keep-alive 组件还有两个钩子函数: activated:在被 keep-alive 缓存的组件激活时调用。 deactivated:在被 keep-alive 缓存的组件停用时调用。 还有一个错误处理捕获函数: errorCaptured:在捕获到一个来自子孙组件的错误时调用。
vue3 生命周期
- setup() : 开始创建组件,在 beforeCreate 和 created 之前执行,创建的是 data 和 method
- onBeforeMount() : 组件挂载到节点上之前执行的函数;
- onMounted() : 组件挂载完成后执行的函数;
- onBeforeUpdate(): 组件更新之前执行的函数;
- onUpdated(): 组件更新完成之后执行的函数;
- onBeforeUnmount(): 组件卸载之前执行的函数;
- onUnmounted(): 组件卸载完成后执行的函数;
- onActivated(): 被包含在
中的组件,会多出两个生命周期钩子函数,被激活时执行; - onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行;
- onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数。
Vue2 的 beforeCreate 、created 两个钩子被setup()钩子来替代。
Vue 子组件和父组件执行顺序
- 组件的调用顺序都是先父后子,渲染完成的顺序是先子后父。
- 组件的销毁操作是先父后子,销毁完成的顺序是先子后父。
加载渲染过程:
- 父组件 beforeCreate
- 父组件 created
- 父组件 beforeMount
- 子组件 beforeCreate
- 子组件 created
- 子组件 beforeMount
- 子组件 mounted
- 父组件 mounted
更新过程:
- 父组件 beforeUpdate
- 子组件 beforeUpdate
- 子组件 updated
- 父组件 updated
销毁过程:
- 父组件 beforeDestroy
- 子组件 beforeDestroy
- 子组件 destroyed
- 父组件 destoryed
数据请求在created和mouted的区别
created 是在组件实例一旦创建完成的时候立刻调用,这时候页面dom节点并未生成;
mounted 是在页面dom节点渲染完毕之后就立刻执行的。
触发时机上 created 是比 mounted 要更早的,两者的相同点:都能拿到实例对象的属性和方法。
讨论这个问题本质就是触发的时机,放在mounted中的请求有可能导致页面闪动(因为此时页面dom结构已经生成),但如果在页面加载前完成请求,则不会出现此情况。建议对页面内容的改动放在created生命周期当中。
为什么 Vue 组件中 data 必须是一个函数
为了保证组件的 独立性 和 可复用性。
如果 data 是一个对象,当复用组件时,因为 data 都会指向同一个引用类型地址,其中一个组件的 data 一旦发生修改,则其他复用的组件中的 data 也会被一并修改。(因为组件是用来复用的,JS 里对象是引用关系,这样作用域没有隔离)
如果 data 是一个返回对象的函数,因为每次重用组件时返回的都是一个新对象,引用地址不同,便不会出现如上问题。
v-show与v-if的区别
v-if 是真实的条件渲染(局部编译/卸载的过程)- 较高的切换消耗;
v-show 则只是简单地基于 CSS display 切换 - 更高的初始渲染消耗;
如果需要频繁切换使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好
常用的指令
v-show 与 v-if:条件渲染
v-model:让表单元素和数据实现双向绑定(映射关系)
v-html:更新元素的innerHTML
v-on:click:可以简写为@click,@绑定一个事件。
v-for:列表渲染
v-bind:当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM语法
v-bind:title=“msg”简写:title=”msg”
的作用
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态、避免重复渲染。
两个生命周期:
- deactivated:组件被切换时
- activated:组件被缓存时
如何获取dom
ref=”domName” 用法:this.$refs.domName
为什么使用key
需要使用key来给每个节点做一个唯一标识,Diff算法就可以更准确/更快的识别此节点。
作用主要是为了高效的更新虚拟DOM。
Vue中双向数据绑定是如何实现的(响应式原理)
vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的,也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;
核心:双向数据绑定其核心是 **Object.defineProperty()** 方法。
v-if和v-for的优先级
当 v-if 与 v-for 一起使用时:
在vue2中**:v-for 比 v-if 更高的优先级**,这意味着 v-if 将分别重复运行于每个 v-for 循环中,将造成很大的性能浪费;
在vue3中:v-if 比 v-for 的优先级更高,这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名。
computed 和 watch 区别
| 计算属性 ‘computed’ | 侦听属性 ‘watch’ | |
|---|---|---|
| 作用不同 | 解决模板语法渲染代码冗余问题 | 监听data中某一个数据的变化 |
| 缓存机制不同 | 支持缓存,只有依赖数据发生变化时,才会重新进行计算函数; | 不支持缓存,只要数据发生变化,就会执行侦听函数; |
| 语法不同 | 有返回值;都有一个 get 和 set | 没有返回值;侦听属性的值可以是一个对象 |
| 是否支持异步 | 不支持异步操作 | 支持异步操作 |
| 监听数量不同 | 可以监听多个数据变化 | 只能监听一个数据变化 |
| 初始执行时机不同 | 在页面一加载的时候就会执行一次 | 在绑定时执行一次 |
1
2
3
4
5
6
7
8
9
10
watch: {
obj: {
//handler接收两个参数(newVal:新值,oldVal:旧值
handler: function(newVal, oldVal){
console.log(newVal);
},
deep: true,//设置为true时会监听对象内部值的变化;
immediate: true//设置为true时会立即以表达式的当前值触发回调;
}
}
常用的修饰符
表单修饰符
.lazy在我们填完信息,光标离开标签的时候,才会将值赋予给value,也就是在change事件之后再进行信息同步.trim自动过滤用户输入的首空格字符,而中间的空格不会过滤.number自动将用户的输入值转为数值类型,但如果这个值无法被parseFloat解析,则会返回原来的值
事件修饰符
.stop:阻止冒泡;相当于调用了event.stopPropagation方法.prevent:阻止默认行为;相当于调用了event.preventDefault方法.self:仅绑定元素自身可触发;
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。 因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击
.once:事件只触发一次
鼠标按钮修饰符
.left** **左键点击.right** **右键点击.middle中键点击
键盘修饰符
键盘修饰符是用来修饰键盘事件(onkeyup,onkeydown)的,有如下:
keyCode存在很多,但vue为我们提供了别名,分为以下两种:
- 普通键(enter、tab、delete、space、esc、up…)
- 系统修饰键(ctrl、alt、meta、shift…)
只有按键为keyCode的时候才触发
<input type="text" @keyup.keyCode="shout()">
v-bind修饰符
.async对 props 进行一个双向绑定- .prop
- .camel
vue自定义指令
**全局注册:**通过 Vue.directive 方法进行注册
Vue.directive 第一个参数是指令的名字(不需要写上v-前缀),第二个参数可以是对象数据,也可以是一个指令函数
1
2
3
4
5
6
7
8
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能
}
})
**局部注册:**通过在组件 options 选项中设置 directive 属性
1
2
3
4
5
6
7
8
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能
}
}
}
应用场景
- 表单防止重复提交
- 图片懒加载
- 一键 Copy的功能
SSR
SSR也就是** 服务端渲染**;也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端。
SSR有着更好的SEO、并且首屏加载速度更快等优点。不过它也有一些缺点,比如我们的开发条件会受到限制,服务器端渲染只支持beforeCreate和created两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js的运行环境。还有就是服务器会有更大的负载需求。
Vue的性能优化
编码阶段
- 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
- v-if和v-for不能连用
- 如果需要使用v-for给每项元素绑定事件时使用事件代理
- SPA 页面采用keep-alive缓存组件
- 在更多的情况下,使用v-if替代v-show
- key保证唯一(key的作用主要是为了高效的更新虚拟DOM)
- 使用路由懒加载、异步组件
- 第三方模块按需导入
- 长列表动态加载
- 图片懒加载
SEO优化
- 预渲染
- 服务端渲染SSR
打包优化
- 压缩代码
- 使用cdn加载第三方模块
- 多线程打包happypack
- splitChunks抽离公共文件
- sourceMap优化
用户体验
- 骨架屏
- PWA
vue-router导航钩子(守卫)
- 全局守卫:在导航触发前调用。
- 路由守卫:在路由配置上定义。
- 组件守卫:在组件中定义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//1:全局钩子:beforeEach(to,form,next) 跳转前拦截、afterEach(to,form) 跳转后拦截
const router = new VueRouter({ ... });
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) {
next({ name: 'Login' });
} else {
next();
}
})
router.afterEach((to, from) => {
// ...
})
//2:路由独享钩子:beforeEnter(to,form,next)
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
//3、组件内钩子
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) { //(2.2 新增)
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
vue-router的两种模式
- hash模式:即地址栏 URL 中的 # 符号;
- history模式:window.history对象打印出来可以看到里边提供的方法和记录长度。利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持)。
active-class的作用
active-class是vue-router模块的router-link组件中的属性,用来做选中样式的切换。
NextTick() 作用
官方定义:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后,立即使用这个回调函数,获取更新后的 DOM。
理解:Vue 更新 DOM 时是异步执行的。当数据发生变化,Vue将开启一个异步更新队列(异步操作队列还会进行去重),NextTick() 等所有的异步队列更新完成之后,再统一进行更新(执行)。
路由的跳转方式
</code> `router-link` 标签会渲染为``标签; - 另一种是编程式导航,也就是通过js跳转。比如:
this.router.push('/home')
路由懒加载
诉求:当打包构建应用时,JavaScript 包会变得非常大,影响页面加载(导致首页加载时间过长,即使做了loading也是不利于用户体验)。而运用懒加载则可以把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就有效的分担首页的加载压力。
总结:为给客户更好的客户体验,首屏加载速度更快一些,解决白屏问题。
方法:
- vue异步组件实现路由懒加载
component:resolve=>([‘需要加载的路由的地址’,resolve])
- es6 提出的import(推荐)
const HelloWorld = ()=>import('需要加载的模块地址')
参考:Vue路由懒加载
**原理:**路由懒加载的实现方式是将路由相关的组件,不再直接导入,而是改写成异步组件的写法,只有当函数被调用的时候,才加载对应的组件内容。
Axios、Fetch、Ajax
**Ajax:** 即“Asynchronous Javascript And XML”(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术;本质是使用XMLHttpRequest对象来请求数据。
Axios: 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。简单的理解就是Axios是通过promise实现对ajax技术的一种封装。
特性**:**
- 从浏览器中创建 XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
Fetch: 是ES6推出基于 promise 设计的。fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。fetch是全局量 window 的一个方法
1、第一个参数是URL;
2、第二个是可选参数,可以控制不同配置的 init 对象;
3、使用了 JavaScript Promises 来处理结果/回调。
1
2
3
4
5
6
7
8
// 链式处理,将异步变为类似单线程的写法: 高级用法.
fetch('/some/url').then(function(response) {
return . //... 执行成功, 第1步...
}).then(function(returnedValue) {
// ... 执行成功, 第2步...
}).catch(function(err) {
// 中途任何地方出错...在此处理 :(
});
特点(优点):
- 浏览器原生支持的api
- 支持 promise api
- 语法简洁,更加语义化
缺点:
- 默认不带cookie
- 不支持请求终止
- 不支持文件上传进度检测
- 所有版本的 IE 均不支持原生 Fetch
- 服务器返回400 500 状态码时并不会reject,只有网络出错导致请求不能完成时,fetch才会被reject****
Vue3 和 Vue2
vue3 重构背景:
主要考虑两点因素:
- 利用新的语言特性(es6)
- 解决架构问题
Vue3 的新特性:
- 速度更快
- 体积减少
- 更易维护
- 更接近原生
- 更易使用
vue3 相比 vue2
- 重写了虚拟Dom实现
- 编译模板的优化
- 更高效的组件初始化
- undate性能提高1.3~2倍
- SSR速度提高了2~3倍
vue3 相比 vue2变化:
- 可以使用 **
**vite**构建项目** - 生命周期钩子名称上 + “on”
- 支持多根节点(支持碎片(Fragments))
- 增加 组合式API写法(Composition API)
- 使用TS重写:更好的支持TS
- 新增组件:
Teleport瞬移组件、Suspense异步加载组件、loading界面
Vue 中组件和插件的区别
**组件:**在Vue中每一个.vue文件都可以视为一个组件
**插件:**通常用来为 Vue 添加全局功能 - 如vue-router
两者的区别:
- 编写形式
组件:每一个.vue文件我们都可以看成是一个组件;通过 template 属性来编写一个组件
插件:插件的实现应该暴露一个 install 方法;
- 注册形式
组件:全局注册与局部注册:全局注册通过 Vue.component 方法,局部注册只需在用到的地方通过 components 属性注册一个组件
插件:插件的注册通过 Vue.use() 的方式进行注册(安装)
- 使用场景
组件:用来构成你的 App 的业务模块
插件:用来增强你的技术栈的功能模块
简单来说,插件就是指对Vue的功能的增强或补充
Vue项目部署404问题
HTTP 404 错误意味着链接指向的资源不存在,问题在于为什么不存在?且为什么只有history模式下会出现这个问题?
Vue是属于单页应用 - 所有用户交互是通过动态重写当前页面,只会产出一个index.html,默认nginx配置只会匹配主目录,当我们跳到其它页面执行刷新时,nginx location 是没有相关配置的,所以就会出现404情况。
而 hash 模式用 # 表示的:仅 hash 符号之前的内容会被包含在请求中,hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对服务端完全没有影响,因此改变 hash 不会重新加载页面;如 website.com/#/login 只有 website.com 会被包含在请求中 ,因此对于服务端来说,即使没有配置location,也不会返回404错误
解决方案:对nginx配置文件.conf修改,添加 try_files $uri $uri/ /index.html;
Vuex 核心属性及作用
- state:vuex的基本数据,用来存储变量。
- getters:从基本数据(state)派生的数据,相当于state的计算属性。
- mutations:提交更新数据的方法,必须是同步的(如果需要异步使用action)。
- actions:和mutation的功能大致相同,不同之处在于action提交的是mutation,而不是直接变更状态;action可以包含任意异步操作。
- modules:模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
mixins
mixins 是一种在Vue中复用Vue组件代码的方式,它是一种可复用的混入对象,可以混入到其他的Vue组件中。
vuex 刷新页面后数据消失,怎么保持数据?
由于 Vuex 的状态存储在客户端的****内存中,因此数据会丢失。要保持数据,你可以采取以下几种方法:
- 本地持久化存储:如
localStorage、sessionStorage或cookies。在组件中,你可以监听 store 的变化,并将变化的数据存储到存储中。当页面刷新后,你可以从存储中读取数据并恢复状态。 - 使用服务端存储:将数据存储在服务端的数据库或其他存储中。当页面刷新后,你可以从服务端获取数据并恢复状态。这种方法需要后端支持,并可能涉及到安全性和权限的问题。
- 使用 Vuex 的插件:如
vuex-persistedstate。这些插件可以与第三方库(如 localForage 或 IndexedDB)一起使用,以实现数据的持久化。
vue3 性能提升主要体现在哪几个方面
- 源码体积优化:通过
tree-shaking技术,减少了 41% 的体积。 - 响应式系统提升:Vue 3 使用
**proxy**对象重写响应式,性能比 Vue 2 的 defineProperty 好。⚠️⚠️⚠️ - 初始渲染速度提升:通过编译优化,提升了 55%。
- 更新速度提升:通过 diff 算法优化,提升了 133%。
- 内存占用减少:通过数据劫持优化,减少 54% 内存占用。
Vux 中的 $router 和 $route 的区别
- $router是全局路由对象实例。它包含了所有的路由及许多关键的对象和属性,用于操作路由,如路由跳转、路由前进、路由替换等。
- $route是当前路由对象。每一个路由都会有一个$route对象,用于获取当前路由信息,如当前路径、查询参数等。
Proxy 和 DefineProperty
Proxy和Object.defineProperty都是用于处理对象属性的工具
Object.defineProperty:
- Object.defineProperty方法直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
- 它主要用于静态属性的创建或修改,不能用于动态拦截属性的读取和设置。
示例:
1
2
3
4
5
6
const obj = {a: 1};
Object.defineProperty(obj, 'b', {
value: 'Hello',
writable: false
});
console.log(obj); // {a: 1, b: 'Hello'}
Proxy:
- Proxy构造函数创建一个新的对象,其行为可以由一个“handler”对象来定义。
- 它主要用于动态地拦截属性的读取和设置,以及其他对象操作,如枚举、函数调用等。
示例:
1
2
3
4
5
6
7
const obj = {};
const handler = {
get(target, prop) {
return 'Hello';
}
};
const proxy = new Proxy(obj, handler);
区别:
- Object.defineProperty更适合于静态属性的创建或修改,而Proxy更适合于动态地拦截和处理属性。
- Proxy拦截几乎所有的对象基本操作,而Object.defineProperty本来就是一个基本操作(众多基本操作中的一个)只能定义或修改一个属性。
- Proxy需要更多的代码和更深入的理解来正确使用,而Object.defineProperty相对简单和直接。
组合式 API 和 React Hooks
React Hooks:
- Hooks 有严格的调用顺序,并不可以写在条件分支中。
- React 组件中定义的变量会被一个钩子函数闭包捕获,若开发者传递了错误的依赖数组,它会变得“过期”。这导致了 React 开发者非常依赖 ESLint 规则以确保传递了正确的依赖,然而,这些规则往往不够智能,保持正确的代价过高,在一些边缘情况时会遇到令人头疼的、不必要的报错信息。
- 昂贵的计算需要使用 useMemo,这也需要传入正确的依赖数组。
- 在默认情况下,传递给子组件的事件处理函数会导致子组件进行不必要的更新。子组件默认更新,并需要显式的调用 useCallback 作优化。这个优化同样需要正确的依赖数组,并且几乎在任何时候都需要。忽视这一点会导致默认情况下对应用进行过度渲染,并可能在不知不觉中导致性能问题。
- 要解决变量闭包导致的问题,再结合并发功能,使得很难推理出一段钩子代码是什么时候运行的,并且很不好处理需要在多次渲染间保持引用 (通过 useRef) 的可变状态。
组合式 API:
- 仅调用 setup() 或
- Vue 的响应性系统运行时会自动收集计算属性和侦听器的依赖,因此无需手动声明依赖。
- 无需手动缓存回调函数来避免不必要的组件更新。Vue 细粒度的响应性系统能够确保在绝大部分情况下组件仅执行必要的更新。对 Vue 开发者来说几乎不怎么需要对子组件更新进行手动优化。
