# 搞懂this指向

💢

🚵This is a big mountain🏆

# 绑定机制

# 默认绑定

非严格模式下🍺全局环境中

  • 浏览器 : this默认绑定到window
  • node : this默认绑定到window

严格模式 全局环境

this->undefined

console.log(this === window); // true

函数独立调用时,this默认绑定到window

function fn(){
    console.log(this === window);
}
fn(); // true

被嵌套的函数独立调用时,this默认绑定到window

// 虽然 innerFn() 函数被嵌套在obj.foo()函数中,但 innerFn() 函数是独立调用,
// 而不是方法调用。所以this默认绑定到window
var x = 0;
var obj = {
          x : 2,
          foo:function(){
                function innerFn(){
                    console.log(this.x);
                }
                innerFn();
            }
}
obj.foo(); // 0

IIFE立即执行函数实际上是函数声明后直接调用执行

var a = 0;
function foo(){
    (function test(){
        console.log(this.a);
    })()
    // 改用箭头函数 输出为2
    // (() => {
    //     console.log(this.a);
    // })()
};
var obj = {
    a : 2,
    foo:foo
}
obj.foo(); // 0 
//等价于上例
var a = 0;
var obj = {
    a : 2,
    foo:function(){
            function test(){
                console.log(this.a);
            }
            test();
    }
}
obj.foo(); // 0

闭包

test()函数是独立调用,而不是方法调用,所以this默认绑定到window

👏来看看函数调用🤳

🛑

由于闭包的this默认绑定到window对象,但又常常需要访问嵌套函数的this, 所以常常在嵌套函数中使用var that = this,然后在闭包中使用that替代this, 使用作用域查找的方法来找到嵌套函数的this值

var a = 0;
function foo(){
    var that = this;  // 如果没有该语句 test 打印this.a的值为0
    function test(){
        console.log(that.a);
    }
    return test;
};
var obj = {
    a : 2,
    foo:foo
}
obj.foo()();//2

# 隐式绑定

一般地,被直接对象所包含的函数调用时,也称为方法调用,this隐式绑定到该直接对象

举个例子

function foo(){
    console.log(this.a);
};
var obj1 = {
    a:1,
    foo:foo,
    obj2:{
        a:2,
        foo:foo
    }
}
//foo()函数的直接对象是obj1,this隐式绑定到obj1
obj1.foo(); // 1

//foo()函数的直接对象是obj2,this隐式绑定到obj2
obj1.obj2.foo(); // 2

# 隐式丢失🍒

隐式丢失是指被隐式绑定的函数丢失绑定对象,从而默认绑定到window。

[👣留心]

这种情况容易出错却又常见

# 函数别名

var a = 0;
function foo(){
    console.log(this.a);
};
var obj = {
    a : 2,
    foo:foo
}
// 把obj.foo赋予别名bar,造成了隐式丢失,
// 因为只是把foo()函数赋给了bar,而bar与obj对象则毫无关系
var bar = obj.foo;
bar(); // 0

这种情况等价于下面示例

//等价于
var a = 0;
var bar = function foo(){
    console.log(this.a);
}
bar(); // 0

# 参数传递

var a = 0;
function foo(){
    console.log(this.a);
};
function bar(fn){
    fn();
}
var obj = {
    a : 2,
    foo:foo
}
// 把obj.foo当作参数传递给bar函数时,有隐式的函数赋值fn=obj.foo。
// 与上例函数别名类似,只是把foo函数赋给了fn,而fn与obj对象则毫无关系
bar(obj.foo); // 0
//等价于
var a = 0;
function bar(fn){
    fn();
}
bar(function foo(){
    console.log(this.a);
});

# 内置函数

内置函数与上例类似,也会造成隐式丢失

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

//等价于
var a = 0;
setTimeout(function foo(){
    console.log(this.a);
},100); // 0

# 间接引用

函数的"间接引用"一般都在无意间创建,最容易在赋值时发生,会造成隐式丢失

function foo() {
    console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
// 将o.foo函数赋值给p.foo函数,然后立即执行。
// 相当于仅仅是foo()函数的立即执行
(p.foo = o.foo)(); // 2
function foo() {
    console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
// 将o.foo函数赋值给p.foo函数,之后p.foo函数再执行
// 属于p对象的foo函数的执行
p.foo = o.foo;
p.foo();//4

# 显式绑定

  • 通过call()、apply()、bind()方法把对象绑定到this上,叫做显式绑定。
  • 对于被调用的函数来说,叫做间接调用
var a = 0;
function foo(x,y){
    console.log(`${this.a}+${x}+${y}=${this.a+x+y}`);
}
var obj = {
    a:2
};
foo(3,4);   // 0+3+4 = 7 !函数独立调用时,this默认绑定到window
foo.call(obj,3,4);  // 2+3+4=9

[注意]

  • 普通的显式绑定无法解决隐式丢失问题
  • 🐥硬绑定是显式绑定的一个变种,使this不能再被修改
  • javascript中新增了许多内置函数,具有显式绑定的功能,如数组的5个迭代方法:map()、forEach()、filter()、some()、every()‘
  • call(null) call(undefinde) = 隐式绑定->window
var a = 0;
function foo(){
    console.log(this.a);
}
var obj = {
    a:2
};
var bar= function(){
    foo.call(obj);
}
// 在bar函数内部手动调用foo.call(obj)。
// 因此,无论之后如何调用函数bar,它总会手动在obj上调用foo
bar();//2
setTimeout(bar,100);//2
bar.call(window);//2

# new绑定

如果函数或者方法调用之前带有关键字new,它就构成构造函数调用。对于this绑定来说,称为new绑定

  • 构造函数通常不使用return关键字,它们通常初始化新对象,当构造函数的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计算结果就是这个新对象的值
function fn(){
    this.a = 2;
}
var test = new fn();
console.log(test); // {a:2}
  • 如果构造函数使用return语句但没有指定返回值,或者返回一个原始值,那么这时将忽略返回值,同时使用这个新对象作为调用结果
function fn(){
    this.a = 2;
    return;
}
var test = new fn();
console.log(test); // {a:2}
  • 如果构造函数显式地使用return语句返回一个对象,那么调用表达式的值就是这个对象
var obj = {a:1};
function fn(){
    this.a = 2;
    return obj;
}
var test = new fn();
console.log(test); // {a:1}

[👣注意]

尽管有时候构造函数看起来像一个方法调用,它依然会使用这个新对象作为this。 也就是说,在表达式new o.m()中,this并不是 o

var o = {
    m: function(){
        return this;
    }
}
var obj = new o.m();
console.log(obj,obj === o);             // {} false
console.log(obj.constructor === o.m);   // true

# 严格模式下

  • 严格模式下,独立调用的函数的this指向undefined
function fn(){
    'use strict';
    console.log(this);//undefined
}
fn();

function fn(){
    console.log(this);//window
}
fn();
  • 在非严格模式下,使用函数的call()或apply()方法时,null或undefined值会被转换为全局对象。而在严格模式下,函数的this值始终是指定的值
var ball = '🏀';
function showBall(){
    console.log(this.ball);
}
showBall.call(null); // 🏀

var ball = '🏀';
function showBall(){
    'use strict';
    console.log(this.ball);
}
showBall.call(null); // TypeError: Cannot read property 'ball' of null

[提示]

  • this的四种绑定规则: 【默认绑定、隐式绑定、显式绑定、new绑定】
  • 对应的函数调用方式: 【独立调用、方法调用、间接调用、构造函数调用】

# 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 

// 在下面语句中,显式绑定call(obj2)和隐式绑定obj1.foo同时出现
// 最终结果为3,说明被绑定到了obj2中
obj1.foo.call( obj2 ); // 3  this指向obj2

obj2.foo.call( obj1 ); // 2  this指向obj1

# new绑定🆚隐式绑定

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

// 在下列代码中,隐式绑定obj1.foo和new绑定同时出现。
// 最终obj1.a结果是2,而bar.a结果是4,说明this被绑定在bar上
var bar = new obj1.foo( 4 );
console.log( obj1.a ); // 2
console.log( bar.a );  // 4

# new绑定🆚显式绑定

function foo(something) {
    this.a = something;
}
var obj1 = {};
//先将obj1绑定到foo函数中,此时this值为obj1
var bar = foo.bind( obj1 );
bar( 2 );
console.log(obj1.a); // 2

//通过new绑定,此时this值为baz
var baz = new bar( 3 );
console.log( obj1.a ); // 2
//说明使用new绑定时,在bar函数内,无论this指向obj1有没有生效,最终this都指向新创建的对象baz
console.log( baz.a ); // 3

# 判断顺序

  • 1、是否是new绑定?如果是,this绑定的是新创建的对象
var bar = new foo();
  • 2、是否是显式绑定?如果是,this绑定的是指定的对象
var bar = foo.call(obj2);
  • 3、是否是隐式绑定?如果是,this绑定的是属于的对象
var bar = obj1.foo(); 
  • 4、如果都不是,则使用默认绑定
var bar = foo();

# 箭头函数下的this

箭头函数下的this有些特殊,函数体内的this对象就是定义时的所在的对象,而不是使用时所在的对象。

例子:

function Timer(){
    this.s1 = 0;
    this.s2 = 0;
    setInterval(()=> this.s1++,1000)
    setInterval(function(){
        this.s2 ++
    },1000)
}

let timer = new Timer()
setTimeout(() => console.log(timer.s1),3100) //  3
setTimeout(() => console.log(timer.s2),3100) //  0

Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数,箭头函数的this指向函数定义时所在的作用域(即Timer函数),普通函数的this指向调用时所在的作用于(即全局对象window)。所以,3100ms之后,timer.s1加到了3,而timer.s2一次都没有更新。

Last Updated: 10/25/2022, 10:02:50 PM