1

1.this指向绑定与函数调用的关系

在正式理解this指向之前,我们需要明确一点,this是在函数被调用执行时才确定绑定的。当函数执行时,会创建执行环境,此时确定this的指向。
this的绑定和函数调用息息相关,函数的四种调用方式(函数独立调用模式、方法调用模式、构造调用模式、间接调用模式)也对应了this的四种绑定方式(默认绑定、隐式绑定、new绑定、显示绑定)。

2.this的默认绑定

this默认绑定指向window,与之对应的是函数独立调用模式。可以分为五种情况:

1)全局环境下,this默认指向window
    console.log(this);//window
2)函数独立调用时,函数内部this指向window
    function fn() {
        console.log(this);
    }
    fn();//window
3)被嵌套的函数独立调用时,this也默认指向window
    var a='全局下的a';
    var obj={
        a:'obj的a',
        foo:function () {
            //foo函数作为对象的方法被调用, this指向该对象
            var that=this;//此this指向obj
            function test() {
                console.log(this.a);//全局下的a
                console.log(that.a);//obj的a
            }
            test();
        }
    }
    obj.foo();//调用obj.foo时,test函数自动执行 其中的this指向window

在调用obj.foo()时,foo()是由obj调用的,他的this指向obj,但是test()是自动执行的,不是作为obj的方法执行的,他的this仍然是指向window。

4)自执行函数(IIFE)内部的this默认指向window
    (function () {
        console.log('自执行函数',this);
    })();
5)闭包函数中的this默认指向window
    var obj={
        a:'obj的a',
        foo:function () {
            var c=this.a;//obj的a
            return function test() {
                console.log(this.a);//全局下的a
                return c;//obj的a
            }
        }
    }
    var fn=obj.foo();
   console.log(fn());
   fn=null;

上例中,函数test是一个闭包函数,他的this指向window。

3.this的显示绑定

this的显示绑定对应的是函数的间接调用,主要有两种情况:

1)call,bind,apply方法绑定
    var a="全局下的a";
    function foo() {
        console.log(this.a);
    }
    var obj={
        a:"obj的a",
    }
    foo();//全局
    foo.call(obj);//obj
    foo.apply(obj);//obj
    foo.bind(obj)();//obj

通过call,apply以及bind方法间接调用函数时,可以传入一个对象,将调用的函数foo中的this绑定到这个对象obj上。关于call、apply、bind方法此处不赘述。

2)在使用数组的forEach/map/filter/some/every等方法时,也可以传入对象,显示绑定this
    //数组的forEach(fn,对象)
    var arr=[1,2,3];
    arr.forEach(foo);//全局下的a *3
    arr.forEach(foo,obj);//obj的a *3

4.this的new绑定

this的new绑定对应函数的构造函数模式。当使用new关键字执行函数时,函数中的this指向当前的实例化对象。

    function foo() {
        console.log(this);//foo{}
    }
   
    var fn=new foo();
    console.log(fn);//foo{}

因为此处函数foo没有返回值,所以创建的fn就是foo函数,this指向的是fn。

5.this的隐式绑定

this的隐式绑定对应的是函数的方法调用模式。当函数作为一个对象的方法被调用时,函数内部this指向该对象。

    function foo() {
        console.log(this.a);
    }
    var a='全局的a';
    var obj={
        a:'obj的a',
        foo:foo,
        obj2:{
            a:'obj2的a',
            foo:foo,
        }
    }
    foo();//window
    //foo()函数的直接对象是obj this的指向为直接对象
    obj.foo();//obj
    //foo()函数的直接对象是obj2 this的指向为直接对象
    obj.obj2.foo();//obj2

在this的隐式绑定中要特别注意隐式丢失的几种情况。隐式丢失是指,被隐式绑定的函数丢失了绑定对象,因此又默认绑定到了window上。如下:

    var a="全局下的a";
    function foo() {
        console.log(this.a);
    }
    var obj={
        a:"obj的a",
        foo:foo,
    }
1)别名赋值
    var bar=obj.foo;
    bar();//全局下的a

很多人可能会觉得,bar调用后应该是输出‘obj的a’,其实不然。bar本身是和对象obj没有任何关系的。
因为在声明函数foo时,js是在堆中开辟了一块内存空间,用于存放foo函数内部的代码(以字符串的形式),然后将这个内存空间的地址给了foo,在对象objfoo:foo,,以及var bar=obj.foo;时,实际上赋值的都是内存地址,在执行时,bar根据内存地址,找到堆中函数体代码进行执行,这个过程和对象obj没有产生任何联系。为了便于理解,可以将obj看为:

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

var bar=obj.foo;看为:

var bar=function(){
            console.log(this.a);
        };
2)参数传递
    function bar(fn) {
        fn();
    }
    bar(obj.foo);//全局下的a

把obj.foo当做参数出入bar,相当于隐式的参数赋值 fn=obj.foo,即

    fn=function(){
            console.log(this.a);
        };

本质和别名赋值相同。

3)内置函数

setTimeout/setInterval的回调函数中的this默认指向window,也可以看做是参数传递

setTimeout(obj.foo,1000);//全局中的a
4)特殊情况
    (obj.foo = obj.foo)();//全局下的a
    (false||obj.foo)();//全局下的a
    (1,obj.foo)();//全局下的a

这三种情况都可以看做是参数传递赋值,将obj.foo赋值给了自执行函数。

以上仅为个人理解,如有错误,敬请指正。

forceddd
271 声望912 粉丝

一名前端爱好者。