JavaScript核心知识点详解
JavaScript是一门功能强大、应用广泛的编程语言,掌握其核心知识点对于前端开发至关重要。基础语法:数据类型、作用域、闭包、原型链和this指向,这些是JavaScript的核心基础,理解这些概念对于编写高质量代码至关重要。异步编程:回调函数、Promise、async/await三种异步模型,以及Event Loop的工作原理,包括宏任务和微任务的执行顺序。异步编程是JavaScript的重
JavaScript核心知识点详解
1. 基础语法
1.1 数据类型
JavaScript有7种原始数据类型和1种引用数据类型:
| 数据类型 | 描述 | 示例 |
|---|---|---|
| 原始类型 | 不可变,直接存储值 | |
String |
字符串 | "Hello"、'World' |
Number |
数值 | 42、3.14、NaN、Infinity |
Boolean |
布尔值 | true、false |
Null |
空值 | null |
Undefined |
未定义 | undefined |
Symbol |
唯一标识符 | Symbol('id') |
BigInt |
大整数 | 123n、BigInt(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.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 宏任务与微任务
| 任务类型 | 包含的异步操作 |
|---|---|
| 宏任务 | setTimeout、setInterval、setImmediate、I/O操作、UI渲染、script标签 |
| 微任务 | Promise.then/catch/finally、async/await、process.nextTick、MutationObserver |
2.2.3 Event Loop执行顺序
- 执行同步代码,将异步任务分别加入宏任务队列和微任务队列
- 同步代码执行完毕,执行栈为空
- 执行所有微任务队列中的任务,顺序执行
- 微任务队列执行完毕,检查是否需要UI渲染
- 从宏任务队列中取出一个任务执行
- 重复步骤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 箭头函数
使用=>语法定义函数,简化函数表达式,不绑定自己的this、arguments、super或new.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引入的模块化系统,允许将代码分割为独立的模块,通过import和export进行模块间的交互。
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(空值合并运算符,??)
空值合并运算符用于为可能为null或undefined的变量提供默认值,与||不同,仅当左侧为null或undefined时才返回右侧值。
代码示例:
// 与 || 的区别
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 实现原理
- 设置一个定时器,在指定时间后执行回调
- 如果在定时器触发前再次调用函数,清除旧定时器,设置新定时器
- 可选:支持立即执行(首次触发时立即执行,之后等待)
代码示例:
// 防抖函数实现
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 实现原理
- 记录上次执行时间
- 每次调用函数时,检查当前时间与上次执行时间的差值
- 如果差值大于等于指定时间间隔,则执行函数,更新上次执行时间
- 否则,跳过执行
代码示例:
// 节流函数实现
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+新特性和性能优化等内容,包括:
-
基础语法:数据类型、作用域、闭包、原型链和this指向,这些是JavaScript的核心基础,理解这些概念对于编写高质量代码至关重要。
-
异步编程:回调函数、Promise、async/await三种异步模型,以及Event Loop的工作原理,包括宏任务和微任务的执行顺序。异步编程是JavaScript的重点和难点,掌握这些知识对于处理网络请求、定时器等异步操作非常重要。
-
ES6+新特性:箭头函数、解构赋值、模板字符串、类、模块化、Set/Map、Symbol、扩展运算符、剩余参数、可选链和空值合并运算符。这些新特性极大地提高了JavaScript的开发效率和代码可读性。
-
性能优化:防抖和节流的概念、实现原理和应用场景,这些技术可以有效减少不必要的函数调用,提高页面性能。
通过深入理解和应用这些核心知识点,开发者可以编写更加高效、可维护和性能优良的JavaScript代码,应对各种复杂的前端开发场景。
更多推荐
所有评论(0)