函数参数默认值的作用域问题
栏目: JavaScript · 发布时间: 5年前
内容简介:本篇是对ECMAScript 6 入门函数参数默认值一章中的作用域一节的学习总结,并且寻找了一些相关问题,同时还注意到 Babel 的一个转译问题。本节内容参考:在 ECMA-262 中的
本篇是对ECMAScript 6 入门函数参数默认值一章中的作用域一节的学习总结,并且寻找了一些相关问题,同时还注意到 Babel 的一个转译问题。
参数默认值与作用域
本节内容参考:
- 关于 es6 函数参数默认值的理解问题? 中dablwow80的回答
- ECMAScript 6 入门
- 参数默认值引起的第三作用域
- ECMAScript® 2015 Language Specification
- js 变量声明 函数声明 变量赋值的实现机制疑惑? - 极光的回答 - 知乎
- 函数执行顺序
在 ECMA-262 中的 9.2.12 FunctionDeclarationInstantiation(func, argumentsList) 章节有相关说明。
当解析一个 JS 函数执行上下文的时候,会创建一个新的 Environment Record
(之后简称 ER
),并且绑定这个 ER
中每个实例化了的形参(这里的实例化应该是指在执行函数的时候,形参才能有值,有值之后代表实例化了)。同时在函数体中的每个声明也被实例化了。
-
形参没有任何默认值的情况下,会在 与参数相同的
ER
中实例化函数体声明 。也就是说函数体内的声明将与形参在同一ER
中实例化。-
函数有形参,形参会被添加到函数的作用域中,并且 形参不会被重新定义 (用
var
声明与形参同名的变量会被忽略)function fun(arg1, arg2) { var arg1; // 声明被忽略 var arg2 = "hello"; // var arg2 声明被忽略,arg2 = "hello" 被执行 console.log(arg1, arg2); } fun(1, 2); // 1 "hello" 复制代码
-
ES6 的
let
和const
会因为作用域内重复声明而报错function fun(arg) { let arg; } fun(); // SyntaxError: Identifier 'arg' has already been declared 复制代码
-
多说一种情况,如果函数内声明一个和形参同名的函数
ES6 之前,函数的执行可以分为 3 个阶段( ES6 之后情况变得复杂,尚未了解 ):
- 准备。包括 形参变量创建 、 函数体内的预解析(
var
声明和函数声明提升,也就是 Hoisting) 和 函数声明创建 - 装载,也就是填充数据。装载顺序为
函数参数
>函数声明
,而在函数声明装载时,如果函数体内有个和参数名相同的函数声明,那么这个函数就会覆盖形参 - 执行,略
function fun(arg) { console.log(arg); function arg() { //... } } fun(1); // [Function: arg] 复制代码
- 准备阶段,创建形参变量
arg
,函数体预解析,创建函数声明 - 装载阶段,先将形参的值 1 赋值给
arg
,arg = 1
,函数体内存在一个函数声明function arg(){}
,所以将函数申明赋值给arg
,也就是arg = function(){}
- 准备。包括 形参变量创建 、 函数体内的预解析(
-
PS:上面几种情况只是通过表现和结果进行总结,并没有严格按照规范进行分析。如有不对,请不吝赐教。
-
在执行函数时,如果函数形参存在默认值,第二个
ER
会被建立,这个作用域是针对函数体内的声明,所以【函数体内的声明】与【形参和本身的函数声明】不在同一作用域因此一个定义在全局环境的、带有默认参数的函数声明,在运行时共产生至少 3 个作用域,如下图:
-
形参的
ER
中的变量只能读取形参ER
中的变量或者函数外的变量,而函数体内的变量可以读取函数体内、形参以及外部的变量 -
函数体内可以修改
ER
里定义的形参的值,但是不能重新定义形参- 用
var
声明的变量显示为 Block,并不是代表它是块级作用域,而 仅仅是为了区分形参的ER
和函数体的ER
- 用
-
一个疑问
var x = 20; function fun(x = 1) { debugger; var x = 10; console.log(x); } fun(2); 复制代码
按我的理解,既然形参作用域和函数体作用域不共享,那么函数体作用域(图中 Block)中使用 var
声明的变量为什么会有一个初始值,并且和形参实例化的值相同?
希望有前辈可以答疑解惑。
分析几个小例子:
-
参数形成单独作用域
let x = 1; function fun(x, y = x) { console.log(y); } fun(2); 复制代码
- 参数
y
的默认值等于变量x
- 调用函数
fun
时,参数形成一个单独的作用域 - 在这个作用域中,默认值变量指向第一个参数
x
,而不是全局环境的x
- 参数
-
有默认值的形参创建的作用域也会沿着作用域链查找变量
function fun(y = x) { let x = 2; console.log(y); } fun(); // ReferenceError: x is not defined 复制代码
- 调用函数
fun
时,参数y=x
形成一个单独的作用域 - 在这个作用域里,没有定义
x
,所以沿着作用域链在全局寻找变量x
- 由于全局环境中也没有定义变量
x
,所以会报错 - 函数调用时, 函数体内部的局部变量
x
影响不到参数默认值变量x
- 调用函数
-
避免暂时性死区(
TDZ
)let x = 1; function fun(x = x) {} fun(); // Uncaught ReferenceError: x is not defined 复制代码
x = x let x = x
如果参数的默认值是一个函数,该函数的作用域也遵守上面的规则
let foo = "outer"; function bar(func = () => foo) { let foo = "inner"; console.log(func()); } bar(); // outer 复制代码
- 函数
bar
的参数func
的默认值是一个匿名函数,返回值为变量foo
- 形参形成的单独作用域里,并没有定义变量
foo
,所以指向外层的全局变量foo
一个 Babel 问题
本节内容参考:
- ES6 函数的扩展
- 关于 es6 函数参数默认值的理解问题? 中dablwow80的回答
- Babel 转译
在阮一峰老师的 ECMAScript 6 入门中,有这样一个例子,本身其实是对复杂的形参默认值的展示,但是发现其经过 Babel 转译后的表现与转译前不同。
-
ES6
var x = 1; function foo( x, y = function() { x = 2; } ) { var x = 3; y(); console.log(x); } foo(); // 3 复制代码
- 由于参数有默认值,所以函数的参数形成一个单独的作用域
-
y
的默认值是一个匿名函数,函数内的变量x
指向同一作用域的第一个参数x
- 函数体内也声明了一个内部变量
x
,该变量与第一个参数x
由于不是同一作用域,所以不是同一个变量 - 执行
y
后,内部变量和外部变量x
的值都没变
-
转译成 ES5 后(Babel@7.3.0)发现与原来的结果不同了。原因是转译后 形参和函数体的作用域没有做隔离
"use strict"; var x = 1; function foo(x) { var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function() { x = 2; }; var x = 3; y(); console.log(x); } foo(); // 2 复制代码
-
基于 Babel 基础上修改
function foo(x) { var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function() { x = 2; }; return function() { var x = 3; y(); console.log(x); }.call(this, x, y); } 复制代码
也许 Babel 出于某些考虑并没有修改,但是从结果上看,转译的代码与原来的结果的确不一致了。
总结
其实在分析这个问题的时候,自己还是很吃力,并不能从 ECMAScript® 2015 Language Specification 中分析原因,也就是无法从根本上解释完整的运行原理。更多的是从其他人的理解中参悟。
这个问题其实在业务场景中很少出现,研究意义大于实用意义。
参考
以上所述就是小编给大家介绍的《函数参数默认值的作用域问题》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- PHP函数默认设置引发的安全问题
- c++中函数的参数传递,内联函数和默认实参的理解
- Python 函数为什么会默认返回 None?
- Python之在函数中使用列表作为默认参数
- 使用IDAPython自动映射二进制文件替换默认函数名
- ES6小技巧 - 使用解构赋值设置函数参数默认值
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Windows黑客编程技术详解
甘迪文 / 人民邮电出版社 / 2018-12 / 108
《Windows黑客编程技术详解》介绍的是黑客编程的基础技术,涉及用户层下的Windows编程和内核层下的Rootkit编程。本书分为用户篇和内核篇两部分,用户篇包括11章,配套49个示例程序源码;内核篇包括7章,配套28个示例程序源码。本书介绍的每个技术都有详细的实现原理,以及对应的示例代码(配套代码均支持32位和64位Windows 7、Windows 8.1及Windows 10系统),旨在......一起来看看 《Windows黑客编程技术详解》 这本书的介绍吧!