前端战五渣学JavaScript——闭包

就决定是你了——闭包

有很多开发人员老是搞不清匿名函数闭包两个概念,所以常常混用。闭包是指有权访问另外一个函数做用域中的变量的函数。建立闭包的常见方式,就是在一个函数内部建立另外一个函数。 ——————摘自《JavaScript高级程序设计》javascript

上面说的很清楚,就是一个函数内部再建立另外一个函数(尴尬。。。好像跟书上说的同样。多余解释)。我以前面试也常常被问到这个问题。。。没想到书上就这么一句。如下须要有一点js的基础,了解做用域才能看明白。。。前端

闭包的旅程才刚刚开始

咱们先来看一个最简单的⬇️java

function a(){
    var b = 10;
    return function() {
        return b
    }
}
console.log(a()()); // 10
复制代码

看,奇迹吧!!!咱们输出了a函数中的局部变量b的值。就是这么神奇。。。
诶??大家可能会说那我这个局部变量b,至关于一个常量,若是我像下面⬇️这样岂不是一个效果?面试

function a() {
    var b = 10;
    return b
}
console.log(a());
复制代码

好的,没有错,那咱们再换种方式⬇️编程

function a(paramA) {
    return function b(paramB) {
        return paramA + paramB
    }
}

var variable10 = a(10);
var variable20 = a(20);

console.log(variable10(5)); // 15
console.log(variable20(5)); // 25
复制代码

上面这个例子,在咱们执行输出variable10(5)variable20(5)的时候,为何咱们能获得5与以前执行a(10)时传进去的10呢?这就是咱们闭包的用处,能够获取到函数内的局部变量,并相加。
等等!也许有人不明白了,怎么是局部变量了呢?那我按下面这种写法可能清楚一些⬇️闭包

function a(paramA) {
  + var variableA = paramA;
  return function b(paramB) {
    return variableA + paramB
  }
}

var variable10 = a(10);
/** * a(10)是传进去是什么样的呢? * * function a(10) { * var variableA = 10; * return function b(paramB) { * return variableA + paramB * } * } * 上面语法不对哦,我只是写的好理解一些 * 也就是说variable10指向了内部函数b * var variable10 = function b(paramB) { * return 10 + paramB * } */
var variable20 = a(20);

console.log(variable10(5)); // 15
console.log(variable20(5)); // 20
复制代码

经过上面的注释,想必你们应该已经了解闭包是什么了,就是在一个外部函数内部建立另外一个函数,以致于全局调用内部函数的时候能够访问到外部函数的局部变量,并使用。函数

真相永远只有一个!

来,上《JavaScript高级程序设计》的例子ui

function createComparisonFunction(propertyName) {

  return function (object1, object2) {
    var value1 = object1[propertyName];
    var value2 = object2[propertyName];

    if (value1 < value2) {
      return -1
    } else if (value1 > value2) {
      return 1
    } else {
      return 0;
    }
  }
}
复制代码

在这个例子中,var value1var value2这两行代码访问了外部函数中的变量propertyName,即便这个内部函数被返回了,并且是在其余地方被调了,但它仍然能够访问变量propertyName;这就相似于我上面的例子(代码接上)this

var manA = { name: '江户川柯南', age: 7 };
var manB = { name: '工藤新一', age: 17 };
var whoOlder = createComparisonFunction('age');
// 返回-1为前者小于后者,返回1位前者大于后者,返回0二者同样大
console.log(whoOlder(manA, manB));  // -1,柯南小于新一
复制代码

上面的例子就至关于开始介绍闭包时的例子,不明白?就是spa

var whoOlder = createComparisonFunction('age');
/** * var whoOlder = createComparisonFunction('age') { * var propertyName = 'age'; * return function (object1, object2) { * var value1 = object1['age']; * var value2 = object2['age']; * if (value1 < value2) { * return -1 * } else if (value1 > value2) { * return 1 * } else { * return 0; * } * } * } * 上面语法不对啊,我只是写的好理解一些 * 最后获得的就是 * var whoOlder = function (object1, object2) { * var value1 = object1['age']; * var value2 = object2['age']; * if (value1 < value2) { * return -1 * } else if (value1 > value2) { * return 1 * } else { * return 0; * } * } * } */
复制代码

重点!!! 在一个函数被调用的时候,会建立一个执行环境(execution context)及相应的做用域链。而后,还有初始化一个活动对象(activation object),这个活动对象里有什么呢?有arguments和其余命名参数的值,就是⬇️

function a(paramA, paramB) {
    return paramA + paramB
}
a(1, 2)
/** * 这个函数的活动对象有什么呢??有三个 * arguments [1, 2] * paramA 1 * paramB 2 */
复制代码

而后就是刚说的做用域链,外部函数的活动对象会始终处于第二位,就是好比我在内部函数用到变量a,会先在内部函数找有没有声明a这个变量,若是没有,就会去外部函数找,外部函数没有就会往全局执行环境找。
在函数执行过程当中,为读取和写入变量的值,就须要在做用域链中查找变量,来看下面的例子(《JavaScript高级程序设计》例子)

function compare(value1, value2) {
    if (value1 < value2) {
        return -1
    } else if (value1 > value2) {
        return 1
    } else {
        return 0
    }
}

var result = compare(5, 10)
复制代码

这个的做用域链画出来就是

做用域链,执行环境,活动对象
如今应该多少能看懂这张图了吧,当时看书的时候真是费死劲了,中间那个做用域链1,0什么的,就是处于0位置的活动对象是函数本身的活动对象,处于1位置的是全局对象,因此上面说外部函数的活动对象始终处于第二位。

天下第一武道大会

想必你们面试的时候都会遇到一个很是很是经典的题,题面就是:

页面上有十个<li></li>标签,就是一个列表有十项,须要点击每一个li输出对应的索引。

经典错误答案:

var list = document.getElementsByTagName('li');
for (var i = 0; i < list.length; i ++) {
  list[i].addEventListener('click', function () {
    console.log(i)
  })
}
复制代码

这咱们如今都知道是输出同一个数了,这是为何呢?我来换一种写法:

var list = document.getElementsByTagName('li');
var i = 0;
for (i; i < list.length; i ++) {
  list[i].addEventListener('click', function () {
    console.log(i)
  })
}
复制代码

这下咱们能够看清楚了吧,当这段代码都执行完,尚未点击li的时候,全局变量i已经变成最后一个数了,假设有十个li,i就已经根据循环变成了10。因此,当咱们点击li的输入全局变量i的时候,固然每次输出的都是10了。

经典正确答案:

var list = document.getElementsByTagName('li');
for (var i = 0;; i < list.length; i ++) {
  (function (i) {
    list[i].addEventListener('click', function () {
      console.log(i)
    })
  })(i)
}
复制代码

在for循环里,每执行一次,执行一个自调函数,并把i当作参数传进去,js函数的参数有个按值传递的特性,也能够理解为咱们前面讲过的活动对象,这个自调函数的活动对象里面有个i的值,因此这时候每一个li上要执行的监听函数输出的就不在是全局变量i,而是活动对象中的i,由于循环的时候没循环一次执行一次自调函数,因此每一个自调函数之间是各自独立的,因此输出的i值也天然不同了。
固然也有人会说用ES6的语法更简单一些:

var list = document.getElementsByTagName('li');
 - for (var i = 0; i < list.length; i ++) {
 + for (let i = 0; i < list.length; i ++) {
  list[i].addEventListener('click', function () {
    console.log(i)
  })
}
复制代码

就是简单的把声明i的var改为let,这是为何呢,这是由于ES6中新的规定let声明的是自带做用域的变量,而且ES6有块级做用域的概念,因此会把每次循环的i值锁死,天然输出的就是不同的值啦;

有木叶的地方就会燃烧火之意志

闭包就先写到这了,若是有哪里写的不对的地方但愿你们指正,我立马更改,我也不想误人子弟。写这篇博客也是为了让本身对闭包这个概念有个从新的认识,对活动对象,做用域链更清楚了,可是还有好多东西没有写,好比this指向问题,内存泄漏怎么处理,ES5是怎么模仿块级做用域的,私有变量,私有函数,模块模式,这都是一个闭包就能够延伸出来的问题,这在《JavaScript高级程序设计》第7章第4节均可以找到,静下心来看一看,我相信仍是能够看明白的,一开始看不懂,就一段时间看一遍,每次确定会有不一样的理解。

对于前端来讲,追求时髦的技术当然没错,毕竟时髦的技术有着先进的思想,他能告诉你应该怎样编程。可是在编程以前,仍是须要学好基础,基础弄明白了,确定会对js,前端,有一个全新的认识。


我是前端战五渣,一个前端界的小学生。

相关文章
相关标签/搜索