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时失效。

  • 相关阅读:
    Anagram
    HDU 1205 吃糖果(鸽巢原理)
    Codeforces 1243D 0-1 MST(补图的连通图数量)
    Codeforces 1243C Tile Painting(素数)
    Codeforces 1243B2 Character Swap (Hard Version)
    Codeforces 1243B1 Character Swap (Easy Version)
    Codeforces 1243A Maximum Square
    Codeforces 1272E Nearest Opposite Parity(BFS)
    Codeforces 1272D Remove One Element
    Codeforces 1272C Yet Another Broken Keyboard
  • 原文地址:https://www.cnblogs.com/huangzhenghaoBKY/p/9830768.html
Copyright © 2011-2022 走看看