重学JavaScript之匿名函数

1. 什么是匿名函数?

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

function a(a1, a2, a3) {
    // 函数体
}

==其他函数表达式==

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

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

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. 闭包

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

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对象的引用,减少其引用数,确保正常回收其占用的内存。

4、 模仿块级作用域

vJS没有块级作用域的概念,这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。JS从来不会告诉你是否多次声明了同一个变量,它总是对后续的声明视而不见。我们可以通过==匿名函数来模仿块级作用域==从而避免这个问题。

(function () {
    // 块级作用域
})()

5、私有变量

严格来说在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(){
        // 方法的代码
    }
}

6、总结

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

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

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

  1. 在后台执行环境汇总,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域;
  2. 通常,函数的作用域及所有变量都会在函数执行结束后被销毁
  3. 但是,如果函数返回了一个闭包时, 这个函数的作用域将会一直在内存中保存到闭包不存在为止

使用闭包可以在JS中模仿块级作用域

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

闭包可以用于对象中创建私有变量

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

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

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

原文地址:https://www.cnblogs.com/lieone/p/11751254.html

时间: 2024-09-29 22:08:09

重学JavaScript之匿名函数的相关文章

Javascript的匿名函数与自执行

函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途.匿名函数:就是没有函数名的函数. 函数的定义,大致可分为三种方式: 第一种:这也是最常规的一种 1 2 3 function double(x){        return 2 * x;       } 第二种:这种方法使用了Function构造函数,把参数列表和函数体都作为字符串,很不方便,不建议使用. 1 var double = new Function('x', 'return 2 * x;'); 第三种: 1

谈谈Javascript的匿名函数

JQuery 里面有这么一种代码: (function(){ // code here })(); 当一个匿名函数被括起来,然后再在后面加一个括号,这个匿名函数就能立即运行起来,神奇吧! 要说匿名函数,我们首先要由函数本身说起.函数的定义如下:函数是将唯一的输出值赋予给每一输入的“法则”.偏关县信访局 当然,这只是数学上的定义.但是,在计算机编程语言中,函数的定义也八九不离十.因为我们都知道,计算机中的函数,也类似数学定义中的描述,它是将输入的若干数据,经过代码设定的逻辑操作处理后,返回唯一的输

Javascript的匿名函数

一.什么是匿名函数?在Javascript定义一个函数一般有如下三种方式:函数关键字(function)语句:function fnMethodName(x){alert(x);}函数字面量(Function Literals):var fnMethodName = function(x){alert(x);}Function()构造函数:var fnMethodName = new Function('x','alert(x);')上面三种方法定义了同一个方法函数fnMethodName,第1

第九章 Javascript之匿名函数

分析: 1.所谓匿名函数,从字面意思理解,就是没有名字的函数,js 用()来代替(注意,是英文状态下的括号) 2.定义形式: function (){ //to add codes that you want to add }   3.匿名函数作用 (1)与闭包函数相比较,最大作用是不污染全局对象,一旦执行完毕,GC自动回收内存,这是与闭包函数本质的区别.闭包函数的一大特点是: 变量常驻内存,只有关闭浏览器时,才释放. function f1(){ var n=999; nAdd=functio

JavaScript的匿名函数和模块化的使用方法

对于开发人员来说,很多时候我们都会涉及到JavaScript的使用,而在使用过程中,最令人沮丧的就是变量没有相应的使用范围. 在开发中,对于任何变量.数组.函数.对象等,只要不在函数的内部,都会被默认为是全局的,意味着页面上的其他脚本都可以访问它,并可进行重写覆盖. 对于不必要放在函数内部的变量,我们要如何保证不被其他代码访问并重写覆盖呢?这时就需要将变量放在一个匿名函数的内部,定义完后立即调用,例如,在JavaScript中按如下方式编写,将会产生三个全局变量和两个全局函数: var name

JavaScript中匿名函数的多种写法

匿名函数没有实际名字,也没有指针,怎么执行滴?  其实大家可以看看小括号的意义就应该可以理解.小括号有返回值,也就是小括号内的函数或者表达式的返回值,所以说小括号内的function返回值等于小括号的返回值,不难理解 (function(){})()可以将没有名字的函数执行了把- 关于匿名函数写法,很发散~ 最常见的用法: 代码如下: (function() { alert('water'); })(); 当然也可以带参数: 代码如下: (function(o) { alert(o); })('

JavaScript&amp;jQuery.匿名函数.立即函数

匿名函数 定义:有参数,有函数体,可是没有函数名 window.onload=function (ev) {     // 声明匿名函数,这个函数没有名字,同时赋值给变量area     var area=function(length,width){         return length*width;     };     // 调用时调用变量名,不是函数名     var value=area(80,60);     document.write("桌子的面积是"+value

JavaScript中匿名函数this指向问题

this对象是在运行时基于函数执行环境绑定的,在全局函数中,this=window,在函数被作为某个对象的方法调用时,this等于这个对象. 但是匿名函数的执行环境是全局性的,所以匿名函数的this指向是window var name = 'window' var person = { name :'Alan', sayName:function () { return function () { console.log(this.name) } } } person.sayName()() /

javascript中的匿名函数整理笔记

以下为总结在开源的JavaScript框架中能看到很多这样语法结构(function(){})()比如我最近看的jQuery,及chediter.刚开始的时候我看到这样的结果有点奇怪,它是怎么执行的,并且这是什么样的语法结构,最近偶尔看闭包的时候,才发现原来这是JavaScript种匿名函数(看到这个有点汗,java的匿名类见过,就从来没想到JavaScript中会有匿名函数,也是学的不够牢固).现在我们了解到以上是JavaScript匿名函数的语法结构,怎么声明函数,匿名函数JavaScrip