重学JavaScript之匿名函数

注意: 本文章为 《重学js之JavaScript高级程序设计》系列第七章。

关于《重学js之JavaScript高级程序设计》是从新回顾js基础的学习。

欢迎关注 前端公众号【小夭同窗】 前端

1. 什么是匿名函数?

匿名函数就是没有名字的函数,有时候也称为《 拉姆达函数》。匿名函数是一种强大的使人难以置信的工具。以下:segmentfault

function a(a1, a2, a3) {
    // 函数体
}
复制代码

==其余函数表达式==数组

var a = function(a1, a2, a3) {
    // 函数体
}
复制代码

以上两个例子在逻辑上等价,其主要的区别是: 前者会在代码执行前被加载到做用域中,然后者则是在代码执行到那一行的时候才会有定义。另外一个重要的区别就是:函数声明会给函数一个指定的名字,而函数表达式则是:建立一个匿名函数,而后将这个匿名函数赋给一个变量。bash

function(a1, a2, a3) {
    // 函数体
}
复制代码

上面例子也是彻底能够的,可是却没法调用这个函数,由于没有指向这个函数的指针,可是能够将这个函数做为参数传入另一个函数,或者从一个函数中返回另外一个函数时就可使用这种形式来定义匿名函数。微信

2. 递归

递归函数是在一个函数经过名字调用自身的状况下构成的闭包

function f(num) {
    if (num <= 1) {
        retrun 1
    } else {
        return num * f(num - 1)
    }
}
复制代码

以上,这是一个经典的递归阶乘函数,表面上没有任何问题,可是却会被如下代码致使出错:函数

var a = f
f = null
console.log(a(4) // 报错
复制代码

以上代码先把 f() 函数保存在变量 a 中,而后将f变量设置为 null ,结果指向原始函数的引用只剩下一个。但在接下来调用 a() 时,因为必须执行 f(),但 f 已经不是函数,全部就会报错。这个时候可使用 arguments.callee工具

function f(num) {
    if (num <= 1) {
        return 1
    } else {
        return num * arguments.callee(num - 1)
        // 经过 arguments.callee 代替函数名,能够保证不会出问题
    }
}

var a = f
a = null
a(4)    // 24
复制代码

3. 闭包

闭包是指有权访问另外一个函数做用域中的变量的函数。建立闭包的方式:在一个函数内部建立另外一个函数。post

function c(p) {
    retrun function(o1,o2){
        // var v1 = o1[p]
        // var v2 = o2[p]
        
        if (v1 < v2) {
            return -1
        } else if (v1 > v2) {
            retrun 1
        }else {
            retrun 0
        }
    }
}
复制代码

在上面代码中,有标记的两行是匿名函数中的代码。这两行代码访问了外部函数中的变量 p。即便这个内部函数被返回了,并且被其余地方调用了,但它仍然能够访问变量 p。之因此还可以访问这个变量,是由于函数的做用域链中包含了c()的做用域。学习

当某个函数第一次被调用时,会建立一个执行环境及相应的做用域链,并把做用域链赋值给一个特殊的内部属性([Scope])。而后,使用 this、arguments和其余命名参数的值来初始化函数的活动对象。但在做用域链中,外部函数的活动对象始终处于第二位,外部函数的外部活动对象处于第三位。直到做为做用域链重点的全局执行环境。

  • 在函数执行过程当中,为读取和写入变量的值,就须要在做用域链中查找变量。后台的每一个执行环境都有一个表示变量的对象--变量对象
  • 全局环境的变量对象始终存在,而局部环境的变量对象,则只在函数执行的过程当中存在。咱们在建立函数的时候会建立一个预先包含全局变量对象的做用域链,这个做用域链被保存在内部的[Scope]属性中,当调用函数时,会为函数建立一个执行环境,而后经过赋值函数的[Scope]属性中的对象构建起执行环境的做用域链。
  • 若是这时候有一个变量对象被建立并被推入执行环境做用域链的前端,对于一开始建立的函数的执行环境而言,其做用域链中包含两个变量:==本地活动对象和全局变量对象==。因此,做用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

不管何时函数在访问一个变量时,就会从做用域链中搜索具备相同名字的变量,函数执行完成后,局部活动对象将被销毁,内存中仅保存全局做用域。可是因为闭包会携带包含它的函数的做用域,所以会比其余函数占用更多的内存。过分使用闭包可能会致使内存占用过多。

在一个函数内部定义的函数会将外部函数的活动对象添加到它的做用域链中。内部函数在外部函数中被返回后,它的做用域链被初始化为包含外部函数的==活动对象和全局变量对象==,这样内部函数就能够访问外部函数中定义的全部的变量。因此在外部函数执行结束后,它并不会被销毁,由于内部函数的做用域链还在引用这个活动对象。也就是说外部函数执行结束后,它的做用域链会被销毁,可是活动对象还在内存中,直到内部函数被销毁后。

3.1 闭包与变量

做用域链的这种配置引出了一个反作用,闭包只能取得包含函数中任何变量的最后一个值。

3.2 关于 this 对象

在闭包中使用this 也可能会致使一些问题。由于this对象是在运行时基于函数的执行环境绑定的。在全局函数中 this === window,函数被做为某个对象的方法调用时,this就等于那个对象。匿名函数的执行环境具备全局性,所以其this 对象一般指向window。==可是这并非绝对的。==

在函数被调用的时候,其活动对象都会自动得到两个特殊变量:==this 和 arguments。== 内部函数在搜索这两个变量时,只会搜索到其活动对象为止,所以永远不可能直接访问外部函数中的这两个变量。若是把外部做用域中的this对象保存在一个闭包可以访问的变量里,就可让闭包访问该对象了。

3.3 内存泄露

因为IE对JS对象和 COM对象使用不一样的垃圾收集例程,所以闭包在IE中会致使一些特殊的问题。也就是说,若是闭包的做用域链中保存着一个HTML元素,那么就意味着该元素将没法被销毁。

==注意==:闭包会引用包含函数的整个活动对象,而其中包含着变量,即便闭包不直接引用变量,包含函数的活动对象中也仍然会保存一个引用。所以把变量设置为 null ,这样就可以解除对DOM对象的引用,减小其引用数,确保正常回收其占用的内存。

四、 模仿块级做用域

vJS没有块级做用域的概念,这意味着在块语句中定义的变量,其实是在包含函数中而非语句中建立的。JS历来不会告诉你是否屡次声明了同一个变量,它老是对后续的声明视而不见。咱们能够经过==匿名函数来模仿块级做用域==从而避免这个问题。

(function () {
    // 块级做用域
})()
复制代码

五、私有变量

严格来讲在JS中并无私有成员的概念:==全部对象属性都是公有的==。不过却是有一个私有变量的概念。任何在函数中定义的变量均可以认为是私有变量,由于不能在函数的外部访问这些变量。私有变量包括函数的参数、局部变量和在函数内部定义的其余函数。

在函数内部若是有私有变量,那么在函数内部能够访问这个变量,但在函数外部则不能访问它们。若是==在这个函数内部建立一个闭包,那么闭包经过本身的做用域链也能够访问这些变量==。

咱们把有权访问私有变量和私有函数的公有方法称为==特权方法==。有两种在对象上建立特权方法的方式,

第一种:在构造函数中定义
function m (){
    let p = 10
    function p (){
        retrun false
    }
    
    // 特权方法
    this.pb = function () {
        p++
        retrun p()
    }
}
复制代码
第二种:静态私有变量

经过在私有做用域中定义私有变量或函数,一样也能够建立特权方法。和在构造函数中定义特权方法的区别在于私有变量和函数是由实例共享的,因为特权方法是在原型上定义的,所以全部实例都使用同一个函数。

多查找做用域链中的一个层次,就会在必定程度上影响查找速度。这正是闭包和私有变量一个不足之处。

5.1 模块模式

指的是为单例建立私有变量和特权方法。所谓单例,指的就是只有一个实例对象,按照惯例,JS是以对象字面量的方式来建立单例对象的:

var s = {
    name : v,
    method: function(){
        // 方法的代码
    }
}
复制代码

六、总结

匿名函数,也称为拉姆达函数,是一种使用JS函数的强大方式。有以下特色:

  1. 任何函数表达式从技术上说都是匿名函数,由于没有引用它们的肯定的方式
  2. 在没法肯定如何引用函数的状况下,递归函数就会变得比较复杂
  3. 递归函数应该始终使用 argument.callee来递归地调用自身,不要使用函数名,由于函数名可能会发生变化。

当函数内部定义了其余函数时,就建立s了闭包,闭包有权访问包含函数内部的全部变量。

  1. 在后台执行环境汇总,闭包的做用域链包含着它本身的做用域、包含函数的做用域和全局做用域;
  2. 一般,函数的做用域及全部变量都会在函数执行结束后被销毁
  3. 可是,若是函数返回了一个闭包时, 这个函数的做用域将会一直在内存中保存到闭包不存在为止

使用闭包能够在JS中模仿块级做用域

  1. 建立并当即调用一个函数,这样便可以执行其中的代码,又不会在内存中留下对该函数的引用
  2. 结果就是函数内部的全部变量都会被当即销毁--除非将某些变量赋值给了包含做用域中的变量

闭包能够用于对象中建立私有变量

  1. 即便JS中没有正式的私有对象属性概念,但可使用闭包来实现公有方法,而经过公有方法能够访问在包含做用域中定义的变量。
  2. 有权访问私有变量的公有方法叫作 特权方法
  3. 可使用构造函数、原型模式来实现自定义类型的特权方法,也可使用模块模式、加强的模块模式来实现单例的特权方法。

JS中的匿名函数和闭包都是很是的特性,可是要注意使用场景和方法。

重学js系列

重学js之JavaScript简介

重学 JS 之在 HTML 中使用 JavaScript

重学js之JavaScript基本概念(上)=> 数据类型

重学js之JavaScript基本概念(中)=> 操做符

重学js之JavaScript基本概念(下)=> 运算符

重学js之JavaScript变量、做用域和内存问题

重学js之JavaScript引用类型

重学js之JavaScript 面向对象的程序设计(建立对象)

重学JavaScript之面向对象的程序设计 => 继承

ES6入门系列

ES6入门之let、cont

ES6入门之变量的解构赋值

ES6入门之字符串的扩展

ES6入门之正则的扩展

ES6入门之数值的扩展

ES6入门之函数的扩展

ES6入门之数组的扩展

ES6入门之对象的扩展

ES6入门之Symbol

ES6入门之Set 和 Map

Es6入门之proxy

ES6入门之Promise对象

Git教程

前端Git基础教程

Python玩转微信

Python 实现微信自动经过好友添加请求!!!

Python + Wxpy 实现微信防撤回。

Python获取好友地区分布及好友性别分布

相关文章
相关标签/搜索