学习笔记=>《你不知道的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时失效。

 

posted @ 2018-10-23 15:13  小菜鸡的梦  阅读(143)  评论(0编辑  收藏  举报