闭包是一个功能,它允许函数捕获定义该函数的环境(或保留对作用域中变量的访问)即使在该作用域已经关闭后。
我们可以说闭包是函数和词法环境的组合,其中定义了该函数。
换句话说,闭包为函数提供了访问自己的作用域、外部函数的作用域和全局作用域的能力,允许它“记住”并继续访问这些作用域中的变量和参数。
function outerFunction() {
let outerVariable = 'I am from the outer function';
return innerFunction() {
console.log(outerVariable); // 访问外部函数作用域中的outerVariable
}
}
let myFunction = outerFunction();
myFunction(); // 输出:I am from the outer function
每次在函数创建时以及在另一个函数内部定义函数时都会创建闭包。
执行上下文是一个执行JavaScript代码的环境。 对于每个函数调用,都会创建一个单独的执行上下文并推送到执行堆栈中。 一旦函数执行完成,它就会从堆栈中弹出。
每个执行上下文都有一个内存空间,其中存储了其变量和函数,一旦从执行堆栈中弹出函数,JavaScript垃圾收集器就会清除所有这些内容。
在JavaScript中,只有当没有引用某个对象时,该对象才会被垃圾回收。
在上面的示例中,匿名执行上下文仍对其外部环境的内存空间中的变量有引用。 即使outerFunction()已完成(它可以访问 outerVariable 变量并在console.log(outerVariable)中使用它)。
JavaScript中的闭包有几个重要用例:
数据隐私和封装:闭包可用于创建私有数据和封装有限范围内的功能。 通过在另一个函数内定义函数,内部函数可以访问外部函数的变量,但这些变量对外部函数不可访问。 这允许创建对外不直接可访问的私有数据和方法,从而增强了数据隐私和封装。
状态维护:闭包通常用于在异步操作和事件处理中维护状态。 例如,在处理异步任务时,闭包可以捕获和保留跨多个异步操作的变量状态,确保在异步任务完成时可以访问到正确的变量。
柯里化和偏函数应用:闭包有助于函数式编程技术如柯里化和偏函数应用。 通过使用闭包捕获和记住特定参数并返回使用这些捕获参数的新函数,可以实现柯里化和偏函数应用。 这允许创建具有预设参数的特化函数,提供灵活性和可重用性。
模块模式:闭包在实现JavaScript中的模块模式中至关重要。 通过使用闭包创建私有变量并只公开必要的公共方法,开发人员可以创建模块化和组织良好的代码,防止对内部模块数据的不必要访问和修改。
回调函数:在使用回调函数时,经常使用闭包。 闭包可用于在异步操作的上下文中捕获和维护变量的状态,以确保在调用回调函数时可以访问到正确的变量。
JavaScript中的变量提升是变量和函数声明自动移至其包含作用域顶部的默认行为。 这发生在编译阶段,在实际代码执行之前。 这意味着您可以在代码中声明之前使用变量或调用函数。
使用 var
声明变量时,声明会被提升到包含函数或块的顶部,并使用默认值“undefined”初始化。
console.log(x); // 输出:undefined
var x = 5;
使用 let
和 const
声明的变量也会被提升,但是它们有一个“暂时性死区”,在该区域中不能在声明之前被访问。
console.log(x); // 抛出错误(ReferenceError)
let x = 5;
函数声明也会被提升到其包含作用域的顶部。您可以在代码中声明之前调用函数。
sayHello(); // 输出:Hello, world!
function sayHello() {
console.log("Hello, world!");
}
箭头函数、函数表达式或变量初始化不会被提升。
暂时性死区(TDZ)是与使用 let
和 const
声明变量相关的 JavaScript 概念。
当您使用 let
或 const
声明一个变量时,它会被提升到其包含作用域的顶部,但是与 var
不同,使用 let
和 const
声明的变量在 TDZ 中保持未初始化状态。
在作用域内实际声明变量之前尝试访问或使用该变量会导致 ReferenceError
。这可防止在变量被适当定义之前使用它。
了解暂时性死区很重要,因为它有助于防止变量在初始化之前使用导致的相关错误。它还通过鼓励在使用之前适当声明变量来推广JavaScript编码的最佳实践。
在 JavaScript 中,每个函数和对象默认都有一个名为 prototype 的属性。
JavaScript中的每个对象都有一个原型。 原型是当前对象继承属性和方法的另一个对象。 您可以将原型视为模板或父对象。
原型链是允许对象从其他对象继承属性和方法的机制
在对象上访问某个属性或方法时,JavaScript会首先在对象本身上查找它。 如果找不到,它会在原型链上向上查找,直到找到该属性或方法。 此过程会一直持续到到达链顶部的 Object.prototype
为止。
Call: call()方法使用指定的this
值调用函数,并以逗号分隔的值的形式传入单独的参数
const person1 = { name: 'John' };
const person2 = { name: 'Jane' };
function greet(greeting) {
console.log(greeting + ' ' + this.name);
}
greet.call(person1, 'Hello'); // 输出:Hello John
greet.call(person2, 'Hi'); // 输出:Hi Jane
使用 call() 方法,一个对象可以调用另一个对象所拥有的方法。
const o1 = {
name: 'ravi',
getName: function(){
console.log(`Hello, ${this.name}`)
}
}
const o2 = {
name: 'JavaScript Centric'
}
o1.getName.call(o2) // Hello, JavaScript Centric
Apply: 使用给定的 this
值调用函数,但是它使用数组的形式接受参数。 当参数的数量未知或者参数已在数组中时,Apply非常有用。
const numbers = [1, 2, 3, 4, 5];
const max = Math.max.apply(null, numbers);
console.log(max); // 输出:5
Bind: 与调用它不同,bind() 会返回一个新函数,并允许您传入任意数量的参数。bind() 方法接受一个对象作为第一个参数,并创建一个新函数。
const module = {
x: 42,
getX: function() {
return this.x;
}
};
const boundGetX = unboundGetX.bind(module);
console.log(boundGetX()); // 输出:42
JavaScript中有两种类型的函数:
常规函数: 我们可以通过两种方式编写常规函数,即函数声明和函数表达式。
箭头函数或胖箭头函数:也称为 lambda 函数,是在 JavaScript(ES6)中引入的一种更简洁的函数表达式语法。 与传统的函数表达式相比,它们具有较短的语法,在创建匿名函数和使用函数式编程概念方面特别有用。
这里没有声明方法,我们只能通过函数表达式来编写。
箭头函数和常规函数之间有一些区别:
柯里化是函数式编程中的一种技术,它将接受多个参数的函数转换成一系列每个接收单个参数的函数。 通过组合这些柯里化的函数,可以构建更复杂的函数。
在 JavaScript 中,您可以使用闭包和返回函数来实现柯里化。
// 接收两个参数的常规函数
function add(x, y) {
return x + y;
}
// 函数的柯里化版本
function curryAdd(x) {
return function(y) {
return x + y;
};
}const add5 = curryAdd(5); // 偏函数应用,创建新函数
console.log(add5(3)); // 输出:8
柯里化在函数式编程中很有用,它可以使代码更模块化和可重用。 当您想要使用变参函数或者构建数据转换流水线时,它特别有用。
ES6,也称为 ECMAScript 2015,为 JavaScript 引入了许多新特性和增强功能,极大地扩展了该语言的功能。 ES6的一些关键特性包括: