zoukankan      html  css  js  c++  java
  • 学习笔记=>《你不知道的JavaScript(上卷)第二部分》第二章:this全面解析

    调用的位置:

      函数中this的绑定取决于它所调用的位置。

      通常来说,寻找调用位置就是寻找"函数被调用的位置",最重要的是要分析调用栈(就是为了当前执行位置所调用的所有函数)。

      通过例子来看调用栈和调用位置:

    function a(){
          //当前调用栈是:a
          //因此当前调用位置是全局作用域
          console.log('this is a');
          b();     //b的调用位置
    }
    
    functoin b(){
          //当前调用栈:a--->b
          console.log('this is b');
          c();     //c的调用位置
    };
    
    function c(){
          //当前调用栈:a--->b--->c
          console.log('this is c');
    };
    
    a();     //--->a的调用位置

      浏览器中一般都带有查看当前函数调用栈的调试器,以下是在Google中在控制点上的查看调用栈方式:

    绑定规则:

      this的绑定规则通常有4种:

        1,默认绑定:最常用的函数调用类型,独立函数调用。可以把这条规则看作是无法应用其他规则的默认规则。

    function bar(){
          console.log(this.a);
    }
    
    var a = 111;
    
    bar();    //111

      函数调用时应用的是this的默认绑定,因此this指向的是全局对象。

      如何知道bar中的this绑定是使用的默认绑定规则?在代码中,bar()是直接使用不带任何修饰的函数引用进行调用的,因此

      ,只能使用默认绑定规则,而不能使用其他绑定规则。

      如果使用严格模式"use strict",那么全局对象将无法使用默认绑定,这个时候this指向undefined:

    function bar(){
           'use strict';
           console.log(this.a);
    }
    
    var a = 111;
    
    bar();    //TypeError: Cannot read property 'a' of undefined

      2,隐式绑定:隐式绑定要考虑的规则是调用位置是否有上下文对象。或者说是否被某个对象拥有或包含。

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

      上面的例子中,obj对象的foo属性引用了foo方法,函数foo严格来说不属于obj对象。然而,调用位置会使用obj上下文来引

      用函数,因此可以说函数被调用时obj拥有或者包含它。当函数引用有上下文对象时,隐式绑定规则会将函数的this绑定到

      这个上下文对象中。

      隐藏丢失(一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会引用默认绑定):

    function bar(){
          console.log(this.a);
    }
    
    var obj = {
          a:111,
          foo:bar
    };
    
    var a = '*****';
    
    var baz = obj.foo;
    
    baz();     // '*****'

      虽然baz是obj.foo的一个引用,但实际上,它引用的是bar函数本身。因此,此时的bar是一个不带任何修饰的函数

      调用,因此使用默认绑定。

      隐藏丢失一种更微妙和常见的情况发生在传入回调函数时:

    function bar(){
          console.log(a);
    }
    
    var obj = {
           a:111,
           foo:bar
    };
    
    function baz(fn){
          //隐式的--->var fn = bar
          fn();
    }
    
    var a = '*****';
    
    baz(obj.foo);    // '*****'

      将函数传入内置函数会怎么样呢?

    function bar(){
            console.log(this.a);
    }
    
    var obj = {
          a:111,
          foo:bar
    };
    
    var a = '*****';
    
    window.setTimeout(obj.foo,0);    //'*****'

      为什么会这样呢?其实JavaScript内置的setTimeout的实现和下面的代码类似:

        function setTimeout(fn,delay){

          //隐式的--> var fn = bar;

        }

      3,显示绑定:使用call与apply可以显示的绑定this到指定参数对象。

      看个例子:

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

      注:当你传入一个原始值(字符串,数字,布尔类型)作为this指向的时候,这个原始值会被转为它的对象形

        式(new Number,new String,new Boolean),这通常被称为'装箱'。

      硬绑定的典型应用场景就是创建一个包裹函数,传入参数并返回接收到的值:

    function bar(argum){
           console.log(this.a,argun); 
           return this.a + argum; 
    }
    
    var obj = {
         a:111,
         foo:bar
    };
    
    function baz(){
         return bar.apply(obj,arguments);
    };
    
    baz(222);   //333

      另一个方法是创建一个可以重复使用的方法:

    function bar(argum){
          console.log(this.a,argum);
          return this.a + argum;
    }
    
    var obj = {
          a:111,
          foo:bar
    };
    
    function bind(fn,obj){
           return function(){
                return fn.apply(obj,arguments);
           }
    }
    
    var b = bind(bar,obj);
    console.log(b(222));    //111,222,333

      由于硬绑定是非常常见的一种使用场景,所以ES5中提供了内置的方法,Function.prototype.bind,用法如下:

    function bar(argum){
        console.log(this.a,argum);
        return this.a + argum;
    }
    
    var obj = {
        a:111,
        foo:bar
    };
    
    var b = bar.bind(obj);
    
    console.log(b(111));

      bind会返回一个硬编码的新函数,它会把参数设置为this的上下文对象,并调用原始函数。

      在许多第三方库与JavaScript语言和宿主环境许多新的内置函数,都提供了一个可选参数,通常被称

      为‘上下文’,其作用和bind一样,确保你的回调函数使用指定的this。例:

    function bar(val){
          console.log(val,this.name);
    };
    
    var obj = {
          name:'lebron'
    };
    
    [1,2,3].forEach(bar,obj);
    
    // 1,'lebron'
    // 2,'lebron'
    // 2,'lebron'

      4,new绑定:

      JavaScript中的构造函数:JavaScript中的构造函数只是一些使用new操作符时被调用的函数,它并不会属于某个

                    类,也不会实例化一个类,实际上,它都不算是一种特殊的函数类型,它只是被new

                    操作符调用的普通函数而已。实际上并不存在所谓的‘构造函数’,只有对函数的构造调用。

      发生构造函数调用时,会有以下操作:1,创建一个全新的对象。

                       2,这个对象会被执行[[原型]]链接。

                       3,这个新对象会绑定到函数调用的this。

                       4,如果函数没有其他返回对象,则返回这个新对象。

      使用new调用函数的时候,我们会构建一个新对象,并把它绑定到new调用函数的this上。

    优先级:

      对比下显示绑定和隐式绑定哪个优先级更高一些:

    function bar(){
          console.log(this.a);
    }
    
    var obj1 = {
          a:111,
          foo:bar     //隐式绑定
    };
    
    var obj2 = {
          a:222,
          foo:bar   //隐式绑定
    };
    
    //调用隐式绑定的bar
    obj1.foo();  //111
    obj2.foo();  //222
    
    //调用显式绑定的bar
    obj1.foo.call(obj2);   //222
    obj2.foo.call(obj1);   //111
    
    //结论:显式绑点优先级高于隐式绑定

      将obj1中隐式绑定的bar显示绑定到obj2,结果打印的是obj2中的a ,所以显然显式绑定>隐式绑定。

      再对比一下new绑定与显式绑定的优先级:

    function bar(argum){
           this.a = argum;
    };
    
    var obj1 = {
          foo:bar
    };
    
    var obj2 = {};
    
    obj1.foo(2);
    console.log(obj1.a);    //2
    
    obj1.foo.call(obj2,3);
    console.log(obj2.a);   //3
    
    var baz = new obj1.foo(4);
    console.log(obj1.a);    //2
    console.log(baz.a);     //4
    
    // 结论:new绑定优先级>隐式绑定

      可以看到new绑定的优先级高于隐式绑定。

      最后:new绑定 > 显示绑定  > 隐式绑定 > 默认绑定

    判断this:

      我们可以根据优先级来判断函数在某种调用位置使用的是哪条规则:

        1,函数是否在new中调用,如果是的话this绑定的是新创建的那个对象。

        2,函数是否通过call,apply(显示绑定)或硬绑定,如果是则指向的是指定对象。

        3,函数是否在某个上下文对象中调用,这执行这个上下文对象。

        4,如果都不是则使用默认绑定。

    绑定例外:

      如果将null,undefined作为上下文对象参数传递给call,apply,bind的时候,这些值在调用时会被忽略掉,实际应用的是默认绑定规则:

    function bar(){
         console.log(this.a);
    }
    
    var a = 111;
    
    //使用undefined和null作为上下文对象
    bar.call(undefined | null);    //111

      硬绑定这种方式可以把this绑定到指定的对象,防止函数调用使用默认绑定规则。但是问题在于,硬绑定会大大降低函数的灵活性,使

      用硬绑定之后就无法使用隐式绑定或显式绑定修改this绑定。

      可以给默认绑定指定一个全局对象和undefined以外的值,那就可以实现和硬绑定相同的效果,同时保留显式绑定与隐式绑定修改this

      的能力,可以通过一种称为软绑定的方式来实现:

    if(!Function.prototype.softBind){
          Function.prototype.softBind = function(obj){
                var fn = this;
                //第一个参数为默认绑定的上下文对象,其他参数均为参数传递
                var currId = [].slice.apply(arguments,1);
                var bound = function(){
                        return fn.apply((!this ||(this === (window || gloable)))?obj:this,currId.concat.apply(currId,arguments));
                 };
                 bound.prototype = Object.create(fn.prototype);
                 return bound;
          }
    }    

    this词法:

      上面说的4种绑定规则对于所有正常函数使用,但是ES6中的箭头函数不使用上面4种规则。

    function bar(){
          return ()=>{
                 console.log(this.a);
          };
    }
    
    var obj1 = {a:111};
    
    var obj2 = {a:222};
    
    var baz = bar.call(obj1);
    baz();    //111
    
    baz.call(obj2);   //111

      bar中的箭头函数会捕获bar调用时的this,由于bar的this绑定到obj1,所以baz的this也是绑定

      到obj1,箭头函数的绑定无法被修改,所以再次call到obj2时失效。

  • 相关阅读:
    web进修之—Hibernate 继承映射(5)
    web进修之—Hibernate 类型(4)
    web进修之—Hibernate 关系映射(3)
    web进修之—Hibernate起步(1)(2)
    poj2828(Buy Tickets)线段树
    hdu2795(Billboard)线段树
    hdu1394(Minimum Inversion Number)线段树
    hdu4407Sum(容斥原理)
    树的重心
    匈牙利算法
  • 原文地址:https://www.cnblogs.com/huangzhenghaoBKY/p/9830768.html
Copyright © 2011-2022 走看看