第6章 函数与闭包(Chapter 6 functions and closures)

绩效

章节 代码量(行)
6.1 0
6.2 14
6.3 44
6.4 154
第6章 212

6.1 函数声明语句与匿名函数表达式

可以通过函数声明语句与匿名函数表达式对函数进行声明。

可以通过函数声明语句与匿名函数表达式对函数进行声明。

6.2 函数调用的分类

表6.1 函数调用的分类

名称 说明
方法调用 通过接收方对象对函数进行调用(包括apply 与call 调用)
构造函数调用 通过new 表达式对函数进行调用
函数调用 以上两种方式之外的函数调用

将以方法调用的方式使用的函数称为方法,同理,将以构造函数调用方式使用的函数称为构造函数。

将以方法调用的方式使用的函数称为方法,同理,将以构造函数调用方式使用的函数称为构造函数。

函数声明语句的后置

通过函数声明语句声明的函数,可以在进行声明的代码行之前就对其调用。虽然这个例子在函数的作
用域内进行,不过对于全局作用域情况也是相同。

通过函数声明语句声明的函数,可以在进行声明的代码行之前就对其调用。虽然这个例子在函数的作
用域内进行,不过对于全局作用域情况也是相同。

function hzh1() {
    hzh2(); // 在声明函数fn之前对其进行调用
    function hzh2() {
        console.log('黄子涵');
    }
}
hzh1();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
黄子涵

[Done] exited with code=0 in 99.724 seconds

通过函数声明语句声明的函数,可以在进行声明的代码行之前就对其调用。在通过匿名函数表达式进行定义的情况下结果将会不同。

通过函数声明语句声明的函数,可以在进行声明的代码行之前就对其调用。在通过匿名函数表达式进行定义的情况下结果将会不同。

function hzh3() {
    hzh4();
    var hzh4 = function() {
        console.log('黄子涵');
    }
}
hzh3();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
e:\HMV\JavaScript\JavaScript.js:2
    hzh4();
    ^

TypeError: hzh4 is not a function
    at hzh3 (e:\HMV\JavaScript\JavaScript.js:2:5)
    at Object.<anonymous> (e:\HMV\JavaScript\JavaScript.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:999:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
    at Module.load (internal/modules/cjs/loader.js:863:32)
    at Function.Module._load (internal/modules/cjs/loader.js:708:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)
    at internal/main/run_main_module.js:17:47

[Done] exited with code=1 in 3.35 seconds

6.3 参数与局部变量

6.3.1 arguments 对象

可以通过在函数内使用 arguments 对象来访问实参。使用方式如代码清单 6.1 所示。

可以通过在函数内使用 arguments 对象来访问实参。使用方式如代码清单 6.1 所示。

代码清单 6.1 使用 arguments 对象的例子

function hzh1() {
    console.log(arguments.length);
    console.log(arguments[0], arguments[1], arguments[2]);
}
console.log("arguments.length 为实参的数量,值为1");
console.log("arguments[0]的值为7");
console.log(hzh1(7));
console.log("*********************************************");
console.log("arguments.length 为实参的数量,值为2");
console.log("arguments[0] 的值为7,arguments[1] 的值为8");
console.log(hzh1(7, 8));
console.log("*********************************************");
console.log("arguments.length为实参的数量,值为1");
console.log("arguments[0]的值为7");
console.log("arguments[1]的值为8");
console.log("arguments[2] 的值为9");
console.log(hzh1(7, 8, 9));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
arguments.length 为实参的数量,值为1
arguments[0]的值为7
1
7 undefined undefined
undefined
*********************************************
arguments.length 为实参的数量,值为2
arguments[0] 的值为7,arguments[1] 的值为8
2
7 8 undefined
undefined
*********************************************
arguments.length为实参的数量,值为1
arguments[0]的值为7
arguments[1]的值为8
arguments[2] 的值为9
3
7 8 9
undefined

[Done] exited with code=0 in 0.506 seconds

没有相对应的形参的实参也可以通过 arguments 访问。由于能够通过arguments.length 获知实参的数量,因此可以写出所谓的可变长参数函数。而形参的数量则可以通过 Function 对象自身的 length 属性来获得。

没有相对应的形参的实参也可以通过 arguments 访问。由于能够通过arguments.length 获知实参的数量,因此可以写出所谓的可变长参数函数。而形参的数量则可以通过 Function 对象自身的 length 属性来获得。

虽然 arguments 可以以数组的方式使用,不过它本身并不是数组对象。因此,无法对其使用数组类中的方法。

虽然 arguments 可以以数组的方式使用,不过它本身并不是数组对象。因此,无法对其使用数组类中的方法。

6.3.2 递归函数

递归函数是一种在函数内对自身进行调用的函数。这种方式被称为递归执行或递归调用。

递归函数是一种在函数内对自身进行调用的函数。这种方式被称为递归执行或递归调用。

代码清单 6.2 n 的阶乘(递归函数的例子)

function hzh1(hzh2) {
    if(hzh2 <= 1) {
        return 1;
    } else {
        return hzh2 * hzh1(hzh2 - 1); 
    }
}
console.log("调用函数hzh1:");
console.log(hzh1(5));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
调用函数hzh1:
120

[Done] exited with code=0 in 1.416 seconds

如果递归函数不停地调用自身,运行将不会终止(这与无限循环的情况是一样的,因此俗称为无限递归)。JavaScript 发生无限递归之后的反应取决于实际的运行环境。如果是 SpiderMonkey 的壳层,则会像下面这样发生 InternalError。而在 Java6 附带的 Rhino 中,发生无限递归后则会产生java.lang.OutOfMemoryError 而使 Rhino 停止运行。

如果递归函数不停地调用自身,运行将不会终止(这与无限循环的情况是一样的,因此俗称为无限递归)。JavaScript 发生无限递归之后的反应取决于实际的运行环境。如果是 SpiderMonkey 的壳层,则会像下面这样发生 InternalError。而在 Java6 附带的 Rhino 中,发生无限递归后则会产生java.lang.OutOfMemoryError 而使 Rhino 停止运行。

// SpiderMonkey 中的无限递归

function hzh() {
    hzh();
}
console.log("调用hzh函数:");
console.log(hzh());
[Running] node "e:\HMV\JavaScript\JavaScript.js"
调用hzh函数:
e:\HMV\JavaScript\JavaScript.js:3
function hzh() {
            ^

RangeError: Maximum call stack size exceeded
    at hzh (e:\HMV\JavaScript\JavaScript.js:3:13)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)

[Done] exited with code=1 in 0.225 seconds

必须在递归函数内部设置递归执行的停止条件判断,这称为终止条件。对于代码清单 6.2 中的情况,在函数开始处会对参数 n 的值是否小于等于 1 进行判断。终止条件的代码并不一定非要写在递归函数的头部,不过一般来说,写在头部更便于阅读。

必须在递归函数内部设置递归执行的停止条件判断,这称为终止条件。对于代码清单 6.2 中的情况,在函数开始处会对参数 n 的值是否小于等于 1 进行判断。终止条件的代码并不一定非要写在递归函数的头部,不过一般来说,写在头部更便于阅读。

可以通过循环实现的处理一定也能够通过递归处理来实现,反之也成立。这是因为,递归调用和循环处理两者的本质说到底都是反复执行某一操作。大多数情况下,通过循环来实现的代码会更为简洁明了。而且,在 JavaScript 中递归处理的执行效率并不一定很高。因此,一般情况下最好避免在 JavaScript中使用递归。

可以通过循环实现的处理一定也能够通过递归处理来实现,反之也成立。这是因为,递归调用和循环处理两者的本质说到底都是反复执行某一操作。大多数情况下,通过循环来实现的代码会更为简洁明了。而且,在 JavaScript 中递归处理的执行效率并不一定很高。因此,一般情况下最好避免在 JavaScript中使用递归。

能够通过 arguments.callee 来获取正在执行的 Function 对象的引用。这一引用可以在通过没有名字的函数(所谓的匿名函数)来实现递归函数时使用。下面是一个计算 n 的阶乘的具体示例(请注意,在 ECMAScript 第 5 版的静态模式中,arguments.callee 被禁止使用)。

能够通过 arguments.callee 来获取正在执行的 Function 对象的引用。这一引用可以在通过没有名字的函数(所谓的匿名函数)来实现递归函数时使用。下面是一个计算 n 的阶乘的具体示例(请注意,在 ECMAScript 第 5 版的静态模式中,arguments.callee 被禁止使用)。

// n 的阶乘(利用arguments.callee)
var hzh = (function(n) {
    if (n <= 1) { 
        return 1; 
    }
    else {
        return n*arguments.callee(n - 1);
    }
})(5);
console.log("输出hzh的值:");
console.log(hzh);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出hzh的值:
120

[Done] exited with code=0 in 0.177 seconds

6.4 作用域

作用域指的是名称(变量名与函数名)的有效范围。

作用域指的是名称(变量名与函数名)的有效范围。

在 JavaScript 中有以下两种作用域。

全局作用域
函数作用域

在 JavaScript 中有以下两种作用域。

  • 全局作用域
  • 函数作用域

全局作用域是函数之外(最外层代码)的作用域。在函数之外进行声明的名称属于全局作用域。这些名称就是所谓的全局变量以及全局函数。

全局作用域是函数之外(最外层代码)的作用域。在函数之外进行声明的名称属于全局作用域。这些名称就是所谓的全局变量以及全局函数。

而在函数内进行声明的名称拥有的是函数作用域,它们仅在该函数内部才有效。相对于全局作用域,可以将其称为局部作用域;相对于全局变量,又可以将其称为局部变量。作为函数形参的参数变量也属于函数作用域。

而在函数内进行声明的名称拥有的是函数作用域,它们仅在该函数内部才有效。相对于全局作用域,可以将其称为局部作用域;相对于全局变量,又可以将其称为局部变量。作为函数形参的参数变量也属于函数作用域。

JavaScript 的函数作用域的机制,与 Java(以及其他很多的程序设计语言)中的局部作用域有着微妙的差异。在 Java 中,局部变量所具有的作用域是从方法内对该变量进行声明的那一行开始的;而在JavaScript 中,函数作用域与进行声明的行数没有关系。

JavaScript 的函数作用域的机制,与 Java(以及其他很多的程序设计语言)中的局部作用域有着微妙的差异。在 Java 中,局部变量所具有的作用域是从方法内对该变量进行声明的那一行开始的;而在JavaScript 中,函数作用域与进行声明的行数没有关系。

请看代码清单6.3的例子。

请看代码清单6.3的例子。

代码清单6.3 函数作用域的注意事项

var hzh1 = 1;
function hzh() {
    // 对变量 x 进行访问
    console.log('hzh1 = ' + hzh1);
    var hzh2 = 2;
    // 对变量 x 进行访问
    console.log('hzh2 = ' + hzh2);
}
hzh();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
hzh1 = 1
hzh2 = 2

[Done] exited with code=0 in 0.311 seconds

【评】这里和书上的结果不一样,标记一下。

乍一看,会认为函数 hzh 内的第一个 console.log() 显示的是全局变量 hzh1。然而,这里的 hzh1 是在下一行进行声明的局部变量 hzh1。这是因为,局部变量 hzh1 的作用域是整个函数 hzh 内部。由于此时还没有对其进行赋值,因此变量 hzh1 的值为 undefined 值。也就是说,函数 hzh 与下面的代码是等价的。

乍一看,会认为函数 hzh 内的第一个 console.log() 显示的是全局变量 hzh1。然而,这里的 hzh1 是在下一行进行声明的局部变量 hzh1。这是因为,局部变量 hzh1 的作用域是整个函数 hzh 内部。由于此时还没有对其进行赋值,因此变量 hzh1 的值为 undefined 值。也就是说,函数 hzh 与下面的代码是等价的。

function HZH() {
    var hzh3;
    console.log('hzh3 = ' + hzh3);
    hzh3 = 3;
    console.log('hzh3 = ' + hzh3);
}
HZH();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
hzh3 = undefined
hzh3 = 3

[Done] exited with code=0 in 0.551 seconds

【评】这里的实验结果和书上的不太一样,标记一下,注释是书上的原文。

代码清单 6.3 中的代码非常不易于理解,常常是发生错误的原因。因此,我们建议在函数的开始处对所有的局部变量进行声明。

代码清单 6.3 中的代码非常不易于理解,常常是发生错误的原因。因此,我们建议在函数的开始处对所有的局部变量进行声明。

Java 等语言建议直到要使用某一变量时才对其进行声明,不过JavaScript 则有所不同,对此请加以注意。

Java 等语言建议直到要使用某一变量时才对其进行声明,不过JavaScript 则有所不同,对此请加以注意。

6.4.1 浏览器与作用域

在客户端 JavaScript 中,各个窗口(标签)、框架(包括 iframe)都有其各自的全局作用域。在窗口之间是无法访问各自全局作用域中的名称的,但父辈与其框架之间可以相互访问。

在客户端 JavaScript 中,各个窗口(标签)、框架(包括 iframe)都有其各自的全局作用域。在窗口之间是无法访问各自全局作用域中的名称的,但父辈与其框架之间可以相互访问。

6.4.2 块级作用域

在JavaScript(ECMAScript)中不存在块级作用域的概念,这一点与其他很多的程序设计语言不同。。举例来说,请看代码清单 6.1。如果认为块级作用域存在,就会认为第二个 console.log() 的结果应该是 1,不过实际的输出却是 2。

在JavaScript(ECMAScript)中不存在块级作用域的概念,这一点与其他很多的程序设计语言不同。。举例来说,请看代码清单 6.1。如果认为块级作用域存在,就会认为第二个 console.log() 的结果应该是 1,不过实际的输出却是 2。

代码清单6.1 对于块级作用域的误解

var hzh1 = 1;                  // 全局变量
{
    var hzh1 = 2;
    console.log("输出块级作用域的hzh1: ");
    console.log('hzh1 = ' + hzh1);
}
console.log("输出全局变量的hzh1:");
console.log('hzh1 = ' + hzh1); // 认为结果会是1?
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出块级作用域的hzh1: 
hzh1 = 2
输出全局变量的hzh1:
hzh1 = 2

[Done] exited with code=0 in 0.197 seconds

在代码清单 6.1 中,看似是在代码块内重新声明了块级作用域中的变量 hzh1,但实际上,它只是将全局变量 hzh1 赋值为了 2。也就是说,这与下面的代码是等价的。

在代码清单 6.1 中,看似是在代码块内重新声明了块级作用域中的变量 hzh1,但实际上,它只是将全局变量 hzh1 赋值为了 2。也就是说,这与下面的代码是等价的。

var hzh3 = 1; // 全局变量
{
    hzh3 = 2;
    console.log("输出块级作用域的hzh3:");
    console.log('hzh3 = ' + hzh3);
}
console.log("输出全局变量的hzh3:");
console.log('hzh3 = ' + hzh3);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出块级作用域的hzh3:
hzh3 = 2
输出全局变量的hzh3:
hzh3 = 2

[Done] exited with code=0 in 0.205 seconds

在函数作用域中也存在这种对块级作用域的错误理解。在 for语句中对循环变量进行声明是一种习惯做法,不过该循环变量的作用域并不局限于 for 语句内。在下面的代码中,其实是对局部变量 i 进行了循环使用。

在函数作用域中也存在这种对块级作用域的错误理解。在 for语句中对循环变量进行声明是一种习惯做法,不过该循环变量的作用域并不局限于 for 语句内。在下面的代码中,其实是对局部变量 i 进行了循环使用。

function hzh() {
    var hzh4 = 4;
    for(var hzh4 = 0; hzh4 < 10; hzh4++) {
        console.log("省略");
    }
    console.log('hzh4 = ' + hzh4);
}
hzh();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
省略
省略
省略
省略
省略
省略
省略
省略
省略
省略
hzh4 = 10

[Done] exited with code=0 in 0.188 seconds

6.4.3 let和块级作用域

虽然在 ECMAScript 第 5 版中没有块级作用域,不过 JavaScript 自带有 let 这一增强功能,可以实现块级作用域的效果。可以通过 let 定义(let 声明)、let 语句,以及 let 表达式三种方式来使用 let 功能。虽然语法结构不同,但是原理是一样的。

虽然在 ECMAScript 第 5 版中没有块级作用域,不过 JavaScript 自带有 let 这一增强功能,可以实现块级作用域的效果。可以通过 let 定义(let 声明)、let 语句,以及 let 表达式三种方式来使用 let 功能。虽然语法结构不同,但是原理是一样的。

let 定义(let 声明)与 var 声明的用法相同。可以通过下面这样的语法结构对变量进行声明。

let 定义(let 声明)与 var 声明的用法相同。可以通过下面这样的语法结构对变量进行声明。

let var1 [= value1] [, var2 [= value2]] [, ..., varN [= valueN]];

通过 let 声明进行声明的变量具有块级作用域。除了作用域之外,它的其他方面与通过 var 进行声明的变量没有区别。代码清单 6.4 中是个简单的例子。

通过 let 声明进行声明的变量具有块级作用域。除了作用域之外,它的其他方面与通过 var 进行声明的变量没有区别。代码清单 6.4 中是个简单的例子。

代码清单 6.4 let 声明

function hzh() {
    let hzh1 = 1;
    console.log("在函数作用域输出hzh1:")
    console.log('hzh1 = ' + hzh1);     // 输出1
    {
        let hzh1 = 2;
        console.log("在块级作用域输出hzh1:");
        console.log('hzh1 = ' + hzh1); // 输出2
    }                                  // let hzh1 = 2 的作用域到此为止
    console.log("在函数作用域输出hzh1:");
    console.log('hzh1 = ' + hzh1);     // 输出1
} 
hzh();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
在函数作用域输出hzh1:
hzh1 = 1
在块级作用域输出hzh1:
hzh1 = 2
在函数作用域输出hzh1:
hzh1 = 1

[Done] exited with code=0 in 0.18 seconds

如果不考虑作用域的不同,let 变量(通过 let 声明进行声明的变量)与 var 变量的执行方式非常相似。请参见代码清单 6.5 中的注释部分。

如果不考虑作用域的不同,let 变量(通过 let 声明进行声明的变量)与 var 变量的执行方式非常相似。请参见代码清单 6.5 中的注释部分。

代码清单 6.5 let变量的执行方式的具体示例

// 名称的查找
function HZH1() {
    let hzh1 = 1;
    {
        console.log("在块级作用域中输出hzh1:");
        console.log('hzh1 = ' + hzh1); // 输出 1。将对代码块由内至外进行名称查找
    }
}
HZH1();
console.log("****************************************************");
// 该名称在进行 let 声明之前也是有效的
function HZH2() {
    let hzh2 = 2;
    {
        // 这里的 let hzh2 = 2的作用域。
        //不过由于还未对其进行赋值,所以 let 变量 hzh2 的值为undefined
        console.log("在块级作用域中第一次输出hzh2:");
        console.log('hzh2 = ' + hzh2);

        let hzh2 = 3;
        console.log("在块级作用域中第二次输出hzh2:");
        console.log('hzh2 = ' + hzh2); // 输出2
    } 
}
HZH2();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
在块级作用域中输出hzh1:
hzh1 = 1
****************************************************
在块级作用域中第一次输出hzh2:
e:\HMV\JavaScript\JavaScript.js:18
        console.log('hzh2 = ' + hzh2);
                                ^

ReferenceError: Cannot access 'hzh2' before initialization
    at HZH2 (e:\HMV\JavaScript\JavaScript.js:18:33)
    at Object.<anonymous> (e:\HMV\JavaScript\JavaScript.js:25:1)
    at Module._compile (internal/modules/cjs/loader.js:999:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
    at Module.load (internal/modules/cjs/loader.js:863:32)
    at Function.Module._load (internal/modules/cjs/loader.js:708:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)
    at internal/main/run_main_module.js:17:47

[Done] exited with code=1 in 0.203 seconds

【评】这里的实验结果和书上的不一样,标记一下。

像下面这样,将 for 语句的初始化表达式中的 var 声明改为 let 变量之后,作用域就将被限制于 for 语句之内。这样的做法更符合通常的思维方式。for in 语句以及 for each in 语句也是同理。

像下面这样,将 for 语句的初始化表达式中的 var 声明改为 let 变量之后,作用域就将被限制于 for 语句之内。这样的做法更符合通常的思维方式。for in 语句以及 for each in 语句也是同理。

var arr = ["h", "z", "h"];
for(let hzh = 0, len = arr.length; hzh < len; hzh++) {
    console.log("arr[" + hzh + "] = " + arr[hzh]);
}
// 这里已是let变量hzh的作用域之外
[Running] node "e:\HMV\JavaScript\JavaScript.js"
arr[0] = h
arr[1] = z
arr[2] = h

[Done] exited with code=0 in 0.259 seconds

let 语句的语法结构如下。let 变量的作用域被限制于语句内部。

let 语句的语法结构如下。let 变量的作用域被限制于语句内部。

let (var1 [= value1] [, var2 [= value2] [, ..., varN [= valueN]]]) 语句;

下面是 let 语句的具体示例。

下面是 let 语句的具体示例。

let hzh = 1;
{                     // 代码块
    console.log("在代码块中输出hzh:");
    console.log("hzh = " + hzh); // 输出1
}                     // let变量的作用域到此为止
[Running] node "e:\HMV\JavaScript\JavaScript.js"
在代码块中输出hzh:
hzh = 1

[Done] exited with code=0 in 0.185 seconds

代码清单 6.6 是一个混用 var 声明与 let 语句的具体示例。

代码清单 6.6 是一个混用 var 声明与 let 语句的具体示例。

代码清单 6.6 var 声明与 let 语句

function huangzihan() {
    var hzh1 = 1;
    let hzh2 = 2;
    {
        console.log("输出hzh2:");
        console.log("hzh2 = " + hzh2);     // 输出2
        console.log("");
        hzh3 = 3;
        console.log("输出hzh3:");
        console.log("hzh3 = " + hzh3);     // 输出3
        console.log("");
    }
    console.log("输出hzh1:");  // 输出1
    console.log("hzh1 = " + hzh1);
}

huangzihan();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出hzh2:
hzh2 = 2

输出hzh3:
hzh3 = 3

输出hzh1:
hzh1 = 1

[Done] exited with code=0 in 0.264 seconds

在 let 语句内部,声明与 let 变量同名的变量会引起 TypeError 问题。下面是一个例子。

在 let 语句内部,声明与 let 变量同名的变量会引起 TypeError 问题。下面是一个例子。

// 不能通过let声明同名变量

let (hzh1 = 1) {
    let hzh1 = 2;
    console.log(hzh1); 
}

// 也不能通过 var 声明同名变量

let (hzh2 = 1) {
    var hzh2 = 2;
    console.log(hzh2); 
}

结果和书上说的不一样,标记一下。

let 表达式的语法结构如下所示。let 变量的作用域被限制于表达式内部。

let 表达式的语法结构如下所示。let 变量的作用域被限制于表达式内部。

let (var1 [= value1] [, var2 [= value2]] [, ..., varN [= valueN]]) 表达式;

下面是let表达式的具体示例。

下面是let表达式的具体示例。

var hzh1 = 1;
var hzh2 = let(hzh1 = 2) hzh1 + 1 ; // 在表达式 hzh1 + 1 中使用了 let变量(值为2)
console.log(hzh1, hzh2);            // 对 var 变量 hzh1 没有影响

这里的也是和上的不一样。

6.4.4 嵌套函数与作用域

在 JavaScript 中我们可以对函数进行嵌套声明。也就是说,可以在一个函数中声明另一个函数。这时,可以在内部的函数中访问其外部函数的作用域。从形式上来说,名称的查找是由内向外的。在最后将会查找全局作用域中的名称。

在 JavaScript 中我们可以对函数进行嵌套声明。也就是说,可以在一个函数中声明另一个函数。这时,可以在内部的函数中访问其外部函数的作用域。从形式上来说,名称的查找是由内向外的。在最后将会查找全局作用域中的名称。

代码清单 6.7 是个具体例子。在代码清单 6.7 中写的是函数声明语句,如果使用的是匿名函数表达式,效果是相同的。

代码清单 6.7 是个具体例子。在代码清单 6.7 中写的是函数声明语句,如果使用的是匿名函数表达式,效果是相同的。

代码清单 6.7 嵌套函数及其作用域

function huangzihan1 () {
    var hzh1 = 1; // 函数huangzihan1的局部变量

    // 嵌套函数的声明
    function huangzihan2 () {
        var hzh2 = 2; // 函数huangzihan2的局部变量
        console.log("对函数huangzihan2的局部变量进行访问:");
        console.log(hzh1); 
        console.log("");
        console.log("对函数huangzihan2的局部变量进行访问:");
        console.log(hzh2);
    }

    function huangzihan3() {
        console.log(hzh2); // 如果不存在全局变量hzh2,则会发生ReferenceError
    }

    // 嵌套函数的调用
    huangzihan2();
    huangzihan3();
}

huangzihan1();
[Running] node "e:\HMV\Babel\hzh.js"
对函数huangzihan2的局部变量进行访问:
1

对函数huangzihan2的局部变量进行访问:
2
e:\HMV\Babel\hzh.js:15
        console.log(hzh2); // 如果不存在全局变量hzh2,则会发生ReferenceError
                    ^

ReferenceError: hzh2 is not defined
    at huangzihan3 (e:\HMV\Babel\hzh.js:15:21)
    at huangzihan1 (e:\HMV\Babel\hzh.js:20:5)
    at Object.<anonymous> (e:\HMV\Babel\hzh.js:23:1)
    at Module._compile (internal/modules/cjs/loader.js:999:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
    at Module.load (internal/modules/cjs/loader.js:863:32)
    at Function.Module._load (internal/modules/cjs/loader.js:708:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)
    at internal/main/run_main_module.js:17:47

[Done] exited with code=1 in 0.371 seconds

6.4.5 变量隐藏

在这里使用了隐藏这一比较专业的术语,它指的是,通过作用域较小的变量(或函数),来隐藏作用域较大的同名变量(或函数)。这种情况常常会在无意中发生,从而造成错误。例如,在下面的代码中,全局变量 n 被局部变量 n 所隐藏。

在这里使用了隐藏这一比较专业的术语,它指的是,通过作用域较小的变量(或函数),来隐藏作用域较大的同名变量(或函数)。这种情况常常会在无意中发生,从而造成错误。例如,在下面的代码中,全局变量 n 被局部变量 n 所隐藏。

var hzh1 = 1; // 全局变量

function huangzihan() { // 局部变量隐藏了全局变量
    var hzh1 = 2;
    console.log("检测一下局部变量有没有隐藏了全局变量:");
    console.log(hzh1);
}

// 函数调用
huangzihan();
[Running] node "e:\HMV\Babel\hzh.js"
检测一下局部变量有没有隐藏了全局变量:
2

[Done] exited with code=0 in 0.24 seconds

这段代码的功能显而易见。乍一看,类似于代码清单 6.3 或代码清单 6.1 那样的函数作用域以及块级作用域所构成的隐藏并不会引发什么问题。不过,当代码变得更为复杂时,问题就不容易发现了,因此仍需多加注意。

这段代码的功能显而易见。乍一看,类似于代码清单 6.3 或代码清单 6.1 那样的函数作用域以及块级作用域所构成的隐藏并不会引发什么问题。不过,当代码变得更为复杂时,问题就不容易发现了,因此仍需多加注意。

6.5 函数是一种对象

6.6 Function类

6.7 嵌套函数声明与闭包

6.8 回调函数设计模式

————————

achievements

章节 代码量(行)
6.1 0
6.2 14
6.3 44
6.4 154
第6章 212

6.1 function declaration statement and anonymous function expression

You can declare functions through function declaration statements and anonymous function expressions.

You can declare functions through function declaration statements and anonymous function expressions.

6.2 classification of function calls

Table 6.1 classification of function calls

名称 说明
方法调用 通过接收方对象对函数进行调用(包括apply 与call 调用)
构造函数调用 通过new 表达式对函数进行调用
函数调用 以上两种方式之外的函数调用

The function used in the way of method call is called method. Similarly, the function used in the way of constructor call is called constructor.

The function used in the way of method call is called method. Similarly, the function used in the way of constructor call is called constructor.

Post of function declaration statement

A function declared by a function declaration statement can be called before the declared line of code. Although this example is done in the function
Within the scope, but the same is true for the global scope.

A function declared by a function declaration statement can be called before the declared line of code. Although this example is done in the function
Within the scope, but the same is true for the global scope.

function hzh1() {
    hzh2(); // 在声明函数fn之前对其进行调用
    function hzh2() {
        console.log('黄子涵');
    }
}
hzh1();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
黄子涵

[Done] exited with code=0 in 99.724 seconds

A function declared by a function declaration statement can be called before the declared line of code. In the case of anonymous function expressions, the results will be different.

A function declared by a function declaration statement can be called before the declared line of code. In the case of anonymous function expressions, the results will be different.

function hzh3() {
    hzh4();
    var hzh4 = function() {
        console.log('黄子涵');
    }
}
hzh3();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
e:\HMV\JavaScript\JavaScript.js:2
    hzh4();
    ^

TypeError: hzh4 is not a function
    at hzh3 (e:\HMV\JavaScript\JavaScript.js:2:5)
    at Object.<anonymous> (e:\HMV\JavaScript\JavaScript.js:7:1)
    at Module._compile (internal/modules/cjs/loader.js:999:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
    at Module.load (internal/modules/cjs/loader.js:863:32)
    at Function.Module._load (internal/modules/cjs/loader.js:708:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)
    at internal/main/run_main_module.js:17:47

[Done] exited with code=1 in 3.35 seconds

6.3 parameters and local variables

6.3.1 arguments 对象

Arguments can be accessed by using the arguments object within the function. The usage is shown in code listing 6.1.

Arguments can be accessed by using the arguments object within the function. The usage is shown in code listing 6.1.

Listing 6.1 shows an example of using the arguments object

function hzh1() {
    console.log(arguments.length);
    console.log(arguments[0], arguments[1], arguments[2]);
}
console.log("arguments.length 为实参的数量,值为1");
console.log("arguments[0]的值为7");
console.log(hzh1(7));
console.log("*********************************************");
console.log("arguments.length 为实参的数量,值为2");
console.log("arguments[0] 的值为7,arguments[1] 的值为8");
console.log(hzh1(7, 8));
console.log("*********************************************");
console.log("arguments.length为实参的数量,值为1");
console.log("arguments[0]的值为7");
console.log("arguments[1]的值为8");
console.log("arguments[2] 的值为9");
console.log(hzh1(7, 8, 9));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
arguments.length 为实参的数量,值为1
arguments[0]的值为7
1
7 undefined undefined
undefined
*********************************************
arguments.length 为实参的数量,值为2
arguments[0] 的值为7,arguments[1] 的值为8
2
7 8 undefined
undefined
*********************************************
arguments.length为实参的数量,值为1
arguments[0]的值为7
arguments[1]的值为8
arguments[2] 的值为9
3
7 8 9
undefined

[Done] exited with code=0 in 0.506 seconds

Arguments without corresponding formal parameters can also be accessed through arguments. Because it can pass arguments Length knows the number of arguments, so you can write the so-called variable length parameter function. The number of formal parameters can be obtained through the length attribute of the function object itself.

Arguments without corresponding formal parameters can also be accessed through arguments. Because it can pass arguments Length knows the number of arguments, so you can write the so-called variable length parameter function. The number of formal parameters can be obtained through the length attribute of the function object itself.

Although arguments can be used as an array, it is not an array object in itself. Therefore, the methods in the array class cannot be used on it.

Although arguments can be used as an array, it is not an array object in itself. Therefore, the methods in the array class cannot be used on it.

6.3.2 recursive function

A recursive function is a function that calls itself within a function. This method is called recursive execution or recursive call.

A recursive function is a function that calls itself within a function. This method is called recursive execution or recursive call.

Code listing 6.2 factorial of n (example of recursive function)

function hzh1(hzh2) {
    if(hzh2 <= 1) {
        return 1;
    } else {
        return hzh2 * hzh1(hzh2 - 1); 
    }
}
console.log("调用函数hzh1:");
console.log(hzh1(5));
[Running] node "e:\HMV\JavaScript\JavaScript.js"
调用函数hzh1:
120

[Done] exited with code=0 in 1.416 seconds

If the recursive function keeps calling itself, the operation will not terminate (this is the same as the case of infinite loop, so it is commonly known as infinite recursion). The reaction of JavaScript after infinite recursion depends on the actual running environment. If it is the shell of SpiderMonkey, internalerror will occur as follows. In rhino attached to Java 6, after infinite recursion, Java Lang. outofmemoryerror to stop rhino.

If the recursive function keeps calling itself, the operation will not terminate (this is the same as the case of infinite loop, so it is commonly known as infinite recursion). The reaction of JavaScript after infinite recursion depends on the actual running environment. If it is the shell of SpiderMonkey, internalerror will occur as follows. In rhino attached to Java 6, after infinite recursion, Java Lang. outofmemoryerror to stop rhino.

// SpiderMonkey 中的无限递归

function hzh() {
    hzh();
}
console.log("调用hzh函数:");
console.log(hzh());
[Running] node "e:\HMV\JavaScript\JavaScript.js"
调用hzh函数:
e:\HMV\JavaScript\JavaScript.js:3
function hzh() {
            ^

RangeError: Maximum call stack size exceeded
    at hzh (e:\HMV\JavaScript\JavaScript.js:3:13)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)
    at hzh (e:\HMV\JavaScript\JavaScript.js:4:5)

[Done] exited with code=1 in 0.225 seconds

The stop condition judgment of recursive execution must be set inside the recursive function, which is called termination condition. In the case of code listing 6.2, whether the value of parameter n is less than or equal to 1 will be judged at the beginning of the function. The code of the termination condition does not have to be written in the head of the recursive function, but in general, it is easier to read.

The stop condition judgment of recursive execution must be set inside the recursive function, which is called termination condition. In the case of code listing 6.2, whether the value of parameter n is less than or equal to 1 will be judged at the beginning of the function. The code of the termination condition does not have to be written in the head of the recursive function, but in general, it is easier to read.

The processing that can be realized through loop must also be realized through recursive processing, and vice versa. This is because the essence of recursive call and loop processing is to perform an operation repeatedly. In most cases, the code implemented through loops will be more concise. Moreover, the execution efficiency of recursive processing in JavaScript is not necessarily very high. Therefore, it is generally best to avoid using recursion in JavaScript.

The processing that can be realized through loop must also be realized through recursive processing, and vice versa. This is because the essence of recursive call and loop processing is to perform an operation repeatedly. In most cases, the code implemented through loops will be more concise. Moreover, the execution efficiency of recursive processing in JavaScript is not necessarily very high. Therefore, it is generally best to avoid using recursion in JavaScript.

Can pass arguments Callee to get the reference of the function object being executed. This reference can be used when implementing recursive functions through unnamed functions (so-called anonymous functions). The following is a specific example of calculating the factorial of n (note that arguments.callee is prohibited in the static mode of ECMAScript version 5).

Can pass arguments Callee to get the reference of the function object being executed. This reference can be used when implementing recursive functions through unnamed functions (so-called anonymous functions). The following is a specific example of calculating the factorial of n (note that arguments.callee is prohibited in the static mode of ECMAScript version 5).

// n 的阶乘(利用arguments.callee)
var hzh = (function(n) {
    if (n <= 1) { 
        return 1; 
    }
    else {
        return n*arguments.callee(n - 1);
    }
})(5);
console.log("输出hzh的值:");
console.log(hzh);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出hzh的值:
120

[Done] exited with code=0 in 0.177 seconds

6.4 scope

Scope refers to the valid range of names (variable name and function name).

Scope refers to the valid range of names (variable name and function name).

There are two scopes in JavaScript.
global scope
Function scope

There are two scopes in JavaScript.

  • global scope
  • Function scope

The most out of scope (global) function is the code. The name declared outside the function belongs to the global scope. These names are called global variables and global functions.

The most out of scope (global) function is the code. The name declared outside the function belongs to the global scope. These names are called global variables and global functions.

Names declared inside a function have a function scope, and they are valid only inside the function. Relative to the global scope, it can be called local scope; Relative to global variables, they can also be called local variables. Parameter variables as function parameters also belong to function scope.

Names declared inside a function have a function scope, and they are valid only inside the function. Relative to the global scope, it can be called local scope; Relative to global variables, they can also be called local variables. Parameter variables as function parameters also belong to function scope.

The function scope mechanism of JavaScript is subtly different from the local scope in Java (and many other programming languages). In Java, the scope of a local variable starts from the line that declares the variable in the method; In JavaScript, the scope of a function has nothing to do with the number of lines declared.

The function scope mechanism of JavaScript is subtly different from the local scope in Java (and many other programming languages). In Java, the scope of a local variable starts from the line that declares the variable in the method; In JavaScript, the scope of a function has nothing to do with the number of lines declared.

Take a look at the example in listing 6.3.

Take a look at the example in listing 6.3.

Code listing 6.3 considerations for function scope

var hzh1 = 1;
function hzh() {
    // 对变量 x 进行访问
    console.log('hzh1 = ' + hzh1);
    var hzh2 = 2;
    // 对变量 x 进行访问
    console.log('hzh2 = ' + hzh2);
}
hzh();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
hzh1 = 1
hzh2 = 2

[Done] exited with code=0 in 0.311 seconds

[comment] the result here is different from that in the book. Please mark it.

At first glance, you’ll think of the first console in the function Hzh Log () shows the global variable hzh1. However, hzh1 here is the local variable hzh1 declared on the next line. This is because the scope of the local variable hzh1 is within the whole function Hzh. Since it has not been assigned at this time, the value of variable hzh1 is undefined. That is, the function Hzh is equivalent to the following code.

At first glance, you’ll think of the first console in the function Hzh Log () shows the global variable hzh1. However, hzh1 here is the local variable hzh1 declared on the next line. This is because the scope of the local variable hzh1 is within the whole function Hzh. Since it has not been assigned at this time, the value of variable hzh1 is undefined. That is, the function Hzh is equivalent to the following code.

function HZH() {
    var hzh3;
    console.log('hzh3 = ' + hzh3);
    hzh3 = 3;
    console.log('hzh3 = ' + hzh3);
}
HZH();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
hzh3 = undefined
hzh3 = 3

[Done] exited with code=0 in 0.551 seconds

[comment] the experimental results here are different from those in the book. Mark it and the notes are the original text in the book.

The code in listing 6.3 is very difficult to understand and is often the cause of errors. Therefore, we recommend declaring all local variables at the beginning of the function.

The code in listing 6.3 is very difficult to understand and is often the cause of errors. Therefore, we recommend declaring all local variables at the beginning of the function.

Languages such as Java suggest that a variable should not be declared until it is used, but JavaScript is different. Please pay attention to this.

Languages such as Java suggest that a variable should not be declared until it is used, but JavaScript is different. Please pay attention to this.

6.4.1 browser and scope

In client-side JavaScript, each window (label) and framework (including iframe) has its own global scope. The names in their global scope cannot be accessed between windows, but the parent and its framework can access each other.

In client-side JavaScript, each window (label) and framework (including iframe) has its own global scope. The names in their global scope cannot be accessed between windows, but the parent and its framework can access each other.

6.4.2 block level scope

There is no concept of block level scope in JavaScript (ECMAScript), which is different from many other programming languages.. For example, see code listing 6.1. If the block level scope is considered to exist, the second console will be considered The result of log() should be 1, but the actual output is 2.

There is no concept of block level scope in JavaScript (ECMAScript), which is different from many other programming languages.. For example, see code listing 6.1. If the block level scope is considered to exist, the second console will be considered The result of log() should be 1, but the actual output is 2.

Code listing 6.1 misunderstandings about block level scopes

var hzh1 = 1;                  // 全局变量
{
    var hzh1 = 2;
    console.log("输出块级作用域的hzh1: ");
    console.log('hzh1 = ' + hzh1);
}
console.log("输出全局变量的hzh1:");
console.log('hzh1 = ' + hzh1); // 认为结果会是1?
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出块级作用域的hzh1: 
hzh1 = 2
输出全局变量的hzh1:
hzh1 = 2

[Done] exited with code=0 in 0.197 seconds

In listing 6.1, it seems that the variable hzh1 in the block level scope is redeclared in the code block, but in fact, it only assigns the global variable hzh1 to 2. In other words, this is equivalent to the following code.

In listing 6.1, it seems that the variable hzh1 in the block level scope is redeclared in the code block, but in fact, it only assigns the global variable hzh1 to 2. In other words, this is equivalent to the following code.

var hzh3 = 1; // 全局变量
{
    hzh3 = 2;
    console.log("输出块级作用域的hzh3:");
    console.log('hzh3 = ' + hzh3);
}
console.log("输出全局变量的hzh3:");
console.log('hzh3 = ' + hzh3);
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出块级作用域的hzh3:
hzh3 = 2
输出全局变量的hzh3:
hzh3 = 2

[Done] exited with code=0 in 0.205 seconds

This misunderstanding of block level scope also exists in function scope. It is customary to declare a loop variable in a for statement, but the scope of the loop variable is not limited to the for statement. In the following code, the local variable I is actually recycled.

This misunderstanding of block level scope also exists in function scope. It is customary to declare a loop variable in a for statement, but the scope of the loop variable is not limited to the for statement. In the following code, the local variable I is actually recycled.

function hzh() {
    var hzh4 = 4;
    for(var hzh4 = 0; hzh4 < 10; hzh4++) {
        console.log("省略");
    }
    console.log('hzh4 = ' + hzh4);
}
hzh();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
省略
省略
省略
省略
省略
省略
省略
省略
省略
省略
hzh4 = 10

[Done] exited with code=0 in 0.188 seconds

6.4.3 let and block level scopes

Although there is no block level scope in ECMAScript version 5, JavaScript comes with the enhancement of let, which can achieve the effect of block level scope. You can use the let function in three ways: let definition (let declaration), let statement, and let expression. Although the grammatical structure is different, the principle is the same.

Although there is no block level scope in ECMAScript version 5, JavaScript comes with the enhancement of let, which can achieve the effect of block level scope. You can use the let function in three ways: let definition (let declaration), let statement, and let expression. Although the grammatical structure is different, the principle is the same.

Let definitions (let declarations) are used in the same way as var declarations. Variables can be declared through the following syntax structure.

Let definitions (let declarations) are used in the same way as var declarations. Variables can be declared through the following syntax structure.

let var1 [= value1] [, var2 [= value2]] [, ..., varN [= valueN]];

Variables declared through let declarations have block level scope. Except for scope, it is no different from variables declared through VAR in other aspects. A simple example is shown in listing 6.4.

Variables declared through let declarations have block level scope. Except for scope, it is no different from variables declared through VAR in other aspects. A simple example is shown in listing 6.4.

Code listing 6.4 let declaration

function hzh() {
    let hzh1 = 1;
    console.log("在函数作用域输出hzh1:")
    console.log('hzh1 = ' + hzh1);     // 输出1
    {
        let hzh1 = 2;
        console.log("在块级作用域输出hzh1:");
        console.log('hzh1 = ' + hzh1); // 输出2
    }                                  // let hzh1 = 2 的作用域到此为止
    console.log("在函数作用域输出hzh1:");
    console.log('hzh1 = ' + hzh1);     // 输出1
} 
hzh();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
在函数作用域输出hzh1:
hzh1 = 1
在块级作用域输出hzh1:
hzh1 = 2
在函数作用域输出hzh1:
hzh1 = 1

[Done] exited with code=0 in 0.18 seconds

Regardless of the scope, let variables (variables declared through let declarations) are executed in a very similar way to var variables. See the comments section in listing 6.5.

Regardless of the scope, let variables (variables declared through let declarations) are executed in a very similar way to var variables. See the comments section in listing 6.5.

Listing 6.5 shows a specific example of how the let variable is executed

// 名称的查找
function HZH1() {
    let hzh1 = 1;
    {
        console.log("在块级作用域中输出hzh1:");
        console.log('hzh1 = ' + hzh1); // 输出 1。将对代码块由内至外进行名称查找
    }
}
HZH1();
console.log("****************************************************");
// 该名称在进行 let 声明之前也是有效的
function HZH2() {
    let hzh2 = 2;
    {
        // 这里的 let hzh2 = 2的作用域。
        //不过由于还未对其进行赋值,所以 let 变量 hzh2 的值为undefined
        console.log("在块级作用域中第一次输出hzh2:");
        console.log('hzh2 = ' + hzh2);

        let hzh2 = 3;
        console.log("在块级作用域中第二次输出hzh2:");
        console.log('hzh2 = ' + hzh2); // 输出2
    } 
}
HZH2();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
在块级作用域中输出hzh1:
hzh1 = 1
****************************************************
在块级作用域中第一次输出hzh2:
e:\HMV\JavaScript\JavaScript.js:18
        console.log('hzh2 = ' + hzh2);
                                ^

ReferenceError: Cannot access 'hzh2' before initialization
    at HZH2 (e:\HMV\JavaScript\JavaScript.js:18:33)
    at Object.<anonymous> (e:\HMV\JavaScript\JavaScript.js:25:1)
    at Module._compile (internal/modules/cjs/loader.js:999:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
    at Module.load (internal/modules/cjs/loader.js:863:32)
    at Function.Module._load (internal/modules/cjs/loader.js:708:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)
    at internal/main/run_main_module.js:17:47

[Done] exited with code=1 in 0.203 seconds

[comment] the experimental results here are different from those in the book. Please mark them.

As follows, after changing the VaR declaration in the initialization expression of the for statement to the let variable, the scope will be limited to the for statement. This approach is more in line with the usual way of thinking. The same is true for the for in statement and for each in statement.

As follows, after changing the VaR declaration in the initialization expression of the for statement to the let variable, the scope will be limited to the for statement. This approach is more in line with the usual way of thinking. The same is true for the for in statement and for each in statement.

var arr = ["h", "z", "h"];
for(let hzh = 0, len = arr.length; hzh < len; hzh++) {
    console.log("arr[" + hzh + "] = " + arr[hzh]);
}
// 这里已是let变量hzh的作用域之外
[Running] node "e:\HMV\JavaScript\JavaScript.js"
arr[0] = h
arr[1] = z
arr[2] = h

[Done] exited with code=0 in 0.259 seconds

The syntax structure of the let statement is as follows. The scope of the let variable is limited to the inside of the statement.

The syntax structure of the let statement is as follows. The scope of the let variable is limited to the inside of the statement.

let (var1 [= value1] [, var2 [= value2] [, ..., varN [= valueN]]]) 语句;

The following is a specific example of a let statement.

The following is a specific example of a let statement.

let hzh = 1;
{                     // 代码块
    console.log("在代码块中输出hzh:");
    console.log("hzh = " + hzh); // 输出1
}                     // let变量的作用域到此为止
[Running] node "e:\HMV\JavaScript\JavaScript.js"
在代码块中输出hzh:
hzh = 1

[Done] exited with code=0 in 0.185 seconds

Listing 6.6 is a concrete example of mixing var declarations with let statements.

Listing 6.6 is a concrete example of mixing var declarations with let statements.

Code listing 6.6 var declaration and let statement

function huangzihan() {
    var hzh1 = 1;
    let hzh2 = 2;
    {
        console.log("输出hzh2:");
        console.log("hzh2 = " + hzh2);     // 输出2
        console.log("");
        hzh3 = 3;
        console.log("输出hzh3:");
        console.log("hzh3 = " + hzh3);     // 输出3
        console.log("");
    }
    console.log("输出hzh1:");  // 输出1
    console.log("hzh1 = " + hzh1);
}

huangzihan();
[Running] node "e:\HMV\JavaScript\JavaScript.js"
输出hzh2:
hzh2 = 2

输出hzh3:
hzh3 = 3

输出hzh1:
hzh1 = 1

[Done] exited with code=0 in 0.264 seconds

Inside the let statement, declaring a variable with the same name as the let variable can cause typeerror problems. Here is an example.

Inside the let statement, declaring a variable with the same name as the let variable can cause typeerror problems. Here is an example.

// 不能通过let声明同名变量

let (hzh1 = 1) {
    let hzh1 = 2;
    console.log(hzh1); 
}

// 也不能通过 var 声明同名变量

let (hzh2 = 1) {
    var hzh2 = 2;
    console.log(hzh2); 
}

The result is different from that in the book. Mark it.

The syntax structure of the let expression is as follows. The scope of the let variable is restricted to the inside of the expression.

The syntax structure of the let expression is as follows. The scope of the let variable is restricted to the inside of the expression.

let (var1 [= value1] [, var2 [= value2]] [, ..., varN [= valueN]]) 表达式;

The following is a specific example of a let expression.

The following is a specific example of a let expression.

var hzh1 = 1;
var hzh2 = let(hzh1 = 2) hzh1 + 1 ; // 在表达式 hzh1 + 1 中使用了 let变量(值为2)
console.log(hzh1, hzh2);            // 对 var 变量 hzh1 没有影响

The here is also different from the above.

6.4.4 nested functions and scopes

In JavaScript, we can make nested declarations of functions. In another function, that is to say, in another function. At this time, you can access the scope of its external function in the internal function. Formally, the search of names is from the inside out. At the end, the name in the global scope will be found.

In JavaScript, we can make nested declarations of functions. In another function, that is to say, in another function. At this time, you can access the scope of its external function in the internal function. Formally, the search of names is from the inside out. At the end, the name in the global scope will be found.

Listing 6.7 is a concrete example. The function declaration statement written in code listing 6.7 has the same effect if an anonymous function expression is used.

Listing 6.7 is a concrete example. The function declaration statement written in code listing 6.7 has the same effect if an anonymous function expression is used.

Code listing 6.7 nested functions and their scopes

function huangzihan1 () {
    var hzh1 = 1; // 函数huangzihan1的局部变量

    // 嵌套函数的声明
    function huangzihan2 () {
        var hzh2 = 2; // 函数huangzihan2的局部变量
        console.log("对函数huangzihan2的局部变量进行访问:");
        console.log(hzh1); 
        console.log("");
        console.log("对函数huangzihan2的局部变量进行访问:");
        console.log(hzh2);
    }

    function huangzihan3() {
        console.log(hzh2); // 如果不存在全局变量hzh2,则会发生ReferenceError
    }

    // 嵌套函数的调用
    huangzihan2();
    huangzihan3();
}

huangzihan1();
[Running] node "e:\HMV\Babel\hzh.js"
对函数huangzihan2的局部变量进行访问:
1

对函数huangzihan2的局部变量进行访问:
2
e:\HMV\Babel\hzh.js:15
        console.log(hzh2); // 如果不存在全局变量hzh2,则会发生ReferenceError
                    ^

ReferenceError: hzh2 is not defined
    at huangzihan3 (e:\HMV\Babel\hzh.js:15:21)
    at huangzihan1 (e:\HMV\Babel\hzh.js:20:5)
    at Object.<anonymous> (e:\HMV\Babel\hzh.js:23:1)
    at Module._compile (internal/modules/cjs/loader.js:999:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
    at Module.load (internal/modules/cjs/loader.js:863:32)
    at Function.Module._load (internal/modules/cjs/loader.js:708:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)
    at internal/main/run_main_module.js:17:47

[Done] exited with code=1 in 0.371 seconds

6.4.5 variable hiding

The professional term “hiding” is used here. It refers to hiding variables (or functions) with the same name with a larger scope through variables (or functions) with a smaller scope. This often happens unintentionally, resulting in errors. For example, in the following code, the global variable n is hidden by the local variable n.

The professional term “hiding” is used here. It refers to hiding variables (or functions) with the same name with a larger scope through variables (or functions) with a smaller scope. This often happens unintentionally, resulting in errors. For example, in the following code, the global variable n is hidden by the local variable n.

var hzh1 = 1; // 全局变量

function huangzihan() { // 局部变量隐藏了全局变量
    var hzh1 = 2;
    console.log("检测一下局部变量有没有隐藏了全局变量:");
    console.log(hzh1);
}

// 函数调用
huangzihan();
[Running] node "e:\HMV\Babel\hzh.js"
检测一下局部变量有没有隐藏了全局变量:
2

[Done] exited with code=0 in 0.24 seconds

The function of this code is obvious. At first glance, the concealment of function scopes like listing 6.3 or listing 6.1 and block level scopes does not cause any problems. However, when the code becomes more complex, the problem is not easy to find, so we still need to pay more attention.

The function of this code is obvious. At first glance, the concealment of function scopes like listing 6.3 or listing 6.1 and block level scopes does not cause any problems. However, when the code becomes more complex, the problem is not easy to find, so we still need to pay more attention.

6.5 a function is an object

6.6 Function类

6.7 nested function declaration and closure

6.8 callback function design mode