1

一、解构赋值的使用场景

  • 交换变量的值
[x, y] = [y, x];
  • 方便从函数取值
function func() { return [1, 2, 3]; } var [a, b, c] = func();
  • 函数参数的定义
// 参数是一组无次序的值 function f({x, y, z}) { ... } f({z: 1, y: 2, x: 3});
  • 提取JSON数据
`var jsonData = { id: 12, name: "sss", data: [867, 5309] }; let { id, name, data: number } = jsonData;`
  • 函数默认参数值 
function move({x = 1, y = 2} = {}) { // 默认值 return [x, y]; } move({x: 3}); // y使用默认值,x:3, y:2

二、let与const

  1. let命令所在的代码块内有
  2. let不存在变量提升
  3. let 暂时性死区
  4. let 不能重复声明同个变量

const 声明一个只读的常量。const声明的常量不能重新赋值。

三、Symbol

常量使用Symbol值最大的好处,就是其他任何值都不可能有相同的值了
Symbol.for方法可以重新使用同一个Symbol值。
对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型

JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法。

  • includes():返回布尔值,表示是否找到了参数字符串。
  • startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
`var s = 'Hello world!'; s.startsWith('Hello') // true s.endsWith('!') // true s.includes('w') // true`

四、ES6数组拓展

Array.from方法用于将两类对象转为真正的数组

var arr= { '0': 'a', '1': 'b', '2': 'c', length: 3 }; // ES6的写法 var arr2 = Array.from(arr); // ['a', 'b', 'c']

Array.of方法用于将一组值,转换为数组。如果没有参数,就返回一个空数组。

Array.of(7, 8, 9) // [7,8,9] Array.of(13) // [13] Array.of() // [] Array.of(13).length // 1

五、箭头函数

箭头函数this对象指向
普通函数this 的指向

  • js中的this是执行上下文,在普通函数中,this指向它的直接调用者;
  • 在默认情况下(非严格模式),在全局环境下,js中的this指向window;

箭头函数的this指向

箭头函数体内的this对象就是定义时所在的对象,而不是使用时所在的对象。

简单而言,箭头函数使用时,不绑定this对象,箭头函数没有自己的this,它的this是继承而来的,默认指向在定义箭头函数时所处的对象。

function foo() { return () => { return () => { return () => { console.log('id:', this.id); }; }; }; } 
var f = foo.call({id: 1}); // 设置foo的id为1
var t1 = f.call({id: 2})()(); // id: 1 
var t2 = f().call({id: 3})(); // id: 1 
var t3 = f()().call({id: 4}); // id: 1

上面代码之中,只有一个this,就是函数foo的this。所以t1、t2、t3都输出同样的结果。

因为所有的内层函数都是箭头函数,都没有自己的this,它们的this其实都是最外层foo函数的this。所以箭头函数的this指向是创建它所在的对象,不会改变。

箭头函数有几个使用注意点:

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作Generator函数。

六、rest参数

ES6引入rest参数(形式为“...变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

function add(...values) 
{ let sum = 0; 
for (var num of values) { sum += num; } 
return sum; } 
add(4, 5, 6,7) // 22

注意,rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。函数的length属性,不包括rest参数。

七、ES6属性的遍历

ES6一共有5种方法可以遍历对象的属性:

  • for...in

    for...in循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)。

  • Object.keys(obj)

    Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)。

  • Object.getOwnPropertyNames(obj)

    Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)。

  • Object.getOwnPropertySymbols(obj)

    Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有Symbol属性。

  • Reflect.ownKeys(obj)

    Reflect.ownKeys返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举。

以上的5种方法遍历对象的属性,都遵守同样的属性遍历的次序规则。

  • 首先遍历所有属性名为数值的属性,按照数字排序。
  • 其次遍历所有属性名为字符串的属性,按照生成时间排序。
  • 最后遍历所有属性名为Symbol值的属性,按照生成时间排序。

八、class类

class关键字创建一个类代码如下:

class Person{ constructor(x, y) 
{ this.x = x; this.y = y; } // 方法之间不加逗号隔开,否则报错
sum(){ // 方法前面不用加function var sum=this.x+this.y; return sum } }

上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,通过new命令生成对象实例时,自动调用该方法。而this关键字则代表实例对象。

构造函数的prototype属性,在ES6的“类”上面继续存在。所有的方法还是在prototype属性上。

类的方法内部如果含有 this,它默认指向类的实例。

九、Map与Set数据结构

ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

var set = new Set([1, 2, 3, 3, 4, 4]);
set   // Set { 1, 2, 3, 4 }

上面代码可以发现,定义的set对象会自己去除重复的值。Set表示数据的类型是Set。

因为Set数据值的唯一性,我们可以用它来对数组进行去重。代码如下

var arr=[1, 2, 2, 3, 4, 4]; // 定义一个数组 
var set = new Set(arr); //使用扩展运算符Set结构转为数组 
var newArr=[...set]; // 或者使用Array.from方法将Set结构转为数组 var newArr=Array.from(set);
newArr // [1, 2, 3, 4]

Set实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)
下面先介绍四个操作方法。

  • add(value):添加某个值,返回Set结构本身。
  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • has(value):返回一个布尔值,表示该值是否为Set的成员。
  • clear():清除所有成员,没有返回值。
var s=new Set(); // 定义s 数据类型为
Set s.add(1).add(2).add(2); // 使用add方法添加值,重复值不会被添加 
s.size // 2 注意2被加入了两次,但是去重了一个,所以s的成员数是2 
s.has(1) // true 使用has方法判断是否有该值,有所以返回true 
s.has(2) // true
s.has(3) // false  没有该值返回
false s.delete(2); // 使用delete删除该值 
s.has(2) // false 因为2被上面删除了,所以没该值,返回false

Set结构的实例有四个遍历方法,可以用于遍历成员。Set的遍历顺序就是插入顺序。

  • keys():返回键名的遍历器。
  • values():返回键值的遍历器,可以省略。
  • entries():返回键值对的遍历器。
  • forEach():使用回调函数遍历每个成员,它的参数是函数。

Map数据结构解决了传统对象只能用字符串当作键的局限性。

Map结构的实例属性和操作方法如下:

(1)size属性

    size属性返回Map结构的成员总数。

(2)set(key, value)

    set方法设置key所对应的键值,然后返回整个Map结构。如果key已经有值,则键值会被更新,否则就新生成该键。

(3)get(key)

    get方法读取key对应的键值,如果找不到key,返回undefined。

(4)has(key)

    has方法返回一个布尔值,表示某个键是否在Map数据结构中。

(5)delete(key)

    delete方法删除某个键,返回true。如果删除失败,返回false。

(6)clear()

    clear方法清除所有成员,没有返回值。

使用方法如下:

var map=new Map();
map.set('foo', true); // 设置 
map.size; // 1
map.get('foo'); // true 
map.has('foo') // true 
map.delete('foo'); // 删除map对象的foo键,删除后使用has返回false 
map.clear() // 删除map对象所有的键

Map其他方法

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

  • keys():返回键名的遍历器。
  • values():返回键值的遍历器。
  • entries():返回所有成员的遍历器。
  • forEach():遍历Map的所有成员。

遍历方法和Set的差不多。

数据结构的互相转换

(1)Map转为数组

前面已经提过,Map转为数组最方便的方法,就是使用扩展运算符(...)。
[...myMap] // myMap表示Map数据

(2)数组转为Map

将数组转入Map构造函数,就可以转为Map。

new Map(数组)

(3)Map转为对象

如果所有Map的键都是字符串,它可以转为对象。

// Map转对象函数 
function cMapToObj(strMap)
{ let obj = Object.create(null); 
for (let [k,v] of strMap)
{ obj[k] = v; } 
return obj; 
}
cMapToObj(myMap)

(4)对象转为Map

function objToMap(obj) { let strMap = new Map(); 
for (let k of Object.keys(obj))
{ strMap.set(k, obj[k]); } 
return strMap; }
objToMap(对象)

(5)Map转为JSON

Map转为JSON要区分两种情况。一种情况是Map的键名都是字符串,这时可以选择转为对象JSON。

JSON.stringify(cMapToObj(myMap)) // cMapToObj是上面定义的函数

另一种情况是Map的键名有非字符串,这时可以选择转为数组JSON。

JSON.stringify([...myMap])

(6)JSON转为Map

JSON转为Map,正常情况下,所有键名都是字符串。

objToMap(JSON.parse( json数据 )) // objToMap是上面定义的函数

十、Proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

下面是常见的 Proxy 支持的拦截操作方法。

(1)get(target, propKey, receiver)

    拦截对象属性的读取,比如proxy.foo和proxy['foo']。

    最后一个参数receiver是一个对象,可选,参见下面Reflect.get的部分。

(2)set(target, propKey, value, receiver)

    拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。

(3)has(target, propKey)

    拦截propKey in proxy的操作,以及对象的hasOwnProperty方法,返回一个布尔值。

(4)deleteProperty(target, propKey)

    拦截delete proxy[propKey]的操作,返回一个布尔值。

(5)apply(target, object, args)

    拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。

(6)construct(target, args)

    拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。

get()

get方法用于拦截某个属性的读取操作。上文已经有一个例子,下面是另一个拦截读取操作的例子。

var person = { name: "jack" }; 
var proxy = new Proxy(person, { 
get: function(target, property) { 
if (property in target) 
{ return target[property]; }
else { throw new ReferenceError("Property \"" + property + "\" does not exist."); } } }); 
proxy.name // "jack" 
proxy.age // 抛出一个错误

上面代码表示,如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回undefined。

set()

set方法用来拦截某个属性的赋值操作。

let obj = { 
  set: function(obj, prop, value) { 
  if (prop === 'age') { 
  if (!Number.isInteger(value)) {
  throw new TypeError('错误信息:不是整数'); } 
  if (value > 200) {
  throw new RangeError('错误信息:年龄大于200'); 
    } 
   } 
// 对于age以外的属性,直接保存 
obj[prop] = value; } }; 
let person = new Proxy({}, obj);
person.age = 13; person.age // 13
person.age = 'jack' // age不是整数报错 
person.age = 300 // age大于200报错

上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值都会抛出一个错误。

十一、Iterator接口

ES6创造了一种新的遍历命令for...of循环。Iterator接口的目的,就是为所有数据结构提供了一种统一的访问机制,即for...of循环

Iterator的遍历过程如下:

  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

return方法的使用场合是,如果for...of循环提前退出(通常是因为出错,或者有break语句或continue语句),就会调用return方法。如果一个对象在完成遍历前,需要清理或释放资源,就可以部署return方法,注意,return方法必须返回一个对象,这是Generator规格决定的。

数组遍历语法的比较

for循环

最原始的就是for循环

for(var i=0;i<10;i++){ console.log(i); }

forEach

数组提供内置的forEach方法,缺点是无法中途跳出forEach循环,break命令或return命令都不能奏效。

myArray.forEach(function (value) { console.log(value); });

for...in

遍历的是key。

缺点:数组的键名是数字,但是for...in循环是以字符串作为键名“0”、“1”、“2”等等。

for...in循环主要是为遍历对象而设计的,不适用于遍历数组。

for (let i of list) { console.log( i ); }

for...of

遍历的是value。

for ...of 循环相比上面几种做法,有一些显著的优点。

  • 有着同for...in一样的简洁语法,但是没有for...in那些缺点。
  • 不同用于forEach方法,它可以与break、continue和return配合使用。
  • 提供了遍历所有数据结构的统一操作接口。
for (var n of list ) { if (n > 1000) break; console.log(n); }

var list=[1,3,"a","b",6];

代码1

for( var i in list){     console.log(i) }

代码2

for(var  i of list){     console.log(i) }

代码1依次输出结果的是:0 1 2 3 4

代码2依次输出的结果是:1 3 a b 6

十二、Promise、async异步函数

Promise是为了解决多重嵌套回调函数而提出的它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套,改成链式调用。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。

Promise提供统一的API,各种异步操作都可以用同样的方法进行处理

Promise有以下两个特点:

  • 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,一直保持这个结果。

Promise也有一些缺点:

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  • 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

ES6规定,Promise对象是一个构造函数,用来生成Promise实例。

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。

下面代码创造了一个Promise实例。

var promise = new Promise(function(resolve, reject) {
if (true){ 
resolve(value); 
} 
else { reject(error); }
});

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用并将异步操作的结果,作为参数传递出去。

**reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用并将异步操作报出的错误,作为参数传递出去。
**
then方法

Promise实例具有then方法。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

catch方法

Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
`

`getJSON("/posts.json") .then(function(posts) {}) .catch(function(error) {console.log('发生错误!', error); });`

`

上面代码中,getJSON方法返回一个Promise对象,如果该对象状态变为Resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为Rejected,就会调用catch方法指定的回调函数来处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误也会被catch方法捕获。

p.then((val) => console.log("fulfilled:", val)) .catch((err) => console.log("rejected:", err)); // 等同于 p.then((val) => console.log("fulfilled:", val)) .then(null, (err) => console.log("rejected:", err));

ES6诞生以前,异步编程的方法,常见的有下面四种。

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise 对象

ES6将JavaScript异步编程带入了一个全新的阶段,ES7的Async函数更是提出了异步编程的新解决方案

async函数

ES7提供了async函数,使得异步操作变得更加方便。

async函数是什么?
async函数就是Generator函数的语法糖。

 语法      async function name(param) { statements }

  • name: 函数名称。
  • param: 要传递给函数的参数名称。
  • statements: 函数体。

返回值

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。

async函数内部return语句返回的值,会成为then方法回调函数的参数。

async function foo(){
return "这是async函数的返回值"; 
} 
console.log(foo()) // Promise { '这是async函数的返回值' } 
foo().then((args)=>{ // 回调函数是箭头函数 
console.log(args); // 这是async函数的返回值 
})

await命令

await操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用。

await 的返回值

  • 如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。
function testAwait (x) { 
return new Promise(resolve => {
setTimeout(() => { 
resolve(x);
}, 2000); 
});
}
async function foo() { 
var x = await testAwait ("hello world");
console.log(x); 
} 
foo (); // hello world
  • 正常情况下,await命令后面是一个Promise对象。如果不是,会被转成一个已完成状态的Promise对象。

async function f() { return await 123; } f().then(v => console.log(v)) // 123

async函数返回的Promise对象,必须等到内部所有await命令的Promise对象执行完才会发生状态改变。也就是说,只有async函数内部的异步操作执行完才会执行then方法指定的回调函数。

async函数的错误处理

如果await后面的异步操作出错,那么等同于async函数返回的Promise对象被reject。

async function f() {
await new Promise(function (resolve, reject) {
throw new Error('出错了111'); 
}); 
} 
f() .then(v => console.log(v)) .catch(e => console.log(e)) // Error:出错了111

上面代码中,async函数f执行后,await后面的Promise对象会抛出一个错误对象,导致catch方法的回调函数被调用,它的参数就是抛出的错误对象。

防止出错的方法,把await命令放在try...catch代码块中。

async function f() { 
try { 
 await new Promise(function (resolve, reject) { 
  throw new Error('出错了'); }); 
  } 
  catch(e) { 
  
  }
  return await('hello world'); 
  }

十三、ES6模块化
ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器

ES6 在语言标准的层面上,实现了模块功能,成为浏览器和服务器通用的模块解决方案

模块功能主要由两个命令构成:export 和 import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。

注意,import命令具有提升效果,会提升到整个模块的头部,首先执行。

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。

如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。


阳光总在风雨后
216 声望23 粉丝

专注web开发、追求新技术