最近团队要求使用React Native开发移动应用, 会使用到JS的相关知识, 趁此机会学习一下ES6, 也算是拓宽自己的知识栈了。学习参考的是阮一峰老师的《ES6标准入门(第二版)》一书, 同时也参考了《JavaScript 标准参考教程》回顾JS在ES6之前的内容。 之前虽然对JS已经有所了解,但终归不是很熟悉,希望通过这次学习可以对JS有一个比较全面和深入的了解,书籍看过了但是还是不够深刻,因此这里将学习的内容整理做下笔记,加深理解与记忆的同时也便于以后查阅。
一、ES6简介
ES6(ECMAScript)是JavaScript语言的下一代标准, 已于2015年6月发布, 其目标是使JavaScript语言可用于编写复杂的大型应用程序。有人疑惑ECMAScript和JavaScript的关系, 简单来说就是前者是后者的规格,后者是前者的一种实现。一般来说两者其实等价
1.ECMAScript发展历史
- 1997年ECMA1.0发布, 1998年发布2.0, 1999年12月发布3.0
- 2000年开始制定4.0并于2007年发布草案,但因太过激进遭到反对,因此将一部分修改延后
- 2009年12月发布5.0
- 2015年6月ECMAScript6.0正式通过
2.部署进度查询
通过访问这里可以查看各大浏览器的最新版本对ES6的支持。
阮一峰老师也写了一个ec-checker模块检查各个运行环境对ES6的支持。
通过运行下面命令可以查看本机对ES6的支持程度。
$ npm install -g es-checker
$ es-checker
3.Babel与Traceur转码器
Babel是一个ES6转码器,可以将ES6代码转化为ES5代码,从而在浏览器或其他环境中执行。这意味着我们可以用ES6的方式编写程序, 而又不需要担心现有的环境是否支持,具体的使用方式参阅官网介绍,这里不再赘述。
代码示例:
//转码前
input.map(item => item+1);
//转码后
input.map(function(item){
return item + 1;
});
Traceur是谷歌公司提供的转码器,有兴趣的同学可以查阅其官网学习。
二、 let与const命令
1.let命令
ES6新增了let命令用来声明变量, 用法与var类似,但是用let声明的变量只在其let所在的代码块内有效。在ES5中, 我们一般都是用var声明变量,在函数之外声明的变量会被视为全局变量,同时存在变量提升现象,其方法和变量的声明都会被默认提升到所在作用域的顶部,因此即使在我们声明之前我们依然可以使用变量和方法,但是let命令与其不同,下面是let命令的几个特点:
- 不存在变量提升, 因此变量必须在声明之后才能使用,否则会报错
- 暂时性死区,因为在变量声明之前该变量是无法使用,在声明之前的代码区域对于该变量而言相当于死区, 这在语法上成为暂时性死区
- let不允许在相同作用域内重复声明同一个变量
ps: 感觉let命令与var命令相比更接近于Java等面向对象语言的声明变量的方式
2.块级作用域
在ES5中只有全局作用域和变量作用域,由此引发了两个问题:
- 内层变量覆盖外层变量
- 用来计数的循环变量会泄露为全局变量
在ES6中因为let命令的加入,实际是使ES6多了一个块级作用域。因为let声明的变量是在let所在的代码块内有效,因此我们可以很明确的知道:
- 外层作用域无法读取内层作用域的变量
- 块级作用域外部无法调用块级作用域内部定义的函数, ES6规定函数本身的作用域在其所在的块级作用域内
注意一点: 在严格模式下,函数只能在顶层作用域和函数内声明, 其他情况下(比如在if/循环代码块)中声明都会报错
3.const命令
const用来声明常量, 一旦声明,其值不能改变。const命令具有以下特点:
- const一旦声明常量,必须立即初始化,不能留到以后赋值
- const的作用域与let相同, 只在声明所在的块级作用域有效,
- const和let一样,不可以重复声明常量
- 对于复合类型变量, 变量名不指向数据, 而是指向数据所在的地址。const命令只保证该地址不会变, 但是该地址的数据可以改变,这一点和Java一样,地址不变,但地址中的对象可以改变。
总体而言感觉let和const命令的加入使得JS声明变量和常量的方式更加像面向对象语言的局部变量了。
如果真的需要将对象冻结, 需要使用Object.freeze方法。下面是一个将对象彻底冻结的函数:
var constanize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach((key, value) => {
if(typeof obj[key] === 'object'){
constantize(obj[key]);
}
});
};
4.跨模块常量以及全局对象的属性
const声明的常量只能在当前代码块有效,如果想要设置跨模块的常量需要用到export和import命令.代码示例如下:
//constants.js模块
export const A = 1;
export const B = 2;
//test.js模块
import * as constants form './constants';
console.log(constants.A);//1
全局对象是最顶层的对象, 在浏览器环境指window对象,在Node.js中指的是global对象。在ES5中全局对象的属性和全局变量是等价的。因为我们很容易不知不觉中创建全局变量,这被视为一个很大的问题。ES6中做了修正:
- var命令和function命令声明的全局变量依旧是全局对象的属性
- let、const、class命令声明的全局变量不属于全局对象的属性
备注: ES6中一共有6种声明变量的方法,除了传统的var和function命令, ES6新添加了let、const、import、class命令。
三、变量的解构赋值
概念: ES6允许按照一定的模式, 从数组和对象中提取值,对变量进行赋值, 这被称为解构。
代码示例:
//以前的变量赋值
var a = 1;
var b = 2;
var c= 3;
//ES6解构赋值
var [a,b,c] = [1,2,3];
本质上这种写法属于”模式匹配”, 只要等号两边的模式相同,左边的变量就会被赋予对应的值。
解构赋值的条件: 等号右边必须是可遍历的解构。只要某种数据结构具有Iterator接口, 都可以采用数组形式的解构赋值。
解构赋值允许指定默认值, ES6内部使用严格相等===来判断一个位置是否有值,只有右边的值严格等于undefined,默认值才会生效;同时默认值也可以引用解构赋值的其他变量, 但是该变量必须已经声明。
1.对象的解构赋值
除了数组之外解构还可用于对象,但与数组不同,数组的元素是按次序排列的, 变量的取值由它的位置决定; 而对象的属性没有次序,变量必须与属性同名才能取到正确的值,代码示例:
var {bar, foo} = {foo:"aaa", bar:"bbb"};
foo//"aaa"
bar//"bbb"
//实际上上面对象的解构赋值是以下形式的简写
var {foo:foo, bar:bar} = {foo:foo, bar:bar}
对象的解构赋值的内部机制, 是先找到同名属性, 然后在赋值给对应的变量。真正被赋值的是后者, 而不是前者。代码示例:
var {foo:baz} = {foo:"aaa", bar:"bbb"};
//首先找到与属性同名的foo,然后为其对应的变量baz赋值,真正被赋值的是变量baz.
baz //"aaa"
foo //error:foo is not defined
对象解构赋值的注意事项:
* 对象解构也可以指定默认值,其生效的条件是对象的属性值严格等于undefined;
* 解构赋值的变量都会重新声明, 因此不能使用let或者const命令提前声明变量, var命令可以;
- 解构也可以用于嵌套结构的对象,如果是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。代码示例:
var obj = {
p:[
"hello",
{y:"world"}
]
}
var {p:[x,{y}]} = obj;
x//"hello"
y//"world"
//报错
var {foo:{bar}} = {baz:"baz"}
2.字符串的解构赋值
字符串可以被转换为一个类似数组的对象, 因此也可以解构赋值, 代码示例:
const{a,b,c,d,e} = 'hello';
a//"h"
b//"e"
c//"l"
d//"l"
e//"o"
//另外转成的对象还有length属性:
let{length:len} = 'hello';
len//5
3.数值和布尔值的解构赋值
如果等号右边是数值或者布尔值,则会先转换为对象,其包装对象都有toString属性,因此我们可以获取到其属性值,代码示例:
let{toString:s} = 123;
s === Number.prototype.toString //true
解构赋值的规则是:只要等号右边的值不是对象,就将其先转化为对象。由于undefined和null无法转换为对象所以对他们进行结构赋值会报错
4.函数参数的结构赋值
函数参数也可以结构赋值,代码示例:
//参数并不是数组,而是通过结构得到x和y变量
function add([x, y]){
return x+y;
}
add([1,2])//3
//也可以使用默认值,undefined会触发函数擦书的默认值
function move({x=0, y=0}={}){
return [x,y];
}
5.圆括号的使用问题
下面三种解构赋值不能使用圆括号:
* 变量声明语句中不得使用圆括号;
* 函数参数中,模式不能带有圆括号;
* 不能将整个模式或嵌套模式中的一层放在圆括号中;
可以使用圆括号的情况只有一种:赋值语句的非模式部分可以使用圆括号
6.解构赋值的用途
交换变量的值
[x,y] = [y,x];
定义函数参数
function f([x,y,z]){...}//f([1,2,3])
functionf({x,y,z}){...}//f({z:3,y:2,x:1})
从函数返回多个值
//返回一个数组
function example(){
return [1,2,3];
}
var [a,b,c] = example();
//返回一个对象
function test(){
return {
foo:1,
bar:2
};
}
var {foo, bar} = test();
提取JSON数据
var jsonData = {
id:42,
status:"OK",
data:[867, 5309]
}
let {id, status, data:number} = jsonData;
//id = 42, status = OK, number = [867, 5309];
指定函数参数的默认值
function move({x=0,y=0}){...}
遍历Map结构
任何部署了Iterator接口的对象都可以用for…of循环遍历,Map原生支持Iterator结构配合解构赋值,获取key和value非常方便
//获取键key
for(let [key] of map){}
//获取值value
for(let[,value] of map){}
//获取键值
for(let [ley,value] of map){}
输入模块的指定方法
加载模块时,往往需要指定输入哪些方法, 解构赋值使得输入语句非常清晰
const {SourceMapConsumer, SourceNode} = require("source-map");
上面就是关于ES6let和const命令的相关简记,继续学习ES6+的其他知识点。