📒【闭包】理解JavaScript闭包及其应用

什么是闭包?

闭包是指那些能够访问自由变量的函数。
自由变量:指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
闭包 = 函数 + 函数能够访问的自由变量

ECMAScript中,闭包指的是

  1. 从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
  2. 从实践角度:以下函数才算是闭包:
  • 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
  • 在代码中引用了自由变量
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo(); //"local scope"
  • 进入全局代码,创建全局执行上下文,全局执行上下文压入执行上下文栈
  • 全局执行上下文初始化
  • 执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 执行上下文被压入执行上下文栈
  • checkscope 执行上下文初始化,创建变量对象、作用域链、this等
  • checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
  • 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈
  • f 执行上下文初始化,创建变量对象、作用域链、this等
  • f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

Q:当 f 函数执行的时候,checkscope 函数上下文已经被销毁了啊(即从执行上下文栈中被弹出),怎么还会读取到 checkscope 作用域下的 scope 值呢?
A: f 执行上下文维护了一个作用域链:

fContext = {
    Scope: [AO, checkscopeContext.AO, globalContext.VO],
}

就是因为这个作用域链,f 函数依然可以读取到 checkscopeContext.AO 的值,说明当 f 函数引用了 checkscopeContext.AO 中的值的时候,即使 checkscopeContext 被销毁了,但是 JavaScript 依然会让 checkscopeContext.AO 活在内存中,f 函数依然可以通过 f 函数的作用域链找到它,正是因为 JavaScript 做到了这一点,从而实现了闭包这个概念。

Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,子可以访问父作用域的变量,但是父无法访问子作用域中的变量。

  function f1(){
    var n=999;
  }
  alert(n); // error

Q:如何从外部读取内部变量?

在函数内部创建一个函数f2,并返回f2

function f1() {
  var n = 999;

  function f2() {
    console.log(n);
  }

  return f2;
}

let result = f1();
result(); // 999 

如上f2函数就是一个闭包。闭包就是可以读取其他函数内部变量的函数,也可以理解是 定义在一个函数内部的函数。 闭包是将函数内部作用域和函数外部连接起来的一座桥梁。

闭包的作用

  1. 读取函数内部的变量
  2. 让这些变量始终保持在内存中
function f1() {
  var n = 999;
  nAdd = function () {
    n += 1
  }
  function f2() {
    console.log(n);
  }
  return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000

result实际是闭包f2函数,两次运行值不一样,说明函数f1的局部变量n一直保存在内存中,并没有再f1调用后被自动清除。
原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
nAdd=function(){n+=1}没有var关键字,所以nAdd是全局变量,其值是一个匿名函数,而这个匿名函数本身是一个闭包,所以可以再函数外部对函数内部的局部变量进行操作。
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

闭包的特点

  1. 闭包外层是一个函数(闭包是定义在函数内部的函数)
  2. 闭包内部都有函数
  3. 闭包会返回内部函数(因为闭包的目地是外部要访问内部变量)
  4. 闭包返回的函数内部不能有return this... 因为这是this已经改变
var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };
  alert(object.getNameFunc()());
  1. 执行闭包后,闭包内部变量会存在,而闭包内部函数的内部变量不存在。

闭包的应用

  1. 模拟私有变量的实现

仅在对象内部生效,无法从外部触及,这样的变量,就是私有变量。

// 利用闭包生成IIFE,返回 User 类
const User = (function() {
    // 定义私有变量_password
    let _password

    class User {
        constructor (username, password) {
            // 初始化私有变量_password
            _password = password
            this.username = username
        }

       login() {
           // 这里我们增加一行 console,为了验证 login 里仍可以顺利拿到密码
           console.log(this.username, _password)
           // 使用 fetch 进行登录请求,同上,此处省略
       }
    }

    return User
})()

let user = new User('xiuyan', 'xiuyan123')

console.log(user.username) // xiuyan
console.log(user.password) // undefined
console.log(user._password) // undefined
user.login() // xiuyan xiuyan123

我们把 _password 放在了 login 方法的外层函数作用域里,并通过立即执行 User 这个函数,创造出了一个闭包的作用域环境。我们看到不管是 password,还是 _password,都被好好地保护在了 User 这个立即执行函数的内部。
看到User对外暴露的属性确实已经没有 password,通过闭包,我们成功达到了用自由变量来模拟私有变量的效果!

  1. 偏函数和柯里化
  • 柯里化:把接受 n 个参数的 1 个函数改造为只接受 1个参数的 n 个互相嵌套的函数的过程。也就是 fn (a, b, c)fn(a,b,c) 会变成 fn (a)(b)(c)fn(a)(b)(c)。
  • 偏函数:固定你函数的某一个或几个参数,然后返回一个新的函数(这个函数用于接收剩下的参数)。
function generateName(prefix) {
    return function(type, itemName) {
        return prefix + type + itemName
    }
}
// 把3个参数分两部分传入
var itemFullName = generateName('大卖网')('母婴', '奶瓶')

使用闭包的注意点

  1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

思考题

var test = (function() {
    var num = 0
    return () => {
        return num++
    }
}())
for (var i = 0; i < 10; i++) {
    test()
}
console.log(test())
function test (){
    var num = []
    var i
    for (i = 0; i < 10; i++) {
        num[i] = function () {
            console.log(i)
        }
    }

    return num[9]
}
test()()
var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}

data[0]();
data[1]();
data[2]();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容

  • 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。 一、变量...
    zock阅读 1,074评论 2 6
  • ● 闭包基础 ● 闭包作用 ● 闭包经典例子 ● 闭包应用 ● 闭包缺点 ● 参考资料 1、闭包基础 作用域和作...
    lzyuan阅读 916评论 0 0
  • 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。 一、变量...
    zouCode阅读 1,269评论 0 13
  • 高考如期而至,拖着疲惫的身体回到山里等待着成绩出来,等待是煎熬的,我日夜难寐,心中恐惧与日俱增。农家的孩子从小都被...
    凉云暮雨阅读 276评论 0 1
  • 只想慢点,再慢点, 过完生活的每一分,每一秒。
    二菟阅读 189评论 4 1