JS read an article closures have 2020, you can still do not understand how closures?

 One ❀ lead

I think every JavaScript worker can not avoid dealing with the closures, but does not use the interview also asked about the norm even in the actual development. For my part for the understanding of closures only stop at some of the concepts, see the relevant code I know this is a closure, but closure which can solve the problem of multi-scene I do not know, this is what I wanted to organize a closure s reason. Let's look at a piece of code, it is clear that this is a closure, then ask to refer the closure of which part of the code below it? Beginning of this article.

function outer() {
    name the let = 'hear the wind is the wind' ;

    function insider() {
        console.log ( `$ {name} Welcome to the blog ');
    };
    return insider;
};
Outer () (); // Welcome to hear the wind is the wind's blog

 II ❀ What is closure?

If asked what is the closure, the answer in most cases is lower (at least I used to be) in the interview A nested function B function, B function uses the function of internal variables A, B and A function returns a function, this is closure .

This description, of course, no problem, then in order to let the next interview answer is more beautiful, let us to rethink the closure from a more professional point of view.

1. Closure Origin

Closures translated from the English word closure ([kloʊʒər] collapse, close, closure) , concept closure first appeared in 1964 in the academic journal "The Computer Journal" on by PJ Landin in " at The Mechanical Evaluation of Expressions " in an article mentioned. 

In this JavaScript, Java and even C language are not born of the 1960s, mainstream programming language is based on the lambda calculus Functional programming languages. And in the earliest closure concepts described in a large number of functional terms, trying to convey meaning about λ expression with a series of information for functional language, λ is the function expression .

Early closure of the environment (an execution environment identifier list) with the expression of two parts, and this corresponds to the composition of JavaScript, the environment corresponding to the right part of the lexical function environment and context identifier list performed JS , expression then the corresponding part of the body of the function of JS .

So here, we know JavaScript closures in the original closure concept is highly consistent, λ expressions with a series of information corresponding to the JavaScript in the past, called the closure is actually a native execution environment (from the outside layer function provided, even if the destruction of the outer function can still access) special functions ; then back to the beginning of the article in question, this code refers to a range of closures generation is the internal function insider, rather than the outer external function included, We got to figure out.

2. Closure wherein

To understand the origins of JavaScript closures, we then take a look at other documents to explain the closure, a deeper impression and summary about what features have closure.

Baidu Encyclopedia:

Closure to read other functions is a function of internal variables. For example in javascript, only the internal function subroutine to read the local variables, so that the closure can be understood as "in the definition of a function within the function."

"JavaScript Advanced Programming Guide":

Closure means has access to the additional function of a variable function scopes.

MDN (explained a few years ago, now updated):

Closures are those functions can be accessed free variables.

Early MDN explanation is more interesting, what is a free variable? It refers to the free variables used in the function, but neither the function nor the variable function parameter arguments local variables. Look at an example:

A. 1 = the let; // free variable

function fn() {
    console.log(a);
};
fn(); //1

For example this example, the variable a does not belong to the function fn, but because of the function fn scope chain, or can be used normally variable a.

Here students will certainly have doubts, MDN does not describe it, is a function fn first, followed by the fn uses free variables a, it would mean that fn is a closure?

That is the truth, the "JavaScript The Definitive Guide," a clear reference to the book, from a theoretical point of view, JavaScript, all functions are closures ....

Is not it for you to subvert the cognitive closures? The above said theoretical angle, stand art practice standpoint, is nothing more than the closure to the following two:

First, the closure must first be a function .

Second, the closure can access the free variables outside the scope of the function , even if the external function context has been destroyed .

So for now MDN described closure has been changed to "closure is a combination of function and create the environment lexical function together, this environment contains all the local variables created when the closure can access", and this is not it in line with our understanding of the characteristics of the closures in the front. We deepen the impression of closure features through an example:

let fn = function () {
    NUM the let =. 1; // free variable 
    return {
        a: function () {
            console.log(num);
        },
        b: function () {
            a ++ ;
        }
    };
};

closure the let = Fn ();
 // here outer function has already been performed, the execution context is released 
closure.a (); // . 1

In the above example, the execution returns to the function fn outer two closure a, b. We know that each time the function is called to perform will create a new execution context, executes the function when the function completes execution context stack is popped and destroyed, so let closure = fn () perform a complete implementation of the function fn context no longer exists but we perform closure.a () can see still access to the local variables of the outer function num.

To make this feeling even stronger, we directly call the function fn in destroying closure function again, you can see not only the access closures and even outer function in operating variables.

fn = null;
closure.b();
closure.a(); // 2

Is not it amazing? Why have destroyed the outer function context, the closure can have access to free variables do, which was to talk about the closure of a special scope chain.

 ❀ with three wonderful closure execution context see

In JavaScript scope refers to the scope of the variables and functions. When using a variable in a scope, will first look at this, there is no scope identifier, if no parent will go to look for, it has not been found so far the source window (window nor on the error), this the search process will form what we call the scope chain.

So in JavaScript specifically how this kind of process it, I  understand JS execution context of an article  in an article there is a detailed description of the implementation process execution context, so here I will briefly describe the next, let's look at an example:

let scope = "global scope";

function checkscope () {
     // This is a free variable 
    the let scope = "local scope" ;
     // this is a closure 
    function F () {
        console.log(scope);
    };
    return f;
};

let foo = checkscope();
foo();

We use the pseudo-code execution stack respectively, in the context of change, and process context is created, the first execution stack will always exist a global execution context .

// Create a global context 
ECStack = [GlobalExectionContext];

At this time, there is a global context, two variables scope, foo with a function checkscope , the context is specifically represented by this pseudocode:

// global context created 
GlobalExectionContext = {
     // the this the global object 
    the ThisBinding: <Object Free Join> ,
     // lexical environment 
    the LexicalEnvironment: {
         // Environment Record 
        EnvironmentRecord: {
            Type: "Object", // Object environment records 
            // identifier binding function here, let const variables created in this 
            scope: <uninitialized> ,
            foo: < uninitialized > ,
            checkscope: < func >
        }
        // global environment, the external environment is introduced as null 
        Outer: < null >
    }
}

Create a global context end phase into the implementation phase, global execution context identifiers like scope, foo like variable is assigned, then started checkscope function, they were creating a new function execution context, according to the advance after the execution stack the characteristics of the execution stack for now:

ECStack = [checkscopeExectionContext,GlobalExectionContext];

So checkscope function execution context also entered the stage of creation, its context we are also expressed in pseudo-code:

// function execution context 
checkscopeExectionContext = {
     // Since this function is called by default binding is also a global object 
    the ThisBinding: <Object Free Join> ,
     // lexical environment 
    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: "the Declarative", // declarative environment record 
            // identifier binding where arguments and local variables in this 
            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

Guess you like

Origin www.cnblogs.com/echolun/p/11897004.html