专栏首页张培跃关于JavaScript中的闭包及应用场景

关于JavaScript中的闭包及应用场景

对于前端开发工程师来讲,闭包是一个很难弄懂而且十分难征服的一个概念!因为闭包的生成不仅仅与变量的作用域相关而且与变量的生命周期也有着密切的关系。最后我可以肯定的告诉你,闭包在实际开发的过程中应用十分广泛,所以你必须要掌握它。

先来看一下关于闭包的定义:闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。

//通过闭包可以获得函数fn的局部变量user
function fn(){
    var user='zhang';
    return function(){
        return user;
    }//通过匿名函数返回局部变量user
}
console.log(fn()());//zhang  通过box()()来直接调用匿名函数返回值
var b=fn();
console.log(b());//zhang 另一种调用匿名函数返回值

通过闭包可以实现函数内的局部变量的累加:

function fn(){
    var num=100;
    return function(){
        num++;
        return num;
    }
}
var b=fn();//获得函数
//你会发现局部变量num并没有在内存当中消失
console.log(b());//调用匿名函数
console.log(b());//第二次调用匿名函数,实现累加

由于在闭包所在的作用域返回的局部变量不会被销毁,所以会占用内存。过度的使用闭包会迫使性能下降,因此建议大家在有必要的情况下再使用闭包。 作用域链的机制会导致一个问题,在循环中里的匿名函数取得的任何变量都是最后一个值

function fn(){
    var arr=[];
    //i为fn函数中的局部变量。
    for(var i=0;i<3;i++){
        arr.push(function(){
            return i;
        });
    }
    return arr;
}
var b=fn();
for(var i=0;i<b.length;i++){
    console.log(b[i]());//3
}

以上示例输出的结果均是3,也就是循环结束后i的值。这是因为在for循环的过程当中,数组中的匿名函数并没有自我执行。当在调用匿名函数的时候,通过闭包获得的i已经是3了,所以每次输出的都是3。 如果想要输出的结果为0,1,2可以做如下的调整:

function fn(){
    var arr=[];
    for(var i=0;i<3;i++){
        arr.push((function(num){
            //将立即执行函数返回的匿名函数放到数组中。
            //因为num为函数的参数,因此有自己独立的作用域
            return function(){
                return num;
            };
        })(i));
    }
    return arr;
}
var b=fn();
for(var i=0;i<b.length;i++){
    console.log(b[i]());//0,1,2
}

通过匿名函数的立即执行,将立即执行后返回的函数直接赋值给数组arr。每次循环即将i的值传递给num,又因为num在函数中,所以有自己的独立作用域,因此num得到的值为每次循环传递进来的i值,即0,1,2

接下来看一下关于闭包当中的this对象:

this对象指的是什么,这个要看函数所运行的环境。如果函数在全局范围内调用 ,函数内的this指向的是window对象。对象中的方法,通过闭包如果运行的环境为window时,则this为window。因为闭包并不是该对象的方法。

var color="red";
function fn(){
    return this.color;
}
var obj={
    color:"yellow",
    fn:function(){
        return function(){//返回匿名函数
            return this.color;
        }
    }
}
console.log(fn());//red  在外部直接调用this为window
var b=obj.fn();//b为window下的变量,获得的值为obj对象下的fn方法返回的匿名函数
console.log(b());//red  因为是在window环境下运行,所以this指缶的是window
//可以通过call或apply改变函数内的this指向
console.log(b.call(obj));//yellow
console.log(b.apply(obj));//yellow
console.log(fn.call(obj));//yellow

通过变量可以获得上一个作用域中的this指向

var color="red";
function fn(){
    return this.color;
}
var obj={
    color:"yellow",
    fn:function(){
        var _this=this;//将this赋值给变量_this
        return function(){
            return _this.color;//通过_this获得上一个作用域中的this指向
        }
    }
}
console.log(fn());//red
var b=obj.fn();
console.log(b());//yellow
可以通过构造方法传参来访问私有变量
function Desk(){
    var str="";//局部变量str,默认值为""
    this.getStr=function(){
        return str;
    }
    this.setStr=function(value){
        str=value;
    };
}
var desk=new Desk();
//为构造函数的局部变量写入值。
desk.setStr("zhangPeiYue");
//获取构造函数的局部变量
console.log(desk.getStr());//zhangPeiYue

闭包常见的作用

1、模拟块级作用域(匿名自执行函数)

if(){}for(){}等没有作用域,所以在其块内声明的变量,在外部是可以使用的。

//javaScript没有块级作用域的概念
function fn(num){
    for(var i=0;i<num;i++){}
    console.log(i);//在for外部i不会失败
}
fn(2);
if(true){
    var a=13;
}
console.log(a);//在if定义的变量在外部可以访问

通过匿名自执行函数可以模拟块级作用域

 (function(){
        //i在外部就不认识啦
        for(var i=0;i<count;i++){}
  })();
  console.log(i);//报错,无法访问

由于外部无法访问自执行函数内的变量,因此在函数执行完后会立刻被销毁,在外部无法访问。从而可有效避免在多人开发时由于全局变量过多而造成的命名冲突问题。另外由于作用域链的机制,局部变量要比全局变量的访问速度更快,可以提升程序的运行速度!

2、对结果进行缓存

写一个用于实现所有参数和的函数:

var fn=function(){
    var sum=0;
    for(var i=0;i<arguments.length;i++){
        sum+=arguments[i];
    }
    return sum;
}
console.log(fn(1,2));//3

以上函数接收一些number类型的参数,并返回这些参数之和。由于每次传递的参数相同,所以返回的结果是一样的。这样势必会造成了一种浪费,在此时咱们可以通过缓存机制来提高这个函数的性能。

var fn=(function(){
    var cache={}//将结果缓存到该对象中
    return function(){
        var str=JSON.stringify(arguments);
        if(cache[str]){//判断缓存中是否存在传递过来的参数,存在直接返回结果,无需计算
            return cache[str];
        }else{//进行计算并返回结果
            var sum=0;
            for(var i=0;i<arguments.length;i++){
                sum+=arguments[i];
            }
            return cache[str]=sum;
        }
    }
})()

上面的示例将计算后的结果缓存到局部变量cache当中,在调用这个函数时,先在缓存中查找,如果找不到,则进行计算,然后将结果放到缓存中并返回,如果找到了,直接返回查找到的值。 好了,今天就到这吧!

本文分享自微信公众号 - 张培跃(zhangpeiyue8),作者:张培跃

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-06-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 2019前端面试基础题集合!赶紧上车!快!快!快!

    在 ES5.1 里面函数是这样执行的(不讨论use strict和一些特殊情况,JS好复杂的),按如下顺序执行:

    用户1272076
  • JavaScript面试题补充(6---10)

    上面的代码考察了一个非常重要的 JavaScript 概念:闭包(Closures)。对于每一个JavaScript开发者来说,如果你想在网页中编写5行以上的代...

    用户1272076
  • 37个JavaScript基本面试问题和解答(建议收藏)

    1、使用typeof bar ===“object”来确定bar是否是一个对象时有什么潜在的缺陷?这个陷阱如何避免?

    用户1272076
  • 如何写出好的 JavaScript —— 浅谈 API 设计

    很多同学觉得写 JavaScript 很简单,只要能写出功能来,效果能实现就好。还有一些培训机构,专门教人写各种“炫酷特效”,以此让许多人觉得这些培训很“牛逼”...

    前朝楚水
  • 理解闭包 js回收机制

    为什么要有回收机制?why? 打个比方,我有一个内存卡,这个内存是8G的,我把文件,视频,音乐,都保存到了这个内存卡,随着我的储存的内容越来越多,这个内存卡已经...

    庞小明
  • 函数基础知识回顾

      String    Number   Boolean   Null    undefined    //占有固定的内存大小,如数值型占八个字节,布尔类型...

    用户3159471
  • 一篇文章带你了解JavaScript中的函数表达式,递归,闭包,变量,this对象,模块作用域

    它的一个重要特点就是:函数声明提升,就是在执行代码前先读取函数声明,可以把函数声明放在调用它的语句后。

    达达前端
  • javascript两种声明函数的方式的一次深入解析

    javascript有两种声明函数的方式,一个是函数表达式定义函数,也就是我们说的匿名函数方式,一个是函数语句定义函数,下面看代码:

    他叫自己MR.张
  • 【译】《Understanding ECMAScript6》- 第二章-函数

    函数在任何一门编程语言中都是很重要的一个环节。JavaScript至今已有多年的历史,但是它的函数仍然停留在很初级的阶段。函数问题的大量堆积,以及某些函数非常微...

    寒月十八
  • 九、函数与函数式编程

    纵观JavaScript中所有必须需要掌握的重点知识中,函数是我们在初学时最容易忽视的知识点。可能会有很多人、很多文章告诉你面向对象很重要,原型很重要,可是却很...

    用户6901603

扫码关注云+社区

领取腾讯云代金券