es6入门之一

参考自《ECMAScript 6入门》——阮一峰

本节主要内容:

let命令、暂时性死区、冻结对象、const命令、解构赋值、块级作用域、跨模块常量、全局对象属性。

 

es6是js语言的下一代标准,15年6月发布,也就是ECMAScript(2015)。

1.ECMAScript和javascript是什么关系?

    前者是后者的规格,后者是前者的一种实现(另外的ECMAScript方言还有Jscript和ActionScript)。在日常场合,这两个词是可以互换的。

2.let命令

    ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

  eg:

{
    let a = 10;
    var b = 1;
}

a//ReferenceError:a is not defined.
b//1

for循环的计数器就很适合使用let命令

eg:(使用var)

以上代码中,变量i 是var声明的,在全局范围内都有效。所以每一次循环,新的i值都会覆盖旧值,导致最后输出的是最后一轮的i的值。

如果使用let,声明的变量仅仅在块级作用域内有效,最后输出的是6

eg:(使用let)

暂时性死区:

只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响

eg:

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

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

ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前使用这些命令,就会报错。即在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ)。

比较隐蔽的死区:

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;//不报错
    }
}

ES5只有两种声明变量的方法:var命令和function命令。ES6除了添加let和const命令,还有import命令和class命令,所以ES6一共有6种声明变量的方法。

 

2.块级作用域

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

一:内层变量可能会覆盖外层变量

var tmp = new Date();

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

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

二.用来计数的循环变量泄露为全局变量。

var s = 'Hello';

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

上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。

 

ES6的块级作用域。

let实际上为javascript新增了块级作用域。

块级作用域的出现,实际上使得获得广泛应用的立即执行匿名函数(IIFE)不再必要了。另外,ES6也规定,函数本身的作用域,在其所在的块级作用域之内。

需要注意的是,如果在严格模式下,函数只能在顶层作用域和函数内声明,其他情况(如if代码块、循环代码块)的声明都会报错。

4.const命令

const也用来声明变量,但是声明的是常量,一旦声明,常量的值就不能改变。即const一旦声明变量,就必须立刻初始化,不能留到以后赋值。

const的作用域与let命令相同:只在声明所在的块级作用域内有效。

const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用;与let一样不可重复声明。

对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变,所以将一个对象声明为常量必须非常小心。

即不可变的只是这个地址,不能将变量指向另一个地址,但对象本身是可以变的,所以依然可以为其添加新属性。

eg:

const a  = [];
a.push('Hello');//可执行;
a.length = 0;//可执行
a = ['world'];//报错

5.对象冻结

如果想将对象冻结,应该使用Object.freeze方法。

除了将对象本身冻结,对象的属性也应该被冻结。

 

6.跨模块常量

   因为const声明的常量只在当前代码块有效。如果想设置跨模块的常量,可以采用下面的写法。

//constants.js
export const A = 1;
export const B = 3;

//test1.js
import *as constants from './constants';
console.log(constants.A);//1
console.log(constants.B);//3

//test2.js
import {A,B} from './constants';
console.log(A);//1
console.log(B);//3

7.全局对象的属性

全局对象是最顶层的对象,浏览器环境指的是window对象,Node.js指的是global对象。ES5中,全局对象的属性与全局变量是等价的。

这种规定被视为JS语言的一大问题,因为很容易不知不觉就创建了全局变量。ES6为了改变这一点,一方面规定,var命令和function命令声明的全局变量,依旧是全局对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。

var a = 1;
window.a //1

let b = 1;
window.b //undefined

8.数组的解构赋值

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。解构是为了使数据访问更加便捷。

以前,为变量赋值,只能直接指定值:

var a = 1;
var b = 2;
var c = 3;

//ES6新版本
var [a,b,c] = [1,2,3];

上面的代码表示,可以从数组中提取值,按照对应位置,对变量进行赋值。

本质上,这种写法属于“模式匹配”,只要两边模式相同,左边的变量就会被赋予对应的值。数组解构中不需要小括号包裹表达式,这一点与对象解构的约定不同。

如果解构不成功,变量的值就等于undefined。

 

 

另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功:

 

 

 

如果等号的右边不是数组(或者严格来说,不是可遍历的结构),那么将会报错。

let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

 以上的表达式都会报错,因为等号右边的值要么转为对象后不具备Iterator接口(前五个表达式),要么本身就不具备Iterator接口(最后一个表达式)。

解构赋值不仅适用于var,也适用于let 和const。

对于Set解构,也可以使用数组的解构赋值。

let[x,y,z] = new Set(['a','b','c']);
x//a

事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。

上面代码中,fibs是一个Generator函数,原生具有Iterator接口。解构赋值会依次从这个接口获取值。

(Generator函数:Generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。类似与python的generator的概念和语法。generator和函数不同的是,generator由function*定义,并且除了return语句,还可以用yield返回多次) 

 

9.对象的解构赋值

解构不仅可以用于数组,还可以用于对象

对象的解构和数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

如果变量名和属性名不一致,必须写成下面这样:

 

这实际上说明,对象的解构赋值是下面形式的简写:

    var {foo:foo,bar:bar} = {foo:'aaa', bar:'bbb'};

也就是说,对象的解构赋值的内部机制是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者。

 

10.字符串的解构赋值

字符串也可以解构赋值,这是因为此时,字符串被转换成了一个类似数组的对象。

 

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

 

 

11.数值和布尔值的解构赋值

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。

 

解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。

12.函数参数的解构赋值

函数的参数也可以使用解构赋值。

上面代码中,函数add的参数实际上不是一个数组,而是通过解构得到的变量x和y。

 

12.关于解构中的圆括号

解构赋值虽然很方便,但是解析起来很不容易。对于编译器来说,一个式子到底是模式,还是表达式,没有办法一开始就知道,必须解析到(或解析不到)等号才能知道。

建议只要有可能,就不要在模式中放置圆括号。

以下三种解构赋值不得使用圆括号。

(1).变量声明语句中,模式不能带有圆括号。

//全部报错
var [(a)] = [1];
var {x:(c)} = {};
var {o:({p:p})} = {o:{p:2}};

上面三个语句都会报错,因为它们都是变量声明语句,模式不能使用圆括号。

(2).函数参数中,模式不能带有圆括号。

        函数参数也属于变量声明,因此不能带有圆括号。

(3).不能将整个模式,或嵌套模式中的一层放在圆括号中。

//全部报错
({p:a}) = {p:42};

 

可以使用圆括号的情况:

可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。

[(b)] = [3];//正确
({p:(d)} = {});//正确
[(parseInt.prop)] = [3];//正确

以上三行语句都可以正确执行,因为首先它们都是赋值语句,而非声明语句;其次它们的圆括号都不属于模式的一部分。第一行语句中,模式是取数组的第一个成员,和圆括号无关;第二行语句中,模式是p,而非d;第三行语句和第一行性质一致;

 

13.变量的解构赋值的用途

(1)交换变量的值

[x,y] = [y,x];

(2)从函数返回多个值

    函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回,有了解构赋值,取出这些值就非常方便

//返回一个数组
function example(){
    return [1,2,3];
}
var [a,b,c] = example();


//返回一个对象
function example(){
    return {
        foo:1,
        bar:2
    };
}
var {foo,bar} = example();

(3) 函数参数的定义

    解构赋值可以方便的将一组参数与变量名对应起来。

//参数是一组有次序的值
function f([x,y,z]){...}
f([1,2,3]);

//参数是一组无次序的值
function f({x,y,z}){...}
f({z:3,y:2,x:1});

(4) 提取JSON数据

        解构赋值对提取JSON对象的数据尤其有用:

var jsonData = {
    id:42,
    status:"OK",
    data:[867,5309]
}

let {id,status,data:number} = jsonData;
console.log(id,status,number);
//42,OK,[867,5309]

(5) 函数参数的默认值

jQuery.ajax = function (url,{
    async = true,
    beforeSend = function(){},
    cache = true,
    complete = function(){},
    crossDomain = false,
    global = true,
    //...more config
}){
    //...do stuff
};

指定参数的默认值,就避免了在函数体内部再写var foo = config.foo||'default foo';这样的语句(这句没看懂呢,求大神解答)。

(6)遍历Map解构

任何部署了Iterator接口的对象,都可以使用for...of循环遍历,Map解构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便。

如果只是想获取键名,或者只想获取键值,可以写成以下:

//获取键名
for(let [key] of map){
    //...
}

//获取键值
for(let [,value] of map){
    //...
}

(7)输入模块的指定方法

加载模块时,往往需要指定输入那些方法。解构赋值使得输入语句非常清晰。

const {SourceMapConsumer, SourceNode} = require('source-map');

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值