前言
今天给大家要讲是js方面的面试题:分别从js中的数组,字符串,类型转换机制,深浅拷贝和闭包这几个方面来介绍。让大家更加饱满的回答面试官的问题。
正文
js中数组常用的方法有哪些?
常用方法总结(我们按照增删改查的顺序来就不会乱,能便于我们说出更多的方法)
增加元素
push():向数组末尾添加一个或多个元素,并返回新的长度。unshift():向数组开头添加一个或多个元素,并返回新的长度。splice():在指定位置插入元素。concat():用于合并两个或多个数组。
删除元素
pop():删除数组的最后一个元素并返回它。shift():删除数组的第一个元素并返回它。splice():从数组中删除元素。slice():返回一个新数组,包含从开始到结束(但不包括结束)的数组的一部分副本。
查找元素
indexOf():返回数组中某个元素的第一个匹配项的索引。includes():检查数组中是否存在某个元素。lastIndexOf():返回数组中某个元素的最后一个匹配项的索引。find():返回数组中满足条件的第一个元素的值。
迭代
forEach():遍历数组中的每个元素。map():创建一个新数组,其结果是调用一个提供的函数处理过的原始数组中的每个元素。reduce():对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。every():测试数组中的所有元素是否都通过了被提供的函数测试。some():测试数组中是否有至少一个元素通过了被提供的函数测试。filter():创建一个新数组,其包含通过测试的所有元素。
转换
join():把数组的所有元素放入一个字符串。reverse():反转数组中元素的顺序。sort():按字典顺序对数组的元素进行排序。
示例
// 以下操作均基于经过修改后的数组 `arr`。
let arr = [1, 2, 3, 4, 5, 6];
// 添加元素
arr.splice(3, 0, 0); // 在索引3的位置插入0 [1, 2, 3, 0, 4, 5, 6]
arr.splice(2, 1); // 删除索引2的元素
console.log(arr); // 输出: [1, 2, 0, 4, 5, 6]
// 查找
let res1 = arr.find(val => val < 1);
console.log(res1); // 输出: 0
// 映射
let newArr = arr.map(val => val * 2);
console.log(newArr); // 输出: [ 2, 4, 6, 8, 10, 12 ]
// 归约
let sum = arr.reduce((pre, item) => pre + item, 0);
console.log(sum); // 输出: 21
// 检查
let res2 = arr.every(val => val < 10);
console.log(res2); // 输出: true
// 测试
let res3 = arr.some(val => val < 10);
console.log(res3); // 输出: true
// 过滤
let res4 = arr.filter(val => val % 2 === 0);
console.log(res4); // 输出: [2, 4, 6]
// 反转
let arr2 = arr.reverse();
console.log(arr2); // 输出: [ 6, 5, 4, 3, 2, 1 ]
js中字符串常用的方法有哪些?(一样的从增删改查这几个方面回答)
常用方法总结
增加
concat():连接两个或多个字符串。
删除
substring(start, end):提取字符串中的一部分。slice(start, end):提取字符串中的一部分。
修改
replace():替换与正则表达式匹配的部分。trim():删除字符串两端的空白字符。trimLeft()/trimStart():删除字符串左侧的空白字符。trimRight()/trimEnd():删除字符串右侧的空白字符。toLowerCase():转换为小写。toUpperCase():转换为大写。
查找
charAt(index):获取指定索引处的字符。startsWith():检查字符串是否以指定的子字符串开头。endsWith():检查字符串是否以指定的子字符串结尾。indexOf():查找子字符串出现的位置。lastIndexOf():查找子字符串最后一次出现的位置。match():查找符合正则表达式的部分。search():查找符合正则表达式的部分。
转换
split():把一个字符串分割成字符串数组。
示例
let str = 'abcd';
// 截取
let str2 = str.substring(1, 3);
console.log(str2); // 输出: 'bc'
// 替换
let str3 = str.replace('a', 'A');
console.log(str3); // 输出: 'Abcd'
// 补充
let str4 = str.padStart(10, '0');
console.log(str4); // 输出: '0000000abcd'
let str5 = str.padEnd(10, '0');
console.log(str5); // 输出: 'abcd000000'
谈谈js中的类型转换机制
是什么
js中有7种原始类型,和引用类型之分,在开发时有时候我们需要人为的将一个变量的类型转换为其他类型,这种转换称之为显示转化,有时候js引擎也会发生隐式转换
方式:
- 显式转换:通过使用内置的转换函数如
Number(),String(),Boolean()或parseInt(),parseFloat()等。 - 隐式转换:在某些特定情况下,JavaScript 会自动将一种类型转换为另一种类型,例如在比较运算符或算术运算中。
面试题
面试题
问题:[] == ![] 的结果是 true 还是 false?
[]是一个空数组,它的类型是object。![]是一个布尔表达式,![]的结果是false,因为一个非空数组在布尔上下文中被认为是true,所以取反后是false。
逐步分析
让我们逐步分析 [] == ![] 的过程:
-
[] == ![]- 转换过程:(!优先,先看右边的)
-
![]->false->ToNumber(false)->0 -
[]
-
ToPrimitive([], Number)valueOf()->[](仍然是空数组)toString()->""(空字符串)ToNumber("")->0
-
- 比较结果:
0 == 0->true
- 转换过程:(!优先,先看右边的)
== VS ===
== 只判断值是否相等,不在乎类型,会发生隐式类型转换
===不发生类型转换(它不仅比较值,还比较类型)
聊聊js的拷贝问题
是什么
开发中经常面对一个要跟原对象得到一个新对象的场景,这种场景我们称之为拷贝
特点
- 浅拷贝:只复制对象的第一层属性。
- 深拷贝:完全复制对象及其所有嵌套的对象。
方法
-
浅拷贝:
Object.assign()Object.create(){...obj}[...arr]arr.concat()arr.slice()arr.toReversed().reverse()
示例
function copy(obj){
let obj1={};
for(let i in obj){
if(obj.hasOwnProperty(i)){
obj1[i]=obj[i];
}
}
return obj1;
}
let obj2=copy(obj);
console.log(obj2);
-
深拷贝:
JSON.parse(JSON.stringify(obj)): 无法处理bigint,Symbol,undefined类型, 无法处理循环引用。structuredClone(): 无法处理函数、Symbol。- 手动实现递归拷贝。
new MessageChannel(): 可以处理循环引用,但不适用于所有数据类型。(管道通信)
structuredClone()
const obj = {
a: 1,
b: 2,
c: {
d: 3
}
};
const clonedObj = structuredClone(obj);
obj.b.n = 20;
console.log(clonedObj); // 输出: { a: 1, b: 2, c: { d: 3 } }
手动实现递归拷贝示例
function deepClone(obj) {
const newObj = {};
for (const key in obj) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
newObj[key] = deepClone(obj[key]);
} else {
newObj[key] = obj[key];
}
}
return newObj;
}
const obj2 = deepClone(obj);
new MessageChannel()
const obj={
a:1,
b:2,
c:{
d:3
}
}
function deepClone(obj){
return new Promise((resolve)=>{
const {port1,port2} =new MessageChannel()
port1.postMessage(obj)
port2.onmessage=(msg)=>{
resolve(msg.data)
}
})
}
deepClone(obj).then((res)=>{
obj.b.n=20
console.log(res) //输出:{ a: 1, b: 2, c: { d: 3 } }
})
解析:在 deepClone 函数中,创建了一个 MessageChannel 实例,并通过 postMessage 方法将原始对象发送到一个端口 (port1),然后在另一个端口 (port2) 上监听消息事件,当接收到消息时,解析得到的克隆对象并将其作为 Promise 的结果返回。最后,通过调用 deepClone(obj) 并等待 Promise 解析,我们可以获得一个与原始对象 obj 深度克隆的新对象。
说说闭包
定义
在JavaScript中,根据词法作用域的规则,内部函数一定能访问外部函数中的变量,当内部函数被拿到外部函数之外调用时,即使外部函数执行完毕,但是内部函数对外部函数中的变量依然存在引用,那么这些被引用的变量会以一个集合的方式保存下来,这个集合就是闭包。
示例
function foo(){
var a = 1;
function bar(){
console.log(a);
}
return bar;
}
const baz=foo(); //输出1
baz();
解析:蓝色为全局环境,首先foo的声明,baz的声明,预编译结束,然后foo的调用,黄色为foo进栈,里面有bar的声明,foo函数被执行完毕后要出栈,但是内部有一个函数还没调用,所在旁边给它留下了一个小背包,里面有它所需要的变量a=1,foo出栈就打一个大大的×,然后baz的调用,导致绿色的bar进栈,通过作用域链找到小背包,所以输出1
优缺点
- 优点:可以定义私有变量,防止全局变量污染,便于封装模块。
- 缺点:可能导致内存泄漏。
应用场景
- 防抖节流
- 单例模式
- 柯里化
总结
以上就是本文的内容,希望对您有所帮助!感谢您的阅读!