资讯专栏INFORMATION COLUMN

[学习笔记] JavaScript 闭包

sunsmell / 2608人阅读

摘要:但是,必须强调,闭包是一个运行期概念。通过原型链可以实现继承,而与闭包相关的就是作用域链。常理来说,一个函数执行完毕,其执行环境的作用域链会被销毁。所以此时,的作用域链虽然销毁了,但是其活动对象仍在内存中。

学习Javascript闭包(Closure)
javascript的闭包
JavaScript 闭包深入理解(closure)
理解 Javascript 的闭包
JavaScript 中的闭包

看了《JS高级程序设计》和上面几篇文章,小总结下JS闭包~

作为一个初学者,学习一个新的概念无非就是知道 “它是什么”、“什么原理”、“怎样创建”、“有什么用”、“怎么用”,这篇文章就从这几个角度介绍闭包,欢迎小伙伴们拍砖~

什么是闭包

上面几篇文章,每一篇对闭包的定义都不相同,其实这真是一个很难用语言完美描述出来的东西。个人更喜欢阮一峰写的:

  

闭包就是能够读取其他函数内部变量的函数。

在JavaScript中,函数A内部的局部变量不能被A以外的函数访问到,只能被A内部的子函数B访问到,当在函数A外调用函数B时,便可通过B访问A中局部变量,那么子函数B就是闭包。(注意,是子函数哦~)
这样说比较抽象,举个栗子~

function outer() {

    var a = 100; // outer的局部变量

    function inner() { // 闭包
        console.log(a);
    }

    return inner; // 没有这条语句,闭包起不到在outer外部访问变量a的作用~
}
console.log(a); // 在外部直接访问a出错,Uncaught ReferenceError: a is not defined

var test = outer(); // outer运行完返回inner,赋值给test
test(); // 100,执行test(),相当于执行inner(),这样就可以访问到outer内部的a了

司徒正美在blog中从另一个角度解释了闭包:

  

简单来说,闭包就是在另一个作用域中保存了一份它从上一级函数或作用域取得的变量(键值对),而这些键值对是不会随上一级函数的执行完成而销毁。周爱民说得更清楚,闭包就是“属性表”,闭包就是一个数据块,闭包就是一个存放着“Name=Value”的对照表。就这么简单。但是,必须强调,闭包是一个运行期概念。

闭包的原理

说原理可能有些大,但这部分确实是要从内部机制的角度,解释下“为什么上面的栗子中通过inner就可以在outer外部访问outer内部的a”~

个人认为在JS中,有两个链很重要:原型链作用域链。通过 原型链 可以实现继承,而与 闭包 相关的就是 作用域链

还是上面的栗子,假设outer定义在全局作用域中

1  function outer() {

2     var a = 100; 

3     function inner() { 
4         console.log(a);
5     }

6     return inner; 
7  }

8  var test = outer(); 
9  test(); 

当执行到1处,outer函数定义,其作用域链中只有一个全局对象。
然后执行8,运行outer()。此时,outer的作用域链中有两个对象:outer的活动对象-->全局对象
运行outer(),会执行outer里面的3,定义inner(),此时inner的作用域链是:outer的活动对象-->全局对象
执行9,其实就是执行inner函数,此时其作用域链是:inner的活动对象-->outer的活动对象-->全局对象

因为inner的作用域链中有outer的活动对象,所以它可以访问到outer的局部变量a。

常理来说,一个函数执行完毕,其执行环境的作用域链会被销毁。但是outer执行完毕后,其活动对象仍然保留在内存中,因为inner的作用域链仍在引用着这个活动对象。所以此时,outer的作用域链虽然销毁了,但是其活动对象仍在内存中。直到test执行完毕,outer的活动对象才被销毁。

也正因为如此,闭包只能取得包含函数中任何变量的最后一个值,即包含函数执行完毕时变量的值。改改之前的栗子~

 function outer() {

    var a = 100; 

    function inner() { 
        console.log(a);
    } 

    a = a + 50; // 改变a的值

    return inner; 
}

var test = outer(); 
test(); // 150,取得的a是150,而不是100

正是因为作用域链,只能里面的访问外面的,外面的不能访问里面的。也是基于作用域链,聪明的大师们想出了闭包,使得外面的可以访问里面的。掌握作用域链很关键啊~

创建闭包的方式

创建闭包最常见的方式就是在一个函数里面创建一个函数,之前举得栗子就是。

司徒正美大神给出了3种闭包实现:

with(obj){
    //这里是对象闭包
}
(function(){
    //函数闭包
 })();
try{
   //...
} catch(e) {
   //catch闭包 但IE里不行
}
闭包的作用

1. 在函数外面读取函数的局部变量
前面一直在说这个事儿~

2. 在内存中维持一个变量

function outer() {

    var a = 100; 

    function inner() { 
        console.log(a++);
    } 

    return inner; 
}

var test = outer(); 
test(); // 100
test(); // 101
test(); // 102

栗子中a一直在内存中,每执行一次test(),输出值加1。因为test在全局执行环境中,所以a一直在内存中,如果test在其他执行环境中,当这个执行执行环境销毁的时候,a就不会再在内存中了。

3. 实现面向对象中的对象
javascript并没有提供类这样的机制,但是可以通过闭包来模拟类的机制。把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value)。

function Person(){
    var name = "XiaoMing";

    return {
        setName : function(theName){
            name = theName;
        },

        getName : function(){
            return name;
        }
    };
}
var person = new Person();
console.log(person.name); // undefined
console.log(person.getName()); // XiaoMing
几个闭包示例

参考的这几篇文章基本都给出了闭包示例,悄悄摘选司徒正美大神的几个放到这里~

//*************闭包uniqueID*************

uniqueID = (function(){

    var id = 0; 
    return function() { 
        return id++; // 返回,自加
    };  
})(); 

document.writeln(uniqueID()); //0
document.writeln(uniqueID()); //1
document.writeln(uniqueID()); //2
document.writeln(uniqueID()); //3
document.writeln(uniqueID()); //4
//*************闭包阶乘*************

var a = (function(n) {
    if (n<1) { 
        alert("invalid arguments"); 
        return 0; 
    }
    if(n==1) { 
        return 1; 
    }
    else { 
        return n * arguments.callee(n-1); 
    }
})(4);

document.writeln(a);
//***********实现面向对象中的对象***********

function Person(){
    var name = "XiaoMing";

    return {
        setName : function(theName){
            name = theName;
        },

        getName : function(){
            return name;
        }
    };
}
var person = new Person();
console.log(person.name); // undefined
console.log(person.getName()); // XiaoMing
//***********避免 Lift 效应***********

var tasks = [];
for (var i = 0; i < 5; i++) {
    // 注意有一层额外的闭包
    tasks[tasks.length] = (function (i) {
        return function () {
            console.log("Current cursor is at " + i);
        };
    })(i);
}

var len = tasks.length;
while (len--) {
    tasks[len]();
}

运行结果:

Current cursor is at 4
Current cursor is at 3
Current cursor is at 2
Current cursor is at 1
Current cursor is at 0

补充:Lift效应

var tasks = [];
for (var i = 0; i < 5; i++) {
    tasks[tasks.length] = function () {
        console.log("Current cursor is at " + i);
    };
}

var len = tasks.length;
while (len--) {
    tasks[len]();
}

上面这段代码输出5

Current cursor is at 5

这一现象称为 Lift效应。原因在于

  

在引用函数外部变量时,函数执行时外部变量的值由运行时决定而非定义时

实际编程的时候,很容易遇见lift效应,加一层闭包就可以解决哦~

需注意的问题

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
如果有非常庞大的对象,且预计会在老旧的引擎中执行,则使用闭包时,注意将闭包不需要的对象置为空引用。

这是我对于闭包学习的总结,错误和不足欢迎大家指正~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85744.html

相关文章

  • 学习笔记JavaScript 闭包是怎么通过作用域链霸占更多内存的?

    摘要:闭包是怎么通过作用域链霸占更多内存的本文是作者学习高级程序设计第一小节的一点个人理解,详细教程请参考原教材。函数执行过程创建了一个函数的活动对象,作用域链的最前端指向这个对象。函数执行完毕返回值后执行环境作用域链和活动对象一并销毁。 JavaScript 闭包是怎么通过作用域链霸占更多内存的? 本文是作者学习《JavaScript 高级程序设计》7.2第一小节的一点个人理解,详细教程请...

    HmyBmny 评论0 收藏0
  • Js学习笔记闭包

    摘要:一前言这个周末,注意力都在学习基础知识上面,刚好看到了闭包这个神圣的东西,所以打算把这两天学到的总结下来,算是巩固自己所学。因此要注意闭包的使用,否则会导致性能问题。五总结闭包的作用能够读取其他函数内部变量。 # 一、前言 这个周末,注意力都在学习基础Js知识上面,刚好看到了闭包这个神圣的东西,所以打算把这两天学到的总结下来,算是巩固自己所学。也可能有些不正确的地方,也请大家看到了,麻...

    Crazy_Coder 评论0 收藏0
  • JS学习笔记(第7章)(函数表达式)

    摘要:递归闭包模仿块级作用域私有变量小结在编程中,使用函数表达式可以无需对函数命名,从而实现动态编程。匿名函数也称为拉姆达函数。函数声明要求有名字,但函数表达式不需要。中的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。 1、递归 2、闭包 3、模仿块级作用域 4、私有变量 5、小结 在JavaScript编程中,使用函数表达式可以无需对函数命名,从而实现动态编程。匿名函数也称...

    xiaokai 评论0 收藏0
  • JS笔记

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。异步编程入门的全称是前端经典面试题从输入到页面加载发生了什么这是一篇开发的科普类文章,涉及到优化等多个方面。 TypeScript 入门教程 从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript。 网络基础知识之 HTTP 协议 详细介绍 HTT...

    rottengeek 评论0 收藏0
  • 重学前端学习笔记(十八)--JavaScript闭包和执行上下文

    摘要:申明与赋值立即执行的函数表达式,通过创建一个函数,并且立即执行,来构造一个新的域,从而控制的范围。函数接受一个的形参,该参数是一个对象引用,并执行了。在最新的标准中,引入了一个新概念。 笔记说明 重学前端是程劭非(winter)【前手机淘宝前端负责人】在极客时间开的一个专栏,每天10分钟,重构你的前端知识体系,笔者主要整理学习过程的一些要点笔记以及感悟,完整的可以加入winter的专栏...

    silencezwm 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<