• 前端分享学习博客,探究前端相关技术,推动天朝前端发展,有任何问题都可以留言一起探究
  • 由于站内自链接问题,部分pre中的代码首字母使用大写以过滤筛选
  • 欢迎友链互换,还有,如果有大神请不要黑我的站点(o´・ェ・`o)
  • 如果你觉得博客还不错,请Ctrl+D收藏( *︾▽︾)

JavaScript笔记之Closures

老生常谈一下,JavaScript闭包是个绕不过去的点,闭包可以使开发者写出更“优美”的代码,简洁高效看着舒服;闭包对于很多人来说也不是很好理解,但是坑总是要跨过去的,所以我也写了这篇总结,对我自己是个巩固,希望也能帮到你;

What is a closure

在理解闭包概念的时候,需要先了解作用域作用域链(scope chain )的概念,因为闭包就是使用这个概念来描述和实施的;那么什么是闭包呢,闭包就是在作用域链内一个内部方法(inner function)可以获取调用外部方法(outer function)的变量;闭包有三个作用域链,首先它可以访问自身作用域链内的变量,还可以访问外部方法(outer function)的变量,再者是可以访问全局变量;这个内部方法不仅可以获取外部方法的变量,还可以调用它的参数(parameters),但是内部函数不能调用外部函数的arguments对象,即使它可以直接调用外部函数的参数;

JavaScript 闭包示例:

function showName(firstName, lastName) {
	var nameIntro = "Your name is ";
	// this inner function has access to the outer function's variables, including the parameter
		
	function makeFullName() {
		return nameIntro + firstName + " " + lastName;
	}
	return makeFullName();
}
showName("Michael", "Jackson"); // Your name is Michael Jackson


闭包同样在Node.js、jQuery等框架库中应用的非常多,实际上,闭包的的应用已经无处不在:

$(function() {

	var selections = [];
	$(".niners").click(function() { // this closure has access to the selections variable
		selections.push(this.prop("name")); // update the selections variable in the outer function's scope
	});

});

闭包的特性及问题

1.即使外部函数返回后,闭包也可以访问外部函数的变量

闭包最重要的功能之一是内部函数仍然可以访问外部函数的变量,即使在外部函数返回后也可以;这里要理解清楚,当JavaScript中的函数执行时,内部方法外部方法创建/使用的是同一个作用域链;这表示即使在外部函数返回后,内部函数仍然可以访问外部函数的变量;当在最外层调用内部方法的时候(通过外部方法celebrityName ),能返回外部方法的变量("This celebrity is"),返回组合结果:

function celebrityName (firstName) {
    var nameIntro = "This celebrity is ";
    // this inner function has access to the outer function's variables, including the parameter
   function lastName (theLastName) {
        return nameIntro + firstName + " " + theLastName;
    }
    return lastName;
}

var mjName = celebrityName ("Michael"); // At this juncture, the celebrityName outer function has returned.

// The closure (lastName) is called here after the outer function has returned above
// Yet, the closure still has access to the outer function's variables and parameter
mjName ("Jackson"); // This celebrity is Michael Jackson

2.闭包存储对外部函数变量的修改

闭包并不存储外部函数的实际变量值(actual value)。闭包会检索到当调用的外部函数的变量值发生的改变,这意味着可以通过闭包修改外部函数的变量,可以巧妙的利用这个功能,看下面的示例:

function celebrityID () {
    var celebrityID = 999;
    // We are returning an object with some inner functions
    // All the inner functions have access to the outer function's variables
    return {
        getID: function ()  {
            // This inner function will return the UPDATED celebrityID variable
            // It will return the current value of celebrityID, even after the changeTheID function changes it
          return celebrityID;
        },
        setID: function (theNewID)  {
            // This inner function will change the outer function's variable anytime
            celebrityID = theNewID;
        }
    }

}

var mjID = celebrityID (); // At this juncture, the celebrityID outer function has returned.
mjID.getID(); // 999
mjID.setID(567); // Changes the outer function's variable
mjID.getID(); // 567: It returns the updated celebrityId variable


3.闭包失效

因为闭包可以检索外部函数变量的变化,当外部函数的变量被for循环修改时,可能会导致错误:

// This example is explained in detail below (just after this code box).
function celebrityIDCreator (theCelebrities) {
    var i;
    var uniqueID = 100;
    for (i = 0; i < theCelebrities.length; i++) {
      theCelebrities[i]["id"] = function ()  {
        return uniqueID + i;
      }
    }
    
    return theCelebrities;
}
var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];
var createIdForActionCelebs = celebrityIDCreator (actionCelebs);
var stalloneID = createIdForActionCelebs [0];

console.log(stalloneID.id()); // 103

在上一个例子中,当异步方法被调用的时候,变量i的值是3,最终id在for执行完后返回的都是100+3=103,而不是预期的100,101,102;因为在这个例子中,外部函数的变量被重新操作赋值了,不是actual value了,这个示例表示闭包可以访问更新过的外部函数变量,因为外部函数运行整个for循环并返回i的最后一个值,即103;


为了解决这个闭包问题(或者说是bug),我们可以用立即调用函数表达式(Immediately Invoked Function Expression)来解决,看示例:

function celebrityIDCreator (theCelebrities) {
    var i;
    var uniqueID = 100;
    for (i = 0; i < theCelebrities.length; i++) {
        theCelebrities[i]["id"] = function (j)  { // the j parametric variable is the i passed in on invocation of this IIFE
            return function () {
                return uniqueID + j; // each iteration of the for loop passes the current value of i into this IIFE and it saves the correct value to the array
            } () // BY adding () at the end of this function, we are executing it immediately and returning just the value of uniqueID + j, instead of returning a function.
        } (i); // immediately invoke the function passing the i variable as a parameter
    }

    return theCelebrities;
}

var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];

var createIdForActionCelebs = celebrityIDCreator (actionCelebs);

var stalloneID = createIdForActionCelebs [0];

console.log(stalloneID.id); // 100 默认值

var cruiseID = createIdForActionCelebs [1];
console.log(cruiseID.id); // 101 结果+1,而不是for完成之后的+3

这个点经常被各大面试官/leader用来考察开发者对闭包的掌握,看完这些例子,你更理解闭包了吗。


薛陈磊的博客 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明JavaScript笔记之Closures
喜欢 (0)
[905044086@qq.com]
分享 (0)
作者薛陈磊
关于作者:
非著名前端工程师,关注Html5、Css3、Javascript、Node.js和各种前端框架发展,学习管理技巧和团队建设方法,期待遇到更多前端小伙伴一起学习进步;
说点什么...
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址