前端面试题库 - JavaScript核心基础篇

一、数据类型与类型转换

1. JavaScript有哪些数据类型?它们之间有什么区别?

基本数据类型(7种):

  • Number:数字类型,包括整数和浮点数
  • String:字符串类型
  • Boolean:布尔类型,true/false
  • Undefined:未定义
  • Null:空值
  • Symbol(ES6):唯一标识符
  • BigInt(ES2020):大整数

引用数据类型:

  • Object:对象,包括普通对象、数组、函数、日期等

核心区别:

  • 存储方式:基本类型存储在栈内存,引用类型存储在堆内存(栈中存储引用地址)
  • 赋值方式:基本类型赋值传值,引用类型赋值传引用
  • 比较方式:基本类型比较值,引用类型比较引用地址
javascript 复制代码
// 基本类型
let a = 10;
let b = a;
b = 20;
console.log(a); // 10,a不受影响

// 引用类型
let obj1 = { name: 'Alice' };
let obj2 = obj1;
obj2.name = 'Bob';
console.log(obj1.name); // 'Bob',obj1被修改

2. 如何准确判断数据类型?

方法对比:

javascript 复制代码
// 1. typeof - 适合基本类型(除null)
typeof 123 // 'number'
typeof 'abc' // 'string'
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof Symbol() // 'symbol'
typeof null // 'object' ⚠️ 历史遗留问题
typeof [] // 'object' ⚠️ 无法区分数组和对象

// 2. instanceof - 检测引用类型的原型链
[] instanceof Array // true
[] instanceof Object // true
({}) instanceof Object // true

// 3. Object.prototype.toString - 最准确的方法
Object.prototype.toString.call(123) // '[object Number]'
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(new Date()) // '[object Date]'

// 4. 封装通用类型检测函数
function getType(value) {
  return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}

getType([]) // 'array'
getType(null) // 'null'
getType(new Map()) // 'map'

3. 说明隐式类型转换的规则

转换规则:

javascript 复制代码
// 1. 转Boolean - 假值只有7个
Boolean(0) // false
Boolean('') // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(NaN) // false
Boolean(false) // false
Boolean(document.all) // false(历史遗留)

// 2. 转Number
Number('123') // 123
Number('12.5') // 12.5
Number('') // 0
Number('abc') // NaN
Number(true) // 1
Number(false) // 0
Number(null) // 0
Number(undefined) // NaN

// 3. 转String
String(123) // '123'
String(true) // 'true'
String(null) // 'null'
String(undefined) // 'undefined'

// 4. 复杂情况 - ToPrimitive
// 对象转基本类型:先调用valueOf,再调用toString
let obj = {
  valueOf() { return 1; },
  toString() { return '2'; }
};
console.log(obj + 1); // 2(调用valueOf)
console.log(`${obj}`); // '2'(调用toString)

// 5. == 运算符的转换规则
null == undefined // true
'0' == 0 // true(字符串转数字)
0 == false // true(布尔转数字)
'' == false // true
[] == false // true(对象转基本类型)
[] == ![] // true(复杂转换)

二、作用域与闭包

4. 解释JavaScript的作用域链

概念: 作用域链是变量查找的机制,从当前作用域逐级向外查找,直到全局作用域。

javascript 复制代码
let global = 'global';

function outer() {
  let outerVar = 'outer';
  
  function inner() {
    let innerVar = 'inner';
    
    // 作用域链:inner -> outer -> global
    console.log(innerVar);  // 'inner' - 当前作用域
    console.log(outerVar);  // 'outer' - 父作用域
    console.log(global);    // 'global' - 全局作用域
  }
  
  inner();
}

outer();

特点:

  • 静态作用域(词法作用域):函数作用域在定义时确定,不是调用时
  • 变量查找顺序:当前作用域 → 外层作用域 → ... → 全局作用域
  • 查找终止:找到变量或到达全局作用域

5. 什么是闭包?闭包的应用场景有哪些?

定义: 闭包是指有权访问另一个函数作用域中变量的函数。

javascript 复制代码
// 基本闭包示例
function createCounter() {
  let count = 0; // 私有变量
  
  return {
    increment() {
      return ++count;
    },
    decrement() {
      return --count;
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount());  // 2

应用场景:

  1. 数据私有化和封装
javascript 复制代码
function createPerson(name) {
  let _name = name; // 私有变量
  
  return {
    getName() { return _name; },
    setName(newName) { _name = newName; }
  };
}
  1. 函数柯里化
javascript 复制代码
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) {
      return fn.apply(this, args);
    }
    return function(...nextArgs) {
      return curried.apply(this, args.concat(nextArgs));
    };
  };
}

const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
  1. 防抖和节流
javascript 复制代码
function debounce(fn, delay) {
  let timer = null; // 闭包保存定时器
  
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}
  1. 模块化模式
javascript 复制代码
const Calculator = (function() {
  let result = 0; // 私有变量
  
  return {
    add(num) { result += num; return this; },
    subtract(num) { result -= num; return this; },
    getResult() { return result; }
  };
})();

注意事项:

  • 闭包会使变量驻留在内存中,可能导致内存泄漏
  • 及时释放不需要的闭包引用

6. var、let、const的区别

特性 var let const
作用域 函数作用域 块级作用域 块级作用域
变量提升 有(值为undefined) 有(暂时性死区) 有(暂时性死区)
重复声明 允许 不允许 不允许
重新赋值 允许 允许 不允许
全局对象属性
javascript 复制代码
// 1. 作用域差异
if (true) {
  var a = 1;
  let b = 2;
  const c = 3;
}
console.log(a); // 1
console.log(b); // ReferenceError
console.log(c); // ReferenceError

// 2. 变量提升
console.log(x); // undefined
var x = 5;

console.log(y); // ReferenceError(暂时性死区)
let y = 5;

// 3. 循环中的经典问题
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 3 3 3
}

for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 100); // 0 1 2
}

// 4. const特性
const obj = { name: 'Alice' };
obj.name = 'Bob'; // ✅ 允许修改属性
obj = {}; // ❌ TypeError,不能重新赋值

// 冻结对象
const frozenObj = Object.freeze({ name: 'Alice' });
frozenObj.name = 'Bob'; // 严格模式下报错,非严格模式静默失败

三、this指向与调用

7. JavaScript中this的指向规则

四种绑定规则(优先级从低到高):

javascript 复制代码
// 1. 默认绑定 - 独立函数调用
function foo() {
  console.log(this); // 非严格模式:window,严格模式:undefined
}
foo();

// 2. 隐式绑定 - 对象方法调用
const obj = {
  name: 'Alice',
  sayName() {
    console.log(this.name);
  }
};
obj.sayName(); // 'Alice',this指向obj

// 隐式绑定丢失
const fn = obj.sayName;
fn(); // undefined,this指向window/undefined

// 3. 显式绑定 - call/apply/bind
function greet(greeting, punctuation) {
  console.log(greeting + ', ' + this.name + punctuation);
}
const person = { name: 'Bob' };

greet.call(person, 'Hello', '!'); // 'Hello, Bob!'
greet.apply(person, ['Hi', '?']); // 'Hi, Bob?'

const boundGreet = greet.bind(person, 'Hey');
boundGreet('!!!'); // 'Hey, Bob!!!'

// 4. new绑定 - 构造函数
function Person(name) {
  this.name = name;
}
const p = new Person('Charlie');
console.log(p.name); // 'Charlie'

// 5. 箭头函数 - 捕获外层this
const obj2 = {
  name: 'David',
  sayName() {
    setTimeout(() => {
      console.log(this.name); // 'David',继承外层this
    }, 100);
  }
};
obj2.sayName();

优先级: new绑定 > 显式绑定 > 隐式绑定 > 默认绑定

8. 手写实现call、apply、bind

javascript 复制代码
// 实现call
Function.prototype.myCall = function(context, ...args) {
  // 处理context为null/undefined的情况
  context = context || window;
  
  // 创建唯一属性,避免覆盖原有属性
  const fnSymbol = Symbol();
  context[fnSymbol] = this;
  
  // 执行函数
  const result = context[fnSymbol](...args);
  
  // 删除临时属性
  delete context[fnSymbol];
  
  return result;
};

// 实现apply
Function.prototype.myApply = function(context, argsArray) {
  context = context || window;
  const fnSymbol = Symbol();
  context[fnSymbol] = this;
  
  // apply接收数组参数
  const result = argsArray 
    ? context[fnSymbol](...argsArray)
    : context[fnSymbol]();
  
  delete context[fnSymbol];
  return result;
};

// 实现bind
Function.prototype.myBind = function(context, ...args) {
  const fn = this;
  
  return function boundFn(...newArgs) {
    // 判断是否被new调用
    if (this instanceof boundFn) {
      return new fn(...args, ...newArgs);
    }
    return fn.apply(context, args.concat(newArgs));
  };
};

// 测试
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person = { name: 'Alice' };
greet.myCall(person, 'Hello', '!'); // 'Hello, Alice!'
greet.myApply(person, ['Hi', '?']); // 'Hi, Alice?'

const boundGreet = greet.myBind(person, 'Hey');
boundGreet('!!!'); // 'Hey, Alice!!!'

四、原型与继承

9. 解释原型链机制

核心概念:

  • 每个对象都有一个__proto__属性指向其原型对象
  • 每个构造函数都有一个prototype属性
  • 原型链:对象 → 构造函数.prototype → Object.prototype → null
javascript 复制代码
function Person(name) {
  this.name = name;
}

Person.prototype.sayName = function() {
  console.log(this.name);
};

const alice = new Person('Alice');

// 原型链关系
console.log(alice.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null

// 属性查找
alice.sayName(); // 查找顺序:alice -> Person.prototype -> Object.prototype
alice.toString(); // 最终在Object.prototype找到

关键属性:

javascript 复制代码
// constructor属性
console.log(Person.prototype.constructor === Person); // true
console.log(alice.constructor === Person); // true(继承自原型)

// instanceof原理:检查原型链
console.log(alice instanceof Person); // true
console.log(alice instanceof Object); // true

10. JavaScript实现继承的多种方式

javascript 复制代码
// 1. 原型链继承
function Parent() {
  this.name = 'parent';
  this.hobbies = ['reading'];
}
Parent.prototype.getName = function() {
  return this.name;
};

function Child() {}
Child.prototype = new Parent();

// 缺点:引用类型共享,无法传参
const child1 = new Child();
const child2 = new Child();
child1.hobbies.push('gaming');
console.log(child2.hobbies); // ['reading', 'gaming']

// 2. 构造函数继承
function Parent(name) {
  this.name = name;
  this.hobbies = ['reading'];
}

function Child(name, age) {
  Parent.call(this, name); // 调用父类构造函数
  this.age = age;
}

// 优点:可传参,引用类型独立
// 缺点:无法继承原型方法,函数无法复用

// 3. 组合继承(最常用)
function Parent(name) {
  this.name = name;
  this.hobbies = ['reading'];
}
Parent.prototype.getName = function() {
  return this.name;
};

function Child(name, age) {
  Parent.call(this, name); // 第二次调用
  this.age = age;
}
Child.prototype = new Parent(); // 第一次调用
Child.prototype.constructor = Child;

// 缺点:调用两次父类构造函数

// 4. 寄生组合继承(最优方案)
function Parent(name) {
  this.name = name;
  this.hobbies = ['reading'];
}
Parent.prototype.getName = function() {
  return this.name;
};

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}

// 创建中间对象,避免调用父类构造函数
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

// 5. ES6 Class继承(语法糖)
class Parent {
  constructor(name) {
    this.name = name;
    this.hobbies = ['reading'];
  }
  
  getName() {
    return this.name;
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); // 必须先调用super
    this.age = age;
  }
  
  getAge() {
    return this.age;
  }
}

const child = new Child('Alice', 18);
console.log(child.getName()); // 'Alice'

继承方式对比:

方式 优点 缺点 推荐度
原型链继承 简单 引用类型共享
构造函数继承 可传参、引用独立 无法继承原型 ⭐⭐
组合继承 结合前两者优点 调用两次构造函数 ⭐⭐⭐
寄生组合继承 完美解决组合继承问题 实现复杂 ⭐⭐⭐⭐⭐
ES6 Class 语法简洁、易读 需要编译 ⭐⭐⭐⭐⭐
相关推荐
JAVA面经实录9178 小时前
Java多线程并发高频面试100题(完整版·含答案·背诵版)
java·开发语言·面试
软件技术NINI8 小时前
泉州html+css 4页
前端·javascript·css·html
再吃一根胡萝卜8 小时前
OpenScreen:免费开源的录屏神器,做出专业级演示视频
前端
Cloud_Shy6188 小时前
Python 数据分析基础入门:《Excel Python:飞速搞定数据分析与处理》学习笔记系列(第十一章 Python 包跟踪器 下篇)
前端·后端·python·数据分析·excel
kyriewen8 小时前
我用AI把公司10万行代码屎山重构了,CTO看了代码后说:你提前转正
前端·javascript·ai编程
ttwuai8 小时前
XYGo Admin 菜单与路由:Vue3 动态路由 + GoFrame 权限菜单的完整实现方案
前端·vue·后台框架
程序员码歌8 小时前
OpenSpec 到 Superpowers:AI 编码从说清到做对
android·前端·人工智能
爱编程的小新☆8 小时前
LangGraph4j工作流框架
前端·数据库·ai·langchain·langgraph4j