ES6的一些新特性
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在2015年6月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。 es6在2017年可谓是大火,被越来越多的人频频提及,在React、ReactNative开发中广泛使用。较为深入的学习es6是非常有必要的,我总结了一些es6相关的语法特性及较为详细的使用说明,希望可以给大家带来帮助。
什么是ES6 ?
es6的全名是ECMAscript 2015
复制代码
es6有哪些新特性?
一、let
1. 没有变量提升
console.log(a);
let a = 4;
// a is not defined
复制代码
2. 不能重复申明
let a = 4;
let a = 5;
console.log(a);
// Identifier 'a' has already been declared
复制代码
3. 临时失效区(暂时性死区)
var a = 5;
function fn(){
console.log(a);
let a = 4;
}
fn();
// a is not defined
复制代码
4. 具有块级作用域(由花括号包裹的区域)
来看一个例子
var a = 5;
function foo(){
console.log(a); // undefined
if(false){
var a = 6;
}
}
复制代码
这显然不是我们想要的结果,因为
js在es6之前没有块级作用域
复制代码
for(let i = 0; i < 3; i++){
console.log(i);
}
console.log(i);
// 0 1 2 i is not defined
复制代码
{
let a = 10;
}
console.log(a);
// a is not defined
复制代码
老生常谈的闭包问题 实质上就是为了解决没有块级作用域带来的问题。
闭包
* 外部函数(作用域)中有内部函数(作用域)
* 内部函数调用了外部函数的局部变量
* 外部函数执行完后,因为内部函数还在使用该局部变量,所以该局部变量不被释放,保证内部函数正常使用
* 闭包函数(立即执行函数)为
(function(){
// ...函数体
})();
复制代码
var aLi = document.getElementsByTagName('li');// len = n
for(var i = 0; i < aLi.length; i++){
aLi[i].onclick = function(){
console.log(i);
}
}
// n......n
复制代码
var aLi = document.getElementsByTagName('li');// len = n
for(var i = 0; i < aLi.length; i++){
(function(i){
aLi[i].onclick = function(){
console.log(i);
}
})(i)
}
// 0、1、2....n
复制代码
for循环实质可以理解为 由n个”{}”构成的,在每个花括号中,let的作用域都是独立的,所以可以拿到每个i值。 但对于var来说实质是一个作用域,所以无法保留每个i的值。
二、const
在es6之前,如果我们想定义常量,通常由程序员自己约定
var NUM = 100;
复制代码
将所有字母都大写的变量约定为常量,但本质还是变量 ####1.const定义只是地址不能修改
const NUM = 100;
NUM = 200;
// Assignment to constant variable.
复制代码
由const定义的常量,无法修改其地址值。 在这里为啥要强调是地址值 因为 以下代码在非严格模式下是可以通过的
const OBJ = {a: 100};
OBJ.a = 200;
console.log(OBJ.A);
// 200
复制代码
这时候会发现这很坑啊!!! 怎么办呢,如果我们想要定义一个常量对象呢
const OBJ = {a: 100};
Object.freeze(OBJ);
OBJ.a = 200;
console.log(OBJ.a);
// 100
复制代码
这样就可以了..
2.没有变量提升
3.块级作用域
let 和 const 声明的变量不挂在window上
三、class类
在es6之前,我们要定义一个类,只能通过function来模拟
function Person(){
this.name = 'xxx';
}
Person.prototype.say = function(){
}
var person = new Person();
复制代码
问:将属性放在原型中会怎么样? 如 Person.prototype.name = 'xxx'; 答:对于普通数据类型,没有问题,但对于引用类型,对一个对象的修改,会影响所有继承该类的对象。
1.语法
class Person{
constructor(name){
this.name = name;
}
say() {
console.log(this.name);
}
}
var person = new Person();
person.say();
复制代码
2.继承的实现
class Man extends Person{
constructor(name, food){
super(name, food); // 必须放在构造器的第一行,代表调用父类的构造方法
this.food = food;
}
eat(){
console.log(this.food);
}
}
var man = new Man();
man.say();
复制代码
3.static 静态方法/变量
class Person{
constructor(){
}
static say(){
console.log(123); // 可以认为这是一个静态方法
}
static name(){
return 'xxx'; // 可以认为这是一个静态变量
}
}
Person.say();
复制代码
在es6中,规定Class 内部只有静态方法, 没有静态属性 以下写法都无效
class Person{
constructor(){
}
name: 2
static name: 2
}
console.log(Person.name)
// undefined
复制代码
四、set集合
Set是一个不能有重复元素的集合,重复添加无效
var s = new Set();
s.add(1);
s.add(2);
// s.delete(2) 删除
// s.clear() 清空
// s.size() 长度
复制代码
1.遍历Set
var keys = s.keys(); // 返回一个迭代器
for(var k of keys){
console.log(k);
}
var values = s.values();
for(var v of values){
console.log(v);
}
var entries = s.entries(); // 将key和value 组合成一个数组返回
for(var e of entries){
console.log(e);
}
复制代码
2.es6之前Array数组去重问题
方法一:
var arr = [1,2,3,4,1,1,1];
function fn(arr){
var map = {};
var newArr = arr.filter(function(item, index){
if(!map[item]){
map[item] = true;
return item;
}
});
return newArr;
}
fn(arr);
复制代码
方法二:
var arr = [1,2,3,4,1,1,1];
function fn(arr){
var newArr = [];
for(var i = 0; i < arr.length; i++){
if(newArr.indexOf(arr[i]) === -1){
newArr.push(arr[i]);
}
}
return newArr;
}
fn(arr);
复制代码
利用Array.from的方法三:
var arr = [1,2,3,4,1,1,1];
function fn(arr){
var s = new Set(arr);
return Array.from(s);
}
fn(arr);
复制代码
3.Array返回只出现一次的元素
var arr = [1,2,3,4,1,1,1];
function fn(arr){
var newArr = [];
for(var i = 0; i < arr.length; i++){
if(arr.indexOf(arr[i]) === arr.lastIndexOf(arr[i])){
newArr.push(arr[i]);
}
}
return newArr;
}
fn(arr);
复制代码
五、Map(键值对)
var m = new Map();
m.set('a',1);
m.set('b',2);
m.get('a'); // 1
// m.delete('a')
复制代码
对象的属性名 认为是字符串,但Map的键 可以是所有数据类型
forEach可以遍历Array、Set、Map。
for of被专门用来遍历迭代器。
复制代码
六、arrow 箭头函数
终于来到了箭头函数的环节()=>{} 这是一种特别简洁、优雅的函数写法 它可以这么写
var fn = (item, index) => {console.log(item)}
复制代码
也可以这么写
var fn = item => {console.log(item)}
复制代码
还可以这么写
var fn = item => (item)
fn(2) // 2
复制代码
还可以更简洁
var fn = item => item
fn(2) // 2
复制代码
=>后使用小括号() 表示将结果作为返回值,单行结果时还可以省略 当参数唯一时,还可以将前面的() 省略 但是他失去了一些东西…
var fn = () => {
console.log(arguments);
}
fn(2) // arguments is not defined
复制代码
解决方法如下:
var fn = (...arg) => {
console.log(arg);
}
fn(2) // [2]
复制代码
箭头函数不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
箭头函数会改变默认的this指向
箭头函数能够将函数外面的this指向同步到函数内部,比如说这样:
var aLi = document.getElementsByTagName('li');// len = n
for(var i = 0; i < aLi.length; i++){
aLi[i].onclick = function(){
setTimeout(function(){
console.log(this.innerHTML);
// 此时无法获取,因为this指向调用他的对象,而setTimeout为window下的方法,此时this指向window
})
}
}
复制代码
将function改为箭头函数就能够正确运行
var obj = {
a: 1,
say: function(){
console.log(this.a);
}
}
obj.say() // 1
复制代码
上面乍一看这个挺正常的结果呀… 但是如果改成箭头函数
var obj = {
a: 1,
say: () => {
console.log(this.a);
}
}
obj.say() // undefined
复制代码
所以箭头函数虽好,也要看清场合使用啊。
七、增强的对象字面量
对象字面量被增强了,写法更加简洁与灵活,同时在定义对象的时候能够做的事情更多了。具体表现在:
1、可以在对象字面量里面定义原型 2、定义方法可以不用function关键字 3、直接调用父类方法 这样一来,对象字面量与前面提到的类概念更加吻合,在编写面向对象的JavaScript时更加轻松方便了。
//通过对象字面量创建对象
var human = {
breathe() {
console.log('breathing...');
}
};
var worker = {
__proto__: human, //设置此对象的原型为human,相当于继承human
company: 'freelancer',
work() {
console.log('working...');
}
};
human.breathe();//输出 ‘breathing...
//调用继承来的breathe方法
worker.breathe();//输出 ‘breathing...
复制代码
八、字符串模板
字符串模板相对简单易懂些。ES6中允许使用反引号 ` 来创建字符串,此种方法创建的字符串里面可以包含由美元符号加花括号包裹的变量${vraible}。
//产生一个随机数
var num=Math.random();
//将这个数字输出到console
console.log(`your num is ${num}`);
复制代码
九、解构
自动解析数组或对象中的值。比如若一个函数要返回多个值,常规的做法是返回一个对象,将每个值做为这个对象的属性返回。但在ES6中,利用解构这一特性,可以直接返回一个数组,然后数组中的值会自动被解析到对应接收该值的变量中。
var [x,y]=getVal(),//函数返回值的解构
[name,,age]=['wayou','male','secrect'];//数组解构
function getVal() {
return [ 1, 2 ];
}
console.log('x:'+x+', y:'+y);//输出:x:1, y:2
console.log('name:'+name+', age:'+age);//输出: name:wayou, age:secrect
复制代码
十、参数默认值,不定参数,拓展参数
1.默认参数值
现在可以在定义函数的时候指定参数的默认值了,而不用像以前那样通过逻辑或操作符来达到目的了。
//传统的指定默认参数的方式
var name=name||'dude';
console.log('Hello '+name);
}
//运用ES6的默认参数
function sayHello2(name='dude'){
console.log(`Hello ${name}`);
}
sayHello();//输出:Hello dude
sayHello('Wayou');//输出:Hello Wayou
sayHello2();//输出:Hello dude
sayHello2('Wayou');//输出:Hello Wayou
复制代码
2.不定参数
不定参数是在函数中使用命名参数同时接收不定数量的未命名参数。这只是一种语法糖,在以前的JavaScript代码中我们可以通过arguments变量来达到这一目的。不定参数的格式是三个句点后跟代表所有不定参数的变量名。比如下面这个例子中,…x代表了所有传入add函数的参数。
//将所有参数相加的函数
function add(...x){
return x.reduce((m,n)=>m+n);
}
//传递任意个数的参数
console.log(add(1,2,3));//输出:6
console.log(add(1,2,3,4,5));//输出:15
复制代码
3.拓展参数
拓展参数则是另一种形式的语法糖,它允许传递数组或者类数组直接做为函数的参数而不用通过apply。
var people=['Wayou','John','Sherlock'];
//sayHello函数本来接收三个单独的参数人妖,人二和人三
function sayHello(people1,people2,people3){
console.log(`Hello ${people1},${people2},${people3}`);
}
//但是我们将一个数组以拓展参数的形式传递,它能很好地映射到每个单独的参数
sayHello(...people);//输出:Hello Wayou,John,Sherlock
//而在以前,如果需要传递数组当参数,我们需要使用函数的apply方法
sayHello.apply(null,people);//输出:Hello Wayou,John,Sherlock
复制代码
十一、for of 值遍历
我们都知道for in 循环用于遍历数组,类数组或对象,ES6中新引入的for of循环功能相似,不同的是每次循环它提供的不是序号而是值。
var someArray = [ "a", "b", "c" ];
for (v of someArray) {
console.log(v);//输出 a,b,c
}
复制代码
十二、模块
在ES6标准中,JavaScript原生支持module了。这种将JS代码分割成不同功能的小块进行模块化的概念是在一些三方规范中流行起来的,比如CommonJS和AMD模式。
将不同功能的代码分别写在不同文件中,各模块只需导出公共接口部分,然后通过模块的导入的方式可以在其他地方使用。
// point.js
module "point" {
export class Point {
constructor (x, y) {
public x = x;
public y = y;
}
}
}
// myapp.js
//声明引用的模块
module point from "/point.js";
//这里可以看出,尽管声明了引用的模块,还是可以通过指定需要的部分进行导入
import Point from "point";
var origin = new Point(0, 0);
console.log(origin);
复制代码
十三、Proxies
Proxy可以监听对象身上发生了什么事情,并在这些事情发生后执行一些相应的操作。一下子让我们对一个对象有了很强的追踪能力,同时在数据绑定方面也很有用处。
//定义被侦听的目标对象
var engineer = { name: 'Joe Sixpack', salary: 50 };
//定义处理程序
var interceptor = {
set: function (receiver, property, value) {
console.log(property, 'is changed to', value);
receiver[property] = value;
}
};
//创建代理以进行侦听
engineer = Proxy(engineer, interceptor);
//做一些改动来触发代理
engineer.salary = 60;//控制台输出:salary is changed to 60
复制代码
十四、Symbols
我们知道对象其实是键值对的集合,而键通常来说是字符串。而现在除了字符串外,我们还可以用symbol这种值来做为对象的键。Symbol是一种基本类型,像数字,字符串还有布尔一样,它不是一个对象。Symbol 通过调用symbol函数产生,它接收一个可选的名字参数,该函数返回的symbol是唯一的。之后就可以用这个返回值做为对象的键了。Symbol还可以用来创建私有属性,外部无法直接访问由symbol做为键的属性值。
(function() {
// 创建symbol
var key = Symbol("key");
function MyClass(privateData) {
this[key] = privateData;
}
MyClass.prototype = {
doStuff: function() {
... this[key] ...
}
};
})();
var c = new MyClass("hello")
c["key"] === undefined//无法访问该属性,因为是私有的
复制代码
十五、Promises
Promises是处理异步操作的一种模式,之前在很多三方库中有实现,比如jQuery的deferred 对象。当你发起一个异步请求,并绑定了.when(), .done()等事件处理程序时,其实就是在应用promise模式。
//创建promise
var promise = new Promise(function(resolve, reject) {
// 进行一些异步或耗时操作
if ( /*如果成功 */ ) {
resolve("Stuff worked!");
} else {
reject(Error("It broke"));
}
});
//绑定处理程序
promise.then(function(result) {
//promise成功的话会执行这里
console.log(result); // "Stuff worked!"
}, function(err) {
//promise失败会执行这里
console.log(err); // Error: "It broke"
});
复制代码