前端面试题汇总---VUE篇(1)

什么是mvvm?

> MVVM是Model-View-ViewModel的缩写。mvvm是一种设计思想。Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑;View 代表UI 组件,它负责将数据模型转化成UI 展现出来,ViewModel 是一个同步View 和 Model的对象

- 在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。

- ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理

vue的优点是什么?

- 低耦合。视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变

- 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑

- 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写

请详细说下你对vue生命周期的理解

> 答:总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后

- 创建前/后: 在beforeCreate阶段,vue实例的挂载元素el和数据对象data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,el还没有

- 载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。

- 更新前/后:当data变化时,会触发beforeUpdate和updated方法

- 销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在

组件之间的传值?

**父组件与子组件传值**

```

//父组件通过标签上面定义传值

<template>

<Main :obj="data"></Main>

</template>

<script>

//引入子组件

import Main form "./main"

exprot default{

name:"parent",

data(){

return {

data:"我要向子组件传递数据"

}

},

//初始化组件

components:{

Main

}

}

</script>


 

//子组件通过props方法接受数据

<template>

<div>{{data}}</div>

</template>

<script>

exprot default{

name:"son",

//接受父组件传值

props:["data"]

}

</script>

```

**子组件向父组件传递数据**

```

//子组件通过$emit方法传递参数

<template>

<div v-on:click="events"></div>

</template>

<script>

//引入子组件

import Main form "./main"

exprot default{

methods:{

events:function(){

}

}

}

</script>

//

<template>

<div>{{data}}</div>

</template>

<script>

exprot default{

name:"son",

//接受父组件传值

props:["data"]

}

</script>

```

路由之间跳转?

**声明式(标签跳转)**

```

<router-link :to="index">

```

**编程式( js跳转)**

```

router.push('index')

```

vuex是什么?怎么使用?哪种功能场景使用它?

> vue框架中状态管理。在main.js引入store,注入。新建了一个目录`store`,….. `export` 。场景有:单页应用中,组件之间的状态。音乐播放、登录状态、加入购物车

实现 Vue SSR

![](http://7xq6al.com1.z0.glb.clouddn.com/vue-ssr.jpg)

**其基本实现原理**

- app.js 作为客户端与服务端的公用入口,导出 Vue 根实例,供客户端 entry 与服务端 entry 使用。客户端 entry 主要作用挂载到 DOM 上,服务端 entry 除了创建和返回实例,还进行路由匹配与数据预获取。

- webpack 为客服端打包一个 Client Bundle ,为服务端打包一个 Server Bundle 。

- 服务器接收请求时,会根据 url,加载相应组件,获取和解析异步数据,创建一个读取 Server Bundle 的 BundleRenderer,然后生成 html 发送给客户端。

- 客户端混合,客户端收到从服务端传来的 DOM 与自己的生成的 DOM 进行对比,把不相同的 DOM 激活,使其可以能够响应后续变化,这个过程称为客户端激活 。为确保混合成功,客户端与服务器端需要共享同一套数据。在服务端,可以在渲染之前获取数据,填充到 stroe 里,这样,在客户端挂载到 DOM 之前,可以直接从 store 里取数据。首屏的动态数据通过 `window.__INITIAL_STATE__ `发送到客户端

> Vue SSR 的实现,主要就是把 Vue 的组件输出成一个完整 HTML, vue-server-renderer 就是干这事的

- `Vue SSR `需要做的事多点(输出完整 HTML),除了` complier -> vnode`,还需如数据获取填充至 HTML、客户端混合(hydration)、缓存等等。

相比于其他模板引擎(ejs, jade 等),最终要实现的目的是一样的,性能上可能要差点

Vue 组件 data 为什么必须是函数

- 每个组件都是 Vue 的实例。

- 组件共享 data 属性,当 data 的值是同一个引用类型的值时,改变其中一个会影响其他

Vue computed 实现

- 建立与其他属性(如:data、 Store)的联系;

- 属性改变后,通知计算属性重新计算

> 实现时,主要如下

- 初始化 data, 使用 `Object.defineProperty` 把这些属性全部转为 `getter/setter`。

- 初始化 `computed`, 遍历 `computed` 里的每个属性,每个 computed 属性都是一个 watch 实例。每个属性提供的函数作为属性的 getter,使用 Object.defineProperty 转化。

- `Object.defineProperty getter` 依赖收集。用于依赖发生变化时,触发属性重新计算。

- 若出现当前 computed 计算属性嵌套其他 computed 计算属性时,先进行其他的依赖收集

Vue complier 实现

- 模板解析这种事,本质是将数据转化为一段 html ,最开始出现在后端,经过各种处理吐给前端。随着各种 mv* 的兴起,模板解析交由前端处理。

- 总的来说,Vue complier 是将 template 转化成一个 render 字符串。

> 可以简单理解成以下步骤:

- parse 过程,将 template 利用正则转化成 AST 抽象语法树。

- optimize 过程,标记静态节点,后 diff 过程跳过静态节点,提升性能。

- generate 过程,生成 render 字符串

怎么快速定位哪个组件出现性能问题

> 用 timeline 工具。 大意是通过 timeline 来查看每个函数的调用时常,定位出哪个函数的问题,从而能判断哪个组件出了问题。

**说说你对MVC和MVVM的理解**

- MVC

- View 传送指令到 Controller

- Controller 完成业务逻辑后,要求 Model 改变状态

- Model 将新的数据发送到 View,用户得到反馈

所有通信都是单向的

**MVVM 由以下三个内容组成**

- `View`:界面

- `Model`:数据模型

- `ViewModel`:作为桥梁负责沟通 `View` 和 `Model`

> - 在 JQuery 时期,如果需要刷新 UI 时,需要先取到对应的 DOM 再更新 UI,这样数据和业务的逻辑就和页面有强耦合

> - 在 MVVM 中,UI 是通过数据驱动的,数据一旦改变就会相应的刷新对应的 UI,UI 如果改变,也会改变对应的数据。这种方式就可以在业务处理中只关心数据的流转,而无需直接和页面打交道。ViewModel 只关心数据和业务的处理,不关心 View 如何处理数据,在这种情况下,View 和 Model 都可以独立出来,任何一方改变了也不一定需要改变另一方,并且可以将一些可复用的逻辑放在一个 ViewModel 中,让多个 View 复用这个 ViewModel

- 在 MVVM 中,最核心的也就是数据双向绑定,例如 Angluar 的脏数据检测,Vue 中的数据劫持

**脏数据检测**

- 当触发了指定事件后会进入脏数据检测,这时会调用 $digest 循环遍历所有的数据观察者,判断当前值是否和先前的值有区别,如果检测到变化的话,会调用 $watch 函数,然后再次调用 $digest 循环直到发现没有变化。循环至少为二次 ,至多为十次

- 脏数据检测虽然存在低效的问题,但是不关心数据是通过什么方式改变的,都可以完成任务,但是这在 Vue 中的双向绑定是存在问题的。并且脏数据检测可以实现批量检测出更新的值,再去统一更新 UI,大大减少了操作 DOM 的次数

**数据劫持**

- `Vue` 内部使用了 `Obeject.defineProperty()` 来实现双向绑定,通过这个函数可以监听到 `set` 和 `get `的事件

```javascript

var data = { name: 'yck' }

observe(data)

let name = data.name // -> get value

data.name = 'yyy' // -> change value

function observe(obj) {

// 判断类型

if (!obj || typeof obj !== 'object') {

return

}

Object.keys(data).forEach(key => {

defineReactive(data, key, data[key])

})

}

function defineReactive(obj, key, val) {

// 递归子属性

observe(val)

Object.defineProperty(obj, key, {

enumerable: true,

configurable: true,

get: function reactiveGetter() {

console.log('get value')

return val

},

set: function reactiveSetter(newVal) {

console.log('change value')

val = newVal

}

})

}

```

> 以上代码简单的实现了如何监听数据的 set 和 get 的事件,但是仅仅如此是不够的,还需要在适当的时候给属性添加发布订阅

```html

<div>

{{name}}

</div>

```

> 在解析如上模板代码时,遇到 `{{name}}` 就会给属性 `name` 添加发布订阅


 

```javascript

// 通过 Dep 解耦

class Dep {

constructor() {

this.subs = []

}

addSub(sub) {

// sub 是 Watcher 实例

this.subs.push(sub)

}

notify() {

this.subs.forEach(sub => {

sub.update()

})

}

}

// 全局属性,通过该属性配置 Watcher

Dep.target = null

function update(value) {

document.querySelector('div').innerText = value

}

class Watcher {

constructor(obj, key, cb) {

// 将 Dep.target 指向自己

// 然后触发属性的 getter 添加监听

// 最后将 Dep.target 置空

Dep.target = this

this.cb = cb

this.obj = obj

this.key = key

this.value = obj[key]

Dep.target = null

}

update() {

// 获得新值

this.value = this.obj[this.key]

// 调用 update 方法更新 Dom

this.cb(this.value)

}

}

var data = { name: 'yck' }

observe(data)

// 模拟解析到 `{{name}}` 触发的操作

new Watcher(data, 'name', update)

// update Dom innerText

data.name = 'yyy'

```

> 接下来,对 defineReactive 函数进行改造

```javascript

function defineReactive(obj, key, val) {

// 递归子属性

observe(val)

let dp = new Dep()

Object.defineProperty(obj, key, {

enumerable: true,

configurable: true,

get: function reactiveGetter() {

console.log('get value')

// 将 Watcher 添加到订阅

if (Dep.target) {

dp.addSub(Dep.target)

}

return val

},

set: function reactiveSetter(newVal) {

console.log('change value')

val = newVal

// 执行 watcher 的 update 方法

dp.notify()

}

})

}

```

> 以上实现了一个简易的双向绑定,核心思路就是手动触发一次属性的 getter 来实现发布订阅的添加

*Proxy 与 Obeject.defineProperty 对比**

- `Obeject.defineProperty` 虽然已经能够实现双向绑定了,但是他还是有缺陷的。

- 只能对属性进行数据劫持,所以需要深度遍历整个对象

- 对于数组不能监听到数据的变化

> 虽然 `Vue` 中确实能检测到数组数据的变化,但是其实是使用了 `hack` 的办法,并且也是有缺陷的

猜你喜欢

转载自blog.csdn.net/weixin_42246997/article/details/88195871