Skip to content

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 有什么区别?

难度: ★★

考点: 模块化规范

答案要点:

特性CommonJSES 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);
  });
}