资讯专栏INFORMATION COLUMN

前端面试之闭包

jackzou / 3305人阅读

摘要:在函数内部的变量称之为局部变量,它可以在函数内部读取,在函数外部无法正常读取,如果想要读取函数内部的变量则需要用到闭包。

前端面试之闭包

闭包属于属于JavaScript的难点,但是在很多高级应用都需要用到,也是前端面试中经常会考到的点。

作用域

谈到闭包首先必须了解作用域,ES5中,JavaScript的作用域只有两种,一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。
JavaScript中变量分为两种:全局变量,局部变量。

全局变量在程序中的任何一个位置都可以调用,及赋值。

在函数内部的变量称之为局部变量,它可以在函数内部读取,在函数外部无法正常读取,如果想要读取函数内部的变量则需要用到闭包。父函数內部定义了子函数,子函数可以引用父函数作用域中的变量。

在网上找了一个图比较好的解释了变量与作用域之间的微妙关系。

必须注意的一点的是函数本身的作用域,是定义时的作用域,这里与this的指向不同。

var a = 1;
var f = function(){
    console.log(a);
}

function f2(){
    var a = 2;
    f();
}

f2() // 1

函数f在全局作用域下定义的,虽然在f2中被引用,但是a仍然是全局作用域下的a。

javascript 中的垃圾收集机制

在谈到闭包之前,还有一个垃圾回收机制需要了解。

JavaScript的内存生命周期:

分配所需要的内存

使用分配到的内存(读、写)

不需要时将其释放

垃圾回收机制的原理其实很简单:确定变量中哪些还在继续使用的,哪些已经不用的,然后垃圾收集器每隔固定的时间就会清理一下,释放内存。

局部变量在程序执行过程中,会为局部变量分配相应的空间,然后在函数中使用这些变量,如果函数运行结束了,而且在函数之外没有仔引用这个变量了,局部变量就没有存在的价值了,因此会被垃圾回收机制回收。在这种情况下,很容易辨别,但是并非所有情况下都这么容易。比如说全局变量。在现代浏览器中,通常使用标记清除策略来辨别及实现垃圾回收(还有一种叫引用计数,即当变量的引用次数为零的时候,就表示不再使用,这里有个循环计数的bug,现代浏览器已经不再使用它)。

标记清除

标记清除会给内存中所有的变量都加上标记,然后去掉环境中的变量以及不在环境中但是被环境中变量引用的变量(闭包)的标记。剩下的被标记的就是等到被删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后垃圾回收器会完成内存清理,销毁那些被标记的值释放内存空间。

闭包

首先抛出一条闭包的定义:闭包是指这样的作用域,它包含有一个函数,这个函数可以调用被这个作用域所封闭的变量、函数或者闭包等内容。
由定义可以看出:

闭包指的是一个函数作用域

闭包包含一个函数,且这个函数调用了作用域里的内容

满足这两点,都可以叫做闭包。

正常情况下,一个函数执行完,且没有在任何地方被调用,这个函数将会被垃圾回收机制销毁。

举个例子:

var obj = function () {
  var a = "";
  return {
    set: function (val) {
      a = val;
    },
    get: function () {
      return a;
    }
  }
};

var b = obj();
b.set("new val");
b.get();

obj这个函数在执行完之后理论上 函数体内的东西都应该被回收掉。但它执行后的返回值 b 具有set和get方法。这两个方法里对a保持了引用,所以obj执行过程中产生的a就不会销毁。直到b先被回收,这个a才会回收。

闭包利用的就是以上原理,以下是一个闭包的例子:

function f1() {
  var a = 1;
  function f2() {
    console.log(a);
  }
  return f2;
}

var a = 2;
var f = f1();
f() // 1

f执行完之后,其实f指向的f2这个函数在f1这个函数的作用域的引用,也就是说,执行f,相当于在f1函数的作用域这个环境下,执行f2。原因是f2是在f1中定义的,而且在这个例子中,a的值不会受外界影响。

闭包的特点

1.闭包内的变量不会影响到全局变量,也不会被全局变量所影响
2.闭包中被函数引用的局部变量不会被垃圾回收机制回收
3.可以创建私有变量和私有函数
4.可以把需要公开的变量和方法绑定在window上放出来

(function() {
    // 私有变量
    var age = 20;
    var name = "Tom";

    // 私有方法
    function getName() {
        return `your name is ` + name;
    }

    // 共有方法
    function getAge() {
        return age;
    }

    // 将引用保存在外部执行环境的变量中,形成闭包,防止该执行环境被垃圾回收
    window.getAge = getAge;
})();
闭包在ES6中的运用

ES6引入了块级作用域,主要是let命令以及const命令。他们的特点是不存在变量提升,不可以重复声明,只在区块中有效,存在暂时性死区。

借一个阮一峰老师的暂时性死区的例子:

var tmp = 123;

if (true) {
  tmp = "abc"; // ReferenceError
  let tmp;
}

在条件语句的区块中,虽然tmp在赋值后再用let命令声明,但是let命令已经生效,不归var所管了。

首先抛砖引玉,来一个关于ES5经典的例子:

var test = function () {
  var arr = [];
  for(var i = 0; i < 5; i++){
    arr.push(function () {
      return i*i;
    })
  }
  return arr;
}

var test1 = test();
console.log(test1[0]());
console.log(test1[1]());
console.log(test1[2]());

这个例子就不用多讲了,最后输出的值都是25。要注意的有两点,一个是i的变量提升,一个是i++,i++实际作用位置为当前循环内容结束,下一个循环之前。i++的意思是当前语句结束后,i加1。当我们打印i的值的时候,i的循环已经执行完了,i已经变成5了。

当我们用ES6的时候,情况就不一样了。

var test = function () {
  const arr = [];
  for(let i = 0; i < 5; i++){
    arr.push(function () {
      return i*i;
    })
  }
  return arr;
}

var test1 = test();
console.log(test1[0]());
console.log(test1[1]());
console.log(test1[2]());

因为使用let,使得for循环为块级作用域,let i=0在这个块级作用域中,而不是在函数作用域中。每次循环都会创建一个新的块级作用域,i值互相独立不受影响。所以最后打印的结果是:0,1,4.

闭包实现一个计数器
var counter = function(){
    var count = 1;
    return function(){
      return  count++;
    }
}

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

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

相关文章

  • 【进阶2-3期】JavaScript深入闭包面试题解

    摘要:闭包面试题解由于作用域链机制的影响,闭包只能取得内部函数的最后一个值,这引起的一个副作用就是如果内部函数在一个循环中,那么变量的值始终为最后一个值。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第二期,本周的主题是作用域闭包,今天是第8天。 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了...

    alanoddsoff 评论0 收藏0
  • 【进阶2-2期】JavaScript深入从作用域链理解闭包

    摘要:使用上一篇文章的例子来说明下自由变量进阶期深入浅出图解作用域链和闭包访问外部的今天是今天是其中既不是参数,也不是局部变量,所以是自由变量。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第二期,本周的主题是作用域闭包,今天是第7天。 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进阶计...

    simpleapples 评论0 收藏0
  • Deep in JS - 收藏集 - 掘金

    摘要:今天同学去面试,做了两道面试题全部做错了,发过来给道典型的面试题前端掘金在界中,开发人员的需求量一直居高不下。 排序算法 -- JavaScript 标准参考教程(alpha) - 前端 - 掘金来自《JavaScript 标准参考教程(alpha)》,by 阮一峰 目录 冒泡排序 简介 算法实现 选择排序 简介 算法实现 ... 图例详解那道 setTimeout 与循环闭包的经典面...

    enali 评论0 收藏0
  • 2017 前端面试准备 - 收藏集 - 掘金

    摘要:最近遇到的前端面试题更新版前端掘金个人博客已上线,欢迎前去访问评论无媛无故的个人博客以下内容非本人原创,是整理后觉得更容易理解的版本,欢迎补充。 一道面试题引发的对 javascript 类型转换的思考 - 前端 - 掘金 最近群里有人发了下面这题:实现一个函数,运算结果可以满足如下预期结果: ... 收集 JavaScript 各种疑难杂症的问题集锦 - 前端 - 掘金 从原博客迁移...

    王晗 评论0 收藏0
  • 2017 前端面试准备 - 收藏集 - 掘金

    摘要:最近遇到的前端面试题更新版前端掘金个人博客已上线,欢迎前去访问评论无媛无故的个人博客以下内容非本人原创,是整理后觉得更容易理解的版本,欢迎补充。 一道面试题引发的对 javascript 类型转换的思考 - 前端 - 掘金 最近群里有人发了下面这题:实现一个函数,运算结果可以满足如下预期结果: ... 收集 JavaScript 各种疑难杂症的问题集锦 - 前端 - 掘金 从原博客迁移...

    xiaochao 评论0 收藏0

发表评论

0条评论

jackzou

|高级讲师

TA的文章

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