this是干什么的?
在函数体内部,指代函数当前的运行环境(因为函数可以在不同的运行环境执行,所以this作用,就是在函数体内部获得当前的运行环境)
var a = 1;
var fn = function () {
console.log(this)
console.log(this.a);
}
var obj = {
fn: fn,
a: 2,
};
fn() // window 1
obj.fn() // {a: 2, fn: ƒ} 2
上述代码展示了函数fn在window和obj两种不同环境执行,产生结果也不一样。这就是this的作用效果。
this对象
函数内部存在的特殊对象,在标准函数中,this引用的是把函数当成方法调用的上下文对象,这时候通常称其为this值(在网页的全局上下文中调用函数时,this指向window)
相关上下文概念可查看我的另一篇博客—Javascript 执行上下文
由上述代码可以看出函数内部的this值是不确定的,可改变的。所以想要确定this值是什么,不是看函数定义的时候,而是看它执行的时候。
this应用场景
1全局上下文中:
function fn1() {
console.log(this)
}
fn1() //window
也叫默认绑定,就是函数调用时无任何调用前缀的情景。需要注意的是,严格模式下,指向undefined
"use strict";
function fn1() {
console.log(this)
}
fn1() //undefined
2 箭头函数:
var a = 1;
var fn = () => {
console.log(this)
console.log(this.a);
}
var obj = {
fn: fn,
a: 2,
};
fn() // window 1
obj.fn() // window 1
箭头函数是ES6新增函数,与标准函数相比,它没有自己的this对象,因此它会向外层中寻找,上述代码,fn函数的外层就是全局,因此this指向window
3 作为对象方法被调用:
const user = {
name: "khkkm",
sayHi() {
console.log(this)
}
}
user.sayHi() //{ name: 'khkkm', sayHi: [Function: sayHi] }
也叫隐式绑定,函数前面存在调用它的对象。this指向user对象
4 new关键字
class People {
constructor(name) {
this.name = name
this.age = 10
}
sayHi() {
console.log(this)
}
}
const khkkm = new People("khkkm")
khkkm.sayHi() //People { name: 'khkkm', age: 10 }
通过new关键字来创建的实例,this指向当前实例本身
5 call、apply、bind
三者共同点:都是函数对象的方法且都可以改变this指向。
apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。
var name = '王五'
var person = {
name: '张三',
say: function (age, sex) {
console.log(this.name, age, sex)
}
}
var person2 = {
name: '李四'
}
person.say(20, '男') //张三 20 男
person.say.apply(person2, [17, '女']) //李四 17 女
person.say.apply(null, [21, '男']) //王五 21 男
call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。
var name = '王五'
var person = {
name: '张三',
say: function (age, sex) {
console.log(this.name, age, sex)
}
}
var person2 = {
name: '李四'
}
person.say(20, '男') //张三 20 男
person.say.call(person2, 17, '女') //李四 17 女
person.say.call(null, 21, '男') //王五 21 男
bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。
var name = '王五'
var person = {
name: '张三',
say: function (age, sex) {
console.log(this.name, age, sex)
}
}
var person2 = {
name: '李四'
}
person.say(20, '男') //张三 20 男
person.say.bind(person2, 17)('女') //李四 17 女
person.say.bind(null, 21)('男') //王五 21 男
bind应注意:连续使用bind,this指向第一次使用bind的那个对象
function fn() {
console.log(this)
}
fn.bind({name:'张三'}).bind({name:'李四'})() // {name: '张三'}
也叫显式绑定,通过call、apply以及bind方法改变this的行为
总结:以上讲了五种this场景,分别是默认绑定、箭头函数绑定、隐式绑定、new绑定、显示绑定。还有在分析this指向时要始终记住一句话:this指向不是看函数定义的时候,而是看它执行的时候。
特殊:定时器中的this->window,拿setTimeout()举例
const zhangsan = {
name: "张三",
sayHi() {
console.log(this)
},
wait() {
setTimeout(function () {
console.log(this)
})
}
}
zhangsan.sayHi() //对象zhangsan
zhangsan.wait() //window
原因:超时调用的代码都是在全局作用域中执行的(相当于默认绑定),因此函数中的this的值在非严格模式下指向window对象
如果想让它指回对象zhangsan,可以这么做
const zhangsan = {
name: "张三",
sayHi() {
console.log(this)
},
wait() {
setTimeout(() => {
console.log(this)
})
}
}
zhangsan.wait() //对象zhangsan
原因:箭头函数无自己的this,会向外层找,它的this指向看wait函数的,因为外层是wait,然后wait是zhangsan对象调用的,所以指向zhangsan对象
优先级
上述5中this指向情况,其实是有优先级的:
箭头函数绑定>new绑定>显示绑定>隐式绑定>默认绑定。其中显示绑定bind > apply和call
最后练习一下吧!
function fn(a) {
this.b = a;
console.log(this);
}
fn(7);
console.log(b);
分析:很明显是默认绑定,this指向window,将参数a赋值给了window下的全局变量b
答案:window 7
function fn(num) {
this.number = num
}
fn.number = 0
fn(1)
console.log(number)
console.log(fn.number)
分析:还是默认绑定,fn中this指向window,执行函数体,给window添加了全局变量number赋值为1,行4给fn添加了number变量赋值为0
答案:1 0
var obj = {
name: "小明",
objInner: {
name: "小红",
sayName: function () {
console.log(this);
console.log(this.name);
}
}
}
obj.objInner.sayName()
分析:是最内层的objInner调用的函数,属于隐式绑定,this指向调用它的对象
答案:{name: '小红', sayName: ƒ} 小红
var obj = {
a: 1,
c: 2,
say: function (a) {
console.log("this1: " + this);
var sayA = function (a) {
console.log("this2: " + this);
this.a = a;
};
function sayC() {
console.log("this3: " + this);
}
sayA(a);
sayC()
}
}
obj.say(3);
console.log(obj.a + " " + obj.c);
console.log(window.a + " " + window.c);
分析:行17属于隐式调用,this指向obj,行13 14都属于默认调用,this指向window
答案:this1: obj this2:window this3:window 1 2 3 undefined
obj = {
func() {
const arrowFunc = () => {
console.log(this._name)
}
return arrowFunc
},
_name: "obj",
}
obj.func()()
func = obj.func
func()()
obj.func.bind({ _name: "newObj" })()()
obj.func.bind()()()
obj.func.bind({ _name: "bindObj" }).apply({ _name: "applyObj" })()
分析:行10在执行函数arrowFunc,箭头函数无this对象,向外层中找,this指向obj
行12也在执行函数arrowFunc,但是属于默认调用,指向全局window
行13 14都是改变this指向,前者改为对象{ _name: "newObj" },后者改为window
行15优先级bind > apply,this指向{ _name: "bindObj" }
答案:obj undefined newObj undefined bindObj
function fn() {
console.log(this)
}
fn.call({})
fn2 = () => {
console.log(this)
}
fn2.call({})
分析:行4输出{},call改变了this指向
行8输出window,优先级箭头函数绑定 > 显示绑定