函数的this

70 阅读6分钟

函数的this

this(非常重要)

该对象在标准函数和箭头函数中有着不同的行为,在标准函数中,this指向的是调用该函数的上下文对象(在网页全局调用该函数时,this指向window对象),与函数的声明位置没有任何关系,只取决于函数的调用方式,并且是在运行时绑定的,不是在编写时绑定

当一个函数被调用时,会创建一个活动记录(也称执行上下文)。指向上下文包含函数在哪里被调用,函数的调用方法,传入的参数等信息,this是执行上下文其中的一个属性,它的指向由一套绑定规则来决定

调用位置和调用栈

  1. 调用栈:到达当前执行位置所调用的所有函数
  2. 调用位置:函数被调用的位置,当前正在执行的函数的前一个调用
function baz() {
  // 调用栈: baz
  // 调用位置:全局作用域
  console.log("baz");
  bar();
  console.log(11)
}
​
function bar() {
  // 调用栈:baz-->bar
  // 调用位置:baz
  console.log("bar");
  foo();
}
​
function foo() {
  // 调用栈:baz-->bar-->foo
  // 调用位置:bar
  console.log("foo");
}
​
baz();
  • 调用栈是JS引擎追踪函数执行的一个机制,当一次有多个函数被调用时,通过调用栈就能够追踪到哪个函数正在被执行以及各个函数之间的调用关系,在分析复杂的代码时,调用栈是非常有利的

  • 栈溢出

    调用栈是用来管理执行上下文的数据结构,先进后出,它有大小,当入栈的上下文超过一定数据,JS引擎就会报错(虽然一般遇不到这种情况)

绑定规则(划重点)

this的绑定规则完全取决于调用方式

默认绑定(默认规则)
  • this指向全局对象(window)

  • 最常用的函数调用类型:独立函数调用

    function foo() {
        console.log( this.a );
    }
    var a = 2;//全局变量
    foo(); //2
    

    当调用 foo() 时, this.a 被解析成了全局变量 a,foo() 是直接使用不带任何修饰的函数引用进行调用的,因此使用默认绑定,但如果使用严格模式( strict mode ),那么全局对象将无法使用默认绑定this 会绑定到 undefined

    function foo() {
        "use strict";
        console.log( this.a );
    }
    var a = 2;
    foo(); 
    ​
    -------- 分割线 --------
    ​
    function foo() {
        console.log( this.a );
    }
    var a = 2;
    ​
    (function(){
        "use strict";
        foo(); 
    })();
    
    注意这两者的差异:

    函数在严格模式下被调用和函数处于严格模式下对于默认绑定来说,决定 this 绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。如果函数体处于严格模式, this 会被绑定到 undefined ,否则this 会被绑定到全局对象。

    通常来说不应该在代码中混合使用 strict modenon-strict mode 整个程序要么严格要么非严格

隐式绑定

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

上下文对象(context object)是指与上下文相关的对象。在 JavaScript 中,上下文对象通常是指在函数调用中隐式绑定 this 值的对象。例如,在对象方法中,该对象就是上下文对象。当调用对象方法时,函数的 this 值会绑定到该对象上。这种绑定关系允许函数在方法内部访问上下文对象的属性和方法。

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

无论是直接在 obj 中定义还是先定义再添加为引用属性,这个函数严格来说都不属于obj 对象,调用位置会使用 obj 上下文来引用函数,可以说函数被调用时 obj 对象“拥有”它。

当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。 调用 foo()this 被绑定到 obj ,因此 this.aobj.a 是一样的。

function foo() {
    console.log( this.a );
}
var obj2 = {
    a: 42,
    foo: foo
};
var obj1 = {
    a: 2,
    obj2: obj2
};
obj1.obj2.foo(); 
对象属性引用链中只有最顶层或者说最后一层会影响调用位置。
隐式丢失

被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上,取决于是否是严格模式

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2,
    foo: foo
};
var bar = obj.foo; 
var a = 6; 
bar(); 

在这里,我们将obj.foo这个方法的函数体赋值给了bar,而bar又是直接调用的,所以此时触发的是默认规则,this将绑定到全局对象上。

function foo() {
    console.log( this.a );
}
function doFoo(fn) {
    fn(); 
}
var obj = {
    a: 2,
    foo: foo
};
var obj2={
    a:10,
    c:0
}
var a = 9; // a 是全局对象的属性
doFoo( obj.foo ); // 9
obj2.c=obj.foo
obj2.c() // 10

显示绑定

通过call()apply()这两种方法来调用函数可以直接指定this绑定的对象,这两个方法的第一个参数都是目标绑定对象

function foo() {
    console.log( this.a );
}
var a=6
var obj = {
    a:2
};
var obj2 = {
    a:5
};
foo()
foo.call(obj); 

显示绑定是在函数调用时手动指定函数的 this 值,并且仅在调用期间有效,函数调用结束后,this 值将返回到默认绑定(非严格模式下为全局对象,严格模式下为 undefined)。

硬绑定

通过bind()方法创建一个新的函数,将其 this 值绑定到指定的对象上。硬绑定的函数无论如何调用,其 this 值都不会改变,始终保持绑定时的值。

function foo() {
  console.log(this.a);
}

var obj = {
  a: 42
};

var bar = foo.bind(obj);
bar(); // 42

new绑定

创建(或者说是构造)一个全新的对象,这个新对象会被执行proto连接:{}.__proto__ = foo.prototype,这个新对象会绑定到函数调用的this,如果函数没有返回其它对象,那么new表达式中的函数调用会自动返回这个新对象

function foo(a) {
   this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); 
优先级

new > 显示绑定 > 硬绑定 > 隐式绑定 > 默认绑定

注意

null或者undefined作为this的绑定对象传入callapply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则,至于什么情况下会传入 null ,我觉得一般都是本身就是要用默认绑定规则来调用函数时对函数的参数进行一点处理时会用到

箭头函数与this

箭头函数无法使用以上的所有规则。它的 this是根据外层(函数或者全局)作用域来决定的

function foo() {
    // 返回一个箭头函数
    return (a) => {
        console.log( this.a );
    };
}
var a=9
var obj1 = {
    a:2
};
var obj2 = {
    a:3
};
var bar = foo.call( obj1 );
bar() // 2
bar.call( obj2 ); // 2

// ----------------- 分割线 ---------------------

function foo() {
    // 返回一个普通函数
    return function (a) {
        console.log( this.a );
    };
}
var a=9
var obj1 = {
    a:2
};
var obj2 = {
    a:3
};
var bar = foo.call( obj1 );
bar() // 9
bar.call( obj2 );  // 3