this的四种绑定规则实例讲解
this的四种绑定规则 作为普通函数调用默认绑定 事件处理程序中使用this 严格模式下 作为对象的方法调用隐式绑定 对象属性引用链中只有最顶层影响调用位置 隐式丢失 Functionprototypecall 或 Functionprototypeapply 调用显式绑定 定时器内含有this 硬绑定 API调用的上下文 构造器调用new绑定
this的四种绑定规则
JavaScript 的 this 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。
1.作为普通函数调用–默认绑定
当函数作为普通函数调用,也就是不作为某个对象的属性调用时,函数中的this指向全局对象window。
window.name = 'globalName'; var getName = function(){ return this.name; }; console.log( getName() ); // globalName
window.name = 'globalName'; var myObject = { name: 'sven', getName: function(){ return this.name; } }; var getName = myObject.getName; //将getName()方法传递给变量getName,此时getName()方法还未执行。 console.log( getName() ); // globalName 当执行的时候,getName()是作为普通函数调用的。函数的执行环节是window对象。 console.log(myObject.getName()); //sven。 执行环境是myObject对象。
事件处理程序中使用this
var btn = document.getElementById("btn"); btn.onclick = function eve(){ alert(this.id); //btn function callback(){ alert(this); } callback(); //window }
以上代码,事件处理程序中eve()中的this,指向它的调用者,也就是btn。但是回调函数callback()是在事件处理程序内部执行的,它并没有被任何对象调用,其this指向window。
如果想让callback的this指向btn,可以用一个变量保存对btn的引用:
btn.onclick = function eve(){ var that = this; function callback(){ alert(that.id); } callback(); //btn }
严格模式下
如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定到 undefined:
function foo() { "use strict"; console.log( this.a ); } var a = 2; foo(); // Uncaught TypeError: Cannot read property 'a' of undefined
2.作为对象的方法调用–隐式绑定
当函数作为对象的方法被调用时,this 指向该对象:
var obj = { a: 1, getA: function(){ alert ( this === obj ); // true alert ( this.a ); // 1 } }; obj.getA();
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2 调用foo()时,this指向obj
以上代码,当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。调用 foo() 时 this 被绑定到 obj,因此 this.a 和 obj.a 是一样的。
对象属性引用链中只有最顶层影响调用位置。
function foo() { console.log( this.a ); } var obj2 = { a: 42, foo: foo }; var obj1 = { a: 2, obj2: obj2 }; obj1.obj2.foo(); // 42 this指向obj2
隐式丢失
一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上(严格模式)。
var a = "oops, global"; // a 是全局对象的属性 function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var bar = obj.foo; // 函数别名! bar(); // "oops, global"
var bar = obj.foo; 相当于: var bar = function{ console.log( this.a ); } //将obj的foo属性传递给变量bar,此时foo()函数并没有执行,执行将函数体给到变量bar。
以上代码,虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的bar() 其实是一个不带任何修饰的函数调用,应用了默认绑定。
3.Function.prototype.call 或 Function.prototype.apply 调用–显式绑定
隐式绑定时,必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把 this 间接(隐式)绑定到这个对象上。
如果不想在对象内部包含函数引用,而想在某个对象上强制调用函数,可以使用call()和apply()方法。它们会把这个对象绑定到this,接着在调用函数时指定这个 this。这种方式称为显示绑定。
var obj1 = { name: 'sven', getName: function(){ return this.name; } }; var obj2 = { name: 'anne' }; console.log( obj1.getName() ); // sven console.log( obj1.getName.call( obj2 ) ); //anne ,call()方法改变this指向,使getName中的this指向obj2.
function foo() { console.log( this.a ); } var obj = {a:2}; foo.call( obj ); // 2 // 通过 foo.call(),在调用 foo 时强制把它的 this 绑定到 obj 上。
定时器内含有this
function foo() { setTimeout(function(){ console.log( this.a ); },3000) } var a = 3; var obj = { a:2}; foo.call( obj ); // 3
以上代码中call()无法改变this的指向。3s后才调用定时器,但是定时器是window对象的方法,所以this还是指向定时器的调用者window。
解决方式1:
function foo() { var that = obj; setTimeout(function(){ console.log( that.a ); },3000) } var a = 3; var obj = {a:2}; foo(); // 2
解决方式2:
function foo() { setTimeout(function(){ console.log( this.a ); }.bind(obj),3000) } var a = 3; var obj = {a:2}; foo(); // 2
解决方式3:
function foo() { setTimeout(() =>{ console.log( this.a ); },3000) } var a = 3; var obj = {a:2}; foo.call(obj); // 2
硬绑定
function foo() { console.log( this.a ); } var obj = { a:2 }; var bar = function() { foo.call( obj ); }; bar(); // 2
var bar = function() { foo.call( obj ); }; 相当于: var bar = function{ console.log( obj.a ); } //使用call()方法时,foo内部的this就已经执行了obj。
ES5中提供了内置的bind()方法来实现硬绑定,
bind(..) 会返回一个硬编码的新函数,它会把参数设置为 this 的上下文。
function foo() { console.log( this.a ); } var obj = { a:2 }; var bar = foo.bind( obj ); bar(); // 2
API调用的上下文
JavaScript 语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”(context),其作用和 bind(..) 一样,确保回调函数使用指定的 this。
function foo(el) { console.log( el, this.id ); } var obj = { id: "awesome" }; [1, 2, 3].forEach( foo, obj ); // 调用 foo(..) 时把 this 绑定到 obj // 1 awesome 2 awesome 3 awesome
这些函数实际上就是通过 call(..) 或者 apply(..) 实现了显式绑定。
4.构造器调用–new绑定
function foo(a) { this.a = a; } var bar = new foo(2); console.log( bar.a ); // 2
以上代码,使用 new 来调用 foo(..) 时,会构造一个新对象并把它绑定到 foo(..) 调用中的 this上。
var MyClass = function(){ this.name = 'sven'; }; var obj = new MyClass(); alert ( obj.name ); // sven
如果构造函数返回了一个引用类型的值,最终的运算结果就会返回这个值:
var MyClass = function(){ this.name = 'sven'; return { // 显式地返回一个对象 name: 'anne' } }; var obj = new MyClass(); alert ( obj.name ); //:anne