闭包

把函数当作值来使用:

  1. 直接进行赋值( 函数表达式)
  2. 函数赋值给对象的属性
  3. 函数当作数组中的项
  4. 将函数当作另外一个函数参数来使用
  5. 将函数当作另外一个函数的返回值来使用

把函数当作另外一个函数的参数来使用

  • 操作函数(数组对象)

    var values = [8,0,3, 1, 5,7, 10,12, 15,20];
    values.sort(function(a,b){return a<b});
    // [20, 15, 12, 10, 8, 7, 5, 3, 1, 0]
    // 操作函数是将函数本身就行传递
    function aa(a,b){return a<b}
    values.sort(aa);
    // 
    function callSomeFunction(someFunction, someArgument){
    return someFunction(someArgument);
    }
    

    可以接受一个函数作为参数,这种方式是可以直接得到返回结果的。是没有时间差的可以直接进行下一步运算。

  • 回调函数 (有时间差)

    需要等待一段时间然后再进行下一步操作的函数叫做回调函数

    Ajax请求,首先拿到服务器的返回结果,然后定义一个函数,再把服务器返回的结果作为你定义的这个函数的参数传递给定义的函数,让这个函数在服务器返回之后运行。

注意:⚠️

是将函数的本身作为参数进行传递,还是将函数的返回值作为参数进行传递。

函数本身作为参数进行传递的话只需要传递函数名称即可也就是将函数的引用进行传递如操作函数。函数的返回值作为参数传递的话,需要在函数后方添加()让函数先运行起来。然后将运行的结果作为参数进行传递。

把函数当作另外一个函数的返回值来返回

当一个函数的返回值还是一个函数的时候,当你运行这个函数你得到的结果就是一个函数,为了得到最终的结果你需要将这个结果再次运行。

有个一数组,数组中的每一项都是一个对象,对象中有两个属性,一个名称一个年龄。需要对数组进行重新排序,此时需要用到sort方法,sort方法默认调用toString来决定排列的顺序,但我们需要按照名称或者年龄进行排序。此时需要创建一个可以生成排序规则的函数的函数。这个函数接受一个参数来生成一个排序函数。

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 data = [{name: "Zachary", age: 28}, {name: "Nicholas", age: 29}];
data.sort(createComparisonFunction("name"));
alert(data[0].name); //Nicholas
data.sort(createComparisonFunction("age"));
alert(data[0].name); //Zachary

闭包

function a(){
    var i=0
    return function(){
       console.log(i++) 
    }
}
var b = a()
// 每次运行b() 时在a中定义的i并没有被重新定义,而是在原来的基础上加一
  • 函数运行过程:

    所有的作用域都是基于函数的。每创建一个函数就建立一个新的作用域。

    1. 函数的声明
    2. 函数的执行
    3. 返回执行结果然后清理
  • 闭包特点:

    闭包会阻断垃圾回收机制,利用闭包可以对变量进行保存。

    闭包相当于在函数内部开通一个对外的通道。这个通道就是return出去的函数。通过这个通道函数可以访问内部的变量。因为内部的变量可能会被访问,所以js垃圾清理机制就不会清理函数内部的变量。

创建一个生成不重复的id方法

// 全局作用域方法
var a="abc"
var b=0
function getId(){
    return a+b++
}
// 闭包方法
var getId =(function(){
    var a="abc";
    var b=0;
    return function(){ return a+b++}
})()
// 清除闭包
var getId = null
  • 作用域
  • 作用域链
  • 执行环境
  • 活动对象

作用域是一套查找规则,当你定义一个变量的时候,首先会去当前作用域下查找是否有这个变量,如果没有就创建一个,有就忽略。这个过程是在js的预处理过程中实现的。更准确的说是在词法分析阶段实现的。

当我们运行一段代码时,如果代码是在全局下运行。这时解释器会创建全局作用域,这个全局作用域下包含一个列表,这个列表称之为作用域链。这个作用域链上包含一个对象。这个对象称为活动对象。然后解释器会首先通过词法分析(一行行扫描代码)对代码进行预处理。词法分析阶段如果遇到声明语句,如:var a 会首先看下当前的活动对象里否含有属性a,如果有就忽略,如果没有就创建属性a 并且赋值为undefined。预处理过程结束后就会生成可执行代码进入可执行阶段。在代码的执行阶段当你对变量a赋值时,这时解释器会根据作用域链先去查找活动对象,看活动对象中是否存在属性a,如果存在就对属性a进行赋值,如果不存在,就创建一个属性a并且对它进行赋值。

同样的当你去运行一个函数b的时候,也是首先会看一下全局的活动对象上是否含有属性b,如果有就执行如果没有同样会去创建属性b,然后赋值一个undefined,接着去运行这个属性b,这时会报错b不是一个函数。

无论变量是否声明都会创建,为什么还要去声明变量?

  1. 使用var 局部变量,不使用为全局变量。

如果你的代码在全局环境下运行会包含一个列表,这个列表叫做作用域链。这个作用域链只包含一个活动对象。如果你的代码在函数中运行的时候,在定义这个函数的时候,就包含了一个作用域链,这个作用域链中也只有一个活动对象即全局活动对象。当你运行一个函数的时候就会创建一个新的活动对象,并且推入到这个作用域链中。这个时候这个函数所对应的作用域链就包含两个活动对象。一个是函数声明的时候就包含一个活动对象指向全局的活动对象,第二个是函数执行的时候创建一个新的活动对象,推入作用链中。这个新的活动对象被推入作用链中之后就进入词法分析阶段。逐行扫描函数中的代码,当遇到声明语句的时候,解释器只会去查看当前活动对象当中,是否含有对应的属性。如果有的话忽略如果没有就创建一个。不同的是在代码执行阶段当你对一个变量赋值的时候,这时解释器会从作用域链中查看第一项,即当前的活动对象,如果有则赋值,如果没有就查看作用域链中的第二项全局活动对象,如果还是没有就在全局活动对象中创建对应的属性,并且赋值。

区别:如果使用了var那么在词法分析阶段会把对应的变量放到函数对应的活动对象中,不使用var则会跳过词法分析阶段直接进入函数执行阶段,在函数的执行阶段因为解释器会顺着作用域链逐个的查找作用链上的活动对象,一直找到最后一个,如果到最后一个还是找不到的话,就在最后一个活动对象上创建一个属性。最后一个活动对象就是全局活动对象。所以创建出来的属性也是你对应的这变量变成了全局变量。

js的解释器在确定作用域的时候是通过词法分析阶段来遍历活动对象实现的,所以在js当中作用域更准确的解释应该叫做词法作用域

作用域链的创建是在函数定义的时候创建的,每个函数都有自己的作用域链,只不过在函数执行的时候被推入了一个新的对象用于词法分析

在函数执行并且返回结果之后,垃圾回收机制会将函数对应的活动对象删除。因此你在函数外面是得不到函数里面的变量的。原因1,你在函数外面去访问变量的时候是通过查找作用域链上的活动对象来实现的。这个作用域链跟函数的作用域链他俩没有交集,原因2,函数的作用域链上包含的活动对象在函数执行完成之后已经被清除了。

var a
var b = function(){
    var c =function(){}
    return c
}
a=b
a()

闭包原理:在全局下定义变量a 和函数b,这时全局的活动对象包含两个属性,a和b同时b还包含一个作用域链,里面只有一个全局的活动对象。如果在b中定义一个函数c那么当你去运行函数b的时候,这个时候b的作用域链上就多了一个活动对象,这个活动对象中包含一个对象c,而c也包含一个作用域链,c的作用域链有两项,第一项是b的活动对象,第二项是全局活动对象。如果把c作为返回值并且赋值给了a,那么这个时候a和c之间就建立一个引用关系,这个引用关系是在函数b运行的时候创建的,由于建立这种引用关系所以在b运行完成之后b的作用域链上的活动对象不会被删除。而当你运行a的时候实际上是运行了c,这个时候c的作用域链上会被推入一个新的活动对象。这时c的作用域链上就变成了3个活动对象。第一个是c自己的活动对象,这个活动对象是新创建的,要经历词法分析,会按照c中的代码初始化一遍。第二个活动对象是b的活动对象,第三个活动对象是全局活动对象。所以当c中的代码要去操作某个变量的时候,会依次查找这三个对象。如果这个变量是在b中定义的,那么就会在b的活动对象中找到并且对它进行相应的操作。而操作的结果也会被保存在b的活动对象当中。函数a运行完成后或者说是函数c运行完成后c自己的活动对象被删除了。而c的作用域链上还是包含两个活动对象,分别是b的活动对象和全局活动对象。因为b的活动对象没有被删除所以其中的值就会被保存住。如果想要删除b的活动对象就要断开a和c之间的联系,并且把a赋值为null,这样在下一次垃圾回收机制运行的时候b的活动对象就被删除了。