爆刷面试详解✊ - 百度一面😰,重视基础🕶

📌 JS 的数据类型及其区别

八大数据类型

  1. undefined:变量未初始化
  2. null:空值或不存在的对象
  3. boolean:布尔值,true 或 false
  4. number:可表示整数和浮点数,其中包含特殊值 NaNInfinity
  5. string:字符串
  6. symbol:创建唯一且不可变的值,主要用于对象属性的唯一标识,避免属性名冲突。(ES6 新增)
  7. bigint:可表示任意精度的整数,允许操作超过 number 表示范围的整数。(ES6 新增)
  8. object:对象,其中囊括了普通对象、数组以及方法。

基础(原始)类型 与 引用类型的区别

存储位置区别

  • 基本类型 存储在 栈(stack) 中,值直接保存在变量访问的位置,由于其大小固定且频繁使用,存储在栈中具有更高的性能。
  • 引用类型 存储在 堆(heap) 中,占用空间较大且大小不固定,变量保存的是对实际对象的引用(即指针),这些引用存储在栈中。

赋值区别

  • 基本类型 :复制的是值本身。例如,将一个 number 类型的变量赋值给另一个变量,两个变量互不影响。
  • 引用类型:复制的是指针。多个变量引用同一个对象时,一个变量的修改会影响其他变量。

📌 JS 中判断数组的方法

设需要判断的变量为 obj

方法 1: object.prototype.toString()

js 复制代码
object.prototype.toString().call(obj).slice(8, -1) === "Array"
  • object.prototype.toString() 方法可按 [object xxxx] 的形式返回调用者 的类型。此时的调用者为 object.prototype
  • 不可以直接使用 obj.toString(), 因为所有数据类型都重写toString() 方法,如 new Array([1,2,3]).toString() 的结果为 1, 2, 3
  • 因此,我们可以通过 call() 改变调用者的指向 ,来确保调用的是重写前的 toString 方法,且调用者是我们需要判断的变量。

方法 2:使用原型链判断

每个构造函数都有一个原型对象 prototype,每个实例都有指向原型对象的指针 __proto__ 。例如:

javascript 复制代码
function fn() {
    let a = 1;
} // fn 含有原型对象 prototype
let instance = new fn();
console.log(fn.prototype.constructor === fn) // true: 原型对象含一个指针指向其构造函数
console.log(instance.__proto__ === fn.prototype) // true
console.log(instance.__proto__.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__) // null

这种顺藤摸瓜找原型的链,就叫 原型链

  • 直接访问变量的 __proto__ 属性,判断其是否等于 Array.prototype。(这个方法不推荐使用,因为 __proto__ 是非标准属性,尽管现在绝大数浏览器都支持)

    js 复制代码
    obj.__proto__ === Array.prototype
  • 使用 instanceof 判断,如 arr instanceof Arrayinstanceof 会遍历左侧变量的原型链,直到找到右侧变量的 prototype。如找到返回 true,反之返回 false。要注意的是这个方法不能判别 null 类型,会报错。

    js 复制代码
    obj instanceof Array === true
  • 使用 Array.prototype.isPrototypeOf(obj),判断 Array.prototype 是否在 obj 变量的原型链中。

    js 复制代码
    Array.prototype.isPrototypeOf(obj) === true

方法 3:Array.isArray(obj)

js 复制代码
Array.isArray(obj) === true
  • 在 ES6 之后,该方法也成为官方最推荐的方法 ,具体原因如下:
    • 他与 instanceof 等使用检查原型链原理的判断的方法不同,该方法不检查原型链 ,也不依赖 Array 的构造函数,而是检查传入的对象是否具有数组特定的行为(比如数组方法和属性)以直接确定该对象是否为数组类型。
    • 正因为这样的特性,它可以安全地使用跨领域对象(cross-realm objects) 。 这指的是在不同 JS 执行环境(例如不同的 iframe、浏览器标签页、Web Worker等)中创建的对象。每个执行环境可能有自己的全局对象(如 windowself),即使两个执行环境中都存在 Array 构造函数,但它们的 Array 可能被视为不同的构造函数 。这样,如果你在一个领域中使用 instanceof Array 检查数组,可能会因为 Array 构造函数的标识不同而失败

📌 JS 中对象与空对象的判断方法

方法 1:Object.keys()

js 复制代码
function isEmptyObject(obj) {
  // 为使得代码更加稳定,应先判断 obj 是否为空、且是否属于对象类型。
  return obj && typeof obj === 'object' && Object.keys(obj).length === 0;
}

Object.keys() 方法会返回一个数组,数组元素为参数(对象)中所有可枚举属性 。因此只需判断这个数组长度是否为 0,就能间接判断对象是否为空。

方法 2:for .. in 遍历对象

js 复制代码
function isEmptyObject(obj) {
  for (let prop in obj) {
    if (Object.hasOwn(obj, prop)) {
      return false;
    }
  }
  return true;
}

方法 3:JSON.stringfy() 将对象转为字符串判断

js 复制代码
function isEmptyObject(obj) {
  return JSON.stringify(obj) === '{}';
}

📌 JS 中 ===== 的区别

  • ==:在比较之前会进行类型转换。如果操作数的类型不同,会尝试将它们转换为相同类型再进行比较。
  • ===:不会进行类型转换。如果操作数的类型不同,直接返回 false

== 的转换规则

  1. 如果两个值类型相同,则直接比较它们的值。
  2. 如果一个值是 null,另一个值是 undefined,则它们相等
  3. 如果一个值是数字,另一个值是字符串,则将字符串转换为数字后再比较。
  4. 如果一个值是布尔值,另一个值是非布尔值,则将布尔值转换为数字后再比较。
  5. 如果一个值是对象,另一个值是数字、字符串或布尔值,则将对象转换为原始值后再比较。

且因为 === 无需类型转换,其执行速度也更快(虽然差距也并不算大)。但 === 的行为也更可预测,不易出现反直觉的结果,所以更受开发者青睐。

📌 Vue 3 与 Vue 2 的区别

首先 Vue 3 使用 TS 进行了完整重构,因此它对 TS 的支持也相当友好,除此之外还有以下改进:

新增组合式 API

Vue 3 引入了组合式 API,它为 Vue 开发者提供了一种更加灵活的方式来组织和复用逻辑。通过 setup() 函数,开发者可以更加清晰地组织逻辑,并将组件内部的响应式状态和方法进行更好的模块化。

  • Vue 2:基于选项式 API(Options API),开发者通常将所有的逻辑(如 data、methods、computed 等)放在组件的不同选项中,随着组件的增长,这种方式可能会导致逻辑难以管理。
  • Vue 3 :通过组合式 API,可以将逻辑根据功能进行拆分,并通过 reactiverefcomputed 等 API 直接在 setup() 中声明响应式数据和方法,提升了代码的可读性和可维护性。

优化了响应式的实现原理

Vue 3 的响应式系统在性能和设计上做了大幅优化,采用了 Proxy 代替 Vue 2 中的 Object.defineProperty。

  • Vue 2 :Vue 2 使用 Object.defineProperty 对对象进行数据劫持,这种方式存在一些限制,如无法监听到对象的新增/删除属性,并且在某些场景下性能较差。
  • Vue 3 :Vue 3 使用 ES6 的 Proxy API 实现响应式系统,它能够监听对象的所有操作(包括新增、删除、修改属性等),并且性能得到了显著提升。Proxy 的使用让 Vue 3 的响应式系统更加强大和灵活,避免了 Vue 2 中的一些性能瓶颈。

引入 tree-shaking 特性

Vue 3 开始支持 tree-shaking,使得只有在项目中实际使用到的代码才会被打包到最终的构建中。这对于优化应用的体积和加载性能非常有帮助。

  • Vue 2 :Vue 2 在构建时并不支持 tree-shaking,即使你只用了 Vue 的一小部分功能,最终的打包文件也会包含整个 Vue 库。
  • Vue 3 :Vue 3 采用了更加模块化的设计,支持 tree-shaking,可以有效去除没有用到的代码,减小最终的打包体积。

diff 算法的优化

Vue 3 在 diff 算法上进行了优化,尤其是在性能方面。Vue 3 引入了更高效的虚拟 DOM 比对机制,减少了不必要的 DOM 更新操作,从而提升了渲染性能。

  • Vue 2:Vue 2 使用的是基于"最小更新"的算法,通过比较新旧虚拟 DOM 来决定最小的 DOM 更新操作。虽然性能已足够优秀,但在一些复杂的组件树中,性能仍然可能是瓶颈。
  • Vue 3:Vue 3 对虚拟 DOM 的比对和更新逻辑进行了精细优化,提升了性能,尤其是在大规模渲染和高频更新场景下,性能显著提高。

新增 Suspense 与 Fragment 支持

Vue 3 引入了 Suspense 组件和 Fragment 组件,这使得开发者能够更好地处理异步加载组件和多个根节点的情况。

  • Vue 2:Vue 2 没有内建的 Suspense 功能,并且所有组件都必须有一个根节点,这对于某些布局要求不方便。
  • Vue 3Suspense 组件允许开发者优雅地处理异步加载的场景,可以在数据或组件加载时显示加载状态。Fragment 允许一个组件有多个根节点,增加了布局的灵活性。

参考文章:

📌 Vue 3 异步请求在哪一个生命周期写?

通常会created 这一生命周期中请求异步数据,理由如下:

  1. 这一生命周期意为实例已创建完成,且数据也已观测完成。组件完成了初始化但没有进行渲染。此时就可以安全地发起异步请求,对数据进行操作。
  2. 之所以不使用其它生命周期,是因为 beforeCreate 及更早时,实例尚未完全初始化 ,还无法访问数据、方法等组件内容。而 mounted 及更晚时,虽然视图已完成渲染,更适合进行 DOM 操作,但若在此之前执行异步请求获取数据可以有效避免视觉抖动问题,优化用户体验。

📌 聊聊 JS 事件冒泡机制

Javascript 的事件处理有两种机制:

  • 捕获机制(Capturing):从外向内截获事件,直到最内层触发事件的元素。
  • 冒泡机制(Bubbling):从内向外传播事件,直到最外层根元素。

这两种机制的优先级是 捕获机制 先于 冒泡机制。即一个事件在某一节点触发,是从根节点往目标元素传播,到达目标处后再向根节点传播。

如果要设置监听事件的方式,可以在使用 addEventListener() 方法时,指定第三个参数:

  • 如为 true : 则代表使用捕获机制监听。
  • 如为 false 或 缺省 : 则代表使用冒泡机制监听。

如果要阻止事件的传递,则使用 event.stopPropagation() 方法即可。

📌 JS 中怎么写自定义事件?怎么在全局监听事件?怎么访问到这个数据?

自定义事件的实现

自定义事件可以通过 EventEmitter(Node.js 中)或者使用 DOM 的原生 CustomEvent 进行实现。对于前端应用(例如 Vue 或原生 JavaScript),通常使用 EventTargetEventEmitter 来管理自定义事件。

1. 使用 EventTarget(原生 JavaScript)

在浏览器中,可以通过 EventTarget 创建一个事件对象,并通过 dispatchEvent 触发事件,使用 addEventListener 来监听事件。

javascript 复制代码
// 创建一个事件目标
const emitter = new EventTarget();

// 监听事件
emitter.addEventListener('customEvent', (e) => {
  console.log('Custom event received:', e.detail);
});

// 派发事件
emitter.dispatchEvent(new CustomEvent('customEvent', { detail: 'This is custom data' }));

全局监听事件

如果要在全局监听自定义事件,可以利用 window 对象或一个单独的事件总线(event bus)。

2. 使用 window 对象作为全局事件总线

javascript 复制代码
// 全局事件监听
window.addEventListener('customEvent', (e) => {
  console.log('Global event listener:', e.detail);
});

// 派发事件
window.dispatchEvent(new CustomEvent('customEvent', { detail: 'Global custom data' }));

3. 使用事件总线(Event Bus)

事件总线是通过创建一个 EventEmitter 实例来监听和触发事件。Vue 2 也常用这种方式来处理兄弟组件之间的通信。

javascript 复制代码
// eventBus.js
import { EventEmitter } from 'events';

const eventBus = new EventEmitter();

export default eventBus;

// 在组件或文件中
import eventBus from './eventBus';

// 监听事件
eventBus.on('customEvent', (data) => {
  console.log('Received custom data:', data);
});

// 触发事件
eventBus.emit('customEvent', 'This is custom data');

通过 eventBus,我们可以在整个应用中实现跨组件、跨模块的事件通信。

📌 Vue 2/3 分别如何定义全局都能访问到的数据?

Vue 2

在 Vue 2 中,通常通过 Vue 实例作为全局事件总线来共享数据。可以通过 Vue.prototype 来为所有组件定义全局方法或变量。

全局数据(如 Vue.prototype

javascript 复制代码
javascript
复制代码
// main.js
import Vue from 'vue';

Vue.prototype.$globalData = { message: 'Hello, World!' };

new Vue({
  render: h => h(App),
}).$mount('#app');

在组件中可以直接访问:

arduino 复制代码
javascript
复制代码
// Inside any component
console.log(this.$globalData.message); // 输出: Hello, World!

Event Bus

在 Vue 2 中,可以通过创建一个空的 Vue 实例作为事件总线:

javascript 复制代码
javascript
复制代码
// eventBus.js
import Vue from 'vue';
export const eventBus = new Vue();

// 发送事件
eventBus.$emit('eventName', data);

// 监听事件
eventBus.$on('eventName', (data) => {
  console.log(data);
});

Vue 3

Vue 3 推荐使用 provideinject 来实现跨组件的数据共享,或者使用 PiniaVuex 来管理全局状态。

通过 provideinject

javascript 复制代码
// App.vue
export default {
  setup() {
    provide('globalData', 'This is global data');
  }
}
javascript 复制代码
// ChildComponent.vue
import { inject } from 'vue';

export default {
  setup() {
    const globalData = inject('globalData');
    console.log(globalData);  // 'This is global data'
  }
}

📌 ES6 循环数组的方法,什么遍历方法能跳出遍历

  • forEach :不可跳出,无法使用 breakreturn 来提前结束遍历。

    javascript 复制代码
    [1, 2, 3, 4].forEach((item) => {
      console.log(item);
    });
  • map:返回一个新数组,用于遍历并生成新的数组。

    javascript 复制代码
    const newArr = [1, 2, 3].map(item => item * 2);
    console.log(newArr); // [2, 4, 6]
  • for...of :可以使用 break 跳出遍历,通常用于需要提前终止的循环。

    javascript 复制代码
    for (const item of [1, 2, 3, 4]) {
      if (item === 3) break;
      console.log(item);
    }
  • some :可以通过返回 true 来跳出遍历。

    javascript 复制代码
    [1, 2, 3, 4].some((item) => {
      if (item === 3) return true; // 终止遍历
      console.log(item);
    });
  • find:用于查找并返回第一个符合条件的元素,自动跳出循环。

    javascript 复制代码
    const found = [1, 2, 3, 4].find(item => item === 3);
    console.log(found); // 3

📌 讲讲 forEach 几个参数,返回什么?

forEach 是一个数组方法,用来遍历数组。它有三个参数:

  1. currentValue:当前元素的值。
  2. index(可选):当前元素的索引。
  3. array(可选):原始数组。
javascript 复制代码
[1, 2, 3].forEach((value, index, array) => {
  console.log(value, index, array);
});

返回值:

forEach 没有返回值,它总是返回 undefined

📌 截取字符串的方法,slicesubstring 的区别

  • slice

    • 用于从字符串或数组中提取指定范围的内容。
    • 支持负数参数,负数表示从末尾开始计算。
    javascript 复制代码
    'abcdef'.slice(1, 4); // 'bcd'
    'abcdef'.slice(-3);   // 'def'
  • substring

    • 用于提取字符串中两个指定索引之间的字符。
    • 不支持负数参数,负数会被当作 0 处理。
    javascript 复制代码
    'abcdef'.substring(1, 4); // 'bcd'
    'abcdef'.substring(-3);   // 'abc' (负数被当作 0)
相关推荐
GISer_Jing几秒前
Javascript排序算法(冒泡排序、快速排序、选择排序、堆排序、插入排序、希尔排序)详解
javascript·算法·排序算法
JustHappy19 分钟前
「我们一起做组件库🌻」做个面包屑🥖,Vue的依赖注入实战💉(VersakitUI开发实录)
前端·javascript·github
拉不动的猪35 分钟前
刷刷题16
前端·javascript·面试
kiramario37 分钟前
【结束】JS如何不通过input的onInputFileChange使用本地mp4文件并播放,nextjs下放入public文件的视频用video标签无法打开
开发语言·javascript·音视频
哑巴语天雨1 小时前
前端面试-网络协议篇
websocket·网络协议·http·面试·https
01_2 小时前
力扣hot100——LRU缓存(面试高频考题)
leetcode·缓存·面试·lru
祈澈菇凉2 小时前
如何结合使用thread-loader和cache-loader以获得最佳效果?
前端
垣宇2 小时前
Vite 和 Webpack 的区别和选择
前端·webpack·node.js
java1234_小锋2 小时前
一周学会Flask3 Python Web开发-客户端状态信息Cookie以及加密
前端·python·flask·flask3
化作繁星2 小时前
如何在 React 中测试高阶组件?
前端·javascript·react.js