前言
this
的值 当前执行上下文(global
、function
或eval
)的一个属性,在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。
this
的值是由不同的执行方式决定。接下来我们依次看一下日常开发中不同情况下使用 this
之间的区别吧。
普通函数
普通函数的使用方式分为两种情况,一种是直接在全局上下文使用,另一个是作为一个对象的属性或者方式使用。
全局上下文
在函数内部,this
的值取决于函数被调用的方式。
下面的代码不在严格模式下,且 this
的值没有在调用时设置的,所以 this
的值默认指向全局对象,浏览器中就是 window
。
var name = 'mike';
function getContext() {
// "use strict"; 在严格模式下,如果进入执行环境时没有设置 this 的值,其会保持为 undefined
// 如 window.getContext()
console.log(this);
}
getContext(); // Window { ..., name: "mike" }
对象方法/属性
当函数作为对象里的方法被调用时,this
被设置为调用该函数的对象。
下面这个示例中 this
被设置为 user
对象。
const user = {
name: 'mike',
getName: function() {
return this.name;
}
};
console.log(user.getName()); // mike
构造函数 / 类
构造函数
- 当一个函数作为构造函数时(使用
new
关键字),它的this
被绑定到正在构造的新对象
虽然构造函数返回的默认值是 this
所指的那个对象,但仍可以手动返回其他的对象(如果返回值不是一个对象,则返回 this
对象)。
/*
* 构造函数这样工作:
*
* function MyConstructor(){
* // 函数实体写在这里
* // 根据需要在this上创建属性,然后赋值给它们,比如:
* this.name = "Tom";
* // 等等...
*
* // 如果函数具有返回对象的 return 语句,
* // 则该对象将是 new 表达式的结果。
* // 否则,表达式的结果是当前绑定到 this 的对象。
* }
*/
function Man() {
this.name = 'Mike';
}
const man = new Man();
console.log(man.name); // Mike
function Woman(){
this.name = 'Lucy';
return { name: 'Lisa' };
}
const woman = new Woman();
console.log(woman.name); // Lisa
- 箭头函数不能用作构造器,和
new
一起用会抛出错误。
const Foo = () => {};
const foo = new Foo(); // TypeError: Foo is not a constructor
类
this
在类中的表现与在函数中类似,因为类本质上也是函数,但也有一些区别和注意事项。
在类的构造函数中,this
是一个常规对象。类中所有非静态的方法都会被添加到 this 的原型中:
class User {
constructor() {
const proto = Object.getPrototypeOf(this);
console.log(Object.getOwnPropertyNames(proto));
}
name() {}
age() {}
static gender() {}
}
new User(); // ['constructor', 'name', 'age']
注意:静态方法不是 this
的属性,它们只是类自身的属性。
- 和其他普通函数一样,方法中的
this
值取决于它们如何被调用。有时,改写这个行为,让类中的this
值总是指向这个类实例会很有用。
class Tom {
constructor() {
this.sayBye = this.sayBye.bind(this);
}
sayHi() {
console.log(`Hello from ${this.name}`);
}
sayBye() {
console.log(`Bye from ${this.name}`);
}
get name() {
return 'Tom';
}
}
class Lucy {
get name() {
return 'Lucy';
}
}
const tom = new Tom();
const lucy = new Lucy();
// this 的值取决于他们的调用者
tom.sayHi(); // Hello from Tom
lucy.sayHi = tom.sayHi;
lucy.sayHi(); // Hello from Lucy
lucy.sayBye = tom.sayBye;
lucy.sayBye(); // Bye from Tom
改变 this
call/apply/bind
call/apply/bind
这几种都可以改变 this
的值,其区别在于入参和执行时机的不同。我们重点来看一下关于改变 this
的用法。
非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装(window/global 对象)
// 对象可以作为 bind 或 apply 的第一个参数传递,并且该参数将绑定到该对象。
var lucy = { name: 'Lucy' };
// 声明一个变量,并将该变量作为全局对象 window 的属性。
var name = 'Mike';
var tom = {
name: 'Tom',
getName: function() {
return this.name; // this 的值取决于函数被调用的方式
}
}
var getName = tom.getName
getName(); // 'Mike' 因为在这个函数中 this 没有被设定,所以它默认为 全局 / window 对象
tom.getName(); // 'Tom' 因为是对象调用方法,this 值为调用的对象
getName.call(lucy); // 'Lucy' 因为函数中的 this 被设置为 lucy
getName.apply(lucy); // 'Lucy' 因为函数中的 this 被设置为 lucy
var getLucyName = getName.bind(lucy) // 'Lucy' 因为函数中的 this 被设置为 lucy
var getTomName = getLucyName.bind(tom) // 'Lucy' bind 只生效一次!
var regainTomName = getName.bind(tom) // 'Tom' 因为函数中的 this 被设置为 tom
getLucyName()
getTomName()
regainTomName()
注意:箭头函数没有自己的 this
指针,通过 call/apply/bind
方法调用一个函数时,只能传递参数,他们的第一个参数会被忽略,不能绑定 this
,第一个参数(thisArg
)应该设置为 null
。
setTimeout/setInterval
在默认情况下,使用 window.setTimeout()
时,this 关键字会指向 window/global
对象。如果要让 this
正确赋值时,需要显式地把 this
绑定到回调函数。
function User() {
this.age = Math.ceil(Math.random() * 100);
}
User.prototype.getAge = function() {
window.setTimeout(this.showAge.bind(this), 1000); // this 关键字会指向 window/global 对象
};
User.prototype.showAge = function() {
console.log(`已经 ${this.age} 岁了`);
};
var Tom = new User();
Tom.getAge(); // 一秒钟后, 调用 'showAge' 方法
箭头函数
箭头函数不提供自身的 this
绑定(this
的值将保持为闭合词法上下文的值)。
箭头函数不会创建自己的 this
, 它只会从自己的作用域链的上一层继承 this
。
function Man() {
this.age = 0;
setInterval(() => {
this.age++; // this 的值为 man 实例
}, 1000);
}
const man = new Man();
setTimeout(() => {
console.log(man.age) // 3
}, 3000)
事件监听
DOM 事件处理函数
当使用 addEventListener()
为一个元素注册事件的时候,句柄里的 this
值是该元素的引用。其与传递给句柄的 event
参数的 currentTarget
属性的值一样。
// 被调用时,将关联的元素变成蓝色
function bluify(e) {
console.log(this === e.currentTarget); // 总是 true
// 当 currentTarget 和 target 是同一个对象时为 true
// currentTarget 是当前事件监听的目标元素,target 是事件的触发元素
console.log(this === e.target);
this.style.backgroundColor = '#a5d9f3';
}
// 获取文档中的所有元素的列表
let elements = document.getElementsByTagName('*');
// 将 bluify 作为元素的点击监听函数,当元素被点击时,就会变成蓝色
for(let i = 0 ; i < elements.length ; i++) {
elements[i].addEventListener('click', bluify, false);
}
内联事件处理函数
当代码被内联 (on-event 处理函数,类似 onclick
,onkeypress
,onfocus
)调用时,它的 this
的值为监听器所在的 **DOM**
元素
<button onclick="alert(this.tagName.toLowerCase());">
Show this
</button>