this与this绑定

208 阅读4分钟

this

定义

thisjs 中一个固有的关键字。为什么需要它呢?是因为它可以用来隐式“传递”一个对象引用,而不需要显式地传递上下文对象。不过它指向什么完全取决于函数在哪里被调用 ,而不是声明的位置。

function foo() {
    var a = 2;
    this.bar();
}

function bar() {
    console.log(this.a);
}
foo(); // ReferenceError: a is not defined

那么这个this到底引用的是什么,或者说真正绑定了什么?

this绑定

当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的其中一个属性,会在函数执行的过程中用到。 只有仔细分析调用位置才能回答这个问题,所以在分析前最好先弄清楚执行上下文与执行栈。

默认绑定

function foo() { console.log( this.a ); }
var a = 2; foo(); // 2 (严格模式下undefined)

foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。

调用位置是否有上下文对象

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

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

存在的问题:绑定丢失

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

  • 情景1
var a = 4
function foo() { 
    console.log( this.a );
}
var obj = {
    a: 2,
    foo: foo
};
var bar = obj.foo
bar()//非严格模式下为4

bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

  • 情景2:回调函数
function foo() { 
	console.log( this.a ); 
}
function doFoo(fn) { // fn 其实引用的是foo
    fn(); // <-- 调用位置!
}
var obj = { a: 2, foo: foo };
var a = "oops, global"; // a 是全局对象的属性
doFoo( obj.foo ); // "oops, global"

赋值也是一种隐式传递。

  • 情景3:库函数

也是一样的结果

setTimeout( obj.foo, 100 ); // "oops, global"

作为一个构造函数

定义

this被绑定到正在构造的新对象。

步骤

通过构造函数创建一个对象执行如下:

  1. 创建新对象
  2. this指向这个对象
  3. 给对象赋值(属性、方法)
  4. 返回this

所以this就是指向创建的这个对象上。

显式绑定之软绑定---callapply

定义

在某个对象上强制调用函数,但是无法解决绑定丢失的问题,

call 和 apply 的区别

  • 相同点:第一个参数都是一个对象,它们会把这个对象绑定到this,接着在调用函数时指定这个 this。因为你可以直接指定 this 的绑定对象,因此我 们称之为显式绑定。

  • 不同点:call()方法接受的是参数列表,而apply()方法接受的是一个参数数组

但是软绑定不能解决绑定丢失的问题.

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

显式绑定的变种之硬绑定---bind

定义

强制把 foothis 绑定到了 obj。无论之后如何调用函数 bar,它 总会手动在 obj 上调用 foo。这种方式可以防止绑定丢失。

硬绑定可以解决绑定丢失的问题。

function foo() {
    console.log(this.a);
}
var obj = {
    a: 2
};
var obj1 = {
    a: 3
};
//包裹函数
var bar = function () {
    foo.call(obj);
};
bar(); // 2 
setTimeout( bar, 100 ); // 2  
bar.call(obj1); //2
// 硬绑定的 bar 不可能再修改它的 this

其原理就是利用闭包创建一个包裹函数,传入所有的参数并返回接收到的所有值,则形成一个硬绑定。

function foo(something) {
    console.log(this.a, something);
    return this.a + something;
}
var obj = {
    a: 2
};
// 包裹函数
var bar = function () {
    return foo.apply(obj, arguments);
};
console.log(bar(3))

es5中bind函数,用来表示这种绑定。

function foo(something) {
    console.log(this.a, something);
    return this.a + something;
}
var obj = {
    a: 2
};
var obj1 = {
    a: 3
};
var bar = foo.bind(obj);
bar = foo.bind(obj)
console.log(bar(8));

总结

this指向

如果要判断一个函数的this绑定,就需要找到这个函数的直接调用位置。然后可以顺序按照下面四条规则来判断this的绑定对象。

  1. new调用:绑定到新创建的对象
  2. callapplybind调用:绑定到指定的对象
  3. 由上下文对象调用:绑定到上下文对象
  4. 默认:全局对象