JS中this的情况总结

初学JS,你有没有被代码里的this搞得晕头转向?
有一说一,反正我晕了。
于是经过一番系统学习,终于算是理清了this这个鬼东西,希望看完这篇文章的你也能战胜this呀!

全局上下文中的this

在浏览器环境中,全局上下文中的this,就是window。

let a = 12;
console.log(this);       // 控制台输出为 window

块级上下文中的this

块级上下文中没有自己的this,它的this是继承所在上级上下文的this。

let obj = {
  fn(){
  // fn函数中的this是obj
	{
	  let a = 12;
	  console.log(this);    // 输出obj。继承上级上下文,即函数fn的私有上下文中的this
	}
  }
}
obj.fn();

函数私有上下文中的this

私有上下文中的this千变万化,可以总结为以下五种情况:

事件绑定

给元素的某个事件行为绑定方法,事件触发时方法执行,此时方法中的this一般为元素本身。

DOM0事件绑定:

let body = document.body;
body.onclick = function(){
	console.log(this);          // body
}

DOM2事件绑定( 除IE6~8 ):

body.addEventListener('click',function(){
	console.log(this);          // body
})

那二般情况呢?
在DOM2事件绑定的IE6~8浏览器中,this指向window。

body.attachEvent('onclick',function(){
	console.log(this);         // window
})

普通方法执行

这里的普通方法包含:普通函数、自执行函数、对象成员访问调取方法执行…等。
只要看函数执行时,方法名前是否有“点”就是了。
如果有“点”——xxx.方法()中this就是xxx;如果没有“点”——this即为window(严格模式下为undefined)。

// 自执行函数
(function(){
	console.log(this);     // 非严格模式下输出window,严格模式下输出undefined
})();

let obj = {
	fn: (function(){
		console.log(this);    // 输出window
		return function(){};
	})();
}

上面两个例子都是自执行函数执行,输出均为window。说明this的指向,与函数在哪定义和执行的无关。自执行函数中的this一般都为window(因为函数执行时前面没有“点”)

function func(){
	console.log(this);
}
let obj = {
	func:func
}

// 普通函数执行
func();            // this:window
// 成员访问方法执行
obj.func();        // this:obj

[].slice()方法中this->当前空数组。
Array.prototype.slice()方法中的this->Array.prototype。

涵盖以上两种情况的一道题:

// 点击body输出什么?
function func(){
	console.log(this);
}
document.body.onclick = function(){
	func();
}
答案:window

为什么不是绑定了方法的元素body呢?整个过程是这样的:
body被点击时,首先执行的是为body的click事件绑定的匿名函数(这个函数中的this是body,但这个this题目中并没有要求输出),在这个匿名函数内部执行了func函数——函数执行时前面没有“点”呀,所以输出window。
注意:“给body的click事件绑定的函数”与“后来执行的func函数”可不是一个函数哦。

构造函数执行

我们在之前的文章中也有提到过,自定义类创建时与普通函数有哪些区别,其中一条就是“让函数中的this指向新创建的实例”。

没错,构造函数体中的this指向的就是当前类的实例。因此,函数中出现的this.xxx = xxx这样的代码就是给当前实例设置私有属性;换句话说,类中凡是不带this的代码统统都与它创建的实例无关,充其量也就是这个函数私有上下文中的私有变量而已。

function Func(){
	console.log(this);
}
Func.prototype.getNum = function getNum(){
	console.log(this);
}
let f = new Func();

构造函数执行,构造函数体中的this指向当前类的实例——f,因此输出的this为f;那原型上getNum方法中的this呢?那就说不好了,上面的代码中getNum方法只是创建还没执行,至于最终输出的this是谁就取决于执行时啥情况了。

因此,原型上方法中的this不一定是实例,要看执行时前面有没有点( 即当作普通函数看待 )

ES6中的箭头函数

箭头函数没有自己的this,它的this继承自上级上下文中的this——这一点和块级上下文类似。(悄悄说,箭头函数不光没有自己的this,还没有arguments实参集合呢)

let obj = {
	func:function(){
		console.log(this);
	}
	sum:()=>{
		console.log(this);
	}
}
obj.func();       // this:obj
obj.sum();        // this:window 
obj.sum.call(obj);    // this:window

① 执行obj.func(),对象成员访问执行函数,“点”前是obj,因此this指向obj。
② 执行箭头函数sum,它没有自己的this,于是找到上级上下文(全局)中的this->window。
③ 试图强行把sum中的this通过call函数修改为obj:我都没有this!你强行修改有用嘛!没用!

遇到箭头函数中使用this的情况,不管代码是咋写的,直接去找它上级上下文中的this。

尽管不建议乱用箭头函数( 因为this的特殊原因 ),但不得不承认在某些需求下,使用箭头函数非常的方便!真香!

let obj = {
	i:0,
	func(){        // 注意哦,这不是箭头函数,而是ES6中普通函数的简写
		setTimeout(function(){
			this.i++;
			console.log(obj);    // {i:0,func:f}
		},1000);
	}
}
obj.func();
// 我就想问,1秒钟之后,obj中i的值是多少嘞?答:它还是0

在func函数内部,this指的是obj没错。而定时器中回调函数中的this可是window啊,所以自加一的操作是给window.i的,跟obj中的 i 一毛钱关系都么得。
那我想让这个++的操作作用在obj中的i,应该咋写?
一个经典的操作是这样的:

let obj = {
	i:0,
	func(){ 
		let _this = this;     // 这里的this是obj,我先用一个变量把它保存起来       
		setTimeout(function(){
			_this.i++;         // 这样就是让之前保存的obj中的i ++啦
			console.log(obj);     // {i:1,func:f}
		},1000);
	}
}
obj.func();

另一种方法:基于bind把函数中的this预先处理为obj

let obj = {
	i:0,
	func(){ 
		setTimeout(function(){
			this.i++;        
			console.log(obj);     // {i:1,func:f}
		}.bind(this),1000);   // 这里强行让内部这个回调函数中的this指向外层的this(即obj)
	}
}
obj.func();

duang~真香警告!

let obj = {
	i:0,
	func(){      
		setTimeout(()=>{  
			this.i++;       // 箭头函数中没有自己的this,这里用的this是继承自外层的,也就是func函数的this->obj!
			console.log(obj);    // {i:0,func:f}
		},1000);
	}
}
obj.func();

call/apply/bind方法

这三兄弟可🐂🍺了!强制手动改变函数中的this指向,前三种情况使用这种方法后,都以手动改变的为主。简单粗暴!
emmm为啥只有前三种情况,难道还有漏网之鱼???
(箭头函数内心os:劳资都没有this,随你怎么改,都对我不起作用蛤蛤蛤蛤蛤蛤!)

这三个函数都在函数类的原型对象Function.prototype上,因此所有的函数都能基于原型链__proto__找到并调用这三个方法。

  • call方法用法:[function].call([context],params1,params2,…)
    含义:执行call方法时,会将后面的参数传递给[function]执行,并把函数中的this修改为[context]
    @ [function]: 希望改变自身this的那个函数
    @ [context]: 希望把this变为它!
    @ params1, params2, … : 传递给函数的参数

    第一个参数不传 / null / undefined时:
    非严格模式——this:window
    严格模式——传谁this就是谁,不传就是undefined

  • apply方法用法:[function].call([context],[params1,params2,…])
    与call方法相同,唯一区别在于传递参数的方式。apply方法不是将参数一项项传给函数,而是将所有参数保存在一个数组中。( 其实内部也会一项项传给function,但这就不需要我们操心啦 )

    let obj = {
    	name:'OBJ'
    }
    function func(){
    	console.log(this,x,y);
    }
    func();  // 这就是一个普通函数执行,输出window undefined undefined
    obj.func();   // 这里有坑,提示:obj和func有半毛钱关系么?
    func.call(obj,10,20);  // 这下就扯上关系了,强行把func中的this改为obj,输出obj 10 20
    func.apply(obj,[10,20]);  // 同上,输出obj 10 20
    
  • bind方法用法:[function].call([context],params1,params2,…)
    bind的语法与call相同,作用与以上两个方法均不同。它是预先修改this,预先存储参数,而函数不被立即执行;也就是说,call / apply都是立即执行函数的。

// 需求:把func函数绑定给body的click事件,触发body的click事件时,func函数执行
// 要让func函数中的this,并给func函数传递参数10,20
// 方案一:
document.body.onclick = func.call(obj,10,20);
// 方案二:
document.body.onclick = function anonymous(){
	func.call(obj,10,20);
}
// 方案三:
document.body.onclick = func.bind(obj,10,20);

依次剖析一下这三种方案:

  • 方案一:不可取
    func.call(obj,10,20)会立即执行func函数(并修改this&传参),它哪管你什么时候点击body。这种写法实际上是将func.call(obj,10,20)执行的结果作为值绑定给body点击事件。问题来了,call函数执行的返回值还是我们想要的么?No!
  • 方案二:可!
    将一个匿名函数绑定给body的点击事件,也就是将这个匿名函数的地址绑定给了事件,并没有立即执行。当点击事件触发时,将匿名函数执行,然后执行func.call。事实上,在bind方法之前,大家就是这样处理的。已经和bind的原理非常类似了。
  • 方案三:也可!
    bind函数不会立即执行,而是预先修改this,并预先存储需要传递的参数。后续需要时(事件触发)再执行函数。
    当然啦,如果你知道bind的内部机制是怎样的,理解起来就更加简单啦!

预知内部机制,且听下回分解~

感谢阅读!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值