es6 是我们大家都很熟悉的,但是作为经验不是很丰富的同志们来说,可能就只是熟悉常用的几个命令,那么除了我们最常用的语法,还有哪些是被我们忽略的呢?
let 和 const
1. let 命令是我们最常用的,类似于var,用来声明一个变量,但与var不同的是,let命令只在他所在的代码块有效,这也就是为什么可以使用let 去解决for循环的问题
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10上面的代码会输出10,但这并不是我们想要的结果,这就要考虑到上面说的作用域的问题,变量i 是var 声明的,全局范围内都有效,因此每次新的值都会覆盖上一次得到的值,也就是为什么会输出10,那么let就可以很好的解决这个问题
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6上面的代码中,由于使用let去声明的,所以变量 i 只在本轮循环有效,且每次循环都会是一个新的变量,因此会输出6,除此之外这个问题还可以用闭包去解决
2. let不存在变量提升
let 不会像var一样存在变量提升,所以变量一定要在声明后使用,这也就在一定程度上对代码的编写进行了规范,举个例子
console.log(a) //undefined
console.log(b) // 报错ReferenceError
var a=2;
let b=3;这样就有了一个新的概念“暂时性死区(temporal dead zone,简称TDZ)”,ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错,(前提是你用的变量名是一致的)那也就是说在用let声明这个变量之前都是属于这个变量的‘死区’
ES6规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在ES5是很常见的,现在有了这种规定,避免此类错误就很容易了。
3. 不允许重复声明
let a=9;
let a=8;
// 这样写会报错滴,不信你试试看喽4. 块级作用域
es5中有全局作用域和函数作用域之分,但是没有块级作用域,这就带来了很多的不便,像第一个例子来说就是由于作用域的问题导致,在比如说像内层变量可能会覆盖外层变量,es6也就新增了块级作用域的概念,很好的缓解了一些问题
const 命令
const 命令用于声明一个只读的常量,一旦声明就常量的值就不能改变,除此之外与let命令有很多相同的规则,比如:不存在变量提升、存在暂时性死区、只在声明所在的块级作用域有效、声明的常量不可重复声明
箭头函数
箭头函数也是我们常用的语法之一,用法大致分为以下几种
1. 只有一个参数,参数可以不用()
var single = a => asingle('hello, world') // 'hello, world'2. 没有参数,必须要有()
let fun=()=>{}3. 有多个参数,必须要有()
let fun=(a,b)=>a+b4. 函数体有多条语句时,要使用{}
var add = (a, b) => { if (typeof a == 'number' && typeof b == 'number') { return a + b } else { return 0 }}这里值得注意的是,当函数体有一条语句,写在{}里,和直接写是有点区别的
var str=(a,b)=>a+b
console.log(str(2,3)) //5
var str=(a,b)=>{a+b}
console.log(str(2,3)) // undefined所以啊,如果要写在{}里,要记得return哦
5. this问题
箭头函数没有一个自己的 this,但当你在内部使用了 this,常规的局部作用域准则就起作用了,它会指向最近一层作用域内的 this
解构赋值
何为解构赋值?从数组和对象中提取值,对变量进行赋值,就被称为解构
1.数组的解构赋值
var [a,b,c]=[1,2,3]
等同于
var a=1;
var b=2;
var c=3从上面的代码可以看出,对数组的解构赋值可以从数组中提取值,按照对应的位置,为变量赋值,但是如果解构不成功,变量的值就会等于undefined, 那么什么时候会解构不成功呢?
var [foo]=[]
var [bar, foo]=[1]以上两种情况都会导致解构不成功,但是如果是这样呢
let [x,y]=[1,2,3]
let [a,[b],c]=[1,[2,3],4]这两种属于不完全解构,但是确是可以成功的,不过如果等号右边不是数组,那么就会报错啦,同时解构赋值还允许使用指定默认值
var [foo=true]=[]
foo // true默认值也可以引用解构赋值的其他变量,前提是该变量必须已经声明
2.对象的解构赋值
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
var { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
var { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined如果变量名与属性名不一致,必须要这样写
var { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'对象的解构赋值也可以有默认值,不同于数组的是,对象的解构默认值生效的条件是对象的属性值严格等于undefined
var {x=3}={x:undefined}此外如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,报错报错报错
var {foo:{bar}}={bar:'bar'}如果将一个已经声明的变量用于解构赋值,下面这样还是会报错的
var x;
{x}={x:1}
// 正确的写法应该是这样
({x}={x:1})3.字符串的解构赋值
var [a,b,c,d,e]='hello'
a //'h'
b // 'e'
c // 'l'
d // 'l'
e // 'o'4. 数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。
解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeErrorpromise
Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
如果某些事件不断地反复发生,一般来说,使用stream模式是比部署Promise更好的选择。
对于promise对象,这里不做过多的介绍了,可参考这位大神的讲解 juejin.cn/post/684490…
set 和 map
1. set
es6 提供了新的数据结构set,类似于数组,只不过成员的值都是唯一的,那也就是说类似于可以去重的操作,来看个例子
var arr=new Set()
[1,1,2,2,3,3,4,4].map(item=>arr.add(item))
for(let i of arr){
console.log(i)
}
// 1,2 3,4相比于用for循环来去重是不是简单很多
set 函数也可以接受一个数组或者类似数组的对象来作为参数,用来初始化
Set结构的实例有以下属性。
Set.prototype.constructor:构造函数,默认就是Set函数。Set.prototype.size:返回Set实例的成员总数。
Set实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
add(value):添加某个值,返回Set结构本身。delete(value):删除某个值,返回一个布尔值,表示删除是否成功。has(value):返回一个布尔值,表示该值是否为Set的成员。clear():清除所有成员,没有返回值
Array.from方法可以将Set结构转为数组。
var items = new Set([1, 2, 3, 4, 5]);
var array = Array.from(items);
这就提供了去除数组重复成员的另一种方法。
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]
// set 结合扩展运算符也可以实现去重
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];Set结构的实例有四个遍历方法,可以用于遍历成员。
keys():返回键名的遍历器,由于set结构没有键名,所以就返回键值,或者可以说键名与键值是同一个值values():返回键值的遍历器entries():返回键值对的遍历器forEach():使用回调函数遍历每个成员,没有返回值
需要特别指出的是,Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用Set保存一个回调函数列表,调用时就能保证按照添加顺序调用。
2. Map
对象本质上都是键值对的结合,但是一般来说只能用字符串作为键,为了解决这个问题,也就有了Map数据结构, 他类似于对象,但是又不局限于字符串作为键,也就是各种类型的值都可以当做键
- map 可接受一个数组作为参数
var map=new Map([
['name','张三'],
['age', 18]
])
map.size // 2
map.has(name) // true
map.get('name') // "张三"
// 上面的操作实际上是执行了下面的算法
var items=[
['name','张三'],
['age', 18]
]
var map=new Map()
items.forEach(([key,value])=>map.set(key,value))
- 对同一个键多次赋值,后面的值将覆盖前面的值
- 读取一个未知的键,返回undefined
- 只有对同一对象的引用,Map结构才会将其视为同一个键
- Map结构的属性和操作方法
- size属性,返回map结构的成员总数
- set(key,value) ,设置key所对应的键值,返回整个Map结构,如果key已经有值,则键值会被更新,否则生成(也可以采用链式写法操作)
- get(key) , 读取key对应的键值,如果找不到key,则返回undefined
- has(key), 返回一个布尔值,判断键是否在Map结构中
- delete(key) 删除某个键,返回true
- clear() 清除所有成员,没有返回值
- 遍历方法
Map原生提供三个遍历器生成函数和一个遍历方法
keys() : 返回键名的遍历器
values():返回键值的遍历器。
需要特别注意的是,Map的遍历顺序就是插入顺序。
数组的扩展 Array.from()
Array.from() 方法用于将两类对象转为真正的数组:类似数组的对象和可比案例的对象
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']扩展运算符 ...
扩展运算符也可以将某些数据结构转为数组
// arguments对象
function foo() {
var args = [...arguments];
}
// NodeList对象
[...document.querySelectorAll('div')]
扩展运算符背后调用的是遍历器接口(Symbol.iterator),如果一个对象没有部署这个接口,就无法转换。Array.from方法则是还支持类似数组的对象。所谓类似数组的对象,本质特征只有一点,即必须有length属性。因此,任何有length属性的对象,都可以通过Array.from方法转为数组,而此时扩展运算符就无法转换。
此外扩展运算符还可以进行数组的拷贝
var arr=[1,2,3]
var result=[...arr]
console.log(result) // [1,2,3]Array.of方法用于将一组值,转换为数组。
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
这个方法的主要目的,是弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。
对象的扩展
ES6允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}除了属性简写,方法也可以简写。
var o = {
method() {
return "Hello!";
}
};
// 等同于
var o = {
method: function() {
return "Hello!";
}
};函数的扩展
在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
function log(x, y) {
y = y || 'World';
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World
上面代码检查函数log的参数y有没有赋值,如果没有,则指定默认值为World。这种写法的缺点在于,如果参数y赋值了,但是对应的布尔值为false,则该赋值不起作用。就像上面代码的最后一行,参数y等于空字符,结果被改为默认值。
为了避免这个问题,通常需要先判断一下参数y是否被赋值,如果没有,再等于默认值。
if (typeof y === 'undefined') {
y = 'World';
}
ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // HelloSymbol
es6中新引入了一种原始数据类型为 symbol,代表独一无二的值,他是javaScript 的第七种数据类型,那么为什么要引入这样一种新的数据类型呢?
在此之前对象的属性名都是字符串,如果你用了别人提供的对象,但是又想为这个对象新增一个属性名,这时就很有可能导致与现有的属性名冲突,这时symbol就派上用场了,他可以保证每个属性的名字都是独一无二的,也就是说避免了这种冲突
Symbol 值是通过Symbol函数生成的,也就是说现在除了原有的字符串,就是现在的Symbol类型,只要是属于Symbol类型就都是独一无二的了,举个例子来说
let a=Symbol() // 注意Symbol前面不能有new
typeof a // "symbol"同时Symbol函数也可以接受一个字符串作为参数,表示对Symbol实例的描述
let s1 = Symbol('foo')
s1 // Symbol(foo)当参数是一个对象时,就会调用该对象的toString方法,将其转换为字符串,然后生成一个Symbol值,而参数只是表示对当前Symbol值得描述,所以相同的参数返回的值是不相等的
// 没有参数
var a1=Symbol()
var a2=Symbol()
a1===a2 // false
// 有参数
var a1=Symbol('a1')
var a2=Symbol('a1')
a1===a2 // false
此外Symbol值不能与其他类型的值进行计算,否则会报错,不过Symbol值可以显示转为字符串,也可以转为布尔值,但是不可以转为数值
这样一看是不是有好多都被我们忽略了呢,其实还有好多,由于时间关系今天就写到这里了……