JS 全局 name 的诡异行为

321 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情


首先看看下面这段代码,自己先心里确定一个答案,然后复制到浏览器看看,结果是不是出乎你的预料?

var name = [1, 2, 3]
name.length

初次看,name = [1,2,3],什么?三个元素,结果居然是 5,不是 3

别急,接着在浏览器输入以下代码:

let name = [1, 2, 3]
name.length // 3

结果又变成了 3,重复声明了,结果怎么又正常了?

两次结果不同,先看两者代码的不同之处。第一次使用 var 声明,第二次使用 let 声明。我们回忆下两者的区别:

  • var 声明的变量的作用域是它 当前的执行上下文
    • 可以是一个全局作用域,例如浏览器的 window 下、nodejs 中全局包裹的 global 等等。
    • 可以是一个(嵌套)函数作用域,也就是说 var 声明的变量只能在当前函数以及内层嵌套函数内可访问。
  • let 声明的是块级作用域,let声明的变量只在其声明的块或子块中可用。 和 var 的区别是:
    • var 声明的变量的作用域是 整个封闭函数(函数作用域),受到函数作用域限制,不受 {} 块的限制。
      (1)var 受函数作用域限制
      function f() {
        var a = 123
      }
      f()
      a // ReferenceError: a is not defined
      
      (2)var 没有块级作用域,不受 {} 块限制
      { 
        var b = 123 
      }
      b // 123
      
    • let 声明的变量的作用域是 块级作用域,具体表现为一个 {},例如 if(){}语句块、for(){} 语句块、{} 块,也包括函数块 function(){}
      {
        let c = 123
      }
      // ReferenceError: c is not defined
      
      function f() {
        let d = 123
      }
      f()
      d // ReferenceError: d is not defined
      

现在来看这道题:

var name = [1, 2, 3]
name.length // 5

前面提到过,var 在浏览器全局环境下声明的变量会放到 window 对象上,但是,有一个容易忽略的问题,那就是 window 上本身就有一个 name 属性,window.name 是浏览器窗口的名称属性。使用 var 可以合法的重复声明一个已经存在的变量,起作用的就只有赋值,也就是说,上述代码等价于:

window.name = [1, 2, 3]

给一个窗户名称属性 name 赋值,会发生什么呢?MDN 上这样说:

window.name 会调用 toString 将赋给它的值转换成对应的字符串表示。 ——MDN window.name

也就是说会执行 [1, 2, 3].toString() 方法,数组的 toString() 默认行为是把数组中的元素使用 , 拼接返回,那么 window.name 的值为 '1,2,3''1,2,3'.length 的结果自然是 5

至于 let name = [1, 2, 3]let 在浏览器全局声明的变量不会在 window 上,而是底层实现的全局环境下(这里我也说不清,总之就是挂载到一个特殊的全局环境下,非 window)。那么 [1,2,3].length 结果自然为 3

综上所述,不要再全局中轻易的以 name 命名!

怎么样,看起来奇怪的表现,其实都是些基础知识,所以基础还是很重要的!

本文完。