[深入理解ES6]块级绑定

var声明与变量提升

变量提升(hoisting):使用var关键字声明的变量,无论声明位置在何处,都会被视为声明于所在函数的顶部(如果声明不在任意函数内,则视为在全局作用域的顶部)。

function getValue(condition) {
  if (condition) {
    var value = "blue";
    // 其他代码
    return value;
  } else {
    // value 在此处可访问,值为 undefined
    return null;
  }
    // value 在此处可访问,值为 undefined
}

刚开始,你可能会认为仅当condition的值为true时,变量value才会被创建。但实际上,JS引擎在后台对getValue进行了调整,像这样:

function getValue(condition) {
  var value;
  if (condition) {
    value = "blue";
    // 其他代码
    return value;
  } else {
    return null;
  }
}

如上代码,value变量的声明被提升到了顶部,而初始化工作则保留在原处。
为了解决这个问题,ES6引入了块级作用域,让变量的生命周期更加可控。

块级声明

块级声明 :让所声明的变量在指定块的作用域外无法被访问。块级作用域(词法作用域)在如下情况被创建:

  1. 在一个函数内部
  2. 在一个代码块({})内部

let声明

同样是上面的代码范例:

function getValue(condition) {
  if (condition) {
    let value = "blue";
    // 其他代码
    return value;
  } else {
    // value 在此处不可用
    return null;
  }
  // value 在此处不可用
}

let声明不会将变量提升到函数顶部。

禁止重复声明

如果一个标识符已经在代码块内部被定义,那么在此代码块内使用同一个标识符进行let声明会抛出错误。eg:

var a=20
// 语法错误
let a=200

然而在嵌套的作用域内使用let声明一个同名变量是正常的:

var a=10
// 不会抛出错误
if (condition) {
  let a=100
  // 其他代码
}

未报错的原因是,在if代码块内部,这个新变量会屏蔽全局的a变量,从而在局部阻止对于后者的访问。

常量声明

使用 const 声明的变量会被认为是常量(constant),所有的 const 变量都需要在声明时进行初始化,eg:

// 有效的常量
const hi=30

// 语法错误:未进行初始化
const hi

对比常量声明与 let 声明

1.常量声明与 let 声明一样,都是块级声明。即在语法块外无法访问,声明也不会被提升;

if (condition) {
  const maxItems = 5;
  // 其他代码
}
// maxItems 在此处无法访问

2.const 声明会在同一个作用域(全局或是函数作用域)内定义一个已有变量时抛出错误;

var message = "Hello!";
let age = 25;
// 二者均会抛出错误
const message = "Goodbye!";
const age = 30;

3.对之前用 const 声明的常量进行赋值会抛出错误,无论严格模式还是非严格模式;

const maxItems = 5;
maxItems = 6; // 抛出错误

与其他语言的常量类似, maxItems 变量不能被再次赋值。然而与其他语言不同, JS 的常量
如果是一个对象,它所包含的值是可以被修改的。

使用 const 声明对象

const person = {
  name: "Nicholas"
};
// 工作正常
person.name = "Greg";
// 抛出错误
person = {
  name: "Greg"
};

const阻止的是对于变量绑定的修改,而不阻止对成员值的修改。

暂时性死区

if(condition){
  console.log(typeof value); // 引用错误
  let value = 'blue';
}

value位于被JS社区称为暂时性死区(temporal dead zone,TDZ)的区域内,替换为 const 会有相同情况。
当JS引擎检视接下来的代码块并发现变量声明时,它会在面对var的情况下将声明提升到函数或全局作用域的顶部,而面对letconst时会将声明放在暂时性死区内。只有执行到变量声明语句时,变量才可安全使用,否则报runtime error
对比以下代码:

console.log(typeof value); // undefined
if(condition){
  left value = 'blue';
}

为什么和上面的结果不一样呢?
因为当typeof运算符被使用时,value并没有在暂时性死区内。

循环中的块级绑定

请对比以下代码

for (var i = 0; i < 10; i++) {
  process(items[i]);
}
// i 在此处仍然可被访问
console.log(i); // 10
for (let i = 0; i < 10; i++) {
  process(items[i]);
}
// i 在此处不可访问,抛出错误
console.log(i);

这一次欣赏到了变量提升的魔性吗!

循环内的函数

var funcs = [];

for (var i = 0; i < 10; i++) {
  funcs.push(function() { console.log(i); });
}

funcs.forEach(function(func) {
  func();  // 输出数值‘10’十次
});

你原本可能预期这段代码会输出 09 的数值,但它却在同一行将数值 10 输出了十次。这是因为变量 i在循环的每次迭代中都被共享了,意味着循环内创建的那些函数都拥有对于同一变量的引用。在循环结束后,变量 i 的值会是 10 ,因此当console.log(i)被调用时,每次都打印出 10。循环内使用立即调用函数表达式(IIFEs)解决这个问题。

var funcs = [];
for (var i = 0; i < 10; i++) {
  funcs.push((function(value) {
    return function() {
      console.log(value);
    }
  }(i)));
}
funcs.forEach(function(func) {
  func(); // 从 0 到 9 依次输出
});

这种写法在循环内使用了 IIFE 。变量i被传递给 IIFE ,从而创建了value 变量作为自身副本并将值存储于其中。 value 变量的值被迭代中的函数所使用,因此在循环从 09 的过程中调用每个函数都返回了预期的值。更简单的方法请往下看:

循环内的let声明

var funcs = [];
for (let i = 0; i < 10; i++) {
  funcs.push(function() {
    console.log(i);
  });
}
funcs.forEach(function(func) {
  func(); // 从 0 到 9 依次输出
})

在循环中,let 声明每次都创建了一个新的 i 变量,因此在循环内部创建的函数获得了各自的 i 副本,而每个 i 副本的值都在每次循环迭代声明变量的时候被确定了。这种方式在 for-infor-of 循环中同样适用,如下所示:

var funcs = [],
object = {
    a: true,
    b: true,
    c: true
};
for (let key in object) {
  funcs.push(function() {
    console.log(key);
  });
}
funcs.forEach(function(func) {
  func(); // 依次输出 "a"、 "b"、 "c"
});

如果使用var 来声明 key ,则所有函数都只会输出 "c"

需要重点了解的是: let 声明在循环内部的行为是在规范中特别定义的,而与不提升变量声明的特征没有必然联系。事实上,在早期 let 的实现中并没有这种行为,它是后来才添加的。

循环内的常量声明

var funcs = [];
// 在一次迭代后抛出错误
for (const i = 0; i < 10; i++) {
  funcs.push(function() {
    console.log(i);
  });
}

在此代码中,i 被声明为一个常量。循环的第一次迭代成功执行,此时 i 的值为 0 。在i++ 执行时,一个错误会被抛出,因为该语句试图更改常量的值。

var funcs = [],
object = {
  a: true,
  b: true,
  c: true
};
// 不会导致错误
for (const key in object) {
  funcs.push(function() {
    console.log(key);
  });
}
funcs.forEach(function(func) {
  func(); // 依次输出 "a"、 "b"、 "c"
});

const 能够在 for-infor-of 循环内工作,是因为循环为每次迭代创建了一个新的变量绑定,而不是试图去修改已绑定的变量的值。

全局块级绑定

// 在浏览器中
var RegExp = "Hello!";
console.log(window.RegExp); // "Hello!"
var ncz = "Hi!";
console.log(window.ncz); // "Hi!"

尽管全局的 RegExp 是定义在 window 上的,它仍然不能防止被 var 重写。这个例子声明了一个新的全局变量 RegExp 而覆盖了原有对象。类似的, ncz 定义为全局变量后就立即成为了 window 的一个属性。这就是 JS 通常的工作方式。

// 在浏览器中
let RegExp = "Hello!";
console.log(RegExp); // "Hello!"
console.log(window.RegExp === RegExp); // false
const ncz = "Hi!";
console.log(ncz); // "Hi!"
console.log("ncz" in window); // false

若想让代码能从全局对象中被访问,你仍然需要使用 var。在浏览器中跨越帧或窗口去访问代码时,这种做法非常普遍。

块级绑定新的最佳实践&&总结

块级绑定当前的最佳实践就是:在默认情况下使用 const ,而只在你知道变量值需要被更改的情况下才使用let 。这在代码中能确保基本层次的不可变性,有助于防止某些类型的错误。

你的赞是我前进的动力

求赞,求评论,求转发...

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

推荐阅读更多精彩内容