vue面试题
2021-07-10 本文已影响0人
yyds花生
1、computed & watch的去区别
- computed有缓存,data不变则不会从新计算
- watch如何深度监听?
例
<template>
<div>
<input v-model="name"/>
<input v-model="info.city"/>
</div>
</template>
<script>
export default {
data() {
return {
name: 'ariel_bo',
info: {
city: '北京'
}
}
},
watch: {
name(oldVal, val) {
// eslint-disable-next-line
console.log('watch name', oldVal, val) // 值类型,可正常拿到 oldVal 和 val
},
info: {
handler(oldVal, val) {
// eslint-disable-next-line
console.log('watch info', oldVal, val) // 引用类型,拿不到 oldVal 。因为指针相同,此时已经指向了新的 val
},
deep: true // 深度监听
}
}
}
</script>
- watch监听引用类型,拿不到oldVal
- class和style
- 使用动态属性
- 使用驼峰式写法
<template>
<div id="app">
<div>
<p :class="{black: isBlack, yellow: isYellow}">使用class</p>
<p :class="[black, yellow]">使用class数组</p>
<p :style="styleData">styleData</p>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
isBlack: true,
isYellow: true,
black: 'black',
yellow: 'yellow',
styleData: {
fontSize: '40px',
color: 'red',
backgroundColor: '#ccc'
}
}
},
mounted() {
},
methods: {
}
}
</script>
<style scoped>
@import './assets/styles/reset.css';
.black {
background: #999;
}
.yellow {
color: yellow;
}
</style>
效果:
image.png<template>
<div id="app">
<p>遍历数组</p>
<ul>
<li v-for="(item, index) in listArr" :key="item.id">
{{index}} -- {{item.id}} --- {{item.title}}
</li>
</ul>
<p>遍历对象</p>
<ul>
<li v-for="(val, key, index) in listObj" :key="key">
{{index}} ---- {{key}} ----- {{val.title}}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
flag: false,
listArr: [
{id: 'a', title: '标题1'}, // 数据结构中最好有id值,方便使用key值
{id: 'b', title: '标题2'},
{id: 'c', title: '标题3'}
],
listObj: {
a: {title: '标题1'},
b: {title: '标题2'},
c: {title: '标题3'}
}
}
}
}
</script>
效果:
image.png
事件
<template>
<div id="app">
<p>{{ num }}</p>
<button @click="increment1">+1</button>
<button @click="increment2(2, $event)">+1</button>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
num: 0
}
},
methods: {
increment1(event) {
console.log(event) // 打印的是MouseEvent事件
console.log(event.__proto__.constructor, 'constructor----')
console.log(event.target); // <button>+1</button>
console.log(event.currentTarget) // <button>+1</button> 注意,事件是被注册到当前元素的,和 React 不一样
this.num++;
// 1、 event是原生的
// 2. 事件被挂载到当前元素
// 3. 和Dom事件一样
},
increment2(val, event) {
console.log(event.target)
this.num = this.num + val;
},
loadHandler() {}
},
mounted() {
window.addEventListener('load', this.loadHandler)
},
beforeDestroy() {
// [注意]用vue绑定事件,组件销毁时会自动解绑
// 自己绑定的事件,需要自己销毁!!!
window.removeEventListener('load', this.loadHandler)
}
}
</script>
image.png
image.png
image.png
<template>
<div id="app">
<p>输入框</p>
{{name1}}
<input type="text" v-model.trim="name">
<input type="text" v-model.lazy="name1"> <!-- 当改变输入框的值时,span中的值是不会变化的(注意光标还在输入框内)
而当输入框失去焦点时,span中的值才会改变(注意光标不在输入框内)
-->
<input type="text" v-model.number="age">
<p>多行文本:{{desc}}</p>
<textarea name="" id="" v-model="desc" cols="30" rows="10"></textarea>
<!-- 注意,<textarea>{{desc}}</textarea> 是不允许的!!! -->
<p>复选框</p>
<input type="checkbox" v-model="checked">
<p>多个复选框 {{checkedName}}</p>
<input type="checkbox" id="jack" value="Jack" v-model="checkedName">
<label for="jack">Jack</label>
<input type="checkbox" id="join" value="Join" v-model="checkedName">
<label for="join">join</label>
<input type="checkbox" id="milk" value="Milk" v-model="checkedName">
<label for="jack">Milk</label>
<p>单选 {{gender}}</p>
<input type="radio" id="male" value="male" v-model="gender"/>
<label for="male">男</label>
<input type="radio" id="female" value="female" v-model="gender"/>
<label for="female">女</label>
<p>下拉列表选择 {{selected}}</p>
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>下拉列表选择(多选){{selectedList}}</p>
<select name="" v-model="selectedList" id="" multiple>
<option>请选择</option>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
<p>下拉列表选择(多选) {{selectedList}}</p>
<select v-model="selectedList" multiple>
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
</template>
<script>
export default {
name: 'App',
data () {
return {
name: 'Ariel_Bo',
name1: 'Ariel_Bo',
age: 18,
desc: '自我介绍',
checked: true,
checkedName: [],
gender: 'male',
selected: '',
selectedList: []
}
},
methods: {
}
}
</script>
image.png
parent.vue
<template>
<div>
<Input @add="addHandler"/>
<List :list="list" @delete="deleteHandler"/>
</div>
</template>
<script>
import Input from './Input'
import List from './List'
export default {
components: {
Input,
List
},
data() {
return {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
}
]
}
},
methods: {
addHandler(title) {
this.list.push({
id: `id-${Date.now()}`,
title
})
},
deleteHandler(id) {
this.list = this.list.filter(item => item.id !== id)
}
},
created() {
// eslint-disable-next-line
console.log('index created')
},
mounted() {
// eslint-disable-next-line
console.log('index mounted')
},
beforeUpdate() {
// eslint-disable-next-line
console.log('index before update')
},
updated() {
// eslint-disable-next-line
console.log('index updated')
},
}
</script>
Input.vue
<template>
<div>
<input type="text" v-model="title"/>
<button @click="addTitle">add</button>
</div>
</template>
<script>
import event from './event'
export default {
data() {
return {
title: ''
}
},
methods: {
addTitle() {
// 调用父组件的事件
this.$emit('add', this.title)
// 调用自定义事件
event.$emit('onAddTitle', this.title)
this.title = ''
}
}
}
</script>
List.vue
<template>
<div>
<ul>
<li v-for="item in list" :key="item.id">
{{item.title}}
<button @click="deleteItem(item.id)">删除</button>
</li>
</ul>
</div>
</template>
<script>
import event from './event'
export default {
// props: ['list']
props: {
// prop 类型和默认值
list: {
type: Array,
default() {
return []
}
}
},
data() {
return {
}
},
methods: {
deleteItem(id) {
this.$emit('delete', id)
},
addTitleHandler(title) {
// eslint-disable-next-line
console.log('on add title', title)
}
},
created() {
// eslint-disable-next-line
console.log('list created')
},
mounted() {
// eslint-disable-next-line
console.log('list mounted')
// 绑定自定义事件
event.$on('onAddTitle', this.addTitleHandler)
},
beforeUpdate() {
// eslint-disable-next-line
console.log('list before update')
},
updated() {
// eslint-disable-next-line
console.log('list updated')
},
beforeDestroy() {
// 及时销毁,否则可能造成内存泄露
event.$off('onAddTitle', this.addTitleHandler)
}
}
</script>
生命周期:
image.png父子组件之间的执行顺序
image.png父beforeCreated ->父 created -> 父 beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
父beforeUpdate-> 子beforeUpdate -> 子updated -> 父updated
父子组件销毁顺序
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
image.png- V-MODEL
封装的组件
image.png
使用
image.png$nextTick
Vue是异步渲染(原理部分会详细讲解)
image.png
例子:
没有加$nextTick时候的效果
<template>
<div>
<ul ref="ul1">
<li v-for="(item, index) in list" :key="index">
{{ item }}
</li>
</ul>
<button @click="addItem">添加一条</button>
</div>
</template>
<script>
export default {
data () {
return {
list: ['a', 'b', 'c']
}
},
methods: {
addItem() {
this.list.push(`${Date.now()}`);
this.list.push(`${Date.now()}`);
this.list.push(`${Date.now()}`);
const ulElem = this.$refs.ul1;
console.log(ulElem.children.length);
}
}
}
</script>
<style lang="scss" scoped>
</style>
image.png
加上$nextTick
<template>
<div>
<ul ref="ul1">
<li v-for="(item, index) in list" :key="index">
{{ item }}
</li>
</ul>
<button @click="addItem">添加一条</button>
</div>
</template>
<script>
export default {
data () {
return {
list: ['a', 'b', 'c']
}
},
methods: {
addItem() {
this.list.push(`${Date.now()}`);
this.list.push(`${Date.now()}`);
this.list.push(`${Date.now()}`);
// 1. 异步渲染,$nextTick待DOM渲染完再回调
// 3. 页面渲染时,会将data的修改做整合,多次data修改只会渲染一次
this.$nextTick(() => {
const ulElem = this.$refs.ul1;
console.log(ulElem.children.length);
})
}
}
}
</script>
<style lang="scss" scoped>
</style>
image.png
插槽的使用
image.png- 基本使用
使用
image.png
作用域插槽
image.png image.png
image.png image.png
image.png image.png image.png
keepalive
<template>
<div id="app">
<button @click="changeState('A')">A</button>
<button @click="changeState('B')">B</button>
<button @click="changeState('C')">C</button>
<!-- <keep-alive>-->
<!-- -->
<!-- </keep-alive>-->
<A v-if="state === 'A'"/>
<B v-if="state === 'B'"/>
<C v-if="state === 'C'"/>
</div>
</template>
<script>
import A from './components/AdvancedUse/a.vue';
import B from './components/AdvancedUse/b.vue';
import C from './components/AdvancedUse/c.vue';
export default {
data() {
return {
state: 'A'
}
},
components: {
A,
B,
C
},
methods: {
changeState(state) {
this.state = state;
}
}
}
</script>
image.png
image.png
不加keep-alive每次都会走钩子函数不停的销毁和重建
image.png
加上keep-alive的话会
image.png image.png image.png
demomixin
image.png image.png image.png image.png image.png
image.png image.png
image.png image.png image.png image.png image.png
image.png image.png
代码展示
// 触发更新视图
function updateView() {
console.log('视图更新')
}
// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
arrProto[methodName] = function () {
updateView() // 触发视图更新
oldArrayProperty[methodName].call(this, ...arguments)
// Array.prototype.push.call(this, ...arguments)
}
})
// 重新定义属性,监听起来
function defineReactive(target, key, value) {
// 深度监听
observer(value)
// 核心 API
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue !== value) {
// 深度监听
observer(newValue)
// 设置新值
// 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
value = newValue
// 触发更新视图
updateView()
}
}
})
}
// 监听对象属性
function observer(target) {
if (typeof target !== 'object' || target === null) {
// 不是对象或数组
return target
}
// 污染全局的 Array 原型
// Array.prototype.push = function () {
// updateView()
// ...
// }
if (Array.isArray(target)) {
target.__proto__ = arrProto
}
// 重新定义各个属性(for in 也可以遍历数组)
for (let key in target) {
defineReactive(target, key, target[key])
}
}
// 准备数据
const data = {
name: 'zhangsan',
age: 20,
info: {
address: '北京' // 需要深度监听
},
nums: [10, 20, 30]
}
// 监听数据
observer(data)
// 测试
// data.name = 'lisi'
// data.age = 21
// // console.log('age', data.age)
// data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
// delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete
// data.info.address = '上海' // 深度监听
data.nums.push(4) // 监听数组
image.png
image.png
image.png
[图片上传失败...(image-3d97e9-1625882948906)]
image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>hash test</title>
</head>
<body>
<p>hash test</p>
<button id="btn1">修改 hash</button>
<script>
// hash 变化,包括:
// a. JS 修改 url
// b. 手动修改 url 的 hash
// c. 浏览器前进、后退
window.onhashchange = (event) => {
console.log('old url', event.oldURL)
console.log('new url', event.newURL)
console.log('hash:', location.hash)
}
// 页面初次加载,获取 hash
document.addEventListener('DOMContentLoaded', () => {
console.log('hash:', location.hash)
})
// JS 修改 url
document.getElementById('btn1').addEventListener('click', () => {
location.href = '#/user'
})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>history API test</title>
</head>
<body>
<p>history API test</p>
<button id="btn1">修改 url</button>
<script>
// 页面初次加载,获取 path
document.addEventListener('DOMContentLoaded', () => {
console.log('load', location.pathname)
})
// 打开一个新的路由
// 【注意】用 pushState 方式,浏览器不会刷新页面
document.getElementById('btn1').addEventListener('click', () => {
const state = { name: 'page1' }
console.log('切换路由到', 'page1')
history.pushState(state, '', 'page1') // 重要!!
})
// 监听浏览器前进、后退
window.onpopstate = (event) => { // 重要!!
console.log('onpopstate', event.state, location.pathname)
}
// 需要 server 端配合,可参考
// https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90
</script>
</body>
</html>
image.png
image.png
image.png image.png image.png image.png image.png image.png image.png image.png
image.png image.png image.png
image.png
image.png
image.png image.png image.png
image.png image.png