面经篇-javaScript手写题
xdf 面经
介绍
记录、复习、巩固、拓展、总结
都是随机面经,会持续更新添加......
# 1、寻找字符串中出现最多的字符怎么实现?
function findMostFrequentChar(str) {
if (!str?.length) return ''
const charMap = {} # 初始化一个对象来存储字符及其出现的次数
# 遍历字符串,统计每个字符出现的次数
for (let char of str) {
if (charMap[char]) {
charMap[char]++;
} else {
charMap[char] = 1
}
}
# 初始化最大次数和对应的字符
let maxCount = 0
let maxChar = ''
# 遍历charCount对象,找到出现次数最多的字符
for (let char in charCount) {
if (charCount[char] > maxCount) {
maxCount = charCount[char];
mostFrequentChar = char;
}
}
return maxChar # 也可以返回次数maxCount
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 2、匹配有效的括号
# 描述:给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
# 有效字符串需满足:
# 左括号必须用相同类型的右括号闭合。
# 左括号必须以正确的顺序闭合。
# 每个右括号都有一个对应的相同类型的左括号。
function isValid(s){
# 创建一个空数组作为栈来存储左括号
const stack = []
# 定义括号映射关系:右括号对应相应的左括号
const bracketsMap = {
'}': '{',
']': '[',
')': '('
}
# 遍历输入字符串中的每个字符
for (let char of s) {
if (['{', '[', '('].includes(char)) {
# 将左括号压入栈中
stack.push(char)
} else { # 如果当前字符是右括号
# 检查栈是否为空(没有左括号)
if (!stack.length) return false
# 从栈顶弹出最近的一个左括号
const top = stack.pop()
# 检查弹出的左括号是否与当前右括号匹配
if (top !== brackersMap[char]) return false
}
}
return stack.length === 0
}
# 方法2:
function isValid2() {
if(s.length%2===1){
return false
}
const stack = []
for(const c of s){
if(c==='('){
stack.push(')')
}else if(c==='['){
stack.push(']')
}else if(c==='{'){
stack.push('}')
}else if (stack.length===0 || stack[stack.length-1] !==c){
return false
}else{
stack.pop()
}
}
return stack.length === 0}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 3、千分位隔数
# 方法1:
function formatNumber(num) {
# 判断传入的数字是否为负数
let isNegative = num < 0
# 将num转为字符串,如果有-(负号就去掉负号)
let numStr = Math.abs(num).toString() # abs 绝对值
# 将字符串切割--主要为了看是否是小数
let partList = numStr.split('.')
let intNum = partList[0] # 整数
let decimalNum = partList[1] || '' # 小数
# 将整数通过正则加上千分位符号 .代表
let formatNumber = intNum.replace(/\B(?=(\d{3})+(?!\d))/g, '.')
let result = formatNumber
if (decimalNum) result += '.' + result
if (isNegative) result += '-' + result
return result
}
# 方法2:
# 只需一行代码即可 toLocaleString(): 是 JavaScript 中用于将对象转换为本地化字符串的方法默认 zh-CN
# 'de-DE' 是语言标签,表示德语-德国地区。 在德国数字格式中,千位分隔符是点号 . 小数点是逗号 ,
# 'de-DE' 是语言标签,表示美国-美国地区。 在美国数字格式中,千位分隔符是逗号 ,
return n.toLocaleString() || n.toLocaleString('de-DE')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 4、知不知道最新的 url 参数获取的 API?
# 是 URLSearchParams()
# 用法
#假设当前页面的URL是 https://example.com/?name=John&age=30
# 方法1:直接使用window.location.search
const searchParams = new URLSearchParams(window.location.search);
const name = searchParams.get('name'); # 获取name参数的值,返回"John"
const age = searchParams.get('age'); # 获取age参数的值,返回"30"
# 方法2:如果URL是通过new URL()创建的
const url = new URL(window.location.href);
const anotherName = url.searchParams.get('name'); # 同样获取name参数的值,返回"John"
# 方法3:遍历查询字符串中的所有参数
for (let [key, value] of url.searchParams.entries()) {
console.log(key, value); # 输出所有参数名和对应的值
}
# URLSearchParams API 是在较新的浏览器版本中引入的,但在现代浏览器中普遍支持
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 5、实现深拷贝?
# 方法1:递归实现深拷贝
function deepClone(source) {
if (!source && typeof source != 'object') {
throw new Error('error arguments', 'deepClone')
}
const targetObj = source.constructor === Array ? [] : {} # 判断source是[]还是{}
object.keys(source)?.forEach(keys => {
if (source[keys] && typeof source[keys] == 'object') {
targetObj[keys] = deepClone(source[keys])
} else {
targetObj[keys] = source[keys]
}
})
return targetObj
}
# 方法2:JSON.parse + JSON.stringify
JSON.parse(JSON.stringify(sourceObj))
#注意以下特殊情况
const obj3 = {
func: function () { console.log(1) },
obj: { name: 'h' },
arr: [1, 2, 3],
und: undefined,
ref: /^123$/,
date: new Date(),
NaN: NaN,
infinity: Infinity,
sym: Symbol(1)
}
console.log(JSON.parse(JSON.stringify(obj3)))
# NaN: null
# arr: (3) [1, 2, 3]
# date: "2023-02-07T10:27:29.165Z"
# infinity: null
# obj: {name: 'h'}
# ref: {}
# 拷贝的对象的值如果有函数,undefined,symbol 这几种类型,经过 JSON.stringify 序列化后字符串中这个键值对会消失。
# 拷贝 Date 类型会变成字符串
# 无法拷贝不可枚举的属性
# 无法拷贝对象原型链
# 拷贝 RegExp 引用类型会变成空对象
# 对象中含有 NaN、infinity 以及 -infinity,JSON 序列化后的结果变成 null
# 方法3:Object.assign
const foo = {
name: '张三',
age: 24
}
const newFoo = Object.assign({}, foo)
foo.age = 25
console.log(foo, newFoo) // {name: '张三', age: 25} {name: '张三', age: 24}
# 对象中有内嵌的对象时
const foo = {
name: '张三',
info: {
age: 24
}
}
const newFoo = Object.assign({}, foo)
foo.info.age = 25
console.log(foo, newFoo) // { name: '张三', info: { age: 25 } } { name: '张三', info: { age: 25 } }
# 注意:只有当对象中没有嵌套对象时,才可以实现深拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 6、实现 Promise
class myPromisse{
# 构造器 https://blog.csdn.net/m0_73531461/article/details/140528328
constructor(executor) {
# 初始化 state 为等待状态
this.state = 'pending'
# 成功的值
this.value = undefined
# 失败的原因
this.reason = undefined
# 成功
let resolve = (value) => {
if (this.state === 'pending') {
# resolve 调用后 state转化为成功状态
this.state = 'fulfilled'
}
# 成功值
this.value = value
}
# 失败
let reject = (reason) => {
if (this.state === 'pending') {
# reject 调用后 state转化为失败状态
this.state = 'reject'
}
# 失败原因
this.reason = reason
}
try {
# 立即执行
# 由于 new Promise((resolve, reject)=>{}) ,所以传入一个参数(函数 executor),传入就执行
# executor 里面有两个参数,一个叫 resolve(成功),一个叫 reject(失败)
executor(resolve, reject)
} catch {
reject(err)
}
}
# then 方法有两个参数 onFulfilled 和 onRejected
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
onFulfilled(this.value)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 7、请实现 find 函数,使下列的代码调用正确。
# 约定:
# title数据类型为String
# userId为主键,数据类型为Number
var data = [
{userId: 8, title: 'title1'},
{userId: 11, title: 'other'},
{userId: 15, title: null},
{userId: 19, title: 'title2'}
];
var find = function(origin) {
return {
data: origin,
where: function(conditions) {
this.data = this.data.filter(row => {
for(let key in conditions) {
let condition = conditions[key]
if (condition instanceof RegExp) {
if (!item[key] || !condition.test(item[key])) return false
}
return true
}
})
return this # this 指向返回的对象
},
orderBy: function(key, order) {
this.data.sort((a, b) => {
if (order == 'desc') {
return b[key] - a[key]
} else {
return a[key] - b[key]
}
})
return this.data
}
}
}
# 查找 data 中,符合条件的数据,并进行排序
var result = find(data).where({
'title': /\d$/
}).orderBy('userId', 'desc');
console.log(result); # [{ userId: 19, title: 'title2'}, { userId: 8, title: 'title1' }];
#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 8、手写Call方法
function myCall(context = window, ...args) { # context 是调用myCall 传入过来的第一个参数
if (typeof this !== 'function') {
throw new TypeError('myCall must be called on a function')
}
const fnKey = Symbol('fn') # 创建一个唯一的Symbol作为临时属性名,避免属性冲突
context[fnKey] = this # this指向调用myCall的函数 相当于:person[临时属性] = greet函数
let result = context[fnKey](...args) # 调用context中的这个方法,并传入参数
delete context[fnKey] # 删除临时添加的方法
return result # 返回函数的执行结果
}
# 使用
function greet() {
console.log(this.name)
}
const person = { name: 'Alice' }
# 这里的 person 就是 context
greet.myCall(person, 'arg1', 'arg2', 'arg3') # context = person 参数格式为参数列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 9、手写Apply方法
# 参数格式为[arg1, arg2, arg3]
function myApply(context = window, args) { # context 是调用myCall 传入过来的第一个参数
if (typeof this !== 'function') {
throw new TypeError('myCall must be called on a function')
}
const fnKey = Symbol('fn') # 创建一个唯一的Symbol作为临时属性名,避免属性冲突
context[fnKey] = this # this指向调用myCall的函数 相当于:person[临时属性] = greet函数
let result = context[fnKey](...(args || [])) # 调用context中的这个方法,并传入参数
delete context[fnKey] # 删除临时添加的方法
return result # 返回函数的执行结果
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 10、手写bind方法
# 参数格式为[arg1, arg2, arg3]
function myBind(context = window, ...bindArgs) {
if (typeof this !== 'function') {
throw new TypeError('myCall must be called on a function')
}
# 保存原始函数的引用,这里的this就是调用myBind的函数
const originalFunc = this
# 返回一个新的绑定函数
retrun function boundFunc(...callArgs) {
# 判断是否使用new操作符调用(是否是构造函数调用) new.target: 在构造函数调用时指向构造函数,普通调用时为 undefined
const isNewCall = new.target !== undefined
# 如果是new调用,忽略传入的context,使用新创建的对象作为this
if (isNewCall) {
return new originalFunc(...bindArgs, ...callArgs)
}
# 普通函数调用, 使用context作为this
# 创建唯一的Symbol作为临时属性名
const fnKey = Symbol('fn')
# 原始函数作为方法添加到thisContext中
context[fnKey] = originalFunc
#调用函数,合并绑定时的参数和调用时的参数
const result = context[fnKey](...bindArgs, ...callArgs)
# 删除临时添加的方法
delete context[fnKey]
# 返回执行结果
return result
}
}
# 普通函数调用方式
function introduce(age, city, hobby) {
# console.log(`I'm ${this.name}, ${age} years old, from ${city}, like ${hobby}`)
}
const person = { name: 'Alice' }
# 绑定this和部分参数
const boundFunc = introduce.myBind(person, 25)
# 调用时传入剩余参数
boundFunc('Beijing', 'swimming') # "I'm Alice, 25 years old, from Beijing, like swimming"
# 构造函数调用方式
function Person(name, age) {
this.name = name
this.age = age
}
const BoundPerson = Person.myBind({}, 'John')
const instance = new BoundPerson(25) # 忽略绑定的context
console.log(instance.name) # "John"
console.log(instance.age) # 25
console.log(instance instanceof Person) # true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 衍生:bind、apply、call有什么区别?bind为什么是返回一个函数?
都是改变this指向
参数不一样: call和bind 是参数列表 apply 参数数组
返回值: call和apply 原函数返回 bind 返回新绑定的函数
call和apply是立即执行,bind是延迟执行
# 11、手写new操作符
function myNew(constructor, ...args) {
# 创建一个空对象,改对象的原型指向构造函数的prototype
const obj = Object.create(constructor.prototype)
# 执行构造函数,并将this绑定到新创建的对象
const result = constructor.apply(obj, args)
# 判断构造函数的返回值类型
# 如果返回值是对象类型,则返回该对象,否则返回新创建的对象
return result instanceof Object ? 'result' : obj
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 12、手写防抖函数
function debounce(fn, wait) {
let timer = null
return function() {
let context = this;
if (timer) cleartTimeout(timer)
timer = setTimeout((...args) => {
fn.apply(context, args)
}, wait)
}
}
# 立即执行函数
function debounce(fn, wait) {
let timer = null
return function() {
let context = this;
let args = arguments
if (timer) cleartTimeout(timer)
timer = setTimeout(() => {
timer = null
}, wait)
if (!timer) fn.apply(context, args)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 13、手写节流函数
function throttle(fn, wait) {
let lastTime = 0
return function(...args) {
let currentTime = Date.now()
if (currentTime - lastTime >= wait){
fn.apply(this, args)
lastTime = currentTime
}
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 14、根据字符串 aabbccddff 返回 a2b2c3d1ff2
function reallocate(str) {
if (!str) return ''
let currentStr = str[0]
const count = 1
const result = ''
for(let i = 1; i < str.length; i++) { // i 为什么等于1,因为count默认等于1了,currentStr取了第一个字符
if (currentStr == str[i]) {
count += 1
} else {
result += currentStr + count
currentStr = str[i]
count = 1
}
}
result += currentStr + count
return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 15、手写实现 instanceof
function myInstanceof(obj, construcetor) {
// 获取对象原型
let current = Object.getPrototypeOf(obj)
// 获取构造函数的prototype
let target = constructor.prototype
// 沿着原型链查找
while (current !== null) {
if (current === target) return true
// 继续向上查找
current = Object.getPrototypeOf(obj)
}
return false
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 16、实现模板字符串``
function templateRender(template, dataObj) {
let reg = /\{\{([^{}]+)\}\}/g
// 在正则中()就代表一个捕获组
return template.replace(reg, (match, key) => { // match {{name}} key(捕获组): name
const trimmedKey = key.trim()
if (trimmedKey in dataObj) { // if in 判断对象中是否存在指定的属性
return dataSet[trimmedKey]
}
// 尝试处理嵌套属性(如user.name)
const keys = trimmedKey.split('.')
let value = dataSet
for (const k of keys) {
if (value && typeof value === 'object' && k in value) {
value = value[k]
} else {
return match // 属性不存在,保持原样
}
}
return value != null ? value : match
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 17、两数之和
/*
* 输入:nums = [2,7,11,15], target = 9
* 输出:[0,1]
* 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
* */
// 解法1:双层循环解法
function twoSum(nums, target) {
for(let i = 0; i < nums.length; i++){
for (let j = i + 1; j < nums.length; j++){
if (nums[i] + nums[j] === tartet) {
return [i,j]
}
}
}
return []
}
// 解法2:哈希表解法
function twoSum(nums, target) {
// 创建哈希表存储数字和对应的索引
const map = new Map()
for(let i = 0; i < nums.length;i++){
const num = nums[i]
const complement = target - num // 补数
// 检查补数是否在哈希表中
if (map.has(complement)) {
return [map.get(complement), i]
}
// 将当前数字和索引存入哈希表
map.set(num,i)
}
}
twoSum([2,7,11,15], 9) // 返回 [0, 1]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32