1.首先先明确,this会出现在哪里。
this出现在全局作用域中,或函数作用域中(普通函数、箭头函数)。
对象是不产生作用域的,对象的{}和函数的{}不一样,this并不会直接出现在对象或类中,只会出现在对象/类的函数中。
2.this指向哪里
this在全局作用域下指向的是window。
this在函数中指向的比较复杂, 满足以下四点:
1.函数在调用时,JavaScript会默认给this绑定一个值;
2.this的绑定和定义的位置(编写的位置)没有关系;
3.this的绑定和调用方式以及调用的位置有关系;
4.this是在运行时被绑定的;
3.this的绑定规则
以下指的规则都是普通函数中的,不包括箭头函数。
1.默认绑定
如果一个函数调用是直接调用,比如fn()这样,没有对象.fn(),就说它是默认绑定的,通常默认绑定时,函数中的this指向全局对象(window);
且要找到函数真正的调用位置,看它是否是独立函数直接调用。
function foo() {console.log(this); // window
}foo();
2.隐式绑定
通过某个对象进行调用的。比如对象.fn()。通过某个对象发起的函数调用。这时候this会被绑定在该对象身上
function foo() {console.log(this); // obj对象
}var obj = {name: "why",foo: foo
}obj.foo();
上面提到要找到函数真正的调用位置,看它是否是独立函数直接调用。
下面举个例子,这个实际的调用位置其实是直接调用的。
function foo() {console.log(this);
}var obj1 = {name: "obj1",foo: foo
}// 讲obj1的foo赋值给bar
var bar = obj1.foo;
bar();
3.显式绑定
通过apply、call、bind进行绑定就是显式绑定
function foo() {console.log(this);
}foo.call(window); // window
foo.call({name: "why"}); // {name: "why"}
foo.call(123); // Number对象,存放时123
4.new绑定
使用new,这个新对象会绑定到函数调用的this上。
绑定的this是这个类/构造函数的实例对象。
// 创建Person
function Person(name) {console.log(this); // Person {}this.name = name; // Person {name: "why"}
}var p = new Person("why");
console.log(p);
4.特殊的this
1.setTimeout
setTimeout内部如果传的是普通函数,那这个this指向的是window。
这和setTimeout源码的内部调用有关,记住就好了。
setTimeout(function() {console.log(this); // window
}, 1000);
2.forEach
内部传普通函数,this指向的也是window。
var names = ["abc", "cba", "nba"];
names.forEach(function(item) {console.log(this); // 三次window
});
3.dom节点
this指向的是绑定事件的节点。
var box = document.querySelector(".box");
box.onclick = function() {console.log(this); // box对象
}
4.箭头函数
箭头函数没有this,也就是不绑定this,如果在箭头函数中使用this,则是根据外层作用域来决定this。
所以与上面的setTimeout、forEach特点结合,经常在setTimeout、forEach中使用箭头函数。
var obj = {data: [],getData: function() {setTimeout(() => {// 模拟获取到的数据var res = ["abc", "cba", "nba"];this.data.push(...res);}, 1000);}
}obj.getData();
这里,setTimeout中通过箭头函数调用this,这个this与外层作用域相同,也就是和在getData函数中使用this的指向相同。
但如果getData也是个箭头函数,那么getData函数中使用this的指向 与 外层作用域相同,也是与全局作用域中的this一致,window。
var obj = {data: [],getData: () => {setTimeout(() => {console.log(this); // window}, 1000);}
}obj.getData();
5.规则优先级
new绑定 > 显式绑定(bind)> 隐式绑定 > 默认绑定
1.new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高
但new绑定优先级高于bind
function foo() {console.log(this);
}var obj = {name: "obj"
}// var foo = new foo.call(obj);
var bar = foo.bind(obj);
var foo = new bar(); // 打印foo, 说明使用的是new绑定
2.new绑定优先级高于隐式绑定
function foo() {console.log(this);
}var obj = {name: "why",foo: foo
}new obj.foo(); // foo对象, 说明new绑定优先级更高
3.显式绑定优先级高于隐式绑定
function foo() {console.log(this);
}var obj1 = {name: "obj1",foo: foo
}var obj2 = {name: "obj2",foo: foo
}// 隐式绑定
obj1.foo(); // obj1
obj2.foo(); // obj2// 隐式绑定和显式绑定同时存在
obj1.foo.call(obj2); // obj2, 说明显式绑定优先级更高
参考:前端面试之彻底搞懂this指向