vue面试题

2021-07-10  本文已影响0人  yyds花生

1、computed & 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>
  1. 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
  1. 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
  1. 基本使用
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
上一篇下一篇

猜你喜欢

热点阅读