我一直在阅读一些 JavaScript 书籍,并且总是听说闭包和副作用。出于某种原因,我无法理解它们的真正含义。谁能用简单的英语和例子向我解释它们是什么?(正如您向具有图形设计师编程水平的人解释的那样)。
6 回答
副作用是更简单的概念。“纯函数”是将其输入值映射到输出值的函数function plus(x, y) { return x + y; }
。“副作用”是返回值以外的任何影响。因此,例如:
function plusWithSideEffects(x, y) {
alert('This is a side effect');
return x + y;
}
具有引发警报对话框(并需要用户交互)的副作用。每个代码函数都有一些副作用(它们都消耗内存并且需要时间,如果没有别的),但是当人们谈论副作用时,他们通常最关心的是 IO(如上面的警报对话框)或写入状态超出函数的执行期。
副作用的挑战在于它们使函数更难推理和重用。(推理和重用尽可能接近“纯函数”的函数要容易得多,因为它们倾向于“做好一件事”。)
具有副作用的函数除了返回一个值之外还做其他事情(尽管它们也可能这样做)。如果您可以将给定参数的所有函数调用替换为这些参数的值并且程序具有相同的行为,则没有副作用。这要求函数总是为给定的参数返回相同的值。
也就是说,假设f(1,2) == 12
. 如果您始终可以替换f(1,2)
为12
并且程序的行为方式相同,则f
这些参数没有副作用。另一方面,如果在一个地方f(1,2) == 12
和另一个地方f(1,2) == 13
,则f
有副作用。f(1,2)
同样,如果程序在替换为 12后停止发送电子邮件,则会f
产生副作用。通常,如果f(x,y) == z
(其中 z 取决于 x 和 y)并且您始终可以将每个f(x,y)
调用替换为z
,则f
没有副作用。
一些有副作用的简单函数:
// doesn't always return the same value
function counter() {
// globals are bad
return ++x;
}
// omitting calls to `say` change logging behavior
function say(x) {
console.log(x);
return x;
}
副作用:
把副作用想象成同时做两件事的东西。例如:
副作用的经典示例:
var i = 1;
var j = i++;
副作用发生在i++
。这里发生的事情是j
变为 1 ,然后 i
递增并变为 2。换句话说,发生了两件事,副作用是i
变为 2。
关闭:
可视化这样的链接链:<><><><><><><>。想象一下,这个链接链的名称叫做作用域链。然后想象所有这些链接将对象连接在一起,如下所示:<>object<>object<>object<>。现在,请记住以下几点:
(1)所有作用域链都以全局对象开头。
(2)定义函数时,存储该函数的作用域链。
(3)调用函数时,它会创建一个新对象并将其添加到作用域链中。
现在,请看下面的例子:
function counter () { // define counter
var count = 0;
return function () { return count + 1;}; // define anonymous function
};
var count = counter(); // invoke counter
在本例中,当counter()
定义时,counter 的作用域链如下所示:<>global object<>。然后,当counter()
被调用时,作用域链看起来像这样:<>全局对象<>计数器对象<>。之后,定义并调用 counter 内部没有名称的函数(称为匿名函数)。匿名函数调用后的作用域链如下所示:<>全局对象<>计数器对象<>匿名函数对象<>
下面是闭包部分。如果您注意到,匿名函数正在使用在count
其外部定义的变量。原因是匿名函数可以访问其作用域链中定义的任何变量。这就是闭包,一个函数以及对其存储范围链中任何变量的引用。
然而,在上面的例子中,一旦函数返回,在调用时创建的对象就会被丢弃,所以真的没有意义。现在看看以下内容:
function counter () { // define counter
var count = 0;
function f() { return count + 1;}; // define f
return f; // return f
};
var count = counter(); // invoke counter
在此示例中,我返回一个名为的函数并将f
其分配给变量count
。现在该变量count
持有对整个作用域链的引用,并且不会被丢弃。换句话说,变量 count 像这样存储作用域链:<>global object<>counter object<>anonymous function object<>。这就是闭包的威力,你可以持有对作用域链的引用,并这样称呼它count()
:
示例
function outer() {
var outerVar;
var func = function() {
var innerVar
...
x = innerVar + outerVar
}
return func
}
当 outer() 死掉时,函数 func() 继续存在,这个用法很实用
我是 JavaScript 新手,不会尝试谈论闭包。然而,我对 JavaScript 的陌生使我非常清楚使用我常用的编程语言 (Erlang) 中不可能使用的副作用。
副作用似乎是改变 JavaScript 状态的常用方法。以 w3cschools.com 网站上的这个例子为例:
<script>
function myFunction() {
document.getElementById("demo").innerHTML = "Paragraph changed.";
}
</script>
这里没有输入参数或返回值,而是更改了文档的内容,因为它们在函数的范围内是全局的。例如,如果你用 Erlang 编写,文档将作为参数传入,并返回新的文档状态。阅读调用程序的人会看到传入的文档和返回的更改文档。
看到调用的函数不返回显式的新状态应该提醒程序员可能使用副作用。
主要的副作用是函数内部与外部世界的交互。副作用的一些示例是:- API 调用或 HTTP 请求、数据突变、打印到屏幕或控制台、DOM 查询/操作。例子 :
var a = 12
function addTwo(){
a = a + 2; // side-effect
}
addTwo()
闭包
根据 MDN,
闭包使您可以从内部函数访问外部函数的范围。在 JavaScript 中,每次创建函数时都会在创建函数时创建闭包。
例子 :
function outer(){
var a = 12; // Declared in outer function
function addTwo(){ // closure function
a = a + 2; // acessing outer function property
console.log(a)
}
addTwo();
}
outer()