要说 JavaScript 这门语言最容易让人困惑的知识点,this 关键词肯定算一个。学习 this 会拓展你的知识,加深对代码的理解。即使你并不打算在你的代码库中使用它,了解清楚 this 的指向也能让你在接手别人代码、理解代码逻辑的时候事半功倍。
一、在普通函数中的指向
(1)如果一个函数中有this,但是它没有被上一级对象所调用,那么this指向就是window(非严格模式下)/ undefined(严格模式下)。
function a(){
var user = "李明"
console.log(this); // 非严格模式为window,严格模式为undefined
console.log(this.user); //非严格模式为undefined,严格模式报错
}
a();
(2)如果一个函数中有this,这个函数有被上一级调用,那么this指向的就是上一级的对象。
var o = {
user:"李明",
fn:function(){
console.log(this.user); //李明
}
};
o.fn();
(3)如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它的上一级的对象。
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); // 12
}
}
};
o.b.fn();
总结:this永远指向最后调用它的对象,也就是看他执行时是谁调用的。
注意:将含this的的函数,赋值给变量后,再单独执行该对象,那么this指向就是window(非严格模式下)/ undefined(严格模式下)。
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this); // 非严格模式为window,严格模式为undefined
console.log(this.a); //非严格模式为undefined,严格模式报错
}
}
};
var j = o.b.fn; //将fn赋值给变量j,并没有执行
j(); //执行,但它没有被上一级调用
二、在构造函数中的指向
(1)构造函数中无return
this指向函数的实例。
因为:new关键字会创建一个空对象,然后会自动调用apply(),将this指向这个空对象。创建实例相当于复制了一份构造函数到实例对象里。
function Fn(){
this.user = "李明";
};
var a = new Fn(); // Fn {user: "李明"}
console.log(a.user); // 李明
(2)构造函数中有return
如果返回的是一个引用类型的值,那么使用new创建实例后,this指向的是那个返回的值; 如果返回值不是一个引用类型的值,那么this还是指向函数的实例。
/* * 示例1 */
function Fn(){
this.user = '李明';
return [1,2]; // 或者{},或者function(){}
};
var a = new Fn;
console.log(a); // [1,2]
console.log(a.user); // undefined
/* * 示例2 */
function Fn() {
this.user = '李明';
return {user:"lily"};
};
var a = new Fn;
console.log(a); // {user: "lily"}
console.log(a.user); //lily
/* * 示例3 */
function Fn() {
this.user = '李明';
return 1; // 或者String,或者Boolean,或者Symbol,或者undefined,或者null
};
var a = new Fn;
console.log(a); // fn {user: "李明"}
console.log(a.user); // 李明
扩展:当使用new运算符时,如果没有参数要传,括号是可以省略来简化写法的。但有一些工具可能会认为这是不规范的写法,进而报警告。
但要注意:new运算符与属性调用一起使用时,有无括号是有区别的。(因为.的优先级高于new)
function Fn() {
this.user = '李明';
};
var a = new Fn.user; // Fn.user is not a constructor
var b = new Fn().user; // '李明'
三、在事件函数中的指向
this指向触发这个事件的标签。
<button class="btn1">button1</button>
<body>
<button class="btn1">button1</button>
<button class="btn2">button2</button>
<button class="btn3">button3</button>
<script type="text/javascript">
for(var i = 1; i < 4; i++) {
var btn = document.getElementsByClassName('btn'+i)[0];
btn.onclick = function () {
console.log(this); //分别是btn1、btn2和btn3对应的标签
}
}
</script>
</body>
注意:
<button onclick="handleClick()"></button>
<script>
function handleClick(){
console.log(this); //Window
};
</script>
四、在定时器中的指向
this指向window。
setInterval( function () {
console.log(this); //window
},1000);
setTimeout(function() {
console.log(this); //window
}, 1000)
var a = 23;
function Demo() {
this.a = 12;
var self = this;
setInterval(this.show, 1000);
}
Demo.prototype.show = function() {
alert(this.a);
};
var demo = new Demo();
demo.show();
// 12
// 23.........23
五、在箭头函数中的指向
箭头函数不绑定this,它的this来自于作用域链的上一层。
function foo() {
setTimeout(() => {
console.log(id); // {id: 42}
console.log('id:', this.id); // id: 42
//setTimeout里的函数使用了箭头函数,所以它会和外层的this保持一致,也就是{ id: 42 }
//如果setTimeout里的是普通函数,执行时this应该指向全局对象window,这时应该输出21。
}, 100);
}
var id = 21;
foo.call({ id: 42 });
使用call/apply/bind无法修改箭头函数的this指向,它是固定的。
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!!!
//箭头函数this对象的指向是固定的,所以后面的call修改不了绑定。
六、可以使用call/apply/bind可以修改this指向(除箭头函数外)
注意:对箭头函数使用call/apply/bind时,只能传递参数,不能绑定this。(他们的第一个参数会被忽略)
const obj = {
a: () => {
console.log(this)
}
}
obj.a.call('123') //打出来的结果依然是window对象
//箭头函数本身与a平级以key:value的形式,也就是箭头函数本身所在的对象为obj,而obj的this就是window,因此这里的this实际上是window
七、全局作用域时,严格模式与非严格模式下,this的指向
非严格模式下,默认的this是window;
严格模式("use strict;")下,默认的this是undefined。
例外:
1、定时器的回调函数为普通函数时,无论严格模式还是非严格模式,函数内this都指向window。
2、箭头函数中,箭头函数的作用域的上一层的this,如果是全局作用域,无论严格模式还是非严格模式,函数内this都指向window(因为ES6中,全局作用域下,无论是否为严格模式,this都指向window)