最早的闭包
最早使用闭包的例子可以追溯到1963年 当时美国计算机科学家John McCarthy(AI之父)在Lisp编程语言中首次实现了闭包的概念
它的定义是:“闭包是一个可以在调用时返回其词法环境的函数” ,这允许函数可以访问在定义时,处在同一范围内定义的变量的值。
这个概念被称为“McCarthy的闭包”
下面是一个用Lisp代码实现的McCarthy的闭包例子:
(define make-adder
(lambda (n)
(lambda (x) (+ n x))))
(define add-three (make-adder 3))
(add-three 4)
; returns 7
简单解释一下:
首先,定义一个名为make-adder的函数,它接受一个参数n,并返回一个内部函数,该函数接受一个参数x,并返回两个参数n和x的和。
然后,将参数3传递给make-adder,返回一个名为add-three的函数,该函数将n的值设置为3,它的自由变量n定义在函数外部,但是可以在函数内部访问。最后,将4作为参数传递给add-three,返回7,这就是McCarthy的闭包的一个简单例子。
源头 Alonzo Church
(lambda (x y)
(lambda (z)
(+ x y z)
)
)
什么是闭包 ?
闭包是一种特殊的作用域,它允许函数在定义它们的作用域之外访问和操作变量。 也就是说,闭包可以记住并访问它被创建时所在的作用域,即使它们当前所处的作用域已经发生了变化。
以下是javascript中的一个简单闭包的例子:
function createClosure() {
let privateData = 'This is private data';
return function() {
console.log(privateData);
}
}
let closure = createClosure();
closure(); // Outputs "This is private data"
在上面的代码中,createClosure()函数创建了一个作用域,它将privateData变量存储在其中。 然后,它返回一个函数,该函数可以访问privateData变量,即使它当前所处的作用域已经发生了变化。
个人理解:
闭包是一种函数作用域,它可以让你在一个函数内部定义另一个函数,这个内部函数可以访问外部函数的变量和参数。闭包的作用是使函数的变量和参数能够保持可访问状态,以便在其它函数中重复使用,并且可以在不同的作用域中使用。
简单说明:
闭包是一种能够访问另一个函数作用域中的变量的函数。
最简说明
函数和它引用的环境就是闭包
所以有人说,一个函数就是一个闭包
function closure() {}
为什么需要闭包 ?
JavaScript中的闭包是一个函数,它可以访问另一个函数作用域中的变量。
闭包使得函数可以维持对局部变量的访问,即使函数已经执行完毕。
它允许函数在当前作用域外被调用,而不会影响它所在的作用域。
因此,闭包可以让函数的内部变量和外部变量保持在可访问状态,即使函数已经执行完毕。
闭包可以让函数的作用域变得更加精确,可以让函数访问和修改函数定义的时候不可见的变量,提高代码的可复用
利用闭包可以保护对象属性的私有性
闭包的应用
- 属性访问:使用闭包可以访问对象的私有属性,而无需暴露在外部。
- 封装函数:使用闭包可以将函数内部的变量封装起来,而不会被外部访问到。
- 回调函数:使用闭包可以将回调函数封装起来以便在之后使用。
- 定时器:使用闭包可以将定时器内部的变量封装起来,而不会被外部访问到。
- 命名空间:使用闭包可以将代码内部的变量封装起来,以避免变量和函数名称冲突。
利用闭包的特性:保护函数内部变量不被外部调用,从而起到私有作用域的作用。
可以实现如下功能
-
使用立即执行函数表达式(IIFE)来创建闭包:
(function () {
var x = 10;
console.log(x);
})();
-
使用函数作为参数传递:
function foo(func) {
var x = 10;
func();
}
foo(function () {
console.log(x);
});
-
使用闭包实现私有变量:
function foo() {
var x = 10;
return function () {
return x;
}
}
var x = foo();
console.log(x());
-
使用闭包实现延迟执行:
function foo() {
var x = 10;
setTimeout(function () {
console.log(x);
}, 1000);
}
foo();
-
使用闭包实现模块模式:
var myModule = (function () {
var privateVar = 10;
function privateFunction() {
console.log(privateVar);
}
return {
publicFunction: privateFunction
};
})();
myModule.publicFunction();
闭包使用的问题
闭包引用的环境变量是“不可见”的,这导致了一系列问题
1. 内存泄漏
由于闭包中的变量始终可以被访问,所以它们需要一直存在于内存中,这可能会导致内存泄漏。
function myFunc() {
let arr = [];
for (let i = 0; i < 10; i++) {
arr.push(function() {
console.log(i);
});
}
return arr;
}
let myArr = myFunc();
上面的代码中,myFunc 函数会返回一个包含 10 个函数的数组。由于每个函数都会引用 i 变量,因此 i 变量会一直存在于内存中,从而导致内存泄漏
2. 不正确的 this 引用
在闭包中,this 指向的是外部作用域中的变量,如果外部作用域中的变量发生改变,那么 this 的指向也会发生改变。
let myObj = {
name: "John",
sayName: function() {
console.log(this.name);
}
};
let myFunc = myObj.sayName;
myObj.sayName() // John
myFunc(); // undefined
上面的代码中,myFunc 函数会引用外部作用域中的 myObj 变量,所以 this 指向的是 myObj 对象,但是当我们将 myFunc 函数赋值给另一个变量 myFunc 时,this 指向的就不是 myObj 了,所以 this.name 会返回 undefined。
小讲一下 this
在 JavaScript 中,this 关键字是一个指向函数的执行上下文的指针,它的值取决于函数的调用方式。
当一个函数被作为对象的方法调用时,this 的值指向调用该函数的对象。因此,当我们在 myObj 对象上调用 sayName 方法时,this 的值指向 myObj 对象,因此 this.name 的值为 John。
然而,当我们以函数形式调用 myFunc 时,this 的值指向全局对象,因为 myFunc 并没有被任何对象调用。因此,this.name 的值为 undefined,因此 myFunc() 会返回 undefined。
3. 回调函数的重复调用问题
由于闭包会一直引用外部作用域中的变量,所以如果外部作用域中的变量发生改变,那么闭包中的函数也会发生改变。
let myFunc = function() {
let count = 0;
return function() {
count++;
console.log(count);
};
};
let myFunc1 = myFunc();
let myFunc2 = myFunc();
myFunc1(); // 1
myFunc2(); // 1
上面的代码中,myFunc定义了一个函数,这个函数内部定义了一个count变量,并返回一个内部函数,该函数每次调用时,count变量值会加1。
然而,myFunc1和myFunc2分别调用myFunc函数,分别得到一个内部闭包,此时,由于myFunc1和myFunc2是两个不同的作用域,所以它们的count变量在不同的作用域上面. 接着,myFunc1和myFunc2分别调用它们的内部闭包函数,都会将各自作用域上的count变量的值加1,所以最终返回的结果都是1.
想要更加深入了解函数式编程,请听下回讲解