地址:前端面试题库
Number
(数字): 用于表示数值,可以是整数或浮点数。例如:42、3.14。String
(字符串): 用于表示文本数据,可以用单引号、双引号或反引号括起来。例如:"Hello"、'World'、"JavaScript"
。Boolean
(布尔): 用于表示逻辑值,只有两个可能的值:true和false。Undefined
(未定义): 表示变量声明了但没有赋值,或者访问不存在的属性时返回的值。Null
(空值): 表示一个空值或者不存在的对象。Symbol
(符号): 是ES6引入的一种数据类型,用于创建唯一的、不可变的值,通常用于对象属性的唯一标识符。BigInt
(大整数): 也是ES11引入的一种数据类型,用于表示任意精度的整数,可以处理超出Number类型表示范围的整数。Object
(对象): 用于表示复杂数据结构,可以包含多个键值对,每个键值对都是属性名和对应的值组成。这些数据类型之间的区别在于它们的值特点、存储方式和操作方式
。例如,Number和String类型可以进行数学运算和字符串操作,Boolean类型用于逻辑判断,Object类型用于组织和存储数据等。理解这些数据类型的特点,有助于编写更清晰、可维护的JavaScript代码。
typeof
?操作符:例如typeof 42; // "number"
instanceof
?操作符:例如let arr = []; arr instanceof Array; // true
Object.prototype.toString.call()
方法:例如Object.prototype.toString.call("Hello"); // "[object String]"
constructor
方法:例如console.log(('str').constructor === String); // true
Array.isArray()
方法:该方法用于判断是否为数组Array.isArray([]); // true
undefined
:
undefined 表示一个声明了但没有被赋值的变量,或者访问一个不存在的属性或变量时返回的值。
当声明一个变量但未初始化时,它的值默认为 undefined。
函数没有返回值时,默认返回 undefined。
在代码中,不要主动将变量赋值为 undefined,而应该让它自然成为未赋值状态。null
:
null 表示一个明确的、空的值,表示此处应该有一个值,但是当前还没有。它是一个赋值操作,表示将变量设置为空值。
null 通常用来显示地表示变量被清空了,或者某个属性被清空了。
在代码中,可以使用 null 来显式地指示变量或属性为空。
其他值转字符串
(Object.prototype.toString())
来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。其他值转数字
其他值转布尔
以下这些是假值: ? undefined ? null ? false ? +0、-0 和 NaN ? ""
JavaScript 中的类型转换分为显式类型转换(Explicit Type Conversion,也称为类型转换或类型强制转换)和隐式类型转换(Implicit Type Conversion,也称为类型转换或类型强制转换)。显式类型转换:
显式类型转换是通过代码明确地使用一些函数或操作符来将一个数据类型转换为另一个数据类型。这种转换是由开发人员手动进行的,以满足特定的需求。例如:
let num = 42;
let str = String(num); // 显式地将数字转换为字符串
let bool = Boolean(str); // 显式地将字符串转换为布尔值
隐式类型转换:
隐式类型转换是在操作过程中自动发生的,JavaScript 在需要不同类型的值时,会根据规则自动执行类型转换。这种转换不需要显式的开发人员干预,JavaScript 引擎会自动处理。例如:
let num = 42;
let str = num + ""; // 隐式地将数字转换为字符串
let bool = !num; // 隐式地将数字转换为布尔值
区别:
显式类型转换是由开发人员明确地编写代码来执行的,使用特定的函数或操作符。
隐式类型转换是在表达式求值过程中自动执行的,由 JavaScript 引擎根据表达式的操作和数据类型进行推断。
显式类型转换可以让开发人员在需要的时候有更精确的控制,确保类型转换的结果符合预期。
隐式类型转换是自动发生的,有时可能导致意外的结果,需要开发人员注意避免潜在的问题。
显式类型转换通常会使代码更加清晰,因为它明确地表达了开发人员的意图。
隐式类型转换可能会让代码更加简洁,但有时可能降低代码的可读性,需要注意平衡。
JavaScript 的包装类型是指基本数据类型(例如 number、string、boolean)在一些特定场景下会被自动转换为对应的包装对象,从而可以使用对象的方法和属性。这种自动转换是临时性的,仅在需要调用对象方法或属性时发生,操作完成后又会自动转换回基本数据类型。这种特性可以让基本数据类型在某些情况下表现得像对象。
JavaScript 中的包装类型包括:
举例说明:
let num = 42;
let str = "Hello";
let bool = true;
console.log(typeof num); // "number"
console.log(typeof str); // "string"
console.log(typeof bool); // "boolean"
// 使用包装类型的方法和属性
let numObj = new Number(num);
console.log(numObj.toFixed(2)); // 调用 Number 对象的 toFixed() 方法
let strObj = new String(str);
console.log(strObj.length); // 调用 String 对象的 length 属性
let boolObj = new Boolean(bool);
console.log(boolObj.valueOf()); // 调用 Boolean 对象的 valueOf() 方法
地址:前端面试题库
JavaScript 提供了许多内置对象,这些对象在语言中默认可用,无需额外导入或加载。以下是一些常见的 JavaScript 内置对象:
这只是内置对象的一部分。每个内置对象都有自己的方法和属性,用于特定的操作和功能。了解这些内置对象和它们的用法,对于编写 JavaScript 代码非常有帮助。
new 操作符用于创建一个实例对象,它执行了以下步骤:
以下是一个使用 new 操作符创建对象的示例:
// 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 使用 new 操作符创建实例对象
let person1 = new Person("Alice", 30);
let person2 = new Person("Bob", 25);
console.log(person1.name); // Alice
console.log(person2.age); // 25
在这个示例中,new 操作符创建了两个 Person 的实例对象,分别是 person1 和 person2。在构造函数中,this 指向了这两个实例对象,使得构造函数可以在实例对象上设置属性。prototype 属性用于设置实例对象的原型链接,从而可以让实例对象通过原型链访问构造函数的原型中的属性和方法。
Map、Object 和 WeakMap 都是 JavaScript 中用于存储键值对的数据结构,但它们之间有一些区别:Object:
Map:
WeakMap:
总结区别:
选择使用哪种数据结构取决于你的需求,例如键的类型、键值对的顺序以及内存管理等。
应用场景:Object 的应用场景:
// 创建自定义对象
const person = {
name: "Alice",
age: 30,
}
// 简单的映射
const colorMap = {
red: "#FF0000",
green: "#00FF00",
blue: "#0000FF",
}
Map 的应用场景:
// 存储用户数据
const userMap = new Map();
const user1 = { name: "Alice", age: 30 };
const user2 = { name: "Bob", age: 25 };
userMap.set(user1, "User data for Alice");
userMap.set(user2, "User data for Bob");
// 记录操作顺序
const logMap = new Map();
logMap.set("1", "First action");
logMap.set("2", "Second action");
logMap.set("3", "Third action");
// 避免命名冲突
const moduleAMap = new Map();
const moduleBMap = new Map();
moduleAMap.set("data", "Module A data");
moduleBMap.set("data", "Module B data");
WeakMap 的应用场景:
// 私有数据存储
const privateDataMap = new WeakMap();
class MyClass {
constructor() {
privateDataMap.set(this, { privateValue: 42 });
}
getPrivateValue() {
return privateDataMap.get(this).privateValue;
}
}
// 元数据存储
const metadataMap = new WeakMap();
function trackMetadata(obj, metadata) {
metadataMap.set(obj, metadata);
}
类数组(Array-like)是一种类似于数组的对象,它具有一些数组的特性,例如有一系列的数字索引和 length 属性,但它并不是真正的数组,缺少数组的方法和属性。
常见的类数组包括函数的 arguments 对象、DOM 元素列表(如 NodeList)、字符串等。虽然类数组在某些情况下可以像数组一样被访问和操作,但由于缺少数组的方法,有时可能需要将其转换为真正的数组。
以下是如何将类数组转换为真正的数组的几种方法:使用 Array.from():
Array.from() 方法可以从类数组对象或可迭代对象创建一个新的数组。它将类数组的值复制到一个新的数组中,并返回这个新数组。
// 类数组对象
function convertToArray() {
const argsArray = Array.from(arguments);
console.log(Array.isArray(argsArray)); // true
return argsArray;
}
convertToArray(1, 2, 3); // [1, 2, 3]
使用 Array.prototype.slice.call():
slice() 方法可以用于截取数组的一部分,当将 slice() 应用于类数组对象时,可以将其转换为真正的数组。
// 类数组对象
const nodeList = document.querySelectorAll("p");
const nodeListArray = Array.prototype.slice.call(nodeList);
console.log(Array.isArray(nodeListArray)); // true
使用扩展运算符:
扩展运算符可以将一个可迭代对象转换为多个参数,然后通过 Array.from() 或直接使用 [] 创建新数组。
// 类数组对象
const nodeList = document.querySelectorAll("p");
const nodeListArray = [...nodeList];
console.log(Array.isArray(nodeListArray)); // true
使用 concat() 方法:
通过将类数组对象与一个空数组(或其他数组)连接,可以创建一个新数组,实现类数组转换。
// 类数组对象
const nodeList = document.querySelectorAll("p");
const nodeListArray = [].concat(nodeList);
console.log(Array.isArray(nodeListArray)); // true
地址:前端面试题库
javaScript 数组有许多原生的方法,用于对数组进行操作、遍历、修改等。以下是一些常见的数组原生方法:
DOM(Document Object Model)操作是指通过 JavaScript 对网页中的 HTML 元素和内容进行增删改查的操作。以下是一些常见的 DOM 操作以及相应的示例:获取元素:
// 获取元素并修改其内容
let heading = document.getElementById("myHeading");
heading.textContent = "Hello, DOM!";
创建和插入元素:
// 创建并插入新元素
let newParagraph = document.createElement("p");
newParagraph.textContent = "This is a new paragraph.";
document.body.appendChild(newParagraph);
修改元素属性和样式:
// 修改元素的属性和样式
let image = document.getElementById("myImage");
image.setAttribute("src", "new-image.jpg");
image.style.width = "200px";
移除元素:
removeChild(node):从父节点中移除指定的子节点。
// 移除元素
let paragraphToRemove = document.getElementById("paragraphToRemove");
paragraphToRemove.parentNode.removeChild(paragraphToRemove);
事件处理:
// 添加事件监听器
let button = document.getElementById("myButton");
button.addEventListener("click", function() {
alert("Button clicked!");
});
for...in 和 for...of 都是用于迭代(遍历)数据结构中的元素,但它们在使用方式和应用场景上有一些区别。for...in 循环:
const obj = { a: 1, b: 2, c: 3 };
for (const key in obj) {
console.log(key); // 输出:a, b, c
}
for...of 循环:
const arr = [1, 2, 3];
for (const value of arr) {
console.log(value); // 输出:1, 2, 3
}
区别总结:
按位与(&):
计算规则:对两个操作数的每个对应位执行逻辑 AND 操作,结果中的每个位都取决于两个操作数的对应位是否都为 1。
示例:a & b 返回一个值,其中每个位都是 a 和 b 对应位的 AND 结果。按位或(|):
计算规则:对两个操作数的每个对应位执行逻辑 OR 操作,结果中的每个位都取决于两个操作数的对应位是否有至少一个为 1。
示例:a | b 返回一个值,其中每个位都是 a 和 b 对应位的 OR 结果。按位异或(^):
计算规则:对两个操作数的每个对应位执行逻辑 XOR 操作,结果中的每个位都取决于两个操作数的对应位是否不同。
示例:a ^ b 返回一个值,其中每个位都是 a 和 b 对应位的 XOR 结果。按位非(~):
计算规则:对操作数的每个位执行逻辑 NOT 操作,即对每个位取反,1 变为 0,0 变为 1。
示例:~a 返回一个值,其中每个位都是 a 每个位取反的结果。左移(<<):
计算规则:将操作数的二进制位向左移动指定的位数,右侧用 0 填充。
示例:a << b 返回一个值,其中 a 的二进制位向左移动 b 位。右移(>>):
计算规则:将操作数的二进制位向右移动指定的位数,左侧用符号位的值填充。
示例:a >> b 返回一个值,其中 a 的二进制位向右移动 b 位。无符号右移(>>>):
计算规则:将操作数的二进制位向右移动指定的位数,左侧用 0 填充。
示例:a >>> b 返回一个值,其中 a 的二进制位向右移动 b 位,左侧用 0 填充。
解释性语言和编译型语言是两种不同的编程语言类型,它们的主要区别在于代码的执行方式和编译过程:
解释性语言:
Python、JavaScript、Ruby、PHP
是一些常见的解释性语言。编译型语言:
C、C++、Rust、Go
是一些常见的编译型语言。总结区别:
JavaScript 中的原型(prototype)和原型链(prototype chain)是理解对象和继承机制的关键概念。原型(prototype):
每个 JavaScript 对象都有一个关联的原型对象,它是一个普通对象,包含一些共享的属性和方法。当你访问对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript 引擎会沿着对象的原型链去查找。对象可以通过 prototype 属性来访问它的原型对象。原型链(prototype chain):
原型链是由一系列连接的原型对象组成的链。当访问对象的属性或方法时,如果对象本身没有找到,引擎会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的顶端(Object.prototype)。这样的搜索路径就构成了原型链。
例如:
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
sayHello() {
console.log('hello')
}
}
const person = new Person('John', 20)
console.log(Person.prototype)
console.log(person.__proto__)
const person2 = Object.create(person)
console.log(person2.__proto__.__proto__)
person2.sayHello()
执行结果如下
?
这里的Person
类就存在自己的原型prototype
,prototype
是一个对象,拥有Person
的constructor和sayHello
方法
使用Person
创建出一个实例person
此时person
上并没有sayHello
方法,但是person
的__proto__
指向Person
的prototype
,当在当前对象上找不到sayHello
方法时,js就会沿着__proto__
的指向向上寻找
后面我们用Object.create(person)
方法创建一个对象person2
,此时该对象是空对象,但是该对象的__proto__
指向person
,而person
的__proto__
又指向Person
的prototype
,因此person2
也可以调用sayHello
方法
这种沿着__proto__
指向一致向上寻找,便形成一个原型链,需要注意的时原型链的最顶端都是null
地址:前端面试题库
闭包是一种在函数内部创建的函数,它可以访问其包含函数(外部函数)的变量和参数,即使外部函数已经执行完毕。换句话说,闭包是一个函数加上该函数创建时所能访问的作用域,它可以"捕获"这个作用域中的变量,并在函数执行后继续保持对这些变量的访问能力。
闭包通常由以下两个主要部分组成:
闭包的作用:
保护数据:
通过闭包,可以将一些数据限制在函数的作用域内,避免全局污染,只暴露必要的接口。保存状态:
闭包可以用于保存函数执行过程中的状态,例如计数器、缓存等,使得状态在函数调用之间得以保持。实现模块化:
通过闭包,可以实现类似于模块的封装,将一组相关的功能组织在一起,避免命名冲突和变量泄露。实现回调和异步操作:
JavaScript 中的回调函数和异步操作通常需要使用闭包来捕获外部函数的状态和数据。创建私有变量:
通过闭包可以模拟私有变量,使得只有内部函数能够访问和修改某些数据。以下是一个闭包的简单示例:
function outerFunction() {
let outerVariable = "I am from outer function";
function innerFunction() {
console.log(outerVariable); // 内部函数访问了外部函数的变量
}
return innerFunction; // 返回内部函数作为闭包
}
let closure = outerFunction(); // 调用外部函数,返回内部函数
closure(); // 调用内部函数,输出 "I am from outer function"
在这个例子中,innerFunction 是一个闭包,它可以访问外部函数 outerFunction 中的变量 outerVariable。当我们调用外部函数并将其返回的内部函数赋值给 closure 变量后,closure 成为了一个保存了 outerVariable 的闭包,可以在之后的调用中继续访问这个变量。
JavaScript 中的作用域(scope)是指变量和函数在代码中可访问的范围。作用域规定了在何处以及如何查找变量。作用域的概念在 JavaScript 中是非常重要的,因为它影响着变量的可见性和生命周期。
作用域的类型:
全局作用域:
在代码的最外层定义的变量和函数,可以在代码的任何位置被访问,称为全局作用域。局部作用域:
在函数内部定义的变量和函数,只能在函数内部被访问,称为局部作用域(也称为函数作用域)。作用域的作用:
隔离变量:
作用域可以将变量限制在特定的代码块或函数内部,防止变量之间发生命名冲突。封装和信息隐藏:
通过使用函数作用域,可以将一些变量隐藏在函数内部,避免了外部代码直接访问和修改这些变量,实现了信息隐藏和封装。变量的生命周期:
作用域也影响了变量的生命周期,变量在进入作用域时被创建,在离开作用域时被销毁。这有助于内存管理和垃圾回收。// 全局作用域
let globalVariable = "I am global";
function myFunction() {
// 局部作用域
let localVariable = "I am local";
console.log(localVariable); // 可以访问局部变量
console.log(globalVariable); // 可以访问全局变量
}
console.log(globalVariable); // 可以在全局范围内访问
// console.log(localVariable); // 无法在全局范围内访问局部变量
在这个示例中,globalVariable 是一个全局变量,可以在代码的任何地方访问。localVariable 是在函数内部定义的局部变量,只能在函数内部访问。
作用域帮助我们组织代码、封装功能、限制变量的可见性,并且有助于避免命名冲突,使得代码更加模块化和可维护。
执行上下文(Execution Context)是 JavaScript 中一个重要的概念,它是代码在执行过程中的环境,包含了当前代码执行所需的所有信息,如变量、函数、作用域等。每当 JavaScript 代码运行时,都会创建一个新的执行上下文。理解执行上下文对于理解代码的执行流程和作用域非常关键。
执行上下文可以分为三种类型:
全局执行上下文:
整个脚本的最外层环境,在代码执行之前被创建。在浏览器中,全局执行上下文通常是 window 对象。函数执行上下文:
每次调用函数时,都会创建一个新的函数执行上下文,用于管理函数内部的变量、参数和执行流程。Eval 函数执行上下文:
使用 eval 函数执行的代码会在一个特殊的执行上下文中运行。执行上下文包含的重要信息:
变量环境(Variable Environment):
包含了变量和函数声明,以及外部环境的引用。在函数执行上下文中,这部分称为函数的“作用域链”。词法环境(Lexical Environment):
类似于变量环境,但是在函数执行上下文中,它会随着函数嵌套的改变而变化,支持闭包。this 值:
指向当前执行上下文的上下文对象。外部引用:
指向包含当前执行上下文的外部执行上下文。执行上下文的生命周期:
创建阶段:
在进入代码块之前,执行上下文会被创建。这时会创建变量对象(VO),初始化函数参数、变量和函数声明。执行阶段:
在进入代码块时,代码会按顺序执行。在这个阶段,变量赋值和函数调用等操作会被执行。销毁阶段:
执行完代码后,执行上下文会被销毁,函数的局部变量等会被释放。示例:
function greet(name) {
var message = "Hello, " + name;
console.log(message);
}
greet("Alice"); // 调用函数,创建函数执行上下文
在这个示例中,当调用 greet("Alice") 时,会创建一个新的函数执行上下文用于执行 greet 函数内部的代码。这个执行上下文包含了 name 参数和 message 变量,以及其他所需的信息。一旦函数执行完毕,这个执行上下文将会被销毁。
Ajax(Asynchronous JavaScript and XML) 是一种用于在不刷新整个页面的情况下,通过 JavaScript 在后台与服务器进行数据交换的技术。通过 Ajax,可以实现异步加载数据、动态更新页面内容,从而提升用户体验。
Ajax 的工作流程:
以下是一个简单的 Ajax 请求的示例,使用原生的 XMLHttpRequest 对象:
<!DOCTYPE html>
<html>
<head>
<title>Ajax Example</title>
</head>
<body>
<button id="loadButton">Load Data</button>
<div id="dataContainer"></div>
<script>
document.getElementById("loadButton").addEventListener("click", function() {
let xhr = new XMLHttpRequest();
xhr.open("GET", "https://jsonplaceholder.typicode.com/posts/1", true); // 发送 GET 请求
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
let response = JSON.parse(xhr.responseText);
document.getElementById("dataContainer").textContent = response.title;
}
};
xhr.send(); // 发送请求
});
</script>
</body>
</html>
在这个示例中,点击 "Load Data" 按钮会触发一个 Ajax 请求,获取?https://jsonplaceholder.typicode.com/posts/1
?地址返回的数据,并将其中的标题显示在页面上。XMLHttpRequest
?对象用于创建和管理请求,onreadystatechange
?事件监听响应状态的变化,readyState
?表示请求状态,status
?表示响应状态码,responseText
?存储响应内容。
使用 Promise 封装 Ajax 请求可以让代码更具可读性和可维护性,同时还可以更好地处理异步操作的结果。以下是一个使用 Promise 封装 Ajax 请求的示例,使用原生的 XMLHttpRequest 对象:
function makeAjaxRequest(url, method) {
return new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(xhr.responseText); // 请求成功,将响应数据传递给 resolve 函数
} else {
reject(new Error("Request failed with status: " + xhr.status)); // 请求失败,将错误信息传递给 reject 函数
}
}
};
xhr.send();
});
}
// 使用封装的函数
makeAjaxRequest("https://jsonplaceholder.typicode.com/posts/1", "GET")
.then(function(response) {
console.log("Response:", response);
})
.catch(function(error) {
console.error("Error:", error);
});
在这个示例中,makeAjaxRequest 函数返回一个 Promise 对象,它将一个回调函数传递给 XMLHttpRequest 的 onreadystatechange 事件处理程序。当请求完成时,如果状态码为 200,则调用 resolve 函数并传递响应数据,否则调用 reject 函数并传递错误信息。
通过使用 Promise,你可以使用 .then() 方法处理成功的响应,使用 .catch() 方法处理失败的情况,使得代码逻辑更加清晰和可控。封装了 Promise 的 Ajax 请求可以更好地管理异步操作,使得代码更具可读性和可维护性。不过需要注意的是,现代的 Web 开发中,许多人更倾向于使用基于 Promise 的库(如 axios)来进行网络请求,因为它们提供了更便捷和强大的功能。
Ajax、axios 和 fetch 都是用于在前端实现网络请求的工具或技术,但它们在用法和功能上有一些区别。Ajax:
axios:
fetch:
区别总结:
在 JavaScript 中,this 是一个特殊关键字,它在不同的上下文中指向不同的值。this 的值取决于代码在何处被调用,以及调用方式。
this 的指向可以分为以下几种情况:全局上下文中的 this:
在全局上下文(没有嵌套在任何函数内部)中,this 指向全局对象,通常在浏览器环境中是 window 对象,在 Node.js 环境中是 global 对象。函数中的 this:
箭头函数中的 this:
箭头函数没有自己的 this 值,它会继承外部函数的 this 值。箭头函数的 this 始终指向包含它的最近的非箭头函数的 this 值,即词法作用域的 this。DOM 事件处理函数中的 this:
在处理 DOM 事件时,事件处理函数的 this 默认指向触发事件的 DOM 元素。但是,如果使用箭头函数作为事件处理函数,this 将保持其外部函数的 this 值。
示例:
console.log(this); // 全局上下文中,指向全局对象(浏览器环境中是 window)
function sayHello() {
console.log(this); // 在函数中,取决于调用方式
}
const obj = {
method: function() {
console.log(this); // 在对象的方法中,指向调用方法的对象(obj)
}
};
const arrowFunction = () => {
console.log(this); // 箭头函数中,继承外部函数的 this 值
};
sayHello(); // 函数中,取决于调用方式
obj.method(); // 对象的方法中,指向调用方法的对象(obj)
arrowFunction(); // 箭头函数中,继承外部函数的 this 值
call、apply 和 bind 是 JavaScript 中用于显式设置函数执行上下文(this 值)的方法。它们的主要作用是在调用函数时,将指定的对象作为函数的上下文来执行函数。这些方法的区别在于参数传递的方式和执行时间。call 方法:
示例:
function greet(name) {
console.log(`Hello, ${name}! I am ${this.name}.`);
}
const person = { name: "Alice" };
greet.call(person, "Bob"); // 输出:Hello, Bob! I am Alice.
apply 方法:
示例:
function greet(name) {
console.log(`Hello, ${name}! I am ${this.name}.`);
}
const person = { name: "Alice" };
greet.apply(person, ["Bob"]); // 输出:Hello, Bob! I am Alice.
bind 方法:
示例:
function greet(name) {
console.log(`Hello, ${name}! I am ${this.name}.`);
}
const person = { name: "Alice" };
const boundGreet = greet.bind(person);
boundGreet("Bob"); // 输出:Hello, Bob! I am Alice.
区别总结:
这些方法通常用于在特定上下文中执行函数,例如在事件处理程序、回调函数或特定环境中的函数调用。
实现 call 函数:
Function.prototype.myCall = function(context, ...args) {
context = context || window; // 如果未传入上下文,则使用全局对象
const uniqueKey = Symbol(); // 使用一个唯一的键来避免覆盖原有属性
context[uniqueKey] = this; // 将当前函数设置为上下文的属性
const result = context[uniqueKey](...args); // 调用函数并传递参数
delete context[uniqueKey]; // 删除添加的属性
return result; // 返回函数执行的结果
};
function greet(name) {
console.log(`Hello, ${name}! I am ${this.name}.`);
}
const person = { name: "Alice" };
greet.myCall(person, "Bob"); // 输出:Hello, Bob! I am Alice.
实现 apply 函数:
Function.prototype.myApply = function(context, args) {
context = context || window;
const uniqueKey = Symbol();
context[uniqueKey] = this;
const result = context[uniqueKey](...args);
delete context[uniqueKey];
return result;
};
function greet(name) {
console.log(`Hello, ${name}! I am ${this.name}.`);
}
const person = { name: "Alice" };
greet.myApply(person, ["Bob"]); // 输出:Hello, Bob! I am Alice.
实现 bind 函数:
Function.prototype.myBind = function(context, ...args1) {
const originalFunction = this;
return function(...args2) {
context = context || window;
const combinedArgs = args1.concat(args2); // 合并传入的参数
return originalFunction.apply(context, combinedArgs);
};
};
function greet(name) {
console.log(`Hello, ${name}! I am ${this.name}.`);
}
const person = { name: "Alice" };
const boundGreet = greet.myBind(person);
boundGreet("Bob"); // 输出:Hello, Bob! I am Alice.
需要注意,使用?Symbol
?来确保添加到上下文中的属性不会与现有属性冲突。这只是一种简化的实现,实际的 call、apply 和 bind 方法还包括更多的错误处理和边界情况考虑。
异步编程是一种编程范式,用于处理在执行过程中需要等待的操作,如网络请求、文件读写、数据库查询等。在传统的同步编程中,代码会按顺序一行一行执行,如果遇到需要等待的操作,整个程序可能会被阻塞,直到操作完成。而异步编程允许程序在执行等待操作的同时继续执行其他任务,以提高程序的效率和响应性。
异步编程的作用有以下几点:
提高响应性:
在等待长时间操作(如网络请求)时,程序不会被阻塞,用户仍然可以与界面交互,提升用户体验。优化资源利用:
在等待 IO 操作时,CPU 不会被空闲浪费,可以继续执行其他任务,充分利用系统资源。降低延迟:
在需要等待的操作完成后,程序可以立即继续执行,而不需要等待整个操作序列完成。并行处理:
异步编程使得程序能够同时执行多个任务,从而更有效地利用多核处理器。在 JavaScript 中,异步编程通常使用以下方式来实现:
回调函数:
将需要在操作完成后执行的代码封装在一个回调函数中,并在操作完成后调用回调函数。Promise:
通过 Promise 对象封装异步操作,并通过链式调用 .then() 方法处理操作完成后的结果。async/await:
使用 async 和 await 关键字来编写更具同步风格的异步代码,使代码更易读和维护。事件监听:
将需要在操作完成时执行的代码绑定到事件,当事件触发时执行相应的代码。异步编程对于处理现代 Web 应用中的网络请求、数据库查询、文件操作等任务非常重要。它能够提高应用的性能、响应性和用户体验,同时还有助于避免程序因等待操作而阻塞。
Promise 是 JavaScript 中处理异步操作的一种机制,它表示一个异步操作的最终完成(或失败),并可以在操作完成后进行处理。Promise 提供了一种更优雅和结构化的方式来处理回调地狱(Callback Hell),使异步代码更加可读和可维护。
一个 Promise 可以处于以下三个状态之一:
Pending(进行中):
初始状态,表示异步操作正在进行中。Fulfilled(已完成):
表示异步操作已成功完成,结果值可用。Rejected(已拒绝):
表示异步操作失败,错误信息可用。使用 Promise 的基本流程如下:
以下是一个简单的使用 Promise 的示例:
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { message: "Data fetched successfully" };
if (data) {
resolve(data); // 成功时调用 resolve 并传递数据
} else {
reject(new Error("Failed to fetch data")); // 失败时调用 reject 并传递错误信息
}
}, 1000);
});
}
// 使用 Promise
fetchData()
.then((result) => {
console.log(result.message); // 处理成功的情况
})
.catch((error) => {
console.error(error.message); // 处理失败的情况
});
在这个示例中,fetchData 函数返回一个 Promise 对象。通过 .then() 方法处理异步操作成功的情况,通过 .catch() 方法处理异步操作失败的情况。
实现一个完整的 Promise 并不是一件简单的事情,因为 Promise 的内部需要处理异步操作的状态管理、回调函数的注册和调用、错误处理等复杂逻辑。以下是一个简化版本的 Promise 实现,用于演示其基本原理:
class MyPromise {
constructor(executor) {
this.state = "pending"; // 初始状态
this.value = undefined; // 保存异步操作的结果
this.callbacks = []; // 存储成功和失败回调
const resolve = (value) => {
if (this.state === "pending") {
this.state = "fulfilled"; // 成功状态
this.value = value;
this.callbacks.forEach(callback => callback.onFulfilled(value));
}
};
const reject = (reason) => {
if (this.state === "pending") {
this.state = "rejected"; // 失败状态
this.value = reason;
this.callbacks.forEach(callback => callback.onRejected(reason));
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
if (this.state === "fulfilled") {
onFulfilled(this.value);
} else if (this.state === "rejected") {
onRejected(this.value);
} else if (this.state === "pending") {
this.callbacks.push({ onFulfilled, onRejected });
}
}
}
// 使用自己实现的 Promise
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("Promise resolved");
// reject("Promise rejected");
}, 1000);
});
promise.then(
value => console.log("Fulfilled:", value),
reason => console.error("Rejected:", reason)
);
这只是一个简化的 Promise 实现,它涵盖了 pending、fulfilled 和 rejected 状态以及 then 方法的基本逻辑。实际的 Promise 还需要考虑更多的细节,如链式调用、错误处理、异步操作的处理等。要在生产环境中使用,建议使用原生的 Promise 或第三方库来获得更完善和稳定的功能。
async/await 是 JavaScript 中用于处理异步操作的一种现代化的语法特性。它使得异步代码的编写和理解更接近于同步代码,使得异步操作的流程更加清晰和可读。async/await 是建立在 Promise 之上的一种语法糖,用于更方便地处理 Promise 的链式调用。async:
await:
使用 async/await 的基本示例:
function fetchData() {
return new Promise(resolve => {
setTimeout(() => {
resolve("Data fetched successfully");
}, 1000);
});
}
async function fetchAndPrintData() {
try {
const data = await fetchData(); // 等待异步操作完成
console.log(data);
} catch (error) {
console.error("Error:", error);
}
}
fetchAndPrintData(); // 调用 async 函数
在这个示例中,fetchAndPrintData 是一个 async 函数,内部使用 await 暂停了执行,直到 fetchData 异步操作完成。使用 try/catch 来处理异步操作的结果或错误。
async/await 和 Promise 都是用于处理异步操作的方式,但它们在语法和使用上有一些区别,以及一些优势和劣势。
区别:语法:
返回值:
优势:可读性:
async/await 的语法更接近同步代码,使异步操作的流程更加清晰和易读,减少了回调地狱(Callback Hell)的问题。错误处理:
在 Promise 中,错误处理需要使用 .catch() 方法,而 async/await 可以使用 try/catch 来处理错误,使得错误处理更加直观和统一。调试:
async/await 在调试时更容易跟踪代码执行流程,因为它更接近同步代码的写法。链式调用:
Promise 的链式调用对于一系列的异步操作很有用,但可能会造成代码深度嵌套,难以维护。
async/await 也可以链式调用,但代码的可读性更高,不易产生回调地狱。并发:
使用 Promise.all() 可以同时处理多个 Promise,在 async/await 中可以使用 Promise.all() 实现类似的功能。
选择使用时的考虑:
需要注意的是,async/await 内部仍然基于 Promise,因此它们并不是互斥的,而是可以相互配合使用的。
JavaScript 中的面向对象(Object-Oriented)编程是一种编程范式,它将程序的组织方式从简单的函数和数据结构转变为更加模块化和抽象化的方式。在面向对象编程中,程序被组织为一组对象,每个对象都具有属性(数据)和方法(函数),这些对象可以相互交互和协作,从而构建出更复杂的系统。
面向对象编程的主要概念包括:
类(Class):
类是对象的蓝图或模板,定义了对象的属性和方法。类描述了对象的特征和行为,可以看作是一个抽象的概念。对象(Object):
对象是类的实例,具有类定义的属性和方法。对象是面向对象编程中的基本单位,代表现实世界中的实体。封装(Encapsulation):
封装是将数据和操作封装在一个对象中,隐藏对象的内部细节,只暴露必要的接口供外部使用。继承(Inheritance):
继承是通过创建一个新的类来扩展已有的类,子类继承了父类的属性和方法,并可以添加自己的属性和方法。多态(Polymorphism):
多态允许不同的类实现相同的接口或方法,并可以以相同的方式进行调用,提高了代码的灵活性和可复用性。在 JavaScript 中,虽然它是一门基于原型的编程语言,但也支持面向对象编程。通过使用构造函数、原型链、类、继承等特性,可以在 JavaScript 中实现面向对象的编程风格。ES6 引入了更现代的类和继承语法,使得 JavaScript 的面向对象编程更加清晰和直观。
以下是一个简单的 JavaScript 面向对象编程的示例:
// 定义一个类
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
// 定义一个子类继承自 Animal
class Dog extends Animal {
speak() {
console.log(`${this.name} barks.`);
}
}
// 创建对象并调用方法
const dog = new Dog("Buddy");
dog.speak(); // 输出:Buddy barks.
地址:前端面试题库
JavaScript 中的垃圾回收(Garbage Collection)是一种自动管理内存的机制,用于检测和回收不再使用的内存,以防止内存泄漏和资源浪费。在 JavaScript 中,开发人员不需要手动释放内存,因为垃圾回收机制会自动处理不再使用的内存对象。
JavaScript 中的垃圾回收主要基于以下两个概念:引用计数垃圾回收:
标记-清除垃圾回收:
需要注意的是,引用计数垃圾回收在解决循环引用(两个对象相互引用)方面存在问题,因为即使对象之间相互引用,它们的引用计数也不会变为零。而标记-清除垃圾回收则能够处理循环引用。
现代 JavaScript 引擎通常使用基于标记-清除的垃圾回收机制。例如,V8 引擎(Chrome、Node.js)采用了分代垃圾回收策略,将对象分为新生代和老生代,以更高效地进行垃圾回收。垃圾回收是一种重要的机制,它确保在代码执行期间不会出现内存泄漏问题,提高了应用的性能和稳定性。
JavaScript 中的内存泄漏是指程序中已不再使用的内存没有被正确释放,导致内存占用不断增加,最终可能导致应用程序的性能下降甚至崩溃。以下是一些可能导致内存泄漏的常见情况:循环引用:
当两个或多个对象彼此相互引用,而且没有其他对象引用它们,就会形成循环引用。这会阻止垃圾回收器回收这些对象,导致内存泄漏。未清理的定时器和事件监听:
如果使用 setTimeout、setInterval 或 addEventListener 注册了定时器或事件监听器,但在不再需要时未进行清理,这些定时器和监听器将继续持有对象的引用,阻止垃圾回收。闭包:
闭包可以使函数内部的变量在函数执行结束后仍然被引用,导致这些变量的内存无法释放。特别是在循环中创建闭包时,可能会导致内存泄漏。未使用的全局变量:
如果创建了全局变量,但没有及时销毁或解除引用,这些变量将继续占用内存,即使在不再需要它们的情况下也是如此。DOM 元素引用:
在 JavaScript 中,保留对 DOM 元素的引用,即使这些元素从页面中删除,也会导致内存泄漏。必须确保在不需要 DOM 元素时解除引用。大量缓存:
缓存数据和对象可以提高性能,但如果缓存不受限制地增长,会导致内存泄漏。必须定期清理缓存,删除不再需要的数据。忘记释放资源:
例如,在使用了底层资源(如数据库连接、文件句柄等)后,没有显式地关闭或释放这些资源,可能导致资源泄漏。循环引用的事件处理器:
如果在 DOM 元素上注册了事件处理器,而这些事件处理器引用了其他对象,且这些对象又引用了相同的 DOM 元素,可能会导致循环引用,阻止垃圾回收。
为了避免内存泄漏,开发者应该注意适时释放不再需要的资源、解除引用,确保定时器和事件监听器的正确清理,以及注意循环引用的情况。使用浏览器的开发者工具和内存分析工具可以帮助识别内存泄漏问题。
防抖(Debouncing)和节流(Throttling)是两种常用的优化技术,用于限制频繁触发的事件的执行次数,从而提升性能和用户体验。
防抖
是指在事件触发后,等待一段时间(例如 200 毫秒),如果在这段时间内没有再次触发事件,那么执行相应的操作。如果在等待时间内又触发了事件,那么等待时间会被重置。节流
是指在事件触发后,一段时间内只执行一次相应的操作。无论触发多少次事件,都会在每个固定的时间间隔内执行一次。防抖的实现:
function debounce(func, delay) {
let timerId;
return function (...args) {
clearTimeout(timerId);
timerId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// 使用示例
const debouncedFn = debounce(() => {
console.log('Debounced function called');
}, 200);
window.addEventListener('scroll', debouncedFn);
节流的实现:
function throttle(func, delay) {
let lastTime = 0;
return function (...args) {
const currentTime = new Date().getTime();
if (currentTime - lastTime >= delay) {
func.apply(this, args);
lastTime = currentTime;
}
};
}
// 使用示例
const throttledFn = throttle(() => {
console.log('Throttled function called');
}, 200);
window.addEventListener('scroll', throttledFn);
浅拷贝和深拷贝是两种不同的对象复制方式,涉及到对象的引用关系。下面分别对浅拷贝和深拷贝进行解释:
浅拷贝:
在浅拷贝中,创建一个新对象,然后将原始对象的属性值复制到新对象中。如果属性值是基本类型,那么直接复制其值;如果属性值是对象或数组等引用类型,那么只是复制其引用,新对象和原始对象共享这些引用。浅拷贝只复制对象的一层属性。深拷贝:
在深拷贝中,递归地复制一个对象及其嵌套的所有属性,直到所有嵌套的属性都是基本类型为止。这样创建了一个完全独立的对象副本,原始对象和新对象之间没有任何引用关系。以下是一个简单的深拷贝函数的示例:
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
let clone = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
const originalObj = {
a: 1,
b: { c: 2 },
d: [3, 4]
};
const copiedObj = deepClone(originalObj);
console.log(copiedObj); // 深拷贝后的新对象
console.log(copiedObj === originalObj); // false,新对象和原始对象无关联
console.log(copiedObj.b === originalObj.b); // false,嵌套对象也是深拷贝
需要注意的是,上述深拷贝函数只是一个简单的实现示例,可能在某些特定情况下存在性能问题。在实际项目中,可以使用成熟的深拷贝工具库,如 lodash 的 cloneDeep 方法,来处理深拷贝的需求。
深拷贝是在创建一个对象的副本时,递归地复制其所有嵌套属性和子对象。在深拷贝过程中,如果对象存在循环引用,即某个对象引用了自身或与其他对象形成了循环引用关系,那么简单的递归拷贝可能会导致无限循环,甚至造成内存溢出。
为了解决循环引用问题,你可以在深拷贝过程中使用一些策略。以下是一种常见的方法
下面是一个使用JavaScript实现深拷贝并解决循环引用问题的示例
function deepCopyWithCycles(obj, cache = new WeakMap()) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (cache.has(obj)) {
return cache.get(obj);
}
if (obj instanceof Date) {
return new Date(obj);
}
if (obj instanceof RegExp) {
return new RegExp(obj);
}
const copy = Array.isArray(obj) ? [] : {};
cache.set(obj, copy);
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepCopyWithCycles(obj[key], cache);
}
}
return copy;
}
const objA = {
name: 'Object A'
};
const objB = {
name: 'Object B'
};
objA.circularRef = objA;
objB.circularRef = objA;
const copiedObjA = deepCopyWithCycles(objA);
console.log(copiedObjA.name); // 输出: Object A
console.log(copiedObjA.circularRef.name); // 输出: Object A
console.log(copiedObjA.circularRef === copiedObjA); // 输出: true
console.log(copiedObjA.circularRef === copiedObjA.circularRef.circularRef); // 输出: true
在上面的示例中,deepCopyWithCycles 函数使用了一个 WeakMap 来缓存已经拷贝的对象,以及它们在新对象中的引用。这样,即使对象存在循环引用,也可以正确地处理。同时,这个函数也考虑了处理特殊类型如 Date 和 RegExp。
事件委托(Event Delegation)是一种在 Web 开发中常用的事件处理技术,它通过将事件监听器绑定到父元素而不是每个子元素上,以达到优化性能、简化代码和处理动态内容的目的。
事件委托的原理是利用了事件冒泡机制。当子元素上的事件被触发时,该事件会向上冒泡到父元素,父元素可以捕获并处理这个事件。通过在父元素上监听事件,可以捕获到子元素触发的事件,从而实现对子元素的事件处理。
事件委托的优势和原因:
减少事件监听器的数量:
当页面上存在大量的子元素时,为每个子元素都绑定事件监听器会增加内存占用和性能开销。使用事件委托可以减少事件监听器的数量,只需在父元素上绑定一个监听器。动态添加的元素也能被处理:
对于通过 JavaScript 动态添加到页面的元素,无需再次绑定事件监听器,因为它们是父元素的后代,事件会冒泡到父元素。性能优化:
由于事件监听器较少,减少了事件冒泡的层级,可以提升页面的响应速度和性能。方便维护:
在有大量相似子元素的情况下,使用事件委托可以简化代码,使代码更易维护和理解。function flattenArray(arr) {
const result = [];
for (const item of arr) {
if (Array.isArray(item)) {
result.push(...flattenArray(item));
} else {
result.push(item);
}
}
return result;
}
const nestedArray = [1, [2, 3, [4, 5]], 6];
const flattenedArray = flattenArray(nestedArray);
console.log(flattenedArray); // [1, 2, 3, 4, 5, 6]
const nestedArray = [1, [2, 3, [4, 5]], 6];
const flattenedArray = nestedArray.flat(Infinity);
console.log(flattenedArray); // [1, 2, 3, 4, 5, 6]
function flattenArray(arr) {
return arr.reduce((result, item) => {
return result.concat(Array.isArray(item) ? flattenArray(item) : item);
}, []);
}
const nestedArray = [1, [2, 3, [4, 5]], 6];
const flattenedArray = flattenArray(nestedArray);
console.log(flattenedArray); // [1, 2, 3, 4, 5, 6]
function flattenArray(arr) {
return [].concat(...arr.map(item => Array.isArray(item) ? flattenArray(item) : item));
}
const nestedArray = [1, [2, 3, [4, 5]], 6];
const flattenedArray = flattenArray(nestedArray);
console.log(flattenedArray); // [1, 2, 3, 4, 5, 6]
地址:前端面试题库
函数柯里化(Currying)是一种将一个接受多个参数的函数转化为一系列接受一个参数的函数的技术。通过函数柯里化,可以将一个函数的调用过程分解为一系列较小的函数调用,每个函数接受一个参数,并返回一个新的函数,直至所有参数都被收集完毕,最后返回最终结果。
函数柯里化的主要思想是,将多参数函数转化为一系列只接受一个参数的函数,这样可以在每个步骤中捕获部分参数,生成一个新的函数来处理这些参数,最终产生一个累积所有参数的结果。
function add(a) {
return function(b) {
return a + b;
};
}
const add5 = add(5); // 第一个参数 5 固定
console.log(add5(3)); // 输出 8,传入第二个参数 3
在这个示例中,add 函数接受一个参数 a,然后返回一个新的函数,这个新函数接受参数 b,并返回 a + b 的结果。通过调用 add(5),可以固定第一个参数为 5,然后返回一个接受一个参数的函数 add5。随后调用 add5(3),将参数 3 传递给 add5 函数,得到结果 8。
函数柯里化的好处包括:
函数的链式调用是指通过在函数调用的结果上连续调用其他函数,形成一个函数调用的链条。每次调用都会返回一个新的对象或值,从而允许在一个连续的调用序列中执行多个操作。链式调用在很多 JavaScript 库和框架中广泛使用,它能够使代码更具有可读性和流畅性。
链式调用的好处在于可以在一个语句中完成多个操作,从而减少了中间变量的使用,使代码更加紧凑和易于理解。这种方式尤其在操作对象或调用方法链时非常有用。
class Calculator {
constructor(value = 0) {
this.value = value;
}
add(num) {
this.value += num;
return this; // 返回实例以支持链式调用
}
subtract(num) {
this.value -= num;
return this;
}
multiply(num) {
this.value *= num;
return this;
}
divide(num) {
this.value /= num;
return this;
}
getValue() {
return this.value;
}
}
const result = new Calculator(10)
.add(5)
.multiply(2)
.subtract(3)
.divide(2)
.getValue();
console.log(result); // 输出 7,(((10 + 5) * 2 - 3) / 2)
在上述示例中,Calculator 类具有一系列的方法,每个方法都会修改实例的属性并返回实例自身,以支持链式调用。通过链式调用,可以在一行中完成一系列的数学操作,使代码更加简洁和易读。
严格模式(Strict Mode)是一种在 JavaScript 中的一种运行模式,它使得代码在执行时遵循更严格的语法和规则,从而减少一些常见的错误,并提供更好的错误检测和调试机制。
要开启严格模式,可以在脚本的顶部或函数的内部添加以下语句:"use strict"
开启严格模式有以下作用:
更严格的错误检测:严格模式提供了更严格的错误检测机制,例如对未声明的变量赋值会抛出引用错误,而非严格模式下会创建一个全局变量。
地址:前端面试题库
作用域:
var:使用 var 声明的变量具有函数作用域,即在函数内部声明的变量在整个函数体内都可见。
let 和 const:使用 let 和 const 声明的变量具有块级作用域,即在 {} 块内声明的变量只在该块内有效。声明提升:
var:var 声明的变量会发生声明提升,即变量的声明会被提升至当前作用域的顶部,但初始化的值会保留在原位置。
let 和 const:虽然也会发生声明提升,但使用 let 和 const 声明的变量在初始化之前是不可访问的。重复声明:
var:可以重复声明同名变量,不会引发错误。
let 和 const:不能在同一个作用域内重复声明同名变量,否则会引发错误。可变性:
var 和 let:声明的变量是可变的,可以重新赋值。
const:声明的变量是常量,一旦赋值就不能再修改。全局对象属性:
var 声明的变量会成为全局对象的属性,例如在浏览器环境中是 window 的属性。
let 和 const 声明的变量不会成为全局对象的属性。临时死区:
let 和 const 声明的变量会在声明之前存在一个“临时死区”,在该区域内访问变量会引发错误。
语法简洁性:
this 绑定:
arguments 对象:
构造函数:
递归:
地址:前端面试题库
命名函数表达式:
扩展运算符(Spread Operator)是 ES6 引入的一种语法,用于展开(拆分)可迭代对象(如数组、字符串、对象等)为独立的元素,以便在函数调用、数组字面量、对象字面量等地方使用。扩展运算符的作用和使用场景包括以下几点:
函数调用中的参数传递:
扩展运算符可以用于将一个数组展开为一个函数的参数列表,这对于传递动态数量的参数非常有用。function add(a, b, c) {
return a + b + c;
}
const numbers = [1, 2, 3];
const result = add(...numbers); // 等同于 add(1, 2, 3)
数组字面量中的元素合并:
扩展运算符可以将一个数组的元素合并到另一个数组中。const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const mergedArray = [...array1, ...array2]; // 合并为 [1, 2, 3, 4, 5, 6]
复制数组和对象:
扩展运算符可以用于浅复制数组和对象,创建一个新的数组或对象,而不是引用同一个内存地址。const originalArray = [1, 2, 3];
const copiedArray = [...originalArray]; // 创建新数组,值相同但引用不同
const originalObject = { key1: 'value1', key2: 'value2' };
const copiedObject = { ...originalObject }; // 创建新对象,值相同但引用不同
字符串转为字符数组:
扩展运算符可以将字符串转换为字符数组,以便逐个访问字符。const str = 'hello';
const chars = [...str]; // 转为字符数组 ['h', 'e', 'l', 'l', 'o']
对象字面量中的属性合并:
扩展运算符可以将一个对象的属性合并到另一个对象中。const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const mergedObject = { ...obj1, ...obj2 }; // 合并为 { a: 1, b: 2, c: 3, d: 4 }
模板字符串(Template Strings)是 ES6 引入的一种字符串语法,它允许在字符串中插入变量、表达式以及换行符,使字符串的拼接和格式化更加方便。模板字符串使用反引号()来界定字符串,并在 `${}`` 内部插入表达式或变量。
const name = 'Alice';
const age = 30;
// 使用模板字符串插入变量和表达式
const greeting = `Hello, my name is ${name} and I am ${age} years old.`;
console.log(greeting);
// 输出:Hello, my name is Alice and I am 30 years old.
模板字符串还支持多行文本:
const multilineText = `
This is a multiline text
that spans across multiple lines.
It's easy to format and read.
`;
console.log(multilineText);
/*
输出:
This is a multiline text
that spans across multiple lines.
It's easy to format and read.
*/
地址:前端面试题库
Set 是 ES6 引入的一种数据结构,它类似于数组,但不允许有重复的值,且没有固定的索引。Set 内部的值是唯一的,不会存在重复的元素。Set 对象的方法和特性使它在许多场景下非常有用,以下是一些常见的 Set 方法的应用场景:
去重:
最常见的用途就是用于去除数组中的重复元素。将数组转换为 Set,再将 Set 转换回数组,就能轻松去重。const array = [1, 2, 2, 3, 3, 4, 5];
const uniqueArray = [...new Set(array)]; // 去重后的数组:[1, 2, 3, 4, 5]
判断元素是否存在:
使用 Set 的 has 方法可以快速判断一个元素是否存在于集合中。const set = new Set([1, 2, 3]);
console.log(set.has(2)); // true
console.log(set.has(4)); // false
交集、并集、差集等操作:
通过将多个 Set 对象转换为数组,然后利用数组的方法进行交集、并集、差集等操作。const set1 = new Set([1, 2, 3]);
const set2 = new Set([2, 3, 4]);
const intersection = [...set1].filter(item => set2.has(item)); // 交集:[2, 3]
const union = [...set1, ...set2]; // 并集:[1, 2, 3, 4]
const difference = [...set1].filter(item => !set2.has(item)); // 差集:[1]
存储任意类型的数据:
Set 可以存储任意类型的数据,包括基本数据类型和对象等。const mixedSet = new Set();
mixedSet.add(1);
mixedSet.add('hello');
mixedSet.add({ key: 'value' });
迭代:
使用 for...of 循环可以迭代 Set 中的每个元素,且顺序与添加顺序一致。const set = new Set([1, 2, 3]);
for (const item of set) {
console.log(item);
}
地址:前端面试题库
Object.defineProperty:
Object.defineProperty 是一个用于直接在一个对象上定义一个新属性或修改现有属性的方法。它允许你精确地控制属性的各种特性,如可枚举性、可配置性、可写性等。主要作用如下:
const obj = {};
Object.defineProperty(obj, 'name', {
value: 'Alice',
writable: false, // 不可写
enumerable: true, // 可枚举
configurable: true // 可配置
});
Proxy:
Proxy 是 ES6 引入的一种代理机制,用于拦截对象上的操作,提供了更强大的操作对象的能力。通过使用 Proxy,可以捕获并自定义对象的各种操作,如读取属性、写入属性、删除属性、函数调用等。主要作用如下:
const target = { name: 'Alice' };
const handler = {
get: function(target, property) {
console.log(`Getting property ${property}`);
return target[property];
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // 触发 get 操作,并输出 "Getting property name"
区别:
综上所述,Object.defineProperty 和 Proxy 在功能上有一定的重叠,但 Proxy 提供了更灵活和强大的能力,适用于更广泛的对象操作需求
Object.assign 方法和扩展运算符都是进行浅拷贝(Shallow Copy),而不是深拷贝(Deep Copy)。浅拷贝
是指在拷贝对象时,只拷贝对象的一层属性,而不会递归地拷贝嵌套对象的属性。拷贝后的对象和原始对象会共享嵌套对象的引用。深拷贝
是指在拷贝对象时,会递归地拷贝所有嵌套对象的属性,从而创建一个完全独立的副本,两者之间没有引用关系。使用 Object.assign 进行浅拷贝:
const originalObj = { a: 1, b: { c: 2 } };
const copiedObj = Object.assign({}, originalObj);
console.log(copiedObj); // { a: 1, b: { c: 2 } }
console.log(copiedObj === originalObj); // false
console.log(copiedObj.b === originalObj.b); // true,嵌套对象的引用相同
使用扩展运算符进行浅拷贝:
const originalObj = { a: 1, b: { c: 2 } };
const copiedObj = { ...originalObj };
console.log(copiedObj); // { a: 1, b: { c: 2 } }
console.log(copiedObj === originalObj); // false
console.log(copiedObj.b === originalObj.b); // true,嵌套对象的引用相同
ES6 模块:
ES6 引入了一种新的模块系统,它在语言层面提供了模块的支持。ES6 模块的导入和导出使用 import 和 export 关键字。
// 导出单个值
export const myVariable = 42;
// 导出多个值
export { foo, bar };
// 导入单个值
import { myVariable } from './myModule';
// 导入多个值
import { foo, bar } from './myModule';
CommonJS:
CommonJS 是一种用于服务器端 JavaScript 的模块系统,它被广泛用于 Node.js。CommonJS 使用 require 来导入模块,使用 module.exports 或 exports 来导出模块。
// 导出单个值
module.exports = 42;
// 导出多个值
exports.foo = foo;
exports.bar = bar;
// 导入单个值
const myVariable = require('./myModule');
// 导入多个值
const { foo, bar } = require('./myModule');
区别:
地址:前端面试题库