JS 进阶篇: 这可能是关于闭包最好的一篇文章

9,228 阅读6分钟

原谅我把标题起的像搞个大新闻的样子~

每次下午打完球之后,晚上的学习总是提不起什么精神,趁着看不下新知识的空,把前两天总结的知识发出来给大家参考参考,挺多是摘抄的,也有一些是自己写的,如果有什么错误的,敬请指正!

数据类型

原始数据类型

ES6之前的原始数据类型有Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number),在ES6时引入了一种新的原始数据类型 Symbol,表示独一无二的值,Symbol我们不详细讲了。(基本数据类型和原始数据类型等意)

引用数据类型

除了上面的基本数据类型,剩下的就是引用数据类型了,统称为 Object 类型,有:Object 类型、Array 类型、Date 类型、RegExp 类型、Function 类型等。

值传递和引用传递的问题

原始数据类型:

var a;var b =0; a =b;

这个时候,a,b是占有不同的内存空间的(a,b这个时候是两个完全不相干的变量)

对于引用数据类型,复制的仅仅是存储空间的地址,a,b指向的地址是相同的(改变其中任意一个都会影响另一个)

重要:ES所有函数的参数都是按值来传递的(不是按引用来传递的)
什么是按值传递:

var a = 10;
function foo(num){  num = num +10;}
num(a);
alert(a);   //a返回的仍旧是10,这种情况我们称之为按值传递

但是呢,原始数据类型和引用数据类型在ES函数中的按值传递仍旧是还有一点不同,因为原始数据类型变量中存储的值就是该变量的值(栈内存,比如a=10,那么其存储的值就是10,传递给ES函数的值也是10)
对于引用数据类型来说,它的变量存储的值就是内存的地址(指向存储的地址,所以它传递给ES函数的值就是变量指向内存空间的地址)

这种按值传递造成的影响就是,对于原始数据来说,由于它传递的仅仅只是它变量的值,所以不管在ES函数中发生了何种变化,都不会影响原始数据类型变量的值。

但是,对于引用数据类型来说,情况可能就没有那么好了,由于它传递的是它内存空间的地址,所以ES函数内发生的变化,都会影响内存空间的值,所以原来变量指向的内存空间的值也就跟着变化了。

执行环境

下面的话基本抄自JS红宝书,对于我们理解执行环境和闭包很有帮助
执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。

每个执行环境都有与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。

这个对象我们编写的代码无法访问,但是解析器在处理数据时会在后台使用它。

全局执行环境,window对象
所有全局变量和函数都是作为window对象的属性和方法创建的。

某个执行环境中所有代码执行完毕后,该环境会被销毁,保存的所有变量和函数定义也随之销毁

当代码在一个环境中执行时,会创建变量对象的一个作用域链,作用域链保证对执行环境有权访问的所有变量和函数的有序访问。

一般地,内部环境可以通过作用域链访问所有的外部环境

作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象

无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量

一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象),但是,但是,但是,闭包的情况有所不同

下面是重点:
内部匿名函数会将外部函数的活动对象添加到它的作用域链中。
当匿名函数被外部函数返回后,它的作用域链会被初始化为包含外部函数的活动对象和全局变量对象,所以这样,匿名函数就可以访问外部函数作用域中定义的所有变量。
更为重要的是,更为重要的是,更为重要的是,在外部函数执行完毕后,其活动对象也不会被销毁,匿名函数的作用域链仍然在引用这个活动对象。
换句话说,外部函数返回后,外部函数执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中,直到匿名函数被销毁后,外部函数的活动对象才会被销毁

由于闭包作用域链的这种奇葩设定,所以导致了作用域链产生了一个及其古怪的副作用,即闭包只能取得外部包含函数中任何变量的最后一个值(因为直到外部函数执行环境被销毁,匿名函数还在引用外部函数的活动对象)

闭包

闭包的问题我们就以红宝书上的两段代码来解释吧

function createFunction (){
  var result = new Array();
  for(var i=0;i<10;i++){
    result[i] = function(){
      return i;
    }
  }
  return result;
}

这里得到的结果其实应该是:

result[0] = i;
result[1] = i;
result[2] = i;
result[3] = i;
.
.
.
result[10] = i;

为什么返回的i而不是i对应的值,主要是因为闭包中局部变量是引用而不是拷贝,也就是说,这个时候的i仅仅只是i的引用而不是i的值。而因为是i的引用,所以到了外部函数执行完毕后(i已经加到10了),这个时候,i才被赋值给result数组,而此时,i的值为10

关于”闭包中局部变量是引用而不是拷贝”,再举个例子

function say888() {
    var num = 887;
    var sayAlert = function() { alert(num); }
    num++;
    return sayAlert;
}

很明显,返回的是888,所以闭包中局部变量是引用而不是拷贝

如果要改变上面的那种情况,可以利用匿名函数的自我执行,这段也是红宝书的代码

function createFunction (){
  var result = new Array();
  for(var i=0;i<10;i++){
    result[i] = function(num){
       
    return function(){
      return num;
               }
        }(i)
    }
  return result;
}

这个时候的匿名函数相当于fn(0),fn(1),fn(2),
此时,数组result中保存的是函数
```javascript
result0 = 0;
result1 = 1;
result2 = 2;
result3 = 3;
.
.
.
result 9 = 9;
因为这个时候的i只是函数参数,而函数参数是按值传递的(不是按引用传递),所以这个时候匿名函数被强制执行。

有一些自己总结的可能语言不是很准确,有错误的地方,敬请指正!