【读书笔记】【深入理解ES6】#1-块级作用域绑定
var声明及变量提升(Hoisting)机制
在函数作用域或全局作用域中通过var关键字声明的变量,无论实际上是在哪里声明的,都会被当成在当前作用域顶部声明的变量。这就是我们常说的提升(Hoisting)机制。
通过下面的getValue函数来说明:
function getValue(condition) {
if (condition) {
var value = "blue";
console.log(value); // blue
} else {
console.log(value); // undefined
}
console.log(value); // 再次打印value的值
return value;
}
getValue(true);
// 打印结果
// blue
// blue
getValue(false);
// 打印结果
// undefined
// undefined
之所以会这样就是因为上面说的提升机制。
在预编译阶段,JavaScript引擎会将上面的getValue函数修改成如下这样:
function getValue(condition) {
var value;
if (condition) {
value = "blue";
console.log(value); // blue
} else {
console.log(value); // undefined
}
console.log(value); // 再次打印value的值
return value;
}
变量value的作用域被提升到了函数顶部,而不仅仅是在if块中。
这同其它大部分编程语言都不一样,很容易引起bug。
为此,ES6中引入了块级作用域来强化对变量生命周期的控制。
块级声明
块级声明用于声明在指定块的作用域之外无法访问的变量。
块级作用域(亦称为词法作用域)存在于:
- 函数内部
- 块中(字符{和}之间的区域)
let声明
let声明的用法与var相同。用let代替var来声明变量,就可以把变量的作用域限制在当前代码块中。
由于let声明不会被提升,因此开发者通常将let声明语句放在封闭代码块的顶部,以便整个代码块都可以访问。
function getValue(condition) {
if (condition) {
let value = "blue";
console.log(value); // blue
} else {
console.log(value); // Uncaught ReferenceError: value is not defined
}
console.log(value); // Uncaught ReferenceError: value is not defined
return value;
}
调用该方法会在第6行和第9行出现如下异常:
Uncaught ReferenceError: value is not defined
禁止重声明
假设作用域中已经存在某个标识符,此时再使用let关键字声明它就会抛出错误。
var a = 1
var a = 2
let a = 3
执行到let时会出现如下错误:
Uncaught SyntaxError: Identifier 'a' has already been declared
const声明
使用const声明的是常量,其值一旦被设定后不可更改。
因此,每个通过const声明的变量必须进行初始化。
const maxItems = 30;
const name;
第二行代码会抛出如下错误:
Uncaught SyntaxError: Missing initializer in const declaration
const和let
const和let声明的都是块级标识符,所以常量也只在当前代码块内有效,一旦执行到块外会立即被销毁。
常量同样也不会被提升至作用域顶部。
与let类似,在同一作用域用const声明已经存在的标识符也会导致语法错误,无论该标识符是以var还是let声明的。
这里我写代码测试时发现了一件比较奇怪的事情。
var name = 'jiajia';
const name = 'ljj';
按照上面的定义,第二行代码应该抛出错误才对,结果却是正常的执行了。
当把变量名 name 改成 message 时,第二行代码是会正常的抛出错误的。
那就只能是 name 这个变量名的问题了,这个名字比较特殊。
特殊在哪里呢?
因为代码是在全局作用域执行的,使用 var 声明的变量会自动变成全局对象(浏览器环境中的 window 对象)的一个属性。
而 name 特殊在它是 window 的一个固有属性。
至于为什么固有属性会有这个特性我就不知道了。
用const声明对象
JS中常量如果是对象,则常量中的值是可以修改的。
这同C#是一样的,java中好像也是这样的。
const person = {
name : 'JiaJia'
}
// 可以修改对象属性的值
person.name = 'ljj'
// 抛出语法错误
person = {
name : 'ljj'
}
最后的赋值会抛出如下错误:
Uncaught TypeError: Assignment to constant variable.
临时死区(Temporal Dead Zone)
与var不同,let和const声明的变量不会被提升到作用域顶部。如果在声明之前访问这些变量,即使是相对安全的typeof操作符也会触发引用错误。
function getValue(condition) {
if (condition) {
console.log(typeof value); // 引用错误
let value = "blue";
}
}
getValue(true);
console.log(typeof value) 会抛出如下错误:
Uncaught ReferenceError: value is not defined
但在let声明的作用域外对该变量使用typeof则不会抛出错误:
function getValue(condition) {
console.log(typeof value); // undefined
if (condition) {
let value = "blue";
}
}
getValue(true);
循环中的块作用域绑定
最常见的for循环:
for (var i = 0; i < 10; i++) {
}
console.log(i); // 10
最终会打印10,说明i的作用域在循环体外,不是想象中的for循环内部。
将var改为let即可以实现想定的效果。
for (let i = 0; i < 10; i++) {
}
// i在这里不可访问,抛出一个错误
console.log(i); // Uncaught ReferenceError: i is not defined
循环中的函数
长久以来,var声明让开发者在循环中创建函数变得异常困难,因为变量到了循环之外仍能访问。
var funcs = [];
for (var i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i);
});
}
funcs.forEach(function(func) {
func(); // 输出10次数字10
});
预想的是输出0~9,但实际输出的是10次10。
是因为循环里的每次迭代都共享着变量i,循环内部创建的函数全部都保留了对相同变量的引用。循环结束时变量i值为10,所以调用 console.log 时就会输出数字10.
为了解决这个问题,开发者们在循环中使用了立即调用函数表达式(IIFE),以强制生成计数器变量的副本。
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都创建了一个副本并存储为变量value。这个变量就是响应迭代创建的函数所使用的值,因此调用每个函数都会像从0到9循环一样得到期望的值。
循环中的let声明
let声明模仿上述实例中IIFE所做的一切来简化循环过程,每次迭代都会创建一个新变量,并以之前迭代中同名变量的值将其初始化。
不需要使用IIFE,只需将示例中的var改成let就可以得到想要的结果。
var funcs = [];
for (let i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i);
});
}
funcs.forEach(function(func) {
func(); // 输出0~9
});
对于for-in循环也是一样的。
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
});
如果变量key使用var声明,则会打印三个c。
可见for-in循环与for循环的表现是一致的。
循环中的const声明
在ES6标准中没有明确指明不允许在循环中使用const声明,然而针对不同类型的循环它会表现出不同的行为。
对于普通的for循环来说,可以在初始化时使用const,但是更改这个变量的值就会抛出错误。
var funcs = [];
// 第二次迭代时会抛出错误
for (const i = 0; i < 10; i++) {
funcs.push(function() {
console.log(i);
});
}
funcs.forEach(function(func) {
func();
});
第二次迭代时会抛出如下错误:
Uncaught TypeError: Assignment to constant variable.
在for-in或for-of循环中使用const时的行为与使用let一直。
全局块作用域绑定
let和const与var的另外一个区别是它们在全局作用域中的行为。
当var被用于全局作用域时,它会创建一个新的全局变量作为全局对象(浏览器环境中的window对象)的属性。
这意味着var可能会无意中覆盖一个已经存在的全局变量。
console.log(window.RegExp); // ƒ RegExp() { [native code] }
var RegExp = "Hello!";
console.log(window.RegExp); // "Hello!"
var ncz = "Hi!";
console.log(window.ncz); // "Hi!"
全局变量RegExp会覆盖之前window中的RegExp属性(window中既存的一个方法),变成了一个字符串。
同样window中也多了一个ncz的属性。
如果你在全局作用域中使用let或const,会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性。换句话说,用let或const不能覆盖全局变量,而只能遮蔽它。
Note
如果希望在全局对像下定义变量,仍然可以使用var。这种情况常见于浏览器中跨frame或跨window访问代码。
块级绑定最佳实践的进化
ES6尚在开发时,人们普遍认为应该默认使用let而不是var。
然而,当更多的开发者迁移到ES6后,另一种做法日益普及:默认使用const,只有在确实需要改变变量的值时使用let。
【读书笔记】【深入理解ES6】#1-块级作用域绑定的更多相关文章
- 深入理解ES6之《块级作用域绑定》
众所周知,js中的var声明存在变量提升机制,因此ESMAScript 6引用了块级作用域来强化对变量生命周期的控制let const 声明不会被提升,有几个需要注意的点1.不能被重复声明 假设作用域 ...
- ES6之块级作用域
一.前言 在ECMAScript6(以下简称ES6)之前,ECMAScript的作用域只有两种: 1. 全局作用域: 2. 函数作用域. 正是因为有这两种作用域,所以在JavaScript中出现一 ...
- 《深入理解ES6》笔记——块级作用域绑定(1)
本章涉及3个知识点,var.let.const,现在让我们了解3个关键字的特性和使用方法. var JavaScript中,我们通常说的作用域是函数作用域,使用var声明的变量,无论是在代码的哪个地方 ...
- 深入理解ES6之—块级绑定
var声明与变量提升 使用var关键字声明的变量,无论其实际声明位置在何处,都会被视为声明于所在函数的顶部(如果声明不在任意函数内,则视为在全局作用域的顶部).这就是所谓的变量提升. 块级声明 块级声 ...
- ES6(块级作用域)
我们都知道在javascript里是没有块级作用域的,而ES6添加了块级作用域,块级作用域能带来什么好处呢?为什么会添加这个功能呢?那就得了解ES5没有块级作用域时出现了哪些问题. ES5在没有块级作 ...
- ES6 - Note1:块级作用域与常量
在ES6以前,ES不支持块级作用域,只有全局作用域和函数作用域,所有变量的声明都存在变量声明提升. 1.let 关键字 声明一个块级变量,只在一个代码块中有效,如果在块外面访问便会报错,如下所示: { ...
- ES6——块级作用域
前面的话 过去,javascript缺乏块级作用域,var声明时的声明提升.属性变量等行为让人困惑.ES6的新语法可以帮助我们更好地控制作用域.本文将详细介绍ES6新引入的块级作用域绑定机制.let和 ...
- ES6的 let const 以及块级作用域
let声明变量 用法类似于var,但是所声明的变量只在let所在的代码块内有效. 1 . 在ES6环境下,let声明的变量不能在声明之前调用. 例: console.log(i); //会报错,这叫做 ...
- ES6 块级作用域
作用域包括:全局作用域,函数作用域,块级作用域. 为什么要用块级作用域: 1.内层变量可能会覆盖外层变量. var name = "kevin"; function call() ...
随机推荐
- 插件安装:包管理器——Package Control
首先,按CTRL+`,打开控制台 粘贴下面的代码,之后回车 如果是sublime3 ? 1 import urllib.request,os,hashlib; h = '7183a2d3e96f1 ...
- ASIHttprequest-创建同步请求
ASIHttprequest-创建同步请求 当你发送一个同步请求后,该请求会在当前的应用主线程中运行并获取程序的控制权限,也就说你的程序处于锁定的状态,在这个期间,你进行不了任何的操作,直到该请求返回 ...
- display:inline、block、inline-block的区别 摘】
display:block就是将元素显示为块级元素. block元素的特点是: 总是在新行上开始: 高度,行高以及顶和底边距都可控制: 宽度缺省是它的容器的100%,除非设定一个宽度 <div& ...
- WebApi初探之基本操作(CRUD)
public class ProductsController : ApiController { static List<Product> products = new List< ...
- How do I list all fields of an object in Objective-C?
http://stackoverflow.com/questions/1213901/how-do-i-list-all-fields-of-an-object-in-objective-c As m ...
- Font Awesome 字体使用方法, 兼容ie7+
WebFont 技术可以让网页使用在线字体,而无需使用图片,从而有机会解决开头设计师提到的问题.它通过 CSS 的@font-face语句引入在线字体,使用 CSS 选择器指定运用字体的文本,与此同时 ...
- 【转载】使用 PIVOT 和 UNPIVOT
可以使用 PIVOT 和 UNPIVOT 关系运算符将表值表达式更改为另一个表.PIVOT 通过将表达式某一列中的唯一值转换为输出中的多个列来旋转表值表达式,并在必要时对最终输出中所需的任何其余列值执 ...
- php7简短而安全的数组遍历方法
在写 PHP 的数组遍历的时候,我们通常会这样写: foreach ($definition['keys'] as $id => $val) { // ... } 但是其实这样会引起一个重要的问 ...
- 51nod 算法马拉松 34 Problem D 区间求和2 (FFT加速卷积)
题目链接 51nod 算法马拉松 34 Problem D 在这个题中$2$这个质数比较特殊,所以我们先特判$2$的情况,然后仅考虑大于等于$3$的奇数即可. 首先考虑任意一个点对$(i, j)$ ...
- 多层代理获取用户真实IP
1. 几个概念remote_addr:如果中间没有代理,这个就是客户端的真实IP,如果有代理,这就是上层代理的IP.X-Forwarded-For:一个HTTP扩展头,格式为 X-Forwarded- ...