关于this

125 阅读5分钟

this是什么

当一个函数被调用,会创建一个执行上下文,它包含了函数在哪里被调用,调用方式,传入的参数信息等,this就是这个记录属性,this是在函数被调用(运行)时绑定的,它指向什么完全取决于函数在哪里被什么方式调用

绑定规则

默认绑定

这是最常用的函数调用类型:独立函数调用,举例!

        function foo() {
            console.log(this.a);
        }
        var a = 2;
        foo() // 结果为2

在本例中,函数调用时应用了this的默认绑定,因此this指向全局对象

在全局作用域下直接调用函数时,this指向window对象

隐式绑定

调用位置是否有上下文对象,也会影响到this的指向,举例!

        function foo() {
            console.log(this.a);
        }
        var obj={
            a:42,
            foo:foo
        };
        obj.foo() // 结果为42

当函数引用有上下文对象时,隐式绑定规则会把函数中调用的this绑定到这个上下文对象,因此此时this指向对象obj1

隐式丢失现象

被隐式绑定的函数会丢失绑定对象,也就是说这个时候它会应用默认绑定,举例!

        function foo() {
            console.log(this.a);
        }
        var obj = {
            a: 2,
            foo: foo
        };
        var bar = obj.foo; // 注意,这里相当于是赋值操作了
        var a = 3;
        bar() // 结果为3

虽然bar是obj.foo的一个引用,但实际上,它引用的是全局下foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,相当于全局下直接调用foo函数了,所以此时this指向window对象

除此以外,回调函数丢失this绑定也是非常常见的,举例!

        function foo() {
            console.log(this.a);
        }
        function doFoo(fn){
            fn()
        }
        var obj = {
            a: 2,
            foo: foo
        };
        var a = 3;
        doFoo(obj.foo) // 结果为3

参数传递其实就是一种隐式赋值(LHS),所以传入参数时也会被隐式赋值,所以结果和上面的一样

当函数被当做对象的方法调用时,this指向这个对象

显示绑定

使用 call(),apply(),bind()方法可以在调用函数时强制把它的this绑定到obj上(但bind 方法会返回一个新函数),举例!

        function foo() {
            console.log(this.a);
        }
        var obj = {
            a: 2,
        };
        foo.call(obj) // 结果为2

从this绑定的角度来说,call()和apply()方法是一样的,区别只在于传参不一样,但这两种方法仍然无法直接解决丢失绑定的问题,不过好在它们的变种可以解决,举例!

        function foo() {
            console.log(this.a);
        }
        var obj = {
            a: 2,
        };
        var bar = function () {
            foo.call(obj);
        }
        var a = 3;
        bar(); // 结果为2
        setTimeout(bar, 100); // 结果为2
        bar.call(window) // 结果为2

我们先创建了函数bar,并在它内部手动调用foo.call(obj),强制把foo的this绑定给了obj,之后无论怎么调用函数bar,它总会手动在obj上调用foo,这种是显式的强制绑定,叫“硬绑定”,硬绑定的典型应用场景就是创建一个包裹函数,负责接受参数并返回值,举例!

        function foo(something) {
            console.log(this.a, something);
            return this.a + something
        }
        var obj = {
            a: 2,
        };
        var bar = function () {
            return foo.apply(obj, arguments)
        }
        var b = bar(3); //结果为2 3
        console.log(b); //结果为5

由于硬绑定是很常用的模式,所以ES5提供了内置方法,也就是bind();举例!

        function foo(something) {
            console.log(this.a, something);
            return this.a + something
        }
        var obj = {
            a: 2,
        };
        var bar = foo.bind(obj);
        var b = bar(3); //结果为2 3
        console.log(b); //结果为5

结果和上例一样,bind()会返回一个硬编码的新函数,会把指定的参数设置为this的上下文并调用原始函数

使用call(),apply(),bind()方法时,this指向传入的对象

new绑定

最后一条this绑定规则,在此之前先澄清一个关于javascript中函数和对象的误解:javascript中的构造函数,只是一些在使用new操作符时被调用的函数,只是被new操作符调用的普通函数而已!实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。

使用new来调用函数,会自动执行以下操作:

  1. 创建一个全选的对象
  2. 这个新对象的原型prototype指向构造函数的原型
  3. 这个新对象会绑定到函数调用的this
  4. 如果函数没有返回其它对象,那么new表达式中的函数调用会自动返回这个新对象

举例!

        function foo(a) {
            this.a = a;
        }
        var bar = new foo(2);
        console.log(bar.a); // 结果为2

使用new来调用foo时,会构造一个新的对象并把它绑定到foo()调用中的this上

使用构造函数时,this指向新创建的实例对象

优先级

隐式绑定和显式绑定哪个优先级更高?举例!

        function foo() {
            console.log(this.a);
        };
        var obj1 = {
            a: 2,
            foo: foo
        };
        var obj2 = {
            a: 3,
            foo: foo
        };
        obj1.foo(); // 2
        obj2.foo(); // 3
        obj1.foo.call(obj2); // 3
        obj2.foo.call(obj1); // 2

显式绑定优先级更高

隐式绑定和new绑定哪个优先级更高?举例!

        function foo(something) {
            this.a = something;
        };
        var obj1 = {
            foo: foo
        };
        var obj2 = {};
        obj1.foo(2);
        console.log(obj1.a); // 2
        obj1.foo.call(obj2, 3);
        console.log(obj2.a); // 3
        var bar = new obj1.foo(4);
        console.log(obj1.a); // 2
        console.log(bar.a); // 4

new绑定比隐式绑定优先级更高

被忽略的this

如果把null或者undefined作为this的绑定对象传入call,apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则,举例!

        function foo() {
            console.log(this.a);
        };
        var a = 2;
        foo.call(null) // 结果为2

箭头函数的this

箭头函数并不是用function关键字定义的,不使用this的四种标准规则,而是根据外层(函数或者全局)作用域来决定this,举例!

        function foo() {
            return (a) => {
                console.log(this.a);
            }
        };
        var obj1 = { a:2 };
        var obj2 = { a:3 };
        var bar = foo.call(obj1);
        bar.call(obj2) // 结果为2

foo()内部创建的箭头函数会捕获调用时foo()的this,由于foo的this绑定待obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改(new也不行)