JavaScript核心知识点详解

1. 基础语法

1.1 数据类型

JavaScript有7种原始数据类型和1种引用数据类型:

数据类型 描述 示例
原始类型 不可变,直接存储值
String 字符串 "Hello"'World'
Number 数值 423.14NaNInfinity
Boolean 布尔值 truefalse
Null 空值 null
Undefined 未定义 undefined
Symbol 唯一标识符 Symbol('id')
BigInt 大整数 123nBigInt(456)
引用类型 可变,存储引用地址
Object 对象 { name: "Alice" }[1, 2, 3]function() {}

类型判断方法:

// typeof 操作符
console.log(typeof "string"); // "string"
console.log(typeof 42); // "number"
console.log(typeof true); // "boolean"
console.log(typeof null); // "object" (历史遗留问题)
console.log(typeof undefined); // "undefined"
console.log(typeof Symbol()); // "symbol"
console.log(typeof 123n); // "bigint"
console.log(typeof {}); // "object"
console.log(typeof []); // "object"
console.log(typeof function() {}); // "function"

// instanceof 操作符 (判断引用类型)
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(function() {} instanceof Function); // true

// Array.isArray() (判断数组)
console.log(Array.isArray([])); // true
console.log(Array.isArray({})); // false

// Object.prototype.toString.call() (最准确的类型判断)
console.log(Object.prototype.toString.call("string")); // "[object String]"
console.log(Object.prototype.toString.call(42)); // "[object Number]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call([])); // "[object Array]"

1.2 作用域

作用域决定了变量和函数的可访问范围,分为全局作用域和局部作用域。

1.2.1 作用域类型
  • 全局作用域:在代码任何地方都能访问的变量
  • 函数作用域:在函数内部定义的变量,仅在函数内部可访问
  • 块级作用域:ES6引入,由{}包裹的代码块,使用let/const声明

代码示例:

// 全局作用域
const globalVar = "global";

function func() {
  // 函数作用域
  const funcVar = "function";
  
  if (true) {
    // 块级作用域 (ES6+)
    let blockVar = "block";
    const blockConst = "constant";
    var varVar = "var"; // var 不支持块级作用域
    
    console.log(blockVar); // "block"
    console.log(globalVar); // "global"
  }
  
  console.log(funcVar); // "function"
  console.log(varVar); // "var" (var 声明提升)
  console.log(blockVar); // ReferenceError: blockVar is not defined
}

func();
console.log(globalVar); // "global"
console.log(funcVar); // ReferenceError: funcVar is not defined
1.2.2 作用域链

当访问一个变量时,JavaScript引擎会沿着作用域链向上查找,直到找到该变量或到达全局作用域。

代码示例:

const globalVar = "global";

function outer() {
  const outerVar = "outer";
  
  function inner() {
    const innerVar = "inner";
    console.log(innerVar); // "inner" (当前作用域)
    console.log(outerVar); // "outer" (外部作用域)
    console.log(globalVar); // "global" (全局作用域)
    console.log(nonExistentVar); // ReferenceError (未找到)
  }
  
  inner();
}

outer();

1.3 闭包

闭包是指有权访问另一个函数作用域中变量的函数,通常在嵌套函数中形成。

1.3.1 闭包的形成条件
  1. 函数嵌套
  2. 内部函数引用外部函数的变量
  3. 外部函数执行后,内部函数仍能访问外部函数的变量
1.3.2 闭包的特性
  • 延长外部函数变量的生命周期
  • 实现数据私有化
  • 可能导致内存泄漏(需合理使用)

代码示例:

// 示例1:基本闭包
function createCounter() {
  let count = 0; // 外部函数变量
  
  return function() {
    count++; // 内部函数引用外部变量
    return count;
  };
}

const counter = createCounter(); // 外部函数执行完毕
console.log(counter()); // 1 (闭包仍能访问count)
console.log(counter()); // 2
console.log(counter()); // 3

// 示例2:数据私有化
function createPerson(name) {
  let _age = 0; // 私有变量
  
  return {
    getName: function() {
      return name;
    },
    getAge: function() {
      return _age;
    },
    setAge: function(age) {
      if (age > 0 && age < 150) {
        _age = age;
      }
    }
  };
}

const person = createPerson("Alice");
person.setAge(30);
console.log(person.getName()); // "Alice"
console.log(person.getAge()); // 30
console.log(person._age); // undefined (无法直接访问私有变量)

1.4 原型链

JavaScript通过原型链实现对象之间的继承关系,每个对象都有一个__proto__属性指向其原型对象,原型对象也有自己的原型,形成链式结构。

1.4.1 核心概念
  • 原型对象(prototype):函数特有的属性,用于定义实例共享的方法和属性
  • __proto__:对象的隐式原型,指向其构造函数的原型对象
  • constructor:原型对象上的属性,指向构造函数本身

代码示例:

// 构造函数
function Person(name) {
  this.name = name;
}

// 在原型上定义方法
Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name}`);
};

// 创建实例
const alice = new Person("Alice");
const bob = new Person("Bob");

// 实例调用原型上的方法
alice.sayHello(); // "Hello, my name is Alice"
bob.sayHello(); // "Hello, my name is Bob"

// 原型链关系
console.log(alice.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true (原型链终点)
console.log(alice.constructor === Person); // true
console.log(Person.prototype.constructor === Person); // true
1.4.2 原型链继承

通过原型链实现对象继承:

代码示例:

// 父类
function Animal(name) {
  this.name = name;
  this.eat = function() {
    console.log(`${this.name} is eating`);
  };
}

// 子类
function Dog(name, breed) {
  Animal.call(this, name); // 调用父类构造函数,继承属性
  this.breed = breed;
}

// 设置原型链,继承方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复constructor指向

// 子类添加自己的方法
Dog.prototype.bark = function() {
  console.log(`${this.name} is barking`);
};

// 创建实例
const dog = new Dog("Buddy", "Golden Retriever");
dog.eat(); // "Buddy is eating" (继承自Animal)
dog.bark(); // "Buddy is barking" (Dog自身方法)
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true

1.5 this指向

this是JavaScript中的关键字,其指向在函数定义时不确定,取决于函数的调用方式。

1.5.1 this指向规则
调用方式 this指向 示例
普通函数调用 全局对象(浏览器:window,Node.js:global) func()
对象方法调用 调用方法的对象 obj.method()
构造函数调用 新创建的实例对象 new Constructor()
apply/call/bind调用 指定的对象 func.call(obj)
箭头函数 定义时的外层作用域this () => {}

代码示例:

// 1. 普通函数调用
function normalFunc() {
  console.log(this); // 浏览器:window,Node.js:global
}
normalFunc();

// 2. 对象方法调用
const obj = {
  name: "Object",
  method: function() {
    console.log(this.name); // "Object"
  }
};
obj.method();

// 3. 构造函数调用
function Person(name) {
  this.name = name;
  console.log(this); // Person实例
}
const person = new Person("Alice");

// 4. apply/call/bind调用
function callFunc() {
  console.log(this.name);
}
const obj1 = { name: "Obj1" };
const obj2 = { name: "Obj2" };
callFunc.call(obj1); // "Obj1"
callFunc.apply(obj2); // "Obj2"
const boundFunc = callFunc.bind(obj1);
boundFunc(); // "Obj1"

// 5. 箭头函数
const arrowObj = {
  name: "ArrowObj",
  normalMethod: function() {
    const arrowFunc = () => {
      console.log(this.name); // "ArrowObj" (继承外层this)
    };
    arrowFunc();
  }
};
arrowObj.normalMethod();

2. 异步编程

2.1 异步模型

2.1.1 回调函数(Callback)

早期的异步编程模式,通过将回调函数作为参数传递给异步函数,在异步操作完成后执行。

代码示例:

// 回调地狱示例
function fetchData(url, callback) {
  setTimeout(() => {
    callback(null, `Data from ${url}`);
  }, 1000);
}

fetchData("url1", (err, data1) => {
  if (err) throw err;
  console.log(data1);
  
  fetchData("url2", (err, data2) => {
    if (err) throw err;
    console.log(data2);
    
    fetchData("url3", (err, data3) => {
      if (err) throw err;
      console.log(data3);
      // ... 继续嵌套
    });
  });
});

问题:

  • 回调地狱,代码可读性差
  • 错误处理复杂
  • 难以实现并发和取消操作
2.1.2 Promise

ES6引入的异步编程解决方案,通过链式调用解决回调地狱问题。

Promise的三种状态:

  • pending:初始状态,既不是成功也不是失败
  • fulfilled:操作成功完成
  • rejected:操作失败

代码示例:

// 创建Promise
function fetchData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url) {
        resolve(`Data from ${url}`);
      } else {
        reject(new Error("Invalid URL"));
      }
    }, 1000);
  });
}

// 使用Promise
fetchData("url1")
  .then(data1 => {
    console.log(data1);
    return fetchData("url2"); // 链式调用
  })
  .then(data2 => {
    console.log(data2);
    return fetchData("url3");
  })
  .then(data3 => {
    console.log(data3);
  })
  .catch(err => {
    console.error(err); // 统一错误处理
  })
  .finally(() => {
    console.log("All requests completed"); // 无论成功失败都会执行
  });
2.1.3 async/await

ES2017引入,基于Promise的语法糖,使异步代码看起来像同步代码。

代码示例:

// async函数返回Promise
async function fetchAllData() {
  try {
    const data1 = await fetchData("url1"); // 等待Promise完成
    console.log(data1);
    
    const data2 = await fetchData("url2");
    console.log(data2);
    
    const data3 = await fetchData("url3");
    console.log(data3);
    
    console.log("All requests completed");
  } catch (err) {
    console.error(err); // 统一错误处理
  }
}

fetchAllData();

// 并发请求
async function fetchConcurrently() {
  try {
    // 同时发起请求,等待所有完成
    const [data1, data2, data3] = await Promise.all([
      fetchData("url1"),
      fetchData("url2"),
      fetchData("url3")
    ]);
    
    console.log(data1, data2, data3);
  } catch (err) {
    console.error(err);
  }
}

fetchConcurrently();

2.2 Event Loop

Event Loop是JavaScript处理异步操作的核心机制,负责协调事件、用户交互、脚本执行、UI渲染和网络请求等。

2.2.1 执行栈与任务队列
  • 执行栈:同步代码执行的地方,遵循后进先出原则
  • 任务队列:存储异步任务,分为宏任务队列和微任务队列
2.2.2 宏任务与微任务
任务类型 包含的异步操作
宏任务 setTimeoutsetIntervalsetImmediate、I/O操作、UI渲染、script标签
微任务 Promise.then/catch/finallyasync/awaitprocess.nextTickMutationObserver
2.2.3 Event Loop执行顺序
  1. 执行同步代码,将异步任务分别加入宏任务队列和微任务队列
  2. 同步代码执行完毕,执行栈为空
  3. 执行所有微任务队列中的任务,顺序执行
  4. 微任务队列执行完毕,检查是否需要UI渲染
  5. 从宏任务队列中取出一个任务执行
  6. 重复步骤3-5,形成循环

代码示例:

console.log("1. 同步代码开始");

setTimeout(() => {
  console.log("5. 宏任务 setTimeout");
}, 0);

Promise.resolve()
  .then(() => {
    console.log("3. 微任务 Promise.then 1");
    return Promise.resolve();
  })
  .then(() => {
    console.log("4. 微任务 Promise.then 2");
  });

console.log("2. 同步代码结束");

// 输出顺序:1 → 2 → 3 → 4 → 5

2.3 异步处理工具

2.3.1 Promise.all

等待所有Promise完成,返回结果数组;如果有一个Promise失败,立即返回失败原因。

代码示例:

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.reject(new Error("Error"));

// 所有成功
Promise.all([promise1, promise2])
  .then(results => {
    console.log(results); // [1, 2]
  })
  .catch(err => {
    console.error(err);
  });

// 有失败
Promise.all([promise1, promise3])
  .then(results => {
    console.log(results); // 不会执行
  })
  .catch(err => {
    console.error(err); // Error: Error
  });
2.3.2 Promise.race

返回第一个完成的Promise结果,无论成功或失败。

代码示例:

const fastPromise = new Promise(resolve => {
  setTimeout(() => resolve("Fast"), 100);
});

const slowPromise = new Promise(resolve => {
  setTimeout(() => resolve("Slow"), 200);
});

Promise.race([fastPromise, slowPromise])
  .then(result => {
    console.log(result); // "Fast"
  })
  .catch(err => {
    console.error(err);
  });

// 用于设置请求超时
const request = new Promise((resolve, reject) => {
  setTimeout(() => resolve("Request Success"), 500);
});

const timeout = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("Timeout")), 300);
});

Promise.race([request, timeout])
  .then(result => {
    console.log(result); // 不会执行,因为timeout先完成
  })
  .catch(err => {
    console.error(err); // Error: Timeout
  });
2.3.3 Promise.allSettled

等待所有Promise完成,返回包含每个Promise结果的对象数组,无论成功或失败。

代码示例:

const promise1 = Promise.resolve(1);
const promise2 = Promise.reject(new Error("Error"));

Promise.allSettled([promise1, promise2])
  .then(results => {
    console.log(results);
    /*
    [
      { status: "fulfilled", value: 1 },
      { status: "rejected", reason: Error: Error }
    ]
    */
  });

3. ES6+新特性

3.1 箭头函数

使用=>语法定义函数,简化函数表达式,不绑定自己的thisargumentssupernew.target

代码示例:

// 基本语法
const add = (a, b) => a + b;
console.log(add(1, 2)); // 3

// 单参数可省略括号
const double = x => x * 2;
console.log(double(4)); // 8

// 多语句需使用大括号和return
const multiply = (a, b) => {
  const result = a * b;
  return result;
};
console.log(multiply(3, 4)); // 12

// 对象字面量需使用括号包裹
const createObj = (name, age) => ({ name, age });
console.log(createObj("Alice", 30)); // { name: "Alice", age: 30 }

// 箭头函数继承外层this
const arrowThis = {
  name: "ArrowThis",
  func: function() {
    const arrow = () => {
      console.log(this.name); // "ArrowThis"
    };
    arrow();
  }
};
arrowThis.func();

3.2 解构赋值

从数组或对象中提取值,赋给变量。

代码示例:

// 数组解构
const [a, b, ...rest] = [1, 2, 3, 4, 5];
console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3, 4, 5]

// 对象解构
const { name, age, city = "Unknown" } = { name: "Alice", age: 30 };
console.log(name); // "Alice"
console.log(age); // 30
console.log(city); // "Unknown" (默认值)

// 重命名
const { name: userName, age: userAge } = { name: "Bob", age: 25 };
console.log(userName); // "Bob"
console.log(userAge); // 25

// 函数参数解构
function printUser({ name, age }) {
  console.log(`${name} is ${age} years old`);
}
printUser({ name: "Charlie", age: 35 }); // "Charlie is 35 years old"

// 嵌套解构
const nestedObj = {
  user: {
    name: "Dave",
    address: {
      city: "New York",
      zip: "10001"
    }
  }
};
const { user: { name, address: { city } } } = nestedObj;
console.log(name); // "Dave"
console.log(city); // "New York"

3.3 模板字符串

使用反引号(`)定义字符串,支持多行字符串和变量插值。

代码示例:

// 基本用法
const name = "Alice";
const greeting = `Hello, ${name}!`;
console.log(greeting); // "Hello, Alice!"

// 多行字符串
const multiLine = `
  This is a
  multi-line
  string.
`;
console.log(multiLine);

// 表达式插值
const a = 10;
const b = 20;
const sum = `The sum of ${a} and ${b} is ${a + b}.`;
console.log(sum); // "The sum of 10 and 20 is 30."

// 函数调用
function getGreeting() {
  return "Hello";
}
const funcGreeting = `${getGreeting()}, ${name}!`;
console.log(funcGreeting); // "Hello, Alice!"

// 标签模板
function tag(strings, ...values) {
  console.log(strings); // ["The number is ", "."]
  console.log(values); // [42]
  return `${strings[0]}${values[0]}${strings[1]}`;
}
const tagged = tag`The number is ${42}.`;
console.log(tagged); // "The number is 42."

3.4 类(Class)

ES6引入的类语法,是基于原型继承的语法糖,使面向对象编程更加直观。

代码示例:

// 基本类定义
class Person {
  // 构造函数
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  // 实例方法
  sayHello() {
    console.log(`Hello, my name is ${this.name}`);
  }
  
  // 静态方法(只能通过类调用)
  static createPerson(name, age) {
    return new Person(name, age);
  }
  
  // getter方法
  get info() {
    return `${this.name} is ${this.age} years old`;
  }
  
  // setter方法
  set age(newAge) {
    if (newAge > 0 && newAge < 150) {
      this._age = newAge;
    }
  }
  
  get age() {
    return this._age;
  }
}

// 类继承
class Student extends Person {
  constructor(name, age, grade) {
    super(name, age); // 调用父类构造函数
    this.grade = grade;
  }
  
  // 重写父类方法
  sayHello() {
    super.sayHello(); // 调用父类方法
    console.log(`I'm in grade ${this.grade}`);
  }
}

// 使用类
const person1 = new Person("Alice", 30);
person1.sayHello(); // "Hello, my name is Alice"
console.log(person1.info); // "Alice is 30 years old"

const student1 = new Student("Bob", 15, 9);
student1.sayHello(); // "Hello, my name is Bob" + "I'm in grade 9"

// 使用静态方法
const person2 = Person.createPerson("Charlie", 25);
console.log(person2.info); // "Charlie is 25 years old"

3.5 模块化(import/export)

ES6引入的模块化系统,允许将代码分割为独立的模块,通过importexport进行模块间的交互。

3.5.1 导出(Export)
// module.js
// 1. 命名导出
export const PI = 3.14159;

export function add(a, b) {
  return a + b;
}

export class Person {
  constructor(name) {
    this.name = name;
  }
}

// 2. 默认导出(每个模块只能有一个默认导出)
export default function multiply(a, b) {
  return a * b;
}

// 3. 导出列表
const subtract = (a, b) => a - b;
const divide = (a, b) => a / b;
export { subtract, divide };
3.5.2 导入(Import)
// main.js
// 1. 导入默认导出
import multiply from './module.js';

// 2. 导入命名导出
import { PI, add, Person } from './module.js';

// 3. 导入所有命名导出
import * as utils from './module.js';

// 4. 混合导入
import multiply, { PI, add } from './module.js';

// 5. 重命名导入
import { add as sum } from './module.js';

// 使用导入的内容
console.log(multiply(2, 3)); // 6
console.log(add(1, 2)); // 3
console.log(PI); // 3.14159
console.log(utils.subtract(5, 3)); // 2
console.log(sum(3, 4)); // 7
const person = new Person("Alice");

3.6 Set/Map数据结构

3.6.1 Set

Set是一种无序的集合,成员唯一,没有重复值。

代码示例:

// 创建Set
const set1 = new Set();
const set2 = new Set([1, 2, 3, 3, 4]); // 自动去重
console.log(set2); // Set { 1, 2, 3, 4 }

// 添加元素
set1.add(1);
set1.add(2);
set1.add(2); // 重复元素不会被添加
console.log(set1); // Set { 1, 2 }

// 删除元素
set1.delete(1);
console.log(set1); // Set { 2 }

// 检查元素是否存在
console.log(set1.has(2)); // true
console.log(set1.has(1)); // false

// 获取元素数量
console.log(set1.size); // 1

// 遍历Set
set2.forEach(value => {
  console.log(value); // 1, 2, 3, 4
});

for (const value of set2) {
  console.log(value); // 1, 2, 3, 4
}

// 转换为数组
const arr = [...set2];
console.log(arr); // [1, 2, 3, 4]

// 使用场景:数组去重
const duplicateArr = [1, 2, 2, 3, 4, 4, 5];
const uniqueArr = [...new Set(duplicateArr)];
console.log(uniqueArr); // [1, 2, 3, 4, 5]
3.6.2 Map

Map是一种键值对集合,键可以是任意类型(包括对象、函数等),键值对按插入顺序排列。

代码示例:

// 创建Map
const map1 = new Map();
const map2 = new Map([
  ["name", "Alice"],
  [1, "number"],
  [{ key: "objKey" }, "object"]
]);

// 添加键值对
map1.set("name", "Bob");
map1.set("age", 30);
map1.set("age", 31); // 覆盖已存在的键

// 获取值
console.log(map1.get("name")); // "Bob"
console.log(map1.get("age")); // 31

// 删除键值对
map1.delete("age");
console.log(map1); // Map { "name" => "Bob" }

// 检查键是否存在
console.log(map1.has("name")); // true

// 获取键值对数量
console.log(map1.size); // 1

// 遍历Map
map2.forEach((value, key) => {
  console.log(`${key} => ${value}`);
});

for (const [key, value] of map2) {
  console.log(`${key} => ${value}`);
}

// 获取所有键和值
console.log([...map2.keys()]); // ["name", 1, { key: "objKey" }]
console.log([...map2.values()]); // ["Alice", "number", "object"]
console.log([...map2.entries()]); // [["name", "Alice"], [1, "number"], [{ key: "objKey" }, "object"]]

// 使用场景:存储对象键
const objKey = { id: 1 };
const map = new Map();
map.set(objKey, "value for object key");
console.log(map.get(objKey)); // "value for object key"

3.7 Symbol类型

Symbol是一种唯一的、不可变的数据类型,用于创建对象的唯一属性名。

代码示例:

// 创建Symbol
const sym1 = Symbol();
const sym2 = Symbol("description"); // 可选描述
const sym3 = Symbol("description");

console.log(sym2); // Symbol(description)
console.log(sym2 === sym3); // false (每个Symbol都是唯一的)

// Symbol作为对象属性名
const obj = {};
const symKey = Symbol("symKey");
obj[symKey] = "symbol value";
obj["stringKey"] = "string value";

console.log(obj[symKey]); // "symbol value"
console.log(obj.stringKey); // "string value"

// Symbol属性不会被for...in遍历
for (const key in obj) {
  console.log(key); // "stringKey" (Symbol属性不会被遍历)
}

// 获取对象的所有Symbol属性
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(symKey)]

// 全局Symbol注册表
const globalSym1 = Symbol.for("globalSymbol");
const globalSym2 = Symbol.for("globalSymbol");
console.log(globalSym1 === globalSym2); // true (同一全局Symbol)

// 获取Symbol的描述
console.log(Symbol.keyFor(globalSym1)); // "globalSymbol"
console.log(Symbol.keyFor(sym1)); // undefined (非全局Symbol)

// 内置Symbol
// Symbol.iterator:对象的默认迭代器
// Symbol.toStringTag:对象的toString()返回值
// Symbol.hasInstance:instanceof运算符的内部实现
// 等等...

3.8 扩展运算符(…)

扩展运算符用于展开数组或对象,可用于数组字面量、函数调用和对象字面量中。

代码示例:

// 数组扩展
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// 合并数组
const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5, 6]

// 复制数组
const copied = [...arr1];
console.log(copied); // [1, 2, 3]

// 函数参数
function sum(a, b, c) {
  return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

// 与解构结合
const [first, ...rest] = arr1;
console.log(first); // 1
console.log(rest); // [2, 3]

// 对象扩展
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };

// 合并对象(后面的属性覆盖前面的)
const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // { a: 1, b: 3, c: 4 }

// 复制对象
const copiedObj = { ...obj1 };
console.log(copiedObj); // { a: 1, b: 2 }

// 添加新属性
const newObj = { ...obj1, d: 5 };
console.log(newObj); // { a: 1, b: 2, d: 5 }

// 扩展运算符只能复制可枚举的自有属性
const obj3 = Object.create({ inherited: "inherited" });
obj3.own = "own";
const copiedObj2 = { ...obj3 };
console.log(copiedObj2); // { own: "own" } (继承属性不会被复制)

3.9 剩余参数

剩余参数与扩展运算符语法相同,但作用相反,用于收集函数的剩余参数为一个数组。

代码示例:

// 基本用法
function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15

// 与普通参数结合(剩余参数必须是最后一个参数)
function multiply(multiplier, ...numbers) {
  return numbers.map(num => num * multiplier);
}
console.log(multiply(2, 1, 2, 3)); // [2, 4, 6]

// 解构剩余参数
function logInfo(name, age, ...rest) {
  console.log(`Name: ${name}, Age: ${age}`);
  console.log(`Rest: ${rest}`);
}
logInfo("Alice", 30, "Engineer", "Female", "New York");
// Name: Alice, Age: 30
// Rest: Engineer,Female,New York

// 箭头函数使用剩余参数
const arrowSum = (...numbers) => numbers.reduce((total, num) => total + num, 0);
console.log(arrowSum(1, 2, 3)); // 6

3.10 Optional Chaining(可选链,?.)

可选链运算符用于安全地访问嵌套对象的属性,避免因中间属性不存在而抛出错误。

代码示例:

// 基本用法
const obj = {
  user: {
    name: "Alice",
    address: {
      city: "New York"
    }
  }
};

// 传统方式
const city1 = obj && obj.user && obj.user.address && obj.user.address.city;
console.log(city1); // "New York"

// 可选链方式
const city2 = obj?.user?.address?.city;
console.log(city2); // "New York"

// 访问不存在的属性
const zip = obj?.user?.address?.zip;
console.log(zip); // undefined (不会抛出错误)

// 调用可选方法
const obj2 = {
  method: function() {
    return "Method called";
  }
};
const result1 = obj2?.method?.();
console.log(result1); // "Method called"

const obj3 = {};
const result2 = obj3?.method?.();
console.log(result2); // undefined (不会抛出错误)

// 访问数组元素
const arr = [1, 2, 3];
const item1 = arr?.[0];
console.log(item1); // 1

const emptyArr = null;
const item2 = emptyArr?.[0];
console.log(item2); // undefined (不会抛出错误)

// 与空值合并运算符结合使用
const user = {
  name: "Bob"
  // 没有address属性
};
const userCity = user?.address?.city ?? "Unknown";
console.log(userCity); // "Unknown"

3.11 Nullish Coalescing(空值合并运算符,??)

空值合并运算符用于为可能为nullundefined的变量提供默认值,与||不同,仅当左侧为nullundefined时才返回右侧值。

代码示例:

// 与 || 的区别
const falsyValues = [0, "", false, null, undefined, NaN];

// || 运算符(会将0、""、false视为假值)
falsyValues.forEach(value => {
  console.log(`value || "default": ${value || "default"}`);
});
// 输出:
// 0 || "default": default
// "" || "default": default
// false || "default": default
// null || "default": default
// undefined || "default": default
// NaN || "default": default

// ?? 运算符(仅null和undefined视为假值)
falsyValues.forEach(value => {
  console.log(`value ?? "default": ${value ?? "default"}`);
});
// 输出:
// 0 ?? "default": 0
// "" ?? "default": 
// false ?? "default": false
// null ?? "default": default
// undefined ?? "default": default
// NaN ?? "default": NaN

// 实际应用场景
const config = {
  timeout: 0, // 合法值,不应该被替换
  retries: null, // 应该使用默认值
  maxAttempts: undefined // 应该使用默认值
};

const timeout = config.timeout ?? 5000; // 0 (正确,保留合法值)
const retries = config.retries ?? 3; // 3 (正确,使用默认值)
const maxAttempts = config.maxAttempts ?? 5; // 5 (正确,使用默认值)

console.log(timeout, retries, maxAttempts); // 0 3 5

4. 性能优化

4.1 防抖(Debounce)

防抖是指在事件触发后,等待一定时间再执行回调函数,如果在等待时间内再次触发,则重新计时。

4.1.1 应用场景
  • 搜索框输入联想
  • 窗口大小调整
  • 滚动事件处理
  • 表单输入验证
4.1.2 实现原理
  1. 设置一个定时器,在指定时间后执行回调
  2. 如果在定时器触发前再次调用函数,清除旧定时器,设置新定时器
  3. 可选:支持立即执行(首次触发时立即执行,之后等待)

代码示例:

// 防抖函数实现
function debounce(func, wait, immediate = false) {
  let timeout;
  
  return function executedFunction(...args) {
    const context = this;
    
    // 清除之前的定时器
    clearTimeout(timeout);
    
    // 立即执行逻辑
    if (immediate) {
      const callNow = !timeout;
      timeout = setTimeout(() => {
        timeout = null;
      }, wait);
      
      if (callNow) func.apply(context, args);
    } else {
      // 延迟执行逻辑
      timeout = setTimeout(() => {
        func.apply(context, args);
      }, wait);
    }
  };
}

// 使用示例:搜索框防抖
const searchInput = document.getElementById("search");

function handleSearch(query) {
  console.log(`Searching for: ${query}`);
  // 实际搜索逻辑...
}

const debouncedSearch = debounce(handleSearch, 300);

searchInput.addEventListener("input", (e) => {
  debouncedSearch(e.target.value);
});

// 使用示例:窗口大小调整防抖
function handleResize() {
  console.log(`Window size: ${window.innerWidth}x${window.innerHeight}`);
  // 实际调整逻辑...
}

const debouncedResize = debounce(handleResize, 200);
window.addEventListener("resize", debouncedResize);

4.2 节流(Throttle)

节流是指在一定时间内,只允许函数执行一次,避免函数被频繁调用。

4.2.1 应用场景
  • 滚动事件监听
  • 鼠标移动事件
  • 游戏中的射击、跳跃等操作
  • 高频点击按钮
4.2.2 实现原理
  1. 记录上次执行时间
  2. 每次调用函数时,检查当前时间与上次执行时间的差值
  3. 如果差值大于等于指定时间间隔,则执行函数,更新上次执行时间
  4. 否则,跳过执行

代码示例:

// 节流函数实现
function throttle(func, limit) {
  let inThrottle;
  
  return function executedFunction(...args) {
    const context = this;
    
    if (!inThrottle) {
      func.apply(context, args);
      inThrottle = true;
      
      setTimeout(() => {
        inThrottle = false;
      }, limit);
    }
  };
}

// 更精确的节流实现(基于时间戳)
function throttleTimestamp(func, limit) {
  let lastFunc;
  let lastRan;
  
  return function executedFunction(...args) {
    const context = this;
    
    if (!lastRan) {
      func.apply(context, args);
      lastRan = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(() => {
        if (Date.now() - lastRan >= limit) {
          func.apply(context, args);
          lastRan = Date.now();
        }
      }, limit - (Date.now() - lastRan));
    }
  };
}

// 使用示例:滚动事件节流
function handleScroll() {
  console.log(`Scroll position: ${window.scrollY}`);
  // 实际滚动逻辑...
}

const throttledScroll = throttle(handleScroll, 200);
window.addEventListener("scroll", throttledScroll);

// 使用示例:鼠标移动事件节流
function handleMouseMove(e) {
  console.log(`Mouse position: ${e.clientX}, ${e.clientY}`);
  // 实际鼠标移动逻辑...
}

const throttledMouseMove = throttleTimestamp(handleMouseMove, 100);
document.addEventListener("mousemove", throttledMouseMove);

总结

JavaScript是一门功能强大、应用广泛的编程语言,掌握其核心知识点对于前端开发至关重要。本文详细介绍了JavaScript的基础语法、异步编程、ES6+新特性和性能优化等内容,包括:

  1. 基础语法:数据类型、作用域、闭包、原型链和this指向,这些是JavaScript的核心基础,理解这些概念对于编写高质量代码至关重要。

  2. 异步编程:回调函数、Promise、async/await三种异步模型,以及Event Loop的工作原理,包括宏任务和微任务的执行顺序。异步编程是JavaScript的重点和难点,掌握这些知识对于处理网络请求、定时器等异步操作非常重要。

  3. ES6+新特性:箭头函数、解构赋值、模板字符串、类、模块化、Set/Map、Symbol、扩展运算符、剩余参数、可选链和空值合并运算符。这些新特性极大地提高了JavaScript的开发效率和代码可读性。

  4. 性能优化:防抖和节流的概念、实现原理和应用场景,这些技术可以有效减少不必要的函数调用,提高页面性能。

通过深入理解和应用这些核心知识点,开发者可以编写更加高效、可维护和性能优良的JavaScript代码,应对各种复杂的前端开发场景。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐