讲讲ECMAScript 6那些事
ECMAScript 6(下面简称ES6)已于2015年6月正式发布,它提供的许多新的语法特性,将会成为Javascript语言新的标准。
ES6发布至今,许多浏览器的最新版本对ES6大部分特性都实现了,但是没有浏览器对ES6全部特性能够完美的支持,可以通过http://kangax.github.io/compat-table/es6/查看浏览器的支持情况。如果想要更好的体验ES6的新特性和新语法,可以使用转换器,将ES6转换成ES5。
ES6的出现,给前端开发者们带来不一样的体验,使用ES6可以更加方便的实现很多复杂的功能,提高前端开发者的效率。这篇博客将会介绍ES6比较常用的语法特性,希望能够给大家带来收获。在线测试代码可以点击这里。
目录
1.let和const
2.箭头函数
3.字符串
4.解构赋值
5.函数参数
6.类 class
7.Symbols
8.for…of循环
9.Set和Map数据解构
10.Promise
11.Generators生成器
1.let和const
let
和const
是ES6新增的声明变量的命令。使用方法与var
相似,不过let
和const
声明的变量只能在所在的代码块使用,也就是块级作用域。
Const
const
即constant,用来声明只读的常量,一旦声明就必须赋值,声明常量之后不可赋值,但是可以继续添加属性。
(1)const
声明常量时必须赋值,否则报错:const Pi; // Missing initializer in const declaration
(2)由于const
声明的Pi是常量,如果初始化后对其赋值,则报错:const Pi = 3.14;
Pi = 3; //Assignment to constant variable.
(3)常量obj
是一个对象,可以对其添加属性,但不可以再次赋值:const obj = {};
obj.name = "张三";
console.log(obj); // {name: "张三"}
obj = {}; //Assignment to constant variable.
let
let
其与var
相似,它们的不同体现在三点:
(1)不存在变量提升
使用var
:var str = "hello";
function getStr(arg) {
if (arg) {
var str = 'hello world';
return str;
}
return str;
}
console.log(getStr(false)); //undefined
使用let
:let str = "hello";
function getStr(arg) {
if (arg) {
let str = 'hello world';
return str;
}
return str;
}
console.log(getStr(false)); //hello
第一个例子中,由于if内声明的变量会被提升到函数头部,所以返回了undefined。第二个例子由于let声明的变量不会发生变量提升,if内声明的变量只能在当前所在的代码块使用,所以直接返回hello。
(2)暂时性死区let
声明的变量只能在代码块中使用,同时必须在声明后才能使用,否则会报错,这就是暂时性死区。if(true){
console.log(str); //ReferenceError: str is not defined
let str = "hello";
}
在let
命令声明str之前,上面的代码都属于str的”死区”。
如果将上面的let
变成var
,则返回undefined:if(true){
console.log(str); //undefined
var str = "hello";
}
(3)不允许重复声明let
不允许在相同的作用域内,重复声明同一个变量。而var
重复声明变量,则后面的变量会覆盖前面声明的变量。if(true){
let str = "hello";
console.log(str);
let str = "world";
console.log(str); //SyntaxError: Identifier 'str' has already been declared
}
因此,对于const
、let
和var
三个命令,当想要定义常量的时候可以选择const
。当声明一个变量赋值后还会修改的可以选择let
或var
,当使用于循环计数或者算法,建议优先选择let
。
2.箭头函数
当我们使用闭包的时候,函数内部的this总是发生改变,不能指向我们所预期的对象。而箭头函数的出现,正是可以让函数里面this指向预期的对象。另外,箭头函数的出现,还可以让我们的代码量大大减少。
箭头函数即使用箭头“=>”定义函数,用法如下:let result = [1,2,3,4].map(i=>i*i);
箭头的左边是输入的参数,右边是进行的操作以及返回的值。
上面代码等同于es5:let rusult = [1,2,3,4].map(function(i){
return i*i;
})
从上两段代码中可以看出,箭头函数在一定程度上可以减少代码量,使得我们的代码更加简洁。
(1)如果函数不需要参数或者需要多个参数,需使用用圆号。let result = () => 5;
let result2 = [1,2,3,4].map((i,item)=>console.log(i:item));
(2)如果箭头右边执行多行命令,则需使用大括号,并使用return返回。let result = [1,2,3,4].map(i=>{return i*i;})
(3)如果返回的是对象,则需在大括号外层添加中括号。let result = i => ({id :i ,name : "cici"})
(4)可代替立即执行函数。(x => x*2)(3); //6
(5)在箭头函数,this
指向的是定义时所在的对象,而不是使用时所在的对象。
例子://ES5
function Person(data){
this.name = data.name;
this.age = data.ag;
this.getInfo = function(){
console.log(this.name + " " + this.age);
}
this.sayHello = function(){
setTimeout(function(){
console.log(this);
},1000)
}
}
let dudu = new Person({
name : "dudu",
age : 20
});
dudu.getInfo(); //dudu 20
dudu.sayHello(); //windows
在超时调用的代码都是在全局作用于中执行,所以函数中的this
的值指向了window对象。而箭头函数可以保持作用域,保证this
的指向不会变: function Person(data){
this.name = data.name;
this.age = data.age;
this.getInfo = function(){
console.log(this.name + " " + this.age);
}
this.sayHello = function(){
setTimeout(() => console.log(this),1000)
}
}
let dudu = new Person({
name : "dudu",
age : 20
});
dudu.getInfo(); //dudu 20
dudu.sayHello(); //Person {name: "dudu", age: 20}
在sayHello函数中,使用了箭头函数,当前作用域是在person对象的一个方法中,箭头函数生成的临时函数的作用域也就是person对象的作用域。
3.字符串
在ES6中,添加了许多对于字符串的接口,使得更加的便利地去处理字符串。
(1) 模版字符串
以前我们需要拼接字符串跟变量时,需要使用“+”进行拼接。而模板字符串的出现可以让变量嵌入到字符串中,可以让代码变得更加简单。同时,模板字符串还可保留换行符空白符,可以让字符串的定义更加方便。
a.可以在字符串中是使用变量 let name = "dudu";
let age = 20;
console.log(`my name is ${name},i am ${age} years old`); // "my name is dudu,i am 20 years old"
在模版字符串中,使用${}
包含变量。也可在变量进行算法运算或执行函数等操作。let a = 5;
let b = 10;
console.log(`5加10等于${a+b},5乘10等于${a*b}`) //5加10等于15,5乘10等于50
function add(x,y){
return x+y;
}
console.log(`5加10等于${add(a,b)}`) //5加10等于15
b.字符串换行符空白符被保留
在ES6之前:let str = "hi,"
+ "i am dudu"
+ "i am 20 years old";
console.log(str); //hi,i am dudui am 20 years old
ES6:let str2 = `hi,
i am dudu,
i am 20 years old`;
console.log(str2);
//hi,
//i am dudu,
// i am 20 years old
通过上面的示例可以看出,模版字符串的出现给字符串的使用带了很大的便利。
(2)includes、startsWith、endsWith
之前我们是通过indexOf
来判断字符串是否包含某个字符串:let str = "hello world";
console.log(str.indexOf("o")>-1);
而在ES6中,增加了三个新方法的进行检索。includes
判断字符串是否包含某字符串,返回布尔值。startsWith
判断字符串是否以某字符串开头,返回布尔值。endsWith
判断字符串是否以某字符串结尾,返回布尔值。let str = "hello world";
console.log(str.includes("wor")); //true
console.log(str.startsWith("he")); //true
console.log(str.endsWith("ld")); //true
console.log(str.includes("llo",1)); //true
console.log(str.includes("llo",5)); //false
console.log(str.startsWith("llo",2)); //true
console.log(str.startsWith("llo",3)); //false
console.log(str.endsWith("llo",5)); //true
console.log(str.endsWith("llo",9)); //false
在上面代码中,加入了第二个参数。includes
和startsWith
的第二个参数代表搜索的开始位置。而endsWith
代表了只搜索字符串的前几位。
(3) repeat repeat
方法用来返回重复多次的字符串。let str = "abc";
console.log(str.repeat(2)); // "abcabc"
console.log(str.repeat("3")); // "abcabc"
console.log(str.repeat("2.8")); // "abcabc"
console.log(str.repeat("a")); // ""
console.log(str.repeat()); // ""
console.log(str.repeat(0)); // ""
console.log(str.repeat(-1)); // 报错: Uncaught RangeError: Invalid count value
repeat
参数为重复字符串的次数,如果参数是字符串,则会先转换成数字,否则输出空字符串;如果是小数,则会被去整;如果参数是0或者不添加参数,则返回空字符串;如果是负数,则会报错。
4.解构赋值
解构赋值可以从数组或对象中提取出值为赋值给不同的变量。
(1)数组的解构赋值
ES6之前:var arr = [1,2,3];
var a = arr[0];
var b = arr[1];
var c = arr[2];
ES6:let [a,b,c] = [1,2,3];
console.log(a,b,c); //1 2 3
数组的解构赋值按照数据的变量顺序进行赋值。
(2)对象的解构赋值
ES6之前:var obj = {name : "dudu",age : 20,city : "shenzhen"};
var name = obj.name;
var age = obj.age;
var city = obj.city;
ES6:let {name,age,city} = {name : "dudu",age : 20,city : "shenzhen"};
console.log(name,age,city);
对象的解构赋值顺序可以打乱,按照变量名进行赋值。
(3)字符串的解构赋值
ES6之前:var str = "hello";
var a = str[0];
var b = str[1];
var c = str[2];
var d = str[3];
var e = str[4];
ES6:let [a,b,c,d,e] = 'hello';
console.log(a,b,c,d,e); //h e l l o
(4)函数参数的解构赋值
函数参数的解构赋值最大的特点是能够为参数设定默认值。
ES6之前:function foo(name,age){
name = name || "dudu";
age = age || 20;
console.log(name,age);
}
foo(); //dudu 20
foo("haha",21) //haha 21
ES6:function foo({name="dudu",age=20} = {}){
console.log([name,age]);
}
foo(); //["dudu", 20]
foo({}) //["dudu", 20]
foo({name : "cici"}) //["cici", 20]
foo({age : 22}); //["dudu", 22]
foo({name : "cici",age :22});//["cici", 22]
注意下面代码参数的写法与上面代码参数的写法不同:function foo({name,age} = {name : "dudu",age : 20}){
console.log([name,age]);
}
foo(); //["dudu", 20]
foo({}); //[undefined, undefined]
foo({name : "cici"}) //["cici", undefined]
foo({age : 22}); //[undefined, 22]
foo({name : "cici",age :22}); //["cici", 22]
第一个代码块中,是为参数中对象的每一个名赋值,第二个代码块中是直接为对象赋值。
5.函数参数
ES6在函数参数上新增加了默认参数、不定参数等。
(1)默认参数
ES6之前:function foo(x,y){
x = x || 1;
y = y || 2;
console.log(x,y)
}
foo(); //1 2
ES6:function foo(x = 1,y = 2){
console.log(x,y)
}
foo(); //1 2
(2) 不定参数
ES6中函数的不定参数通过(…变量名)实现,用来获取函数多余的参数。function add(...value){
let sum = 0;
for(var val of value){
sum += val;
}
console.log(sum);
}
add(1,2,3,4,5,6);
不定参数与arguments的区别是,不定参数中的变量代表的是一个数组,可以使用数组的所有方法,而arguments是类数组,只能用length属性。
6.类 class
ES6为了更接近传统语言的写法,提出了类的写法,作为对象的模版,通过关键字class来定义类。
ES6之前:function Person(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype.getInfo = function(){
console.log(this.name,this.age,this.sex)
}
ES6:class Person{
constructor(name,age,sex){
this.name = name;
this.age = age ;
this.sex = sex;
}
getInfo(){
console.log(this.name,this.age,this.sex)
}
}
在上面的代码中,constructor
方法就是构造方法。使用的时候直接用new
命令,自动调用constructor
方法。如果没有显示定义constructor
方法,则会自动添加空的constructor
。let person1 = new Person("dudu",20,"women");
person1.getInfo(); //dudu 20 women
类通过extends
关键字进行继承。class student extends Person{
constructor(name,age,sex,school){
super(name,age,school);
this.school = school;
}
getInfo(){
super.getInfo();
console.log(this.school);
}
}
super
关键字是用来继承新建父类的this
对象,因为子类没有自己的this
对象,新建实例的时候会报错。使用super
之后,可继承父类的this
对象。
ES6提出的类实现继承,比ES5通过原型链实现继承要清晰,可以让我们写出更加简洁的代码。
7.Symbols
symbols
是ES6新增的第七种原始数据类型,表示独一无二的值。简单来说,就是用来做标记的方法。
用法:let a = Symbol();
console.log(typeof a) //symbol
上面代码中,通过typeof
得出a
是symbol
数据类型。symbol
创造出来的符号是独一无二的,因此设置了相同参数symbol
函数的返回值是不想等的。console.log(Symbol() === Symbol()); //false
console.log(Symbol("a") === Symbol("a")) //false
symbol
可以用于对象的属性名,由于每个symbol
值都是不相等的,所以就可以用来作为标记,就可以保证不会出现相同的属性。let a = Symbol();
let obj = {};
obj[a] = "hello";
console.log(obj[a]); // hello
有时候会需要重新使用同一个symbol
值,可以使用 symbol.for
方法。这个方法会接受一个字符串作为参数,搜索有没有以该参数作为名称的symbol
值,如果有,则返回symbol
值,否则就新建并返回一个以该字符串为参数的symbol
值。console.log(Symbol.for("a") === Symbol.for("a"))
上面代码中,返回true
,说明上面两个的symbol
为同一个值。
8.for…of循环
for...of
是ES6新增的遍历器。可以遍历数组、类数组对象、字符串、对象等。
ES6前:let arr = ["a","b","c","d"];
for(var i in arr){
console.log(arr[i]) // a b c d
}
ES6:let arr = ["a","b","c","d"];
for(let val of arr){
console.log(val) // a b c d
}
遍历数组可以直接获取数组值。
entries()、keys()、values() entries()
、keys()
、values()
用来遍历数组。entries()
是对键值对的遍历。keys()
是对键名的遍历。values()
是对键值的遍历。let arr = ["a","b","c","d"];
for(let val of arr.keys()){
console.log(val); // 0,1,2,3
}
for(let val of arr.values()){
console.log(val); // a,b,c,d
}
for(let [i,val] of arr.entries()){
console.log(i,val); // 0 "a"
// 1 "b"
// 2 "c"
// 3 "d"
}
9.Set和Map数据解构
set和WeakSet
ES6增添了新的数据结构集set
和弱集weakset
,类似于数组,但是set
和weakset
具有元素的唯一性,若添加了已存在的元素,会被自动忽略。let set = new Set();
set.add("hello").add("world").add("hello");
console.log(set.size); // 2
console.log(set); // Set {"hello", "world"}
由于具有元素的唯一性,所以可以利用set
来除去数组重复的元素。let arr = [2,3,4,2,5,3,3,3,2];
arr = Array.from(new Set(arr));
console.log(arr); //[2, 3, 4, 5]
set
的属性:size
:用来获取set
实例的元素个数。set
的方法有四个:add
:添加实例元素。delete
:删除某个元素。has
:判断某个元素是否存在。clear
:清除所有元素。let set = new Set();
set.add("hello").add("world").add("hello");
console.log(set.size); // 2
console.log(set); // Set {"hello", "world"}
console.log(set.has("world")); // ture
set.delete("world");
console.log(set.has("world")); //false
set.clear()
console.log(set); //Set {}
weakset
是弱集,与set
相比,它能够检查元素的变量引用情况,如果元素的引用已被全部解除,则该元素就会删除,可以节省空间内存。另外,weakset
的成员只能是对象。let weakset = new WeakSet();
weakset.add("hello"); // TypeError: Invalid value used in weak set
let str = new String("hello");
weakset.add(str);
console.log(weakset.has(str)); //true
map和weakmap map
和weakmap
则与原本的object
相似,都是key/value
的键值对结构,但是object
的key
值必须是字符串或数字,而map
可以使用任何对象作为key
值。let map = new Map();
let obj = { name : "dudu"};
map.set(obj,'hello');
map.set('hello','world');
console.log(map); //Map {Object {name: "dudu"} => "hello", "hello" => "world"}
console.log(map.size); //2
console.log(map.has(obj)); // true
console.log(map.get("hello")); // world
set
的属性和方法:size
:用来获取map
实例的元素个数。set
:用来设置key
对应的键值。get
:用来获取key
对应的键值。delete
:删除某个键。has
:判断某个键是否存在。clear
:清除所有键。weakmap
和weakset
相似,但是weakmap
会检查键和值,只要其中一个的引用全被解除,则该键值对就会被删除。let map = new Map();
let obj = { name : "dudu"};
map.set(obj,'hello');
map.set('hello','world');
console.log(map.has(obj)); //true
obj = null;
console.log(map.has(obj)); //false
10.promise
ES6新添的promise
是可以用来解决回调函数无限嵌套的工具,也就是可以获取异步操作的消息,进行相应的处理。promise
的状态变化有两种:从pending
未完成到resolved
成功和pending
未完成到rejected
失败,一旦这两个状态任一发生了,就会进行下一步操作。
用法:
(1)创建一个promise实例,参数为一个函数,向该函数的传入两个参数分别为resolve
和reject
。var promise = new Promise(function(resolve,reject){
if(/*成功*/){
resolve(value);
}else{
reject(error);
}
})
resolve
函数是当promise
对象的状态从pending
未完成到resolved
成功时,进行的异步操作。reject
函数是当promise
对象的状态从pending
未完成到rejected
失败时,进行的异步操作。
(2)当promise
实例创建好之后,则用then
方法分别指定resolve
和reject
状态的毁掉函数。promise.then(function(value){
//成功
},function(error){
//失败
})
then
方法中,参入两个函数作为参数,第一个参数是promise
对象的状态变成成功resolve
时执行的回调函数。第二个参数是promise
对象的状态报错reject
时执行的回调函数。
也可以通过catch
方法进行reject
状态的回调函数。promise.then(function(value){
//成功
}).catch(function(error){
//失败
})
当promise
对象的状态变成resolve
时,则执行then
方法,当promise
对象的状态变成reject
时,则执行catch
方法。
异步加载图片实例:function loadImageAsync(url){
return new Promise(function(resolve,reject){
var image = new Image();
image.onload = function(){
resolve(image);
};
image.onerror = function(){
reject(new Error('could not load image at ' + url));
};
image.src = url;
});
}
loadImageAsync('./images/1.png').then(function(image){
//success
}).catch(function(error){
//failure
})
11.Generators生成器
ES6提供的Generators
函数是一种异步编程解决方案,本质上是一个可以暂停计算并且可以随后返回表达式的值的函数。与普通函数相比,Generators
有两个不同点:1.定义Generators
函数需要在function
与函数名之间插入*
;2、在函数内部,使用yield
语句来切出返回值。yield
与return
相似,但是yield
不退出函数,只是在函数运行过程中,通过.next()
切出一个值。function* helloworld(){
}
斐波那契数列例子:
(斐波那契数列:从第三项开始,值为前两项之和,第一二项都为1)
(1)构建生成器function* Fibo(){
let [a,b] = [1,1];
yield a;
yield b;
(true){
[a,b] = [b,a+b];
yield b;
}
}
(2)启动生成器let fibo = Fibo();
(3)运行生成器
输出前十项斐波那契数:let arr = [];
for(let i = 0;i < 10;i++){
arr.push(fibo.next().value);
}
console.log(arr); //[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]