let命令
ES6新增了 let 命令,用来声明变量。它的用法类似于 var ,但是所声明的变量,只在 let 命令所在的代码块内有效
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
a[6]();//10
i是由var声明的所以全局范围内有效,所以每次循环都会覆盖旧值,最后输出的是最后一轮i的值
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
a[6](); // 6
i由let声明所以只在本轮循环有效,每次都是一个新的变量,所以最后输出是6
变量提升
javascript的预编译是以代码块为范围<script></script>,即每遇到一个代码块都会进行 预编译>执行
<script>
var a = "1"; //声明变量a
function b(){ //声明方法b
alert();
}
var c = function(){ //声明变量c
alert();
}
</script>
a、c为变量赋值,b为函数声明
当执行以上的代码时,首先会进入预编译阶段,对与变量赋值a、c会在内存中开辟一块内存空间并指向变量名,且赋值为undefined对于函数声明,则同样会进行开辟内存空间,但此时会直接将函数体进行处理,即用函数声明方式,则在预编译阶段便已完成了函数的创建工作
无论顺序如何先声明变量后声明函数
预编译阶段
<script>
var a = undefined;
var c = undefined;
var b = function(){
alert();
}
</script>
执行阶段
<script>
a = "1";
c = function(){
alert();
}
</script>
而let命令不存在预编译
暂时性死区
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
ES6规定存在let和const的区块中,这些命令声明的变量从一开始就成了封闭作用,在声明之前使用就会报错。称为暂时性死区TDZ
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
typeof undeclared_variable // "undefined"
undeclared_variable 是一个不存在的变量名,结果返回“undefined”。
typeof x; // ReferenceError
let x;
变量 x 使用 let 命令声明,所以在声明之前,都属于 x 的“死区”,typeof 运行时就会抛出一个 ReferenceError
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错
上面代码中,调用 bar 函数之所以报错(某些实现可能不报错),是因为参数 x 默认值等于另一个参数 y ,而此时 y 还没有声明,属于”死区“。如果 y 的默认值是 x ,就不会报错,因为此时 x 已经声明了。
function bar(x = 2, y = x) {
return [x, y];
}
bar(); // [2, 2]
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
不允许重复声明
let不允许在相同作用域内,重复声明同一个变量。
// 报错
function () {
let a = 10;
var a = 1;
}
// 报错
function () {
let a = 10;
let a = 1;
}
不能在函数内部重新声明参数
function func(arg){
let arg; // 报错
}
function func(arg) {
let arg; // 不报错
}
块级作用域
function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}
这表示外层代码块不受内层代码块的影响。外层作用域无法读取内层作用域的变量。内层作用域可以定义外层作用域的同名变量。如果使用 var 定义变量 n ,最后输出的值就是10。
function f() {
console.log('I am outside!');
}
(function () {
if(false) {// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());
在ES5中运行,会得到“I am inside!”,但是在ES6中运行,会得到“I am outside!”。这是因为ES5存在函数提升,不管会不会进入 if 代码块,函数声明都会提升到当前作用域的顶部,得到执行;而ES6支持块级作用域,不管会不会进入if代码块,其内部声明的函数皆不会影响到作用域的外部。
{
let a = 'secret';
function f() {
return a;
}
}
f(); // 报错
块级作用域外部,无法调用块级作用域内部定义的函数
let f;
{
let a = 'secret';
f = function () {
return a;
};
}
f(); // "secret"
const命令
const 声明一个只读的常量。一旦声明,常量的值就不能改变
const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: "PI" is read-only
const一旦声明变量,就必须立即初始化,不能留到以后赋值
const foo;
SyntaxError: missing = in const declarati
const的作用域与let命令相同:只在声明所在的块级作用域内有效
if (true) {
const MAX = 5;
}
MAX // Uncaught ReferenceError: MAX is not defined
const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
if (true) {
console.log(MAX); // ReferenceError
const MAX = 5;
}
const声明的常量,也与 let 一样不可重复声明
var message = "Hello!";
let age = 25;
// 以下两行都会报错
const message = "Goodbye!";
const age = 30;
对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变
const foo = {};
foo.prop = 123;
foo.prop
// 123
foo = {}; // TypeError: "foo" is read-only
如果真的想将对象冻结,应该使用 Object.freeze 方法。
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
除了将对象本身冻结,对象的属性也应该冻结。
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, value) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
ES5只有两种声明变量的方法: var 命令和 function 命令。ES6除了添加 let 和 const 命令,后面章节还会提到,另外两种声明变量的方法: import 命令和 class 命令。所以,ES6一共有6种声明变量的方法
全局对象的属性
未声明的全局变量,自动成为全局对象 window 的属性,这被认为是JavaScript语言最大的设计败笔之一。这样的设计带来了两个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道,其次程序员很容易不知不觉地就创建了全局变量(比如打字出错)。另一方面,从语义上讲,语言的顶层对象是一个有实体含义的对象,也是不合适的。
ES6为了改变这一点,let 命令、 const 命令、 class 命令声明的全局变量,不属于全局对象的属性
var a = 1;
// 如果在Node的REPL环境,可以写成global.a
// 或者采用通用方法,写成this.a
window.a // 1
let b = 1;
window.b // undefined
网友评论