美文网首页
vue面试题

vue面试题

作者: yyds花生 | 来源:发表于2021-07-10 17:55 被阅读0次

    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
    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

    相关文章

      网友评论

          本文标题:vue面试题

          本文链接:https://www.haomeiwen.com/subject/rvlpultx.html