参数的默认值和参数环境与TDZ

今天的一大收获,就是知道了,函数参数默认值不是传值调用。 ​​​​
yan-wen-jun:根据mdn的文档,默认值是在函数被调用的时候evaluate的,和传值还是传引用没什么关系。比如上图的foo函数,调用它的时候会先计算x+1,计算出来的是一个数字,而数字作为一个参数传給函数的时候,是传值调用。
貘吃馍香:看规范吧从[[Call]](thisArgument, argumentsList)可以缕到FunctionDeclarationInstantiation(func, argumentsList) 里头有说带有默认值的参数,创建第二个环境记录说明。同时侧面解释了你上条的疑问。当然规范没说这环境记录里用 LexEnv 还是 VarEnv 来搞,但细看规则肯定会觉得 let 实现省事儿[doge]
都亲切的叫我韬B:let x = 100 function foo(p=function(){return x+1}) {console.log(p)}
下面的代码会报错,左思右想,认定是 V8 的 bug …… ​​​​
Tang金华:没毛病,不严格的说基本等价于 var x = 1; function foo(){let x = x; console.log(x);} foo(); 注意let,其他自己想。

ruanyf:想通了,Chrome 的处理没错,这里函数变量是 let 声明的,不是 var 声明的。

以下来自:http://code.wileam.com/default-value-n-params-env/

看 ES6 parameter default value 的时候,发现一些很困惑的现象。

let y = 1;
function foo(x = y, y) {}
foo();

结果是 reference error: y is not defined. 因为这里 y 是处在 TDZ(Temporal Dead Zone)。

console.log(x); // TDZ
let x;

可是为什么呢,明明全局有定义 y 呀,为什么未定义?难道参数默认值有单独的作用域?继续试验:

let y = 1;
function foo(x = function(){console.log(y)}, y = 2) {
  x(); // 2
  y = 3;
  x(); // 3
}
foo();
console.log(y); //1
let y = 1;
function foo(x = function(){console.log(y)}) {
  let y = 3;
  x(); // 1
}
foo();
function foo(x = function(){console.log(y)}) {
  let y = 3;
  x(); // ReferenceError: y is not defined
}
foo();

很让人困惑,感觉是存在3个作用域,全局/参数/函数体。参数默认值的函数,可以访问参数中定义的,和参数外定义(outer/global)的变量,不能访问函数体中定义的变量。

于是去找ES标准中的定义,找到了 9.2.12 FunctionDeclarationInstantiation

If the function’s formal parameters do not include any default value initializers then the body declarations are instantiated in the same Environment Record as the parameters. If default value parameter initializers exist, a second Environment Record is created for the body declarations. Formal parameters and functions are initialized as part of FunctionDeclarationInstantiation. All other bindings are initialized during evaluation of the function body.

豁然开朗,果然就是这样。

结论:

  • 如果参数存在默认值,则有三个环境 environmentenvironment in ES6 = scope in ES5). Outer environment / parameters environment / function body environment.
  • parameters environment 可以访问自己和外层,不能访问函数体内的变量。
  • 函数体内可以修改 parameters env 里定义的 formal parameters 的值,不能重新定义(除非用var……)。

关于var可以看一个更晕的例子:

let y =1;
function foo(x = function(){console.log(y)},y=2) {
  x(); // 2
  var y = 3; // if use let, then throw error: y is already declared, which is much more clear.
  console.log(y); //3
  x(); // 2
}
foo();
console.log(y); //1

so,个人建议:

  • 参数变量、函数体内变量、全局变量别用一样的名字
  • 不要用 var 来定义变量,总是用 let 或 const

参考资料:


关注我

我的微信公众号:前端开发博客,在后台回复以下关键字可以获取资源。

  • 回复「小抄」,领取Vue、JavaScript 和 WebComponent 小抄 PDF
  • 回复「Vue脑图」获取 Vue 相关脑图
  • 回复「思维图」获取 JavaScript 相关思维图
  • 回复「简历」获取简历制作建议
  • 回复「简历模板」获取精选的简历模板
  • 回复「加群」进入500人前端精英群
  • 回复「电子书」下载我整理的大量前端资源,含面试、Vue实战项目、CSS和JavaScript电子书等。
  • 回复「知识点」下载高清JavaScript知识点图谱

每日分享有用的前端开发知识,加我微信:caibaojian89 交流