export function函数传参_从底层看前端(七)—— JavaScript到底有多少种函数?

e945ccb92b41098e6fb2c83b36d99532.png

在上篇文章中我们了解到了执行上下文是什么,也知道了任何语句的执行都会依赖特定的上下文。

一旦上下文被切换,整个语句的效果可能都会发生变化。那么,切换上下文的时机就显得非常重要。

在JavaScript中,切换上下文最主要的场景就是函数调用。在这篇文章中,我们就来讲讲函数调用切换上下文的事情。

在我们讲函数调用之前,我们先来认识一下函数家族。

函数

在ES2018中,函数已经是一个很复杂的体系了,我在这里整理了一下。

第一种,普通函数,用function关键字定义的函数。

示例:

function foo(){//code
}
第二种,箭头函数:用 => 运算符定义的函数。

示例:

const foo = ()=>{//code
}
第三种,在class中定义的函数。

示例:

class C {foo(){//code}
}
第四种,生成器函数:用 function* 定义的函数。

示例:

function* foo(){//code
}
第五种:类。

用class定义的类,实际上也是函数。

示例:

class Foo {constructor(){//code}
}
第六,七,八种,异步函数:普通函数,箭头函数和生成器函数前加上async关键字。

示例:

async function foo(){// code
}
const foo = async () => {// code
}
async function foo*(){// code
}

ES6以来,大量加入的新语法极大地方便了我们编程的同时,也增加了很多我们理解的心智负担。要想认识这些函数的执行上下文切换,我们必须要对他们行为上的区别有所了解。

对普通变量而言,这些函数并没有本质区别,都是遵循了'继承定义时的环境'的规则,它们的一个行为差异在于this关键字。

那么,this 关键字是什么呢?

this 关键字行为

this 是JavaScript中的一个关键字,它的使用方法类似一个变量(但是this跟变量有很多不同)。

this是执行上下文中很重要的一个组成部分。同一个函数调用方式不同,得到的this值也不同,我们看一个例子:

function showThis(){console.info(this)
}var o = {showThis: showThis
}showThis(); //global
o.showThis(); //o

在这个例子中,我们定义了函数showThis,我们把它赋值给了一个对象o的属性,然后尝试分别使用两个引用来调用同一个函数,结果得到了不同的this值。

普通函数的 this 值由'调用它所使用的的引用'来决定,其中奥秘就在于:我们获取函数的表达式,它实际上返回的并非函数本身,而是一个 Reference 类型(其中标准类型之一)。

Reference 类型由两部分组成,一个对象和一个属性值。不难理解 o.showThis 产生的Reference类型,即由对象 o 和属性'showThis'构成。

当做一些算术运算时,Reference类型会被解引用,即获取真正的值来参与运算,而类似函数调用,delete等操作,都需要用到 Reference 类型中的对象。

在这个例子中,Reference类型中的对象被当做this值,传入了执行函数的上下文中。

至此,我们对this的解释已经非常清晰了,调用函数时使用的引用,决定了函数执行时刻的this值。

实际上从运行时的角度来看,this跟面向对象毫无关联,它是与函数调用的表达式相关。

这个设计来自JavaScript早年,通过这样的方式,巧妙地模拟了Java的语法,但是仍然保留了纯粹的'无类'运行时设施。

如果,我们把这个例子稍作修改,换成箭头函数,结果就不一样了。

const showThis = () => {console.log(this);
}var o = {showThis: showThis
}showThis(); // global
o.showThis(); // global

我们看到,改为箭头函数后,无论用什么来调用它,都不影响它的this值。

接下来我们看看'方法',它的行为又不一样了:

class C {showThis() {console.log(this);}
}
var o = new C();
var showThis = o.showThis;showThis(); // undefined
o.showThis(); // o

这里我们创建了一个类C,并且实例化出对象o,再把 o 的方法赋值给了变量 showThis。

这时候,我们使用 showThis 这个引用去调用方法时,得到了undefined。

所以,在方法中,我们看到this的行为也不大一样,它得到了undefined的结果。

按照我们上面的方法,不难验证出:生成器函数,异步函数和异步普通函数跟普通函数行为是一致的,异步箭头函数与箭头函数的行为是一致的。

this关键字机制

说完了this行为,我们再来简单谈谈在JavaScript内部,实现this这些行为的机制。

函数能够引用定义时的变量,如上文分析,函数能记住定义时的this,因此,函数内部必须有一个机制来保存这些信息。

在JavaScript标准中,为函数规定了用来保存定义是上下文的私有属性[[Environment]]。

当一个函数执行时,会创建一条新的执行环境记录,记录的外层词法环境(outer lexical environment)会被设置成函数的[[Environment]]。

这个动作就是切换上下文了,我们假设有这样的代码:

var a = 1;
foo();

在别处定义了foo:

var b = 2;
function foo(){console.log(b); // 2console.log(a); // error
}

这里的foo能够访问 b (定义时的词法环境),却不能访问 a (执行时的词法环境),这就是执行上下文的切换机制了。

JavaScript用一个栈来管理执行上下文,这个栈的每一项又包含一个链表。如下图所示:

014763644fc4edd620d1db4a3f0f0b85.png

当函数调用时,会入栈一个新的执行上下文,函数调用结束时,执行上下文被出栈。

而this则是一个更为复杂的机制,JavaScript标准定义了[[thisMode]]私有属性。

[[thisMode]]私有属性有三个取值

lexical:表示从上下文中找this,这对应了箭头函数。
global:表示this为undefined时,取全局对象,对应了普通函数。
strict:当严格模式时使用,this严格按照调用时传入的值,可能为null或者undefined。

非常有意思的是,方法的行为跟普通函数有差异,恰恰是因为class设计成了默认按照strict模式执行。

我们可以用strict达成与上一节中方法的例子中一样的效果。

"use strict"
function showThis(){console.log(this);
}var o = {showThis: showThis
}showThis(); // undefined
o.showThis(); // o

函数创建新的执行上下文中词法环境记录时,会根据[[thisMode]]来标记新纪录的[[ThisBindingStatus]]私有属性。

代码执行遇到this时,会组成检查当前词法环境记录中的[[ThisBindingStatus]],当我们找到有this的环境记录时获取this的值。

这样的规则的实际效果时,嵌套的箭头函数中的代码都指向外层this,例如:

var o = {}
o.foo = function foo(){console.log(this);return () => {console.log(this);return () => console.log(this);}
}o.foo()()(); // o, o, o

在上面的例子中,我们定义了三层嵌套的函数,最外层的是普通函数,两层都是箭头函数。

这里调用三个函数,获得的this值是一样的,对象都是o。

JavaScript还提供了一系列函数的内置方法来操作this值,下面我们来了解一下。

操作this的内置函数

Function.prototype.call和Function.prototype.apply可以执行函数调用时传入的this值,示例如下:

function foo(a, b, c){console.log(this);console.log(a, b, c);
}
foo.call({}, 1, 2, 3);
foo.apply({}, [1, 2, 3]);

这里call和apply作用是一样的,只是传参方式有区别。

此外,还有Function.prototype.bind 它可以生成一个绑定的函数,这个函数的this值固定了参数。

function foo(a, b, c){console.log(this);console.log(a, b, c);
}
foo.bind({}, 1, 2, 3)();

有趣的是,call,bind和apply用于不接受this的函数类型如箭头,class都不会报错。

这时候,它们无法实现改变this的能力,但是可以实现传参。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/429544.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

liunx常用命令0

1 开启Linux操作系统,要求以root用户登录GNOME图形界面,语言支持选择为汉语 点击“未列出?”-->输入root和密码 2 使用快捷键切换到虚拟终端2,使用普通用户身份登录,查看系统提示符 ctrlaltf2 3 使用命令退出虚拟终…

2个td合成一个td_18个月16个爆款,合成类玩法的下一个机会在哪?

18个月16个爆款!近日,编者体验了近18个月爆款小程序榜单之中的游戏,体验之后编者发现,在这200多款游戏中,有16款都应用了合成类玩法,而且部分游戏是数次登榜,比如枪火工厂、全民养鲲、世界争霸等…

在线代码图片生成工具carbon

在日常工作中时常需要和同事间进行代码的沟通和交流,有时只是需要讨论某一段代码的内容,因此不必将整个文件发给同事。通常可以将部分代码进行截图,或者直接将部分代码复制粘贴发送给同事。但以上方法或因为代码太长需要多次截图,…

antd 日期时间选择_Excel最全时间类函数总结,有必要收藏一下哦

Excel数据格式中,一共分十一类,其中两类分别是日期与时间。Excel中存在大量公式用于处理这两个类型的数据,下面一一介绍与之相关的函数。年月日函数Excel函数中分别用year()、month()、day()函数返回一日期的年、月、日,这三个函数…

C#中的变量类型(值类型、引用类型)

C#中的变量类型: 值类型:值类型直接存储的是变量的值,变量空间在栈上分配,分配速度比较快。给变量赋值时需注意变量类型的取值范围,给变量赋不合理的值会导致编译器报错。布尔类型的变量只有两种可选择的值true/false&…

Java:IDEA下使用JUNIT

单元测试的基本使用 一、环境配置 使用idea IDE 进行单元测试,首先需要安装JUnit 插件。 1.安装JUnit插件步骤 File-->settings-->Plguins-->Browse repositories-->输入JUnit-->选择JUnit Generator V2.0安装。 2.使用JUnit插件 在需要进行单元测试…

arcore之路-unity开发从入门到实践_Unity游戏开发——单例模式的最佳实践

0.前言StarryFun:Unity游戏开发——关于单例模式的理解​zhuanlan.zhihu.com之前一篇文章讲了单例模式的简单理解,自知其中有很多不严谨的地方,由于本萌新也是在学习阶段,所以去翻看了开源的项目都是怎么实现的,发现了…

光耦的简介

光耦,光电耦合器的简称,它是以光信号作为介质传输电信号的元器件。光耦的输入端和输出端信号可以非常好的进行隔离,因此在隔离电路中经常会见到光耦。光耦合器一般由三部分组成:光的发射、光的接收及信号放大。输入的电信号驱动发…

接待员如何向客人upsell_客房留言卡也能收获好评,看看高情商酒店如何做的?...

客人对酒店最客观的评价从点评中就能看到,那酒店服务如何才能被客人看到呢?可不可以通过一张留言卡,被客人感知到呢?本文整理了多个客房服务实际场景案例,帮助酒店了解如何写好这张留言卡。一、什么场景下放置留言卡&a…

“2020 RT-Thread开发者大会” 思考感悟

从2019年开始参加RT-Thread的一次线下培训活动后,就深深的喜欢上这个国产的RTOS,之后RT-Thread举办的活动基本都有参加(但每次活动抽奖都抽不到!)。当然,最为盛大的还属一年一度的RT-Thread开发者大会&…

小红书笔记_小红书的沙雕笔记,害人不浅啊

话说,当代人的十大必备软件是什么?要猫姐来说的话。除了微信微博抖音,小红书也肯定少不了!它简直就是大部分PLMM的“种草神器”。就连明星都纷纷入驻小红书变身为“美妆博主”。前一阵上了热搜榜和李晨分手的范冰冰也是小红书的一…

2020 RT-Thread开发者大会 ART-Badge电子胸牌(可二次开发)

2020 RT-Thread开发者大会 ART-Badge电子胸牌(可二次开发),动手实验IOT会场有介绍使用PersimmonUI设计器进行柿饼UI的开发,使用拖拽控件、注册事件的方式进行GUI的开发。开发语言使用JS,界面实现非常方便,后…

datatables分页下一页不能点击_干货,删不掉Word文末最后一页?学会5个方法,再也不愁啦...

在处理Word文档时,经常会在文档最后出现一页空白,非常讨厌,更可恶的是总是删不掉,按退格键或者delete键都无济于事。你是不是也正在为删除Word文末空白页发愁呢?有没有好的方法解决这一问题呢?有的&#xf…

计分员计分程序

1.估计这个任务需要多长时间:两星期 2.需求分析: 作为一名现场记分员,我希望详细记录比赛现场比分增长情况,以便观众及运动员、教练员及时掌握比赛状况。(满意条件:每一次比分的改变,都要形成一…

蓝牙入门基础知识(一)

蓝牙起源 1995年5月20日,在美国华盛顿州,索尼爱立信、IBM、英特尔、诺基亚、东芝成立了蓝牙技术联盟(Bluetooth Special Interest Group,SIG)。SIG是一个制定蓝牙规范,推送蓝牙技术发展的国际组织。SIG授权…

C#语言入门详解---委托(刘铁猛)

委托:函数指针的升级版,可以类比C语言中的函数指针进行理解 变量的本质就是以变量名所对应的内存地址为起点的一段内存,这段内存中存储的就是变量的数据,这段内存的大小由变量的数据类型决定。 函数代表算法,函数的本…

单元测试原来是这样的呼

我们要先创建一个界面: 显示的界面是这样的: 接下来我们来写java代码, 在输入框里的内容,点击button,将数据显示到上面的textview. 后面写测试用例,但我不知道哪里出错了,不知道是不是创建就出错…

C#中宏定义#define、预处理#if #else #endif的使用

C#中预处理的使用: 预处理指令并不会被编译为执行代码中的指令,但使用预处理指令可以选择编译程序中的哪部分代码。一般在调试代码时或在发布不同功能等级的软件版本中使用。 需要特别注意的是,宏定义必须在C#的.cs源文件最开头的位置定义&am…

C# 串口接收1次数据会进入2次串口接收事件serialPort1_DataReceived,第2次进入时串口缓冲区为空

在C#中使用串口接收数据时发现,在完整的接收完一次数据后,还会再次进入串口接收事件。 在网上搜索资料发现其他开发者也有遇到该问题: [1] c#串口事件接受一次数据莫名其妙会触发两次 原文链接:https://www.52pojie.cn/threa…