函数参数默认值的作用域问题
栏目: JavaScript · 发布时间: 6年前
内容简介:本篇是对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小技巧 - 使用解构赋值设置函数参数默认值
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript Patterns
Stoyan Stefanov / O'Reilly Media, Inc. / 2010-09-21 / USD 29.99
What's the best approach for developing an application with JavaScript? This book helps you answer that question with numerous JavaScript coding patterns and best practices. If you're an experienced d......一起来看看 《JavaScript Patterns》 这本书的介绍吧!