土豆博客

JavaScript中的this绑定解析

this的绑定规则总共有下面5种。

  • 1、默认绑定(严格/非严格模式)
  • 2、隐式绑定
  • 3、显式绑定
  • 4、new绑定
  • 5、箭头函数绑定
# 一、默认绑定
  • 非严格模式下,函数独立调用,默认绑定到全局对象,即this指向window
var num = 10;
function demo() {
	console.log(this.num); 
}
demo(); //此时this指向window,windwo.num = 10
  • 严格模式下,this会绑定到undefined。只有函数运行在非严格模式下,默认绑定才能绑定到全局对象
  var num = 10;
  function demo() { // 运行在严格模式下,this会绑定到undefined
  	"use strict";
  	console.log(this.num);
  }
  demo(); //控制台报错 Uncaught TypeError: Cannot read property 'num' of undefined
  • 但是在严格模式下调用函数则不影响默认绑定。
var num = 10;
function demo() {
	console.log(this.num); 
}
(function () {      // 严格模式下调用函数则不影响默认绑定
	"use strict";
	demo(); //此时this仍指向全局变量window,windwo.num = 10
})()
# 二、隐式绑定

当函数引用有上下文对象时,隐式绑定规则会把函数中的this绑定到这个上下文对象。对象属性引用链中只有上一层或者说最后一层在调用中起作用。

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

var obj = {
    a: 2,
    foo: foo
};

obj.foo(); // 2

特殊情况隐式丢失

被隐式绑定的函数特定情况下会丢失绑定对象,应用默认绑定,把this绑定到全局对象或者undefined上。

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

var obj = {
    a: 2,
    foo: foo
};

var bar = obj.foo; 
// 虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身。
//故调用bar()是一个不带任何修饰的函数调用,对应默认绑定,this指向window

var a = "这是window下的a"; // a是全局对象的属性

bar(); //输出:这是window下的a

参数传递就是一种隐式赋值,传入函数时也会被隐式赋值。回调函数丢失this绑定是非常常见的。

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

function doFoo(fn) {
    // fn其实引用的是foo
    fn(); 
}

var obj = {
    a: 2,
    foo: foo
};

var a = "这是window下的a"; // a是全局对象的属性

doFoo( obj.foo ); //输出:这是window下的a
# 三、显式绑定

通过call(..) 或者 apply(..)方法。第一个参数是一个对象,在调用函数时将这个对象绑定到this。因为直接指定this的绑定对象,称之为显示绑定。

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

var obj = {
    a: 2
};

foo.call( obj ); // 2  调用foo时强制把foo的this绑定到obj上

注意:显示绑定无法解决丢失绑定问题

解决方案:

1.硬绑定

创建函数bar(),并在它的内部手动调用foo.call(obj),强制把foo的this绑定到了obj。

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

var obj = {
    a: 2
};

var bar = function() {
    foo.call( obj );
};

bar(); // 2
setTimeout( bar, 100 ); // 2

// 硬绑定的bar不可能再修改它的this
bar.call( window ); // 2
# 四、new绑定
  • 在JS中,构造函数只是使用new操作符时被调用的普通函数,他们不属于某个类,也不会实例化一个类。
  • 包括内置对象函数(比如Number(..))在内的所有函数都可以用new来调用,这种函数调用被称为构造函数调用。
  • 实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。

使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

  • 1、创建(或者说构造)一个新对象。
  • 2、这个新对象会被执行[[Prototype]]连接。
  • 3、这个新对象会绑定到函数调用的this
  • 4、如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

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

function foo(a) {
    this.a = a;
}

var bar = new foo(2); // bar和foo(..)调用中的this进行绑定
console.log( bar.a ); // 2
# 五、箭头函数绑定

ES6新增一种特殊函数类型:箭头函数,箭头函数无法使用上述四条规则,而是根据外层(函数或者全局)作用域(词法作用域)来决定this。

  • foo()内部创建的箭头函数会捕获调用时foo()的this。由于foo()的this绑定到obj1bar(引用箭头函数)的this也会绑定到obj1箭头函数的绑定无法被修改(new也不行)。
function foo() {
    // 返回一个箭头函数
    return (a) => {
        // this继承自foo()
        console.log( this.a );
    };
}

var obj1 = {
    a: 2
};

var obj2 = {
    a: 3
}

var bar = foo.call( obj1 );
bar.call( obj2 ); // 2,不是3!

ES6之前和箭头函数类似的模式,采用的是词法作用域取代了传统的this机制。

function foo() {
    var self = this; // lexical capture of this
    setTimeout( function() {
        console.log( self.a ); // self只是继承了foo()函数的this绑定
    }, 100 );
}

var obj = {
    a: 2
};

foo.call(obj); // 2

转载自:https://segmentfault.com/a/1190000017193677

code
top

扫码添加,一起进步

wechat-code

为了保障最佳预览体验,博客已不支持IE浏览器的访问,邀请您使用以下现代高级浏览器。

谷歌浏览器(推荐) 火狐浏览器

注:如果你使用的是360,QQ等双核浏览器,请开启极速模式