简明英语的 Javascript 闭包和副作用? (分别地)

Posted

技术标签:

【中文标题】简明英语的 Javascript 闭包和副作用? (分别地)【英文标题】:Javascript closures and side effects in plain English? (separately) 【发布时间】:2011-12-29 01:40:43 【问题描述】:

我一直在阅读一些 javascript 书籍,并且总是听说闭包和副作用。出于某种原因,我无法理解它们的真正含义。谁能用简单的英语和例子向我解释它们是什么? (正如您向具有图形设计师编程水平的人解释的那样)。

【问题讨论】:

请澄清:您是否要求解释闭包并单独解释副作用?还是两者结合? @delnan 抱歉我修改了标题。 然后查看***.com/questions/111102/… 并删除闭包部分,这将是重复的。副作用也不是完全新的,但我不知道关于 SO 的任何具体内容,所以我暂时不投票关闭。 这是两个问题,一个是关于副作用的,一个是关于闭包的,应该分开。正如 delnan 所指出的,闭包之前已经介绍过,所以最好提出这个关于副作用的问题,并参考另一个问题来了解关于闭包的解释。 【参考方案1】:

副作用是更简单的概念。 “纯函数”是将其输入值映射到输出值function plus(x, y) return x + y; 的函数。 “副作用”是返回值以外的任何影响。所以,例如:

function plusWithSideEffects(x, y) 
  alert('This is a side effect'); 
  return x + y;
 

具有引发警报对话框(并需要用户交互)的副作用。每个代码函数都有一些副作用(它们都消耗内存并且需要时间,如果没有别的),但是当人们谈论副作用时,他们通常最关心的是任一 IO(如上面的警报对话框) ) 或写入超出函数执行期的状态。

副作用的挑战在于它们使函数更难推理和重用。 (推理和重用尽可能接近“纯函数”的函数要容易得多,因为它们倾向于“做好一件事”。)

【讨论】:

对于“平面设计师的编程水平”,这是一个真正的答案。 我不会对此投反对票,因为答案确实回答了这个问题,但是,我不喜欢示例中使用的 def 关键字,因为它不是 javascript 保留字,而是 python 的东西不是吗? @zappa def 不是 Python 特定的,并且该示例在 Python 中不起作用,因为它有大括号并且没有正确的缩进。这更像是伪代码甚至数学中的一种表示法,用于显示什么是纯函数。 @Haralan Dobrev,谢谢你帮我解决这个问题 这里使用def真的合理吗?这是一个特定于 Javascript 的问题,否则代码完全是 Javascript。除了def 之外,没有任何关于它的。只需将其更改为function...【参考方案2】:

具有副作用的函数除了返回一个值之外还做其他事情(尽管它们也可能这样做)。如果您可以将给定参数的所有函数调用替换为这些参数的值,并且程序具有相同的行为,则没有副作用。这要求函数总是为给定的参数返回相同的值。

也就是说,假设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;

【讨论】:

【参考方案3】:

副作用:

把副作用想象成同时做两件事的事情。 例如:

副作用的经典示例:

var i = 1;
var j = i++;

副作用发生在i++。这里发生的是j 变为 1 然后 i 增加并变为 2。换句话说,发生了两件事,副作用是 i 变为 2。

关闭:

想象一下这样的链接链:。 想象一下这个链接链的名字叫做作用域链。然后想象所有这些链接将 objects 连接在一起,如下所示:objectobjectobject。 现在,请记住以下几点:

(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 objectcounter objectanonymous function object。这就是闭包的威力,你可以持有对作用域链的引用,这样称呼它:count()

【讨论】:

此答案的结束部分不正确。 JavaScript有static scope,也就是说一个函数的作用域链是定义在functiondefinitionnotfunctioninvocation。该示例之所以有效,是因为每次调用 counter 时都会定义匿名函数,这意味着每个返回的函数都将具有一个作用域链,其中 counter 具有不同的激活对象,因此一组不同的局部变量(例如count)。 See this answer for more info. @josh3736:感谢您的评论。我改变了我的答案,如果有什么问题,请指教。另外,我想评论一下,对于全局函数声明,函数只会引用包含全局对象的作用域链。直到你调用它才会有对激活对象的引用。【参考方案4】:

示例

function outer() 
    var outerVar;

    var func = function() 
        var innerVar
        ...
        x = innerVar + outerVar
    
    return func

当 outer() 死掉时,函数 func() 继续存在,这个用法很实用

【讨论】:

【参考方案5】:

我是 JavaScript 新手,不会尝试谈论闭包。然而,我对 JavaScript 的陌生使我非常清楚使用我常用的编程语言 (Erlang) 中不可能使用的副作用。

副作用似乎是改变 JavaScript 状态的常用方法。以 w3cschools.com 网站上的这个例子为例:

<script>
function myFunction() 
    document.getElementById("demo").innerhtml = "Paragraph changed.";

</script>

这里没有输入参数或返回值,而是更改了文档的内容,因为它们在函数范围内是全局的。例如,如果你用 Erlang 编写,文档将作为参数传入,并返回新的文档状态。阅读调用程序的人会看到传入的文档和返回的更改文档。

看到调用的函数不返回显式的新状态应该提醒程序员可能使用副作用。

【讨论】:

【参考方案6】:

主要的副作用是函数内部与外部世界的交互。副作用的一些例子是: - 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()

【讨论】:

以上是关于简明英语的 Javascript 闭包和副作用? (分别地)的主要内容,如果未能解决你的问题,请参考以下文章

javaScript——作用域和闭包概念

JavaScript 作用域和闭包

JavaScript学习总结2--作用域和闭包

浅析JavaScript闭包

JavaScript ---- 闭包(什么是闭包,为什么使用闭包,闭包的作用)

JavaScript闭包的用法和场景