为什么需要this?
在常见的编程语言中几乎都有this关键字,但是JS中的this和常见的面向对象语言中的this不太一样:
- 常见面向对象的编程语言中,比如Java、C++等,this通常只出现在类的方法中
- 也就是你需要有一个类,类中的方法中,this代表的是当前调用对象
- 但是JS中的this更加灵活,无论是它出现的位置还是它代表的含义
我们编写一个obj对象,比较一下有this和没有this的区别:
var obj = {
name: "zx",
eating: function () {
console.log(this.name + "吃东西");
},
running: function () {
console.log(this.name + "跑步");
},
studying: function () {
console.log(this.name + "学习");
},
};
obj.eating();
obj.running();
obj.studying();
我们可以通过this来输出当前对象的属性,如果不使用this,我们也可以实现这个功能,不过没有this这么灵活
var obj1 = {
name: "zx",
eating: function () {
console.log(this.obj1 + "吃东西");
},
running: function () {
console.log(this.obj1 + "跑步");
},
studying: function () {
console.log(this.obj1 + "学习");
},
};
obj1.eating();
obj1.running();
obj1.studying();
this指向什么
this在全局环境下的指向
this在全局环境下指向window,也就是GO。
console.log(this === window); // true
this不同执行方式结果不同
但是,开发中很少直接在全局作用域使用this,通常是在函数中使用。
- 所有的函数被调用时,都会创建一个执行上下文
- 这个上下文中记录着函数的调用栈、AO对象等
- this也是其中的一条记录
案例:
// this指向什么,跟函数所处的位置是没有关系的
// 跟函数被调用的方式有关系
function foo() {
console.log(this);
}
// 1.直接调用这个函数
foo();
// 2.创建一个对象,对象中的函数指向foo
var obj = {
name: "why",
foo: foo,
};
obj.foo();
// 3.apply调用
foo.apply("abc");
打印结果:
三种不同的调用方式,产生了三种不同的结果,由此可得:
- 函数在调用时,JS会默认给this绑定一个值
- this的绑定和定义的位置(编写的位置)没关系
- this的绑定和调用方式以及调用的位置有关系
- this是在运行时被绑定的
this一共有四个绑定规则:
- 规则一:默认绑定
- 规则二:隐式绑定
- 规则三:显示绑定
-
默认绑定
什么情况下使用默认绑定?独立函数调用。
独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用
-
隐式绑定
另外一种比较常见的调用方式是通过某个对象进行调用:
也就是它的调用位置中,是通过某个对象发起的函数调用
-
显示绑定
隐式绑定有一个前提条件:
必须在调用的对象内部有一个对函数的引用(比如一个属性)
- 如果没有这样的引用,在进行调用时,会报找不到该函数的错误
- 正是通过这个引用,间接的将this绑定到了这个对象上
但是如果我们不希望在对象内部包含这个函数的引用,同时又希望在这个对象上进行强制引用,该怎么做呢?
- JS所有的函数都可以使用call和apply方法
- 这两个函数肚饿第一个参数都要求是一个对象
- 在调用这个函数时,会将this绑定到这个传入的对象上
new绑定
JS中的函数可以当作一个类的构造函数来使用,也就是使用new关键字
使用new关键字来调用函数时,会执行如下操作:
- 创建一个全新的对象
- 这个新对象会被执行prototype链接
- 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成)
- 如果函数没有返回其他对象,表达式会返回这个新对象
系统API中this指向分析
```javascript // 1.setTimeout setTimeout(function () { console.log(this); // window,可以看作是函数单独调用 }, 2000);
// 2.监听点击 const boxDiv = document.querySelector(“.box”); boxDiv.onclick = function () { console.log(this); // 绑定的对象,也就是boxDiv };
// 3.数组 forEach/map/filter/find var names = [“abc”, “cba”, “nba”]; names.forEach(function () { console.log(this); // window,可以看作是函数单独调用 });
// 如果想要改变forEch中this指向,高阶函数都是有提供接口的 var names = [“abc”, “cba”, “nba”]; names.forEach(function () { console.log(this); // [“abc”, “cba”, “nba”] }, names); // 高阶函数有一个thisArg的形参,可以改变this指向
<a name="PJ75P"></a>
# 多个绑定规则优先级比较
如果一个函数调用位置应用了多条规则,哪种规则优先级更高呢?
1. 默认绑定的优先级最低
1. 只要存在其他的绑定规则,都会使用其他的方式来绑定this
2. 显示绑定优先级高于隐式绑定
```javascript
function foo() {
console.log(this); // String {'aaa'}
}
var obj = {
name: "obj",
foo: foo.bind("aaa"),
};
obj.foo();
// 因为打印的是字符串对象aaa而不是obj对象,所以显示绑定优先级高于隐式绑定
- new绑定优先级高于隐式绑定 ```javascript var obj = { name: “obj”, foo: function () { console.log(this); // foo {} }, };
var f = new obj.foo(); // 因为打印的不是obj对象,而是foo函数,所以说明new的优先级高于隐式绑定的优先级
4. new优先级高于显示绑定
```javascript
function foo() {
console.log(this); // foo {}
}
var bar = foo.bind("aaa");
var obj = new bar();
// 因为打印的是foo函数而不是字符串对象aaa,所以说明new的优先级高于bind
结论:new绑定 -> 显示绑定(call/apply/bind)-> 隐式绑定 -> 默认绑定
this规则之外
忽略显示绑定
当我们的显示绑定(call/apply/bind)中传入的值是null或者是undefined时,this指向为window。
function foo() {
console.log(this);
}
foo.apply("abc"); // String {'abc'}
foo.apply({}); // {}
foo.apply(null); // window
foo.apply(undefined); // window
var bar = foo.bind(null);
bar() // window
间接函数引用
另外一种情况,创建一个函数的间接引用,这种情况使用默认绑定规则
var obj1 = {
name: "obj1",
foo: function () {
console.log(this);
},
};
var obj2 = {
name: "obj2",
};
obj2.bar = obj1.foo
obj2.bar() // {name: 'obj2', bar: ƒ}
(obj2.bar = obj1.foo)();
箭头函数
箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this
var obj = {
data: [],
getData: function () {
setTimeout(() => {
console.log(this);
}, 2000);
},
};
obj.getData();