📌 JS 的数据类型及其区别
八大数据类型
undefined
:变量未初始化null
:空值或不存在的对象boolean
:布尔值,true 或 falsenumber
:可表示整数和浮点数,其中包含特殊值NaN
与Infinity
string
:字符串symbol
:创建唯一且不可变的值,主要用于对象属性的唯一标识,避免属性名冲突。(ES6 新增)bigint
:可表示任意精度的整数,允许操作超过number
表示范围的整数。(ES6 新增)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__
。例如:
javascriptfunction 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__
是非标准属性,尽管现在绝大数浏览器都支持)jsobj.__proto__ === Array.prototype
-
使用
instanceof
判断,如arr instanceof Array
。instanceof
会遍历左侧变量的原型链,直到找到右侧变量的prototype
。如找到返回true
,反之返回false
。要注意的是这个方法不能判别null
类型,会报错。jsobj instanceof Array === true
-
使用
Array.prototype.isPrototypeOf(obj)
,判断Array.prototype
是否在obj
变量的原型链中。jsArray.prototype.isPrototypeOf(obj) === true
方法 3:Array.isArray(obj)
js
Array.isArray(obj) === true
- 在 ES6 之后,该方法也成为官方最推荐的方法 ,具体原因如下:
- 他与
instanceof
等使用检查原型链原理的判断的方法不同,该方法不检查原型链 ,也不依赖Array
的构造函数,而是检查传入的对象是否具有数组特定的行为(比如数组方法和属性)以直接确定该对象是否为数组类型。 - 正因为这样的特性,它可以安全地使用跨领域对象(cross-realm objects) 。 这指的是在不同 JS 执行环境(例如不同的 iframe、浏览器标签页、Web Worker等)中创建的对象。每个执行环境可能有自己的全局对象(如
window
或self
),即使两个执行环境中都存在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
。
==
的转换规则
- 如果两个值类型相同,则直接比较它们的值。
- 如果一个值是
null
,另一个值是undefined
,则它们相等。- 如果一个值是数字,另一个值是字符串,则将字符串转换为数字后再比较。
- 如果一个值是布尔值,另一个值是非布尔值,则将布尔值转换为数字后再比较。
- 如果一个值是对象,另一个值是数字、字符串或布尔值,则将对象转换为原始值后再比较。
且因为 ===
无需类型转换,其执行速度也更快(虽然差距也并不算大)。但 ===
的行为也更可预测,不易出现反直觉的结果,所以更受开发者青睐。
📌 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,可以将逻辑根据功能进行拆分,并通过
reactive
、ref
、computed
等 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 3 :
Suspense
组件允许开发者优雅地处理异步加载的场景,可以在数据或组件加载时显示加载状态。Fragment
允许一个组件有多个根节点,增加了布局的灵活性。
参考文章:
📌 Vue 3 异步请求在哪一个生命周期写?
通常会在 created
这一生命周期中请求异步数据,理由如下:
- 这一生命周期意为实例已创建完成,且数据也已观测完成。组件完成了初始化但没有进行渲染。此时就可以安全地发起异步请求,对数据进行操作。
- 之所以不使用其它生命周期,是因为
beforeCreate
及更早时,实例尚未完全初始化 ,还无法访问数据、方法等组件内容。而mounted
及更晚时,虽然视图已完成渲染,更适合进行 DOM 操作,但若在此之前执行异步请求获取数据可以有效避免视觉抖动问题,优化用户体验。
📌 聊聊 JS 事件冒泡机制
Javascript 的事件处理有两种机制:
- 捕获机制(Capturing):从外向内截获事件,直到最内层触发事件的元素。
- 冒泡机制(Bubbling):从内向外传播事件,直到最外层根元素。
这两种机制的优先级是 捕获机制 先于 冒泡机制。即一个事件在某一节点触发,是从根节点往目标元素传播,到达目标处后再向根节点传播。
如果要设置监听事件的方式,可以在使用 addEventListener()
方法时,指定第三个参数:
- 如为
true
: 则代表使用捕获机制监听。 - 如为
false
或 缺省 : 则代表使用冒泡机制监听。
如果要阻止事件的传递,则使用 event.stopPropagation()
方法即可。
📌 JS 中怎么写自定义事件?怎么在全局监听事件?怎么访问到这个数据?
自定义事件的实现
自定义事件可以通过 EventEmitter
(Node.js 中)或者使用 DOM 的原生 CustomEvent
进行实现。对于前端应用(例如 Vue 或原生 JavaScript),通常使用 EventTarget
或 EventEmitter
来管理自定义事件。
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 推荐使用 provide
和 inject
来实现跨组件的数据共享,或者使用 Pinia 或 Vuex 来管理全局状态。
通过 provide
和 inject
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
:不可跳出,无法使用break
或return
来提前结束遍历。javascript[1, 2, 3, 4].forEach((item) => { console.log(item); });
-
map
:返回一个新数组,用于遍历并生成新的数组。javascriptconst newArr = [1, 2, 3].map(item => item * 2); console.log(newArr); // [2, 4, 6]
-
for...of
:可以使用break
跳出遍历,通常用于需要提前终止的循环。javascriptfor (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
:用于查找并返回第一个符合条件的元素,自动跳出循环。javascriptconst found = [1, 2, 3, 4].find(item => item === 3); console.log(found); // 3
📌 讲讲 forEach
几个参数,返回什么?
forEach
是一个数组方法,用来遍历数组。它有三个参数:
currentValue
:当前元素的值。index
(可选):当前元素的索引。array
(可选):原始数组。
javascript
[1, 2, 3].forEach((value, index, array) => {
console.log(value, index, array);
});
返回值:
forEach
没有返回值,它总是返回 undefined
。
📌 截取字符串的方法,slice
和 substring
的区别
-
slice
:- 用于从字符串或数组中提取指定范围的内容。
- 支持负数参数,负数表示从末尾开始计算。
javascript'abcdef'.slice(1, 4); // 'bcd' 'abcdef'.slice(-3); // 'def'
-
substring
:- 用于提取字符串中两个指定索引之间的字符。
- 不支持负数参数,负数会被当作 0 处理。
javascript'abcdef'.substring(1, 4); // 'bcd' 'abcdef'.substring(-3); // 'abc' (负数被当作 0)