JS中this的指向
本文目录
- JS中this的指向
- 全局上下文(Global Context)
- 函数上下文(Function Context)
- 普通函数调用
- 作为对象的方法调用
- 构造函数调用
- 箭头函数
- 回调函数
- 事件处理器上下文(Event Handler Context)
- 通过 call,apply,bind 改变this指向
-
this永远指向一个对象
-
this的指向完全取决于函数调用的位置
-
JavaScript支持运行环境动态切换,this的指向是动态的
全局上下文(Global Context)
在全局执行环境中(在任何函数体外部),this都是指向全局对象,在浏览器中,window对象即是全局对象。
// 在浏览器中,window 对象同时也是全局对象:
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "MDN";
console.log(window.b) // "MDN"
console.log(b) // "MDN"
console.log(this.b) // "MDN"
函数上下文(Function Context)
在函数内部,this的值取决于函数的调用方式。这包括普通函数调用、作为对象的方法调用、构造函数调用、以及通过call、apply或bind方法调用。
普通函数调用
如果一个函数不是作为对象的方法被调用,而是作为普通函数被调用,那么 this 通常指向全局对象(在严格模式下,this 是 undefined)
var name = 'window';
var doSth = function(){
console.log(this.name);
}
doSth(); // 'window'
let 不给顶层对象添加属性(浏览器为Window)
let name2 = 'window2';
let doSth2 = function(){
console.log(this === window);
console.log(this.name2);
}
doSth2() // true, undefined
严格模式下,正常函数中的 this 行为不同,如未定义输出undefined
'use strict'
var name3 = 'window3';
var doSth3 = function(){
console.log(typeof this === 'undefined');
console.log(this.name3);
}
doSth3();
window.doSth3();
作为对象的方法调用
如果一个函数作为对象的方法被调用,this 就指向这个对象
var name = 'window';
var doSth = function(){
console.log(this.name);
}
var student = {
name: 'dog',
doSth: doSth,
other: {
name: 'other',
doSth: doSth,
}
}
student.doSth(); // 'dog'
// call like this
student.doSth.call(student);
student.other.doSth(); // 'other'
// call like this
student.other.doSth.call(student.other);
将对象中的函数分配给变量,实际上又是一个普通函数,所以使用普通函数的规则(默认绑定)。
var studentDoSth = student.doSth;
studentDoSth(); // 'window'
// call like this :
studentDoSth.call(undefined);
当 o.f()
被调用时,函数内的 this
将绑定到 o
对象
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); // 37
也可以先定义函数,然后再将其附属到o.f
,这样做的结果是一样的
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // 37
原型链中的 this
对于在对象原型链上某处定义的方法,同样的概念也适用。如果该方法存在于一个对象的原型链上,那么 this 指向的是调用这个方法的对象
var o = {
f: function() {
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
构造函数调用
当一个函数用作构造函数时(使用new关键字),this 指向新创建的对象。
function Foo() {
this.name = 'Test';
console.log(this);
}
// 使用new关键字调用
var obj = new Foo(); // this指向新创建的obj对象
console.log(obj.name); // 输出 'Test'
// 不使用new关键字调用
Foo(); // this指向全局对象(非严格模式)或undefined(严格模式)
console.log(window.name); // 在非严格模式下,输出 'Test'
- 如果构造函数没有返回对象(也就是没有返回值或者返回非对象),那么new表达式的结果就是新创建并且被this关键字指向的对象。
- 如果构造函数返回一个对象,那么new表达式的结果就是这个返回的对象。
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); // logs 37
function C2(){
this.a = 37;
return {a:38};
}
o = new C2();
console.log(o.a); // logs 38
箭头函数
箭头函数上下文(Arrow Function Context)
箭头函数不绑定this,它会捕获其所在(即定义的位置)的上下文的this值作为自己的this值。
this 被设置为它被创建时的环境
var name = 'window';
var student = {
name: 'dog',
doSth: function(){
// var self = this;
var arrowDoSth = () => {
// console.log(self.name);
console.log(this.name);
}
arrowDoSth();
},
arrowDoSth2: () => {
console.log(this.name);
}
}
student.doSth(); // 'dog'
student.arrowDoSth2(); // 'window'
不能通过call、apply、bind来绑定箭头函数的this(它本身没有this)
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
// 作为对象的一个方法调用
var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true
// 尝试使用 call 来设定 this
console.log(foo.call(obj) === globalObject); // true
// 尝试使用 bind 来设定 this
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
call、apply、bind可以绑定缓存箭头函数上面的普通函数的this
var student = {
name: 'dog',
doSth: function(){
console.log(this.name);
return () => {
console.log('arrowFn:', this.name);
}
}
}
var person = {
name: 'person',
}
student.doSth().call(person); // 'dog' 'arrowFn:' 'dog'
student.doSth.call(person)(); // 'person' 'arrowFn:' 'person'
回调函数
回调函数中 this 的指向,决定于执行回调函数 时的执行上下文环境
(function(){
console.log(this); // window
})();
setTimeout(() => {
console.log(this); // window
}, 0);
setTimeout(function(){
console.log(this); // window
}, 0);
组合使用
第一个setTimeout,执行obj.getage 之后,相当于setTimeout的回调是一个匿名函数,执行的时候,函数内部未设置this的指向。相当于是普通函数调用。所以this默认指向window,所以结果是undefined。
第二个setTimeout,传给setTimeout的也是一个匿名回调函数,执行匿名函数,执行到 obj.getage() 的时候,getage函数里的this,指向的就是obj了,所以能打印出10。遵循 谁调用产生 this指针的函数,this就指向谁的规则
var obj = {
age:10,
getage:function(){
console.log(this.age)
}
}
setTimeout(obj.getage,1000) // undefined
setTimeout(function(){
obj.getage() // 10
},1000)
let obj={
a:222,
fn:function(){
setTimeout(()=>{console.log(this.a)});
}
};
obj.fn(); // 222
var name = 'window';
var A = {
name: 'A',
sayHello: () => {
console.log(this.name)
}
}
A.sayHello(); // 输出的是window,根据刚才讲的规则就可以判断
// 那如何改造成永远绑定A呢:
var name = 'window';
var A = {
name: 'A',
sayHello: function(){
var s = () => console.log(this.name)
return s//返回箭头函数s
}
}
var sayHello = A.sayHello();
sayHello();// 输出A
let obj = {
value: 'Hello, World',
print: function() {
setTimeout(function() {
console.log(this.value);
}, 1000);
}
};
obj.print(); // 输出:undefined(严格模式)或者一个全局value(非严格模式)
let obj = {
value: 'Hello, World',
print: function() {
setTimeout(() => {
console.log(this.value); // 输出:Hello, World
}, 1000);
}
};
// 或者
let obj = {
value: 'Hello, World',
print: function() {
setTimeout(function() {
console.log(this.value);
}.bind(this), 1000);
}
};
事件处理器上下文(Event Handler Context)
在DOM事件处理器中,this通常指向触发事件的元素。
在事件处理函数(或者说事件监听器)中,this通常指向触发事件的 DOM 元素。第一个事件监听器中,this打印出来的是按钮元素本身。
bluify函数作为事件处理函数使用,因此在这个函数中,this指向触发点击事件的按钮元素。
let btn = document.getElementById('btn');
btn.addEventListener('click', function () {
console.log(this); // button
});
btn.addEventListener('click', bluify, false);
function bluify(e) {
console.log(this === e.currentTarget); // 总是 true
// 当 currentTarget 和 target 是同一个对象时为 true
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
通过 call,apply,bind 改变this指向
-
call(a, b, c
方法接收三个参数,第一个是this指向,第二个,三个是传递给函数的实参,可以是数字,字符串,数组等类型的数据类型都可以 -
apply(a, [b])
和call基本上一致,唯一区别在于传参方式,apply把需要传递给fn()的参数放到一个数组(或者类数组)中传递进去,虽然写的是一个数组,但是也相当于给fn()一个个的传递 -
bind(a, b, c
:语法和call一模一样,区别在于立即执行还是等待执行
fn.bind(第一个参数是this的指向)
fn.call(第一个参数是this的指向,parma2,param3...)
fn.apply(第一个参数是this的指向,[parma2,parma3..])
//call()方法:改变fn中的this,并且把fn立即执行
fn.call(obj, 1, 2);
//bind()方法:改变fn中的this,fn并不执行
fn.bind(obj, 1, 2);
var obj = {
name:'111',
getName:function(){
console.log(this.name)
}
};
var otherObj = {
name:'222',
};
var name = '333';
obj.getName(); // 111
obj.getName.call(); // 333
obj.getName.call(otherObj); // 222
obj.getName.apply(); // 333
obj.getName.apply(otherObj); // 222
obj.getName.bind(this)(); // 333
obj.getName.bind(otherObj)();// 222