轻松学习 JavaScript

英文: Dhananjay Kumar   译文:码农网/小峰

http://www.codeceo.com/article/easy-javascript-05-hoisting.html

轻松学习 JavaScript(1):了解 let 语句

使用let语句,允许你在JavaScript中创建块范围局部变量。let语句是在JavaScript的ECMAScript 6标准中引入的。在你往下了解let语句之前,我建议你先查看基于Infragistics jQuery库的Ignite UI,它可以帮助你更快地编写和运行Web应用程序。你可以使用JavaScript库的Ignite UI来快速解决HTML5,jQuery,Angular,React或ASP.NET MVC中复杂的LOB需求。(你可以在这里下载Ignite UI的免费试用版。)

在ECMAScript 6之前,JavaScript有三种类型的范围:

  • 全局范围

  • 函数范围

  • 词汇范围

  • 为了详细探索let语句,请细想下面的代码段:

    function foo() {

        var x = 9;

        if (x > 5) {

            var x = 7;

            console.log("Value of x in if statement = " + x);

        }

        console.log("Value of x outside if statement = " + x);

    }

    foo();

    以上代码得到的输出:

    轻松学习 JavaScript

    在上面的代码中,我们使用var语句声明变量x。因此,变量x的范围是函数范围。if语句内的变量x 就是if语句外创建的变量x 。因此,在你修改if语句块内变量x的值时,也会修改函数中变量x的所有引用的值。

    为了避免这种情况,你需要使用块级别范围,let语句允许你创建块范围的局部变量。

    修改上面的代码片段,使用let语句声明变量:

    function foo() {

        var x = 9;

        if (x > 5) {

            let x = 7;

            console.log("Value of x in if statement = " + x);

        }

        console.log("Value of x outside if statement = " + x);

    }

    foo();

    在上面的代码段中,我们使用let语句来声明范围级局部变量x。因此,在if语句内更新变量x的值不会影响if语句外的变量x的值。

    下面是上述代码的输出:

    轻松学习 JavaScript

    与使用函数范围(或全局范围)声明的变量不同,使用let声明的变量是块范围的:它们只存在于它们定义的块中。

    变量提升

    使用let声明的变量提升不同于使用var声明的变量。因此,使用let声明的变量没有变量提升,这意味着使用let声明的变量不会移动到执行上下文的顶部。

    为了更好地理解这一点,请看以下这段代码:

    function foo() {

        console.log(x);

        console.log(y);

        var x = 9;

        let y = 67;

    }

    foo();

    作为输出,你将获得变量y的ReferenceError,变量y使用let语句声明。使用let声明的变量不会提升到执行上下文之上。

    轻松学习 JavaScript

    重新声明变量

    你不能在同一个函数或块中使用let重新声明一个变量。这样做会出现语法错误。请看以下代码:

    function foo() {

        if(true){

            let x = 9;

            let x = 89;

        }

    }

    foo();

    运行上面的代码会出来一个语法错误,如下所示:

    轻松学习 JavaScript

    暂时性死区

    有时,使用let声明的变量会导致暂时性死区。在以下代码中,let x=x+67 将抛出x未定义的异常。

    之所以会出现这个错误,是因为表达式(x + 67)求的是if块范围内局部变量x的值,而不是函数范围内局部变量x的值。运行上面的代码,你会得到这样一个异常:

    轻松学习 JavaScript

    你可以通过移动声明变量到表达式的上面一行来修复上述错误,如下所示:

    块级范围界定是任何编程语言最重要的功能之一,并且随着ECMAScript 6中let语句的引入,JavaScript现在也有了这个功能。使用let语句,允许创建一个作用域在块范围内的变量。这可以解决许多问题,例如全局范围变量的意外修改,闭包中的局部变量,以及帮助编写更清晰的代码。

    在“轻松学习JavaScript”系列的下一篇文章中,我们将介绍JavaScript函数中的rest参数。你可以在ECMA International网站上详细了解有关这方面的内容。此外,不要忘记查看Ignite UI,它可以与HTML5,Angular,React或ASP.NET MVC一起来帮助创建丰富的互联网应用程序。

    轻松学习 JavaScript (2):函数中的 Rest 参数

    JavaScript函数可以使用任意数量的参数。与其他语言(如C#和Java)不同,你可以在调用JavaScript函数时传递任意数量的参数。JavaScript函数允许未知数量的函数参数。在ECMAScript 6之前,JavaScript有一个变量来访问这些未知或可变数目的参数,这是一个类似数组的对象,并非一个数组。细想以下代码来理解arguments变量:

    function add(){

        var result = 0;

        for(let i=0;i<arguments.length;i++){

            result = result + arguments[i];

        }

        return result;

    }

    var r = add(6,9,3,2);

    console.log(r);

    var t = add(7,56,9);

    console.log(t);

    如你所见,arguments对象用于访问未知或可变的函数参数。即使arguments使用length属性和方括号,它也不是一个真正的JavaScript数组。你不能对arguments对象使用其他JavaScript数组方法,如pop,push,slice等。在使用arguments时存在的一些问题是:

  • JavaScript函数arguments对象不是一个真正的JavaScript数组;因此,你不能使用其他数组方法,如pop,push,slice等。

  • 在内部函数中访问外部函数的arguments对象是很困难的。要访问的话,你需要在变量中分配外部函数的arguments函数,然后在内部函数中使用它。

  • 如果你想要使用arguments对象作为数组,那么你需要通过Aarry.prototype.slice手动转换。

  • ECMAScript 6引入了一个新功能,Rest参数,它表示一个未知数量的参数作为函数中的一个数组。它不仅将额外的参数表示为数组,还解决了arguments对象的许多问题。使用rest参数重写上面的add函数。

    function add(...theArgs){

        var result = 0;

        for(let i=0;i<theArgs.length;i++){

            result = result + theArgs[i];

        }

        return result;

    }

    var r = add(6,9,3,2);

    console.log(r);

    var t = add(7,56,9);

    console.log(t);

    你可以将rest参数定义为…theArgs或… args。如果最后命名的函数参数以…(三个点)作为前缀,那么它将成为函数的rest参数。JavaScript函数的rest参数是纯JavaScript数组。在上面的代码中,…theArgs是函数add的rest参数,因为它是唯一的命名参数,且也以…(三个点)作为前缀。由于rest参数是JavaScript数组,所以你可以对rest参数theArgs执行诸如push,pop等操作,如下面的代码所示:

    function add(...theArgs){

        theArgs.push(10);

        var result = 0;

        for(let i=0;i<theArgs.length;i++){

            result = result + theArgs[i];

        }

        var lastItem  = theArgs.pop();

        console.log(lastItem);

        return result;

    }

    JavaScript函数的rest参数也可以与其他参数一起工作。如果你不想在rest参数数组中包含特定参数的话,那么你可能需要在函数中使用其他命名参数。细想以下代码块:

    function add(num1, num2, ...theArgs){

        console.log(num1);

        console.log(num2);

        console.log(theArgs.length);

    }

    var r = add(6,9,3,2);

    var t = add(7,56,9);

    对于第一次函数调用,6和9将分别分配给num1和num2。对于第二个函数调用,7和56将被分配给num1和num2。启动第三个参数的参数将被分配给rest参数数组。请记住,前两个参数不会成为rest参数数组的一部分。所以,如果你打算将所有的值都包含在rest参数中,那么你应该一开始就将它们定义为用逗号分隔的命名参数。下面给出的代码将会导致错误:

    function add(num1, ...theArgs,num2){

        console.log(num1);

        console.log(num2);

        console.log(theArgs.length);

    }

    在上面的代码中,rest参数不是最后一个参数,所以JavaScript会抛出错误。rest参数必须是最后一个正式参数。

    轻松学习 JavaScript

    JavaScript允许你破坏rest参数,这意味着你可以将rest变量数据解包为不同的变量名称。请看下面的代码:

    function add(...[a,b,c]){

        return a+b+c;

    }

    var r = add(6);

    console.log(r);

    var t = add(7,56,9);

    console.log(t);

    第一次函数调用,将分配a = 6,b = undefined,c = undefined,第二次函数调用,将分配a = 7,b = 56,c = 9。在此例子中,函数将忽略传递的任何额外的参数。

    JavaScript函数的rest参数是对arguments对象使用函数未知参数的一个巨大改进。它是一个纯JavaScript数组;因此,你可以对它使用所有数组方法。你可以将rest变量数据解包到命名变量中。你可以给rest参数指定任何名称,这又是一个使用arguments关键字的重大改进。

    轻松学习 JavaScript (3):函数中的默认参数

    JavaScript函数可以有默认参数值。通过默认函数参数,你可以初始化带有默认值的正式参数。如果不初始化具有某些值的参数,则该参数的默认值为undefined。

    请看下列代码:

    function foo(num1){

        console.log(num1);

    }

    foo();

    在调用函数foo时,你没有传递任何参数,因此变量num1的默认值设置为undefined。但是,有时你可能需要设置默认值而非undefined。过去,最好的策略是测试参数值undefined,然后分配一个值。所以,在上面的例子中,如果你想要将num1的默认值设置为9,那么你可以按照以下代码所示的方式做:

    function foo(num1) {

        if (num1 === undefined) {

            num1 = 9;

        }

        console.log(num1);

    }

    foo();

    ECMAScript 6引入了函数的默认参数。使用ECMA 2015的默认参数功能,你将不再需要检查未定义的参数值。现在,你可以将9设置为参数本身的默认值。你可以重写上述函数以使用默认值,如下所示:

    function foo(num1 =9) {

        console.log(num1);

    }

    foo();

    对于函数foo,如果num1参数的值未被传递,那么JavaScript将设置9作为num1的默认值。

    检查未定义的参数

    即使你在调用函数时明确地传递undefined作为参数值,参数值也将设置为默认值。

    function foo(num1 =9) {

        console.log(num1);

    }

    foo(undefined);

    在上面的代码中,你传递undefined为num1的值;因此,num1的值将被设置为默认值9。

    运行时计算默认值

    JavaScript函数默认值在运行时计算。为了更好地理解这一点,请看以下代码:

    function foo(value = koo()) {

        return value;

    }

    function koo() {

         return "Ignite UI";

    }

    var a = foo();

    console.log(a);

    在函数foo中,参数值的默认值设置为函数koo。在运行时调用函数foo时,将计算函数koo。调用foo函数后,你会得到如下图所示的输出(在这个例子中,我们使用了Ignite UI框架)。

     轻松学习 JavaScript

    重用默认参数

    默认参数可供之后的默认参数使用。请看下列代码:

    function foo(num1 = 9, num2 = num1 + 8){

        console.log(num2);

    }

    foo();

    在上面的代码中,使用num1的默认值来计算num2的默认值。调用函数foo时将得到以下输出:

    轻松学习 JavaScript

    结论

    JavaScript默认参数在编写函数时非常有用。在调用函数时,如果缺少参数,则默认参数功能允许你为函数参数分配默认值,而不是将其定义为undefined。

    轻松学习 JavaScript (4):函数中的 arguments 对象

    JavaScript函数具有像数组一样的对象,这些对象称为arguments,与传递给函数的参数相对应。传递给JavaScript函数的所有参数都可以使用arguments对象来引用。

    现在我们开始学习,仔细看下面列出的代码:

    function add(num1, num2) {

        var res = num1 + num2;

        return res;

    }

    var r = add(7, 8);

    console.log(r);

    在上面的函数中,num1和num2是两个参数。你可以使用名为num1和num2的arguments来引用这些参数。除了arguments名称之外,你还可以使用JavaScript数组,如对象arguments来引用它们。所以,上面的函数可以重写,如下所示:

    function add(num1, num2) {

        var res = arguments[0] + arguments[1];

        return res;

    }

    var r = add(7, 8);

    console.log(r);

    在JavaScript函数中,arguments对象用于访问或引用传递给函数的所有参数。arguments对象是可用于函数的局部变量。arguments对象的长度相当于传递给函数的arguments数量。请看下面的代码,作为输出将得到2,因为有两个arguments传递给函数:

    function add(num1, num2) {

        var res = arguments.length;

        return res;

    }

    var r = add(7, 8);

    console.log(r);

    arguments对象不是纯数组

    JavaScript的arguments对象不是纯粹的JavaScript数组。你不能对arguments对象执行诸如push,pop,slice等操作。正如你将在下面列出的代码中所看到的那样,执行push操作会引发异常,因为arguments.push不是函数。

    function add(num1, num2) {

        arguments.push(78);

        var res = num1 + num2;

        return res;

    }

    可以设置arguments对象

    你可以在arguments对象数组中设置特定的项。首先,你可以使用索引0设置数组的第一个项,如下所示:

    function add(num1, num2) {

        arguments[0] = 15;

        var res = num1 + num2;

        return res;

    }

    var r = add(7, 8);

    console.log(r);

    在add函数中,num1和arguments[0]引用相同的值。所以,当你更新arguments[0]时,num1的值也会被更新。对于上面的代码,输出将是23。

    将arguments对象转换为数组

    正如我们在这篇文章中介绍的那样,JavaScript函数arguments对象不是纯数组。除了长度属性外,它没有任何其他属性。但是,你可以使用Array.prototype.slice.call将arguments对象转换为数组,如下所示:

    function add(num1, num2) {

        var arg = Array.prototype.slice.call(arguments);

        console.log(arg.pop());

    }

    在ECMAScript 6中,你可以将arguments对象转换为一个数组,如下所示:

    function add(num1, num2) {

        var arg = Array.from(arguments);

        console.log(arg.pop());

    }

    结论

    总而言之,关于arguments对象需要谨记的一些重要事情有:

  • arguments对象的长度等于传递给函数的参数的数量。

  • arguments对象是类似数组的对象,但不是JavaScript数组。

  • 你不能对arguments对象使用其他JavaScript数组方法,例如push,pop,slice等等。

  • JavaScript arguments对象索引从零开始。所以第一个参数将被arguments[0]引用,第二个参数将被arguments[1]引用,等等。

  • 简单地说,JavaScript arguments对象是一个类似数组的对象,它引用传递给函数的参数。在ECMAScript 6中,引入的rest参数现已被广泛用来替代函数中的arguments对象用于变量数或参数。

    轻松学习 JavaScript(5):简化函数提升

    为了理解函数提升,让我们从以下代码开启我们的学习之旅:

    console.log(foo);

    var foo = 9;

    应该输出什么呢?

    在任何其他编程语言中,这里的输出将会是reference error。但是,在JavaScript中,你将得到undefined作为输出。为什么?因为JavaScript会提升变量到执行上下文的顶部。执行上下文可以是声明变量的函数,也可以是声明变量的JavaScript文件。所以,让我们用函数重写上面的代码片段:

    function abc() {

        console.log(foo);

        var foo = 9;

    }

    abc();

    这里,变量“foo”提升到函数abc执行上下文的顶部;这意味着你可以在声明之前访问foo。简而言之,无论何时你声明一个变量,JavaScript解释器都可以将其分成两个语句:

  • 声明一个变量。

  • 赋值。

  • 变量的声明位于执行上下文的顶部,而赋值发生在创建变量的位置。所以上面的代码片段被分解成两个语句,如下图所示:Hoisting-1

     轻松学习 JavaScript

    变量foo被提升到函数abc的执行上下文的顶部,因此当你在声明之前使用它时,你会得到“undefined”作为输出。

    请记住,使用let语句声明的变量不会被提升到执行上下文的顶部。

    现在你知道JavaScript中的变量是如何被提升的了,接下来让我们来探讨JavaScript中的函数提升。在JavaScript中,可以通过两种方式来创建函数:

  • 作为声明而创建的函数。

  • 作为表达式而创建的函数。

  • 作为声明或语句创建的函数作为一个整体提升到执行上下文的顶部。但是,作为表达式创建的函数会像变量一样提升。

    为了说明这一点,让我们创建一个作为语句的函数:

    foo();

    function foo() {

        console.log("hello");

    }

    在上面的代码中,如果你在函数创建之前使用函数,那么你会得到hello的输出。发生这种情况的原因是,作为语句创建的函数会当作一个整体被提升到执行上下文的顶部。

    无论何时创建作为语句的函数,都可以在函数创建之前使用该函数。因此,如果你在第5行创建作为语句的函数,那么你可以在第1-4行中使用该函数,因为函数语句会随函数主体一起提升到执行上下文的顶部。

    函数语句会随函数主体一起提升到执行上下文的顶部。

    函数表达式会像一个变量一样被提升到执行上下文的顶部。请看下面的代码:

    foo();

    var foo = function () {

        console.log("hello");

    }

    你正在代码中创建函数foo作为表达式,所以JavaScript会像普通变量一样提升它。 JavaScript会像下图所示那样处理上面的代码:

    轻松学习 JavaScript

    正如你在上面的图片中看到的那样,foo在执行上下文的顶部被声明为一个变量,然而,在变量foo中的函数赋值发生在第6行,也就是创建作为表达式函数的地方。所以,当你尝试执行上面的代码时,你会得到错误undefined is not a function,如下图所示:

    轻松学习 JavaScript

    因此,你不能在函数表达式被创建之前使用函数表达式,因为只有函数声明会提升到顶部。

    综上所述:

  • 函数语句随函数主体一起被提升到执行上下文的顶部。你可以在函数创建之前使用作为语句创建的函数。

  • 函数表达式在创建之前不能使用。只有声明部分会被提升,赋值发生在创建函数的那一行。

  • 在“轻松学习JavaScript”系列的下一篇文章中,我们将介绍JavaScript中更为重要的概念,敬请期待。

    ●本文编号2769,以后想阅读这篇文章直接输入2769即可

    ●输入m获取文章目录

    推荐↓↓↓

    轻松学习 JavaScript

    前端开发

    更多推荐18个技术类微信公众号

    涵盖:程序人生、算法与数据结构、黑客技术与网络安全、大数据技术、前端开发、Java、Python、Web开发、安卓开发、iOS开发、C/C++、.NET、Linux、数据库、运维等。