vue面试题

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容