文章目录
前言
看了这篇文章,麻麻再也不用担心我不理解JS中this的指向问题了。本文以文章目录为顺序,层层递进。耐心的看完,会有很大的收获。
一、this的作用?我们为什么要用this,没它不行吗?
this关键字是JavaScript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。但是即使是非常有经验的JavaScript开发者也很难说清它到底指向什么。
实际上,JavaScript中this的机制并没有那么先进,但是开发者往往会把理解过程复杂化。毫不夸张地说,不理解它的含义,大部分开发任务都无法完成。
this都有一个共同点,它总是返回一个对象。简单说,this就是属性或方法“当前”所在的对象。
从某些角度来说,开发中如果没有this,很多问题也有其他的解决办法。
但是没有this,会让我们编写代码变得非常不放便。
话不多说,举个栗子 看看就知道了,示例代码如下:
var obj = {
name: '张三',
eating () {
console.log(obj.name + '在吃饭')
},
studying () {
console.log(obj.name + '在学习')
},
running () {
console.log(obj.name + '在跑步')
}
}
obj.eating()
obj.studying()
obj.running()
上述示例代码,如果我们修改了变量obj名字为object,之后需要修改的代码有以下两处:
首先,需要修改的代码有方法的调用。
其次,就是在eating、studying、running三个方法中打印语句中的变量名
如果我们的obj对象中不止三个方法呢?如果是一百个方法呢?一个一个改,杀了我吧(裂开)。
如下示例代码,展示了使用this进行优化的效果:
var obj = {
name: '张三',
eating () {
console.log(this.name + '在吃饭')
},
studying () {
console.log(this.name + '在学习')
},
running () {
console.log(this.name + '在跑步')
}
}
obj.eating()
优化后的代码不管我们怎么改变变量名称 ,我们需要修改的地方只有一个,那就是方法调用时的对象名称。是不是简便了许多,为编程的效率提升的不止一点哦。
二、this在全局作用域中指向哪里?
其实在大多数情况下,this都是出现在函数中。但也有出现在全局作用域下的情况,我们就来举个栗子说一说。
示例代码如下:
var name = 'code'
console.log(this)
console.log(this.name) // 输出 code
console.log(Window.name) // 输出 code
浏览器环境下输出结果如下图所示:
由图可知,全局作用域下,浏览器环境中:this指向的是全局对象Window。
这里需要补充一下的是,在全局作用域下,node环境中,this指向的是一个
空对象{}
。
三、同一个函数中this的不同指向
先看代码,再做结论。如下示例代码展示了不同的调用方法,得到的this的不同指向。示例代码如下:
// 定义一个函数
function foo () {
console.log(this)
}
// 调用方式一:直接调用
foo() // this指向:Window
// 调用方式二:间接调用
var obj = {
name: '张三',
fn: foo
}
obj.fn() // this指向:obj对象
// 调用方式三:通过call/apply调用
foo.call('123') //this指向: String {‘123’}对象
浏览器输出结果如下所示:
三个不同的调用方法,得到了三个不同的this指向。
结论:
1.函数在调用时,JavaScript会默认给this绑定一个值;
2.this的绑定值与定义的位置没有关系;
3.this的绑定和调用方式以及调用的位置没有关系;
4.this是在运行时被绑定的,而不是在定义时。
四、this的四种绑定规则
1、默认绑定
在一个函数体中使用this,当该函数被独立调用时,就会被默认绑定一个对象。
在默认绑定情况下,只要是独立函数调用就是指向Window。
如下展示五个案例进行深入学习,层层深入。示例代码如下:
// 案例一:
function foo1 () {
console.log(this)
}
foo1()
// 案例二: 三个方法被调用的时候都是独立的,所以this的指向都是Window
function one () {
console.log(this)
}
function two () {
console.log(this)
one()
}
function three () {
console.log(this)
two()
}
three()
// 案例三:
var obj = {
name: '张三',
foo3 () {
console.log(this)
}
}
var bar3 = obj.foo3
bar3() // Window
// 案例四:
function foo4 () {
console.log(this)
}
var obj = {
name: '张三',
fn4: foo4
}
var bar4 = obj.fn4
bar4() // Window
// 案例五:
function foo5 () {
function bar () {
console.log(this)
}
return bar
}
var fn = foo5()
fn()
上述代码中五个案例,全部都是独立调用,所以显而易见,输出结果
this
都是指向Window对象
2、隐式绑定
通过某个对象进行调用,也就是它的调用位置中,是通过某个对象发起的函数调用。
隐式绑定有一个前提条件:
- 必须在调用的对象内部有一个对函数的引用(比如一个属性)
- 如果没有这样的引用,在进行调用时,会报找不到该函数的错误
- 正式通过这个引用,间接的讲this绑定到了这个对象上
如下通过三个案例,进行隐式绑定的详细了解,示例代码如下:
// 案例一:通过隐式绑定将fn函数中的this指向obj对象
function fn () {
console.log(this)
}
var obj = {
name: '张三',
studying: fn
}
obj.studying() // {name: '张三', studying: ƒ}
// 案例二:
var obj2 = {
name: '张三',
eating () {
console.log(this.name + '正在吃饭')
},
running () {
console.log(obj.name + '正在跑步')
}
}
// 隐式绑定进行调用
obj2.eating() // 张三正在吃饭
obj2.running() // 张三正在跑步
var fn = obj.eating
fn() // 独立函数调用 指向window,name为window中的name的值为空 输出结果为: 正在吃饭
// 案例三
var obj3 = {
name: 'foo3',
foo () {
console.log(this)
}
}
var obj4 = {
name: 'foo4',
bar: obj3.foo
}
obj4.bar() // {name: 'foo4', bar: ƒ}
上述三个案例中,三个
this
都被隐式绑定到了调用此方法的对象上。
3、显示绑定 (call
/apply
/bind
)
显示绑定就是指使用js中的原型方法call()
、apply()
、bind()
,对this
进行显示绑定。
首先我们先对三个方法的概念下手,如下是参考的相关文档整理的:
①、call()
定义:
call()
方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
语法:
fun.call(thisArg, arg1, arg2, ...)
参数:
- thisArg:在 fun 函数运行时指定的 this 值。
if(thisArg == undefined|null) this = window,if(thisArg == number|boolean|string) this == new Number()|new Boolean()| new String()
- arg1, arg2, …: 指定的参数列表
返回值: 使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined
。
②、apply()
定义:
apply()
方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。
语法:
func.apply(thisArg, [argsArray])
参数:
- thisArg:可选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
- argsArray: 可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。
返回值: 调用有指定this值和参数的函数的结果
③、bind()
定义:
bind()
方法创建一个新的函数,在bind()
被调用时,这个新函数的this
被bind
的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。
语法:
function.bind(thisArg[,arg1[,arg2[, ...]]])
参数:
- thisArg:调用绑定函数时作为this参数传递给目标函数的值。如果bind函数的参数列表为空,执行作用域的this将被视为新函数的thisArg。
- arg1, arg2, …: 当目标函数被调用时,预先添加到绑定函数的参数列表中的参数。
返回值: 返回一个原函数的拷贝,并拥有指定的this值和初始参数。
概念介绍完了,上代码:
// call 与 apply
function foo () {
console.log(this + 'foo函数被调用了')
}
var obj = {
name: 'obj'
}
// 调用方法一:直接调用 指向的是全局对象(window)
foo()
// 调用方法二: call和apply与直接调用不同的在于this的绑定不同 ,可以直接指定this的绑定对象为obj
foo.call(obj) //{name: 'obj'}
foo.apply(obj) //{name: 'obj'}
// bind
function bar () {
console.log(this)
}
var newBar = bar.bind('aaa')
newBar() // String {'aaa'} 解释一下 newBar()句话 很明显是直接调用应该指向全局对象window 但是bind显示绑定的优先级更高 (优先级会在下一篇文章更新)
4、new绑定
在JavaScript中,构造函数只是一些使用new操作符时被调用的函数。包括内置对象函数在内的所有函数都可以用new来调用,这种函数调用称为构造函数调用。
我们通过一个new关键字调用一个函数时(构造器),这个时候this是在调用这个构造器时创建出来的对象。 this 等于
创建出来的对象 ,这个过程就是 new 绑定。
示例代码如下:
function Person (name, sex) {
this.name = name
this.sex = sex
}
var p1 = new Person('张三', '男')
console.log(p1.name, p1.sex) // 张三 男
// 每次都会创建新对象赋值给this
var p2 = new Person('李红', '女')
console.log(p2.name, p2.sex) // 李红 女
5、call和apply的区别
根据上面的概念我们可以很明显的看出来,两个方法接收的参数不一样,其实作用基本一致。
// call 与 apply的区别是什么?
function sum (num1, num2, num3) {
console.log(num1 + num2 + num3)
}
sum.call('call', 10, 20, 30) // 60
sum.apply('apply', [10, 20, 30]) // 60
// call参数是单个的值,而apply的参数是一个数组,就这点差别