js高频面试题 50道及答案

JavaScript 面试常见50题及答案

一、基础概念与数据类型

1. JavaScript 有哪些数据类型?

答案:

基本类型:String、Number、Boolean、null、undefined、Symbol(ES6)、BigInt(ES2020)

引用类型:Object(包括 Array、Function、Date、RegExp 等)

2. null 和 undefined 的区别?

答案:

undefined:变量已声明但未赋值

null:表示空值,是一个可以赋给变量的特殊值

typeof null 返回 "object",这是历史遗留 bug

3. == 和 === 的区别?

答案:

==:宽松相等,会进行类型转换

===:严格相等,不会进行类型转换

javascript 复制代码
1 == '1'  // true
1 === '1' // false

4. 什么是变量提升?

答案:

var 声明的变量会提升到作用域顶部

只有声明会提升,赋值不会提升

let 和 const 存在暂时性死区,不会提升

5. let、const、var 的区别?

JavaScript 面试常见50题及答案

一、基础概念与数据类型

1. JavaScript 有哪些数据类型?

答案:

基本类型:String、Number、Boolean、null、undefined、Symbol(ES6)、BigInt(ES2020)

引用类型:Object(包括 Array、Function、Date、RegExp 等)

2. null 和 undefined 的区别?

答案:

undefined:变量已声明但未赋值

null:表示空值,是一个可以赋给变量的特殊值

typeof null 返回 "object",这是历史遗留 bug

3. == 和 === 的区别?

答案:

==:宽松相等,会进行类型转换

===:严格相等,不会进行类型转换

javascript 复制代码
1 == '1'  // true
1 === '1' // false

4. 什么是变量提升?

答案:

var 声明的变量会提升到作用域顶部

只有声明会提升,赋值不会提升

let 和 const 存在暂时性死区,不会提升

5. let、const、var 的区别?

6. 什么是作用域链?

答案:

函数在查找变量时,先从自身作用域查找

找不到则向父级作用域查找,直到全局作用域

这种链式查找关系称为作用域链

7. 什么是闭包?

答案:

函数嵌套函数,内部函数可以访问外部函数的变量

外部函数执行完毕后,其变量仍然被内部函数引用

常见用途:私有变量、函数工厂、模块模式

8. 闭包的优缺点?

答案:
优点

创建私有变量和方法

实现函数柯里化

模块化开发

缺点

内存泄漏(如果闭包引用不被释放)

性能考虑(每次创建函数都会创建闭包)

9. 解释词法作用域?

答案:

JavaScript 采用词法作用域(静态作用域)

作用域在函数定义时就确定了,而不是调用时

与动态作用域相对

二、原型与继承

10. 什么是原型链?

答案:

每个对象都有 __proto__ 属性,指向其构造函数的 prototype

prototype 也是对象,也有 __proto__,形成链式结构

查找属性时,沿着原型链向上查找

11. 如何实现继承?

答案:

原型链继承

javascript 复制代码
function Parent() {}
function Child() {}
Child.prototype = new Parent()

构造函数继承

javascript 复制代码
function Child() {
    Parent.call(this)
}

组合继承(最常用)

javascript 复制代码
function Child() {
    Parent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child

Class 继承(ES6)

javascript 复制代码
class Child extends Parent {
    constructor() {
        super()
    }
}

12. new 操作符做了什么?

答案:

创建一个空对象

将空对象的 __proto__ 指向构造函数的 prototype

将 this 指向这个空对象

执行构造函数

如果构造函数返回对象则返回该对象,否则返回新对象

13. instanceof 的原理?

答案:

检查右边构造函数的 prototype 是否在左边对象的原型链上

实现原理:

javascript 复制代码
function myInstanceof(left, right) {
    let proto = Object.getPrototypeOf(left)
    while (true) {
        if (proto === null) return false
        if (proto === right.prototype) return true
        proto = Object.getPrototypeOf(proto)
    }
}

三、函数与 this

14. 解释 this 的指向?

答案:

  1. 普通函数调用:this 指向全局对象(严格模式下为 undefined)

  2. 方法调用:this 指向调用该方法的对象

  3. 构造函数调用:this 指向新创建的实例

  4. call/apply/bind 调用:this 指向第一个参数

  5. 箭头函数:this 指向定义时的上下文,不会改变

15. call、apply、bind 的区别?

答案:

javascript 复制代码
func.call(thisArg, arg1, arg2, ...)    // 参数逐个传递
func.apply(thisArg, [argsArray])       // 参数作为数组传递
func.bind(thisArg, arg1, arg2, ...)    // 返回新函数,不立即执行

16. 箭头函数与普通函数的区别?

答案:

箭头函数没有自己的 this,继承外层

箭头函数没有 arguments 对象

箭头函数不能作为构造函数(不能用 new)

箭头函数没有 prototype 属性

箭头函数不能使用 yield,不能用作生成器

17. 什么是高阶函数?

答案:

接受函数作为参数

或返回一个函数

例如:map、filter、reduce、bind

异步编程

18. 什么是事件循环?

答案:

JavaScript 是单线程的,通过事件循环处理异步

任务分为宏任务和微任务

执行顺序:同步代码 → 微任务 → 宏任务

宏任务:setTimeout、setInterval、I/O

微任务:Promise.then、process.nextTick、MutationObserver

19. Promise 的状态?

答案:

pending:初始状态

fulfilled:操作成功完成

rejected:操作失败

状态一旦改变就不会再变

20. Promise 的常用方法?

答案:

javascript 复制代码
Promise.resolve(value)     // 返回 resolved 状态的 Promise
Promise.reject(reason)     // 返回 rejected 状态的 Promise
Promise.all(iterable)      // 所有成功才成功,一个失败就失败
Promise.race(iterable)     // 第一个改变状态的 Promise 决定结果
Promise.allSettled(iterable) // 所有 Promise 都完成后返回结果数组

21. async/await 的优点?

答案:

代码更简洁,类似同步写法

更好的错误处理(可以使用 try-catch

更容易调试

避免回调地狱

22. 实现一个 Promise?

答案:

javascript 复制代码
class MyPromise {
    constructor(executor) {
        this.state = 'pending'
        this.value = undefined
        this.reason = undefined
        
        const resolve = (value) => {
            if (this.state === 'pending') {
                this.state = 'fulfilled'
                this.value = value
            }
        }
        
        const reject = (reason) => {
            if (this.state === 'pending') {
                this.state = 'rejected'
                this.reason = reason
            }
        }
        
        try {
            executor(resolve, reject)
        } catch (err) {
            reject(err)
        }
    }
    
    then(onFulfilled, onRejected) {
        // 简化实现,实际更复杂
        if (this.state === 'fulfilled') {
            onFulfilled(this.value)
        }
        if (this.state === 'rejected') {
            onRejected(this.reason)
        }
    }
}

四、ES6+ 新特性

23. 解构赋值的用途?

答案:

javascript 复制代码
// 数组解构
const [a, b] = [1, 2]

// 对象解构
const { name, age } = { name: 'John', age: 30 }

// 函数参数解构
function foo({ x, y }) { return x + y }

// 交换变量
[a, b] = [b, a]

24. 扩展运算符的用途?

答案:

javascript 复制代码
// 复制数组
const arr2 = [...arr1]

// 合并数组
const arr3 = [...arr1, ...arr2]

// 函数参数
Math.max(...numbers)

// 对象浅拷贝
const obj2 = { ...obj1 }

25. 模板字符串的特性?

答案:

javascript 复制代码
// 支持换行
const str = `第一行
第二行`

// 支持表达式
const name = 'John'
const greeting = `Hello, ${name}!`

// 标签模板
function tag(strings, ...values) {
    // strings: 模板字符串的静态部分
    // values: 表达式的值
}

26. Symbol 的作用?

答案:

创建唯一的值,避免属性名冲突

可用作对象的私有属性

内置 Symbol 值如 Symbol.iterator、Symbol.toStringTag

27. Set 和 Map 的区别?

答案:

Set:值的集合,值唯一

Map:键值对的集合,键可以是任意类型

WeakSet:弱引用集合,只能存对象

WeakMap:弱引用键值对,键只能是对象

五、DOM 与 BOM

28. 事件委托是什么?

答案:

将事件监听器绑定到父元素

利用事件冒泡机制处理子元素事件

优点:减少内存消耗,动态元素也能处理

29. 事件冒泡和事件捕获?

答案:

事件冒泡:从目标元素向上传播到根元素

事件捕获:从根元素向下传播到目标元素

DOM 事件流:捕获 → 目标 → 冒泡

addEventListener 第三个参数控制阶段

30. 阻止事件默认行为和冒泡?

答案:

javascript 复制代码
event.preventDefault()    // 阻止默认行为
event.stopPropagation()   // 阻止冒泡
event.stopImmediatePropagation() // 阻止同一事件的其他监听器

六、性能与安全

31. 什么是防抖和节流?

答案:

防抖:事件触发后延迟执行,如果期间再次触发则重新计时

节流:在一定时间内只执行一次

javascript 复制代码
// 防抖
function debounce(fn, delay) {
    let timer
    return function(...args) {
        clearTimeout(timer)
        timer = setTimeout(() => fn.apply(this, args), delay)
    }
}

// 节流
function throttle(fn, delay) {
    let lastTime = 0
    return function(...args) {
        const now = Date.now()
        if (now - lastTime >= delay) {
            fn.apply(this, args)
            lastTime = now
        }
    }
}

32. 什么是跨域?如何解决?

答案:

浏览器同源策略限制

解决方案:

bash 复制代码
JSONP(仅 GET 请求)

CORS(服务器设置响应头)

代理服务器

postMessage

WebSocket

33. 什么是 XSS 攻击?如何防范?

答案:

跨站脚本攻击,注入恶意脚本

防范措施:

输入过滤和转义

设置 HttpOnly Cookie

使用 CSP(内容安全策略)

避免内联事件处理

34. 什么是 CSRF 攻击?如何防范?

答案:

跨站请求伪造,诱导用户发送恶意请求

防范措施:

使用 CSRF Token

验证 Referer 头

设置 SameSite Cookie

验证码

七、算法与数据结构

35. 数组去重的方法?

答案:

javascript 复制代码
// 1. Set
[...new Set(array)]

// 2. filter + indexOf
array.filter((item, index) => array.indexOf(item) === index)

// 3. reduce
array.reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], [])

36. 数组扁平化的方法?

答案:

javascript 复制代码
// 1. flat
arr.flat(Infinity)

// 2. reduce + 递归
function flatten(arr) {
    return arr.reduce((acc, cur) => 
        Array.isArray(cur) 
            ? acc.concat(flatten(cur)) 
            : acc.concat(cur), 
    [])
}

// 3. toString(仅限数字数组)
arr.toString().split(',').map(Number)

37. 深拷贝的实现?

答案:

javascript 复制代码
function deepClone(obj, map = new WeakMap()) {
    if (obj === null || typeof obj !== 'object') return obj
    if (map.has(obj)) return map.get(obj)
    
    const clone = Array.isArray(obj) ? [] : {}
    map.set(obj, clone)
    
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            clone[key] = deepClone(obj[key], map)
        }
    }
    return clone
}

八、 浏览器相关

38. 从输入 URL 到页面显示的过程?

答案:

DNS 解析

TCP 连接

发送 HTTP 请求

服务器处理请求并返回响应

浏览器解析渲染

连接结束

39. 重排和重绘?

答案:

重排:布局改变,需要重新计算

重绘:外观改变,不需要重新布局

优化:避免频繁操作 DOM,使用 transform 和 opacity

40. 内存泄漏的原因?

答案:

意外的全局变量

未清理的定时器

闭包滥用

未解绑的事件监听

DOM 引用未清除

九、模块化

41. CommonJS 和 ES6 Module 的区别?

42. AMD 和 CMD 的区别?

答案:

AMD:异步加载,提前执行(RequireJS)

CMD:异步加载,延迟执行(SeaJS)

现在多用 ES6 Module

其他重要概念

43. 什么是柯里化?

答案:

将多参数函数转化为单参数函数序列

javascript 复制代码
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args)
        } else {
            return function(...args2) {
                return curried.apply(this, args.concat(args2))
            }
        }
    }
}

44. 什么是函数组合?

答案:

将多个函数组合成一个新函数

javascript 复制代码
function compose(...fns) {
    return function(x) {
        return fns.reduceRight((acc, fn) => fn(acc), x)
    }
}

45. 什么是尾调用优化?

答案:

函数最后一步调用另一个函数

ES6 严格模式下支持

可以优化递归性能

46. Generator 函数的作用?

答案:

可以暂停执行和恢复执行

配合 yield 使用

常用于异步编程(async/await 的基础)

javascript 复制代码
function* gen() {
    yield 1
    yield 2
    return 3
}

47. Proxy 和 Reflect?

答案:

Proxy:代理对象,拦截操作

Reflect:操作对象的 API,与 Proxy 对应

javascript 复制代码
const proxy = new Proxy(target, {
    get(target, prop) {
        return Reflect.get(target, prop)
    }
})

48. WeakMap 和 WeakSet 的特点?

答案:

弱引用,不影响垃圾回收

键/值必须是对象

不可遍历

没有 size 属性

49. 什么是 Optional Chaining?

答案:

ES2020 新增,可选链操作符 ?.

避免访问嵌套对象时的空值错误

bash 复制代码
const name = obj?.user?.name

50. 什么是 Nullish Coalescing?

答案:

ES2020 新增,空值合并操作符 ??

仅在左侧为 null 或 undefined 时返回右侧

javascript 复制代码
const value = a ?? 'default'
相关推荐
夏幻灵2 小时前
指针在 C++ 中最核心、最实用的两个作用:“避免大数据的复制” 和 “共享”。
开发语言·c++
湛海不过深蓝2 小时前
【echarts】折线图颜色分段设置不同颜色
前端·javascript·echarts
八哥程序员2 小时前
Chrome DevTools 详解系列之 Console 面板
javascript·chrome
ghie90902 小时前
MATLAB 高速公路裂缝检测
开发语言·matlab
BD_Marathon2 小时前
Vue3_计算属性
javascript·vue.js·ecmascript
wniuniu_2 小时前
ceph运维
运维·javascript·ceph
Yyyyy123jsjs2 小时前
Python 如何做量化交易?从行情获取开始
开发语言·python
violet-lz2 小时前
C++ 内存分区详解
开发语言·jvm·c++
周小码2 小时前
Spacedrive:用Rust构建的虚拟分布式文件系统
开发语言·后端·rust