关于this,你真的了解吗?

2021年09月15日 阅读数:1
这篇文章主要向大家介绍关于this,你真的了解吗?,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

关于this,你真的了解吗? 你能清楚的说明this在函数调用模式、方法调用模式、构造调用模式、特指调用模式下不一样的区别吗? 能区分严格模式和非严格模式对this影响吗? 知道箭头函数下的this为何与四种调用模式不一致吗? 若是你回答这些问题有疑虑,这篇文章就是为你准备的。es6

前言

我曾觉得func()其实就是window.func()编程

function func(){
	console.log('this : ' + this);
}

func();//this : [object Window]
window.func();//this : [object Window]

直到数组

'use strict'
function func(){
	console.log('this : ' + this);
}
func();//this : undefined
window.func();//this : [object Window]

也曾为输出inside  this : [object Window] 而困惑不已浏览器

function outside(){
	console.log('outside  this : ' + this);//outside  this : [object Object]
	function inside(){
		console.log('inside  this : ' + this);//inside  this : [object Window]
	}
	inside();
}
let obj = {
	outside : outside
}
obj.outside();

曾感慨Java之美好[1],唾弃JavaScript中this的‘灵活’。
...app

一直到我尝试总结出this的规律:
1.构造函数中的this关键字在任何模式下都指向new出来的对象;ide

2.严格模式下this关键字指向调用该函数的对象,若是该函数未被对象调用,则 this === 'undefined';函数

3.非严格模式下this关键字指向调用该函数的对象,若是该函数未被对象调用 this === window;this

再到后来,拜读了JavaScript语言精粹,知晓了4种调用模式,知晓了箭头函数的一个我未曾知晓的做用,结合过往,我感受本身已经摸清了this的规律,亦或者至少摸清了一部分的规律,特撰此文,做为总结;prototype

首先,此文全部代码运行环境皆为浏览器,因此我不会强调global;
再者,function中this指向被肯定于function被调用时(抛开箭头函数和class不论),
相似像下面这种代码,我以为没有提的必要;代码规范

function func(){
	console.log('this : ' + this);
}
func;
setTimeout(func,100); 
//我想表达的意思是 func是被setTimeout调用的 一样我也能够写一个mySetTimeout
mySetTimeout = function(func,delay){
	setTimeout(func.bind({}),delay); //咱们应该把目光放在function调用时
}
mySetTimeout(func,100); 
//在不运行代码的前提下,若是不看mySetTimeout代码,能准确判断this是什么吗?

最后,我不想提with,由于with的使用每每会引发歧义,就如同下面的代码,明明调用时的代码如出一辙,但一个在全局做用域window中调用func,而另外一个在obj的做用域中调用,输出的结果天差地别。

function func(){
	console.log('this : ' + this);
}
let obj = {};
with(obj){
	func();//this : [object Window]
}
obj.func = func;
with(obj){
	func();//this : [object Object]
}

接下来的内容我将如下图中的思路展开:
关于this,你真的了解吗?_this

ES6以前

这里的主要思路仍是沿用的JavaScript语言精粹。

函数调用模式

JavaScript中的function不一样于Java,Java虽说万物皆对象,可是基础类型和function就不是对象。Java中的function只是对象的行为,可是JavaScript不一样,JavaScript虽然同时包含了一些像原型、函数柯里化等编程思想,可是在万物皆对象这一方面,反而比Java更像是面向对象编程。JavaScript中的function是支持直接调用的。
在非严格模式:

function func(){
	console.log('this : ' + this);
}
func();//this : [object Window]

在严格模式:

'use strict'
function func(){
	console.log('this : ' + this);
}
func();//this : undefined

方法调用模式

方法调用模式就是把function当成对象的行为来调用,既然是对象的行为,那么function中的this指向的固然是这个调用的对象了。
在非严格模式:

let _self = null;
function func(){
	_self = this;
}
let obj = {
	 func : func
}
obj.func();
console.log('_self === obj : ' + (_self === obj));//_self === obj : true

在严格模式:

'use strict'
let _self = null;
function func(){
	_self = this;
}
let obj = {
	 func : func
}
obj.func();
console.log('_self === obj : ' + (_self === obj));//_self === obj : true

构造调用模式

构造调用模式就是把function当作构造函数调用,在其左边加上new关键字,为了迎合代码规范,这里的function我将以大写字母开头。
在非严格模式:

let _self = null;
function Person(){
	_self = this;
}
let person = new Person();
console.log('_self === person : ' + (_self === person));//_self === person : true

在严格模式:

'use strict'
let _self = null;
function Person(){
	_self = this;
}
let person = new Person();
console.log('_self === person : ' + (_self === person));//_self === person : true

构造函数这里我以为有必要扩展一下:
1.构造函数中返回对象(非基础类型),会影响上面的结果;

let _self = null;
function Person(){
	_self = this;
	return window;
}
let person = new Person();
console.log('_self === person : ' + (_self === person));//_self === person : false
console.log('window === person : ' + (window === person));//window === person : true

2.省略new关键字,一样会影响上面的结果;

let _self = null;
function Person(){
	_self = this;
}
let person = Person();
console.log('_self === person : ' + (_self === person));//_self === person : false
console.log('window === person : ' + (window === person));//window === person : false
console.log('typeof person : ' + typeof person);//typeof person : undefined

在Person调用时省略new关键字还可能会污染全局做用域:

function Person(){
	this.personName = 'person';
}
let person = Person();
console.log('person.personName : '+person.personName);//Cannot read property 'personName' of undefined
console.log('window.personName : '+window.personName);//window.personName : person

蠢办法解决调用构造函数不用new关键字的:

function Person(){
	if(this === window){
        throw Error('You must use the new keyword.');
    }
	this.personName = 'person';
}
let person = Person();//You must use the new keyword.

改进版

function Person(){
	let context;
    (function(){
        context = this;
    }())
    if(this === context){
        throw Error('You must use the new keyword.');
    }
	this.personName = 'person';
}
let person = Person();//You must use the new keyword.

特指调用模式

bind虽然是es6的,可是我也放到这个模式一块儿讲了,由于我以为把bind和apply、call一块儿讲可能会更容易理解一些。

apply

apply的第一个参数是绑定的对象,第二个参数是array。call和apply的不一样之处在于call的第二个参数对于function中arguments的第一位,第三个参数对于function中的arguments的第二位,以此类推;而apply的第二个参数对应function中的arguments。因为这里主要是讲this,因此第二个参数的例子就不提了,后面的call也同样。
在非严格模式:

function func(){
	console.log('this : ' + this);
}
func.apply({});//this : [object Object]
func.apply(window);//this : [object Window]
func.apply(null);//this : [object Window]
func.apply();//this : [object Window]

在严格模式:

'use strict'
function func(){
	console.log('this : ' + this);
}
func.apply({});//this : [object Object]
func.apply(window);//this : [object Window]
func.apply(null);//this : null
func.apply();//this : undefined

实现apply

知足条件

1.把第一个参数绑定到调用myApply的function运行时的this;
2.第二个参数应与调用myApply的function的arguments内容一致;
3.严格模式和非严格模式第一个参数为null或undefined时状况要与apply函数一致;

代码
Function.prototype.myApply = function(){
	var context,arr;
	//谁调用的myApply this就指向谁
	if(typeof this !== 'function'){
		throw Error('typeof this !== "function"');
	}
	context = arguments[0];
	arr = arguments[1] ? arguments[1] : [];
	if(typeof context === 'undefined' || context === null){
		//知足条件3
		context = (function(){
			return this;
		}());
	}
	if(typeof context === 'undefined'){
		this(...arr);
	}else{
		context.f = this;
		context.f(...arr);
	}
}

call

call若是只传入第一个参数,结果和只传入第一个参数的apply是一致的。
在非严格模式:

function func(){
	console.log('this : ' + this);
}
func.call({});//this : [object Object]
func.call(window);//this : [object Window]
func.call(null);//this : [object Window]
func.call();//this : [object Window]

在严格模式:

'use strict'
function func(){
	console.log('this : ' + this);
}
func.call({});//this : [object Object]
func.call(window);//this : [object Window]
func.call(null);//this : null
func.call();//this : undefined

实现call

知足条件

1.把第一个参数绑定到调用myCall的function运行时的this;
2.除第一个参数外其他参数组成的数组应与调用myCall的function的arguments内容一致;
3.严格模式和非严格模式第一个参数为null或undefined时状况要与call函数一致;

代码
Function.prototype.myCall = function(){
	var context,arr;
	//谁调用的myCall this就指向谁
	if(typeof this !== 'function'){
		throw Error('typeof this !== "function"');
	}
	context = arguments[0];
	//差别点 call与apply的传值方式所致
	arr = [...arguments].slice(1);//手动转型
	if(typeof context === 'undefined' || context === null){
		//知足条件3
		context = (function(){
			return this;
		}());
	}
	if(typeof context === 'undefined'){
		this(...arr);
	}else{
		context.f = this;
		context.f(...arr);
	}
}

bind

bind和call很类似,主要的不一样点在于func.call(window)立马就调用了,而func.bind(window)会返回一个绑定了window的function,可是这个function尚未执行。能够这样理解func.bind(window)()的效果与func.call(window)一致。
在非严格模式:

function func(){
	console.log('this : ' + this);
}

func.bind({})();//this : [object Object]
func.bind(null)();//this : [object Window]
func.bind()();//this : [object Window]
let obj = {
	func : func.bind(window)
}
obj.func();//this : [object Window]
//构造函数
let _self = null;
function Person(){
	_self = this;
}
let P = Person.bind(window);
let person = new P();
console.log('_self === person : ' + (_self === person));//_self === person : true
console.log('window === person : ' + (window === person));//window === person : false

在严格模式:

'use strict'
function func(){
	console.log('this : ' + this);
}

func.bind({})();//this : [object Object]
func.bind(null)();//this : null
func.bind()();//this : undefined
let obj = {
	func : func.bind(window)
}
obj.func();//this : [object Window]
//构造函数
let _self = null;
function Person(){
	_self = this;
}
let P = Person.bind(window);
let person = new P();
console.log('_self === person : ' + (_self === person));//_self === person : true
console.log('window === person : ' + (window === person));//window === person : false

从上面的例子,咱们不仅仅能够发现bind在严格模式和非严格模式下的不一样,还能够得出构造调用模式的优先级最高,bind其次,方法调用模式和函数调用模式最低。

实现bind

知足条件

1.把第一个参数绑定到调用myBind的function运行时的this;
2.将除第一个参数外其他参数与function中参数合并;
3.严格模式和非严格模式第一个参数为null或undefined时状况要与bind函数一致;

代码
Function.prototype.myBind = function(){
	var context,arr,_self;
	//谁调用的myBind this就指向谁
	if(typeof this !== 'function'){
		throw Error('typeof this !== "function"');
	}
	context = arguments[0];
	arr = [...arguments].slice(1);//手动转型
	if(typeof context === 'undefined' || context === null){
		//知足条件3
		context = (function(){
			return this;
		}());
	}
	_self = this;
	return function(){
		if(typeof context === 'undefined'){//严格模式
			_self(arr.concat(...arguments));
		}else{
			context.f = _self;
			context.f(arr.concat(...arguments));
		}
		
	}
}
ES6

据我所知,有一部分人,他们奉行箭头函数+class来解决一切问题。我对此观点的正确性不表态,可是这样作能减小不少判断this的麻烦。

箭头函数

箭头函数没有this,箭头函数中的this来自于它处于的做用域链中的上一层。我在前言中说过,我曾为输出inside  this : [object Window] 而困惑不已,可是我如今把代码略微修改一下,输出就将符合个人预期(inside继承了outside的this值)。

function outside(){
	console.log('outside  this : ' + this);//outside  this : [object Object]
	let inside = ()=>{
		console.log('inside  this : ' + this);//inside  this : [object Object]
	}
	inside();
}
let obj = {
	outside : outside
}
obj.outside();

要是把outside也改为箭头函数,结果又会大不同

let outside = ()=>{
	console.log('outside  this : ' + this);//outside  this : [object Window]
	let inside = ()=>{
		console.log('inside  this : ' + this);//inside  this : [object Window]
	}
	inside();
}
let obj = {
	outside : outside
}
obj.outside();

由于箭头函数的this值是继承于它身处的做用域上一层的this,outside上一层是全局做用域,不会再发生更改了,因此这里就算用方法调用模式,也没法改变this的值。
在非严格模式:

let func = ()=>{
	console.log('this : ' + this);
}
func();//this : [object Window]
let obj = {
	func : func
}
obj.func();//this : [object Window]
func.apply({});//this : [object Window]
func.call({});//this : [object Window]
func.bind({})();//this : [object Window]
func.apply(null);//this : [object Window]
func.apply();//this : [object Window]

let _self = null;
let Person = ()=>{
	_self = this;
}
let person = new Person();//Person is not a constructor

在严格模式:

'use strict'
let func = ()=>{
	console.log('this : ' + this);
}
func();//this : [object Window]
let obj = {
	func : func
}
obj.func();//this : [object Window]
func.apply({});//this : [object Window]
func.call({});//this : [object Window]
func.bind({})();//this : [object Window]
func.apply(null);//this : [object Window]
func.apply();//this : [object Window]

let _self = null;
let Person = ()=>{
	_self = this;
}
let person = new Person();//Person is not a constructor

观察上述代码运行结果可知:
1.严格模式和非严格模式对箭头函数中的this无影响;
2.箭头函数没法看成构造函数使用;
3.箭头函数中的this只与自身处于的做用域链上一层有关;

class

第一次看到class的用法时,我就不由感慨原型的强大,对于我这种之前使用Java的人来讲,class真的是太友好了。
在非严格模式:

let constructorThis = null;
let funcThis = null;
let staticFuncThis = null;
class Person{
	constructor(){
		constructorThis = this;
	}
	func(){
		funcThis = this;
	}
	static staticFunc(){
		staticFuncThis = this;
	}
}

let person = new Person();
person.func();
Person.staticFunc();

console.log('constructorThis === person : ' + (constructorThis === person));//constructorThis === person : true
console.log('funcThis === person : ' + (funcThis === person));//funcThis === person : true
console.log('staticFuncThis === person : ' + (staticFuncThis === person));//staticFuncThis === person : false
console.log('staticFuncThis : ' + staticFuncThis);//staticFuncThis : class Person...

在严格模式:

'use strict'
let constructorThis = null;
let funcThis = null;
let staticFuncThis = null;
class Person{
	constructor(){
		constructorThis = this;
	}
	func(){
		funcThis = this;
	}
	static staticFunc(){
		staticFuncThis = this;
	}
}

let person = new Person();
person.func();
Person.staticFunc();

console.log('constructorThis === person : ' + (constructorThis === person));//constructorThis === person : true
console.log('funcThis === person : ' + (funcThis === person));//funcThis === person : true
console.log('staticFuncThis === person : ' + (staticFuncThis === person));//staticFuncThis === person : false
console.log('staticFuncThis : ' + staticFuncThis);//staticFuncThis : class Person...

观察上述代码运行结果可知:
1.严格模式和非严格模式对class function中的this无影响;
2.构造函数和普通方法的this就是new出来的值(和方法调用模式、构造调用模式一致)
3.静态方法的this就是这个class(仍是和方法调用模式一致 毕竟是用class调用的静态方法)

结尾

因为本人水平有限,若有缺失和错误,还望告知。


  1. Java中function只能是方法,被对象或者类调用。非静态方法被对象调用时,this是这个调用的对象;静态方法被类调用时,则没有this; ↩︎