美文网首页
JS基础之函数

JS基础之函数

作者: 人失格 | 来源:发表于2017-05-17 16:00 被阅读12次

    JS中 我认为设计最出色的便是函数,它几乎接近完美,但是也有些许瑕疵

    函数对象

    JS中的函数就是对象, 对象是 key value对的集合并拥有一个连到原型对象的隐藏连接,对象字面量产生的对象连接到Object.prototype 函数对象连接到Function.prototype。每个函数在创建时都会附加两个隐藏属性: 函数的上下文和实现函数行为的代码。

    因为函数式对象,所以它们可以像其他值一样被使用,函数可以保存变量 对象和数组, 函数可以被当做参数传递给其他函数,函数也可以再返回函数,而且,因为函数是对象,所以函数可以拥有方法。

    函数的与众不同在于他们可以被调用。

    函数字面量

    var add = function(a,b){
      return a+ b;
    }
    
    1. 第一个为保留字 function
    2. 第二部分为函数名,可以被省略 如上例,这个被称为匿名函数
    3. 函数的参数,它们不像普通的变量那样将初始化为undefined 而是该函数被调用时初始化实际提供的参数
    4. 大括号内部的一组语句,这些是函数的主体。

    调用

    调用一个函数会暂停当前函数的执行,传递控制权和参数给新函数,每个函数还接受两个附加的参数: this和arguments,在刚写JS的代码的时候经常被this搞崩溃,this在面向对象编程的时候非常的重要,它的值取决于调用的模式。JS中一共有六种调用模式: 每一种调用方式在如何初始化this存在差异性。

    1. 方法调用 保存为一个对象的属性的时候
    2. 函数调用 不是为一个对象的属性的时候
    3. 构造器调用
    4. apply call调用
    5. bind()调用 es6 用来返回一个重新绑定this的函数
    6. 箭头符号调用 es6 this的绑定从它的封闭域来绑定

    函数的参数问题: 当实际的参数的个数和形式参数的个数不匹配时,不会导致运行时错误,如果实际参数过多,过多的参数会被忽略掉,如果实际参数过少,则缺失的值会被替换为undefined 对参数值不会进行类型检查,任何类型的值都可以呗传递给任何参数

    this

    调用点 为了理解this绑定,我们不得不理解调用点: 函数在代码中被调用的位置。在实际中,我们要考虑到调用栈,首先来段代码来熟悉一下调用栈和调用点

    function baz() {
        // 调用栈是: `baz`
        // 我们的调用点是global scope(全局作用域)
    
        console.log( "baz" );
        bar(); // <-- `bar`的调用点
    }
    
    function bar() {
        // 调用栈是: `baz` -> `bar`
        // 我们的调用点位于`baz`
    
        console.log( "bar" );
        foo(); // <-- `foo`的call-site
    }
    
    function foo() {
        // 调用栈是: `baz` -> `bar` -> `foo`
        // 我们的调用点位于`bar`
    
        console.log( "foo" );
    }
    
    baz(); // <-- `baz`的调用点
    

    函数是如何被调用的决定了函数执行期间this指向哪里,this在一般人看来有点头晕在于亮点1. this不是在编译的时候确定的,相反作为一个动态语言来说 this是在函数运行的时候来确定的,2. this如何绑定取决于它是如何被调用的

    1. 函数调用

      function foo(){
        console.log(this.a);
      }
      
      var a = 2;
      foo();
      

      函数调用来说 this 默认是绑定在全局变量中, 如果js运行在浏览器中则为window 如果是node.js则为undefined。

    2. 方法调用

      如果函数式作为一个对象的属性来调用的话,也就是调用点是有一个环境对象(context object),当函数是在一个对象中 通过 . 符号来触发的时候 ,此时函数中的this 默认是指向它所在的对象种。

      function foo(){
        console.log(this.a);
      }
      
      var obj = {
        a : 2,
        foo: foo
      }
      
      obj.foo();// 2
      

      调用点使用Obj环境来引用函数,因为obj是foo调用的this 所以 this.a就是obj,a的同义词

      只有对象属性引用链中的最后一层是影响调用点的

      function foo() {
       console.log( this.a );
      }
      
      var obj2 = {
       a: 42,
       foo: foo
      };
      
      var obj1 = {
       a: 2,
       obj2: obj2
      };
      
      obj1.obj2.foo(); // 42
      

      this丢失 在方法调用的过程中,最常见的问题便是this丢失导致的错误。先看下端代码

      function foo() {
       console.log( this.a );
      }
      
      var obj = {
       a: 2,
       foo: foo
      };
      
      var bar = obj.foo; // 函数引用!
      
      var a = "oops, global"; // `a`也是一个全局对象的属性
      
      bar(); // "oops, global"
      

      上面代码 ,首先bar 看上去是obj.foo的引用,实际上其实它不过是foo的自己的引用而已,另外,起作用的调用点是bar() 为普通的函数调用,导致此处的this 其实是绑定到全局变量上面。

      这种错误在回调函数比较常见

      function foo() {
       console.log( this.a );
      }
      
      var obj = {
       a: 2,
       foo: foo
      };
      
      var a = "oops, global"; // `a`也是一个全局对象的属性
      
      setTimeout( obj.foo, 100 ); // "oops, global"
      

      不管是哪种改变this的方式,你都不能真正地控制你的回校函数引用如何被执行,所以一般我们需要固定this来解决问题

      1. apply call调用

        我们可以明确的指定this来明确绑定this, 允许我们强制函数的this指定我们需要的参数

        function foo() {
          console.log( this.a );
        }
        
        var obj = {
          a: 2
        };
        
        foo.call( obj ); // 2
        

        在函数固定绑定中 有一个小技巧,我们可以利用闭包的概念来实现硬绑定来防止我们需要绑定的this没改变。

        function foo() {
          console.log( this.a );
        }
        
        var obj = {
          a: 2
        };
        
        var bar = function() {
          foo.call( obj );
        };
        
        bar(); // 2
        setTimeout( bar, 100 ); // 2
        
        // `bar`将`foo`的`this`硬绑定到`obj`
        // 所以它不可以被覆盖
        bar.call( window ); // 2
        
      2. 构造器绑定

        something = new MyClass(..);
        

        它看起来和我们之前使用的面向对象的语言是一样,其实Js的机制和new在JS的用法面向对象的功能,没有任何联系。

        首先,让我们重新定义JavaScript的“构造器”是什么。在JS中,构造器 仅仅是一个函数,它们偶然地被前置的new操作符调用。它们不依附于类,它们也不初始化一个类。它们甚至不是一种特殊的函数类型。它们本质上只是一般的函数,在被使用new来调用时改变了行为。

        当在函数前面被加入new调用时,也就是构造器调用时,下面这些事情会自动完成:

        1. 一个全新的对象会凭空创建(就是被构建)
        2. 这个新构建的对象会被接入原形链([[Prototype]]-linked)
        3. 这个新构建的对象被设置为函数调用的this绑定
        4. 除非函数返回一个它自己的其他 对象,这个被new调用的函数将 自动 返回这个新构建的对象。

    判断this

    现在,我们可以按照优先顺序来总结一下从函数调用的调用点来判定this的规则了。按照这个顺序来问问题,然后在第一个规则适用的地方停下。

    1. 函数是和new一起被调用的吗(new绑定)?如果是,this就是新构建的对象。

      var bar = new foo()

    2. 函数是用callapply被调用(明确绑定),甚至是隐藏在bind 硬绑定 之中吗?如果是,this就是明确指定的对象。

      var bar = foo.call( obj2 )

    3. 函数是用环境对象(也称为拥有者或容器对象)被调用的吗(隐含绑定)?如果是,this就是那个环境对象。

      var bar = obj1.foo()

    4. 否则,使用默认的this默认绑定)。如果在strict mode下,就是undefined,否则是global对象。 var bar = foo()

    箭头函数

    一般我们遵循上面的四种规则,ES6引入了一种不适合上面这些规则的函数: 箭头函数。箭头函数不是靠关键字function来声明的,而是所谓的箭头函数: =》 ,箭头函数从封闭它的作用域(function global)采取this绑定。

    function foo() {
      // 返回一个arrow function
        return (a) => {
        // 这里的`this`是词法上从`foo()`采用
            console.log( this.a );
        };
    }
    
    var obj1 = {
        a: 2
    };
    
    var obj2 = {
        a: 3
    };
    
    var bar = foo.call( obj1 );
    bar.call( obj2 ); // 2, 不是3!
    

    foo中会创建的箭头函数捕捉foo调用的this,因为foo()被this绑定到obj1,所以bar(被返回的箭头函数的一个引用),一个箭头函数的this绑定是不能被覆盖的。

    相关文章

      网友评论

          本文标题:JS基础之函数

          本文链接:https://www.haomeiwen.com/subject/uavdxxtx.html