你真的熟悉es6吗???

649 阅读15分钟

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,但这并不是我们想要的结果,这就要考虑到上面说的作用域的问题,变量ivar 声明的,全局范围内都有效,因此每次新的值都会覆盖上一次得到的值,也就是为什么会输出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明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错,(前提是你用的变量名是一致的)那也就是说在用let声明这个变量之前都是属于这个变量的‘死区

ES6规定暂时性死区和letconst语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在ES5是很常见的,现在有了这种规定,避免此类错误就很容易了。

3. 不允许重复声明

let a=9;
let a=8;
// 这样写会报错滴,不信你试试看喽

4. 块级作用域

es5中有全局作用域和函数作用域之分,但是没有块级作用域,这就带来了很多的不便,像第一个例子来说就是由于作用域的问题导致,在比如说像内层变量可能会覆盖外层变量,es6也就新增了块级作用域的概念,很好的缓解了一些问题

const 命令

const 命令用于声明一个只读的常量,一旦声明就常量的值就不能改变,除此之外与let命令有很多相同的规则,比如:不存在变量提升、存在暂时性死区、只在声明所在的块级作用域有效、声明的常量不可重复声明

箭头函数

箭头函数也是我们常用的语法之一,用法大致分为以下几种

1. 只有一个参数,参数可以不用()

var single = a => a
single('hello, world') // 'hello, world'

2. 没有参数,必须要有()

let fun=()=>{}

3. 有多个参数,必须要有()

let fun=(a,b)=>a+b

4. 函数体有多条语句时,要使用{}

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都能取到值。

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

let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError

promise

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结构的属性和操作方法
  1. size属性,返回map结构的成员总数
  2. set(key,value) ,设置key所对应的键值,返回整个Map结构,如果key已经有值,则键值会被更新,否则生成(也可以采用链式写法操作)
  3. get(key) , 读取key对应的键值,如果找不到key,则返回undefined
  4. has(key), 返回一个布尔值,判断键是否在Map结构中
  5. delete(key) 删除某个键,返回true
  6. clear() 清除所有成员,没有返回值
  • 遍历方法

Map原生提供三个遍历器生成函数和一个遍历方法

keys() : 返回键名的遍历器

values():返回键值的遍历器。

forEach():遍历Map的所有成员。

entries():返回所有成员的遍历器

需要特别注意的是,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', '') // Hello

Symbol

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值可以显示转为字符串,也可以转为布尔值,但是不可以转为数值

这样一看是不是有好多都被我们忽略了呢,其实还有好多,由于时间关系今天就写到这里了……