深拷贝、浅拷贝、递归、优化
扁平化
柯里化
this指向+原型
继承
call、apply、bind
js取整的方法,parseInt第二个参数是什么
forEach和map有什么区别,使用场景?
内存泄漏的场景
原型链+原型
严格模式
Js中for in 和for of的区别
slice、splice、split 三者的区别
变量提升、函数提升
循环引用?如何解决
null与undefined
闭包
数组多种去重方法
slice是干嘛的,splice是否会改变原数组
== 和 === 有什么区别?
什么是事件委托?为什么它是有用的?
什么是立即执行的函数表达式(IIFE)?
如何检查一个变量是否是数组?
描述map、reduce 和 filter 方法。
JS
这些前端JS面试题涵盖了许多JavaScript的核心和高级概念。我们可以根据不同的主题对它们进行分类。以下是一个分类示例:
一. 基础概念与语法:
- null与undefined
- == 和 === 有什么区别?
- Js中for in 和for of的区别
- slice、splice、split 三者的区别
- 变量提升、函数提升
- 严格模式
二. 数据结构与类型操作:
1.深拷贝、浅拷贝、递归、优化
浅拷贝:只复制引用,而未复制真正的值,改变其中一个的值,另一个的值也随之改变
深拷贝:复制真正的值,改变其中一个的值,另一个的值不会改变
assignAPI实现一级内容深拷贝,二级内容浅拷贝
assign:一级内容深拷贝,二级及以上浅拷贝 一次把深拷贝和浅拷贝都写出来
// assin:一级内容深拷贝,二级及以上浅拷贝
let obj = {name:"Tom", age:18, job:["web"]}
let objCopy = Object.assign({}, obj)//这里一定要两个参数{},不然就全是深拷贝
// 深拷贝
console.log(obj)
console.log(objCopy)
objCopy.job[0] = "IT"
console.log(obj)
console.log(objCopy)
console.log("--------------------")
// 浅拷贝
console.log(obj)
console.log(objCopy)
objCopy.name = "Mick"
console.log(obj)
console.log(objCopy)
let objCopy = Object.assign({}, obj)//这里一定要两个参数{},不然就全是浅拷贝
递归实现深拷贝
递归实现深拷贝主要是对对象的每一个属性进行递归复制。下面是一个递归实现深拷贝的基本示例:(核心步骤:使用instanceof检查对象(用typeof就要多加一个判断不等于null的条件)、循环引用处理)
循环引用:WeakMap 用于存储已经被复制过的对象。如果在递归的过程中遇到同一个对象,则直接从 WeakMap中获取,以此避免循环引用的问题。
function deepClone(target, map = new Map()) {
if (typeof target === 'object' && target !== null) {
// 避免循环引用问题
if (map.has(target)) {
return map.get(target);
}
// 处理数组和对象的情况
const cloneTarget = Array.isArray(target) ? [] : {};
map.set(target, cloneTarget);
for (let key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = deepClone(target[key], map);
}
}
return cloneTarget;
} else {
return target;
}
}
// 示例
const obj = {
num: 0,
str: '',
boolean: true,
unf: undefined,
nul: null,
obj: { name: 'I am an object', id: 1 },
arr: [0, 1, 2],
func: function() { console.log('I am a function') },
date: new Date(0),
reg: new RegExp('/I am a regular expression/ig'),
[Symbol('key')]: 'I am a symbol',
error: new Error('I am an error object'),
map: new Map([['name', 'I am a map']]),
set: new Set([1, 'I am a set'])
};
const clonedObj = deepClone(obj);
console.log(clonedObj);
console.log(clonedObj.obj !== obj.obj); // true,说明确实进行了深拷贝
注意:这个示例并不完整,例如对于某些特殊对象(如 Function
、Date
、RegExp
、Error
、Map
、Set
等),可能还需要进行特定的处理。此外,不同的应用场景可能对深拷贝的需求有所不同,因此需要根据具体情况对上述代码进行适当的调整或扩展。
2.typeof 和 instanceof的区别
在JavaScript中,typeof
和 instanceof
用于确定变量的类型,但它们在应用和行为上有所不同。
-
typeof:
- 对于原始类型(如
'string'
,'number'
,'boolean'
等),typeof
运行得很好。 - 但对于数组和普通对象,
typeof
都返回'object'
,这会引起一些混淆。 - 对于
null
,typeof
也返回'object'
,这是一个著名的JavaScript错误。
- 对于原始类型(如
-
instanceof:
- 它基于原型链来工作,检查左侧的对象是否是右侧构造函数的实例。
- 对于自定义对象或者其他内置对象如Array、Date等,使用
instanceof
更为准确。
3.数组多种去重方法
数组去重的5种方法:
1、用“[…new Set(arr)]”语句去重;
2、用“Array.from(new Set(arr))”语句去重;
3、利用indexOf()去重;
4、利用includes()去重;
5、利用filter()去重。
数组去重的方法
1、[…new Set(arr)]
const arr = [1, 2, 3, 2, 3];
[...new Set(arr)]; // [1, 2, 3]
2、Array.from(new Set(arr))
加粗样式
const arr = [1, 2, 3, 2, 3];
Array.from(new Set(arr)); // [1, 2, 3]
由于 Set 中的元素是唯一的,无论是原始值或者是对象引用,所以可以通过将数组转换成 Set 对象来实现去重
Array.from方法可以将 Set 对象转换成数组
3、利用indexOf去重
function unique(arr) {
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array .indexOf(arr[i]) === -1) {
array .push(arr[i])
}
}
return array;
}
新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组。
4、利用includes
function unique(arr) {
var array =[];
for(var i = 0; i < arr.length; i++) {
if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
array.push(arr[i]);
}
}
return array
}
5、利用filter
function unique(arr) {
return arr.filter(function(item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index;
});
}
描述map、reduce 和 filter 方法。
如何检查一个变量是否是数组?
slice是干嘛的,splice是否会改变原数组
forEach和map有什么区别,使用场景?
区别
forEach
和 map
都是数组的常用方法,但它们有不同的目的和用法。下面是它们之间的主要区别以及各自的使用场景:
-
目的:
- forEach: 主要用于迭代数组并执行某些操作,但不返回一个新数组。
- map: 迭代数组,并对每个元素执行某些操作,然后返回一个新数组,该数组包含与原始数组相同数量的元素(可能已被修改)。
-
返回值:
- forEach: 无返回值(返回
undefined
)。 - map: 返回一个新数组。
- forEach: 无返回值(返回
-
是否改变原始数组:
- forEach: 不直接改变原始数组,但可以在回调中更改原始数组(第二个参数index索引可以用来修改)。
- map: 不改变原始数组,但新数组的元素可能已被修改。
-
使用场景:
- forEach: 当你只是想对数组的每个元素执行操作,而不关心结果时,如打印每个元素。
- map: 当你想基于现有数组创建一个新数组时,如将每个数字元素乘以2。
-
示例:
const arr = [1, 2, 3, 4, 5]; // 使用 forEach 打印每个元素 arr.forEach(item => { console.log(item); }); // 使用 map 创建一个新数组,其中每个数字都乘以2 const doubled = arr.map(item => item * 2); console.log(doubled); // [2, 4, 6, 8, 10]
什么意思?forEach: 不直接改变原始数组,但可以在回调中更改原始数组。
当你使用 forEach
遍历数组时,它不会自动更改数组的内容。但是,在 forEach
的回调函数中,你可以手动修改原始数组。
让我们通过代码来解释:
-
forEach
不会自动更改数组:const arr = [1, 2, 3]; arr.forEach(item => item * 2); console.log(arr); // 输出:[1, 2, 3]
尽管我们尝试将每个项乘以2,但原始数组
arr
并没有更改。 -
在
forEach
的回调中手动修改原始数组:const arr = [1, 2, 3]; arr.forEach((item, index) => { arr[index] = item * 2; }); console.log(arr); // 输出:[2, 4, 6]
在这个例子中,我们明确地使用了数组的索引来修改原始数组
arr
。因此,数组的内容已经更改。
这就是所说的"forEach
不直接改变原始数组,但可以在回调中通过索引更改原始数组"的意思。
3. 函数与作用域:
- 闭包
- 柯里化
- call、apply、bind
- this指向+原型
- 什么是立即执行的函数表达式(IIFE)?
-
对象与原型:
- 原型链+原型
- 继承
- js取整的方法,parseInt第二个参数是什么
-
优化与问题解决:
- 递归、优化
- 扁平化
- 内存泄漏的场景
- 循环引用?如何解决
-
事件与异步编程:
- 什么是事件委托?为什么它是有用的?
此分类只是为了将相关的概念归入相同的组中,但实际上,不同的面试官可能会采用不同的分类方式,或根据他们想测试面试者的哪一方面的知识来调整问题。