当函数赋值给相同名称的变量的问题

问题描述:
我有两个js文件
我在第一个js文件中定义了一个这个函数

function formHeader (msg) {
   let formHeaderContent = `
   <caption>${msg}</caption>
   <tr>
   <th>商品</th>
   <th>地区</th>
   <th>1月</th>
   <th>2月</th>
   <th>3月</th>
   <th>4月</th>
   <th>5月</th>
   <th>6月</th>
   <th>7月</th>
   <th>8月</th>
   <th>9月</th>
   <th>10月</th>
   <th>11月</th>
   <th>12月</th>
   </tr>`;
   return formHeaderContent;
}

在第二个.js中

function renderForm(data, msg) {
    // 输出表头:商品、地区、1月、2月、…… 12月
    // 遍历数据 {
    //    输出每一行的表格HTML内容
    // }
    // 把生成的HTML内容赋给table-wrapper

    const formHeader = formHeader(msg);
    ....
 }

写了一个这个函数,然后报错说 Uncaught ReferenceError: formHeader is not defined
我是这么理解的:我这个函数是在另一个js文件的全局里声明定义的,为什么在这里它会报错undefined?我的理解是两个.js都属于一个全局环境,我在其中一个.js中生命函数,另外一个.js也应该能进行调用才对,不知道哪里理解的有问题
它和这个函数又什么区别呢?

function abc(val) {
    return val;
}
var abc = abc('hello') // 这个就可以正常输出
阅读 2.5k
4 个回答

这两段代码的区别就在于第一段创建了一个新的formHeader局部常量,第二段没有。

我的理解是两个.js都属于一个全局环境,我在其中一个.js中生命函数,另外一个.js也应该能进行调用才对

没错,这么写是可以的:

function renderForm(data, msg) {
    const formHeaderValue = formHeader(msg);
}
为什么在这里它会报错undefined?

新人最常见的错误,就是以为赋初值是先计算右边,再创建变量,再赋值。但实际上,是先创建变量,再计算右边,再赋值。程序看到const formHeader的时候,会立即记下formHeader是个局部变量,再去计算右边的时候,formHeader已经是局部的未初始化的值了,而不是外部的函数。

第二段因为一直在同一个作用域内,第二次var没有创建新的变量,所以可以。


有评论在上面讲“let/const 变量只有定义被求值后才会初始化”。如果“初始化”是指“记为本地变量”,那么这个评论就是错误的。

考虑:

function foo() {} { };
(function () {
  debugger;
  const bar = foo();
  const foo = bar;
})()
  1. debugger可以看到两个变量已经在Local Scope创建出来了
    clipboard.png
  2. 程序会在第四行报错,而不是调用外层的foo

可能有些同学对let/const产生了误解,以为它们就不会变量提升了。事实上,JS引擎还是先扫描一次所有定义,生成Scope,然后再处理代码逻辑。为了在效率和方便之间平衡,这么做是必须的。过于简化地讲,let/constvar唯一的差别就是把undefined换成了ReferenceError。所以我上面说,formHeader已经是局部的未初始化的值了,就是指访问的时候会根据用的关键词返回该关键词对应的未初始化值(varundefinedlet/constReferenceError)。

可能我没有直接解释ReferenceError的问题,但是我觉得“变量声明的时机”才是更根本的问题,毕竟题主问题的标题是“当函数赋值给相同名称的变量”,而且就算是老手也不一定能弄明白。楼上各位都直接解释暂时性死区,但是换成var之后,第一段代码一样不能运行,题主又会到SF上提修改后的问题。

下面的评论说“应该报的错的是 formHeader is not a function.”,实际上如果把题主的const换成var,或者hack进浏览器内核禁用TDZ,报的正好是这个错误。

const, let 暂时性死区TDZ

当程序的控制流程在新的作用域(module, function或block作用域)进行实例化时,在此作用域中的用let/const声明的变量会先在作用域中被创建出来,但因此时还未进行词法绑定,也就是对声明语句进行求值运算,所以是不能被访问的,访问就会抛出错误。所以在这运行流程一进入作用域创建变量,到变量开始可被访问之间的一段时间,就称之为TDZ(暂时死区)

clipboard.png

你代码是怎么写的,是不是用了模块化。 如果用了模块化是不会生命到window上。 而是有自己的作用域。需要通过导入(require/import)来引入

如果排除使用了模块化的情况,那就是顺序问题了。 你要确保前面定义的函数先执行

一点一点说:

第一,你的原话是这样的: "我是这么理解的:我这个函数是在另一个js文件的全局里声明定义的,为什么在这里它会报错undefined?我的理解是两个.js都属于一个全局环境,我在其中一个.js中生命函数,另外一个.js也应该能进行调用才对";
你的全局声明引用是没问题的,前提是引入顺序正常。

下面说说为什么会报错,错误发生在你的第二个函数里面

先详细说明一下es6规定的let和const特性,
es6出的 let 和 const都存在一种特性,没有之前var的变量声明提升,而引入了一个绑定区域,也叫暂时性死区。

什么意思呢?就是说在未对这个变量声明之前不可使用,哪怕上层作用域已经声明了这个变量,如下:

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

当然你的 错误并不是 暂时性死区 引发的。
你这个属于在 变量声明完成之前使用变量错误,正在创建变量,还没有创建完毕,

推荐问题