前端面试拼图-前端基础(二)

本文介绍了前端开发面试中常见的面试题,包括DOM属性计算(如offsetHeight与clientHeight的区别)、HTMLCollection和NodeList的差异、Vue中computed与watch的区别,以及Vue组件间的通讯方法、Vuex状态管理、HTTP跨域的Option请求原理等内容。
摘要由CSDN通过智能技术生成

摘要:最近,看了下慕课2周刷完n道面试题,记录下...

1. offsetHeight scrollHeight clientHeight 区别

        计算规则:

        offsetHeight offsetWidth : border + padding + content

        clientHeight clientWidth: padding + content

        scrollHeight scrollWidth: padding + 实际内容尺寸(当显示滚动条时,就大于content尺寸)

2. HTMLCollection 和 NodeList区别

        Node 和Element

        DOM是一棵树,所有的节点都是Node

        Node是Element的基类

        Element是其他HTML元素的基类,如HTMLDivElement

        HTMLCollection是Element的集合;NodeList是Node的集合

        注:获取Node和Element的返回结果可能不一样

        如果elem.childNode和elem.children不一样

        前者包含Text和Comment等节点,后者不包含

        扩展:HTMLCollection和NodeLIst都是类数组,将类数组转换为数组的几种方式

const arr1 = Array.from(list)
const arr2 = Array.prototype.slice.call(list)
const arr3 = [...list]

3. Vue computed和watch的区别

        两者用途不同

        computed 又称计算属性,它提供给开发者根据依赖数据动态生成派生值的计算方式,同时确保计算结果的缓存和更新机制。[1]

import { ref, computed } from 'vue';
const count1 = ref(0);
const count2 = ref(1);
// 创建只读计算属性
const double = computed(() => count1.value * 2);

// 创建可写计算属性
const plusOne = computed({
  get: () => count2.value + 1,
  set: (val) => {
    count2.value = val - 1;
  }
});

// Vue 3的computed函数可以根据计算函数的返回值自动推导类型
console.log(double.value); // 计算属性的值; 推导得到的类型:ComputedRef<number>
plusOne.value = 1; // 设置计算属性的值
console.log(count2.value); // 0

        watch 又称侦听器,开发者可以使用它监视响应式数据的变化,并执行自定义的逻辑。常用语监听多个数据源变化、异步任务和副作用操作。

import { ref, watch } from 'vue';

const count1 = ref(0);
const count2 = ref(0);
const double = ref(0);

// 创建单个数据源的侦听器
watch(count1, (newVal, oldVal) => {
  console.log(`count1变化:新值 - ${newVal}, 旧值 - ${oldVal}`);
});
// 创建多个数据源的侦听器
watch([count2, double], ([newCount, newDouble], [oldCount, oldDouble]) => {
  console.log(`count2变化:新值 - ${newCount}, 旧值 - ${oldCount}`);
  console.log(`double变化:新值 - ${newDouble}, 旧值 - ${oldDouble}`);
});

        此外,Vue 3 还引入了即时执行的侦听器watchEffect ,它无需指定侦听器的数据源。简单比较下这三者。

自动收集依赖否返回值是否可以赋值立即执行本质
computed自动可以class
watch需指定依赖对象不可以看参数function
watcheffect自动不可以function

4. Vue组件通讯有几种方式,尽量全面

        Vue组件间通讯分为不同场景的:

  • 父子组件
  • 上下级组件(跨多级)通讯
  • 全局组件

        几种组件通讯方式:

props和Event

        父组件通过 props 向子组件传递数据,子组件通过 events 向父组件发送消息;

<!--Home.vue-->
<template>
  <HelloWorld msg="Welcome to your Vue.js App" @showMsg="showMsg"/>
</template>
<script>
  import HelloWorld from '@/component/HelloWorld.vue'
  export defult {
    name: 'Home',
    components: {
      HelloWorld,
    },
    methods: {
      showMsg(msg) {
        console.log(msg)
      }
    }
  }
</script>

<!--HelloWorld.vue-->
<template>
  <h1 @click="clickHandler"> {{msg}} </h1>
</template>
<script>
  export default {
    name: 'HelloWorld',
    props: {
      msg: String
    },
    emits: ['showMsg'],  // Vue3新增属性
    methods: {
      clickHandler() {
        this.$emit('showMsg', 'hello world')
      }
    }
  }
</script>

自定义事件

        Vue 实例可以通过 $on 方法监听自定义事件,通过 $emit 触发自定义事件,从而实现组件之间的通讯。自定定义事件常用于兄弟组件,跨多级通讯,灵活但容易用乱。

<!--ParentComponent.vue-->
<template>
  <CustomEvent1/>
  <CustomEvent2/>
</template>
<script>
  import CoustomEvent1 from '@/component/CoustomEvent1.vue'
  import CoustomEvent1 from '@/component/CoustomEvent2.vue'
  export defult {
    name: 'Home',
    components: {
      CustomEvent1,
      CustomEvent2
    },
    methods: {
      showMsg(msg) {
        console.log(msg)
      }
    }
  }
</script>

<!--CustomEvent1.vue 接收方-->
<template>
  <p> recive coustom event</p>
</template>
<script>
import event from '../utils/event.js'  //引入event发布订阅的总线
export default {
  name: 'CustomEvent1',
  methods: {
    showMsg(msg) {
      console.log(msg)    
    }
  },
  mounted() {
    event.on('showMsg', this.showMsg)  // 监听
  },
  // vue 2.x beforeDestroy 需要销毁避免内存泄漏
  beforeUnmount() {
    event.off('showMsg', this.showMsg)  // 销毁;监听和销毁要写函数名字,处理同一个函数
  }
}
</script>

<!--CustomEvent2.vue 发送方-->
<template>
  <p><button @click="trigger">trigger coustom event<button></p>
</template>
<script>
import event from '../utils/event.js'  //引入event发布订阅的总线
export default {
  name: 'CustomEvent2',
  methods: {
    trigger() {
      event.emit('showMsg', 'hello coustom event')
    }
  },
}
</script>


//event.js
import ee from 'event-emitter'
// Vue2 : new Vue()就是 event
// Vue3 : 引入第三方库
const event = ee()

export default event

$attrs

        之前资料$attrs 和 $listeners比较多,但是后者在Vue3中已经移除;子组件可以通过 $attrs 获取父组件传递的非 prop 和emits特性。

<!--ParentComponent.vue-->
<template>
  <AttrsDome/>
</template>
<script>
  import AttrsDome from '@/component/AttrsAndListeners/Level1.vue' //主需要引用Level1
  export defult {
    name: 'Home',
    components: {
      AttrsDome,
    },
    methods: {
      showMsg(msg) {
        console.log(msg)
      }
    }
  }
</script>

<!--Level1-->
<template>
  <p>Level1</p>
  <Level2  :a="a" :b="b" :c="c" @getA="getA" @getB="getB" @getC="getC"></Level2>
</template>
<script>
import Level2 from './Level2'
export default {
  name: 'Level1'
  components: {Level2},
  date() {
    return {
      a: 'aaa',
      b: 'bbb',
      c: 'ccc'
    }
  },
  methods: {
    getA(){
      return this.a
    },
    getB(){
      return this.b
    },
    getC(){
      return this.c
    }
  }
}
</script>

<!--Level2-->
<template>
  <p>Level2</p>
  <Level3  :x="x" :y="y" :z="z" @getX="getx" @getY="gety" @getZ="getz"></Level3>
  v-bind="$attrs"  //将attrs传入下一级Level3,
</template>
<script>
import Level3 from './Level3'
export default {
  name: 'Level2'
  components: {Level3},
  // 只接受Level1传入的a和getA
  props: ['a'],   // Vue2中属性在$attrs,方法在$listener中
  emits: ['getA']
  date() {
    return {
      x: 'xxx',
      y: 'yyy',
      z: 'zzz'
    }
  },
  methods: {
    getX(){
      return this.x
    },
    getY(){
      return this.y
    },
    getZ(){
      return this.z
    }
  },
  created() {
    console.log('level2', Object.keys(this.$attrs))  // ['b', 'c','onGetB', 'onGetC']
    // 也就是说,Level1传入的属性,没有被包含在props和$emits,就会放在attrs中;是前两者的后补
  }
}
</script>

<!--Level3-->
<template>
  <p>Level3</p>
</template>
<script>
export default {
  name: 'Level3',
  // 只接受Levelx传入的a和getX
  props: ['x'],
  emits: ['getX']
  inheritAttrs: false,// 避免属性继承
  date() {
    return {
  
    }
  },
  mounted() {
    console.log('Level3 $attrs', Object.keys(this.$attrs))//['y','z','onGetY','onGetZ']
    // Level2中增加v-bind传入attrs后,上面输出为['y','z','onGetY','onGetZ','b', 'c','onGetB', 'onGetC']
  }
}
</script>

$parent和 $refs

        Vue2中可以通过 $parent 和 children 直接访问父组件和子组件的实例,进行直接的组件通讯(父子组件通讯);Vue3中children建议用$refs来获取,可以使用 $refs 获取对子组件实例的引用,从而直接调用子组件的方法或访问子组件的数据。

        上例子中Level3,可以通过$parent直接获取父节点,获取属性调用方法都可以;

<!--Level3-->
<template>
  <p>Level3</p>
  <!--添加子组件HelloWorld的ref属性,为读取是的名称-->
  <HelloWorld msg="hello czh" ref="hello1"/>
</template>
<script>
import HelloWorld from '../HelloWorld'

export default {
  name: 'Level3',
  components: {HelloWorld},
  // 只接受Levelx传入的a和getX
  props: ['x'],
  emits: ['getX']
  inheritAttrs: false,// 避免属性继承
  date() {
    return {
  
    }
  },
  mounted() {
    console.log(this.$parent)  //父节点
    console.log(this.$parent.y)   //yyy   ,获取属性
    console.log(this.$parent.getX())   //xxx   ,调用方法
    console.log(this.refs.hello1.name)  // hello-world
  }
}
</script>

<!--HelloWorld.vue-->
<template>
  <h1 @click="clickHandler"> {{msg}} </h1>
</template>
<script>
  export default {
    name: 'HelloWorld',
    props: {
      msg: String
    },
    emits: ['showMsg'],  // Vue3新增属性
    data() {    // 新增data中的name属性
      return {
        name: 'hello-world'
      }
    },
    methods: {
      clickHandler() {
        this.$emit('showMsg', 'hello world')
      }
    }
  }
</script>

        需要注意$parent 和 $refs获取父组件和子组件,需要在mounted中获取,因为created中组件还未渲染完成。

provide/inject

        provide/inject是多层级组件间通讯较完美的解决方案,父组件通过 provide 向子孙组件注入数据,子孙组件通过 inject 接收注入的数据(可以跨级接收注入的数据)。

<!--Level1-->
<template>
  <p>Level1 :<input v-model=name></p>
  <Level2></Level2>
</template>
<script>
import {computed} from 'vue'  // 传入data中响应式的数据需要使用computed包裹
import Level2 from './Level2'
export default {
  name: 'Level1'
  components: {Level2},
  date() {
    return {
     name: 'czh'
    }
  },
  // provide: {    // 传入数据为静态字符串
  //   info: 'aaa'
  // },
  provide () {   //传入data中响应式的数据
    return {
      info: computed(() => this.name)  // 使用computed包裹
    }
  }
}
</script>

<!--Level2-->
<template>
  <p>Level2  {{info}}</p>   <!--使用注入的数据info-->
  <Level3></Level3>
</template>
<script>
import Level3 from './Level3'
export default {
  name: 'Level2'
  components: {Level3},
  inject: ['info'],   // 接收注入的数据
}
</script>

<!--Level3-->
<template>
  <p>Level3 {{info}}</p>   <!--使用注入的数据info-->
</template>
<script>
export default {
  name: 'Level3',
  inject: ['info'],   // 接收注入的数据
}

Vuex

        使用 Vuex 来进行状态管理,实现组件之间共享状态和通信。

5. Vuex 中mutation和action的区别

        mutation:原子操作,必须是同步代码;action: 可包含多个mutation,可包含异步代码;

         具体来说:

        mutation:

  • mutation 是 Vuex 中用于修改状态的唯一途径;
  • mutation 必须是同步函数,用于直接改变状态;
  • 在组件中通过 commit 方法来触发 mutation;
  • 由于 mutation 是同步执行的,因此可以更容易地追踪状态的变化和调试。

        action:

  • action 类似于 Mutation,但是可以包含任意异步操作;
  • action 可以包含业务逻辑、异步请求等操作,并且最终提交 mutation 来修改状态;
  • 在组件中通过 dispatch 方法来触发 action;
  • action 可以处理复杂的逻辑,例如异步请求、多个 mutation 的组合等。

6. JS严格模式有什么特点

        JavaScript 的严格模式(Strict Mode)是一种在 ECMAScript 5 中引入的特性,它对 JavaScript 解析和执行时的行为做了一些限制和改变。使用严格模式可以帮助开发者写出更加安全、规范的 JavaScript 代码开启严格模式:

'use strict'  //全局开启

function fn() {
  'use strict' // 某个函数开启
}

特点:

        全局变量必须先声明

'use strict'
m = 10;   //Uncaught ReferenceError: m is not defined

        禁止使用with

'use strict'
const obj = {x:100, y:200}
with(obj) {
  console.log(x, y)    // Strict mode code may not include a with statement
}

        创建eval作用域(eval有自己单独的作用域, 不推荐使用)

'use strict'
var x = 10;
eval(`var x = 20; console.log('in eval', x);`)
console.log('out eval', x);

        禁止this指向window

'use strict'
function fn() {
  console.log('this', this)   // undefined
}
fn()

        函数参数不能重名

'use strict'
function fn(x,x,y) {  // Duplicate parameter name not allowed in this context
}
fn(10,10,20)

7. HTTP跨域时为何要发送option请求

        跨域请求是由于浏览器存在同源策略;

        同源策略一般限制Ajax网络请求,不能跨域请求sever

        不会限制<link> <img><script><iframe>加载第三方资源

        JSONP

        执行原理:利用<script>标签不存在跨域限制,aaa跨域请求bbb的接口。首先,定义全局函数onSuccess, 并将src设置为bbb接口(需要接口配合),这样可以获取bbb接口返回的字符串,该字符串将被作为js执行,调用onSuccess获取接口返回的数据

<!--www.aaa.com-->
<script>
  window.onsuccess = function (date) {
    console.log(data)
  }
</script>
<script src = "https://www.bbb.com/api/getData"></script>


//https://www.bbb.com/api/getData返回了一段字符串:
'onSuccess({erroeno:0, data:{/*数据内容*/}})'

        CORS

// CORS配置允许跨域(服务端)
response.setHeader("Access-Control-Allow-Origin", "允许跨域请求的地址") // 或者'*'
resopnse.setHeader("Access-Control-Allow-Headers", "X-Request-With")
resopnse.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS")
resopnse.setHeader("Access-Control-Allow-Credentials", "true")   //允许跨域接受Cookie

        Options请求(预检请求)的主要目的是为了实现跨域资源共享(CORS)机制的安全性和规范性;

        通过预检请求可以检查服务器是否支持跨域请求、验证请求的安全与否、处理复杂请求(如带有自定义头部,非简单请求方法PUT DELETE或者需要身份认证)、有助于防止跨站点请求伪造(CSRF)等安全问题。

        Options请求浏览器自行发起,无需干涉,也不会影响实际的功能。

[1] 解锁Vue 3的神秘力量:深入理解computed和watch: https://juejin.cn/post/7308563909560549391?searchId=20240228083853714901F13DB2C63CCF19

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值