前端(vue面试题总结)
1. Vue 实例的 data 属性,可以在哪些生命周期中获取到?A. beforeCreate B. created C. beforeMount D. mounted(b,c,d)2. 下列对 Vue 原理的叙述,哪些是正确的?A. Vue 中的数组变更通知,通过拦截数组操作方法而实现B. 编译器目标是创建渲染函数,渲染函数执行后将得到 VNode 树C. 组件内 data 发生变化时会通知其
1. Vue 实例的 data 属性,可以在哪些生命周期中获取到?
A. beforeCreate B. created C. beforeMount D. mounted
(b,c,d)
2. 下列对 Vue 原理的叙述,哪些是正确的?
A. Vue 中的数组变更通知,通过拦截数组操作方法而实现
B. 编译器目标是创建渲染函数,渲染函数执行后将得到 VNode 树
C. 组件内 data 发生变化时会通知其对应 watcher,执行异步更新
D. patching 算法首先进行同层级比较,可能执行的操作是节点的增加、删除和更新
(A,B,C,D)
3. 对于 Vue 中响应式数据原理的说法,下列哪项是不正确的?
A. 采用数据劫持方式,即 Object.defineProperty() 劫持 data 中各属性,实现响应式数据
B. 视图中的变化会通过 watcher 更新 data 中的数据
C. 若 data 中某属性多次发生变化,watcher 仅会进入更新队列一次
D. 通过编译过程进行依赖收集
(B D)
4. 下列说法不正确的是哪项?
A. key 的作用主要是为了高效地更新虚拟 DOM
B. 若指定了组件的 template 选项,render 函数不会执行
C. 使用 vm.$nextTick 可以确保获得 DOM 异步更新的结果
D. 若没有 el 选项,vm.$mount(dom) 可将 Vue 实例挂载于指定元素上
(B)
5. 下列关于 Vuex 的描述,不正确的是哪项?
A. Vuex 通过 Vue 实现响应式状态,因此只能用于 Vue
B. Vuex 是一个状态管理模式
C. Vuex 主要用于多视图间状态全局共享与管理
D. 在 Vuex 中改变状态,可以通过 mutations 和 actions
(C)
6. 关于 Vue 组件间的参数传递,下列哪项是不正确的?
A. 若子组件给父组件传值,可使用 $emit 方法
B. 祖孙组件之间可以使用 provide 和 inject 方式跨层级相互传值
C. 若子组件使用 $emit('say') 派发事件,父组件可使用 @say 监听
D. 若父组件给子组件传值,子组件可通过 props 接受数据
(B)
7. 下列关于 vue-router 的描述,不正确的是哪项?
A. vue-router 的常用模式有 hash 和 history 两种
B. 可通过 addRoutes 方法动态添加路由
C. 可通过 beforeEnter 对单个组件进行路由守卫
D. vue-router 借助 Vue 实现响应式的路由,因此只能用于 Vue
(C)
8. 下列说法不正确的是哪项?
A. 可通过 this.$parent 查找当前组件的父组件
B. 可使用 this.$refs 查找命名子组件
C. 可使用 this.$children 按顺序查找当前组件的直接子组件
D. 可使用 $root 查找根组件,并可配合 children 遍历全部组件
(C)
9. 下列关于 v-model 的说法,哪项是不正确的?
A. v-model 能实现双向绑定
B. v-model 本质上是语法糖,它负责监听用户的输入事件以更新数据
C. v-model 是内置指令,不能用在自定义组件上
D. 对 input 使用 v-model,实际上是指定其 :value 和 :input
(C)
10. 关于 Vue 的生命周期,下列哪项是不正确的?
A. DOM 渲染在 mounted 中就已经完成了
B. Vue 实例从创建到销毁的过程,就是生命周期
C. created 表示完成数据观测、属性和方法的运算和初始化事件,此时 $el 属性还未显示出来
D. 页面首次加载过程中,会依次触发 beforeCreate,created,beforeMount,mounted,beforeUpdate,updated
(D)
10,vue2.0和vue3.0渲染器的Diff算法分别说一下
简单来说,diff算法有以下过程
1、 同级比较,再比较子节点
2、 先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
3、 比较都有子节点的情况(核心diff)
4、 递归比较子节点
正常Diff两个树的时间复杂度是O(n^3)
,但实际情况下我们很少会进行跨层级的移动DOM
,所以Vue将Diff进行了优化,从O(n^3) -> O(n)
,只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。
Vue2的核心Diff算法采用了双端比较
的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。
在创建VNode时就确定其类型,以及在mount/patch
的过程中采用位运算
来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。(实际的实现可以结合Vue3.x源码看。)
该算法中还运用了动态规划
的思想求解最长递归子序列。
(看到这你还会发现,框架内无处不蕴藏着数据结构和算法的魅力)
2.解释一下:响应式原理?
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data
选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty把这些 property 全部转为 getter/setter这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更,每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
3.vue有哪些缺点
- Vue 不能检测数组和对象的变化
4.为什么vue不能检测对象的变化
对于对象, Vue 无法检测 property 的添加或移除,由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的.
- 追问:那如何解决上述问题?
使用this.$set
(this.someObject,'b',2) 添加新的属性
使用this.$delete
(this.someObject,'b') 删除旧属性
5.讲一下 $set 的实现原理
1、如果目标是数组,使用 vue 实现的变异方法 splice 实现响应式
2、如果目标是对象,判断属性存在,即为响应式,直接赋值
3、如果 target 本身就不是响应式,直接赋值
4、如果属性不是响应式,则调用 defineReactive 方法进行响应式处理
6.new Vue()实例中,data 可以直接是一个对象,为什么在 vue 组件中,data 必须是一个函数呢?
关键词:复用
+污染
+ 函数返回
+ 数据拷贝
因为组件是可以复用的,JS 里对象是引用关系,如果组件 data 是一个对象,那么子组件中的 data 属性值会互相污染,产生副作用。所以一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。new Vue 的实例是不会被复用的,因此不存在以上问题。
7.computed 和 watch 有什么区别?
computed 计算属性 :
依赖其它属性值,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值,如果和上次计算结果不一致,重新渲染页面。
watch 侦听器 : 更多的是「观察」的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。
追问:computed 和 watch 应用场景?
关键词 computed
+缓存
computed :当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算。
watch: 当我们需要在数据变化时执行的操作时使用(如调用其它函数)
追问 :能使用箭头函数定义computed和watch吗?
不应该使用箭头函数来定义 watcher 函数,理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向 Vue 实例,为undefined
8.vue数据绑定是双向还是单向的
Vue 在不同组件间强制使用单向数据流。这使应用中的数据流更加清晰易懂。
9.v-model双向绑定的原理?
v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据.
Object.defineproperty()重新定义(set方法)对象设置属性值和(get方法)获取属性值的操纵来实现的.
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
10.全局导航钩子函数应用场景?
vue router.beforeEach(全局前置守卫)router.beforeEach 是页面加载之前(before each)意思是在 每次每一个路由改变的时候都得执行一遍.
vue router.afterEach(全局后置守卫),相反router.afterEach是页面加载之后.
应用场景:
- 可进行一些页面跳转前处理,例如判断需要登录的页面进行拦截,做登录跳转!
2.进入页面登录判断、管理员权限判断、浏览器判断
11.vue-loader是什么?它有什么作用?
解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代 码 style、以及 HTML 模版 template,再分别把它们交给对应的 Loader 去处理。
12.怎样动态加载路由?
通过router.addRoutes方法可以动态加载路由.
let router=new VueRouter({
routes:[
{path:'/product',component:a,name:'product'}
]
});
router.addRoutes([
{path:'/user',component:c,name:'user'},
{path:'/address',component:address,name:'address'}
]);
22.说说active-class是哪个组件的属性?
active-class是vue-router模块的router-link组件中的属性,用来设置选中连接的样式.
23.为什么vue使用异步更新组件?
收集当前的改动一次性批量更新,为了节省diff开销.
24.vuex中actions和mutations有什么区别?
1.mutations可以直接修改state,但只能包含同步操作,同时,只能通过提交commit调用.
2.actions可以包含异步操作,通过store.dispatch触发,不能直接修改数据,需要调用commit去修改数据.
25. 怎么监听vuex数据的变化?
通过watch监听数据的变化
watch:{
'$store.state.test':function(value){
console.log('数据修改了',value)
}}
28.开启vuex中的严格模式有什么好处?
主要用户防止不合理的改变状态值如:this.$.store.state.list = [],这样就会抛出异常
A.在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
B. 不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
29.vue组件会在什么时候下被销毁?
1.页面关闭、
2.路由跳转、
3.v-if为false
4.改变key值
30.你了解vue中的diff算法吗?
题目分析:vue基于虚拟DOM做更新,diff又是其核心部分,因此常被问道,此题考查面试者深度。
答题思路:3w1h
1. 定义diff
2. 它的必要性
3. 它在哪里被使用
4. 它如何运作
5. 提升:说一些细节
回答范例:
1. diff算法是虚拟DOM技术的产物,vue里面实际叫做patch,它的核心实现来自于snabbdom;通过新旧虚拟DOM作对比(即patch),将变化的地方转换为DOM操作
2. 在vue 1中是没有patch的,因为界面中每个依赖都有专门的watcher负责更新,这样项目规模变大就会成为性能瓶颈,vue 2中为了降低watcher粒度,每个组件只有一个watcher,但是当需要更新的时候,怎样才能精确找到发生变化的地方?这就需要引入patch才行。
3. 组件中数据发生变化时,对应的watcher会通知更新并执行其更新函数,它会执行渲染函数获取全新虚拟dom:newVnode,此时就会执行patch比对上次渲染结果oldVnode和新的渲染结果newVnode。
4. patch过程遵循深度优先、同层比较的策略;两个节点之间比较时,如果它们拥有子节点,会先比较子节点;比较两组子节点时,会假设头尾节点可能相同先做尝试,没有找到相同节点后才按照通用方式遍历查找;查找结束再按情况处理剩下的节点;借助key通常可以非常精确找到相同节点,因此整个patch过程非常高效。
知其所以然:测试代码[test.html](test.html)
31.vue-router中如何保护路由?
此题是考查项目实践能力,项目中基本都有路由守卫的需求,保护指定路由考查的就是这个知识点。
答题整体思路:
1. 阐述vue-router中路由保护策略
2. 描述具体实现方式
3. 简单说一下它们是怎么生效的
回答范例:
1. vue-router中保护路由安全通常使用导航守卫来做,通过设置路由导航钩子函数的方式添加守卫函数,在里面判断用户的登录状态和权限,从而达到保护指定路由的目的。
2. 具体实现有几个层级:全局前置守卫beforeEach、路由独享守卫beforeEnter或组件内守卫beforeRouteEnter。以全局守卫为例来说,可以使用`router.beforeEach((to,from,next)=>{})`方式设置守卫,每次路由导航时,都会执行该守卫,从而检查当前用户是否可以继续导航,通过给next函数传递多种参数达到不同的目的,比如如果禁止用户继续导航可以传递next(false),正常放行可以不传递参数,传递path字符串可以重定向到一个新的地址等等。
3. 这些钩子函数之所以能够生效,也和vue-router工作方式有关,像beforeEach只是注册一个hook,当路由发生变化,router准备导航之前会批量执行这些hooks,并且把目标路由to,当前路由from,以及后续处理函数next传递给我们设置的hook。
可能的追问:
1. 能不能说说全局守卫、路由独享守卫和组件内守卫区别?
- 作用范围
- 组件实例的获取
```
beforeRouteEnter(to,from,next) {
next(vm => {
})
}
```
- 名称/数量/顺序
> 1. 导航被触发。
> 2. 在失活的组件里调用离开守卫。
> 3. 调用全局的 `beforeEach` 守卫。
> 4. 在重用的组件里调用 `beforeRouteUpdate` 守卫 (2.2+)。
> 5. 在路由配置里调用 `beforeEnter`。
> 6. 解析异步路由组件。
> 7. 在被激活的组件里调用 `beforeRouteEnter`。
> 8. 调用全局的 `beforeResolve` 守卫 (2.5+)。
> 9. 导航被确认。
> 10. 调用全局的 `afterEach` 钩子。
> 11. 触发 DOM 更新。
> 12. 用创建好的实例调用 `beforeRouteEnter` 守卫中传给 `next` 的回调函数。
2. 你项目中的路由守卫是怎么做的?
3. 前后端路由一样吗?
4. 前端路由是用什么方式实现的?
你前面提到的next方法是怎么实现的?
32.你了解哪些Vue性能优化方法?
- 路由懒加载
```js
const router = new VueRouter({
routes: [
{ path: '/foo', component: () => import('./Foo.vue') }
]
})
```
- keep-alive缓存页面
```vue
<template>
<div id="app">
<keep-alive>
<router-view/>
</keep-alive>
</div>
</template>
```
- 使用v-show复用DOM
```vue
<template>
<div class="cell">
<!--这种情况用v-show复用DOM,比v-if效果好-->
<div v-show="value" class="on">
<Heavy :n="10000"/>
</div>
<section v-show="!value" class="off">
<Heavy :n="10000"/>
</section>
</div>
</template>
```
- v-for 遍历避免同时使用 v-if
```vue
<template>
<ul>
<li
v-for="user in activeUsers"
:key="user.id">
{{ user.name }}
</li>
</ul>
</template>
<script>
export default {
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}
}
</script>
```
- 长列表性能优化
- 如果列表是纯粹的数据展示,不会有任何改变,就不需要做响应化
```js
export default {
data: () => ({
users: []
}),
async created() {
const users = await axios.get("/api/users");
this.users = Object.freeze(users);
}
};
```
- 如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容
```html
<recycle-scroller
class="items"
:items="items"
:item-size="24"
>
<template v-slot="{ item }">
<FetchItemView
:item="item"
@vote="voteItem(item)"
/>
</template>
</recycle-scroller>
```
> 参考[vue-virtual-scroller](https://github.com/Akryum/vue-virtual-scroller)、[vue-virtual-scroll-list](https://github.com/tangbc/vue-virtual-scroll-list)
- 事件的销毁
Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。
```
created() {
this.timer = setInterval(this.refresh, 2000)
},
beforeDestroy() {
clearInterval(this.timer)
}
```
- 图片懒加载
对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。
```html
<img v-lazy="/static/img/1.png">
```
> 参考项目:[vue-lazyload](https://github.com/hilongjw/vue-lazyload)
- 第三方插件按需引入
像element-ui这样的第三方组件库可以按需引入避免体积太大。
```js
import Vue from 'vue';
import { Button, Select } from 'element-ui';
Vue.use(Button)
Vue.use(Select)
```
- 无状态的组件标记为函数式组件
```vue
<template functional>
<div class="cell">
<div v-if="props.value" class="on"></div>
<section v-else class="off"></section>
</div>
</template>
<script>
export default {
props: ['value']
}
</script>
```
- 子组件分割
```vue
<template>
<div>
<ChildComp/>
</div>
</template>
<script>
export default {
components: {
ChildComp: {
methods: {
heavy () { /* 耗时任务 */ }
},
render (h) {
return h('div', this.heavy())
}
}
}
}
</script>
```
- 变量本地化
```vue
<template>
<div :style="{ opacity: start / 300 }">
{{ result }}
</div>
</template>
<script>
import { heavy } from '@/utils'
export default {
props: ['start'],
computed: {
base () { return 42 },
result () {
const base = this.base // 不要频繁引用this.base
let result = this.start
for (let i = 0; i < 1000; i++) {
result += heavy(base)
}
return result
}
}
}
</script>
33.你知道nextTick吗,它是干什么的,实现原理是什么?
这道题考查对vue异步更新队列的理解,有一定深度。
答题思路:
1. nextTick是啥?下一个定义
2. 为什么需要它呢?用异步更新队列实现原理解释
3. 我再什么地方用它呢?抓抓头,想想你在平时开发中使用它的地方
4. 下面介绍一下如何使用nextTick
5. 最后能说出源码实现就会显得你格外优秀
先看看官方定义
> Vue.nextTick( \[callback, context\] )
>
> 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
>
> ```js
> // 修改数据
> vm.msg = 'Hello'
> // DOM 还没有更新
> Vue.nextTick(function () {
> // DOM 更新了
> })
> ```
回答范例:
1. nextTick是Vue提供的一个全局API,由于vue的异步更新策略导致我们对数据的修改不会立刻体现在dom变化上,此时如果想要立即获取更新后的dom状态,就需要使用这个方法
2. Vue 在更新 DOM 时是**异步**执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用。
3. 所以当我们想在修改数据后立即看到dom执行结果就需要用到nextTick方法。
4. 比如,我在干什么的时候就会使用nextTick,传一个回调函数进去,在里面执行dom操作即可。
5. 我也有简单了解nextTick实现,它会在callbacks里面加入我们传入的函数,然后用timerFunc异步方式调用它们,首选的异步方式会是Promise。这让我明白了为什么可以在nextTick中看到dom操作结果。
34.说一说你对vue响应式理解?
烂大街的问题,但却不是每个人都能回答到位。因为如果你只是看看别人写的网文,通常没什么底气,也经不住面试官推敲,但像我们这样即看过源码还造过轮子的,回答这个问题就会比较有底气。
答题思路:
1. 啥是响应式?
2. 为什么vue需要响应式?
3. 它能给我们带来什么好处?
4. vue的响应式是怎么实现的?有哪些优缺点?
5. vue3中的响应式的新变化
回答范例:
1. 所谓数据响应式就是能够使数据变化可以被检测并对这种变化做出响应的机制。
2. mvvm框架中要解决的一个核心问题是连接数据层和视图层,通过数据驱动应用,数据变化,视图更新,要做到这点的就需要对数据做响应式处理,这样一旦数据发生变化就可以立即做出更新处理。
3. 以vue为例说明,通过数据响应式加上虚拟DOM和patch算法,可以使我们只需要操作数据,完全不用接触繁琐的dom操作,从而大大提升开发效率,降低开发难度。
4. vue2中的数据响应式会根据数据类型来做不同处理,如果是对象则采用Object.defineProperty()的方式定义数据拦截,当数据被访问或发生变化时,我们感知并作出响应;如果是数组则通过覆盖该数组原型的方法,扩展它的7个变更方法,使这些方法可以额外的做更新通知,从而作出响应。这种机制很好的解决了数据响应化的问题,但在实际使用中也存在一些缺点:比如初始化时的递归遍历会造成性能损失;新增或删除属性时需要用户使用Vue.set/delete这样特殊的api才能生效;对于es6中新产生的Map、Set这些数据结构不支持等问题。
5. 为了解决这些问题,vue3重新编写了这一部分的实现:利用ES6的Proxy机制代理要响应化的数据,它有很多好处,编程体验是一致的,不需要使用特殊api,初始化性能和内存消耗都得到了大幅改善;另外由于响应化的实现代码抽取为独立的reactivity包,使得我们可以更灵活的使用它,我们甚至不需要引入vue都可以体验。
35.你如果想要扩展某个Vue组件时会怎么做?
此题属于实践题,着重考察对vue常用api使用熟练度,答题时不仅要列出这些解决方案,同时最好说出他们异同。
答题思路:
按照逻辑扩展和内容扩展来列举,逻辑扩展有:mixins、extends、composition api;内容扩展有slots;
分别说出他们使用使用方法、场景差异和问题。
作为扩展,还可以说说vue3中新引入的composition api带来的变化
回答范例:
1. 常见的组件扩展方法有:mixins,slots,extends等
2. 混入mixins是分发 Vue 组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。
```js
// 复用代码:它是一个配置对象,选项和组件里面一样
const mymixin = {
methods: {
dosomething(){}
}
}
// 全局混入:将混入对象传入
Vue.mixin(mymixin)
// 局部混入:做数组项设置到mixins选项,仅作用于当前组件
const Comp = {
mixins: [mymixin]
}
```
3. 插槽主要用于vue组件中的内容分发,也可以用于组件扩展。
子组件Child
```html
<div>
<slot>这个内容会被父组件传递的内容替换</slot>
</div>
```
父组件Parent
```html
<div>
<Child>来自老爹的内容</Child>
</div>
```
如果要精确分发到不同位置可以使用具名插槽,如果要使用子组件中的数据可以使用作用域插槽。
4. 组件选项中还有一个不太常用的选项extends,也可以起到扩展组件的目的
```js
// 扩展对象
const myextends = {
methods: {
dosomething(){}
}
}
// 组件扩展:做数组项设置到extends选项,仅作用于当前组件
// 跟混入的不同是它只能扩展单个对象
// 另外如果和混入发生冲突,该选项优先级较高,优先起作用
const Comp = {
extends: myextends
}
```
5. 混入的数据和方法不能明确判断来源且可能和当前组件内变量产生命名冲突,vue3中引入的composition api,可以很好解决这些问题,利用独立出来的响应式模块可以很方便的编写独立逻辑并提供响应式的数据,然后在setup选项中有机组合使用。例如:
```js
// 复用逻辑1
function useXX() {}
// 复用逻辑2
function useYY() {}
// 逻辑组合
const Comp = {
setup() {
const {xx} = useXX()
const {yy} = useYY()
return {xx, yy}
}
}
```
还可以追问的
Vue.extend方法你用过吗?它能用来做组件扩展吗?
7.Vue模版编译原理知道吗,能简单说一下吗?
1.将模板字符串转换成element AST(解析器parser)
2.对ast进行静态节点标记,主要用来做虚拟dom的渲染优化(优化器optimizer)
3.使用element AST 生成render函数代码字符串(代码生成器code generator )
更多推荐
所有评论(0)