亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

【譯】看權(quán)威的wikipedia如何解釋閉包

Alliot / 957人閱讀

摘要:準(zhǔn)確的說(shuō),是形成了的閉包。因此對(duì)象函數(shù)也是對(duì)象和控制流能通過(guò)閉包實(shí)現(xiàn)。這種方式的閉包不再具有引用透明性,即他不再是一個(gè)純函數(shù)。類閉包結(jié)構(gòu)一些語(yǔ)言的特性能夠模擬出閉包的效果。

寫在開(kāi)頭

?本來(lái)是很討厭談?wù)撻]包這個(gè)話題的,因?yàn)樵谶@一方面我比較傾向于玉伯還有一些朋友的觀點(diǎn),搞懂作用域才是最重要的,多帶帶談?wù)撻]包,真的意義不大。

?今天剛好在wiki上查其他東西的時(shí)候看到了,想了想以前也沒(méi)從比較科學(xué)的角度考慮這一問(wèn)題,所以寫下這篇文章,也算是記錄一個(gè)翻譯+閱讀的過(guò)程,原文中涉及很多其他概念,理解起來(lái)并不容易,本人知識(shí)水平和翻譯水平都非常有限,所以如果翻譯得有錯(cuò)誤,還望各位海涵,更歡迎各位批評(píng)指正~

關(guān)于原文

原文來(lái)自wikipedia中關(guān)于閉包的解釋(英文),有興趣的同學(xué)可以閱讀原文。同時(shí)還有一份本身的譯文,但是個(gè)人感覺(jué)這個(gè)省略了一些東西,有興趣也可以看看。

譯文內(nèi)容 閱讀前的提示

?因?yàn)槠渲星短讑A雜著許多其他概念,而這些概念可能又引出其他概念,所有這里提前約定下,下方譯文中的無(wú)序列表(即●開(kāi)頭的內(nèi)容)為對(duì)譯文中一些名詞所作的解釋。并且在譯文中對(duì)一些概念也會(huì)直接用超鏈接的方式鏈接到我認(rèn)為解釋得比較好的頁(yè)面。

正文開(kāi)始
閉包

在程序語(yǔ)言中,閉包(也叫詞法閉包或者函數(shù)閉包)是那些實(shí)現(xiàn)了詞法作用域命名綁定的把函數(shù)作為一等公民(原文這里叫做first-class functions)的語(yǔ)言中的一種技巧(或者說(shuō)特性)。

詞法作用域:詞法作用域也叫靜態(tài)作用域,是指作用域在詞法解析階段就已經(jīng)確定了,不會(huì)改變。這也是大多數(shù)語(yǔ)言采取的方式,JS也是如此,函數(shù)在他創(chuàng)建的地方運(yùn)行,而不是調(diào)用的地方。

動(dòng)態(tài)作用域:是指作用域在運(yùn)行時(shí)才能確定。參看下面的例子,引自楊志的回答

var foo=1;

function static(){
    alert(foo);
}

!function(){
    var foo=2;
    
    static();
}();

在js中,會(huì)彈出1而非2,因?yàn)閟tatic的scope在創(chuàng)建時(shí),記錄的foo是1。
如果js是動(dòng)態(tài)作用域,那么他應(yīng)該彈出2

在評(píng)論中賀師俊還提到,eval 和 with可以產(chǎn)生動(dòng)態(tài)作用域的效果:

比如 with(o) { console.log(x) } 這個(gè)x實(shí)際有可能是 o.x 。所以這就不是靜態(tài)(詞法)作用域了。
var x = 0; void function(code){ eval(code); console.log(x) }("var x=1")
不過(guò)注意eval在strict模式下被限制了,不再能形成動(dòng)態(tài)作用域了。

命名綁定:在程序語(yǔ)言中,命名綁定是一種關(guān)聯(lián),他將實(shí)體(數(shù)據(jù)或者說(shuō)是代碼)與標(biāo)識(shí)符聯(lián)系或者說(shuō)對(duì)應(yīng)起來(lái)。一個(gè)標(biāo)識(shí)符與一個(gè)對(duì)象綁定是指他持有這個(gè)對(duì)象的引用。機(jī)器語(yǔ)言沒(méi)有這個(gè)概念,但是程序語(yǔ)言實(shí)現(xiàn)了這一點(diǎn)。綁定與作用域密切相關(guān),作用域決定了某個(gè)名稱或者說(shuō)是標(biāo)識(shí)符到底與哪個(gè)對(duì)象相關(guān)聯(lián)。

first-class functions:在計(jì)算機(jī)科學(xué)中,如果一門語(yǔ)言把函數(shù)當(dāng)作一等公民來(lái)對(duì)待,我們稱他為first-class functions。具體來(lái)說(shuō),就是函數(shù)可以作為參數(shù)傳遞和返回,可以將它們作為變量聲明,可以將它們存儲(chǔ)在數(shù)據(jù)結(jié)構(gòu)中(比如我們常用的obj.xxx = somefunc)。同時(shí),有些語(yǔ)言還支持匿名函數(shù)(看到這里你應(yīng)該知道JS是屬于這類的)。在把函數(shù)當(dāng)作一等公民的語(yǔ)言中,函數(shù)名沒(méi)有任何特別的地方,它們就像普通變量名一樣。(這里感覺(jué)原文的重點(diǎn)是告訴大家不要把函數(shù)想得太過(guò)復(fù)雜,所以大家也就不要舉諸如函數(shù)名有l(wèi)ength,name這些屬性來(lái)反駁了)。

實(shí)際中來(lái)說(shuō),閉包是一種記錄,他將函數(shù)與他的上下文環(huán)境存儲(chǔ)起來(lái):它是一種將函數(shù)內(nèi)使用到的自由變量與他的值或者閉包被創(chuàng)建時(shí)那些指向其他地方的引用關(guān)聯(lián)起來(lái)的映射。注意這里的使用二字很重要(原文為variables that are used locally),我們可以看下方的代碼:

function fn1() {
    var a = 1;
    
    function fn2() {
        debugger;
        console.log(a);
    }

    fn2();
}

fn1();

這里我們?cè)趂n2中使用a,可以看到右圖中fn1形成了閉包。準(zhǔn)確的說(shuō),是fn1形成了fn2的閉包。

如果我們不使用a,那么就形成不了閉包。

這里糾正一個(gè)誤區(qū),很多人認(rèn)為必須要返回函數(shù)才能形成閉包,比如上面必須要在fn1中返回fn2,然后fn1()()這樣調(diào)用才會(huì)形成閉包,其實(shí)通過(guò)上面的截圖我們可以發(fā)現(xiàn)并不是這樣。

同時(shí)要注意,識(shí)別閉包在詞法分析階段就已經(jīng)確定了,意思是說(shuō)即使我們可以肯定用不到a,fn1也會(huì)識(shí)別為fn2的閉包,因?yàn)槲覀?使用"了a。如下所示:

自由變量:在計(jì)算機(jī)程序中,自由變量是指在一個(gè)函數(shù)中即不是局部變量也不是函數(shù)參數(shù)的變量。他與非局部變量是同義詞。

非局部變量:是指未定義在本作用域或者說(shuō)當(dāng)前作用域里的變量。也就是我們常說(shuō)的外部變量。舉例來(lái)說(shuō),在函數(shù)func中使用變量x,卻沒(méi)有在func中聲明(即在其他作用域中聲明的),對(duì)于func的作用域來(lái)說(shuō),x就是非局部變量。

var x = 1;

function func() {
    console.log(x);
}

應(yīng)用

閉包被用作實(shí)現(xiàn)連續(xù)式風(fēng)格,并且在這種風(fēng)格中隱藏狀態(tài)。因此對(duì)象(函數(shù)也是對(duì)象)和控制流能通過(guò)閉包實(shí)現(xiàn)。在一些語(yǔ)言中,閉包發(fā)生于在一個(gè)函數(shù)內(nèi)定義另一個(gè)函數(shù),在內(nèi)部函數(shù)中我們引用了外部函數(shù)里的局部變量(就是上圖中的例子)。在運(yùn)行時(shí),當(dāng)外部函數(shù)運(yùn)行的時(shí)候,一個(gè)閉包就形成了,他由內(nèi)部函數(shù)的代碼以及任何內(nèi)部函數(shù)中指向外部函數(shù)局部變量的引用組成。

連續(xù)式風(fēng)格(continuation-passing style,簡(jiǎn)稱CPS):在函數(shù)式編程中,CPS是一種編程風(fēng)格,在這種風(fēng)格中,控制流以一種續(xù)延的形式傳遞。這個(gè)概念真的很難解釋,包括我自己也不是太明白,所以建議搜索或者參考怎樣理解 Continuation-passing style以及尾遞歸與Continuation以及續(xù)延(continuation)以及函數(shù)式編程中cps是什么意思以及Continuation-passing style原文
個(gè)人建議結(jié)合原文然后再去看尾遞歸那一篇理解效果會(huì)比較好。這個(gè)概念真的很有趣,對(duì)FP來(lái)說(shuō)意義重大,有興趣的朋友可以詳細(xì)去了解

連續(xù)式風(fēng)格與直接式風(fēng)格相對(duì)。

直接式風(fēng)格(direct style):也是語(yǔ)言中常用的風(fēng)格。他是順序程序設(shè)計(jì)中常用的,在這之中,控制流通過(guò)運(yùn)行下一行被子程序調(diào)用實(shí)現(xiàn)顯示的傳遞,或者通過(guò)像return, yield, await這樣的結(jié)構(gòu)實(shí)現(xiàn)。

CPS與direct style對(duì)比的Example,摘自wiki
For example in Dart(例子以這種語(yǔ)言書寫), 一個(gè)循環(huán)動(dòng)畫可能以下面形式書寫

Continuation-passing style(CPS風(fēng)格)
var running = true;    // Set false to stop.

tick(time) {
  context.clearRect(0, 0, 500, 500);
  context.fillRect(time % 450, 20, 50, 50);

  if (running) window.animationFrame.then(tick);
}

window.animationFrame.then(tick);

在CPS中,在下一幀中異步調(diào)用window.animationFrame,然后調(diào)用回調(diào)(tick)函數(shù)。
這個(gè)回調(diào)函數(shù)需要在尾部再次調(diào)用,也就是要形成尾遞歸
Direct style(直接式風(fēng)格)
var running = true;    // Set false to stop.

while (running) {
  var time = await window.animationFrame;
  context.clearRect(0, 0, 500, 500);
  context.fillRect(time % 450, 20, 50, 50);
}

在直接式風(fēng)格中,異步調(diào)用window.animationFrame簡(jiǎn)單的yield控制流,然后繼續(xù)執(zhí)行。
一個(gè)while循環(huán)可以代替遞歸調(diào)用

在主流語(yǔ)言中,CPS常常發(fā)生在將閉包作為函數(shù)參數(shù)傳遞的時(shí)候,因此直接式風(fēng)格更簡(jiǎn)單的意味著函數(shù)返回了一個(gè)值,而不是攜帶了一個(gè)函數(shù)參數(shù)。

控制流:在計(jì)算機(jī)科學(xué)中,控制流(也稱作流控制)是一種順序,在這種順序中,個(gè)體的語(yǔ)句,指令,函數(shù)調(diào)用以命令式執(zhí)行或者解析,它強(qiáng)調(diào)控制流,這是與聲明式有差異的地方??梢曰叵胍幌挛覀兂.嫷某绦驁?zhí)行流程圖,就是控制流的一種體現(xiàn)。關(guān)于命令式與聲明式,可以參考命令式與聲明式的區(qū)別-1,命令式與聲明式的區(qū)別-2

中斷和信號(hào)是低等級(jí)的改變控制流的機(jī)制(應(yīng)該就是指break,continue,throw這一類),但是通常發(fā)生時(shí)被當(dāng)作一種對(duì)外部刺激或者事件(也可能異步發(fā)生)的響應(yīng),而不是一個(gè)內(nèi)聯(lián)控制流語(yǔ)句的執(zhí)行。在機(jī)器語(yǔ)言或者匯編語(yǔ)言層面,控制流常常通過(guò)程序計(jì)數(shù)器PC來(lái)改變。對(duì)一些CPU而言,唯一可用的控制流指令就是條件指令(類似于if)和非條件分支指令(原文為also called jumps,就是我們常說(shuō)的goto)。

狀態(tài)表示

閉包能被用作與函數(shù)的私有變量相關(guān)聯(lián),讓外部調(diào)用呈現(xiàn)連續(xù)性(比如一個(gè)高階函數(shù)實(shí)現(xiàn)累加)。私有變量只能被內(nèi)部函數(shù)訪問(wèn)到,其他任何地方都訪問(wèn)不到這個(gè)變量。

因此,在有狀態(tài)語(yǔ)言中,閉包能被用來(lái)實(shí)現(xiàn)狀態(tài)表示和信息隱藏(可以理解為私有變量),因此,閉包內(nèi)的局部變量的生命周期是不確定的,所以創(chuàng)建的一個(gè)變量在函數(shù)被下一次調(diào)用時(shí)仍然可用。這種方式的閉包不再具有引用透明性,即他不再是一個(gè)純函數(shù)。

退出閉包

在其他詞法作用域結(jié)構(gòu)中,閉包是存在很多區(qū)別的。比如return,break 和 continue語(yǔ)句。一般來(lái)說(shuō),這些結(jié)構(gòu)被認(rèn)為脫離延續(xù)(原文為escape continuation,異常處理就屬于這類),即脫離一個(gè)封閉的語(yǔ)句(如break和continue,從函數(shù)遞歸調(diào)用的角度講,這些指令被認(rèn)為需要循環(huán)結(jié)構(gòu)才能工作)。

在一些語(yǔ)言中,例如ECMAScript,return指向了詞法閉包語(yǔ)句建立的最內(nèi)層的continuation,在閉包中return將控制流轉(zhuǎn)移到調(diào)用它的代碼。

然而,在Smalltalk語(yǔ)言中,對(duì)于方法調(diào)用,有一個(gè)表面上很相似的操作符^,它調(diào)用建立的escape continuation,忽略任何中間的嵌套閉包。一個(gè)特定閉包中escape continuation只能在達(dá)到閉包代碼結(jié)束的時(shí)候被顯式調(diào)用。下面的例子展示了這之間的區(qū)別:

"Smalltalk"
foo
  | xs |
  xs := #(1 2 3 4).
  xs do: [:x | ^x].
  ^0
bar
  Transcript show: (self foo printString) "prints 1"
// ECMAScript
function foo() {
  var xs = [1, 2, 3, 4];
  xs.forEach(function (x) { return x; });
  return 0;
}
alert(foo()); // prints 0

上面的代碼片段描述了在Smalltalk中的^操作符與JS中的return操作符的行為并不是相同的。在上面的JS中,return x將會(huì)離開(kāi)內(nèi)層閉包并開(kāi)始forEach循環(huán)的下一次迭代,而在Smalltalk中,^x將會(huì)終止循環(huán)并從foo方法返回。

類閉包結(jié)構(gòu)

一些語(yǔ)言的特性能夠模擬出閉包的效果。包括那些面向?qū)ο蟮恼Z(yǔ)言,如JAVA,C++,OC,C#,D,有這方面興趣的朋友可以看看原文,這里我們選出原文中提到的關(guān)于JAVA的部分作為介紹:

Java中的局部類與lambda函數(shù)

Java中可以將類定義在方法內(nèi)部。我們把這叫做局部類(包括方法內(nèi)部類和匿名內(nèi)部類)。當(dāng)這些類沒(méi)有名稱時(shí),我們把它們叫做匿名類或者匿名內(nèi)部類。一個(gè)局部類中可以引用閉包類中的變量,或者閉包方法中的final變量。

class CalculationWindow extends JFrame {
  private volatile int result;
  ...
  public void calculateInSeparateThread(final URI uri) {
    // "new Runnable() { ... }"是一個(gè)實(shí)現(xiàn)了Runnable接口的匿名內(nèi)部類
    new Thread(
      new Runnable() {
        void run() {
          // 他可以訪問(wèn)局部的final變量
          calculate(uri);
          
          // 他可以訪問(wèn)閉包類的成員變量
          result = result + 10;
        }
      }
    ).start();
  }
}

隨著JAVA8支持lambda表達(dá)式,上面的代碼可以改寫成如下形式:

class CalculationWindow extends JFrame {
  private volatile int result;
  ...
  public void calculateInSeparateThread(final URI uri) {
    // 下面的形如 code () -> { /* code */ } 就是一個(gè)閉包
    new Thread(() -> {
        calculate(uri);
        result = result + 10;
    }).start();
  }
}

局部類是內(nèi)部類的一種,他們被聲明在方法體中。Java也支持在閉包類中聲明非靜態(tài)內(nèi)部類(就是我們常說(shuō)的成員內(nèi)部類)。他們都叫做內(nèi)部類。他們?cè)陂]包類中定義,也完全能夠訪問(wèn)閉包類的實(shí)例。由于他們與實(shí)例相綁定,一個(gè)內(nèi)部類也許要使用特殊的語(yǔ)法才能被實(shí)例化(即必須先實(shí)例化外部類,再通過(guò)外部類實(shí)例化內(nèi)部類,當(dāng)然靜態(tài)內(nèi)部類不需要這樣,這里指的是成員內(nèi)部類)。

public class EnclosingClass {
  /* 定義成員內(nèi)部類 */
  public class InnerClass {
    public int incrementAndReturnCounter() {
      return counter++;
    }
  }

  private int counter;

  {
    counter = 0;
  }

  public int getCounter() {
    return counter;
  }

  public static void main(String[] args) {
    EnclosingClass enclosingClassInstance = new EnclosingClass();
    /* 通過(guò)外部類的實(shí)例來(lái)實(shí)例化內(nèi)部類 */
    EnclosingClass.InnerClass innerClassInstance =
      enclosingClassInstance.new InnerClass();

    for(int i = enclosingClassInstance.getCounter(); (i =
    innerClassInstance.incrementAndReturnCounter()) < 10;) {
      System.out.println(i); // 在運(yùn)行之后,會(huì)打印0到9。
    }
  }
}

從Java8起,Java也將函數(shù)作為一等公民。Lambda表達(dá)式是一種具體體現(xiàn),它被當(dāng)作Function類型,其中T是輸入,U是輸出。表達(dá)式能被他的apply方法調(diào)用,而不是標(biāo)準(zhǔn)的call方法。

public static void main(String[] args) {
  Function length = s -> s.length(); // 原文這里的length沒(méi)有括號(hào),明顯是錯(cuò)誤的
  
  System.out.println( length.apply("Hello, world!") ); // Will print 13.
}
寫在結(jié)尾

在準(zhǔn)備翻譯之前,沒(méi)有想到其中有很多其他概念,通過(guò)這些概念,也學(xué)習(xí)到了很多其他方面的知識(shí),原始概念大多來(lái)自于自然科學(xué)(以數(shù)學(xué)為主,這里推薦一篇從20世紀(jì)數(shù)學(xué)危機(jī)到圖靈機(jī)到命令式與FP),且大部分都在在上世紀(jì)6,70年代就已經(jīng)提出。

無(wú)論發(fā)揚(yáng)這些理論的先行者,還是正在學(xué)習(xí)也許會(huì)發(fā)揚(yáng)下一個(gè)理論的我們,還是最初那些提出這些概念的前輩,我們都確實(shí)是在站在巨人的肩膀上。

由于水平和精力有限,也只是對(duì)其中一些概念做了簡(jiǎn)單的介紹,也希望拋磚引玉,歡迎各位補(bǔ)充~

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/80789.html

相關(guān)文章

  • [] 如何恰當(dāng)?shù)貙W(xué)習(xí) JavaScript

    摘要:原文鏈接恰當(dāng)?shù)貙W(xué)習(xí)適合第一次編程和非的程序員持續(xù)時(shí)間到周前提無(wú)需編程經(jīng)驗(yàn)繼續(xù)下面的課程。如果你沒(méi)有足夠的時(shí)間在周內(nèi)完成全部的章節(jié),學(xué)習(xí)時(shí)間盡力不要超過(guò)周。你還不是一個(gè)絕地武士,必須持續(xù)使用你最新學(xué)到的知識(shí)和技能,盡可能地經(jīng)常持續(xù)學(xué)習(xí)和提高。 原文鏈接:How to Learn JavaScript Properly 恰當(dāng)?shù)貙W(xué)習(xí) JavaScript (適合第一次編程和非 JavaSc...

    Jason 評(píng)論0 收藏0
  • 】JavaScript是如何工作:內(nèi)存管理 + 如何處理4個(gè)常見(jiàn)內(nèi)存泄露

    摘要:本文作為第三篇,將會(huì)討論另一個(gè)開(kāi)發(fā)者容易忽視的重要主題內(nèi)存管理。我們也會(huì)提供一些關(guān)于如何處理內(nèi)存泄露的技巧。這是當(dāng)前整型和雙精度的大小。然而,這是一組可以收集的內(nèi)存空間的近似值。 本文轉(zhuǎn)載自:眾成翻譯譯者:Leslie Wang審校: 為之漫筆鏈接:http://www.zcfy.cc/article/4211原文:https://blog.sessionstack.com/how-j...

    IntMain 評(píng)論0 收藏0
  • [] 你應(yīng)了解4種JS設(shè)計(jì)模式

    摘要:盡管特定環(huán)境下有各種各樣的設(shè)計(jì)模式,開(kāi)發(fā)者還是傾向于使用一些習(xí)慣性的模式。原型設(shè)計(jì)模式依賴于原型繼承原型模式主要用于為高性能環(huán)境創(chuàng)建對(duì)象。對(duì)于一個(gè)新創(chuàng)建的對(duì)象,它將保持構(gòu)造器初始化的狀態(tài)。這樣做主要是為了避免訂閱者和發(fā)布者之間的依賴。 2016-10-07 每個(gè)JS開(kāi)發(fā)者都力求寫出可維護(hù)、復(fù)用性和可讀性高的代碼。隨著應(yīng)用不斷擴(kuò)大,代碼組織的合理性也越來(lái)越重要。設(shè)計(jì)模式為特定環(huán)境下的常見(jiàn)...

    awokezhou 評(píng)論0 收藏0
  • JavaScript中閉包

    摘要:閉包引起的內(nèi)存泄漏總結(jié)從理論的角度將由于作用域鏈的特性中所有函數(shù)都是閉包但是從應(yīng)用的角度來(lái)說(shuō)只有當(dāng)函數(shù)以返回值返回或者當(dāng)函數(shù)以參數(shù)形式使用或者當(dāng)函數(shù)中自由變量在函數(shù)外被引用時(shí)才能成為明確意義上的閉包。 文章同步到github js的閉包概念幾乎是任何面試官都會(huì)問(wèn)的問(wèn)題,最近把閉包這塊的概念梳理了一下,記錄成以下文章。 什么是閉包 我先列出一些官方及經(jīng)典書籍等書中給出的概念,這些概念雖然...

    HmyBmny 評(píng)論0 收藏0
  • JavaScript系列(四) - 收藏集 - 掘金

    摘要:函數(shù)式編程前端掘金引言面向?qū)ο缶幊桃恢币詠?lái)都是中的主導(dǎo)范式。函數(shù)式編程是一種強(qiáng)調(diào)減少對(duì)程序外部狀態(tài)產(chǎn)生改變的方式。 JavaScript 函數(shù)式編程 - 前端 - 掘金引言 面向?qū)ο缶幊桃恢币詠?lái)都是JavaScript中的主導(dǎo)范式。JavaScript作為一門多范式編程語(yǔ)言,然而,近幾年,函數(shù)式編程越來(lái)越多得受到開(kāi)發(fā)者的青睞。函數(shù)式編程是一種強(qiáng)調(diào)減少對(duì)程序外部狀態(tài)產(chǎn)生改變的方式。因此,...

    cfanr 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<