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

📌 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)
相关推荐
uhakadotcom4 分钟前
Apache Airflow入门指南:数据管道的强大工具
算法·面试·github
树上有只程序猿16 分钟前
后端思维之高并发处理方案
前端
uhakadotcom16 分钟前
Ruff:Python 代码分析工具的新选择
后端·面试·github
uhakadotcom19 分钟前
Mypy入门:Python静态类型检查工具
后端·面试·github
庸俗今天不摸鱼1 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
QTX187301 小时前
JavaScript 中的原型链与继承
开发语言·javascript·原型模式
黄毛火烧雪下1 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox1 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞1 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行1 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox