this 的值取决于代码的执行环境(execution context),this 存在于函数中,在声明时不会被绑定,只在调用时被绑定。只要记住,谁调用,绑定谁,就不会糊涂。
1.默认绑定
函数直接在全局环境下执行,严格模式下 this 为 undefined,非严格模式下 this 为 window。
function f(){
console.log(this);
}
f(); //window
‘use strict‘
function f(){
console.log(this);
}
f(); //undefined
注意:这里仅指在函数内部的 this,在全局环境下,this 都指向 window,不管是不是严格模式
"use strict";
console.log(this === window); //true
2.隐式绑定
函数存在对象中,用该对象调用该函数
var obj = {
f(){
console.log(this)
}
};
obj.f(); //obj
这里 this 指向,并不是因为函数 f 声明在 obj 内,而是因为 obj 调用了 f(),如果函数 f 声明在全局环境里也是可以的
var f = function() {
console.log(this)
};
var obj = {
f: f
};
obj.f();
函数是在堆空间中存放,然后把地址存放在 obj 对象的 f 属性下,原始的对象以字典结构保存,具体的可以看这篇博文:JavaScript 的 this 原理
如果你直接把函数赋值给对象的属性,则 this 也是指向该对象
document.onclick = function() {
console.log(this)
}
这相当于给 document 对象的 onclick 属性赋值,this 自然就指向 document 对象。
如果函数暴露给了全局,则 this 指向全局
var obj = {
f: function (){
return function (){
console.log(this)
}
}
};
obj.f()(); //window,严格模式为undefined
如果函数为私有函数,则也是指向全局
var obj = {
f: function (){
console.log(this); //obj
(function (){
console.log(this); //window
})();
}
};
obj.f();
对于这种函数内部的私有函数,没办法定义它的执行环境,所以引擎内部把他扔给了 window 对象,所以 this 指向 window。
3.强绑定
这个很好记,就是用 call,apply,bind 函数改变 this 的指向,这个是绑定谁就指向谁。这里需要注意的是 bind 函数,因为他的返回值是一个函数,这个函数的 this 是不可被修改的。
function f(){
console.log(this)
}
var obj1 = {name:‘obj1‘};
var obj2 = {name:‘obj2‘};
var bf = f.bind(obj1);
bf.call(obj2); //output:{name: "obj1"}
4.构造函数中的 this
构造函数如果和普通函数一样使用,那没什么区别
var x = 0;
function A(x) {
this.x = x;
}
A(1);
console.log(x) //output: 1,这里的 this 指向 window
因为 new 操作符,绑定了 this,MDN上面有 new 操作符做了哪些事:
new
关键字会进行如下的操作:
- 创建一个空的简单JavaScript对象(即
**{}**
);- 链接该对象(即设置该对象的构造函数)到另一个对象 ;(这里指实现原型链,把对象的原型 _proto_ 指向构造函数的 prototype )
- 将步骤1新创建的对象作为
**this**
的上下文 ;(这里的上下文就是指执行环境)- 如果该函数没有返回对象,则返回
**this**
。
function A(x) {
this.x = x;
this.f = function (){
console.log(this)
}
}
var a = new A(1);
a.f(); //output:
5.箭头函数与 this
首先,箭头函数是没有 this 的,它的 this 来源于它声明时的外部环境。
阮一峰老师的《ECMAScript 6 入门》这本书里面有个例子挺好的,我就直接拿来用了
function foo() {
return () => {
return () => {
return () => {
console.log(‘id:‘, this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1
为什么执行一次 call 函数后,后面都不会改变 this 的指向了呢?
让我们用 Babel 把 foo 函数转换成 ES5 的写法后一切就会明了了:
"use strict";
function foo() {
var _this = this;
return function () {
return function () {
return function () {
console.log(‘id:‘, _this.id);
};
};
};
}
只要一执行 foo(),this 的值就被固定了,_this 中存的是绑定的对象的地址。
在学习 Vue 时,经常能看见这样的提醒:
注意,不应该使用箭头函数来定义 method 函数 (例如
plus: () => this.a++
)。理由是箭头函数绑定了父级作用域的上下文,所以this
将不会按照期望指向 Vue 实例,this.a
将是 undefined。
不止是 methods ,computed、watch 等函数也是一样,看一下如果用箭头函数会怎样把:
var a = 3;
var vm = new Vue({
data: {
a: 1
},
methods: {
plus: () => {
this.a++
}
}
});
vm.plus();
console.log(vm.a,a); //output: 1 4
这里,this 绑定的是 window,如果不用箭头函数,当然是 vm。
6.回调函数中的 this
其实,回调函数的 this 和主函数的调用对象没有什么关系,主要看主函数的内部怎么去处理这个回调函数。
function foo(f) {
console.log(this);
f();
}
var obj = {name: ‘obj‘};
foo.call(obj, function() {
console.log(this)
})
当然,讨论这个是没有意义的,主要是要记住一些特殊的异步函数的 callback 内部的 this 指向,如:
setTimeout 函数的 callback 内部的 this 永远指向 window,无论在何处执行 setTimeout ,当然,这不包括回调为箭头函数。还有 addEventListener 的 callback 中的 this 指向绑定事件的 DOM 对象 。
如果回调函数是箭头函数呢?
同样的你把箭头函数转换成 ES5 语法代码,就很明了。
document.addEventListener(‘click‘,()=>{
console.log(this)
},false); //#document
Babel 转换:
"use strict";
var _this = void 0;
document.addEventListener(‘click‘, function () {
console.log(_this);
}, false); //undefined
当需要函数作为参数时,使用箭头函数一定要注意 this ,箭头函数绑定了父级作用域的上下文。比方说上面 Vue 的那个例子,如果我一定要用箭头函数可以吗?我觉得是可以的:
var vm = new Vue({
data: {
a: 1
},
methods: {
plus: () => {
vm.a++
}
}
});
vm.plus();
console.log(vm.a)
把 this 换成具体的对象就解决啦,哈哈。