面经篇-javaScript手写题

面经

介绍

记录、复习、巩固、拓展、总结
都是随机面经,会持续更新添加......

# 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、匹配有效的括号

    # 描述:给定一个只包括 '(',')','{','}','[',']' 的字符串 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 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

# 衍生: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

# 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

# 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

# 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

# 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

# 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

# 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
Last Updated: 2025/10/29 15:59:04