JSは、記事の閉鎖は2020を持って読んで、あなたはまだどのようにクロージャを理解していないことができますか?

 ワン❀リード

私はすべてのJavaScriptの労働者は、閉鎖を扱う避けることができないと思いますが、またしても実際の開発に規範について尋ねられたインタビューを使用していません。私の部分については、クロージャを理解するためにのみ、私は、これは閉鎖ですけど、私は知らないマルチシーンの問題を解決することができますクロージャが、これは私が閉鎖を整理したいものです関連するコードを参照して、概念の一部で停止理由。コードの一部で見てみましょうが、それ以下のコードのどの部分の閉鎖を参照するように依頼し、これはクロージャであることは明らかですか?この資料の冒頭。

機能外(){ 
    LET名 = '風が風で聞く' ; 

    機能インサイダー(){ 
        はconsole.log( `$ {name}がblog`へようこそ); 
    }; 
    リターンインサイダー; 
}; 
表地()(); //は風を聞くためにようこそ風のブログです

 II❀クロージャとは何ですか?

クロージャが何であるかを尋ねられた場合は、ほとんどの場合、答えはインタビューの中で(少なくとも私がために使用)低いAネストされた関数B関数、B関数はA、BおよびAの関数を返す関数の内部変数の関数を使用して、これはクロージャです

この説明は、当然のことながら、何の問題は、次の面接の答えは、より美しくさせるために、私たちは、ビューのより専門的な観点から閉鎖を再考する聞かせていません。

1.閉鎖起源

英語の単語から翻訳クロージャクロージャ([kloʊʒər]崩壊、近くに、閉鎖)、最初の1964の学術誌に登場したコンセプトの閉鎖「コンピュータ・ジャーナル」で上PJとLandin式の力学的評価で記事に言及しました。 

このJavaScript、JavaとでもC言語では、1960年代から生まれていない、主流のプログラミング言語が基づいている計算ラムダ関数型プログラミング言語。 そして最も初期の閉鎖コンセプトに、機能的な用語が多数に記述についての意味を伝えようと、一連の情報とλ表現関数型言語のため、λは、関数式です

早期の閉鎖発現と環境(実行環境識別子のリスト)のJavaScriptの組成、2つの部分の、この対応字句関数環境の右部分に対応する環境とコンテキスト識別子リストは、JSを実行し式次に、JSの機能の本体の対応する部分

そこでここでは、我々は、元の閉鎖概念でJavaScriptの閉鎖は、過去にはJavaScriptに対応した一連の情報とλ式で非常に一貫している知っている外部から(閉鎖が実際にネイティブ実行環境であると呼ばれますレイヤ機能に設けられ、たとえ外機能を破壊することができ、依然としてアクセス)特殊機能 ;戻し問題の資料の冒頭に、このコードが生成は、外側の外部関数が含まれるのではなく、内部機能インサイダーであるクロージャの範囲を指し、私たちは、把握するようになりました。

2.閉鎖前記

JavaScriptのクロージャの起源を理解するために、我々はその後閉鎖、閉鎖を持っていますかについて、より深い感動と概要を説明するために他の文書を見てみましょう。

Baiduの百科事典:

他の機能を読み込むための閉鎖は、内部変数の関数です。JavaScriptで、例えば、内部でのみ機能サブルーチンはクロージャは以下のように理解できるように、ローカル変数を読み取る「関数内の関数の定義に。」

「JavaScriptの高度なプログラミングガイド」:

閉鎖手段は、変数、関数スコープの追加機能へのアクセスを持っています。

MDNは(今更新し、数年前に説明しました):

クロージャは、これらの機能は、自由変数をアクセスすることができます。

早期MDNの説明は自由変数が何であるかを、もっと面白いのですか?これは、関数で使用される自由変数を指しますが、機能や引数のローカル変数パラメータ変数の関数でもありません。例を見てください:

A. 1 = LET; // 自由変数の

関数のFn(){ 
    にconsole.log(A); 
}; 
のFn(); // 1

例えばこの例では、変数aが関数fnに属していない、しかし理由関数fnスコープ鎖の、または通常は変数Aを使用することができます。

ここでは、学生は確かに疑問を持つことになり、MDNはそれを説明していません、FNが続く最初の関数fnは、自由変数、それはfnはクロージャであることを意味しますを使用しているのですか?

それが真実であり、「JavaScriptのDefinitive Guideの、」理論的な観点から本への明確な言及、は、JavaScript、すべての機能はクロージャです....

あなたが認知クロージャを覆すことはありませんか?上記の理論的な角度、スタンドアートの練習の観点は、より多くの2を次のように閉鎖よりも何もないと述べました。

まず、クロージャは、最初にしなければならない関数です

第二に、閉鎖はできる機能の範囲外で自由変数にアクセスし外部関数のコンテキストが破棄された場合でも

だから、今のMDNが閉鎖に変更された説明「閉鎖機能の組み合わせで、一緒に環境レキシカル関数を作成し、この環境は閉鎖がアクセスすることができたときに作成されたすべてのローカル変数が含まれている」、とこれはこれではありません前のクロージャの特性の理解とラインインチ ここでは、例を通じて閉鎖機能の印象を深めます。

FN = LETの関数(){ 
    せNUM = 1; // フリー変数
    リターン{ 
        A:関数(){ 
            にconsole.log(NUM); 
        }、
        B:関数(){ 
            NUM ++ ; 
        } 
    }; 
}; 

LET閉鎖 = のFn();
 // ここでアウター機能既に実行された、実行コンテキストが解放さ 
closure.a(); // 1

上記の例では、外側の2つの閉鎖A、B FN関数に実行戻ります。私たちは、実行コンテキストスタックをポップし、破壊された機能が完了したときに関数を実行し、その機能を実行するために呼び出されるたびに新しい実行コンテキストを作成することを知ってみましょう閉鎖= FN()は 関数fnコンテキストの完全な実装がもう存在しない実行しませんしかし、我々は実行closure.aを()はまだ外側の関数num個のローカル変数へのアクセスを見ることができます。

この感覚にも強いを作るために、我々は直接再び閉鎖機能を破壊する関数fnを呼び出して、あなただけでなく動作した変数でアクセス閉鎖、さらには外側の関数を見ることができます。

FN = nullを
closure.b(); 
closure.a(); // 2

それは驚くほどではないですか?なぜ、外側の関数のコンテキストを破壊し、閉鎖は特殊なスコープチェーンの閉鎖の話にあった、自由変数へのアクセス権を持つことができます。

 ❀3つの素晴らしい閉鎖実行コンテキストを参照してくださいと

JavaScriptのスコープ内の変数や関数の範囲を指します。スコープで変数を使用する場合は、まずそれは、これまでのソース・ウィンドウ(ウィンドウもエラーに)発見されていない、親が探しに行くんだろう場合は何のスコープ識別子は、存在しない、この時に、これを見ていきます検索プロセスは、我々はスコープチェーンと呼ぶものを形成します。

だから、JavaScriptでこのような処理は、それが、私は特にどのように  記事のJSの実行コンテキストを理解する  記事では、実装プロセスの実行コンテキストの詳細な説明は、私は簡単に、次の記述例での外観をできるようになるので、ここがあります:

範囲=う「グローバルスコープ」

関数checkscope(){
     // これは自由変数である 
    LETスコープ=「ローカルスコープ」;
     // これはクロージャである
    関数F(){ 
        にconsole.log(スコープ); 
    }; 
    リターンF; 
}; 

LETのFOO = checkscope(); 
FOO()。

我々は変化の文脈において、それぞれの擬似コードが実行されるスタックを使用し、プロセスコンテキストが作成され、最初の実行スタックは、常にグローバルな実行コンテキストが存在します

// グローバルコンテキスト作成 
ECStackを= [GlobalExectionContext]。

この時点で、グローバルコンテキストがあり、二つの変数のスコープ、関数checkscopeとfooは、コンテキストは、具体的には、この擬似コードで表されます。

// グローバルコンテキストが作成さ 
GlobalExectionContext = {
     // このグローバルオブジェクト 
    ThisBinding:<オブジェクト参加無料> // 字句環境
    LexicalEnvironment:{
         // 環境を録音
        EnvironmentRecord:{ 
            タイプ:「オブジェクト」、// オブジェクト環境レコード
            // 識別子ここで結合機能を、聞かせて、この中に作成されたのconst変数 
            のスコープ:<初期化されていない> 
            FOO: <初期化されていない> 
            checkscope: <FUNC> 
        } 
        // 地球環境、外部環境はヌルとして導入され 
        、<:外ヌル > 
    }
}

実施段階にグローバルコンテキスト終了フェーズを作成し、スコープのようなグローバル実行コンテキスト識別子、変数のようにfooが付与され、その後、開始checkscope機能、彼らは実行スタックした後、事前によると、新機能の実行コンテキストを作成しました。今の実行スタックの特性:

ECStack = [checkscopeExectionContext、GlobalExectionContext]。

checkscope機能実行コンテキストはまた、創造の段階、そのコンテキストに入ったので、我々はまた、擬似コードで表現されています。

// 函数执行上下文
checkscopeExectionContext = {
    //由于函数是默认调用 this绑定同样是全局对象
    ThisBinding: < Global Object > ,
    // 词法环境
    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: "Declarative", // 声明性环境记录
            // 标识符绑定在这里  arguments与局部变量在这
            Arguments: {},
            scope: < uninitialized > ,
            f: < func >
        },
        // 外部环境引入记录为</Global>
        outer: < GlobalLexicalEnvironment >
    }
}

由于 checkscope() 等同于 window.checkscope() ,所以在 checkExectionContext 中this指向全局,而且外部环境引用outer也指向了全局(作用域链),其次在标识符中我们可以看到记录了形参arguments对象以及一个变量scope与一个函数 f 。

函数 checkscope 执行到返回返回函数 f 时,函数执行完毕,checkscope 的执行上下文被弹出执行栈,所以此时执行栈中又只剩下全局执行上下文:

ECStack = [GlobalExectionContext];

代码执行又走到了foo(),foo函数被执行,于是foo的执行上下文被创建,执行栈中现在是这样:

ECStack = [fooExectionContext, GlobalExectionContext];

foo的执行上下文是这样:

fooExectionContext = {
    //由于函数是默认调用 this绑定同样是全局对象
    ThisBinding: < Global Object > ,
    // 词法环境
    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: "Declarative", // 声明性环境记录
            // 标识符绑定在这里  arguments与局部变量在这
            Arguments: {},
        },
        // 外部环境引入记录为</checkscope>
        outer: < checkscopeEnvironment >
    }
}

foo执行也等同于是window调用,所以this同样指向全局window,但outer外部环境引入有点不同,这里指向了外层函数 checkscope,为啥是checkscope?

我们知道JavaScript采用的是词法作用域,也就是静态作用域,函数的作用域在定义时就确定了,而不是执行时确定。看个小例子来巩固下静态作用域:

var a = 1;

function fn1() {
    console.log(a);
};

function fn2() {
    var a = 2;
    fn1(a);
};

fn2(); //1

这里输出1,这是因为 fn1 定义在全局作用域中,它能访问的作用域就是全局,即便我们在 fn2中 调用,它依旧只能访问定义它地方的作用域。

明白了这个概念,这下总能理解foo执行上下文outer外部环境引入为啥是 checkscopeExectionContext 了吧。

那也不对啊,现在执行栈中一共就 fooExectionContext 与 GlobalExectionContext 这两个,checkscopeExectionContext 早被释放了啊,怎么还能访问到 checkscope 中的变量。

正常来说确实是不可以,但是JavaScript骚就骚在这里,即使 checkscope 执行上下文被释放,因为闭包 foo 外部环境 outer 的引用,从而让 checkscope作用域中的变量依旧存活在内存中,无法被释放。

这也是为什么谈到闭包我们总是强调手动释放自由变量

这也是为什么文章开头我们说闭包是自带了执行环境的函数

那么闭包的理解就点到这里,让我们总结一句,闭包是指能使用其它作用域自由变量的函数,即使作用域已销毁。

如果你在阅读上下文这段有疑惑,如果你好奇为什么var存在变量声明提升而let没有,还是强烈阅读博主这篇文章 一篇文章看懂JS执行上下文 

 肆 ❀ 闭包有什么用?

说闭包聊闭包,结果闭包有啥用都不知道,甚至遇到了一个闭包第一时间都没反应过来这是闭包,这就是我以前的常态。那么我们专门说说闭包有啥用,不管用不用得上,作为了解也没坏处。

1.模拟私有属性、方法

在Java这类编程语言中是支持创建私有属性与方法的,所谓私有属性方法其实就是这些属性方法只能被同一个类中的其它方法所调用,但是JavaScript中并未提供专门用于创建私有属性的方法,但我们可以通过闭包模拟它,比如:

let fn = (function () {
    var privateCounter = 0;

    function changeBy(val) {
        privateCounter += val;
    };
    return {
        increment: function () {
            changeBy(1);
        },
        decrement: function () {
            changeBy(-1);
        },
        value: function () {
            console.log(privateCounter);
        }
    };
})();
Counter.value(); //0
Counter.increment();
Counter.increment();
Counter.value(); //2
Counter.decrement();
Counter.value(); //1

这个例子中我们通过自执行函数返回了一个对象,这个对象中包含了三个闭包方法,除了这三个方法能访问变量privateCounter与 changeBy函数外,你无法再通过其它手段操作它们。

构造函数大家不陌生吧,构造函数中也有闭包,直接上例子:

function Echo(name) {
    //这是一个私有属性
    var age = 26;
    //这些是构造器属性
    this.name = name;
    this.hello = function () {
        console.log(`我的名字是${this.name},我今年${age}了`);
    };
};
var person = new Echo('听风是风');
person.hello();//我的名字是听风是风,我今年26了

如果大家对于我说构造函数中使用了闭包有疑问,可以阅读博主这篇文章 js new一个对象的过程,实现一个简单的new方法 这篇文章,其实new过程都会隐性返回一个对象,这个对象中也包含了构造函数中构造器属性中的方法。

如果某个属性方法在所有实例中都需要使用,我们一般推荐加在构造函数的prototype原型链上,还有种做法就是利用私有属性。比如这个例子中所有实例都可以正常使用变量 age。同时我们将age称为私有属性的同时,我们也会将this.hello称为特权方法,因为你只有通过这个方法才能访问被保护的私有属性age啊。

我在JavaScript模式 精读JavaScript模式(七),命名空间模式,私有成员与静态成员 这篇文章中有介绍私有属性方法,静态属性法,特权方法,有兴趣也可以读读看(内链推的飞起...)。

2.工厂函数

什么是工厂函数?工厂函数给我的感觉与构造函数或者class类似,调用工厂函数就会生产该类(构造函数)的实例,我们举一个MDN的简单例子:

function makeAdder(x) {
    return function (y) {
        console.log(x + y);
    };
};

var a = makeAdder(5);
var b = makeAdder(10);
a(2); // 7
b(2); // 12

在这个例子中,我们利用了闭包自带执行环境的特性(即使外层作用域已销毁),仅仅使用一个形参完成了两个形参求和的骚操作,是不是很奈斯。

3.其它应用

闭包其实在很多框架中都是随处可见的,比如angularjs中可以自定义过滤器,而自定义过滤器的方式同样也是一个闭包,比如这样:

angular.module('myApp',[])
    .filter('filterName',function () {
        return function () {
            //do something
        };
    })

如果我没记错,vue创建过滤器的方式貌似也是闭包....

 伍 ❀ 闭包使用注意

说了这么多,闭包总给我们一种高逼格的感觉,其实说到底也就是自带执行环境的函数而已,如果你要使用闭包有些地方还真的注意一下。

1.闭包的性能与内存占用

我们已经知道了闭包是自带执行环境的函数,相比普通函数,闭包对于内存的占用还真就比普通函数大,毕竟外层函数的自由变量无法释放。

function bindEvent(){
    let ele = document.querySelector('.ele');
    ele.onclick = function () {
        console.log(ele.style.color);
    };
};
bindEvent();

比如这个例子中,由于点击事件中使用到了外层函数中的DOM ele,导致 ele 始终无法释放,大家都知道操作DOM本来是件不太友好的事情,你现在操作别人不说,还抓着不放了,你良心不会痛?

比如这个例子你要获取color属性,那就单独复制一份color属性,在外层函数执行完毕后手动释放ele,像这样:

function bindEvent() {
    let ele = document.querySelector('.ele');
    let color = ele.style.color;
    ele.onclick = function () {
        console.log(color);
    };
    ele = null;
};
bindEvent();

2.闭包中的this

闭包中的this也会让人产生误解,我们在前面说了静态作用域的概念,即函数作用域在定义时就已经确定了,而不是调用时确定。this这个东西我们也知道,this在最终调用时才确定,而不是定义时确定,跟静态作用域有点相反。

var name = "听风是风";
var obj = {
    name: "行星飞行",
    sayName: function () {
        return function () {
            console.log(this.name);
        };
    }
};

obj.sayName()(); //

猜猜这里输出什么,很遗憾这里输出外层的听风是风,具体为什么其实在上文中通过执行上下文看闭包就解释了,下面的解释看不懂就回去重新读一遍。

函数每次执行都会创建执行上下文,而上下文又由this、词法环境、变量环境以及外部环境引用等组成,我们只说作用域是可以继承的,没人说this指向也可以继承吧。我们上面的代码改改:

var a = obj.sayName()
a(); //等同于window.a()

this指向是不能像作用域一样存在链式的,执行第二个方法时其实是window在调用,这下明白没?

那么有同学就要问了,那我要用在闭包中使用外层函数的this咋办,这还不简单,保存this呗:

var name = "听风是风";
var obj = {
    name: "行星飞行",
    sayName: function () {
        var that = this;
        return function () {
            console.log(that.name);
        };
    }
};
obj.sayName()();//行星飞行

 陆 ❀ 总

那么到这里,我们从闭包的起源解释了JavaScript闭包的来源,了解到闭包其实就是自带了执行环境的函数,如果在以后的面试中有面试官问你闭包,我希望你能通过在这里学到的知识秀的对方头皮发麻。

除了知道闭包的概念,我们还从执行上下文的角度解释了为何闭包还能使用已销毁父级函数的自由变量,并复习了作用域,作用域链以及静态作用域的概念。

说闭包用闭包,我们介绍了几种常规的闭包用法,以及在实际使用中我们应该注意的点。

那么到这里闭包文章就算写完了,下一篇写this。

如果你对于本文描述存在疑惑或者本文存在描述错误,欢迎留言讨论,我会在第一时间回复你,毕竟对于一个孤独的人来说,收到陌生人的评论也是件开心的事。

 参考

 JavaScript深入之从作用域链理解闭包

JavaScript深入之闭包

深入javascript——作用域和闭包

MDN

おすすめ

転載: www.cnblogs.com/echolun/p/11897004.html