zoukankan      html  css  js  c++  java
  • 函数调用_强制指定回调函数的函数上下文

    <!DOCTYPE html>
    <html lang="en">
    <head>  
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>使用forEach迭代方法展示如何设置函数上下文</title>
        <script src="../unitl/test.js"></script>
        <style>
            #results li.pass {color:green;}
            #results li.fail {color:red;}
        </style>
    </head>
    <body>
        <ul id="results"></ul>
    </body>
    <script>
    
       // forEach函数接受两个参数,要遍历的集合和回调函数
        function forEach(list,callback) {
    
            for(var n=0; n<list.length; n++) {
    
                //当前遍历到的元素作为函数的上下文调用回调函数
                callback.call(list[n],n);
    
            }
    
        }
    
        //测试数组
        var weapons = [{type:'shuriken'},{type:'katana'},{type:'nunchucks'}];
    
        //调用迭代函数forEach。确保每个元素的上下文正确。
        forEach(weapons,function(index){
    
            assert(this===weapons[index],"Got the expected value of " + weapons[index].type);
    
        });
    
    
    </script>
    </html>
        
    

    迭代函数接受需要遍历的目标对象数组作为第一个参数,回调函数作为第二个参数。迭代函数遍历数组,对每个数组元素执行回调函数:

            function forEach(list,callback) {
    
            for(var n=0; n<list.length; n++) {
    
                callback.call(list[n],n);
    
            }
    
        }
    

    使用call方法调用回调函数,将当前遍历到的元素作为第一个参数,循环索引作为第二个参数,使得当前元素作为函数上下文,循环所引作为回调函数的参数。
    执行测试时,设置一个简单的数组weapons,然后调用forEach函数,传入数组及回调函数:

          //调用迭代函数forEach。确保每个元素的上下文正确。
        forEach(weapons,function(index){
    
            assert(this===weapons[index],"Got the expected value of " + weapons[index].type);
    
        });
    

    从下图可以看到,函数运行得很完美

    apply和call的功能类似,但问题是在二者如何选中?答案与许多其他问题的答案是相似的:选择任意可以精简代码的方法。更实际的答案是选择与现有参数相匹配的方法。如果有一组无关的值,则直接使用call方法。若已有参数是数组类型,apply方法是
    更加选择。

    解决函数上下文的问题

    箭头函数作为回调函数还有一个更优秀的特性:箭头函数没有单独的this值。箭头函数的this和声明所在的上下文的相同。

    <!DOCTYPE html>
    <html lang="en">
    <head>  
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>使用forEach迭代方法展示如何设置函数上下文</title>
        <script src="../unitl/test.js"></script>
        <style>
            #results li.pass {color:green;}
            #results li.fail {color:red;}
        </style>
    </head>
    <body>
        <button id="test">Click me </button>
        <ul id="results"></ul>
    </body>
    <script>
    
    
        // Button构造函数用于创建保存按钮的状态的对象
        function Button () {
            this.clicked = false;
            // 声明用于处于点击事件的箭头函数。因为click是对象的方法。我们在函数内部使用this获得对象的引用
            this.click = ()=>{
    
                this.clicked = true;
                //在函数内,验证点击后按钮的状态发生改变
                assert(button.clicked,"The button has been clicked");
            }
    
        }
    
        var button = new Button();
        var elem = document.getElementById("test");
        //在按钮上监听点击事件
        elem.addEventListener("click",button.click);
    
    
    </script>
    </html>       
    

    代码运行后的结果如下图所示。

    可以看出一切正常。按钮对象保持单击状态,在button构造函数内部使用箭头函数创建时间的处理方法如下:

          function Button () {
            this.clicked = false;
            // 声明用于处于点击事件的箭头函数。因为click是对象的方法。我们在函数内部使用this获得对象的引用
            this.click = ()=>{
    
                this.clicked = true;
                //在函数内,验证点击后按钮的状态发生改变
                assert(button.clicked,"The button has been clicked");
            }
    
        }
    

    调用箭头函数时,不会隐式传入this参数,而是从定义时的函数继承上下文。在本例中,箭头函数在构造函数内部,this指向新创建的对象本身,因此无论何时调用click函数,this都将指向新创建的button对象。

    警告:箭头函数和对象字面量

    由于this值在箭头函数创建时确定的,所以会导致一些看似奇怪的行为。回到按钮单击示例中,因为只是一个按钮,因此可以假设不需要构造函数。直接使用对象字面量。如下面例子所示。

          <!DOCTYPE html>
    <html lang="en">
    <head>  
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>使用forEach迭代方法展示如何设置函数上下文</title>
        <script src="../unitl/test.js"></script>
        <style>
            #results li.pass {color:green;}
            #results li.fail {color:red;}
        </style>
    </head>
    <body>
        <button id="test">Click me </button>
        <ul id="results"></ul>
    </body>
    <script>
    
        //全局代码中的this指向全局window对象
        assert(this===window,"this===window");
    
    
    
        //使用对象字面量定义button
        var button = {
            clicked:false,
            //箭头函数是对象字面量的属性。
            click:()=>{
                this.clicked = true,
                //验证是否单击按钮
                assert(button.clicked,"The button has been clicked");
                //箭头函数中的this指向全局window对象
                assert(this===window,"In arrow function this === window");
                //Clicked属性存储在window对象上
                assert(window.clicked,"clicked is stored in window");
            }
        }
    
        var elem = document.getElementById("test");
        elem.addEventListener("click",button.click);
    
    </script>
    </html>
        
    

    运行程序我们将会感到失望,因为button对象无法跟踪clicked状态。如下图所示

    在代码中定义一些断言会有帮助。例如,在全局代码中编写如下代码确认this的值。

          assert(this===window,"this===window");
    

    断言通过,因此可以确定全局代码中的this指向全局window对象。

          //使用对象字面量定义button
        var button = {
            clicked:false,
            //箭头函数是对象字面量的属性。
            click:()=>{
                this.clicked = true,
                //验证是否单击按钮
                assert(button.clicked,"The button has been clicked");
                //箭头函数中的this指向全局window对象
                assert(this===window,"In arrow function this === window");
                //Clicked属性存储在window对象上
                assert(window.clicked,"clicked is stored in window");
            }
        }
    

    回顾一下规则,箭头函数在创建时确定了this的指向。由于click的函数是作为对象字面量定义的,对象字面量在全局代码中定义,因此,箭头函数内部this值与全局代码的this值相同。代码清单中中第一句断言:

          assert(this===window,"this===window")
    

    可以看出全局代码的this指向全局window对象。因此,clicked属性被定义在另外window对象上,而不再button对象上。最后断言可以确定clicked属性赋值在window对象上:

          assert(window.clicked,"Clicked is stored in window");
    

    如果忘记箭头函数的副作用可能会导致一些bug,需要特别小心!
    已经看到箭头函数可以规避函数上下文的问题,继续看另一种解决方案。

    使用bind方法

    函数还可访问bind方法创建新函数。无论使用哪种方法调用,bind方法创建的新函数与原始函数的函数体相同,新函数被绑定到指定的对象上。再次回顾按钮单击事件处理的问题。

    <!DOCTYPE html>
    <html lang="en">
    <head>  
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>使用forEach迭代方法展示如何设置函数上下文</title>
        <script src="../unitl/test.js"></script>
        <style>
            #results li.pass {color:green;}
            #results li.fail {color:red;}
        </style>
    </head>
    <body>
        <button id="test">Click me </button>
        <ul id="results"></ul>
    </body>
    <script>
    
    
    
    
        //使用对象字面量定义button
        var button = {
            clicked:false,
            //箭头函数是对象字面量的属性。
            click:function() {
                this.clicked = true;
                assert(button.clicked,"The button has been clicked");
            }
        }
    
        var elem = document.getElementById("test");
    
        //使用bind函数创建新函数
        elem.addEventListener("click",button.click.bind(button));
    
    
        var boundFunction = button.click.bind(button);
        assert(boundFunction != button.click,"Calling bind create s a completly new Function");
    
    </script>
    </html>
          
    

    所有函数均可访问bind方法,可以创建并返回一个新函数,并绑定在传入的对象上(在本例中,绑定在button对象上)。不管如何调用该函数,this均被设置为对象本身。
    被绑定的函数与原始函数行为一致,函数体一致。
    无论何时单击按钮,都将调用绑定的函数,函数的上下文是button对象。
    从示例代码中的最后一局断言可以看出,调用bind方法不会修改原始函数,而是创建了一个全新的函数

          var boundFunction = button.click.bind(button);
          assert(boundFunction != button.click,"Calling bind creates a completly new function");
    

    以上我们完成了对函数上下文的研究。

    对象字面量就是创建对象的一种简单容易阅读的方法。如下创建了一个对象

         var obj = {
            a:'add',
            b:'bro',
            c:'chris'
        }
     obj.a  //add
     obj['a']//"add"
        
    
  • 相关阅读:
    HBase 列族数量为什么越少越好
    Hbase 认识及其作用
    Hbase 源码讲解
    Hbase 目录树
    rabbitmq 连接过程详解
    rabbit 兔子和兔子窝
    rabbit 函数参数详解
    rabbitmq 用户和授权
    火狐浏览器安装有道插件
    rabbitmq vhost
  • 原文地址:https://www.cnblogs.com/jamal/p/14143072.html
Copyright © 2011-2022 走看看