JavaScript 核心概念
1. 数据类型与类型判断
题目:JavaScript 的数据类型有哪些?如何准确判断一个变量是否为数组?
难度: ★
考点: JS 基础类型、类型检测
答案要点:
JavaScript 分为基本数据类型和引用数据类型,判断数组最稳妥的方法是使用原生提供的静态方法。
- 基本类型: String, Number, Boolean, Null, Undefined, Symbol, BigInt
- 引用类型: Object, Array, Function, Date, RegExp
- 判断数组:
Array.isArray()是首选,也可以使用Object.prototype.toString.call() - 区别: typeof 对数组会返回 object,instanceof 在多窗口/iframe 环境下可能失效
常见坑:
- 认为 typeof 可以区分数组和对象
- 忽略了 null 的 typeof 结果是 object
追问:
- 为什么 typeof null 是 object?
- 如何判断一个对象是否为空对象?
2. 原型链与继承
题目:谈谈你对原型链的理解,Object 和 Function 的关系是怎样的?
难度: ★★
考点: JS 面向对象基础
答案要点:
原型链是 JS 实现继承的机制,每个对象都有一个指向其原型对象的内部链接,直到指向 null。
- 原型对象: 每个函数都有 prototype 属性,每个实例对象都有 proto 属性
- 查找机制: 访问属性时,先找自身,再顺着 proto 向上找
- 顶层结构: Object.prototype 是原型链的终点(null 之前)
- 函数特殊性: Function 也是对象,Function.proto 指向 Function.prototype,Object 是 Function 的实例
常见坑:
- 混淆 prototype 和 proto
- 忽略了 constructor 属性的指向
追问:
- 如何判断一个属性是存在于对象本身还是原型链上?
3. 闭包
题目:什么是闭包?它在实际开发中有什么应用场景?
难度: ★
考点: 作用域
答案要点:
闭包是指有权访问另一个函数作用域中变量的函数,本质是函数与其词法环境的引用捆绑。
- 形成条件: 函数嵌套且内部函数引用了外部函数的变量
- 应用场景: 封装私有变量、柯里化(Currying)、防抖节流、模块化模式
- 内存影响: 闭包会导致外部函数的变量常驻内存,不当使用可能引起内存泄漏
- 作用域链: 闭包通过作用域链查找变量,查找过程是从内向外的
常见坑:
- 在循环中使用 var 定义变量导致闭包引用了同一个值
- 滥用闭包导致性能下降
追问:
- 现代浏览器引擎对闭包的内存优化做了哪些工作?
4. 深浅拷贝
题目:解释一下浅拷贝和深拷贝,如何实现一个深拷贝?
难度: ★★
考点: JS 核心原理
答案要点:
原型链是 JS 实现继承的基础,通过 proto 指针将对象连接起来;深拷贝则是为了彻底断开引用。
- 原型链: 每个对象都有原型,查找属性时会顺着原型链向上直到 null
- 深拷贝实现: 简单场景用 JSON.parse,复杂场景需递归处理
- 循环引用: 使用 WeakMap 存储已拷贝对象,防止死循环
- 特殊类型: 需处理 Date、RegExp、Map、Set 等特殊对象的克隆
代码示例:
javascript
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (hash.has(obj)) return hash.get(obj);
const cloneObj = new obj.constructor();
hash.set(obj, cloneObj);
Reflect.ownKeys(obj).forEach(key => {
cloneObj[key] = deepClone(obj[key], hash);
});
return cloneObj;
}常见坑:
- 忽略 Symbol 属性的拷贝
- 使用 typeof 判断 null 时返回 object 的陷阱
追问:
- 为什么深拷贝中推荐使用 WeakMap 而不是 Map?
5. this 指向
题目:JavaScript 中 this 的指向规则有哪些?
难度: ★★
考点: 执行上下文
答案要点:
this 的指向是在函数执行时确定的,不同调用方式有不同的指向规则。
- 默认绑定: 独立函数调用,this 指向全局对象(严格模式为 undefined)
- 隐式绑定: 方法调用,this 指向调用它的对象
- 显式绑定: call/apply/bind 可以指定 this 指向
- new 绑定: 构造函数中的 this 指向新创建的实例
- 箭头函数: 没有自己的 this,继承外层作用域的 this
常见坑:
- 回调函数中的 this 丢失问题
- 箭头函数不能作为构造函数使用
6. 作用域与作用域链
题目:谈谈你对 JavaScript 作用域的理解
难度: ★
考点: 词法作用域
答案要点:
JavaScript 采用词法作用域(静态作用域),作用域链用于变量的查找。
- 全局作用域: 全局可访问的变量
- 函数作用域: 函数内部声明的变量
- 块级作用域: ES6 引入的 let/const 声明的变量
- 作用域链: 查找变量时从内到外依次查找
常见坑:
- var 声明的变量存在变量提升
- let/const 存在暂时性死区(TDZ)
7. 数组方法
题目:数组中有哪些常用的高阶函数?如何实现数组扁平化?
难度: ★
考点: JS API
答案要点:
高阶函数是接收函数作为参数或返回函数的函数,常用于集合处理。
- 常用函数: map, filter, reduce, some, every, find
- 扁平化方法: Array.prototype.flat()、递归处理、扩展运算符结合 some
- reduce 实现: 通过 reduce 累加器递归拼接数组
- 性能考量: 对于超大数组,递归可能导致栈溢出,需考虑迭代解法
代码示例:
javascript
// reduce 实现 map
Array.prototype.myMap = function(fn) {
return this.reduce((acc, item, index) => {
acc.push(fn(item, index, this));
return acc;
}, []);
};
// 数组扁平化
function flatten(arr) {
return arr.reduce((acc, val) =>
acc.concat(Array.isArray(val) ? flatten(val) : val), []);
}常见坑:
- map 忘记写 return 导致结果全是 undefined
- reduce 没传初始值导致空数组报错
8. 模块化
题目:CommonJS 和 ES Module 有什么区别?
难度: ★★
考点: 模块化规范
答案要点:
| 特性 | CommonJS | ES Module |
|---|---|---|
| 加载方式 | 运行时加载 | 编译时加载 |
| 导出值 | 值的拷贝 | 值的引用 |
| this 指向 | 当前模块 | undefined |
| 循环引用 | 返回已执行部分的值 | 返回引用错误或 undefined |
| 使用环境 | Node.js | 浏览器/Node.js |
常见坑:
- 混淆 exports 和 module.exports 的指向关系
- ES Module 中不能使用 require
高频手写题汇总
1. 实现 new 操作符
javascript
function myNew(constructor, ...args) {
const obj = Object.create(constructor.prototype);
const result = constructor.apply(obj, args);
return result instanceof Object ? result : obj;
}2. 实现 instanceof
javascript
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left);
while (proto) {
if (proto === right.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}3. 实现 call/apply/bind
javascript
Function.prototype.myCall = function(context, ...args) {
context = context || globalThis;
const fn = Symbol('fn');
context[fn] = this;
const result = context[fn](...args);
delete context[fn];
return result;
};
Function.prototype.myBind = function(context, ...args1) {
const fn = this;
return function(...args2) {
return fn.apply(context, [...args1, ...args2]);
};
};4. 实现 JSONP
javascript
function jsonp(url, params, callbackName) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
window[callbackName] = (data) => {
resolve(data);
document.body.removeChild(script);
delete window[callbackName];
};
const queryString = Object.entries(params)
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
.join('&');
script.src = `${url}?${queryString}&callback=${callbackName}`;
document.body.appendChild(script);
});
}