前言
白的不能再白的小白,通过各种渠道学习到了this,和大家分享,有什么错误的话还望指出,共同学习进步。觉得好的话还望点个小赞,为继续坚持写文章增加动力。
正文
我在想写这篇文章的时候,我是无从下手的。因为我自己也同样说不清在JavaScript中this到底是什么?我们都知道在一些强类型语言中(像java)对this有一个很好的定义:this就是一个指针,指向当前对象。在JavaScript中如果用上面这种方法来表示this的话,是不完全的。对于我来说,this就像JavaScript中的魔术师,千变万化中又有着那么一丝丝小套路。
何为this?
this是JavaScript语法中很难捉摸透的一个东西。我们都知道JavaScript执行机制分为四个步骤:词法分析
、语法分析
、预编译
、解释执行(运行时)
。我们在执行代码的时候,通常是在全局中执行一个函数或者通过对象方法调用执行函数,在函数调用过程中得到this。也就是说this这个东西就生在运行时,即this是在运行时绑定的,而非编译时。
JavaScript中函数调用的几种方式
默认调用
在JavaScript中调用函数的最常见的一种方法就是默认调用。
function test(){
console.log(this);
}
test()
正如上面代码这样调用test函数,就是默认调用。
默认调用的情况下,函数的this指向全局对象。
我们都知道在ES5出现了严格模式
这个概念。在严格模式下全局对象的this
指向undefined
,非严格模式下全局对象的this
指向Window
对象。
//严格模式
"use strict";
function test(){
console.log(this); //undefined
}
test()
//非严格模式下
function test(){
console.log(this); //window
}
test();
IIFE
立即执行函数在执行的时候会形成一个闭包,其中的this
永远指向window
。
(function(){
console.log(this); //window
}())
隐式绑定
隐式绑定也可以说成作为对象的方法来调用。看下面代码
function test(){
console.log(this.a);
}
var obj = {
a : 2,
test:test,
}
obj.test(); //2
这里可以看出obj中有一个属性为test,它的属性名为test函数。当test这个属性被创建的时候,会在内存中开辟一小段空间,而内存中所存的值就是test函数所在内存的地址。此时就把test函数隐式的绑定到了obj上面,当使用obj调用test的时候,test中的this就指向了调用test的对象。
这里就可以总结出一个套路:谁调用函数,函数的this就指向谁。
另外一种情况:
function foo(){
console.log(this.a);
}
var obj2 = {
a:1,
foo :foo,
}
var obj1 = {
a : 2,
obj2 : obj2
}
obj1.obj2.foo() //1
当对象方法调用链很长的时候,只有方法引用链的上一层或者说是最后一层在调用位置中起作用。上面代码中只有obj2会起作用。
隐式绑定中还有一个现象是隐式丢失,也就是说当把一个对象的方法暴露在外部的时候(也就是说把对象的方法赋于一个全局的变量),再通过默认调用的方式调用这个函数,此时的this就不会再指向对象,而是指向window。
var obj ={
foo : function(){
console.log(this);
}
}
obj.foo() //obj
var fun = obj.foo; //把对象的方法暴露在外部
fun(); //window
显示绑定
显示绑定也可以称之为硬绑定。所谓硬绑定就是使用一种手段强制的改变函数的this执行,这种强制的手段通过call、apply、bind等函数的API来实现。这里由于说的是this机制,就先不提call、apply、bind等原理。
Function.prototype.call
call的作用就是改变函数调用中的this指向,call方法第一个参数就是绑定的this到哪个对象上,后面的参数是传递函数的实参。
function test(name,age){
console.log(name);
console.log(age);
console.log(this);
}
var obj ={}
test.call(obj,'xiaoming',18); // xiaohong 18 obj
Function.prototype.apply
apply的作用和call的作用相同,同样是为了改变this指向,唯一不同的区别是call在传递函数实参的时候是通过分来传参,apply函数传参是通过传递一个数组。
function test(name,age){
console.log(name);
console.log(age);
console.log(this);
}
var obj ={}
test.call(obj,['xiaoming',18]); // xiaohong 18 obj
Function.prototype.bind
bind是ES5出现的一个API,同样也是可以改变this指向。bind和上面两个API有很大的不同之处,call和apply是立即执行所调用的函数。bind是返回一个新函数,并且支持柯里化(分布传参)。
function test(name,age){
console.log(name);
console.log(age);
console.log(this);
}
var obj ={}
var fun = test.bind(obj,'xiaoming'); //调用bind方法,第一个参数传入所要绑定的对象,第二个参数传入name,并且返回一个新函数。
fun(18); //传递剩余的参数。
//输出 xiaohong 18 obj
构造函数调用
JavaScript中的面向对象编程是很模糊的,起初这个语言并不是为面向对象编程而设计的,JavaScript语言的特性更类似于函数式编程。我们为了使用JavaScript模拟面向对象编程,从而有了构造函数的概念:使用new调用的函数称为构造调用,而这个函数被称为构造函数。
发生构造函数调用会自动执行以下操作:
- 创建一个全新的对象。
- 这个新对象会被执行[[Prototype]连接
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象(对象、数组、函数),那么new表达式中的函数调用会自动返回这个新对象。
function Test(name,age){
this.name = name;
this.age = age;
}
var test = new Test('xiaoming',18);
console.log(test) //Test{}
函数调用的优先级
那么多函数调用方式,如果同时出现的话究竟哪个优先级高,哪个优先级低呢?
默认调用和隐式绑定
先来看一下最基础的两种
function test(){
console.log(this);
}
//默认调用
test(); //window
var obj = {
test : test,
}
//隐式绑定
obj.test(); //obj
可以明显的看出,同一个函数默认调用的情况下this为window,隐式绑定的情况下this指向obj,很明显隐式绑定改变了默认调用的this,所以隐式绑定的优先级比默认调用的优先级高。
隐式绑定和显示绑定
再来看一下隐式绑定
var temp = {
a : 1,
}
var obj = {
a : 2
foo : function(){
console.log(this.a);
}
}
//隐式绑定
obj.foo() //2
//显示绑定
obj.foo.call(temp); //1
我们可以发现显示绑定改变了隐式绑定的this,即显示绑定的优先级大于隐式绑定
显示绑定和构造调用
如果让显示绑定和构造调用相比的话,就需要bind了,因为call和apply都是直接调用,没办法和构造调用相比,而bing则是返回一个新函数,可以使用构造调用。
var obj = {
a : 1,
}
function Test(){
console.log(this.a);
}
//显示绑定
var fun = Test.bind(obj); //此时函数已经绑定了obj对象。
fun(); //执行fun,输出1
//构造调用
new fun(); //unfeinde
我们发现,fun此时已经绑定了obj,输出1,对的没有关系,使用new fun怎么就输出undefined了呢,其实这里输出的是fun构造函数返回的对象。新创建的对象上没有a属性,我们来修改一行代码再测试下。
function Test(){
console.log(this)
}
重新调用 得出结论构造函数调用比显示绑定的优先级高,这里涉及到bind原理的东西,目前没有写这类文章,喜欢探索的可以自行google下。
所以最后得出的结论是:构造函数调用>显示绑定>隐式绑定>默认调用。
判断this规则
- 判断是否是立即执行函数,如果是则this为window,如果不是移步第2步骤。
- 判断this是否是通过构造函数调用,如果是this就绑定新创建的对象,如果不是移步第3步骤。
- 判断是否是通过call、apply、bind等绑定,如果是this绑定指定对象,如果不是移步第4步骤。
- 判断函数是够是在某个上下文对象中调用,如果是this就绑定在上下文对象上,如果不是请移步第5步骤。
- 如果上面都不是的话,那么函数使用默认调用,如果是严格模式,则this为undefined,如果是非严格模式,则this为window。
箭头函数的this
起这个小标题说实话不太想起,因为箭头函数中是没有this的,怎么说呢,请移步软大大的箭头函数。在其他函数中this是可变的,在箭头函数中this是固定的,同样也是因为箭头函数中没有this。在你不知道的JavaScript(上)
中有那么一句话:箭头函数在涉及this绑定的行为和普通的行为完全不一致。它放弃了所有普通this绑定的规则,取而代之的是用当前的词法作用域覆盖了this本来的值。
这句话和上面其实是不矛盾的,所谓词法通俗的将就是书写代码的位置。箭头函数书写在什么地方,它的this就是所处环境的执行上下文对象的this。
var a = 2;
var obj = {
a : 1,
foo : ()=> {
console.log(this.a);
}
}
//此时obj定义在全局内,箭头函数中的this为window,故:输出2
obj.foo(); //2
var a = 2;
var obj = {
a : 1,
fun : function(){
var foo = ()=>{
console.log(this.a);
}
foo();
}
}
//此时foo定义在fun函数中,执行fun函数,fun函数中的this为obj,所以箭头函数中的this为obj。故:输出1
obj.fun(); //1
总结
- 如果是箭头函数,则就判断箭头函数定义在什么地方,this就是什么。
- 如果是立即执行函数,则this就是windows
- 其他形式则按照判断this的规则来。
文章总结自:你不知道的JavaScript(上),安利一波,值得一读。