闭包是什么

  • 第一种说法:闭包创建一个词法作用域,这个作用域里面的变量被引用之后可以在这个词法作用域外面被自由访问,是一个函数和声明该函数的词法环境的组合
  • 第二种说法:闭包就是引用了自由变量的函数,这个自由变量与函数一同存在,即使脱离了创建它的环境。所以经常看到的说闭包就是绑定了上下文环境的函数。
  • 我更偏向于闭包是一个函数和声明该函数的词法环境的组合。

JS里面的闭包

先上一个闭包

function sayHello(name){
    let str = 'Hello,${name}';
    function say(){
        console.log(str);
    }
    return say;
}

let myHello = sayHello('abby');
myHello();

该例子的解释

上面的代码,在sayHello函数里面定义的say函数和这个函数声明的词法环境就形成了一个闭包。say函数引用了sayHello函数里面定义的一个变量str,并且sayHello函数将say这个函数return了出去,这样,在sayHello函数的外面也能访问到它词法作用域里面的变量str,最后就像say这个函数和str这个变量绑定了一样

为什么在外部还能访问到变量str呢?

  • 在一些语言中,一般认为函数的局部变量只在函数的执行期间可以访问
  • 当上段代码在执行到let myHello = sayHello('abby');这段代码的时候,按理会销毁掉sayHello这个函数的执行环境,但是在这里却没有,因为,sayHello这个函数返回的是一个函数,这个函数里面的str引用了外部的变量str,如果销毁了sayHello的执行环境就会找不到了,所以,sayHello的执行环境会一直在内存中,所以也就会有闭包会增加内存开销的说法

体会

  • 在JavaScript语言中,只有函数内部的子函数才能读取内部变量,可以把闭包简单理解成“定义在一个函数内部的函数”
  • 闭包就是将函数内部和函数外部连接起来的一座桥梁

闭包的用处

1、读取函数内部的变量

2、让这些变量始终保持在内存中

function createIncrementor(start) {
    return function () {
        return start++;
    };
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7

start是函数createIncrementor的内部变量,通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算,闭包inc使得函数createIncrementor的内部环境一直存在,因为inc始终存在内存中,而inc的存在依赖于createIncrementor,因此该函数不会在调用结束后,被垃圾回收机制回收

3、封装对象的私有属性和私有方法

function Person(name) {
    var _age;
    function setAge(n) {
        _age = n;
    }
    function getAge() {
        return _age;
    }
    return {
        name: name,
        getAge: getAge,
        setAge: setAge
    };
}

var p1 = Person("xiaoming");
p1.setAge(25);
pa.getAge(); //25

函数Person的内部变量_age,通过闭包getAge和setAge,变成了返回对象p1的私有变量,外层函数每次运行,都会产生一个新的闭包,而这个闭包又会保留外层函数的内部变量,内存也就消耗较多

在举几个例子

1、常见的闭包都是return出来一个函数,但并不是说明,闭包一定需要return一个函数,return一个函数也只是为了能在作用域范围之外访问一个变量

let say;
function sayHello(name){
    let str = 'Hello,${name}';
    say = function(){
        console.log(str);
    }
}

let myHello = sayHello('abby');
say();

2、同一个调用函数生成同一个闭包环境,在里面声明的所有函数同时具有这个环境里面的变量的引用

let get,up,down
function setUp(){
    let number = 20;
    get = function(){
        console.log(number);
    }
    up = function(){
        number += 3;
    }
    down = function(){
        number -= 2;
    }
}

setUp();
get();
up();
down();
get();

3、每一个调用函数都会创建不同的闭包环境,里面的变量互不影响

function newClosure(){
    let array = [1,2];
    return function(num){
        array.push(num);
        console.log('array:${array}');
    }
}

let myClosure = newClosure();
let yourClosure = newClosure();
myClosure(3);
yourClosure(4);
myClosure(5);

4、在循环里面创建闭包

function newClosure(){
    for(var i=0;i<5;i++){
        setTimeout(function(){
            console.log(i);
        });
    }
}

newClosure();//5个5

改进方法一:创建一个新的闭包对象,这样每个闭包对象里面的变量就互不影响

function log(i){
    return function(){
        console.log(i);
    }
}

function newClosure(){
    for(var i=0;i<5;i++){
        setTimeout(log(i));
    }
}
newClosure();

每次log(i)都会创建不同的闭包对象,所有的回调函数不会指向同一个环境

改进方法二:使用自执行函数,外部的匿名函数会立即执行,并且把i作为它的参数,此时函数内变量e就拥有了i的一个拷贝。当传递给setTimeout的匿名函数执行时,它就拥有了对e的引用,而这个值是不会被循环改变的

function newClosure(){
    for(var i=0;i<5;i++){
        (function(e){
            setTimeout(function(){
                console.log(e);
            });
        })(i)
    }
}

newClosure();




pitaojin
326 声望36 粉丝