zoukankan      html  css  js  c++  java
  • JavaScript 作用域和闭包——另一个角度:扩展你对作用域和闭包的认识【翻译+整理】

    原文地址

    ——这篇文章有点意思,可以扩展你对作用域和闭包的认识。

    本文内容

    • 背景
    • 作用域
    • 闭包
      • 臭名昭著的循环问题
      • 自调用函数(匿名函数)
    • 其他

    我认为,尝试向别人解释 JavaScript 作用域和闭包是很纠结的事情。

    背景


    有很多文章和博客都在试图解释的作用域(scope)和关闭(closure),但总体来说,我认为大多数都不是很清楚。此外,一些人想当然地认为,之前,每个人都已经大概用15种其他语言开发,而我的经验是,很多这样编写 JavaScript 代码的人都具有 HTML 和 CSS 背景,而不是 C 和 Java。

    因此,本文的目标是,让每个人掌握什么是作用域和闭包,他们如何工作,特别是你如何可以从中受益。在阅读本文前,你需要理解变量和函数这些基本概念。

     

    作用域(Scope)


    作用域是指变量和函数可访问性在哪里,以及执行环境是什么。最基本的是,一个变量或函数可以定义在全局或局部作用域。变量具有所谓的函数作用域,函数跟变量具有相同的作用域。

    全局(Global)作用域

    当是全局的时候,意味着,在你的代码中,可以从任何地方访问。如下代码所示:

            var monkey = "Gorilla";
     
            function greetVisitor() {
                return alert("Hello dear blog reader!");
            }

    如果该代码执行在 Web 浏览器中,那么,函数的作用域是 window,因此,对运行在 Web 浏览器窗口中所有东西,该函数都是可用的。

    局部(Local)作用域

    与全局作用域相反,局部作用域只是在你某部分的代码中定义并访问,如一个函数。代码如下所示:

            function talkDirty() {
                var saying = "Oh, you little VB lover, you";
                return alert(saying);
            }
            alert(saying); // Throws an error

    上面代码,变量 saying 只在 talkDirty 函数内可用。而在外边,更本没有定义。注意:如果声明 saying 前边没有 var 关键字,那么该变量将自动变成一个全局变量。

    这也意味着,如果你有嵌套(nested)函数,那么,这个嵌套函数将可以访问包含其函数内的变量和函数:

            function saveName(firstName) {
                function capitalizeName() {
                    return firstName.toUpperCase();
                }
                var capitalized = capitalizeName();
                return capitalized;
            }
            alert(saveName("Robert")); // Returns "ROBERT"

    正如上面看到的,嵌套函数 capitalizeName 不需要任何参数传递,就可以完全访问它外边的 saveName 函数的参数 firstName。清楚起见,让我们看下面的例子:

            function siblings() {
                var siblings = ["John", "Liza", "Peter"];
                function siblingCount() {
                    var siblingsLength = siblings.length;
                    return siblingsLength;
                }
                function joinSiblingNames() {
                    return "I have " + siblingCount() + " siblings:
    
    " + siblings.join("
    ");
                }
                return joinSiblingNames();
            }
            alert(siblings()); // Outputs "I have 3 siblings: John Liza Peter"

    上面代码,那两个嵌套函数都可以访问包含其函数内的 siblings 数组,并且,每个嵌套函数都可以同等地访问另一个嵌套函数(在这种情况下,joinSiblingNames 可以访问 siblingCount)。但是, siblingCount 函数中的变量 siblingsLength 只在它所在的函数可用,也就是作用域。

     

    闭包(Closures)


    现在,当你希望获得更好地掌握作用域是什么时,让我们向组合添加闭包。闭包是表达式,通常是函数,再一个某个上下文环境内进行变量设置。或者,为了尝试并使其更容易,涉及局部变量的内部函数创建闭包。例如:

            function add(x) {
                return function (y) {
                    return x + y;
                };
            }
            var add5 = add(5);
            var no8 = add5(3);
            alert(no8); // Returns 8

    发生了什么?

    1. add 函数被调用时,它返回了一个函数。
    2. 这个返回的函数关闭上下文环境,记住参数 x 在那个时候是什么(例如,上面代码的 5)。
    3. 当调用 add 函数的返回结果被分配给变量 add5 时,add5 函数总是知道 x 是什么,当 add5 被初始化创建时。
    4. add5 变量引用一个函数,它总是把传给它的参数加 5
    5. 这意味着,当用 3 调用 add5 时,它应该返回 3 加 5,等于 8。

    因此,在 JavaScript 世界里,事实上,add5 函数实际像如下所示:

            function add5(y) {
                return 5 + y;
            }

    臭名昭著的循环问题

    有多少次你创建一个循环,想以某种方式分配 i 的值,例如:给一个元素,有没有发现它只是返回的最后一个 i 的值?

    • 错误的引用

    让我们看下错误的代码,它创建五个链接元素,每个元素的文本值为 i,并且每个元素的单击事件为弹出内容为 i 的 alert。把这五个元素追加到 document body:

            function addLinks() {
                for (var i = 0, link; i < 5; i++) {
                    link = document.createElement("a");
                    link.innerHTML = "Link " + i;
                    link.onclick = function () {
                        alert(i);
                    };
                    document.body.appendChild(link);
                }
            }
            window.onload = addLinks;

    每个元素都获得了正确的文本,例如“Link 0”、“Link 1”等等,但是你单击的每个链接,alert 中的值都是 5,这显然不对,为什么?原因在于,变量 i 以 1 递增,可 onclick 事件没有被执行,仅仅是应用到了元素,i 再增加 1。

    因此,循环继续直到等于 5,这是 i 的最后一个值,此时,函数 addLinks 退出。之后,当 onclick 事件实际被触发时,只得到 i 的最后一个值。

    • 正确的引用

    你所需要做的就是创建一个闭包,这样,当你把 i 值应用到元素的 onclick 事件时,就会在触发那个时刻得到 i 的准确值。如下所示:

            function addLinks() {
                for (var i = 0, link; i < 5; i++) {
                    link = document.createElement("a");
                    link.innerHTML = "Link " + i;
                    link.onclick = function (num) {
                        return function () {
                            alert(num);
                        };
                    } (i);
                    document.body.appendChild(link);
                }
            }
            window.onload = addLinks;

    上面代码就没有问题了。单击第一个创建的链接元素,alter 为 0;第二个为 1 等等。该解决方案是,应用到 onclick 事件的内部函数创建了一个闭包,在那里引用参数 num,也就是 i 的值。

    自调用函数(Self-Invoking Functions)

    自调用函数是那些自动执行的函数(匿名函数),并且创建它们自己的闭包,如下代码所示:

            (function () {
                var dog = "German Shepherd";
                alert(dog);
            })();
            alert(dog); // Returns undefined

    dog 变量只在其作用域内可用。它可以解决我们上面的循环问题,而且这也是 Yahoo JavaScript Module Pattern 的基础。

    Yahoo JavaScript Module Pattern

    Yahoo JavaScript 模块模式的要点是,它使用了一个自调用函数来创建一个封闭,因此,使具有私有和公共的属性和方法成为可能。一个简单的例子,如下所示:

            var person = function () {
                // Private
                var name = "Robert";
                return {
                    getName: function () {
                        return name;
                    },
                    setName: function (newName) {
                        name = newName;
                    }
                };
            } ();
            alert(person.name); // Undefined
            alert(person.getName()); // "Robert"
            person.setName("Robert Nyman");
            alert(person.getName()); // "Robert Nyman"

    这个代码就很优雅了。现在,你可以自己决定公开什么,隐藏什么了。变量 name 就是对外部隐藏的(在外部引用为 undefined),但可以通过 getNamesetName 函数访问,因为,它们创建了闭包,在里边引用了 name 变量。

     

    其他


     

    下载 Demo

  • 相关阅读:
    清除图片周围的空白区域
    试题识别与生成
    需要继续研究
    工作中的必要举措
    教学云平台要求的硬件配置
    处理程序安装部署标准流程
    Node.js 回调函数
    git 学习
    在 Selenium 中让 PhantomJS 执行它的 API
    RF常用库简介(robotframework)
  • 原文地址:https://www.cnblogs.com/liuning8023/p/3357975.html
Copyright © 2011-2022 走看看