前端一面常见vue面试题汇总

说说你对 proxy 的理解,Proxy 相比于 defineProperty 的优势

Object.defineProperty() 的问题主要有三个:

  • 不能监听数组的变化 :无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应
  • 必须遍历对象的每个属性 :只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果属性值是对象,还需要深度遍历。Proxy 可以劫持整个对象,并返回一个新的对象
  • 必须深层遍历嵌套的对象

Proxy的优势如下:

  • 针对对象: 针对整个对象,而不是对象的某个属性 ,所以也就不需要对 keys 进行遍历
  • 支持数组:Proxy 不需要对数组的方法进行重载,省去了众多 hack,减少代码量等于减少了维护成本,而且标准的就是最好的
  • Proxy的第二个参数可以有 13 种拦截方:不限于applyownKeysdeletePropertyhas等等是Object.defineProperty不具备的
  • Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改
  • Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利

proxy详细使用点击查看(opens new window)

Object.defineProperty的优势如下:

兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平

defineProperty的属性值有哪些

Object.defineProperty(obj, prop, descriptor)

// obj 要定义属性的对象
// prop 要定义或修改的属性的名称
// descriptor 要定义或修改的属性描述符

Object.defineProperty(obj,"name",{
   
  value:"poetry", // 初始值
  writable:true, // 该属性是否可写入
  enumerable:true, // 该属性是否可被遍历得到(for...in, Object.keys等)
  configurable:true, // 定该属性是否可被删除,且除writable外的其他描述符是否可被修改
  get: function() {
   },
  set: function(newVal) {
   }
})

相关代码如下

import {
    mutableHandlers } from "./baseHandlers"; // 代理相关逻辑
import {
    isObject } from "./util"; // 工具方法

export function reactive(target) {
   
  // 根据不同参数创建不同响应式对象
  return createReactiveObject(target, mutableHandlers);
}
function createReactiveObject(target, baseHandler) {
   
  if (!isObject(target)) {
   
    return target;
  }
  const observed = new Proxy(target, baseHandler);
  return observed;
}

const get = createGetter();
const set = createSetter();

function createGetter() {
   
  return function get(target, key, receiver) {
   
    // 对获取的值进行放射
    const res = Reflect.get(target, key, receiver);
    console.log("属性获取", key);
    if (isObject(res)) {
   
      // 如果获取的值是对象类型,则返回当前对象的代理对象
      return reactive(res);
    }
    return res;
  };
}
function createSetter() {
   
  return function set(target, key, value, receiver) {
   
    const oldValue = target[key];
    const hadKey = hasOwn(target, key);
    const result = Reflect.set(target, key, value, receiver);
    if (!hadKey) {
   
      console.log("属性新增", key, value);
    } else if (hasChanged(value, oldValue)) {
   
      console.log("属性值被修改", key, value);
    }
    return result;
  };
}
export const mutableHandlers = {
   
  get, // 当获取属性时调用此方法
  set, // 当修改属性时调用此方法
};

Proxy只会代理对象的第一层,那么Vue3又是怎样处理这个问题的呢?

判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理, 这样就实现了深度观测。

监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?

我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger

组件通信

组件通信的方式如下:

(1) props  /   $emit

父组件通过props向子组件传递数据,子组件通过$emit和父组件通信

1. 父组件向子组件传值
  • props只能是父组件向子组件进行传值,props使得父子组件之间形成了一个单向下行绑定。子组件的数据会随着父组件不断更新。
  • props 可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。
  • props属性名规则:若在props中使用驼峰形式,模板中需要使用短横线的形式
// 父组件
<template>
  <div id="father">
    <son :msg="msgData" :fn="myFunction"></son>
  </div>
</template>

<script>
import son from "./son.vue";
export default {
   
  name: father,
  data() {
   
    msgData: "父组件数据";
  },
  methods: {
   
    myFunction() {
   
      console.log("vue");
    },
  },
  components: {
    son },
};
</script>

// 子组件
<template>
  <div id="son">
    <p>{
   {
    msg }}</p>
    <button @click="fn">按钮</button>
  </div>
</template>
<script>
export default {
    name: "son", props: ["msg", "fn"] };
</script>

2. 子组件向父组件传值
  • $emit绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过v-on监听并接收参数。
// 父组件
<template>
  <div class="section">
    <com-article
      :articles="articleList"
      @onEmitIndex="onEmitIndex"
    ></com-article>
    <p>{
   {
    currentIndex }}</p>
  </div>
</template>

<script>
import comArticle from "./test/article.vue";
export default {
   
  name: "comArticle",
  components: {
    comArticle },
  data() {
   
    return {
    currentIndex: -1, articleList: ["红楼梦", "西游记", "三国演义"] };
  },
  methods: {
   
    onEmitIndex(idx) {
   
      this.currentIndex = idx;
    },
  },
};
</script>

//子组件
<template>
  <div>
    <div
      v-for="(item, index) in articles"
      :key="index"
      @click="emitIndex(index)"
    >
      {
   {
    item }}
    </div>
  </div>
</template>

<script>
export default {
   
  props: ["articles"],
  methods: {
   
    emitIndex(index) {
   
      this.$emit("onEmitIndex", index); // 触发父组件的方法,并传递参数index
    },
  },
};
</script>

(2)eventBus事件总线($emit / $on

eventBus事件总线适用于父子组件非父子组件等之间的通信,使用步骤如下: (1)创建事件中心管理组件之间的通信

// event-bus.js

import Vue from 'vue'
export const EventBus = new Vue()

(2)发送事件 假设有两个兄弟组件firstComsecondCom

<template>
  <div>
    <first-com></first-com>
    <second-com></second-com>
  </div>
</template>

<script>
import firstCom from "./firstCom.vue";
import secondCom from "./secondCom.vue";
export default {
    components: {
    firstCom, secondCom } };
</script>

firstCom组件中发送事件:

<template>
  <div>
    <button @click="add">加法</button>
  </div>
</template>

<script>
import {
    EventBus } from "./event-bus.js"; // 引入事件中心

export default {
   
  data() {
   
    return {
    num: 0 };
  },
  methods: {
   
    add() {
   
      EventBus.$emit("addition", {
    num: this.num++ });
    },
  },
};
</script>

(3)接收事件secondCom组件中发送事件:

<template>
  <div>求和: {
   {
    count }}</div>
</template>

<script>
import {
    EventBus } from "./event-bus.js";
export default {
   
  data() {
   
    return {
    count: 0 };
  },
  mounted() {
   
    EventBus.$on("addition", (param) => {
   
      this.count = this.count + param.num;
    });
  },
};
</script>

在上述代码中,这就相当于将num值存贮在了事件总线中,在其他组件中可以直接访问。事件总线就相当于一个桥梁,不用组件通过它来通信。

虽然看起来比较简单,但是这种方法也有不变之处,如果项目过大,使用这种方式进行通信,后期维护起来会很困难。

(3)依赖注入(provide / inject)

这种方式就是Vue中的依赖注入,该方法用于父子组件之间的通信。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方法来进行传值。就不用一层一层的传递了。

provide / inject是Vue提供的两个钩子,和datamethods是同级的。并且provide的书写形式和data一样。

  • provide 钩子用来发送数据或方法
  • inject钩子用来接收数据或方法

在父组件中:

provide() {
    
    return {
        
        num: this.num  
    };
}

在子组件中:

inject: ['num']

还可以这样写,这样写就可以访问父组件中的所有属性:

provide() {
   
 return {
   
    app: this
  };
}
data() {
   
 return {
   
    num: 1
  };
}

inject: ['app']
console.log(this.app.num)

注意: 依赖注入所提供的属性是非响应式的。

(3)ref / $refs

这种方式也是实现父子组件之间的通信。

ref: 这个属性用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法。

在子组件中:

export default {
   
  data () {
   
    return {
   
      name: 'JavaScript'
    }
  },
  methods: {
   
    sayHello () {
   
      console.log('hello')
    }
  }
}

在父组件中:

<template>
  <child ref="child"></component-a>
</template>
<script>
import child from "./child.vue";
export default {
   
  components: {
    child },
  mounted() {
   
    console.log(this.$refs.child.name); // JavaScript
    this.$refs.child.sayHello(); // hello
  },
};
</script>

(4)$parent / $children

  • 使用$parent可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)
  • 使用$children可以让组件访问子组件的实例,但是,$children并不能保证顺序,并且访问的数据也不是响应式的。

在子组件中:


                
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值