02-面试之前端 Vue 基础
语法
Vue 中常用的一些指令
v-model
:用于表单输入,实现表单控件和数据的双向绑定。v-on
:简写为@
,基础事件绑定。v-bind
:简写为:
,动态绑定一些元素的属性,类型可以是:字符串、对象或数组。v-if
:取值为true/false
,控制元素是否需要被渲染v-else
:和v-if
指令搭配使用,没有对应的值。当v-if
的值false
,v-else
才会被渲染出来。v-show
:指令的取值为true/false
,分别对应着显示/隐藏。v-for
:遍历 data 中存放的数组数据,实现列表的渲染。v-once
:使用v-once
指令,你能执行一次性地插值,当数据改变时,插值处的内容不会更新
v-show
与 v-if
有什么区别 ★★★★★
v-show
和 v-if
都是用来显示隐藏元素,v-if
还有一个 v-else
配合使用,两者达到的效果都一样,但是 v-if
更消耗性能的,因为 v-if
在显示隐藏过程中有 DOM 的添加和删除,v-show
就简单多了,只是操作 css。
v-if
: 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。v-show
:就简单得多。不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的display
属性进行切换。
所以,v-if
适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show
则适用于需要非常频繁切换条件的场景。
为什么避免 v-if
和 v-for
一起使用
- vue2.x 版本中,当
v-if
与v-for
一起使用时,v-for
具有比v-if
更高的优先级。 - vue3.x 版本中,当
v-if
与v-for
一起使用时,v-if
具有比v-for
更高的优先级。
官网明确指出:避免 v-if
和 v-for
一起使用,永远不要在一个元素上同时使用 v-if
和 v-for
。可以先对数据在计算数据中进行过滤,然后再进行遍历渲染;操作和实现起来都没有什么问题,页面也会正常展示。但是会带来不必要的性能消耗;
computed 和 watch 的区别
computed
: 是计算属性,依赖其它属性值,并且computed
的值有缓存,只有它依赖的属性值发生改变,下一次获取computed
的值时才会重新计算watch
: 更多的是「观察」的作用,监听某一个值,当被监听的值发生变化时,执行相关操作。
运用场景:
- 当我们需要进行数值计算,并且依赖于其它数据时,应该使用
computed
,因为可以利用computed
的缓存特性,避免每次获取值时,都要重新计算; - 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用
watch
,使用watch
选项允许我们执行异步操作(访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
computed 与 method 的区别
相同点:
- 如果作为模板的数据显示,二者能实现响应的功能,唯一不同的是 methods 定义的方法需要执行
不同点:
- computed 会基于响应数据缓存,methods 不会缓存
- diff 之前先看 data 里的数据是否发生变化,如果没有变化 computed 的方法不会执行,但 methods 里的方法会执行
- computed 是属性调用,methods 是函数调用
delete 与 vue.delete 区别?
delete 会删除数组的值,但是它依然会在内存中占位置
而 vue.delete 会删除数组在内存中的占位
1 |
|
Class 与 Style 如何动态绑定?
Class 可以通过对象语法和数组语法进行动态绑定:
对象语法:
1
2
3
4
5<div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>
data: {
isActive: true,
hasError: false
}数组语法:
data: { activeColor: 'red', fontSize: 30 }1
2
3
4
5
6
7
8
9
10<div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
```
Style 也可以通过对象语法和数组语法进行动态绑定:
* 对象语法:data: { styleColor: { color: 'red' }, styleSize:{ fontSize:'23px' } }1
2
* 数组语法:1
2
3
## 组件中 data 为什么是一个函数,然后 return 一个对象,而 new Vue 实例里 data 可以直接是一个对象?
// data
data() {
return {
message: “子组件”,
childName:this.name
}
}
// new Vue
new Vue({
el: ‘#app’,
router,
template: ‘
components: {App}
})
1 |
|
<slot:自定义 name=data中的属性对象>
1 |
|
// initState 部分源码
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
1 |
|
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
1 |
|
axios(config): 通用/最本质的发任意类型请求的方式
axios(url[, config]): 可以只指定 url 发 get 请求
axios.request(config): 等同于 axios(config)
axios.get(url[, config]): 发 get 请求
axios.delete(url[, config]): 发 delete 请求
axios.post(url[, data, config]): 发 post 请求
axios.put(url[, data, config]): 发 put 请求
axios.defaults.xxx: 请求的默认全局配置
axios.interceptors.request.use(): 添加请求拦截器
axios.interceptors.response.use(): 添加响应拦截器
axios.create([config]): 创建一个新的 axios(它没有下面的功能)
axios.Cancel(): 用于创建取消请求的错误对象
axios.CancelToken(): 用于创建取消请求的 token 对象
axios.isCancel(): 是否是一个取消请求的错误
axios.all(promises): 用于批量执行多个异步请求
axios.spread(): 用来指定接收所有成功数据的回调函数的方法
1 |
|
import axios from ‘axios’
// 配置默认的host,假如你的API host是:http://api.htmlx.club
axios.defaults.baseURL = ‘http://api.htmlx.club‘
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error)
});
### Vue axios 拦截器和 router 导航守卫的区别
**导航守卫**:导航守卫只能在前端路由发生变化时作出判断,此时不一定会发起 ajax 请求,比如
* 检查请求头中是否带有 token,但是不能判断 token 是否失效
* 判断该路由的访问,该用户是否需有权限登录该页面
**axios 拦截器**:拦截的是 ajax 的请求
## Vue 项目前端开发环境请求服务器接口跨域问题
* 对于 vue-cli 2.x 版本在 config 文件夹配置服务器代理;
* 对于 vue-cli 3.x 版本前端配置服务器代理在 `vue.config.js` 中设置服务器代理;
* target: 对应的属性值为你准备向后端服务器发送请求的主机+端口,含义为:相当于把前端发送请求的主机+端口自动替换成挂载的主机和端口,这样前后端的主机端口都一一就不会存在跨域问题;
* ws: 表示WebSocket协议;
* changeOrigin: true;表示是否改变原域名;这个一定要选择为 true;
这样发送请求的时候就不会出现跨域问题了。
## 既然 Vue 通过数据劫持可以精准探测数据在具体 dom 上的变化,为什么还需要虚拟 DOM diff 呢
**前置知识:** 依赖收集、虚拟 DOM、响应式系统
现代前端框架有两种方式侦测变化,一种是 **pull** ,一种是 **push**
* **pull:** 其代表为 React,我们可以回忆一下 React 是如何侦测到变化的,我们通常会用 setStateAPI 显式更新,然后 React 会进行一层层的 Virtual Dom Diff 操作找出差异,然后 Patch 到 DOM 上,React 从一开始就不知道到底是哪发生了变化,只是知道「有变化了」,然后再进行比较暴力的 Diff 操作查找「哪发生变化了」,另外一个代表就是 Angular 的脏检查操作。
* **push:** Vue 的响应式系统则是 push 的代表,当 Vue 程序初始化的时候就会对数据 data 进行依赖的收集,一但数据发生变化,响应式系统就会立刻得知。因此 Vue 是一开始就知道是「在哪发生变化了」,但是这又会产生一个问题,如果你熟悉 Vue 的响应式系统就知道,通常一个绑定一个数据就需要一个 Watcher,但我们的绑定细粒度过高就会产生大量的 Watcher,这会带来内存以及依赖追踪的开销,而细粒度过低会无法精准侦测变化,因此 Vue 的设计是选择中等细粒度的方案,在组件级别进行 push 侦测的方式,也就是那套响应式系统,通常我们会第一时间侦测到发生变化的组件,然后在组件内部进行 Virtual Dom Diff 获取更加具体的差异,而 Virtual Dom Diff 则是 pull 操作,Vue 是 push+pull 结合的方式进行变化侦测的。