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

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

在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用一个栈来管理执行上下文,这个栈的每一项又包含一个链表。如下图所示:

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

而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的能力,但是可以实现传参。

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

  1. c++中把一个函数中的语句复制到另一个语句中报错_从底层看前端(十一)—— JavaScript语法:脚本,模块和函数体。...

    这篇文章我们继续聊JavaScript语法. 在讲解具体的语法结构之前,先看看语法的一些基本规则. 脚本和模块 首先,JavaScript有两种源文件,一种叫脚本(script),一种叫做模块(mod ...

  2. javascript java 传参_[Java教程]【JS】JavaScript中的参数传递

    [Java教程][JS]JavaScript中的参数传递 0 2017-02-18 00:00:24 ECMAScript中所有函数的参数都是按值传递的,简单讲就是函数外部的值 复制给函数内部的参数, ...

  3. 点到曲线的距离公式_推导点到直线的距离公式到底有多少种方法?

    [总结]方程思想,这也是解析几何的主题思想,几何问题代数化,转化为代数计算. 优点:思路简单清晰易于理解. 缺点:计算量较大. [总结]此方法优点:计算量大幅度减小,紧扣问题入手,切入点准确. 缺点: ...

  4. 十三水牌型 图片_十三水,得玩法到底有多少种!

    特殊牌型 至尊清龙:同花色的从1(A)-13(k) 一条龙:从1(A)-13(k) 十二皇族:12张都是10以上的牌 三同花顺:三墩都是同花顺 三分天下:13张牌出现3副炸弹加一张杂牌(或称三套四梅. ...

  5. assert函数_悉数Python函数传参的语法糖

    TIOBE排行榜是程序开发语言的流行使用程度的有效指标,对世界范围内开发语言的走势具有重要参考意义.随着数据挖掘.机器学习和人工智能相关概念的风行,Python一举收获2018年年度语言,这也是Pyt ...

  6. delphi中的函数传参如何传枚举参数_我是这样使用SpringBoot(API传参)

    spring boot 传参 spring boot 中的Controller或者RestController接收参数的方法是一样的.这章目标是对几种常用的传参都写个例子. 创建package: co ...

  7. PHP变量、引用、函数传参之彻底掌握,从此节操是路人

    2019独角兽企业重金招聘Python工程师标准>>> 今天绝对是兴奋的一天,不仅仅是周五这个特殊的日子(周六日可以休息啦),也不是弄清了某wordpress插件的功能流程,更不是再 ...

  8. vue中定时器一般用法,定时器函数传参以及清除定时器

    一.vue中定时器一般用法(举个例子) 显示当前时间, setInterval()方法会每秒执行一次函数,类似手表功能: <template><div class="use ...

  9. delphi中的函数传参如何传枚举参数_shell脚本的函数介绍使用和工作常用案例。建议收藏...

    #前言:今天我们来聊聊shell脚本中的函数知识,看一下函数的优势,执行过程和相关的使用案例. #简介 1.函数也具有别名类似的功能 2.函数是把程序里多次调用相同的代码部分定义成一份,然后给这份代码 ...

最新文章

  1. 第一轮通知 | 2022年中国生物物理学会肠道菌群分会年会暨“崂山论肠菌”学术论坛...
  2. 12年后,人工智能和人类会是什么样?这是900位专家的看法|报告
  3. java 泛型 窜讲
  4. 【考试认证专场】大牛带你全面掌握学习技巧,攻克考试难题(8.2-8.6精品课程限时特惠)...
  5. java lambda 局部变量_java Lambda表达式访问局部变量详细介绍
  6. 关于文件描述符的问题的解决
  7. 存储过程语法 - 变量
  8. ORACLE的ProC用法讲解
  9. 干货整理:处理不平衡数据的技巧总结!收好不谢
  10. 使用iozone和bonnie测试磁盘IO
  11. 哈工大大数据实验_科研常用 | 实验大数据分析方法
  12. ORACLE sid,pid,spid总结
  13. ijkplayer-音视频变速播放实现
  14. Mysql数据备份与mysqldump增量备份
  15. WPS Office 2012 专业版 附正版序列号
  16. 飞秋怎么搜索指定ip好友_飞秋怎么加好友
  17. 谷歌浏览器反复提示PageOffice安装
  18. 智能呼叫系统之客户互动中心
  19. 统一社会信用代码=营业执照注册号 + 营业执照注册号+营业执照注册号
  20. COA-2019-第十四章 Instruction Sets

热门文章

  1. 使用 bat 文件管理计算机服务
  2. 后台返回数据打印是[object object]的,报错:SyntaxError: JSON.parse: expected property name or ‘}‘ at line 1 column
  3. Redis内部数据结构-跳跃表
  4. 开发服务器 VSS开发库 自动备份方案
  5. (csc)Visual C# 2010 编译器选项.
  6. jquery 一些特效使用
  7. mysql中新建不了查询语句_将excel和mysql建立链接后,如何通过在excel里面执行mysql查询语句,然后建立查询...
  8. java jsp ajax_ajax的json传值方式在jsp页面中的应用
  9. 关于ubuntu终端命令路径太长的问题
  10. 【软件开发底层知识修炼】二十二 ABI-应用程序二进制接口 二