1.这其中的奥秘
这
关键字对我来说一直是个谜。
从Java、PHP或其他背景标准语言,这
是类方法中当前对象的实例。这
不能在方法外使用,并且这样简单的方法不会造成混淆。
在JavaScript中,情况有所不同:这
是函数调用(也称为执行)的上下文。该语言有4种函数调用类型:
- 函数调用:
alert(“你好,世界!”)
- 方法调用:
console.log(“你好,世界!”)
- 构造函数调用:
新RegExp('\\d')
- 间接调用:
alert.call(未定义,“Hello World!”)
每个调用类型都以其方式定义上下文,因此这
行为与开发人员预期的不同。
此外严格模式也会影响执行上下文。
理解的关键这
关键字可以清楚地查看函数调用及其对上下文的影响。
本文重点介绍调用解释,函数调用如何影响这
并展示了识别这
.
开始之前,让我们熟悉几个术语:
- 调用函数的代码是执行生成函数体的代码,或者简单地打电话函数。例如
解析Int
功能调用是解析Int('15')
.
- 上下文的值
这
在函数体中。
- 范围函数的是函数体中可访问的变量和函数的集合。
2.函数调用
函数调用当计算为函数对象的表达式后跟左括号时执行(
,以逗号分隔的参数表达式列表和右括号)
。例如解析Int('18')
.
函数调用的一个简单示例:
hello(“世界”)
是函数调用:你好
表达式求值为函数对象,后跟一对带“世界”
参数。
函数调用表达式不能是属性访问器 对象myFunc()
,这将创建方法调用。例如[1,5].连接(',')
是不函数调用,但方法调用。请记住他们之间的区别。
一个更高级的示例是IIFE公司(立即调用的函数表达式):
IIFE也是一个函数调用:第一对括号(函数(名称){…})
是计算为函数对象的表达式,后跟一对带“世界”
参数:(“世界”)
.
2.1. 这是在函数调用中
这
是全局对象在函数调用中。
全局对象由执行环境确定。在浏览器中,全局对象是窗口
对象。
在函数调用中,执行上下文是全局对象。
让我们检查以下函数中的上下文:
console.log(这个===窗口);//=>真实的
this.myNumber=20;//将“myNumber”属性添加到全局对象
当时总和(15,16)
被调用时,JavaScript会自动设置这
作为全局对象(窗口
在浏览器中)。
何时这
在任何函数作用域(最上面的作用域:全局执行上下文)之外使用,它也等于全局对象:
console.log(这个===窗口);//=>真实的
console.log(window.myString);//=>'你好,世界!”
<script type=“text/javascript”>
console.log(这个===窗口);//=>真实的
2.2. 这是一个函数调用,严格模式
这
是未定义
在严格模式下的函数调用中
严格模式可启动ECMAScript 5.1版,这是JavaScript的受限变体。它提供了更好的安全性和更强的错误检查。
要启用严格模式,请放置指令'使用严格'
在函数体的顶部。
启用后,严格模式会影响执行上下文这
成为未定义
在常规函数调用中。执行上下文是不全局对象,与上述情况相反2.1.
在严格模式下调用的函数示例:
console.log(此===未定义);//=>真实的
何时乘法(2,5)
在严格模式下作为函数调用,这
是未定义
.
严格模式不仅在当前范围内有效,而且在内部范围内也有效(对于内部声明的所有函数):
console.log(此===未定义);//=>真实的
concat('Hello','World!');//=>“你好,世界!”
'使用严格'
坐在执行
主体,在其范围内启用严格模式。因为凹面(concat)
在中声明执行
范围,它继承了严格模式。还有祈祷concat(“你好”,“世界!”)
制造这
成为未定义
.
单个JavaScript文件可以包含严格模式和非严格模式。因此,对于相同的调用类型,在单个脚本中可能有不同的上下文行为:
console.log(这个===窗口);//=>真实的
console.log(此===未定义);//=>真实的
//nonStrictSum()在非限制模式下作为函数调用
//nonStrictSum()中的这个是窗口对象
//strictSum()在严格模式下作为函数调用
2.3。陷阱:这是内在功能
⚠️ 函数调用的一个常见陷阱是认为这
在内部函数中与在外部函数中相同。
👍 内部函数(箭头函数除外)的上下文仅取决于其自身的调用类型,而不取决于外部函数的上下文。
制造这
具有所需的值,使用间接调用(使用.call()
或.apply()
,请参阅5)或创建绑定函数(使用.bind()
,请参阅6).
以下示例计算两个数字的总和:
console.log(这===数字);//=>真实的
console.log(这===数字);//=>假
返回this.numberA+this.number B;
numbers.sum();//=>NaN或在严格模式下抛出TypeError
⚠️numbers.sum()
是对对象的方法调用(请参见三。)因此这
等于数字
.计算()
函数在内部定义总和()
,所以您可能希望这
作为数字
调用时的对象计算()
也是。
计算()
是函数调用(但是不方法调用),因此这里这
是全局对象窗口
(案例2.1.)或未定义
严格模式下(案例2.2.). 即使外部功能numbers.sum()
上下文为数字
物体,它在这里没有影响。
的调用结果numbers.sum()
是NaN公司
(或抛出错误TypeError:无法读取未定义的属性“numberA”
严格模式下)。绝对不是预期的结果5 + 10 = 15
都是因为计算()
未正确调用。
👍为了解决这个问题,计算()
函数必须使用与numbers.sum()
方法,以访问此编号A
和此编号B
属性。
一种解决方案是手动更改计算()
打电话给想要的人calculate.call(this)
(函数的间接调用,请参阅第节5):
console.log(这===数字);//=>真实的
console.log(这===数字);//=>真实的
返回this.numberA+this.number B;
return calculate.call(this);
calculate.call(this)
执行计算()
函数,但还将上下文修改为指定为第一个参数的值。
现在此编号A+此编号B
与相同编号A+编号B
。该函数返回预期结果5 + 10 = 15
.
另一个稍微好一点的解决方案是使用箭头功能:
console.log(这===数字);//=>真实的
console.log(这===数字);//=>真实的
返回this.numberA+this.number B;
箭头函数解析这
词汇上,或者换句话说,使用这
的值numbers.sum()
方法。
3.方法调用
A类方法是存储在对象属性中的函数。例如:
const消息=myObject.helloMethod();
hello方法
是一种方法myObject(我的对象)
。使用属性访问器myObject.hello方法
以访问该方法。
方法调用当表达式的形式为属性访问器求值为函数对象的后接左括号(
,以逗号分隔的参数表达式列表和右括号)
.
回顾前面的示例,myObject.helloMethod()
是的方法调用hello方法
在对象上myObject(我的对象)
.
方法调用的更多示例包括:[1,2].加入(',')
或/\s/.test(“美丽的世界”)
.
了解两者之间的差异函数调用(参见第节2)和方法调用很重要!
方法调用需要属性访问器调用函数的窗体(对象myFunc()
或对象['myFunc']()
),而函数调用不(我的函数()
).
const words=['Hello','World'];
return new Date().toString();
parseFloat('16.6');//函数调用
3.1. 这是在方法调用中
这
是拥有该方法的对象在方法调用中
在对象上调用方法时,这
是拥有该方法的对象。
让我们使用递增数字的方法创建一个对象:
console.log(这===计算);//=>真实的
打电话计算增量()
使上下文增量
函数为计算
对象。所以使用this.num这个
增加数字属性效果很好。
让我们来看另一个案例。JavaScript对象从其原型
。在对象上调用继承的方法时,调用的上下文仍然是对象本身:
console.log(this===myDog);//=>真实的
对象.create()
创建新对象我的狗
并从第一个参数设置其原型。我的狗
对象继承说出姓名
方法。
何时myDog.sayName()
执行,我的狗
是调用的上下文。
2015年ECMAScript班
语法,方法调用上下文也是实例本身:
console.log(此===接地);//=>真实的
3.2. 陷阱:将方法与其对象分离
⚠️ 可以将方法从对象提取到单独的变量中仅const=myObj.myMethod
。单独调用方法时单独()
,与原始对象分离,您可能会认为这
是对象myObject(我的对象)
在此基础上定义方法。
👍 正确地说,如果在没有对象的情况下调用方法,则会发生函数调用,其中这
是全局对象窗口
或未定义
在严格模式下(请参见2.1和2.2).
绑定函数仅const=myObj.myMethod.bind(myObj)
(使用.bind()
,请参阅6)通过绑定修复上下文这
拥有该方法的对象。
以下示例定义宠物
构造函数并生成其实例:我的猫
.然后设置超时()
1秒日志后我的猫
对象信息:
控制台.log(this===myCat);//=>假
console.log(`${this.type}有${this.legs}腿`);
setTimeout(myCat.logInfo,1000);
⚠️ 你可能会认为setTimeout(myCat.logInfo,1000)
将调用myCat.log信息()
,它应该记录有关我的猫
对象。
不幸的是,当作为参数传递时,该方法与其对象分离:设置超时(myCat.logInfo)
以下情况等效:
setTimeout(myCat.logInfo);
const extractedLogInfo=myCat.logInfo;
setTimeout(extractedLogInfo);
当分开时logInfo(日志信息)
作为函数调用,这
是全局对象或未定义
严格模式下(但不 我的猫
对象)。因此,对象信息没有正确记录。
👍 函数与对象的边界使用.bind()
方法(请参见6). 如果分隔的方法与绑定我的猫
对象,上下文问题得到解决:
控制台.log(this===myCat);//=>真实的
console.log(`${this.type}有${this.legs}腿`);
const boundLogInfo=myCat.logInfo.bind(myCat);
setTimeout(boundLogInfo,1000);
myCat.logInfo.bind(myCat)
返回一个执行方式与logInfo(日志信息)
,但有这
作为我的猫
甚至在函数调用中。
另一种解决方案是定义日志信息()
方法作为箭头函数,它绑定这
词汇上:
控制台.log(this===myCat);//=>真实的
console.log(`${this.type}有${this.legs}腿`);
setTimeout(myCat.logInfo,1000);
如果您想使用类和绑定这
对于方法中的类实例,使用箭头函数作为类属性:
控制台.log(this===myCat);//=>真实的
console.log(`${this.type}有${this.legs}腿`);
setTimeout(myCat.logInfo,1000);
4.构造函数调用
构造函数调用在以下情况下执行新的
关键字后面是计算为函数对象的表达式,即左括号(
,以逗号分隔的参数表达式列表和右括号)
.
施工调用示例:新宠物('cat',4)
,新RegExp('\\d')
.
本例声明了一个函数国家
,然后将其作为构造函数调用:
this.traveled=布尔值(移动);//转换为布尔值
Country.prototype.travel=函数(){
const france=新国家('france',false);
新国家('France',false)
是的构造函数调用国家
功能。此调用创建一个新对象,该对象名称
属性为“法国”
.
如果调用构造函数时没有参数,则可以省略括号对:新国家/地区
.
从ECMAScript 2015开始,JavaScript允许使用班
语法:
const paris=新城市('paris',false);
新城(“巴黎”)
是构造函数调用。对象的初始化由类中的一个特殊方法处理:建造师
,其中有这
作为新创建的对象。
构造函数的作用是初始化实例。构造函数调用创建一个新的空对象,该对象继承构造函数原型的属性。
当属性访问器myObject.my函数
前面是新的
关键字,JavaScript执行构造函数调用,但是不是方法调用.
例如新建myObject.myFunction()
:首先使用属性访问器提取函数extractedFunction=myObject.myFunction
,然后作为构造函数调用以创建新对象:新建extractedFunction()
.
4.1. 这是在构造函数调用中
这
是新创建的对象在构造函数调用中
构造函数调用的上下文是新创建的对象。构造函数使用来自构造函数参数的数据初始化对象,为属性设置初始值,附加事件处理程序等。
让我们检查以下示例中的上下文:
const fooInstance=新Foo();
fooInstance.property;//=>'默认值'
新Foo()
正在上下文所在的位置进行构造函数调用fooInstance
.内部富
对象已初始化:此属性
指定了默认值。
使用时会发生相同的情况班
语法(在ES2015中可用),只有初始化发生在建造师
方法:
const barInstance=新Bar();
barInstance.properties;//=>'默认值'
当新Bar()
执行时,JavaScript创建一个空对象,并将其作为构造函数()
方法。现在可以使用向对象添加属性这
关键词:this.property='默认值'
.
4.2. 陷阱:忘记新事物
有些JavaScript函数不仅在作为构造函数调用时创建实例,而且在作为函数调用时也创建实例。例如注册Exp
:
const reg1=新RegExp('\\w+');
const reg2=RegExp('\\w+');
reg1.source===reg2.source;//=>真实的
执行时新RegExp('\\w+')
和RegExp('\\w+')
,JavaScript创建等效的正则表达式对象。
⚠️ 使用函数调用创建对象是一个潜在的问题(不包括工厂模式),因为当新的
缺少关键字。
以下示例说明了该问题:
车辆
是一个设置类型
和车轮计数
上下文对象上的属性。执行时车辆(“汽车”,4)
一个物体汽车
返回,它具有正确的属性:汽车类型
是“汽车”
和汽车车轮计数
是4
.
您可能认为它很适合创建和初始化新对象。
然而,这
是窗口
函数调用中的对象(请参见2.1.),因此车辆(“汽车”,4)
在上设置属性窗口
对象。这是一个错误。不会创建新对象。
👍 确保使用新的
运算符(如果需要构造函数调用):
const brokenCar=车辆(“破损车辆”,3);
新车(“汽车”,4)
工作良好:创建并初始化一个新对象是因为新的
关键字出现在构造函数调用中。
构造函数中添加了验证:这辆车
,以确保执行上下文是正确的对象类型-无论何时车辆(“破损汽车”,3)
执行时没有新的
引发异常:错误:调用错误
.
5.间接调用
间接调用在使用调用函数时执行myFun.call()
或myFun.apply()
方法。
JavaScript中的函数是一级对象,这意味着函数就是一个对象。函数对象的类型为功能
.
从方法列表函数对象具有,.call()
和.apply()
用于调用具有可配置上下文的函数。
myFunction.call(thisArg、arg1、arg2…)
接受第一个参数此Arg
作为调用的上下文和参数列表arg1、args2。。。
作为参数传递给被调用函数的。
myFunction.apply(thisArg,[arg1,arg2,…])
接受第一个参数此Arg
作为调用的上下文和参数数组[arg1,args,…]
作为参数传递给被调用函数的。
以下示例演示了间接调用:
sum.call(未定义,10,2);//=>12
sum.apply(未定义,[10,2]);//=>12
sum.call()
和sum.apply()
两者都使用调用函数10
和2
论据。
5.1. 这是间接调用
这
是第一个参数属于.call()
或.apply()
在间接调用中
这
在间接调用中,是作为第一个参数传递给.call()
或.apply()
.
以下示例显示了间接调用上下文:
console.log(这===兔子);//=>真实的
concatName.call(rabbit,'Hello');//=>'你好,白兔
concatName.apply(rabbit,['Bye']);//=>'再见白兔
当函数应该在特定上下文中执行时,间接调用非常有用。例如,要解决函数调用的上下文问题,其中这
总是窗口
或未定义
在严格模式下(请参见2.3.). 它可以用于模拟对对象的方法调用(请参阅前面的代码示例)。
另一个实际示例是在ES5中创建类的层次结构以调用父构造函数:
console.log(兔子的这个实例);//=>真实的
console.log(兔子的这个实例);//=>真实的
const-myRabbit=新兔子(“白色兔子”,4);
我的兔子;//{name:'白兔',countLegs:4}
Runner.call(这个名字)
里面兔子
间接调用父函数来初始化对象。
6.绑定函数
绑定函数是其上下文和/或参数绑定到特定值的函数。使用创建绑定函数.bind()
方法。原始函数和绑定函数共享相同的代码和范围,但执行时的上下文和参数不同。
myFunc.bind(thisArg[,arg1,arg2,…)
接受第一个参数此Arg
作为上下文和可选参数列表arg1、arg2。。。
绑定到。.bind()
返回上下文绑定到的新函数此Arg
和参数arg1、arg2。。。
.
以下代码创建了一个绑定函数,然后调用它:
const double=multiply.bind(2);
乘法绑定(2)
返回新的函数对象双重的
,与数字绑定2
.乘
和双重的
具有相同的代码和范围。
与…相反.apply()
和.call()
方法(请参见5),它立即调用函数.bind()
方法只返回一个应该稍后使用预定义的这
值。
6.1. 这在一个绑定函数中
这
是第一个参数属于myFunc.bind(thisArg)
调用绑定函数时
的作用.bind()
是创建一个新函数,该调用将上下文作为第一个参数传递给.bind()
它是一种强大的技术,允许使用预定义的这
值。
让我们看看如何配置这
绑定函数的:
const boundGetNumbers=numbers.getNumbers.bind(numbers);
boundGetNumbers();//=>[3, 5, 10]
const simpleGetNumbers=数字.getNumbers;
simpleGetNumbers();//=>未定义或在严格模式下抛出错误
numbers.getNumbers.bind(数字)
返回函数绑定GetNumbers
绑定到哪个上下文数字
.然后绑定GetNumbers()
使用调用这
作为数字
并返回正确的数组对象。
功能数字.get数字
提取到变量中简单获取数字
没有约束力。稍后的函数调用simpleGetNumbers()
有这
作为窗口
或未定义
严格模式,但不是数字
对象(请参见3.2. 陷阱). 在这种情况下simpleGetNumbers()
将无法正确返回数组。
6.2. 紧密的上下文绑定
.bind()
制作永久上下文链接并将始终保留它。绑定函数在使用时无法更改其链接上下文.call()
或.apply()
在不同的背景下,甚至反弹都没有任何影响。
只有绑定函数的构造函数调用才能更改已绑定的上下文,但这不是您通常会做的事情(构造函数调用必须使用有规律的,非绑定函数)。
以下示例创建了一个绑定函数,然后尝试更改其已预定义的上下文:
const one=getThis.bind(1);
仅限新建()
更改绑定函数的上下文。其他类型的调用总是有这
等于1
.
7.箭头功能
箭头函数旨在以较短的形式声明函数,并且词法上绑定上下文。
它可以使用以下方式:
[1,2,5,6].筛选器(项=>项%2===0);//=>[2,6]
箭头函数有简单的语法,没有verbose关键字功能
。当arrow函数只有1条语句时,甚至可以省略返回
关键字。
箭头功能是匿名的,但它无法推断名称。它没有词法函数名(这对于递归、分离事件处理程序非常有用)。
此外,它也没有提供论据
对象,而不是常规函数。失踪者论据
使用ES2015修复静止参数:
const-sumArguments=(…args)=>{
console.log(参数类型);//=>'未定义'
return args.reduce((result,item)=>result+item);
sumArguments(5,5,6);//=>16
7.1. 此为箭头函数
这
是封闭上下文箭头函数的定义位置
arrow函数不创建自己的执行上下文,但接受这
从定义它的外部函数。换句话说,箭头函数解析这
词汇上。
以下示例显示上下文透明度属性:
console.log(this===myPoint);//=>真实的
console.log(this===myPoint);//=>真实的
console.log(this.x+':'+this.y);//=>'95:165英寸
设置超时()
使用相同的上下文调用arrow函数(我的观点
对象)作为日志()
方法。如图所示,箭头函数从定义它的函数“继承”上下文。
本例中的正则函数创建自己的上下文(窗口
或未定义
严格模式下)。因此,要使相同的代码正确地与函数表达式一起工作,需要手动绑定上下文:setTimeout(function(){…}.bind(this))
这很冗长,使用箭头函数是一种更简洁的解决方案。
如果箭头函数是在最顶层的作用域中定义的(在任何函数之外),则上下文始终是全局对象(窗口
在浏览器中):
console.log(这个===窗口);//=>真实的
console.log(getContext()===窗口);//=>真实的
箭头函数与词法绑定这
一劳永逸.这
即使使用上下文修改方法也无法修改:
console.log(这===数字);//=>真实的
console.log(这===数字);//=>真实的
get.bind([0])();//=>[1, 2]
无论箭头如何工作获取()
被称为,它总是保持词汇上下文数字
.与其他上下文的间接调用get.call([0])
或.get.apply([0])
,正在重新绑定get.bind([0])()
没有效果。
箭头函数不能用作构造函数。将其作为构造函数调用新建get()
抛出错误:TypeError:get不是构造函数
.
7.2. 陷阱:使用箭头函数定义方法
⚠️ 您可能希望使用箭头函数来声明对象上的方法。很公平:他们的声明与函数表达式:(参数)=>{…}
而不是函数(参数){..}
.
这个例子定义了一个方法格式()
在一个类上周期
使用箭头功能:
Period.prototype.format=()=>{
console.log(这个===窗口);//=>真实的
返回this.hours+“hours and”+this.minutes+“minutes”;
const walkPeriod=新周期(2,30);
walkPeriod.format();//=>'未定义小时和未定义分钟
自格式
是一个箭头函数,在全局上下文(最顶部范围)中定义,它具有这
作为窗口
对象。
即使格式
作为对象上的方法执行walkPeriod.format()
,窗口
保留为调用的上下文。之所以会发生这种情况,是因为arrow函数有一个静态上下文,它不会因不同的调用类型而改变。
该方法返回'未定义小时和未定义分钟'
,这不是预期的结果。
👍 函数表达式解决了这个问题,因为正则函数会改变上下文取决于调用:
Period.prototype.format=函数(){
console.log(this===walkPeriod);//=>真实的
返回this.hours+“hours and”+this.minutes+“minutes”;
const walkPeriod=新周期(2,30);
walkPeriod.format();//=>'2小时30分钟
walkPeriod.format()
是对对象的方法调用(请参见3.1.)根据上下文walkPeriod(步行周期)
对象。这个小时
计算结果为2
和这个小坚果
到30
,因此该方法返回正确的结果:“2小时30分钟”
.
8.结论
因为函数调用对这
,从现在开始不要问问自己:
在哪里这
取自?
但是做问问自己:
如何调用*`函数*?
对于箭头功能,请自问:
是什么这
在箭头函数所在的外部函数内定义?
这种心态是正确的这
这样你就不会头疼了。
如果您有一个有趣的上下文陷阱示例,或者只是在案例中遇到了困难,请在下面写下评论,让我们一起讨论!