# 闭包

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

# 词法作用域

词法作用域根据源代码中声明变量的位置来确定该变量在何处可用。嵌套函数可访问声明于它们外部作用域的变量。

function init() {
    var name = "Mozilla"; // name 是一个被 init 创建的局部变量
    function displayName() { // displayName() 是内部函数,一个闭包
        alert(name); // 使用了父函数中声明的变量
    }
    displayName();
}
init();

displayName() 没有自己的局部变量。然而,因为它可以访问到外部函数的变量,所以 displayName() 可以使用父函数 init() 中声明的变量 name


function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();

displayName 的实例维持了一个对它的词法环境(变量 name 存在于其中)的引用。因此,当 myFunc 被调用时,变量 name 仍然可用,其值 Mozilla 就被传递到alert中。

# 闭包的作用

闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。这显然类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

# 通常你使用只有一个方法的对象的地方,都可以使用闭包。

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

# 用闭包模拟私有方法

JavaScript 没有这种将方法申明为私有的原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */

请注意两个计数器 Counter1 和 Counter2 是如何维护它们各自的独立性的。每个闭包都是引用自己词法作用域内的变量 privateCounter 。

每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。

以这种方式使用闭包,提供了许多与面向对象编程相关的好处 —— 特别是数据隐藏和封装

function test() {
  var apple = 'yideng';
  return function () {
    debugger;
    window.eval('');
  };
}

# 练习

function fun(n,o){
    console.log(o);
    return {
        fun:function(m){//[2]
            return fun(m,n);//[1]
        }
    }
}

var a=fun(0);
a.fun(1);
a.fun(2);
a.fun(3);
var b=fun(0).fun(1).fun(2).fun(3);
var c=fun(0).fun(1);
c.fun(2);
c.fun(3);
var name = 'window'

function Person(name) {
    this.name = name;
    this.fn1 = function () {
        console.log(this.name);
    };
    this.fn2 = () => console.log(this.name);
    this.fn3 = function () {
        return function () {
            console.log(this.name)
        };
    };
    this.fn4 = function () {
        return () => console.log(this.name);
    };
};

var obj1 = new Person('听风是风');
console.dir(obj1);
var obj2 = new Person('行星飞行');

obj1.fn1();
obj1.fn1.call(obj2);

obj1.fn2();
obj1.fn2.call(obj2);

obj1.fn3()();//window
obj1.fn3().call(obj2);
obj1.fn3.call(obj2)();

obj1.fn4()();
obj1.fn4().call(obj2);
obj1.fn4.call(obj2)();
// 循环打印
for (var i = 0; i < 3; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000)
}

//立即执行函数 
for (var i = 0; i < 3; i++) {
    (function (num) {
        setTimeout(function () {
            console.log(num);
        }, 1000);
    })(i);
}// 0// 1// 2


//返回一个匿名函数赋值
var data = [];
for (var i = 0; i < 3; i++) {
    data[i] = (function (num) {
        return function () {
            console.log(num);
        }
    })(i);
}

for (let i = 0; i < data.length; i++) {
    data[i]();
}
// 获取多个元素并添加点击事件
// 找出所有P 打印对应的索引
var op = document.querySelectorAll("p");
for (var j = 0; j < op.length; j++) {
    op[j].onclick = function () {
        console.log(j);
    };
}

//解决1
for (let i = 0; i < op.length; i++) {
    op[i].onclick = (function (num) {
        return function () {
            console.log(num);
        }
    })(i)
}

for (let i = 0; i < op.length; i++) {
    (function (j) {
        op[j].onclick = function () {
            console.log(j);
        };
    })(i)
}

# 解析

// 写出输出值

function fun(n, o) {
    console.log(o);
    return {
        fun: function (m) {
            return fun(m, n)
        }
    }
}
var a = fun(0)
a.fun(1);
a.fun(2);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);
c.fun(2);
c.fun(3)

// 答案与解析
// return返回的对象的fun属性对应一个新建的函数对象,这个函数对象将形成一个闭包作用域,使其能够访问外层函数的变量n及外层函数fun,为了不将fun函数和fun属性搞混,我们将上述代码修改如下:
function _fun_(n,o){
    console.log(o);
    return {
        fun:function(m){
            return _fun_(m,n);
        }
    }
}
var a=_fun_(0);//undefined  // 调用最外层的函数,只传入了n,所以打印o是undefined
a.fun(1);//0  //因为外层函数的返回值是一个对象,这里通过对象调用方法,并传入一个实参1,此时m=1,然后返回值继续调用外层函数fun,并将m、n的值1,0作为形参传入即fun(1,0),此时n=1,o=0,打印o的值,所以打印0
a.fun(2);//0  // 同上
a.fun(3);//0 // 同上

/*
	_fun_(0), 调用最外层函数,并且传入一个参数0,进入函数代码块,首先用var 声明两个形参,此时第一个参数n=0,第二个参数o没有赋值,所以打印unfined
	fun(1), 因为fun(0)的返回值是一个对象,这里通过对象调用方法,并传入一个实参1,此时m=1,然后返回值继续调用外层函数fun,并将m、n的值1,0作为形参传入即fun(1,0),此时n=1,o=0,打印o的值,所以打印0
	fun(2), 因为fun(1)的返回值是一个对象,这里通过对象调用方法,并传入一个实参2,此时m=2,然后返回值继续调用外层函数fun,并将m、n的值2,1作为形参传入即fun(2,1),此时n=2,o=1,打印o的值,所以打印1
	fun(3) 同上
*/
var b=_fun_(0).fun(1).fun(2).fun(3);
//undefined,0,1,2

// 同上
var c=_fun_(0).fun(1);//undefined,0,
c.fun(2);//1
c.fun(3); //1

-demo

更新时间: 12/9/2020, 9:34:18 PM