• 《jquery实战》javascript 必知必会(2)


    A2 一等公民函数

    在传统 OO 语言里,对象包含数据和方法。这些语言里,数据和方法通常是不同的概念:javascript另辟蹊径。

    与其他 js 的类型一样,函数可以作为对象处理,如String、Number、Date等。
    与其他对象类似,函数可以通过javascript函数定义 —— 此时函数可以

    • 赋值给变量;
    • 赋值给对象属性;
    • 作为参数传递;
    • 作为函数结果返回;
    • 使用文本创建。

    因为这个语言里,函数与其他对象的处理方式类似,所以我们说函数是一等对象(first-class object)。
    在 js 里,函数可以做不同的事,也可以用不同的方式定义。

    A2.1 函数表达式与函数声明

    函数不仅可以调用值,而且会证明这是正确的。其中一种定义方式就是 函数声明(function declaration)。思考下面代码:

    function doSomethingWonderful(){
    	alert('Does something wonderful');
    }
    

    函数声明由关键字 function 组成,接着是函数的名字,还有参数或者无参及函数体。

    正确的代码里,定义函数名字是 doSomethingWonderful,这是无参的函数。当调用时,就会执行函数体,这里只调用了 alert() 弹出消息。看起来没有返回值,但是在 js 里,如果没前面提到的顶级定义的变量会作为 window 对象的属性。 Function 对象也不例外。如果之前函数声明在顶级层次,那么可以创建与函数名同名的window属性。因此,下面的语句是等价的

    function hello() { alert('Hi there!'); }
    hello = function hello() { alert('Hi there!'); }
    window.hello = function hello() { alert('Hi there!'); }
    

    在支持 ECMAScript 6 规范的浏览器里,可以通过名为 name 的函数属性名访问函数。
    在 js 里,函数可以作为代码的一部分定义,而且被称为函数表达式(function expression)。函数表达式的值可以作为函数对象。

    var myFunc = function(){
    	alert('this is a function');
    }
    

    定义了一个变量myFunc,赋值了一个函数。因为这是一条代码,注意这个函数没有名字(name 属性是空字符串),所以不能使用函数名调用它。但是,因为已经赋值给一个变量,所以可以按下面方法执行:

    myFunc();
    

    这并非函数声明与函数表达式的唯一区别。另外一个重要的却别:函数声明是升起的(hoisted)(有的地方叫函数声明的声明提前),而函数表达式不是。为了实际理解这个概念,思考下面的例子:
    函数声明与函数表达式

    例子中定义两个函数 funcDecl()funcExpr()。但是实际定义之前,我们执行了调用。首先调用(funcDecl();)成功,然后调用(funcExpr();)抛出错误。不同的行为是因为 funcDecl() 是升起的,而 funcExpr() 不是。

    var 声明提前:

    变量在声明它们的脚本或函数中都是有定义的,变量声明语句会被提前到脚本或函数的顶部。但是,变量初始化的操作还是在原来var语句的位置执行,在声明语句之前变量的值是undefined。
    var 声明提前
    下部分是实际执行的过程。

    函数声明提前

    函数声明是在预执行期执行的,就是说函数声明是在浏览器准备执行代码的时候执行的。因为函数声明在预执行期被执行,所以到了执行期,函数声明就不再执行了(人家都执行过了自然就不再执行了)。
    函数声明和函数表达式 - myvin
     

    可以采用同样的方式给变量赋值函数表达式,也可以作为属性赋值给对象:

    var myObj = {
            bar: function() {}
        };
    

    A2.2 回调函数

    处理事件或计时器,或执行 AJAX 请求时,WEB 页面代码的本性是异步的。其中异步编程最流行的概念就是 回调函数

    下面看计时器例子。可以调用计时器来触发 —— 假设5秒钟,通过传递适当的间隔时间给 window.setTimeout() 方法。但是这个方法怎么让等待时间结束后执行想要的方法?也是通过调用设置的函数实现的。

    function hello(){alert('Hi there!');}
    setTimeout(hello, 5000);
    

    第一个参数设置给 setTimeout() 方法,是函数的引用。传递函数作为参数与传递其他值没有区别,就好像传递一个数一样。

    当计时器结束时,调用 hello 函数。因为 setTimeout() 方法在代码里发起一个回调,所以函数被称为 回调函数(callback function)

    因为只使用一次函数,没必要创建函数名 hello。除非在其他地方多次调用函数,否则没必要创建 window 属性来存储 hello 函数并把它传递给回调参数。更加优美的代码:

    setTimeout(function() {alert('Hi there!');}, 5000);
    

    这里在参数列表直接定义(实际是内联匿名函数),无须生成函数名字。可在 jQuery 中经常看到这种用法,没必要赋值给顶级属性。

    在这个例子里创建的函数或者顶级函数(作为 window 属性),或者赋值给函数调用。也可以复制 Function 对象 给对象的属性。

    A2.3 寻根求源

      OO 语言会自动一共一种方式来引用当前方法内对象的实例。在JAVA和C#这种语言中,this 变量指向当前实例的引用。在JS中,类似的概念存在,也使用this关键字,还可以访问与函数关联的对象。但是JS实现的 this 与OO语言的不同。

    • 在OO语言中,this 通常引用的是声明方法类的实例。
    • 在JS中,函数是一等对象,不会作为其他东西的一部分,对于通过 this 引用 —— 称为函数上下文(function context) —— 不是由函数声明来决定而是由函数调用(invoked)来确定的。

    这意味着相同的函数可以有不同的上下文依赖,取决于如何调用它。这一点非常诡异,但是非常有用。
      默认情况下,函数调用的上下文(this)属性包含了对于调用函数引用的对象。我们来看看摩托车代码的例子,修改对象的创建代码如下:

        var ride = {
            make: 'Yamaha',
            model: 'XT660R',
            year: 2014,
            purchased: new Date(2015, 7, 21),
            owner: {
                name: 'Tg',
                occupation: 'bounty hunter'
            },
            whatAmI: function() {
                return this.year + ' ' + this.make + ' ' + this.model;
            }
        };
    

    新增了代码:

    whatAmI: function() {
        return this.year + ' ' + this.make + ' ' + this.model;
    }
    

    新代码添加了一个名为 whatAmI 的属性,它引用了一个函数 Function 实例。新的对象层次,使用 Function 实例赋值给了一个名为 whatAmI 的属性。

    A.2 模型展示了函数不是对象的一部分,但是可以通过对象的属性 whatAmI 访问引用

    当函数通过这个属性引用调用时,代码如下:

    var bike = ride.whatAmI();
    

    函数上下文(this)设置为 ride 引用的对象实例。结果变量 bike 设置为 '2014 Ymaha XT660R',因为函数用过 this 获得了对象的属性。

    对于顶级函数也一样。记住顶级函数是 window 对象的属性,所以调用函数上下文是 window 对象。

    虽然是通常和隐含的行为,但是JS给了我们现实控制函数上下文的机会。可以通过 Function 方法 call() 或者 apply() 来设置调用函数的上下文。虽然看起有点疯狂,但是作为一等对象,函数有通过 Function 构造函数定义的方法。

    call() 方法调用函数指定第一个对象参数作为上下文,而剩余的参数作为调用函数使用 —— call() 的第二个参数变成调用函数的第一个参数,依次类推。apply() 方法与此方法的工作方式类似,除了第二个参数是数组参数用来调用函数使用。

    call() 和 apply()

    在 JavaScript 中, 函数是对象。JavaScript 函数有它的属性和方法。
    call() 和 apply() 是预定义的函数方法。 两个方法可用于调用函数,两个方法的第一个参数必须是对象本身。
    两者的区别在于第二个参数: apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入,而call则作为call的参数传入(从第二个参数开始)。
    在 JavaScript 严格模式(strict mode)下, 在调用函数时第一个参数会成为 this 的值, 即使该参数不是一个对象。
    在 JavaScript 非严格模式(non-strict mode)下, 如果第一个参数的值是 null 或 undefined, 它将使用全局对象替代。

    • 通过 call() 或 apply() 方法你可以设置 this 的值, 且作为已存在对象的新方法调用。

    为了强化概念,我们看一个例子。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Function Conctext Example</title>
    </head>
    <body>
        <script type="text/javascript">
        var obj1 = { handle: 'obj1' };
        var obj2 = { handle: 'obj2' };
        var obj3 = { handle: 'obj3' };
        var value = 'test';
        window.handle = 'window';
    
        function whoAmI(param) {
            return this.handle + '' + param;
        }
    
        obj1.identifyMe = whoAmI;
    
        console.log(whoAmI(value)); //windowtest
        console.log(obj1.identifyMe(value));//obj1test
        console.log(whoAmI.call(obj2, value));//obj2test
        console.log(whoAmI.apply(obj3, [value]));//obj3test
        </script>
    </body>
    </html>
    

    列表A.1 函数上下文的值取决于函数调用

      代码里的定义了三个对象,每个对象使用 handel 属性来区分对象的引用①。同样也为 handel 实例添加了属性,因此它易于辨认。

      然后定义了一个顶级函数,它可以返回任意作为任意函数上下文对象的 handel 属性的值②,并把同一个函数赋值给 obj1identifyMe 属性③。可以说在 obj1 上创建了一个名为 identifyMe 的方法,虽然函数是和对象独立声明的。

      当 obj 作为函数 func 调用的上下文时,函数 func 作为对象 obj 的方法。为了更进一步演示这个概念,思考下面的代码:

    console.log(obj1.identifyMe.call(obj3));  //obj3undefined
    

    虽然作为 obj1 的属性引用了函数,但这时调用函数上下文是 obj3,也进一步强调了函数声明无法决定上下文而是取决于如何调用函数。

    A2.4 闭包

    闭包(closure)指的是 Function 实例,与其执行需要的局部变量耦合在一起。当声明函数时,它可以引用自己范围内的任意变量。但是对于闭包,这些变量被函数携带,甚至在声明点之后,已经超出了范围,关闭声明。

    回调函数在声明的时候引用局部变量是一个编写高效 javascript 代码的必备工具。使用计时器我们来看一个例子,列表2。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Closure Example</title>
    </head>
    <body>
        <div id="display"></div>
        <script type="text/javascript" src="jquery.js"></script>
        <script type="text/javascript">
        function timer() {
            var local = 1;
            window.setInterval(
                function() {
                    $('#display').append(
                        '<div>At ' + new Date() + ' local=' + local + '</div>'
                    );
                    local++;
                },
                2000);
        }
        timer();
        </script>
    </body>
    </html>
    

    列表A.2 闭包允许访问函数声明的范围

    这个例子中,我们创建了一个函数 timer(),在定义之后执行⑤。在 timer() 函数内声明了局部变量 local②,并且赋值为1,然后使用 window.setInterval() 方法来建立计时器,每隔2秒触发一次③。作为计时器的回调函数,我们指定了一个内敛函数来引用局部变量 local,通过向页面里名为 display 的元素附加 div 来展示当前时间和 local 变量的值①。作为回调函数的一部分,local 变量的值每次递增1④。

    如果不了解闭包,也许会认为,因为回调函数会在 timer() 函数调用后2秒触发,local 变量的值在执行回调期间是未定义的。但是,加载页面并运行一小段时间,会看到如图A.4所示结果。

    A.4 闭包允许回调函数访问自己的环境,虽然环境已经超出了范围
    虽然当 ready 处理器已经退出,local 变量已经超出了范围,函数声明的闭包,包含 local,仍然存在于函数的生命周期范围内。

    闭包的另一特性,函数上下文从来不会作为闭包的一部分。例如,下面的代码不会按照我们的预期执行。

    ···js
    this.id = 'someID';
    $('*').each(function(){
      alert(this.id);
    });
    

    记住每个函数调用都有自己的函数上下文,所以,这个代码的回调函数内存底给 each() 函数上下文是 jQuery 集合中的元素(DOM元素),不是外部函数设置的 someID 属性。每次调用回调函数都会轮流显示 jQuery 集合中的每个元素的 ID。

    当访问作为函数上下文的对象时,可以在局部变量里使用常见的版本来创建 this 引用的拷贝,这个局部变量将会包括在闭包里。思考下面的代码:

        <div id="display"></div>
        <script type="text/javascript" src="jquery.js"></script>
        <script type="text/javascript">
        this.id = 'someID';
        //var outer = this;                 //this 引用 window
        $('#display').each(function() {
            var outer = this;               // this 引用 display
            console.log(outer.id);
        });
        </script>
    

    变量(绝大部分时间命名为 that),变成了闭包的一部分,因为它已经在回调函数内部被引用了,因此可以被访问。outer 赋值给任意上下文,而不是回调函数定义的上下文。例如,前面的代码包括在名为 foo 的函数内,outer 变量会引用 foo 函数的上下文。如果前面的代码定义在 HTML 页面里,而没有被函数包裹,则 outer 变量将会引用 window 对象。

    修改后的代码现在显示警告框来展示字符串 someID 任意多次,只要 jQuery 集合中元素就行。

    我们发现闭包在使用 jQuery 异步回调来创建优美代码时非常重要,尤其是在编写 Ajax 请求和事件处理代码时。

  • 相关阅读:
    hdu-2841 Visible Trees---容斥定理
    hdu-4135 Co-prime---容斥定理经典&&求1-m中与n互质的数目
    hdu-1796 How many integers can you find---容斥定理
    hdu-2837 Calculation---指数循环节
    FZU-1759 Super A^B mod C---欧拉降幂&指数循环节
    指数循环节&欧拉降幂
    hdu-3074 Multiply game---线段树+单点更新
    hdu-1792 A New Change Problem---数论&剩余系
    POJ-2429 GCD & LCM Inverse---给出gcd和lcm求原来两个数
    hdu-2685 I won't tell you this is about number theory---gcd和快速幂的性质
  • 原文地址:https://www.cnblogs.com/tangge/p/6765305.html
Copyright © 2020-2023  润新知