1、this是什么
在js中,有一套神奇的this
机制
我们先来看一个例子:
var person = {
namer: '张三',
sayNmae: function() {
console.log(namer);
}
}
var namer = '李四'
person.sayNmae(); // 李四
我的天呐,怎么跟我们想的不一样?
这是因为sayNmae
方法中的变量是属于全局作用域下面的,但是我们想要访问的是对象里面的变量
使用this
便可以帮我们访问到对象里面的变量
var person = {
namer: '张三',
sayNmae: function() {
console.log(this.namer);
}
}
var namer = '李四'
person.sayNmae(); // 张三
这是因为方法中的this
指向的是方法所在的对象
this
是一个对象
分类
this
分为全局作用域下的this
和 函数中的this
全局作用域下的this
指向的是windows
对象
函数中this
的指向就比较复杂了,接下里我们就来详细介绍一下函数中this
的指向
2、函数中this
的指向
this
的指向是我们在调用函数的时候确定的。调用方法的不同导致this
的指向不同
调用方式 | this 指向 |
---|---|
普通函数调用 | window |
构造函数调用 | 实例对象 原型对象里面的方法也指向实例对象 |
对象调用方法 | 该方法所属对象 |
事件绑定函数 | 绑定事件对象 |
定时器函数 | window |
立即执行函数 | window |
箭头函数 | 箭头函数的this 就是它外层函数的this |
1、普通函数
function foo() {
console.log(this);
}
foo()
返回window
对象
2、构造函数
// 构造函数this指向 p 这个实例对象
function Person() {}
// 原型对象里面的 方法也指向实例对象
Person.prototype.say = function() {
console.log(this);
}
var p = new Person()
p.say(); // Person {}
3、对象方法
var person = {
namer: '张三',
sayNmae: function() {
console.log(this.namer);
}
}
person.sayNmae(); // 张三
当函数作为对象的方法调用时,函数中的this
就是该对象
但是,如果将该对象赋值全局变量,那么this
就指向window
对象
var namer = '李四'
var p = person.sayNmae;
p(); // 李四
4、事件绑定函数
var btn = document.querySelector('button')
btn.onclick = function() {
console.log(this); // <button>this指向事件对象</button>
}
事件绑定函数中this
指向绑定的事件,所以点击按钮,输出 button
事件
5、定时器函数
setTimeout(function() {
console.log(this);
}, 500)
0.5s后打印window
对象
6、立即执行函数
(function() {
console.log(this);
})()
立即执行函数本质上也是普通函数,其this
自然而然地指向 window
对象
7、箭头函数
ES6中的箭头函数,由于它没有自己的执行上下文,所以箭头函数的this
就是它外层函数的this
注意:
- 是箭头函数的外层函数中的
this
,不是对象中的this
- 若没有外层函数,其
this
默认指向window
对象
var namer = '李四'
var person = {
namer: '张三',
}
// 箭头函数
var sayNmae = () => console.log(this.namer)
sayNmae(); // 李四7
// 添加箭头函数
person.sayNmae = sayNmae
// 访问到的仍然是全局变量
person.sayNmae(); // 李四
箭头函数中,this
引用的是定义箭头函数的上下文,上面箭头函数的两次调用中,this
指向的都是window
对象,因为箭头函数都是在window
中定义的
3、改变函数内部this
的指向
js为我们提供了一些方法来改变函数内部this
的指向,常用的有3个:
call()
apply()
apply()
3.1 call()
方法
call()
方法有两个作用:改变this
的指向 和 调用函数
var person = {
name: '张三'
}
function foo(a, b) {
console.log(this);
console.log(a + b);
}
foo.call(person, 1, 2) // { name: '张三' } 3
此时this
已经指向person对象了
call
方法的主要作用是 实现继承
3.2 apply()
方法
func.apply(thisAry, [argsArray])
thisAry
:在func函数运行时指定的this
值argsArray
:传递的值,必须包含在数组里面apply()
会自动将数组转换为我们需要的数值类型
var person = {
name: '张三'
}
function foo(a, b) {
console.log(this);
console.log(a + b);
}
foo.apply(person, [1, 2]) // { name: '张三' } 3
apply()
经常跟数组有关系,比如借助内置的数组对象提取数组的最大值和最小值
Math.max()
规定必须传入单个的数值
var max = Math.max(1, 20, 3, 10, 15)
console.log(max);
我们通过apply
将数组分解为单个的值,然后传给max
,实现查找数组的最大值
var arr = [11, 20, 3, 10, 15]
var max = Math.max.apply(Math, arr)
console.log(max); // 20
var min = Math.min.apply(Math, arr)
console.log(min); // 3
将apply
中的this
指向函数的调用者 Math
3.3 bind()
方法
bind()
最大的特点是 不会调用函数,用法与call()
无异
func.bind(thisAry, arg1, arg2, ...);
thisAry
:在func函数运行时指定this
值arg1
、arg2
:传递单个的参数
var person = {
name: '张三'
}
function foo(a, b) {
console.log(this);
console.log(a + b);
}
var f = foo.bind(person, 1, 2)
f(); // { name: '张三' } 3
如果有的函数我们不需要立即调用,但是又想改变函数内部this
的指向时,可以使用bind
bind
也是这三个方法中我们用得最多的
4、this
有一个的缺陷
缺陷:嵌套函数中的this
不会继承外层函数中的this
var namer = '李四'
var obj = {
namer: '张三',
show: function() {
console.log(this.namer); // 张三
function foo() {
console.log(this.namer); // 李四
}
foo()
}
}
obj.say()
可以看到,show
方法的this
就指向其所在的对象obj,foo
函数中的this
并没有继承其外部函数show
的this
,而是全局对象window
有两个解决办法:
- 声明一个变量来保存
this
- 使用箭头函数
方法一“声明一个变量that
来保存this
var namer = '李四'
var obj = {
namer: '张三',
show: function() {
console.log(this.namer); // 张三
// 保存this
var that = this
function foo() {
console.log(that.namer); // 张三
}
foo()
}
}
obj.say()
方法二:箭头函数
箭头函数没有自己的上下文,所以箭头函数定义在哪里,它的上下文就在哪里的,因此其this
也是指向哪里
var namer = '李四'
var obj = {
namer: '张三',
say: function() {
console.log(this.namer); // 张三
var foo = () => {
console.log(this.namer); // 张三
}
foo()
}
}
obj.say()
小应用:当我们点击这个按钮,立即禁用这个按钮,2秒后再开启
说到延时,首先想到的就是利用 setTimeout
或 setInterval
var btn = document.querySelector('button')
// 点击按钮
btn.onclick = function() {
// 事件绑定函数,this指向的就是被绑定的事件btn
this.disabled = true; // 先禁用按钮
setTimeout(function() {
// 此处this指向的是window对象
this.disabled = false;
}, 2000)
}
按照我们上面的分析,这种方式绝对是gg的,无法实现效果的
事件函数中的this
指向的事件对象,这个某得问题,点击之后按钮被禁用了;但是延时函数中的this
指向的是window
对象,这个问题就来了
所以我们当务之急就是在延时函数中访问到事件对象,有4种方法:
- 直接使用
btn
事件对象 - 保存
this
- 使用箭头函数
- 利用
bind
修改this
指向
方法一:使用btn事件对象
var btn = document.querySelector('button')
btn.onclick = function() {
this.disabled = true;
var that = this
setTimeout(function() {
btn.disabled = false;
}, 2000)
}
缺点:事件名称一变,下面就要跟着修改,很不方便,不推荐
方式二:保存this
btn.onclick = function() {
this.disabled = true;
var that = this // 保存this
setTimeout(function() {
that.disabled = false
}, 2000)
}
缺点:需要开辟新的内存空间来存储that
,增加代码
方式三:箭头函数
btn.onclick = function() {
this.disabled = true;
// 箭头函数
setTimeout(() => {
this.disabled = false
}, 1000)
}
定义在事件点击函数中的箭头函数,其this
指向的就是其上下文所在的地方 —— 事件对象
方式四:利用bind
修改this
指向
上面刚学完bind
方法,还热乎乎的,bind
有两大特点:修改this
指向、不会调用函数
因为我们只需要修改延时函数中的this
指向,不需要调用函数,调用函数让延时函数自己来
btn.onclick = function() {
this.disabled = true;
setTimeout(function() {
this.disabled = false
}.bind(this), 2000)
}
总结
- 非严格模式下,函数的
this
指向window
对象;严格模式下,函数中的this
的值为undefined
call
和apply
会自己执行函数,bind
则不会,它会返回一个新的函数,这个新的函数已经绑定了新的this
- 箭头函数就像寄生虫,定义在谁的上下文,其
this
就指向谁 - 继承函数中的
this
不会继承其外层函数的this
值