ES6 let 和 const

一、声明全局变量

a = 1一定是在声明一个全局变量吗?

function fn() {
  a = 1
}
fn()
console.log(a) // 1

上面的代码的确是在声明一个全局变量a,然后我们再来看看下面这种情况

var a
function fn() {
  var a
  function fn2() {
    a = 1
  }
}
fn()
console.log(a) // undefined

在这种情况下很明显就不是在声明全局变量aa = 1会向上寻找离它最近的一个声明,即函数fn里的var a,并没有到达最顶层,因此上面的代码实际上等价于下面这种写法

var a
function fn() {
  var a = 1
  function fn2() {
  }
}
fn()
console.log(a) // undefined

总结一下就是,如果在当前作用域内存在变量a的声明,那么a = 1就会直接去利用这个声明;如果在当前作用域内不存在对a的声明,那么就会按照“作用域链”一级一级地向上寻找所有父对象的变量。只有当找完了所有父对象的变量之后都没有发现有对变量a的声明,这时候才是在声明一个全局变量a(window.a)

二、ES5 作用域

ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。

第一种场景,内层变量可能会覆盖外层变量。

var tmp = new Date();

function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}

f(); // undefined

上面代码的原意是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

第二种场景,用来计数的循环变量泄露为全局变量。

var s = 'hello';

for (var i = 0; i < s.length; i++) {
  console.log(s[i]);
}

console.log(i); // 5

上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。(可以使用立即执行函数来解决这个问题)

由于以上var的种种缺陷,因此就有了下文的letconst以及“块级作用域”

三、let 命令

1. 块级作用域

function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}

上面的函数有两个代码块,都声明了变量n,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量n,最后输出的值才是 10。

2. 暂时性死区

var tmp = 123;

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

上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

ES6 明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

3. 不允许重复声明

let不允许在相同作用域内,重复声明同一个变量。

// 报错
function func() {
  let a = 10;
  var a = 1;
}

// 报错
function func() {
  let a = 10;
  let a = 1;
}
image.png

四、const 命令

const声明一个只读的常量。一旦声明,常量的值就不能改变。而且必须在声明的时候立刻进行赋值。

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

const foo;
// SyntaxError: Missing initializer in const declaration

五、面试题

  1. 下面代码的打印结果一定是1吗?
var a = 1

function fn() {
  console.log(a)
}
/*这里有一行代码*/
fn()

如果加的一行代码是a = 2呢?

var a = 1

function fn() {
  console.log(a) // 2
}
a = 2
fn()
  1. 下面的代码为什么打印出i的值总是6?
for (var i=0; i<6; i++){
  
}
console.log(i) // 6

因为它等价于下面这种写法

var i
for (i=0; i<6; i++){
  
}
console.log(i) // 6
  1. 下面的代码一定会打印出0~5吗?
for (var i=0; i<6; i++){
  function fn(){
    console.log(i) // ?
  }
  /*这里有一行代码*/
}
console.log(i) // 6

如果是直接调用fn,会打印出1~5,我的理解是每进入for循环中一次,就执行一次fn,因此会打印出对应的i的值

for (var i=0; i<6; i++){
  function fn(){
    console.log(i) // 0,1,2,3,4,5
  }
  fn()
}
console.log(i) // 6

但如果加的代码是x.onclick = fn,则直接就打印出6,我的理解是当点击这个按钮的时候,i已经累加到6了

/*<button id="x">x</button>*/
for (var i=0; i<6; i++){
  function fn(){
    console.log(i) // 6
  }
  x.onclick = fn
}
console.log(i) // 6
  1. 一道面试题
<ul>
  <li>导航1</li>
  <li>导航2</li>
  <li>导航3</li>
  <li>导航4</li>
  <li>导航5</li>
  <li>导航6</li>
</ul>
var liTags = document.querySelectorAll('li')

for (var i = 0; i < liTags.length; i++) {
  liTags[i].onclick = function() {
    console.log(i) // 为什么打印出i的值总是6?
  }
}

这就是因为用来计数的循环变量i泄露为全局变量

有以下三种解法:

解法一:用ES5的立即执行函数来创造一个局部作用域

var liTags = document.querySelectorAll('li')

for (var i = 0; i < liTags.length; i++) {
  (function(j) {
    liTags[j].onclick = function() {
      console.log(j) // 0,1,2,3,4,5
    }
  })(i)
}

解法二:在for循环内部用另一个变量来接收i的值

var liTags = document.querySelectorAll('li')

for (var i = 0; i < liTags.length; i++) {
  let j = i // 注意这里依然只能用let,不能用var
  liTags[j].onclick = function() {
    console.log(j) // 0,1,2,3,4,5
  }
}

解法三:直接在for循环的条件中使用let来声明变量

var liTags = document.querySelectorAll('li')

for (let i = 0; i < liTags.length; i++) {
  liTags[i].onclick = function() {
    console.log(i) // 0,1,2,3,4,5
  }
}

注意:上面解法三的这种写法,i的作用域只在for后面的圆括号中,而下面的花括号又是另外一个作用域,可以理解为下面这样

在这个过程中一共产生了7个i,块里面6个,圆括号里面1个

参考:
http://es6.ruanyifeng.com/#docs/let
我用了两个月的时间才理解 let

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335