10个JavaScript 知识点
当谈及JavaScript高级概念时,我们指的是JavaScript编程语言的更加复杂和精妙的方面。以下是一些关键概念。
文章目录
1、闭包(Closures)
在JavaScript中,闭包常被用来创建私有变量和封装功能。通过在外部函数内定义变量,并返回内部函数来访问和修改这些变量,您可以控制数据的可见性和操作性。这使您能够实现信息隐藏,避免全局命名空间的污染。闭包能够帮助您实现数据的封装和保护。
简单的说闭包是指在函数内部创建的函数,它可以访问并持有父函数作用域中的变量。这种特性使得函数可以保留状态并延长变量的生命周期。
function outerFunction() {
const outerVariable = 'I am from the outer function';
function innerFunction() {
console.log(outerVariable)
}
return innerFunction;
}
const closure = outerFunction();
closure(); // Output:I am from the outer function
在上面的示例中,outerFunction定义了outerVariable并返回innerFunction,innerFunction引用了outerVariable。当调用outerFunction并将其赋值给closure时,它创建了一个保留对outerVariable引用的闭包。稍后,当调用该闭包时,它仍然可以访问outerVariable并记录其值。
闭包常常用于事件处理程序、回调函数以及在函数式编程中维护状态等场景。它们提供了一种创建对变量的持久引用的方式,并在JavaScript中实现了强大而灵活的编程技术。通过使用闭包,我们可以在函数内部创建和操纵数据,并将其状态保持在闭包中,从而实现了更高级的编程模式。
2、Promises(承诺)
Promise表示异步操作的最终结果,可以是已解决的值或拒绝的原因。它有三种可能的状态:待定(pending)、已履行(resolved)或已拒绝(rejected)。
要创建一个Promise,您可以使用Promise构造函数,它接受一个带有两个参数(resolve和reject)的回调函数。在这个回调函数中,您执行异步任务,并通过调用resolve(value)来履行Promise并返回一个值,或通过调用reject(reason)来拒绝Promise并返回一个原因(通常是一个错误对象)。
使用Promises可以更好地处理异步操作,避免了回调函数的层层嵌套,提供了更清晰、可读性更高的代码结构。Promise还提供了一组方法(如then()和catch()),用于处理Promise的结果或捕获可能发生的错误,使得异步编程更加简洁和可维护。
function fetchData() {
return new Promise((resolve, reject) => {
// Simulating an asynchronous operation (e.g., API request)
setTimeout(() => {
const data = {
id: 1,
name: 'John Doe'
};
resolve(data);
// Resolve the promise with the fethced data
//reject(new Error('Failed to fetch data'));
//Uncomment to simulate
}, 2000)
})
}
fetchData()
.then((data) => {
console.log('Fetched data:', data);
})
.catch((error) => {
console.log('Error:', error.message);
})
.finally(() => {
console.log('Fetch operation completed.');
});
then()方法用于处理Promise的履行。它接受一个回调函数作为参数,该函数接收解决后的值作为参数。您可以链接多个then()调用来对解决后的值执行顺序操作或转换。
catch()方法用于处理Promise的拒绝。它接受一个回调函数作为参数,该函数接收拒绝的原因(错误)作为参数。通常在Promise链的末尾使用catch()来处理异步操作期间发生的任何错误。
Promise还提供了其他方法,例如finally(),它允许您指定一个回调函数,无论Promise是履行还是拒绝,都会调用该函数;Promise.all()用于等待多个Promise履行。
通过使用Promise的这些方法,您可以更灵活地处理异步操作的结果,并对其进行链式操作、错误处理和最终处理。这种方式使得异步代码更加易于理解和维护。
3、原型和原型继承(Prototypes and Prototypal Inheritance)
原型是一个对象,其他对象可以从中继承属性和方法。当访问一个对象的属性或方法时,JavaScript首先检查对象本身是否具有该属性。如果没有,它会沿着原型链向上查找,检查对象的原型,然后是原型的原型,依此类推,直到找到该属性或到达链的末端。
原型继承是JavaScript中一种基于原型的继承机制。每个对象都有一个原型,可以通过原型继承获得其属性和方法。当我们访问一个对象的属性时,如果该对象本身没有这个属性,JavaScript会自动在其原型中查找。如果原型中也没有,就会继续向上查找,直到找到属性或到达原型链的末端。
原型继承的特性使得我们可以创建对象之间的继承关系,共享属性和方法,实现代码的重用。通过修改原型对象,我们可以动态地添加、修改或删除对象的属性和方法。原型继承是JavaScript面向对象编程的核心概念之一,对于理解和使用JavaScript中的对象和继承非常重要。
//parent constructor function
function Animal(name) {
this.name = name;
}
//Adding a method to the parent's prototype
Animal.prototype.greet = function() {
console.log(`Hello , my name is ${this.name}`);
}
//Child constructor function
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
//Establishing the prototype chain
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
//Adding a method to the child's prototype
Dog.prototype.bark = function() {
console.log("Woof woof!");
};
//Creating instances
const animal = new Animal('Max');
const dog = new Dog('Buddy', 'Labrador');
//Using the inherited methods
animal.greet(); //Output:Hello , my name is Max
dog.greet(); //Output:Hello , my name is Buddy
//Using the specific method for the child
dog.bark(); //Output:Woof woof!
在这个程序中,我们有两个构造函数:Animal和Dog。
Animal构造函数接受一个name参数,并使用this.name将其赋值给新创建的对象的name属性。我们在Animal.prototype上添加了一个greet方法,这个方法将被Animal构造函数创建的所有实例共享。Dog构造函数使用Animal.call(this, name)继承了Animal构造函数的属性。
我们通过使用Object.create(Animal.prototype)创建一个新对象,并将其赋值给Dog.prototype,从而建立原型链。这样就将Dog实例的原型链接到Animal.prototype,实现了继承。
我们在Dog.prototype上添加了一个bark方法,这个方法是特定于由Dog构造函数创建的实例的。
我们使用new关键字创建了Animal和Dog的实例。animal实例使用了从Animal.prototype继承的greet方法,而dog实例同时使用了从Animal.prototype继承的greet方法和在Dog.prototype中定义的bark方法。
运行这个程序时,您应该在控制台上看到相应的输出。原型和原型继承是JavaScript中的基本概念。它们允许对象从其他对象中继承属性和方法,实现代码的重用,并建立对象之间的关系。
4、事件循环(Event Loop)
事件循环是JavaScript运行时环境的固有部分,不需要显式编程。然而,我可以提供一个示例来演示JavaScript中事件循环的工作原理,通过模拟异步行为。
console.log('Start');
setTimeout(() => {
console.log('Timeout 1');
}, 0);
setTimeout(() => {
console.log('Timeout 2');
}, 0);
Promise.resolve()
.then(() => {
console.log('Promise resolved');
});
console.log('End')
//Start
//End
// Timeout 1
// Timeout 2
// Promise resolved
在这个例子中,我们使用setTimeout()和Promise调度了多个异步操作。下面是程序的执行过程:
- “Start”被console.log输出(同步)
- “End”被console.log输出(同步)
- Promise被执行并输出“Promise resolved”(微任务)
- 第一个setTimeout被执行并输出“Timeout 1”(宏任务)
- 第二个setTimeout被执行并输出“Timeout 2”(宏任务)
虽然两个setTimeout函数在同一时间被调用并加入宏任务队列,但是它们在执行的上下文环境被创建之后被调用,因此它们的执行仍然需要等待当前代码块的同步代码和之前注册的宏任务完成之后才能开始。而Promise的执行则会优先于下一个宏任务执行,因为Promise属于微任务,它们的执行优先于宏任务。因此,它会在两个setTimeout函数之前被处理。
需要注意的是setTimeout实际上是一个宏任务,它将回调函数推入到宏任务队列。而Promise.then是一个微任务,它将回调函数推入到微任务队列。在这个例子中,先处理完同步的代码,再执行所有的微任务队列,最后按照宏任务队列的顺序执行所以的setTimeout函数。
请记住,事件循环处理异步任务的执行顺序,确保它们不会阻塞主要执行流,并使JavaScript保持响应性。
5、模块(Modules)
模块是将代码封装成可重用、独立的单元,有助于组织和维护复杂的JavaScript应用程序。模块化编程使得代码更可读、可维护,并提供了命名空间隔离和代码复用的机制。
在传统的JavaScript中,模块化的支持有限。但是,现代的JavaScript环境(如Node.js和现代浏览器)提供了原生的模块系统,例如CommonJS和ES Modules(ESM)。
//math.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export function multiply(a, b) {
return a * b;
}
首先,我们创建一个名为math.js的模块,用来导出一些数学函数:
接下来,我们创建另一个文件main.js,用来导入并使用math.js模块中的函数:
在这个程序中,我们有两个文件:math.js和main.js。
math.js文件是一个模块,它导出三个函数:add、subtract和multiply。每个函数都是使用export关键字进行定义的。
在main.js文件中,我们使用import语句从math.js模块中导入这些函数。我们使用花括号{}来指定我们想要导入的函数名称。import语句使用相对路径(’./math.js’)来定位模块文件。
然后,我们在main.js文件中使用导入的函数add、subtract和multiply来执行数学运算,并将结果记录到控制台。
要运行这个程序,你需要一个支持ES模块的JavaScript环境,比如具有原生模块支持的现代浏览器或带有–experimental-modules标志的较新版本的Node.js。
执行main.js文件时,你应该在控制台上看到数学运算的结果。
JavaScript模块提供了一种清晰、标准化的方式来组织代码、管理依赖关系,并促进代码的重用性。
6、生成器(Generators)
生成器是JavaScript中一种特殊的函数,它可以在执行过程中暂停和恢复。通过使用生成器函数和迭代器协议,我们可以控制生成器的迭代过程,并实现惰性计算或异步编程。
生成器函数使用function*语法定义,内部包含一个或多个yield语句。yield语句用于产生一个值并暂停生成器的执行,将值返回给调用者。每次调用生成器的next()方法,生成器都会执行到下一个yield语句,并将产生的值返回。
function* countUp(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
//Create a generator object
const generator = countUp(1, 5);
//Iterate over the generator using a for...of loop
for (const num of generator) {
console.log(num);
}
//1
//2
//3
//4
//5
在这个程序中,我们定义了一个名为countUp的生成器函数。countUp生成器使用for循环从指定的起始值到结束值产生数字。yield关键字用于暂停生成器并发出当前值。
然后,我们通过使用所需的参数(在本例中为1和5)调用countUp函数来创建一个生成器对象。
为了消费生成器生成的值,我们使用for…of循环迭代生成器对象。在每次迭代中,循环获取生成器产生的下一个值,并将其赋值给num变量。然后,我们将num的值记录到控制台。
运行这个程序时,你应该在控制台上看到从1到5的数字。
生成器提供了一种强大的方式来创建具有惰性评估的可迭代序列。它们允许您控制迭代的流程,暂停执行并稍后恢复。生成器特别适用于处理大型或无限序列的数据,或者用于实现自定义的迭代模式。
7、箭头函数(Arrow Functions)
箭头函数是一种简洁的函数表达式语法,它提供了更简单的函数定义方式和更清晰的上下文绑定。
//Regular function
function multiply(a, b) {
return a * b;
}
//Arrow function
const divide = (a, b) => {
return a / b;
}
//Arrow function with implicit return
const add = (a, b) => a + b;
//Usage
console.log(multiply(3, 4)); //Output:12
console.log(divide(10, 2)); //Output:5
console.log(add(5, 7)); //Output:12
在这个程序中,我们有三个函数:multiply、divide和add。
multiply函数是一个使用function关键字定义的普通函数。它接受两个参数(a和b),并返回它们的乘积。
divide函数是一个使用箭头(=>)语法定义的箭头函数。它也接受两个参数,并返回它们的除法结果。箭头函数相比普通函数提供了更简洁的语法。
add函数是另一个箭头函数,但它使用了隐式返回。当箭头函数的函数体只有一个表达式时,可以省略花括号{}和return关键字。表达式的结果将被隐式返回。
最后,我们使用不同的参数调用这些函数,并将结果记录到控制台。
运行这个程序时,你应该在控制台上看到数学运算的结果。
箭头函数提供了一种更紧凑、更简洁的语法来编写函数,特别适用于较短的函数或没有复杂体的函数。它们以词法方式绑定this值,使其在处理回调或需要保留封闭上下文的函数时特别有用。
8、异步迭代(Asynchronous Iteration)
JavaScript中的异步迭代允许您在异步数据源上进行迭代,例如promises或异步生成器。下面是一个示例程序,演示了使用for await…of循环进行异步迭代:
//Asynchronous generator function
async function* getData() {
const data = [1, 2, 3, 4, 5];
for (const item of data) {
//Simulating an asynchronous operation
await new Promise((resolve) => setTimeout(resolve, 1000));
yield item;
}
}
//Asynchronous iteration
(async () => {
for await (const value of getData()) {
console.log(value);
}
})();
//1
//2
//3
//4
//5
在这个程序中,我们定义了一个名为getData的异步生成器函数。该生成器在模拟的异步操作之后,从一个数组(data)中产生值。在循环内部使用await关键字来暂停生成器,等待promise解析完成。
为了执行异步迭代,我们使用了一个自执行的async函数,其中包含一个for await…of循环。循环遍历由getData()返回的异步生成器对象。在每次迭代中,循环等待生成器产生的下一个值,并将其赋值给value变量。然后,我们将value记录到控制台。
运行这个程序时,你应该在控制台上看到1、2、3、4和5这些值被记录下来,每个值之间间隔1秒,这是由于模拟的异步操作造成的。
异步迭代在处理异步数据源或在需要以异步方式对每个项执行操作时非常有用,比如进行API请求或处理数据流等情况。
需要注意的是,在这个例子中,控制台输出顺序和代码中的顺序是不一样的,因为异步操作有可能会在代码的输出完成之前完成。所以这里可能会把后打印的数字先打印出来。
9、代理(Proxy)
代理是JavaScript提供的一种高级特性,它允许您拦截并自定义对象的操作。通过使用代理,您可以对对象的访问、修改和删除等操作进行拦截,并在执行相应操作之前或之后执行自定义的逻辑。
//Target object
const traget = {
name: 'John',
age: 30,
}
//Proxy object
const proxy = new Proxy(traget, {
get(target, prop) {
console.log(`Getting property:${prop}`);
return target[prop];
},
set(target, prop, value) {
console.log(`Setting property:${prop} to ${value}`);
target[prop] = value;
},
deleteProperty(target, prop) {
console.log(`Deleting property:${prop}`);
delete target[prop];
}
})
//Accessing properties through the proxy
console.log(proxy.name);
//Output:Getting property:name
//Output:John
proxy.age = 35;
//Output:Setting property:age to 35
console.log(proxy.age);
//Output:Getting property:age
//Output:35
delete proxy.name;
//Output:Deleting property:name
console.log(proxy.name);
//Output:Getting property:name
//Output:undefined
在这个程序中,我们有一个名为target的对象,我们希望对它应用代理。我们通过将target对象作为第一个参数和handler对象作为第二个参数创建了一个Proxy对象。
handler对象包含各种陷阱或方法,用于拦截对代理执行的不同操作。在这个示例中,我们定义了三个陷阱:
get:当访问代理上的属性时,调用这个陷阱。它记录被访问的属性,并从target对象返回相应的值。set:当在代理上设置属性时,调用这个陷阱。它记录被设置的属性,并将值赋给target对象中相应的属性。deleteProperty:当从代理中删除属性时,调用这个陷阱。它记录被删除的属性,并从target对象中删除该属性。然后,我们创建了一个代理对象,它充当代码和目标对象之间的透明中介。对代理执行的任何操作都会触发在handler对象中定义的相应陷阱方法。
在程序中,我们访问属性(name和age),为age属性设置一个新值,删除name属性,并通过代理再次访问name属性。每个操作都触发相应的陷阱,相应的日志语句将被打印到控制台上。
运行这个程序时,你应该在控制台上看到日志语句及其相应的输出,展示了代理对象拦截和处理目标对象上的操作的行为。
10、反射 API(Reflect API)
Reflect API 是 JavaScript 提供的一组用于操作对象的方法集合。它提供了一种统一和更灵活的方式来执行常见的对象操作,比如属性的获取、设置和删除,函数的调用等。
Reflect API 中的方法与相应的操作符或语句具有相似的功能,但提供了更直观和一致的语法。下面是一些常用的 Reflect API 方法示例:
- Reflect.get(target, property): 获取目标对象中指定属性的值。
- Reflect.set(target, property, value): 将目标对象中指定属性的值设置为新值。
- Reflect.has(target, property): 检查目标对象是否具有指定属性。
- Reflect.deleteProperty(target, property): 删除目标对象中指定的属性。
- Reflect.apply(func, thisArg, args): 在给定的上下文对象(thisArg)中调用指定的函数(func),并传递参数(args)。
- Reflect.construct(constructor, args): 使用指定的参数(args)调用构造函数(constructor),创建一个新的对象实例。
下面是一个使用 Reflect API 的示例程序:
// Target object
const obj = {
name: 'John',
age: 30,
}
// Using the Reflect API
const handler = {
get(target, prop) {
console.log(`Getting property:${prop}`);
return Reflect.get(target, prop);
},
set(target, prop, value) {
console.log(`Setting property:${prop} to ${value}`);
return Reflect.set(target, prop, value);
},
};
//Creating a proxy using the Reflect API
const proxy = new Proxy(obj, handler);
// Accessing properties through the proxy using the Reflect API
console.log(proxy.name);
// Output:Getting property:name
// Output:John
proxy.age = 35;
// Output:Setting property:age to 35
console.log(proxy.age);
// Output:Getting property:age
// Output:35
在这个程序中,我们有一个名为obj的对象,我们希望使用 Reflect API 对它应用代理。
我们定义了一个handler对象,其中包含拦截器方法(get和set),它们将拦截代理上的属性访问和赋值操作。
在get和set拦截器方法内部,我们使用相应的Reflect方法(Reflect.get和Reflect.set)来执行实际的属性访问和赋值操作。
接下来,我们使用Proxy构造函数创建一个代理对象,将obj作为目标对象和handler对象传递给它。
然后,我们通过代理访问属性(name和age),并为age属性设置一个新值。每个操作都会触发相应的拦截器,并使用console.log将相关的日志语句打印到控制台。
Reflect API 提供了一组实用方法,这些方法与对象执行的基本操作相对应,例如属性访问(Reflect.get)、属性赋值(Reflect.set)、属性删除(Reflect.deleteProperty)等。这些方法可以与Proxy API结合使用,提供自定义行为和对对象操作的精细控制。
理解并掌握这些概念可以让你充分发挥该语言的威力,并利用先进的技术来解决复杂的问题。
通过掌握闭包、Promise、原型、事件循环、模块、箭头函数、生成器、代理和Reflect API等概念,你可以编写更模块化、可维护和高效的代码。这些概念使你能够创建封装良好的代码,有效处理异步操作,管理对象的继承和行为,控制程序流程等等。
随着你不断探索和加深对这些概念的理解,你将能够处理更复杂的编程任务,优化你的代码以获得更好的性能,并充分利用JavaScript提供的各种特性和能力