前端面试复习指南【代码演示多多版】之——JS

0 阅读6分钟

我把JS复习分成六个模块,从基础到进阶:

模块内容重要程度
基础语法变量、数据类型、运算符⭐⭐⭐ 必会
函数定义、作用域、闭包⭐⭐⭐ 必会
对象/数组增删改查、方法⭐⭐⭐ 必会
DOM操作找元素、改内容、绑事件⭐⭐⭐ 必会
异步回调、Promise、async/await⭐⭐⭐⭐ 进阶
进阶概念原型链、this、事件循环⭐⭐⭐⭐ 面试

1. 基础语法(快速过)

1.1 变量声明

// 三种方式
var name = '旧时代的'// 函数作用域,可重复声明
let age = 18// 块级作用域,不能重复声明
const PI = 3.14// 常量,不能改

1.2 数据类型

// 基本类型(存值)
let str = 'hello'// 字符串
let num = 123// 数字
let bool = true// 布尔
let und = undefined// 未定义
let nul = null// 空
let s1 = Symbol();       // symbol(符号)ES6
let big = 123456n// bigint(大整数)ES2020

// 引用类型(存地址)
let obj = { name: 'Tom' };   // 对象
let arr = [1, 2, 3];         // 数组
let fn = function() {};       // 函数

1.3 类型判断

// 1. typeof(最常用)
typeof 'hello'// "string"
typeof 123// "number"
typeof true// "boolean"
typeof undefined// "undefined"
typeof Symbol();    // "symbol"
typeof 123n// "bigint"
typeof null// "object"(bug)
typeof [];          // "object"
typeof {};          // "object"

// 2. instanceof
[] instanceof Array// true
[] instanceof Object// true

// 3. Array.isArray
Array.isArray([]);      // true
Array.isArray({});      // false

// 4. 终极方案
Object.prototype.toString.call([]);    // "[object Array]"
Object.prototype.toString.call(null);  // "[object Null]"

2. 函数(重点)

2.1 作用域

let global = '全局'// 全局作用域

function fn() {
  let local = '局部'// 函数作用域
  if (true) {
    let block = '块级'// 块级作用域(let/const)
    var old = '老家伙'// 函数作用域(var)
  }
  console.log(old);       // ✅ 能访问
  console.log(block);      // ❌ 报错
}

2.2 闭包(面试必考)

// 闭包 = 函数 + 它能访问的外部变量
function createCounter() {
  let count = 0// 外部变量
  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter());  // 1
console.log(counter());  // 2
// count 没有被销毁,被内部函数一直引用着

神奇之处:

  • createCounter 执行完了,本该销毁的 count 变量居然还活着
  • 内部函数把它记住了,还能继续用
  • 外面直接访问不到 count,安全!

3. 数组方法(必会)

const arr = [1, 2, 3, 4, 5];

// 增删改
arr.push(6);           // 尾部加 → [1,2,3,4,5,6]
arr.pop();             // 尾部删 → [1,2,3,4,5]
arr.unshift(0);        // 头部加 → [0,1,2,3,4,5]
arr.shift();           // 头部删 → [1,2,3,4,5]

// 遍历
arr.forEach(item => console.log(item));

// 映射(返回新数组)
const double = arr.map(item => item * 2);  // [2,4,6,8,10]

// 过滤(返回新数组)
const even = arr.filter(item => item % 2 === 0);  // [2,4]

// 查找
const found = arr.find(item => item > 3);  // 4
const index = arr.findIndex(item => item > 3);  // 3

// 判断
const hasEven = arr.some(item => item % 2 === 0);  // true
const allEven = arr.every(item => item % 2 === 0);  // false

// 累加
const sum = arr.reduce((acc, cur) => acc + cur, 0);  // 15

4. 异步编程(最难也最重要)

4.1 为什么需要异步?

// 同步(会卡住)
console.log('开始');
for(let i = 0; i < 1000000000; i++) {}  // 卡死
console.log('结束');  // 要等上面循环完

// 异步(不卡)
console.log('开始');
setTimeout(() => {
  console.log('1秒后');
}, 1000);
console.log('结束');  // 先打印,不等setTimeout

4.2 回调函数(最原始)

// 这就是一个回调函数
function callback() {
  console.log('回调被执行了');
}

// 这个函数接收一个函数作为参数
function doSomething(cb) {
  console.log('做事中...');
  cb();  // 执行回调
}

doSomething(callback);
// 输出:
// 做事中...
// 回调被执行了

4.3 Promise(现代方案)

// 创建Promise
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const success = trueif (success) {
      resolve('成功的数据');   // 成功调用
    } else {
      reject('失败啦');        // 失败调用
    }
  }, 1000);
});

// 使用Promise
promise
  .then(result => {
    console.log('成功:', result);
  })
  .catch(error => {
    console.log('失败:', error);
  });

4.4 async/await(终极方案)

async/await 是 JavaScript 中处理异步操作的一种语法糖,建立在 Promise 之上,让异步代码写起来和读起来都像同步代码。

async 函数

在函数声明前加上 async 关键字,该函数就会返回一个 Promise:

async function fetchData() {
  return '数据';
}
// 等价于
function fetchData() {
  return Promise.resolve('数据');
}

// 调用
fetchData().then(data => console.log(data)); // 输出: 数据

await 关键字

await 只能用在 async 函数内部,它会暂停函数执行,等待 Promise 解决:

async function getData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
}

实际应用示例

基本用法对比
// Promise 链式调用
function getUserInfo(userId) {
  return fetch(`/api/users/${userId}`)
    .then(response => response.json())
    .then(user => {
      return fetch(`/api/posts?userId=${user.id}`);
    })
    .then(response => response.json())
    .catch(error => console.error('错误:', error));
}

// Async/Await 版本
async function getUserInfo(userId) {
  try {
    const userResponse = await fetch(`/api/users/${userId}`);
    const user = await userResponse.json();
    
    const postsResponse = await fetch(`/api/posts?userId=${user.id}`);
    const posts = await postsResponse.json();
    
    return posts;
  } catch (error) {
    console.error('错误:', error);
  }
}
错误处理
async function fetchDataWithError() {
  try {
    const response = await fetch('https://api.example.com/data');
    
    if (!response.ok) {
      throw new Error(`HTTP 错误! 状态码: ${response.status}`);
    }
    
    const data = await response.json();
    return data;
    
  } catch (error) {
    console.error('请求失败:', error.message);
    // 可以返回默认值或重新抛出
    return { error: true, message: error.message };
  }
}

// 或者使用 .catch()
async function fetchData() {
  const data = await fetch('https://api.example.com/data')
    .catch(error => {
      console.error('网络错误:', error);
      return { error: true };
    });
  
  return data;
}
并行执行
// 串行执行(耗时较长)
async function loadSequential() {
  const start = Date.now();
  
  const user = await fetch('/api/user');
  const posts = await fetch('/api/posts');
  const comments = await fetch('/api/comments');
  
  console.log('耗时:', Date.now() - start, 'ms');
  return { user, posts, comments };
}

// 并行执行(更高效)
async function loadParallel() {
  const start = Date.now();
  
  const [user, posts, comments] = await Promise.all([
    fetch('/api/user'),
    fetch('/api/posts'),
    fetch('/api/comments')
  ]);
  
  console.log('耗时:', Date.now() - start, 'ms');
  return { user, posts, comments };
}

// 处理多个 Promise,但不想全部失败
async function loadWithSettled() {
  const results = await Promise.allSettled([
    fetch('/api/user'),
    fetch('/api/posts'),
    fetch('/api/comments')
  ]);
  
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      console.log(`请求 ${index} 成功:`, result.value);
    } else {
      console.log(`请求 ${index} 失败:`, result.reason);
    }
  });
}

5. 面试进阶概念

5.1 this 指向(经典难题)

this 的绑定规则(按优先级从高到低)

new 绑定

使用 new 调用函数时,this 指向新创建的对象:

function Person(name) {
  this.name = name;
  this.sayHi = function() {
    console.log(`Hi, I'm ${this.name}`);
  };
}

const p1 = new Person('Alice');
const p2 = new Person('Bob');

p1.sayHi(); // Hi, I'm Alice
p2.sayHi(); // Hi, I'm Bob
显式绑定(call, apply, bind)

通过 callapplybind 手动指定 this

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

const person = { name: 'Alice' };

// call - 参数列表
greet.call(person, 'Hello', '!'); // Hello, Alice!

// apply - 参数数组
greet.apply(person, ['Hi', '...']); // Hi, Alice...

// bind - 返回新函数,永久绑定 this
const boundGreet = greet.bind(person, 'Hey');
boundGreet('?'); // Hey, Alice?
隐式绑定

函数作为对象的方法调用时,this 指向该对象:

const user = {
  name: 'Alice',
  age: 25,
  greet() {
    console.log(`Hello, I'm ${this.name}`);
  },
  friends: ['Bob', 'Charlie'],
  showFriends() {
    this.friends.forEach(function(friend) {
      // 这里的 this 不是 user 对象!
      console.log(`${this.name}'s friend: ${friend}`);
    });
  }
};

user.greet(); // Hello, I'm Alice
user.showFriends(); 
// undefined's friend: Bob  (this 指向 window/全局对象)
默认绑定

独立函数调用时,this 指向全局对象(浏览器中的 window,Node.js 中的 global),严格模式下为 undefined

function showThis() {
  console.log(this);
}

// 非严格模式
showThis(); // window (浏览器) 或 global (Node.js)

// 严格模式
function showThisStrict() {
  'use strict';
  console.log(this);
}
showThisStrict(); // undefined

常见场景的 this 指向

箭头函数

箭头函数没有自己的 this,它继承定义时的外层作用域的 this

const obj = {
  name: 'Alice',
  regularFunc: function() {
    console.log('regular:', this.name);
    
    setTimeout(function() {
      console.log('regular timeout:', this.name); // undefined
    }, 100);
    
    setTimeout(() => {
      console.log('arrow timeout:', this.name); // Alice
    }, 100);
  },
  
  arrowFunc: () => {
    console.log('arrow:', this.name); // undefined (this 是外层作用域的 this)
  }
};

obj.regularFunc();
obj.arrowFunc();
事件处理函数
// DOM 事件
button.addEventListener('click', function() {
  console.log(this); // 指向 button 元素
});

button.addEventListener('click', () => {
  console.log(this); // 指向外层作用域的 this(可能是 window)
});

// 对象方法作为事件处理
const handler = {
  name: 'MyHandler',
  handleClick: function() {
    console.log(this.name);
  }
};

button.addEventListener('click', handler.handleClick); 
// 点击时输出 undefined(this 指向 button)

// 正确方式
button.addEventListener('click', () => handler.handleClick());
// 或
button.addEventListener('click', handler.handleClick.bind(handler));
类中的 this
class MyClass {
  constructor(name) {
    this.name = name;
    
    // 方法1:箭头函数(自动绑定)
    this.sayHiArrow = () => {
      console.log(`Hi, I'm ${this.name}`);
    };
  }
  
  // 方法2:常规方法
  sayHiRegular() {
    console.log(`Hi, I'm ${this.name}`);
  }
  
  // 方法3:在构造函数中 bind
  constructor(name) {
    this.name = name;
    this.sayHiRegular = this.sayHiRegular.bind(this);
  }
}

const obj = new MyClass('Alice');
const { sayHiArrow, sayHiRegular } = obj;

sayHiArrow();   // Hi, I'm Alice(箭头函数保持 this)
sayHiRegular(); // undefined(丢失 this)

5.2 原型链

// 每个对象都有__proto__,指向它的原型
const arr = [1,2,3];
arr.__proto__ === Array.prototype// true
Array.prototype.__proto__ === Object.prototype// true
Object.prototype.__proto__ === null// true

// 实现继承
function Parent(name) {
  this.name = name;
}
Parent.prototype.sayHi = function() {
  console.log(`Hi, I'm ${this.name}`);
};

function Child(name, age) {
  Parent.call(this, name);  // 继承属性
  this.age = age;
}
Child.prototype = Object.create(Parent.prototype);  // 继承方法
Child.prototype.constructor = Child

5.3 事件循环(Event Loop)

console.log('1');  // 同步

setTimeout(() => {
  console.log('2');  // 宏任务
}, 0);

Promise.resolve().then(() => {
  console.log('3');  // 微任务
});

console.log('4');  // 同步

// 打印顺序:1, 4, 3, 2
// 同步 → 微任务 → 宏任务

6. 常用工具方法

6.1 防抖和接楼

一句话理解

防抖拖延症患者 - 一直催就一直推迟,不催了才开始干活
节流公交车司机 - 不管多少人等,固定时间发一班车

防抖(搜索框)

function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);  // 取消上次
    timer = setTimeout(() => fn.apply(this, args), delay); // 重新计时
  };
}
// 使用:input输入停止500ms后搜索
input.addEventListener('input', debounce(search, 500));

节流(滚动加载)

function throttle(fn, delay) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime > delay) {  // 时间到了才执行
      fn.apply(this, args);
      lastTime = now;
    }
  };
}
// 使用:滚动时每200ms加载一次
window.addEventListener('scroll', throttle(loadMore, 200));

6.2 深拷贝和浅拷贝

一句话理解

浅拷贝只复制第一层,内部对象还是共用
深拷贝完全复制所有层,新旧对象完全独立

核心区别

const original = {
  name: 'Alice',
  age: 25,
  skills: ['js', 'react'],  // 引用类型
  address: {
    city: '北京',
    zip: '100000'
  }
};
浅拷贝结果
const shallow = { ...original };

shallow.name = 'Bob';        // ✅ original.name 不变
shallow.skills.push('vue');  // ❌ original.skills 也被改了!
shallow.address.city = '上海'; // ❌ original.address.city 也被改了!

console.log(original.skills); // ['js', 'react', 'vue'] 😱
深拷贝结果
const deep = JSON.parse(JSON.stringify(original));

deep.name = 'Bob';            // ✅ original.name 不变
deep.skills.push('vue');      // ✅ original.skills 不变
deep.address.city = '上海';    // ✅ original.address.city 不变

console.log(original.skills); // ['js', 'react'] 😊

6.3 实现方式

浅拷贝方法

// 1. 扩展运算符
const copy1 = { ...obj };
const copyArr1 = [...arr];

// 2. Object.assign
const copy2 = Object.assign({}, obj);

// 3. 数组方法
const copyArr2 = arr.slice();
const copyArr3 = arr.concat();

// 4. 循环遍历
const copy4 = {};
for (let key in obj) {
  copy4[key] = obj[key];
}

深拷贝方法

// 1. JSON 方法(最简单,但有坑)
const copy1 = JSON.parse(JSON.stringify(obj));
// 缺点:不能复制函数、undefined、Symbol、循环引用

// 2. 递归实现
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  
  const clone = Array.isArray(obj) ? [] : {};
  
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key]);
    }
  }
  
  return clone;
}

// 3. 使用第三方库
// const copy = _.cloneDeep(obj);  // lodash
// const copy = structuredClone(obj); // 现代浏览器内置