在JavaScript语言史中,ES6.0阶段也是JavaScript进阶的一个大阶段,产生了很多新的知识和新的语法规定。
let
在ES6中出现了let命令,其语法类似于var,用于声明变量,但是所声明的变量只能在let声明的代码块内有效。
上面这句话是什么意思呢,我们来说一下。
在ES6中引入了块级作用域的特点,我们知道每一个{}就是代表着一个代码块,用let声明的变量只在声明变量的代码块中有效,不受外界的影响,同样也不影响外界。
下面说一下let的一些语法结构
1.不存在变量声明
我们知道在ES6之前用var声明变量的时候,是允许先用变量后声明的(非严格模式)
console.log(num); //undefined
var num = 2;
上述代码不会报错,输出undefined。在ES6中用let声明是不允许这样的,必须先声明后用
console.log(num); //ReferenceError
let num = 3;
报错,ReferenceError : num is not defined
2.暂时性死区
只要是块级作用域存在let命令。它所声明的变量就封锁了这个区域,不受外界影响。
var tmp = 123;
if(true){
console.log(tmp); //ReferenceError
let tmp = 234;
}
ES6明确指出,如果区块中存在const和let命令,这个区块对这些命令声明的变量,从一开始就行成了封闭作用域,凡是在声明之前使用变量就会报错。总之,在代码块中let命令的变量之前,改变量是不可用的。在语法上称为‘暂时性死区’(temporal dead zone 简称(TDZ))
if(true){
// TDZ 开始
tmp = "abc"; // ReferenceError
console.log(tmp); // ReferencrError
let tmp; //TDZ 结束
console.log(tmp); //uncdfined
tmp = 123;
console.log(tmp); //123
}
在暂时性死区中,变量x又能给let声明,在声明之前都是x的死区,只要用x都会报错。因此,使用typeof也会抛出错误
console.log(typeof(n)); //undefined
console.log(typeof(x)); //ReferenceError
let x;
类似暂时性死区在函数传参的时候也会出现,比如下面代码
function bar(x = y, y = 2){ //报错
return [x,y];
}
console.log(bar());
上面代码默认x = y ,而此时y还没有定义 。属于y的死区,因此会报错。 如果把y = 2 放到第一个参数, x = y 放到第二个参数就不会报错了。
当变量赋予此变量的时候 同样也是属于死区的状态 比如:在声明时把x赋予x
var x = x; //不报错
let x = x //报错
3.不允许重复声明
let不允许在同一块级作用域中声明相同的变量,否则会报错
function func(){
let a = 10;
var a = 1;
}
func(); //报错 a已声明;
function func(){
let a = 10;
let a = 1;
}
func(); //报错 a已声明
块级作用域
在ES5之前只有全局作用域和函数作用域,出现了很多不合理的场景:比如:内层变量覆盖外层变量,用来计数的循环泄露为全局变量等等。
ES6中的块级作用域改变了这一点。
var a = [];
for(let i = 0 ; i < 10 ; i ++){
a[i] = function(){
console.log(i);
}
}
a[6](); //6 let块级作用域的i
如果不是在块级作用域在ES5之前时,i元素泄露到全局,数组a里面元素全是10,当用到块级作用域的时候,let声明的i变量每一次循环都是全新的变量。
1.ES6允许块级作用域任意嵌套:如下
{{{{{let n = 6;
console.log(n); //6
}}}}}
外层作用域无法读取到内层作用域
{{{{
let n = 5;
}
console.log(n); //ReferenceError
}}}
内层作用域也可以定义外层作用域相同的变量
{{{
let n = 10;
{
let n = 5;
console.log(n); //5
}
console.log(n); //10
}}}
2.块级作用域有了立即执行函数的功能。
var li = document.getElementsByTagName('li');
for(var i = 0; i < 10; i ++){
li[i].onclick = function(){
console.log(i); // 全部输出10
}
}
//立即执行函数状态
for(var i = 0; i < 10; i ++){
(function(i){
li[i].onclick = function(){
console.log(i); // 按照i的顺序输出 正常
}
}(i))
}
//块级作用域状态
for(let i = 0; i < 10; i++){
li[i].onclick = function(){
console.log(i); //按照i的顺序输出 和立即执行函数一样
}
}
3.块级作用域和函数声明
ES5规定,函数只能在顶层作用域和函数作用域中声明,不能再块级作用域中声明。但是浏览器没有遵守这个规定,为了兼容以前的旧代码还是支持在块级作用域中声明函数,不会报错。
在ES6中引入了块级作用域,明确的允许块级作用域内声明函数,并且在ES6函数声明相当于let,块级作用域之外不可引用。
function f(){
console.log('123');
}
(function(){
if(false){
function f(){
console.log('234');
}
}
f(); //f is not function
}());
上述代码中在ES5中会得到234,在ES6中理应会的到123,但是报错了。因为如果改变了块级作用域内声明的函数的处理规则,显然对老代码产生了很大的影响,为了减轻这种产生的不兼容的问题,ES6规定浏览器的实现可以不遵守上面的规定,有自己的语法规定:
1.允许在块级作用域内声明函数。
2.函数声明类似于var,会提升到全局作用域或者函数作用域的头部。
3.同时函数声明也会提升到块级作用域的顶部。
注意:上面三条规则对ES6的浏览器有效,其他环境不遵守,还是把函数声明当做let处理
const命令
const声明了个只读的常量,一旦声明,常量的值就不能改变
const PI = 3.14;
console.log(PI); //3.14
PI = 3;
console.log(PI); //报错 TypeError
1.const命令声明的变量不能改变,这就意味,变量用const生命的时候一旦声明必须初始化赋值,不能留到以后复制
2.对于const来说,声明不赋值就会报错
const foo //报错
3.const的作用域和let相同,声明的变量只在所在的块级作用域内有效
if(true){
const MAX = 5;
console.log(MAX); // 5
}
console.log(MAX); // ReferencrError MAX is not defined
4.const和let一样不能重复声明变量。
var message = 'Hello';
let age = 25;
const message = 'Hello word'; //报错:不可重复声明
const age = 18; // 报错: 不可重复声明
5.const声明变量的本质
const只能生成只读变量的本质时,它实际上并不是保证了声明的变量的值不能改动,而是保证了声明变量的内存地址不能改动,对于原始值来说,值就保存在内存地址中,因此等同于常量。对于引用类型来说,内存地址里面存的指针,const只能保证这个指针不会改变,并不能保证指针所指的里面的数据结构不能改变。
const obj = {};
obj.name = 'wang';
console.log(obj.name); //wang
obj = { name : '123'}; //
const a = [];
a.push('HEllo');
a.length = 0;
console.log(a); //[]
a = ['dov']; //报错
上面两种代表了引用类型内部的数据结构是可以改变的,指针不能改变。
同样也是有办法把对象冻结的,我们可以用Object.freeze()方法
const obj = Object.freeze({});
obj.name = '123'; //常规模式下不起作用,严格模式下会报错
console.log(obj.name); //undefined
如果引用值的内部还有引用值的话,我们可以通过封装冻结方法来把对象的属性也冻结
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach((key,i) => {
if(typeof(obj[key] ) === 'object'){
constantize(obj[key]);
}
})
}
通过
ES6这个东西学习ES6,感觉还是可以的,学习中的一些总结。