this四种绑定方式和优先级

159 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。 this是javascript中一个比较特别的关键字,this是在代码执行过程中被动态绑定的,不同的执行条件下很可能会被绑定不同的对象,关于this的绑定其中也有很多让人迷惑的地方,这次,一起来看一下this的绑定规则吧。

this的绑定方式

this共有四种绑定方式

  1. 默认绑定
  2. 隐式绑定
  3. 显示绑定
  4. new绑定

默认绑定

隐式绑定表现为一个独立函数的直接调用,比如下列代码:

function foo(){
    console.log(this);
}
foo();//window

这种调用方式就算作独立函数的直接调用,this执行的就是默认绑定。this会被绑定到window对象上(严格模式下会绑定undefined) 接下来看一些案例(非严格模式下): 1.函数的链式调用

function test1() {
    console.log(this);
    test2();
}
function test2() {
    console.log(this);
}
test1();

答案:

test1和test2打印的this都指向window

虽然test2是在test1内部执行的,但是在js中this并不会继承父级的this,this的指向主要取决于其调用方式,而非调用位置。所以虽然在内部被调用,但仍然是一个独立函数的调用,所以两个打印的this都是指向window对象的。

隐式绑定

隐式绑定往往需要一个调用主体,比较常见的有对象内方法的调用,如下述代码:

var obj={
    name:"echo",
    foo:function(){
        console.log(this);
    }
}
obj.foo();//obj

像这种调用方式,foo函数前面有一个调用主体obj,this执行的就是隐式的绑定,this绑定的就是调用主体。 看一个上述案例的变形:

var obj={
    name:"echo",
    foo:function(){
        console.log(this);
    }
}
obj.foo();
var bar=obj.foo;
bar();

答案:

obj.foo();//obj
bar();//window

obj.foo()指向obj这个不用多说,因为前面有个调用主体

看一下执行bar()的时候为何会执行window

首先先是执行var bar=obj.foo,这一步将obj对象下的foo函数赋给了变量bar,然后对bar执行调用,bar指向obj里的foo函数,但其最终的调用方式其实还是一个独立函数调用,所以最终打印结果还是指向window的。

显示绑定

显示绑定就是通过call/apply/bind三种方式进行this的绑定。 举个例子:

function foo(){
    console.log(this);
}
var obj={name:"li"};
foo.call(obj);
foo.apply(obj);
foo.bind(obj)();

答案:

foo.call(obj);//obj
foo.apply(obj);//obj
foo.bind(obj)();//obj

new绑定

new绑定就是通过new一个构造函数的方式进行绑定,this会指向被new出来的那个对象。比如下述代码:

function Person(name){
    this.name=name;
}
var person1=new Person();//person1

这里的this就是指向person1的。

this的规则优先级

  1. 独立调用优先级最低
  2. 显示调用优先级高于隐式调用
  3. new绑定优先级高于隐式调用
  4. new绑定优先级高于显示调用 第一条就不用多说了,先来验证一下2,3,4
//测试显示绑定优先级高于隐式绑定
var obj = {
    name: "echo",
    foo: function () {
             console.log(this);//{name:"jango"}
        }
    }
obj.foo.call({name: "jango"});

obj.foo.call()同时使用了隐式绑定和显示绑定,最后打印的this指向的是{name:"jango"},说明最终生效的是call函数的绑定,因此可证明显示绑定的优先级是高于隐式绑定的。

//验证new绑定优先级高于隐式绑定
var obj = {
            name: "echo",
            foo: function () {
                console.log(this);
            }
        }
        new obj.foo();//foo

这里的this指向是foo,说明new绑定的优先级高于隐式绑定。

//验证new绑定优先级高于显示绑定
function foo() {
            console.log(this);
        }
        var bar = foo.bind("abc");
        new bar();//foo

new无法和call,apply一起使用,因为使用call,apply的话都会直接执行,只有bind是返回一个函数。所以这里用bind进行测试。

箭头函数的this绑定

箭头函数是不会绑定this的,因此以上的四个规则对箭头函数并不适用,箭头函数的this取决于其父级作用域。 看个例子验证一下箭头函数不绑定this

var obj = {
            name: "echo",
            foo: function () {
                setTimeout(function () {
                    console.log(this);
                },1000)
            }
        }
        obj.foo();

答案:

obj.foo();//window

setTimeout内传递的参数是独立调用的,所以其打印结果的this是指向window对象的。可以再来看一下settimeout里传入箭头函数的情况

var obj = {
            name: "echo",
            foo: function () {
                setTimeout(()=> {
                    console.log(this);
                })
            }
        }
        obj.foo();

答案:

obj.foo();//obj

这里打印的this指向是obj,这是因为箭头函数并不绑定this,所以它会去箭头函数的父级作用域查找this,它的父级作用域是foo。故指向foo;

一波面试题

var name = 'window'
        var person1 = {
            name: 'person1',
            foo1: function () {
                console.log(this.name)
            },
            foo2: () => console.log(this.name),
            foo3: function () {
                return function () {
                    console.log(this.name)
                }
            },
            foo4: function () {
                return () => {
                    console.log(this.name)
                }
            }
        }

        var person2 = {
            name: 'person2'
        }

        person1.foo1();
        person1.foo1.call(person2);

        person1.foo2();
        person1.foo2.call(person2);

        person1.foo3()();
        person1.foo3.call(person2)();
        person1.foo3().call(person2);

        person1.foo4()();
        person1.foo4.call(person2)();
        person1.foo4().call(person2);

答案:

1.person1.foo1(); //person1
2.person1.foo1.call(person2); //person2

3.person1.foo2(); //window
4.person1.foo2.call(person2); //window

5.person1.foo3()(); //window
6.person1.foo3.call(person2)(); //window
7.person1.foo3().call(person2); //person2

8.person1.foo4()(); //person1
9.person1.foo4.call(person2)(); //person2
10.person1.foo4().call(person2); //person1
  1. 隐式调用 指向调用它的主体
  2. 显式调用优先级高于隐式调用优先级,故为person2
  3. 箭头函数不绑定this,打印this的时候会去它的父级作用域查找,它的父级作用域为window,故打印window
  4. 同样的箭头函数不绑定this
  5. person1.foo3()()中的person1.foo()部分是将foo3.this绑定到了person1,并且返回一个函数,然后又在后面通过()调用了这个函数。这个函数实质上是一个独立调用,所以打印的仍旧是window
  6. 与5类似,person1.foo3.call(person2)是将foo3的this绑定到了person2,并返回了一个函数,再通过()调用,本质上还是一个独立调用,故为window
  7. 这里是person1.foo3()返回了一个函数,然后对这个返回的函数执行.call(person2).因此打印的是person2
  8. person1.foo4()绑定foo4的this为person1,然后返回一个箭头函数并在后面使用()调用,因为箭头函数不绑定this,所以会打印父级作用域也就是foo4的this,故为person1
  9. 同样是打印foo4的this,但是这里foo4被优先级更高的call绑定到了person2的this
  10. person1.foo4()将foo4的this绑定为person1,并返回了一个箭头函数,然后为这个箭头函数通过call绑定person2,但是箭头函数是无法绑定this的,所以最终打印的还是foo4里面的this-person1