前言
为了在 面试中脱颖而出,不仅要对基础知识了如指掌,还要掌握全面,并且能够在回答问题时揭示出其他面试者可能不了解的点,具备这些能力,你将能在众多候选人中脱颖而出。接下来,我们将为友友盘点 JavaScript 面试中经常被问到的问题,并提供全面且深入的答案。
一:数组操作
1.增
-
push()
:向数组的末尾添加一个或多个元素,会改变原数组,并返回新的长度。- 如果需要添加多个元素,可以传递多个参数给
push()
。 - 使用
push()
时,原数组会被修改。
jslet arr = [1, 2, 3]; arr.push(4, 5); // 添加两个元素 console.log(arr); // 输出: [1, 2, 3, 4, 5] console.log(arr.length); // 输出: 5
- 如果需要添加多个元素,可以传递多个参数给
-
unshift()
:向数组的开头添加一个或多个元素,会改变原数组,并返回新的长度。unshift()
会使数组的所有元素向后移动,因此性能上不如push()
。- 如果数组很大,频繁使用
unshift()
可能会影响性能。
jslet arr = [1, 2, 3]; arr.unshift(0, -1); // 添加两个元素 console.log(arr); // 输出: [-1, 0, 1, 2, 3] console.log(arr.length); // 输出: 5
-
splice()
:可以在任意位置插入元素,它可以接受三个参数:起始索引、要删除的元素数量以及要添加的元素列表。splice()
会修改原数组,并返回一个包含被删除元素的新数组。- 如果第二个参数为0,则不会删除任何元素,只会插入元素。
- 如果第二个参数为负数,则从数组的末尾开始计算起始索引。
jslet arr = [1, 2, 3, 4, 5]; arr.splice(1, 0, 'a', 'b'); // 在索引1处插入'a'和'b' console.log(arr); // 输出: [1, "a", "b", 2, 3, 4, 5] arr.splice(2, 2); // 删除索引2和3处的元素 console.log(arr); // 输出: [1, "a", 3, 4, 5] arr.splice(-1, 1, 'z'); // 从数组末尾开始删除一个元素并插入'z' console.log(arr); // 输出: [1, "a", 3, 4, 'z']
-
concat()
:连接两个或多个数组,并返回一个新的数组concat()
返回的是新数组,所以不会影响原始数组。- 如果需要合并多个数组,可以连续调用
concat()
或者传递多个参数。
jslet arr1 = [1, 2, 3]; let arr2 = [4, 5, 6]; let combined = arr1.concat(arr2); console.log(combined); // 输出: [1, 2, 3, 4, 5, 6] console.log(arr1); // 输出: [1, 2, 3] (原数组未变)
2.删
-
pop()
:移除数组最后一个元素,并返回该元素的值,会改变原数组。pop()
总是从数组的末尾移除元素。- 如果数组为空,
pop()
将返回undefined
。
jslet arr = [1, 2, 3, 4]; let removedElement = arr.pop(); // 移除最后一个元素 console.log(removedElement); // 输出: 4 console.log(arr); // 输出: [1, 2, 3]
-
shift()
:移除数组第一个元素,并返回该元素的值,会改变原数组。shift()
总是从数组的开头移除元素。- 如果数组为空,
shift()
将返回undefined
。 shift()
会使数组中的所有元素向前移动一位。
jslet arr = [1, 2, 3, 4]; let removedElement = arr.shift(); // 移除第一个元素 console.log(removedElement); // 输出: 1 console.log(arr); // 输出: [2, 3, 4]
-
splice()
:从数组中删除指定范围的元素,与增的方式类似,此处省略 -
slice()
:提取数组的一部分而不改变原数组。slice()
接受两个参数:起始索引和结束索引(非包含)。- 如果省略结束索引,则返回从起始索引到数组末尾的所有元素。
- 如果起始索引为负数,则从数组的末尾开始计数。
- 如果结束索引为负数,则从数组的末尾开始计数。
jslet arr = [1, 2, 3, 4, 5]; let slicedArray = arr.slice(1, 4); // 提取索引1到3之间的元素 console.log(slicedArray); // 输出: [2, 3, 4] console.log(arr); // 输出: [1, 2, 3, 4, 5] (原数组未变) let slicedArrayNegative = arr.slice(-4, -1); // 提取索引1到3之间的元素,使用负数索引 console.log(slicedArrayNegative); // 输出: [2, 3, 4]
3.查
-
indexOf()
:查找元素首次出现的位置,如果没有找到该元素,则返回-1
indexOf()
从数组的开头开始搜索。- 如果数组中有重复的元素,
indexOf()
只返回第一个匹配项的索引。
jslet arr = [1, 2, 3, 2, 4]; let index = arr.indexOf(2); // 查找首次出现的位置 console.log(index); // 输出: 1
-
lastIndexOf()
:查找元素最后一次出现的位置,。如果没有找到该元素,则返回-1
lastIndexOf()
从数组的末尾开始搜索。- 如果数组中有重复的元素,
lastIndexOf()
返回最后一个匹配项的索引。
jslet arr = [1, 2, 3, 2, 4]; let index = arr.lastIndexOf(2); // 查找最后一次出现的位置 console.log(index); // 输出: 3
-
includes()
:检查数组中是否包含特定元素,如果找到该元素,返回true
;否则返回false
。includes()
可以接受一个可选的第二个参数,用于指定搜索的起始位置。- 默认情况下,搜索从数组的开头开始。
jslet arr = [1, 2, 3, 2, 4]; let found = arr.includes(2); // 检查数组中是否包含2 console.log(found); // 输出: true let foundFromIndex = arr.includes(2, 2); // 从索引2开始检查 console.log(foundFromIndex); // 输出: true
-
find()
:找到第一个符合给定条件的元素,接受一个回调函数作为参数,该函数用于定义条件。- 回调函数接收三个参数:当前元素、当前元素的索引以及整个数组。
- 如果没有找到符合条件的元素,
find()
返回undefined
。
jslet arr = [1, 2, 3, 4, 5]; let found = arr.find(function(element, index, array) { return element > 3; // 查找第一个大于3的元素 }); console.log(found); // 输出: 4
4.迭代
-
forEach()
:对每个元素执行一次提供的函数,不返回任何值,只是执行提供的函数。forEach()
不会跳过缺失的元素,但如果数组元素是undefined
或者被设置为undefined
,则提供的函数仍然会被调用。- 如果数组在
forEach()
执行过程中被修改,新增的元素会被处理,但已处理过的元素不会再被处理。
jslet arr = [1, 2, 3, 4, 5]; arr.forEach(function(item, index, array) { console.log(`Item at index ${index} is ${item}`); }); // 输出: // Item at index 0 is 1 // Item at index 1 is 2 // Item at index 2 is 3 // Item at index 3 is 4 // Item at index 4 is 5
-
map()
:创建一个新数组,其结果是该数组中的每个元素都调用了提供的函数,原数组不会被修改。map()
会跳过缺失的元素。- 如果数组在
map()
执行过程中被修改,新增的元素不会被处理。
jslet arr = [1, 2, 3, 4, 5]; let squared = arr.map(function(item) { return item * item; }); console.log(squared); // 输出: [1, 4, 9, 16, 25]
-
filter()
:创建一个新数组,包含通过测试的所有元素,测试是通过提供的函数实现的filter()
会跳过缺失的元素。- 如果数组在
filter()
执行过程中被修改,新增的元素不会被处理。
jslet arr = [1, 2, 3, 4, 5]; let evenNumbers = arr.filter(function(item) { return item % 2 === 0; }); console.log(evenNumbers); // 输出: [2, 4]
-
reduce()
:对数组中的每个元素执行一个由提供的缩减函数(升序执行),将其结果汇总为单个返回值。reduce()
接受一个回调函数,该函数至少需要两个参数:累积器(accumulator)和当前值(currentValue)。- 第一个参数(累积器)是累积的计算结果,或者是初始值。
- 第二个参数(当前值)是当前正在处理的数组元素。
- 第三个参数是当前索引。
- 第四个参数是原数组。
jslet arr = [1, 2, 3, 4, 5]; let sum = arr.reduce(function(accumulator, currentValue) { return accumulator + currentValue; }, 0); // 初始值为0 console.log(sum); // 输出: 15
-
every()
:检查是否数组中的所有元素都符合提供的测试函数,如果所有 元素都符合测试条件,则返回true
;否则返回false
。every()
会跳过缺失的元素。- 如果数组在
every()
执行过程中被修改,新增的元素不会被处理。
jslet arr = [1, 2, 3, 4, 5]; let allPositive = arr.every(function(item) { return item > 0; }); console.log(allPositive); // 输出: true
-
some()
:检查是否数组中有任何元素符合提供的测试函数,如果有任何一个 元素符合测试条件,则返回true
;否则返回false
。some()
会跳过缺失的元素。- 如果数组在
some()
执行过程中被修改,新增的元素不会被处理。
jslet arr = [1, 2, 3, 4, 5]; let hasEven = arr.some(function(item) { return item % 2 === 0; }); console.log(hasEven); // 输出: true
5.转换
-
sort()
:按字母顺序对数组进行排序,默认情况下,它将数组元素转换为字符串,并按照字符串的Unicode码点顺序进行排序。sort()
方法会直接修改原数组。- 对于数值排序,需要提供一个比较函数来确保正确的排序顺序。
- 如果数组包含非字符串类型的元素,它们会被转换为字符串。
jslet arr = [10, 5, 3, 25, 1]; arr.sort(); // 默认按字符串排序 console.log(arr); // 输出: [1, 10, 25, 3, 5] // 数值排序 let arrNumbers = [10, 5, 3, 25, 1]; arrNumbers.sort((a, b) => a - b); // 使用比较函数 console.log(arrNumbers); // 输出: [1, 3, 5, 10, 25]
-
reverse()
:颠倒数组中元素的顺序,直接修改原数组reverse()
方法会改变原数组的顺序。- 如果需要保留原数组不变,可以先复制数组再调用
reverse()
。
jslet arr = [1, 2, 3, 4, 5]; arr.reverse(); console.log(arr); // 输出: [5, 4, 3, 2, 1]
-
toReversed()
:返回一个新的反转数组,不会修改原数组。toReversed()
方法返回一个新数组,原数组保持不变。- 此方法在较新的JavaScript环境中可用,例如ES2022及以后版本。
jslet arr = [1, 2, 3, 4, 5]; let reversedArr = arr.toReversed(); console.log(reversedArr); // 输出: [5, 4, 3, 2, 1] console.log(arr); // 输出: [1, 2, 3, 4, 5] (原数组未变)
-
join()
:将数组元素组合成一个字符串,接受一个可选的分隔符参数,用于指定元素之间的分隔符,默认分隔符是一个逗号(,
)join()
方法不会修改原数组。- 如果数组中包含非字符串类型的元素,它们会被转换为字符串。
jslet arr = ['apple', 'banana', 'cherry']; let str = arr.join('-'); console.log(str); // 输出: apple-banana-cherry let arrNumbers = [1, 2, 3]; let strNumbers = arrNumbers.join(' '); console.log(strNumbers); // 输出: 1 2 3
二:字符串操作
1.增
-
concat()
:连接两个或多个字符串,并返回一个新的字符串,不会修改原有的字符串concat()
方法可以接受一个或多个字符串参数。- 如果参数不是字符串,
concat()
会尝试将其转换为字符串。 - 如果只需要连接两个字符串,使用加号 (
+
) 运算符可能更为简洁。
jslet str1 = 'Hello, '; let str2 = 'world!'; let fullStr = str1.concat(str2); console.log(fullStr); // 输出: Hello, world! // 连接多个字符串 let str3 = 'Welcome to '; let str4 = 'JavaScript '; let str5 = 'tutorial.'; let fullStr2 = str3.concat(str4, str5); console.log(fullStr2); // 输出: Welcome to JavaScript tutorial.
使用加号 (+
) 运算符连接字符串
虽然 concat()
方法可以用来连接字符串,但在实际开发中,使用加号 (+
) 运算符连接字符串更为常见和简洁
js
let str1 = 'Hello, ';
let str2 = 'world!';
let fullStr = str1 + str2;
console.log(fullStr); // 输出: Hello, world!
2.删
-
substr()
:已被废弃,不推荐使用,它用于提取字符串的一部分,接受两个参数:起始索引和要提取的长度。 如果你需要提取字符串的一部分,请使用substring()
或slice()
。 -
substring()
:提取字符串的一部分,接受两个参数:起始索引和结束索引(非包含)- 如果参数的顺序颠倒,
substring()
会自动交换它们。 - 如果只提供一个参数,
substring()
会从该索引到字符串的末尾提取。 - 参数必须是非负整数
jslet str = 'Hello, world!'; let extracted = str.substring(7, 12); console.log(extracted); // 输出: world
- 如果参数的顺序颠倒,
-
slice()
:提取字符串的一部分,接受两个参数:起始索引和结束索引(非包含)slice()
方法的行为类似于substring()
,但支持负数索引。- 如果只提供一个参数,
slice()
会从该索引到字符串的末尾提取。 - 如果参数为负数,则从字符串的末尾开始计数。
jslet str = 'Hello, world!'; let extracted = str.slice(7, 12); console.log(extracted); // 输出: world let extractedNegative = str.slice(-6, -1); console.log(extractedNegative); // 输出: world
3.改
-
replace()
:替换字符串中的子串,接受一个要替换的模式(可以是字符串或正则表达式)和一个替换文本或一个函数- 默认情况下,
replace()
只替换第一个匹配项。 - 如果要替换所有匹配项,可以使用全局正则表达式
/g
。
jslet str = 'Hello, world!'; let replaced = str.replace('world', 'JavaScript'); console.log(replaced); // 输出: Hello, JavaScript! // 替换所有匹配项 let str2 = 'Hello, world! Goodbye, world!'; let replacedAll = str2.replace(/world/g, 'JavaScript'); console.log(replacedAll); // 输出: Hello, JavaScript! Goodbye, JavaScript!
- 默认情况下,
-
toUpperCase()
:将字符串转换为全大写,不会修改原字符串jslet str = 'Hello, world!'; let upperCaseStr = str.toUpperCase(); console.log(upperCaseStr); // 输出: HELLO, WORLD!
-
toLowerCase()
:将字符串转换为全小写,不会修改原字符串jslet str = 'Hello, World!'; let lowerCaseStr = str.toLowerCase(); console.log(lowerCaseStr); // 输出: hello, world!
-
trim()
:去除字符串两端的空白字符,不会修改原字符串,而是返回一个新的去除空白字符后的字符串jslet str = ' Hello, world! '; let trimmedStr = str.trim(); console.log(trimmedStr); // 输出: Hello, world!
-
trimStart()
:去除字符串开始的空白字符,不会修改原字符串,而是返回一个新的去除起始空白字符后的字符串jslet str = ' Hello, world!'; let trimmedStartStr = str.trimStart(); console.log(trimmedStartStr); // 输出: Hello, world!
-
trimEnd()
:去除字符串结束的空白字符,不会修改原字符串,而是返回一个新的去除末尾空白字符后的字符串。jslet str = 'Hello, world! '; let trimmedEndStr = str.trimEnd(); console.log(trimmedEndStr); // 输出: Hello, world!
-
padStart()
:在字符串开始处添加填充字符,不会修改原字符串,而是返回一个新的字符串,并且接受两个参数:目标长度和填充字符串。jslet str = '123'; let paddedStr = str.padStart(6, '0'); console.log(paddedStr); // 输出: 000123
-
padEnd()
:在字符串结束处添加填充字符,不会修改原字符串,而是返回一个新的字符串,并且接受两个参数:目标长度和填充字符串。jslet str = '123'; let paddedStr = str.padEnd(6, '0'); console.log(paddedStr); // 输出: 123000
4.查
-
charAt()
:获取指定索引处的字符charAt()
方法接受一个整数参数,表示索引位置- 如果索引超出范围,
charAt()
返回空字符串''
jslet str = 'Hello, world!'; let char = str.charAt(7); console.log(char); // 输出: w
-
indexOf()
:查找子串首次出现的位置indexOf()
方法接受一个子串作为参数,可选地接受一个开始搜索的索引位置。- 如果没有找到子串,
indexOf()
返回-1
。
jslet str = 'Hello, world!'; let index = str.indexOf('world'); console.log(index); // 输出: 7
-
lastIndexOf()
:查找子串最后一次出现的位置,其他特性与indexOf()
类似 -
includes()
:检查字符串是否包含另一个字符串includes()
方法接受一个子串作为参数,可选地接受一个开始搜索的索引位置。- 如果找到子串,
includes()
返回true
;否则返回false
。
jslet str = 'Hello, world!'; let contains = str.includes('world'); console.log(contains); // 输出: true
-
startsWith()
:检查字符串是否以指定的字符串开始startsWith()
方法接受一个子串作为参数,可选地接受一个开始搜索的索引位置。- 如果字符串以指定子串开始,
startsWith()
返回true
;否则返回false
。
jslet str = 'Hello, world!'; let startsWith = str.startsWith('Hello'); console.log(startsWith); // 输出: true
-
endsWith()
:检查字符串是否以指定的字符串结束,其他特性与startsWith()
类似 -
match()
:执行正则表达式搜索match()
方法接受一个正则表达式作为参数。- 如果找到匹配项,
match()
返回一个数组,其中包含匹配的字符串;如果没有找到匹配项,返回null
jslet str = 'Hello, world!'; let matchResult = str.match(/world/); console.log(matchResult); // 输出: ["world", index: 7, input: "Hello, world!", groups: undefined]
-
search()
:执行正则表达式搜索并返回匹配的位置search()
方法接受一个正则表达式作为参数。- 如果找到匹配项,
search()
返回匹配的起始位置;如果没有找到匹配项,返回-1
。
jslet str = 'Hello, world!'; let searchResult = str.search(/world/); console.log(searchResult); // 输出: 7
5.转换
-
split()
:将字符串分割成数组,接受一个分隔符作为参数,并可选地接受一个限制结果数组长度的参数。split()
方法接受一个分隔符作为参数,可以是一个字符串或正则表达式。- 如果分隔符是一个空字符串
''
,split()
会将字符串分割成单个字符的数组。 - 如果提供了第二个参数,它指定了返回数组的最大长度。
- 如果没有找到分隔符,
split()
返回一个只包含原字符串的数组。
jslet str = 'Hello, world! Welcome to JavaScript.'; let words = str.split(' '); console.log(words); // 输出: ["Hello,", "world!", "Welcome", "to", "JavaScript."] // 使用正则表达式作为分隔符 let wordsRegex = str.split(/[ ,.]+/); console.log(wordsRegex); // 输出: ["Hello", "world", "Welcome", "to", "JavaScript"] // 分割成单个字符 let chars = str.split(''); console.log(chars); // 输出: ["H", "e", "l", "l", "o", ",", " ", "w", "o", "r", "l", "d", "!", " ", "W", "e", "l", "c", "o", "m", "e", " ", "t", "o", " ", "J", "a", "v", "a", "S", "c", "r", "i", "p", "t", "."] // 限制结果数组的长度 let limitedWords = str.split(' ', 3); console.log(limitedWords); // 输出: ["Hello,", "world!", "Welcome"]
当然可以。下面是关于 JavaScript 中类型转换机制的详细介绍,包括显示转换和隐式转换的例子。
三:类型转换机制
1.介绍
JavaScript 是一种动态类型的语言,这意味着变量可以在运行时改变类型,其中支持两种类型的类型转换:显示转换和隐式转换。
2.显示转换
显示转换是指开发者明确地将一个值从一种类型转换为另一种类型。这通常通过内置的类型转换函数完成。
方式
-
使用
Number()
,Boolean()
,String()
等函数。Number()
:将值转换为数字。Boolean()
:将值转换为布尔值。String()
:将值转换为字符串。
-
parseInt()
:将字符串转换为整数。parseInt()
函数用于将字符串解析为整数。
js
let num = Number('123'); // 显示转换为数字
console.log(num); // 输出: 123
let bool = Boolean(0); // 显示转换为布尔值
console.log(bool); // 输出: false
let str = String(123); // 显示转换为字符串
console.log(str); // 输出: "123"
let int = parseInt('123abc'); // 显示转换为整数
console.log(int); // 输出: 123
3.隐式转换
隐式转换是指 JavaScript 自动将一个值从一种类型转换为另一种类型,通常发生在算术运算、比较运算符等场合。
js
let num = 5;
let str = '10';
let result = num + str; // 隐式转换为字符串
console.log(result); // 输出: "510"
let bool = true;
let num2 = 1 + bool; // 隐式转换为数字
console.log(num2); // 输出: 2
let arr = [];
let bool2 = !arr; // 隐式转换为布尔值
console.log(bool2); // 输出: false
let emptyArr = [];
let num3 = emptyArr == !emptyArr; // 隐式转换为数字
console.log(num3); // 输出: true
4.经典题
js
[] == ![] // true
// 解析: [] -> false -> 0, ![] -> false -> 0, 0 == 0
[]
作为空数组,在布尔上下文中会被隐式转换为false
。![]
也是空数组,取反后仍然是false
。- 当进行
==
比较时,两边都会被转换为数字,false
转换为0
。 - 因此,
0 == 0
返回true
。
四:==
vs ===
1.==
比较运算符
==
运算符用于比较两个值是否相等。如果两个值的类型不同,JavaScript 会尝试进行类型转换,使得值可以被比较。这种行为被称为类型强制(type coercion)。 特点:
- 如果类型不同,会尝试进行类型转换。
- 如果类型相同,则直接比较值。
js
let num = 5;
let str = '5';
console.log(num == str); // 输出: true
// 解析: 由于类型不同,JavaScript 尝试将两者转换为相同的类型再比较。
// '5' 转换为数字 5,因此 5 == 5 返回 true。
2.===
比较运算符
===
运算符用于比较两个值是否严格相等。这意味着不仅要比较值,还要比较类型。如果两个值的类型不同,===
直接返回 false
。
特点:
- 如果类型不同,直接返回
false
。 - 如果类型相同,则直接比较值。
js
let num = 5;
let str = '5';
console.log(num === str); // 输出: false
// 解析: 由于类型不同,直接返回 false。
在实际开发中,推荐使用 ===
运算符,能够减少由类型转换而导致的意外结果的可能性
五:拷贝问题
1.介绍
-
浅拷贝:只复制对象的第一层属性,对于对象内部的对象属性,浅拷贝只会复制引用。
- 新对象仍然引用了原对象的内部属性。
- 如果原对象的内部属性发生变化,新对象也会受到影响。
-
深拷贝:递归地复制所有层级的属性,创建一个完全独立的新对象。
- 新对象与原对象之间没有任何引用关系。
- 原对象的变化不会影响到新对象。
2.浅拷贝方法
-
Object.create()
- 创建一个新对象,使用现有的对象来提供新创建对象的原型。
- 只复制对象的可枚举属性。
jslet obj = { a: 1, b: { c: 2 } }; let shallowCopy = Object.create(obj); shallowCopy.a = 3; console.log(shallowCopy.b.c); // 输出: 2
-
Object.assign()
- 复制源对象的所有可枚举属性到目标对象。
- 只复制第一层属性。
js
let obj = { a: 1, b: { c: 2 } };
let shallowCopy = Object.assign({}, obj);
shallowCopy.a = 3;
console.log(shallowCopy.b.c); // 输出: 2
-
{...obj}
:快速复制对象的第一层属性。jslet obj = { a: 1, b: { c: 2 } }; let shallowCopy = { ...obj }; shallowCopy.a = 3; console.log(shallowCopy.b.c); // 输出: 2
-
[...arr]
:快速复制数组jslet arr = [1, 2, 3]; let shallowCopy = [...arr]; shallowCopy.push(4); console.log(shallowCopy); // 输出: [1, 2, 3, 4]
-
concat()
:复制数组,但不适用于对象jslet arr = [1, 2, 3]; let shallowCopy = arr.slice(); shallowCopy.push(4); console.log(shallowCopy); // 输出: [1, 2, 3, 4]
-
slice()
:复制数组,但不适用于对象jslet arr = [1, 2, 3]; let shallowCopy = arr.slice(); shallowCopy.push(4); console.log(shallowCopy); // 输出: [1, 2, 3, 4]
-
arr.toReversed().reverse()
:先反转数组,再反转回来,相当于浅拷贝数组。jslet arr = [1, 2, 3]; let shallowCopy = arr.toReversed().reverse(); shallowCopy.push(4); console.log(shallowCopy); // 输出: [1, 2, 3, 4]
3.深拷贝方法
-
JSON.parse(JSON.stringify(obj))
- 简单快速,但有局限性。
- 无法处理循环引用,忽略函数和 Symbol 类型的属性。
jslet obj = { a: 1, b: { c: 2 } }; let deepCopy = JSON.parse(JSON.stringify(obj)); deepCopy.a = 3; console.log(deepCopy.b.c); // 输出: 2
-
structuredClone(obj)
- 现代浏览器支持,更安全。
- 支持更多类型,如 Blob 和 File,但不支持循环引用。
jslet obj = { a: 1, b: { c: 2 } }; let deepCopy = structuredClone(obj); deepCopy.a = 3; console.log(deepCopy.b.c); // 输出: 2
-
lodash.cloneDeep(obj)
:提供更全面的支持,可以处理循环引用等问题jslet _ = require('lodash'); // 假设已经安装并引入 lodash 库 let obj = { a: 1, b: { c: 2 } }; let deepCopy = _.cloneDeep(obj); deepCopy.a = 3; console.log(deepCopy.b.c); // 输出: 2
以上就是最全面的深浅拷贝的方法,面试中往往面试者只能答出其中几个,倘若全部能答出,定会让面试官刮目相看, 像lodash.cloneDeep(obj)
这种方法更是很少人知道
六:闭包
1.定义
闭包是指能够读取其他函数内部变量的函数。当一个函数被定义在一个外层函数内,并且保留对外层函数局部变量的引用时,就形成了闭包。
js
function outerFunction() {
let outerVariable = 'I am outside!';
function innerFunction() {
console.log(outerVariable);
}
return innerFunction;
}
const myClosure = outerFunction();
myClosure(); // 输出: I am outside!
在这个例子中,innerFunction
是一个闭包,因为它可以访问 outerFunction
的局部变量 outerVariable
,即使 outerFunction
已经返回。
2.优点
- 定义私有变量:闭包允许我们创建私有变量,这些变量只能被闭包内部的函数访问。
- 防止全局变量污染:通过闭包,我们可以避免将变量暴露在全局作用域中,从而减少全局变量的数量,避免命名冲突。
3.缺点
- 可能导致内存泄漏:闭包会维持对外层函数中变量的引用,如果这些变量不再被需要,但是由于闭包的存在而不能被垃圾回收器回收,可能会导致内存泄漏。
4.应用场景
- 防抖节流:控制函数的调用频率,避免频繁触发事件导致性能问题。
- 单例模式:确保类只有一个实例,通常通过闭包实现。
- 柯里化:函数部分参数提前绑定,以便稍后调用时可以使用预设的参数。
这里友友要注意哦,接下来面试官可能会根据你的回答进行下一步的提问,比如手写防抖节流,聊聊单例模式,讲讲柯里化的实现,提前准备好这些,又可以再接面试官一招,暗搓搓的反客为主