工作8年总被面试官问过的高频问题(VUE篇)

一、如何在created钩子函数中对DOM进行操作?

created里面怎么获取dom这里我用了两种方法一是 $nextTick 和 setTimeout ,一个是次 DOM 更新循环之后执行,一个是可以转换异步操作。

created()钩子函数中进行DOM操作时一般人会这样写:


<template>
  <div class="hello">
    <h1 id="nextTick" ref="hello">hello boy!</h1>
  </div>
</template>
 
<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'HelloWorld'
    }
  },
    created(){
    let that=this;
    that.$refs.aa.innerHTML="hello girl!";  //写入到DOM元素
   },
}
</script>
 
<style scoped>
h1 {
  font-weight: normal;
}
</style>

但这样写会报错:

[Vue warn]: Error in created hook: "TypeError: Cannot set property 'innerHTML' of undefined"

原因:在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM挂载已完成。

nextTick正确的写法应该是:

<template>
  <div class="hello">
    <h1 id="nextTick" ref="hello">hello boy!</h1>
  </div>
</template>
 
<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'HelloWorld'
    }
  },
    created(){
        let that=this;
          that.$nextTick(function(){  //不使用this.$nextTick()方法会报错
          that.$refs.hello.innerHTML="hello girl!";  //写入到DOM元素
      });
   },
}
</script>
 
<style scoped>
h1 {
  font-weight: normal;
}
</style>

setTimeout正确的写法应该是:

created: function() {
         setTimeout(function() {
                    var body = document.getElementById('ID');
                    console.log(body);
         })
},

nextTick与setTimeout都是异步函数 不同的是nextTick比setTimeout优先执行

原因:

异步执行顺序先执行微任务队列再执行宏任务队列

macro-task(宏任务):包括整体代码script,setTimeout,setInterval

micro-task(微任务):Promise,process.nextTick

process.nextTick:process.nextTick(callback)类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。

javascript是单线程

setTimeout:因为要入微任务队列,即使是0最快也要4ms,要等待出队。

二、v-model的实现原理?

2.1什么是v-model?

v-model本质上只是一颗语法糖,可以用 v-model 指令在表单 <input>、<textarea> 及 <select>元素上创建双向数据绑定。

2.2 v-model实现原理

v-bind:绑定响应式数据

触发oninput 事件并传递数据

v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

text 和 textarea 元素使用 value 属性和 input 事件;

checkbox 和 radio 使用 checked 属性和 change 事件;

select 字段将 value 作为 prop 并将 change 作为事件。

2.3实现实例

三、介绍一下插槽?

slot就是插槽,主要的作用就是拓展组件,在重复使用一个组件的时候可以通过少量的修改就达到复用的效果。

分成默认插槽、具名插槽和作用域插槽。其中前两个都是元素在父组件中,拓展的结构也在父组件中,直接在子组件中占位,在父组件中添加结构即可,区别就是具名插槽给插槽取了名字,多个插槽存在时可以一一对应。而作用域插槽的数据在子组件中,扩展的结构要在父组件中,这时就要利用slot进行子===>父的通信,给数据一个新的作用域,因此叫作用域插槽。

vue3在父组件中使用具名插槽使用v-slot,而vue2使用slot

vue3必须把v-slot写在template标签中,而vue2中的slot可以写在任意标签中

四、Vue组件通信的方式

4.1 Props和$emit

父传子是props接收数据,然后双花括号引用到标签中,子传父是,父用@自定义事件名=“父method函数” 子:this.$emit(“自定义事件名”,传值)。

4.2 $parent/$children

子实例可以通过this.$parent访问父实例,父实例可以通过$children访问子实例。

4.3 provide/inject

父组件给子孙组件传值,一个组件修改状态所有组件都会影响,使用provide/inject编写组件。

4.4 ref/refs

如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据

4.5 eventBus

eventBus兄弟之间的传参,创建一个Vue实例eventBus作为媒介。在相互通信的兄弟组件中都引入eventBus,让各兄弟组件共用一个事件机制。

通过eventBus.$emit(事件名,值)传递数据,eventBus.$on(事件名,函数体)接收数据

4.6 localStorage/sessionStorage

localStorage.setItem保存数据

localStorage.getItem获取数据

localStorage.removeItem删除数据

4.7 $attrs/$listeners

父组件给子孙组件传值,二次封装组件

4.8 Vuex 状态管理

State是数据源,view以声明方式将state映射到视图,actions:响应动作

State状态类似data

mutations状态更新,同步请求

actions 异步请求,要改state需要提交给mutations

getters 计算属性类似computed

modules 模块拆分

五、为什么data属性是一个函数而不是一个对象?

如果是对象,所有组件的data都会指向同一个内存地址,一个组件的data修改,其他的也会受影响。如果是函数的话,复用一次,就会返回新的data,保护各自的数据互不影响。

在我们定义好一个组件的时候,vue最终都会通过Vue.extend()构成组件实例

这里我们模仿组件构造函数,定义data属性,采用对象的形式

function Component(){
}

Component.prototype.data = {
count : 0
}

创建两个组件实例 

const componentA = new Component()
const componentB = new Component()

修改componentA组件data属性的值,componentB中的值不受影响

console.log(componentB.data.count)  // 0
componentA.data.count = 1
console.log(componentB.data.count)  // 1

产生这样的原因是两者共用了同一个内存地址,componentA 修改的内容,同样对componentB 产生了影响

如果我们采用函数的形式,则不会出现这种情况(函数返回的对象内存地址并不相同)

function Component(){
this.data = this.data()
}

Component.prototype.data = function (){
    return {
    count : 0
    }
}

修改componentA组件data属性的值,componentB中的值不受影响

console.log(componentB.data.count)  // 0
componentA.data.count = 1
console.log(componentB.data.count)  // 0

vue组件可能会有很多个实例,采用函数返回一个全新data形式,使每个实例对象的数据不会受到其他实例对象数据的污染

六、this指向的问题?

普通函数=>window

定时器=>window

立即执行函数=>window

setTimeout 中传入的函数=>window

setInterval 中传入的函数=>window

构造函数=>实例对象 原型里面的方法也指向实例对象

对象方法=>指向该对象

事件绑定方法=>绑定事件对象

箭头函数中的this指向他的父级

七、Vue的生命周期

四阶段、八状态

初始化beforeCreate,created、

挂载beforeMount,mounted、

更新beforeUpdate,updated、

销毁beforeDestroy,destroyed

7.1 beforeCreate

官网:在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用。

详细:在这个阶段,数据是获取不到的,并且真实dom元素也是没有渲染出来的

7.2 created

官网:在实例创建完成后被立即同步调用。在这一步中,实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。然而,挂载阶段还没开始,且 $el property 目前尚不可用。

详细:在这个阶段,可以访问到数据了,但是页面当中真实dom节点还是没有渲染出来,在这个钩子函数里面,可以进行相关初始化事件的绑定、发送请求操作

7.3. beforeMount

官网:在挂载开始之前被调用:相关的 render 函数首次被调用。

详细:代表dom马上就要被渲染出来了,但是却还没有真正的渲染出来,这个钩子函数与created钩子函数用法基本一致,可以进行相关初始化事件的绑定、发送ajax操作

7.4 mounted

官网:实例被挂载后调用,这时 el 被新创建的 vm.$el 替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内。

注意 mounted 不会保证所有的子组件也都被挂载完成。如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 vm.$nextTick:

详细:挂载阶段的最后一个钩子函数,数据挂载完毕,真实dom元素也已经渲染完成了,这个钩子函数内部可以做一些实例化相关的操作

7.5 beforeUpdate

官网:在数据发生改变后,DOM 被更新之前被调用。这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。

详细:这个钩子函数初始化的不会执行,当组件挂载完毕的时候,并且当数据改变的时候,才会立马执行,这个钩子函数获取dom的内容是更新之前的内容

7.6 updated

官网:在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。

当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。

详细:这个钩子函数获取dom的内容是更新之后的内容生成新的虚拟dom,新的虚拟dom与之前的虚拟dom进行比对,差异之后,就会进行真实dom渲染。在updated钩子函数里面就可以获取到因diff算法比较差异得出来的真实dom渲染了。

7.7 beforeDestroy

官网:实例销毁之前调用。在这一步,实例仍然完全可用。

详细:当组件销毁的时候,就会触发这个钩子函数代表销毁之前,可以做一些善后操作,可以清除一些初始化事件、定时器相关的东西。

7.8 destroyed

官网:实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。

详细:Vue实例失去活性,完全丧失功能

<template>
  <div id="app">
    <p id="box">{{msg}}</p>
    <button @click="change">更新</button>
  </div>
</template>

<script>

export default {
  data () {
    return {
      msg: 'hello'
    }
  },

  methods: {
    change () {
      this.msg = 'hello world'
    }
  },

  beforeCreate () {
    console.log('---------------->beforeCreate')
    console.log(this.msg, document.getElementById('box'))
  },

  created () {
    console.log('---------------->created')
    console.log(this.msg, document.getElementById('box'))
  },

  beforeMount () {
    console.log('---------------->beforeMount')
    console.log(this.msg, document.getElementById('box'))
  },

  mounted () {
    console.log('---------------->mounted')
    console.log(this.msg, document.getElementById('box'))
  },

  beforeUpdate () {
    console.log('---------------->beforeUpdate')
    console.log(this.$el.innerHTML)
    console.log(this.msg, document.getElementById('box'))
  },

  updated () {
    console.log('---------------->updated')
    console.log(this.$el.innerHTML)
    console.log(this.msg, document.getElementById('box'))
  }
}
</script>

当页面初始化挂载完成之后,

当数据改变之后又会触发beforeUpdate,updated两个钩子函数

八、Vue中的两大核心是啥?

8.1 数据驱动视图

v-model双相绑定的原理,通过Object.defineProperty()来拦截各个属性的get和 set,在数据变动时发布消息给订阅者,触发相应的监听回调。

通过v-bind绑定data数据,通过v-on监听表单的input事件,通过$event.target.value获取输入的内容,然后赋值给data数据,从而模拟实现v-model

8.2 组件化系统

就是把页面拆分成多个组件,每个组件依赖的 CSS、JS、模板、图片等资源放在一起开发和维护。 因为组件是资源独立的,所以组件在系统内部可复用,组件和组件之间可以嵌套,如果项目比较复杂,可以极大简化代码量,并且对后期的需求变更和维护也更加友好。

九、computed和watch的区别?

Computed是计算属性,同步操作,有缓存、computed中的函数必须用return返回

watch是监听值的变化,然后执行回调,异步操作,没缓存

使用场景:computed=>当一个属性受多个属性影响的时候,使用computed---购物车商品结算。watch=>当一条数据影响多条数据的时候,使用watch---搜索框

9.1 示例

我们要实现 第三个表单的值 是第一个和第二个的拼接,并且在前俩表单数值变化时,第三个表单数值也在变化

html:

<div id="myDiv">
    <input type="text" v-model="firstName">
    <input type="text" v-model="lastName">
    <input type="text" v-model="fullName">
</div>

js: 用watch方法来实现 

new Vue({
  el: '#myDiv',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },

  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },

    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

js: 利用computed 来写  

new Vue({
       el:"#myDiv",
            data:{
                firstName:"Den",
                lastName:"wang",
            },

            computed:{
                fullName:function(){
                    return  this.firstName  + " " +this.lastName;
                }
            }
   })

很容易看出 computed 在实现上边的效果时,是更简单的。

9.2 详解 comouted 计算属性

在vue的 模板内({{}})是可以写一些简单的js表达式的 ,很便利。但是如果在页面中使用大量或是复杂的表达式去处理数据,对页面的维护会有很大的影响。这个时候就需要用到computed 计算属性来处理复杂的逻辑运算。

1.优点:

在数据未发生变化时,优先读取缓存。computed 计算属性只有在相关的数据发生变化时才会改变要计算的属性,当相关数据没有变化时,它会读取缓存。而不必想 motheds方法 和 watch 方法是否每次都去执行函数。

2.setter 和 getter方法:(注意在vue中书写时用set 和 get)

setter 方法在设置值时触发。

getter 方法在获取值时触发。

computed:{
   fullName:{
    //这里用了es6书写方法
        set(){
             alert("set");
        },
        get(){
           alert("get");
           return  this.firstName  + " " +this.lastName;
        },
   }
 }

9.3 watch 方法

虽然计算属性在大多数情况下是非常适合的,但是在有些情况下我们需要自定义一个watcher,在数据变化时来执行异步操作,这时watch是非常有用的。

十、改变this指向的方法有几种?

call 立即调用 任意参数  用途:继承

apply 立即调用 参数数组形式  用途:数学公式求最大最小

bind 不立即调用 任意参数  用途:想改变this指向,但是不想立即执行

10.1 call方法

1、call()方法可以进行普通函数的调用

2、call()方法可以改变this的指向,如果没有参数,this指向window

3、call()方法可以改变this的指向,如果有一个参数,this指向该参数

4、call()方法可以改变this的指向,如果有多个参数,this指向第一个参数,剩下的是个参数列表

实例:

<script>
    function add(a,b){
        alert(a+b);
    }

    function sub(a,b){
        alert(a-b);
    }

    document.getElementById("id1").onclick = function(){
        add.call(sub,3,1);
    }
 </script>

10.2 apply方法

1、 apply()方法可以进行普通函数的调用

2、apply()方法可以改变this的指向,如果没有参数,this指向window

3、apply()方法可以改变this的指向,如果有一个参数,this指向该参数

4、apply()方法可以改变this的指向,如果有多个参数,第一个参数是null或者window,第二个参数是数组

实例:

var arr = [11, 55, 33, 66, 88];

  console.log(Math.max(11, 22, 33));//33

  console.log(Math.max(arr));//NaN

  console.log(Math.max.apply(window, arr));//88

  console.log(Math.max.apply(null, [11, 55, 33, 66, 88]));//88

10.3 bind方法

推荐使用第二种形式,第三种用的相对较少,但也是必须掌握的内容。

bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

1、bind()不能进行函数的调用

2、可以改变this指向

实例:

var name = "小明";
var obj3 = {
    name: "小刚",
    getName: function () {
      var name = "小熊";
      console.log(this.name);//小刚
      console.log(name);//
      that = this;
      /*  that = this
       var fn2 = function () {
         console.log(this.name);//小明 //小刚
       }.bind(that); */

      var fn2 = function () {
        var name = "小李";
        console.log(this.name);//小明
        console.log(name);//小李
      }.bind(that);

      fn2();
    }
 }
 
obj3.getName();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值