1.作用域

常见的作用域主要分为几个类型:
全局作用域:变量在函数或者代码块{}外使用var定义,即为全局作用域。
在函数或者代码块{}内未使用var定义的变量属于windows的属性,也拥有全局作用域,但不是变量,是属性。
函数作用域:在函数内部定义的变量,就是局部作用域。函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域。如果想读取函数内的变量,必须借助 return 或者闭包。
闭包:
function bar(value) {
  var testValue = 'inner';

  var rusult = testValue + value;

  function innser() {
     return rusult;
  };

  return innser();
}

console.log(bar('fun'));
块状作用域:
在这个代码中, if 后 {} 就是“块”,这个里面的变量就是拥有这个块状作用域,按照规则,{} 之外是无法访问这个变量的,利用let和conse进行块状作用域变量的声明
if(true){
  let a = 1
  console.log(a)
}

let 和 conse:

let:
1. let 声明的变量拥有块级作用域
2. let 声明的全局变量不是全局对象的属性:即不能通过window.变量名 的方式访问let定义的变量
3.用let重定义变量会抛出一个语法错误:var可以重复定义 但let会报错
4.let声明的变量不会进行变量提升:
使用var定义,可在声明前调用变量,值为 undefined,
function test () {
  console.log(a)
  var a = 1
}

test() //undefined
因为进行了变量提升,相当于:
function test () {
  var a
  console.log(a)
  a = 1
}

test() //undefined
但是let不可以在声明前调用,会报错
const:
const除了具有let的块级作用域和不会变量提升外,还有就是它定义的是常量,在用const定义变量后,我们就不能修改它了,对变量的修改会抛出异常。

动态作用域:
window.a = 3
function test () {
  console.log(this.a)
}

test.bind({ a: 2 })() // 2
test() // 3
在这里 bind 已经把作用域的范围进行了修改指向了 { a: 2 },而 this 指向的是当前作用域对象

注意:JavaScript 默认采用词法(静态)作用域,如果要开启动态作用域请借助 bind、with、eval 等。

2.数组

对数组进行遍历:

ES5的方法:
1.传统for循环:
for (var i = 0; i < array.length; i++) {
  console.log(array[i]);
}
2.forEach:
array.forEach(function(i){
  console.log(i);
})
但不支持break和continue
3.every
every方法默认的返回值是false,即默认认为不进行下一次操作,因此:return false 等同于 break,return true 等同于 continue
break:
[1,2,3,4,5].every(function(i){
  if(i===2){
    return false;
  }else{
    console.log(i)
    return true
  }
})
continue:
[1,2,3,4,5].every(function(i){
  if(i===2){

  }else{
    console.log(i)
  }
return true
})
4.for……in
for (var index in array) {
  console.log(array[index]);
}
for…in 确支持 continue、break等功能 但它同时会将数组array的自定义属性也遍历出来,
这是因为 for…in 是为遍历对象创造的({a:1,b:2}),不是为数组设计的。
5.map方法
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
[1,2,3,4,5].map(item => {
    console.log(item)
})

ES6新增方法:
for……of:
for (variable of iterable) {

} 
ES6支持自定义遍历,而for……of可以遍历一切可遍历的元素(数组、对象、集合)等iterable在这里指得就是一切可遍历的对象
for (let val of [1,2,3]) {
  console.log(val);
}
// 1,2,3
实际例子:
在实际生活中:
客户:老板给我来瓶最便宜的啤酒、最便宜的火腿肠、最便宜的打火机,多少钱?
商户:稍等,我算下
所以我们需要遍历以下对象:
const Price = {
  A: [1.5, 2.3, 4.5],
  B: [3, 4, 5],
  C: [0.5, 0.8, 1.2]
}
对以上数据结构自定义遍历规则(Iterator),,希望输出1.5 3 0.5 而不是全部的数据结构
此时就只能使用for……of


将伪数组转换为数组:

伪数据具有两个特征:
1. 按索引方式储存数据
 2. 具有length属性
函数中的 arguments、DOM中的 NodeList等都是伪数组(Array-Like),需要转换成数组后才能使用数组的API

ES5中传统方法:
let args = [].slice.call(arguments);
let imgs = [].slice.call(document.querySelectorAll('img'));
基本原理是使用 call 将数组的 api 应用在新的对象上,换句话说是利用改变函数的上下文来间接使用数组的 api。

ES6中的新方法:
Array.from
let args = Array.from(arguments);
let imgs = Array.from(document.querySelectorAll('img'));
但他的作用不止转换数组,还可进行数组初始化
其语法定义:
Array.from(arrayLike[, mapFn[, thisArg]])
参数 含义 必选
arrayLike 想要转换成数组的伪数组对象或可迭代对象 Y
mapFn 如果指定了该参数,新数组中的每个元素会执行该回调函数 N
thisArg 可选参数,执行回调函数 mapFn 时 this 对象 N
因此可以利用map进行初始化:
Array.from({ length: 5 }, function () { return 1 })
为对象设置length属性后也可以转换为数组,但要下标为数值或数值字符串
let user = {
  0: '后盾人',
  '1': 18,
  length: 2
};
console.log(Array.from(user)); //["后盾人", 18]



生成新数组:

Array.of() :
该方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。
对比:
Array.of(7);       // [7]
Array.of(1, 2, 3); // [1, 2, 3]

Array(7);          // [ , , , , , , ]
Array(1, 2, 3);    // [1, 2, 3]
语法:
Array.of(element0[, element1[, …[, elementN]]])
参数 含义 必选
elementN 任意个参数,将按顺序成为返回数组中的元素 Y

Array.prototype.fill()
方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。
arr.fill(value[, start[, end]])
参数 含义 必选
value 用来填充数组元素的值 Y
start 起始索引,默认值为0 N
end 终止索引,默认值为 this.length N
let array = [1, 2, 3, 4]
array.fill(0, 1, 2)
// [1,0,3,4]
也可实现初始化:
前文中使用了:
Array.from({ length: 5 }, function () { return 1 })
进行初始化
也可以使用:
Array(5).fill(1)// [1,1,1,1,1]

查找数组内容:

ES5的方法:
filter():
方法创建一个新数组, 其返回值为包含通过所提供函数实现的测试的所有元素。 
var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])
callback用来测试数组的每个元素的函数。返回 true 表示该元素通过测试,保留该元素,false 则不保留。
它接受以下三个参数:
element:数组中当前正在处理的元素。
index:可选正在处理的元素在数组中的索引。
array:可选调用了 filter 的数组本身。
thisArg:可选执行 callback 时,用于 this 的值
function isBigEnough(element) {
  return element >= 10;
}
var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
// filtered is [12, 130, 44] 
ES6的方法:
Array.prototype.find()
find() 方法返回数组中满足提供的测试函数的第一个元素的值,否则返回 undefined。
注意:只返回一个值
let array = [5, 12, 8, 130, 44];

let found = array.find(function(element) {
  return element > 10;
});

console.log(found);
// 12
语法:
arr.find(callback[, thisArg])
参数 含义 必选
callback 在数组每一项上执行的函数,接收 3 个参数,element、index、array Y
thisArg 执行回调时用作 this 的对象 N
Array.prototype.findIndex():
与上述方法类似,该方法返回的是满足提供的测试函数的第一个元素的索引。否则返回-1。
let array = [5, 12, 8, 130, 44];

let found = array.findIndex(function(element) {
  return element > 10;
});

console.log(found);
// 1
语法:
arr.findIndex(callback[, thisArg])
参数 含义 必选
callback 在数组每一项上执行的函数,接收 3 个参数,element、index、array Y
thisArg 执行回调时用作 this 的对象 N

3.类Class

声明

ES5的声明方法:
注意:JS中的自定义类都是函数
let Animal = function (type) {
  this.type = type
  this.walk = function () {
    console.log(`I am walking`)
  }
}

let dog = new Animal('dog')
let monkey = new Animal('monkey')
也可以将某些方法放在prototype上,这样每个实例化的对象就不需要都实例化该方法
let Animal = function (type) {
  this.type = type
}

Animal.prototype.walk = function () {
  console.log(`I am walking`)
}

let dog = new Animal('dog')
let monkey = new Animal('monkey')
ES6的声明方法:
class Animal {
  constructor (type) {
    this.type = type
  }
  walk () {
    console.log(`I am walking`)
  }
}
let dog = new Animal('dog')
let monkey = new Animal('monkey')
注意:
虽然在定义上更加专业,但本质上ES6中class的类型还是funtion

属性

ES6中属性的访问和修改通过get 和 set 来实现:
let #age = 1
class Animal {
  constructor(type) {
    this.type = type
  }
  get age() {
    return #age
  }
  set age(val) {
    if (val > 0 && val < 10) {
      #age = val
    }
  }
}
注意:虽然这里set 和 get写成函数的形式 但在使用时仍旧是以属性的形式使用

方法

ES5的方法:
let Animal = function (type) {
  this.type = type
  this.walk = function () {
    console.log(`I am walking`)
  }
}

Animal.eat = function (food) {
  console.log(`I am eating`);
}
ES6的方法:
class Animal {
  constructor (type) {
    this.type = type
  }
  walk () {
    console.log(`I am walking`)
  }
  static eat () {
    console.log(`I am eating`)
  }
}

继承

在ES5中:
// 定义父类
let Animal = function (type) {
  this.type = type
}
// 定义方法
Animal.prototype.walk = function () {
  console.log(`I am walking`)
}
// 定义静态方法
Animal.eat = function (food) {
  console.log(`I am eating`)
}
// 定义子类
let Dog = function () {
  // 初始化父类
  Animal.call(this, 'dog')
  this.run = function () {
    console.log('I can run')
  }
}
// 继承
Dog.prototype = Animal.prototype
ES6中:
class Animal {
  constructor (type) {
    this.type = type
  }
  walk () {
    console.log(`I am walking`)
  }
  static eat () {
    console.log(`I am eating`)
  }
}

class Dog extends Animal {
  constructor () {
    super('dog')
  }
  run () {
    console.log('I can run')
  }
}

4.函数Function

函数默认值:

ES5中:
关于参数的默认值通常都是写在函数体中:
function f (x, y, z) {
    if (y === undefined)
        y = 7;
    if (z === undefined)
        z = 42;
    return x + y + z;
};
f(1) === 50;
ES6中:
可以直接写在函数的定义中:
function f (x, y = 7, z = 42) {
    return x + y + z
}
f(1) === 50
如果我们想让具体某个参数使用默认值,我们可以使用 undefined 进行赋值:
function f (x, y = 7, z = 42) {
  return x + y + z
}
console.log(f(1, undefined, 43))// 51
而且参数赋值支持参数的逻辑运算进行赋值:
function f (x, y = 7, z = x + y) {
  return z * 0.5
}

console.log(f(1, 7))// 4

判断函数参数个数:

ES5中:
使用 arguments 来判断
function test (a, b = 1, c) {
  console.log(arguments.length)
}
test('a', 'b')//2
ES6中:
ES6不支持使用rguments 来判断,但可以借助Function.length 来判断:
function test (a, b = 1, c) {
  console.log(test.length)
}
test('a', 'b')// 1
但两者结果不同,因为Function.length 是统计第一个默认参数前面的变量数:
function test (a = 2, b = 1, c) {
  console.log(test.length)
}
test('a', 'b')// 0

Rest Parameter

经常有无法确定参数的情况,比如求和运算
ES5中:
function sum () {
  let num = 0
  Array.prototype.forEach.call(arguments, function (item) {
    num += item * 1
  })
  return num
}

console.log(sum(1, 2, 3))// 6
console.log(sum(1, 2, 3, 4))// 10
但ES6不能再使用arguments,所以
ES6中:
function sum (...nums) {
  let num = 0
  nums.forEach(function (item) {
    num += item * 1
  })
  return num
}

console.log(sum(1, 2, 3))// 6
console.log(sum(1, 2, 3, 4))// 10
也可以和其它参数一起使用:
function sum (base, ...nums) {
  let num = base
  nums.forEach(function (item) {
    num += item * 1
  })
  return num
}

console.log(sum(30, 1, 2, 3))// 36
console.log(sum(30, 1, 2, 3, 4))// 40
arguments 不是数组,所以不能直接使用数组的原生 API 如 forEach,而 Rest Parameter 是数组,可以直接使用数组的原生 API。

Spread Operator
可以把Spread Operator 看作和Rest Parameter 相反的操作,用来把固定的数组内容“打散”到对应的参数
function sum (x = 1, y = 2, z = 3) {
  return x + y + z
}

console.log(sum(...[4]))// 9
console.log(sum(...[4, 5]))// 12
console.log(sum(...[4, 5, 6]))// 15

Arrow Function

箭头函数是个好东西。
在ES5中定义函数需要:
function hello () {
  console.log('say hello')
}
// 或

let hello = function () {
  console.log('say hello')
}
而ES6中只需要:
let hello = () => {
  console.log('say hello')
}
带参数的情况:
let hello = (name) => {
  console.log('say hello', name)
}
// 或者

let hello = name => {
  console.log('say hello', name)
}
注意:
  • 如果返回值是表达式可以省略 return 和 {}
    let pow = x => x * x
  • 如果返回值是字面量对象,一定要用小括号包起来
    let person = (name) => ({
      age: 20,
      addr: 'Beijing City'
    })
  • 普通函数和箭头函数对 this 的处理方式截然不同:
    let test = {
      name: 'test',
      say: function () {
        console.log(this.name)
      }
    }
    
    console.log(test.say())// test
    以上是普通的写法,say在调用后,this 指向的是调用 say 方法的对象,显示是 test 对象,所以 this === test,那么 this.name 也就是 test.name
    let test = {
      name: 'test',
      say: () => {
        console.log(this.name, this)
      }
    }
    console.log(test.say())// undefined
    而在箭头函数中,this的指向由定义是决定,即this 的指向也就是 test 外层的所指向的 window,而 window 没有 name 属性,所以结果是 undefined。

5.Object

Enhanced Object Properties

1.Property Shorthand
ES5中,Object 的属性必须是 key-value 形式:
var x = 0, y = 0;
obj = { x: x, y: y };
在ES6中,可以简写
var x = 0, y = 0
obj = { x, y }
但这两种方式本质上是相同的,遇到简写形式要反应上来
2.Computed Property Names
以上的key值都是字符串,当key值是变量或表达式时,ES6中可以直接写:
let obj = {
  foo: 'bar',
  ['baz'+ quux()]: 42
}
3.Method Properties
value值可能是一个函数 可以直接写在object里 包括异步函数 *
let obj = {
  foo (a, b) {

  },
  bar (x, y) {

  },
  * quux (x, y) {

  }
}
相当于
var obj = {
  foo: function (a, b) {

  },
  bar: function (x, y) {

  }
  //  quux: no equivalent in ES5
}

Set

Set类似于一个“集合” 不允许数据重复
1.实例化
let s = new Set()
let s = new Set([1, 2, 3, 4])
2.添加
s.add('hello')
s.add('goodbye')
3.删除
// 删除指定数据
s.delete('hello') // true
// 删除全部数据
s.clear()
4.查找统计
// 判断是否包含数据项,返回 true 或 false
s.has('hello') // true
// 计算数据项总数
s.size // 2
其他方法有:
  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员
  • for…of:可以直接遍历每个成员

Map

Map相当于一个字典,通过键值队进行保存
1.实例化
let map = new Map([iterable])
2.添加
let keyObj = {}
let keyFunc = function () {}
let keyString = 'a string'

// 添加键
map.set(keyString, "和键'a string'关联的值")
map.set(keyObj, '和键keyObj关联的值')
map.set(keyFunc, '和键keyFunc关联的值')
3.删除
// 删除指定的数据
map.delete(keyObj)
// 删除所有数据
map.clear()
4.查找统计
// 统计所有 key-value 的总数
console.log(map.size) //2
// 判断是否有 key-value
console.log(map.has(keyObj)) // true
其他方法有:
  • get() 方法返回某个 Map 对象中的一个指定元素
  • keys() 返回一个新的 Iterator 对象。它包含按照顺序插入 Map 对象中每个元素的 key 值
  • values() 方法返回一个新的 Iterator 对象。它包含按顺序插入Map对象中每个元素的 value 值
  • entries() 方法返回一个新的包含 [key, value] 对的 Iterator ?对象,返回的迭代器的迭代顺序与 Map 对象的插入顺序相同
  • forEach() 方法将会以插入顺序对 Map 对象中的每一个键值对执行一次参数中提供的回调函数
  • for…of 可以直接遍历每个成员
注意:forEach 和 for…of 这两个函数的传参顺序不同
map.forEach((value, key, map) => {
  console.log(value, key, map)
}, thisArg)
for ([key, value] of map) {
  console.log(key, value)
注意:理解一个数据结构 从实例化 加 删 查 四个功能入手

Map 和 Object 的区别

键的类型
一个Object的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值,包括函数、对象、基本类型。

键的顺序
Map 中的键值是有序的,而添加到对象中的键则不是。因此,当对它进行遍历时,Map 对象是按插入的顺序返回键值。

键值对的统计
你可以通过 size 属性直接获取一个 Map 的键值对个数,而 Object 的键值对个数只能手动计算。

键值对的遍历
Map 可直接进行迭代,而 Object 的迭代需要先获取它的键数组,然后再进行迭代。

性能
Map 在涉及频繁增删键值对的场景下会有些性能优势。

Object.assign()

该方法用于将所有可枚举属性的值复制到另一个对象中,并返回。
1.基本语法:
Object.assign(target, …sources)
参数 含义 必选
target 目标对象 Y
sources 源对象 N
其中,目标对象若不是对象,会自动转化为对象。源对象不限制数目
let t = Object.assign(2)
// Number {2}
let s = Object.assign(2, { a: 2 })
// Number {2, a: 2}

6.正则

Sticky

y修饰符,意为粘连”(sticky),y的作用和g的类似,也是全局匹配。
但是g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。
const s = 'aaa_aa_a'
const r1 = /a+/g
const r2 = /a+/y

r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]

r1.exec(s) // ["aa"]
r2.exec(s) // null
进一步说,y修饰符号隐含了头部匹配的标志^。

Unicode

u修饰符,含义为“Unicode模式”,用来正确处理大于 \uFFFF 的Unicode字符。也就是说,会正确处理四个字节的UTF-16编码。
/^\uD83D/u.test('\uD83D\uDC2A')
// false
/^\uD83D/.test('\uD83D\uDC2A')
// true

7.Template

 String Literals

用以解决字符串拼接 和 多行字符串 的问题
`string text`

`string text line 1
 string text line 2`

`string text ${expression} string text`
${} 可以任意插入变量或者表达式

Tag Literals

 Tag 函数可以来充当一个模板引擎
function Price (strings, type) {
  let s1 = strings[0]//这里传入的strings是一个数组 包含了模板中要用到的各个字符串
  const retailPrice = 20
  const wholesalePrice = 16
  let txt = ''
  if (type === 'retail') {
    txt = `购买单价是:${retailPrice}`
  } else {
    txt = `批发价是:${wholesalePrice}`
  }
  return `${s1}${txt}`//最后的返回值
}

let showTxt = Price`您此次的${'retail'}`//这里就相当于传参

console.log(showTxt) //您此次的购买单价是:20
标签函数的第一个参数包含一个字符串值的数组。其余的参数与表达式相关。
下面是传入多个字符串和表达式的例子:
var person = 'Mike';
var age = 28;

function myTag(strings, personExp, ageExp) //多个表达式参数 
{
    var str0 = strings[0]; // "that "
    var str1 = strings[1]; // " is a "   
    var ageStr;
    if (ageExp > 99) {
        ageStr = 'centenarian';
    } else {
        ageStr = 'youngster';
    }
    return str0 + personExp + str1 + ageStr; //通过传统的字符串拼接返回了结果
    //也可以写作 return `${str0}${personExp}${str1}${ageStr} `;  
}
var output = myTag `that ${ person } is a ${ age }`;
console.log(output); // that Mike is a youngster

8.Desctructuring

解构赋值,又能解构,又能赋值。可以方便的实现许多复杂数据结构的赋值。
比如:
let [firstName, surname] = ['Ilya', 'Kantor']
console.log(firstName) // Ilya
console.log(surname) // Kantor
可以一口气赋值

Array Destructuring

1.可以使用逗号跳过赋值元素
2.赋值元素可以是任何可遍历的对象 如字符串 Set数组等
3.可以对 对象的属性 进行赋值 而不仅仅是变量
4.解构赋值在循环体中的应用,可以配合 entries 使用:
let user = {
  name: "John",
  age: 30
};

// loop over keys-and-values
for (let [key, value] of Object.entries(user)) {
  console.log(`${key}:${value}`); // name:John, then age:30
}
let user = new Map();
user.set("name", "John");
user.set("age", "30");

for (let [key, value] of user.entries()) {
  console.log(`${key}:${value}`); // name:John, then age:30
}
5.在不确定赋值对象个数的情况下,可以使用rest参数,但要把rest参数放在最后一位
let [name1, name2, ...rest] = 
["Julius", "Caesar", "Consul", "of the Roman Republic"];
6.如果数组的内容少于变量的个数,并不会报错,没有分配到内容的变量会是 undefined,也可以自行赋默认值。

Object Destructuring

将一个Object里面的属性,赋值给指定的变量,需要创建和Object一致的结构:
let options = {
  title: "Menu",
  width: 100,
  height: 200
};

let {title, width, height} = options;
以上采用的简写的方式:
let {title: title, width: width, height: height} = options;
如果想使用其他变量名,可以
let {width: w, height: h, title} = options;
但是冒号前的key值必须和Object里的一致,才能寻找到Object里对应的属性,先后顺序可以不同
1.可以指认默认值
2.也可以使用rest参数
3.嵌套对象
如果一个 Array 或者 Object 比较复杂,它嵌套了 Array 或者 Object,那只要被赋值的结构和右侧赋值的元素一致就好了:
let options = {
  size: {
    width: 100,
    height: 200
  },
  items: ["Cake", "Donut"],
  extra: true    // something extra that we will not destruct
};

// destructuring assignment on multiple lines for clarity
let {
  size: { // put size here
    width,
    height
  },
  items: [item1, item2], // assign items here
  title = "Menu" // not present in the object (default value is used)
} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200
alert(item1);  // Cake
alert(item2);  // Donut

9.Promise

Promise用来解决回调地狱,能够将异步操作队列化,从而按照期望的顺序执行
Promise 可以理解为承诺,就像我们去KFC点餐服务员给我们一引取餐票,这就是承诺。如果餐做好了叫我们这就是成功,如果没有办法给我们做出食物这就是拒绝。
let kfc = new Promise((resolve, reject) => {
  console.log("肯德基厨房开始做饭");
  resolve("我是肯德基,你的餐已经做好了");
});
let dad = kfc.then(msg => {
  console.log(`收到肯德基消息: ${msg}`);
  return {
    then(resolve) {
      setTimeout(() => {
        resolve("孩子,我吃了两秒了,不辣,你可以吃了");
      }, 2000);
    }
  };
});
let son = dad.then(msg => {
  return new Promise((resolve, reject) => {
    console.log(`收到爸爸消息: ${msg}`);
    setTimeout(() => {
      resolve("妈妈,我和向军爸爸吃完饭了");
    }, 2000);
  });
});
let ma = son.then(msg => {
  console.log(`收到孩子消息: ${msg},事情结束`);
});

基本语法

new Promise( function(resolve, reject) {…} );
通过创建 Promise 对象开启一个异步操作的过程,一般用几步完成多次异步操作:
  1. new Promise(fn) 返回一个Promise 对象
  2. 在fn 中指定异步等处理
  3. 处理结果正常的话,调用resolve(处理结果值)
  4. 处理结果错误的话,调用reject(Error对象)

状态说明

Promise包含pendingfulfilledrejected三种状态

  • pending 指初始等待状态,初始化 promise 时的状态 
  • resolve 指已经解决,将 promise 状态设置为fulfilled
  • reject 指拒绝处理,将 promise 状态设置为rejected
  • 状态转化是单行的,不可逆转。一个 promise 必须有一个 then 方法用于处理状态改变

then

promise.then(onFulfilled, onRejected);
then方法用来访问promise的结果,针对不同状况做不同的处理
  • then 方法必须返回 promise,用户返回或系统自动返回
  • 第一个函数在resolved 状态时执行,即执行resolve时执行then第一个函数处理成功状态
  • 第二个函数在rejected状态时执行,即执行reject 时执行第二个函数处理失败状态,该函数是可选的
  • 两个函数都接收 promise 传出的值做为参数
  • 也可以使用catch 来处理失败的状态
  • 如果 then 返回 promise ,下一个then 会在当前promise 状态改变后执行
例子:
var promise = new Promise(function (resolve, reject) {
  resolve('传递给then的值')
})
promise.then(function (value) {
  console.log(value)
}, function (error) {
  console.error(error)
})
在调用then时也可以不传入参数
function loadScript (src) {
  // pending,undefined
  return new Promise((resolve, reject) => {
    let script = document.createElement('script')
    script.src = src
    script.onload = () => resolve(src)// fulfilled,result
    script.onerror = (err) => reject(err)// rejected,error
    document.head.append(script)
  })
}

loadScript('./1.js')
  .then(() => {
    return loadScript('./2.js')//如果js1加载成功 则继续加载js2
  }, (err) => {
    console.log(err)
  })
  .then(() => {
    return loadScript('./3.js')//上一步不许返回的是一个promise对象 才能继续调用then方法
  }, (err) => {
    console.log(err)
  })

resolve

resolve可以作为快捷创建方法
 Promise.resolve(42) 
等同于
new Promise(function (resolve) {
  resolve(42)
})
resolve可以使promise对象立刻进入确定(即resolved)状态,并将 42 传递给后面 then 里所指定的 onFulfilled 函数。
Promise.resolve(42).then(function (value) {
  console.log(value)
})

reject

Promise.reject(error) 是和 Promise.resolve(value) 类似的静态方法,也可以作为快捷创建方法
Promise.reject(new Error(“出错了”)) 
等同于
new Promise(function (resolve, reject) {
  reject(new Error('出错了'))
})

catch

使用 catch 方法可捕获异步操作过程中出现的任何异常等同于 then(null,reject){}
将 catch 放在最后面用于统一处理前面发生的错误,而不需要像使用 then(null,reject){}那样挨个调用
p.catch(onRejected);
p.catch(function(reason) {
// rejection
});
例子:
function test () {
  return new Promise((resolve, reject) => {
    reject(new Error('es'))
  })
}

test().catch((e) => {
  console.log(e.message) // es
})

all()

Promise.all(promiseArray);
使用该方法可以同时执行多个并行异步操作,比如页面加载时同进获取课程列表与推荐课程。
  • 任何一个 Promise 执行失败,则整个Promise.all调用会立即终止。会调用 catch方法。并返回一个reject的新的 Promise 对象。
  • 适用于一次发送多个异步操作
  • 参数必须是可迭代类型,如Array/Set
  • 所有的 Promise 对象都变为resolve时才算成功,返回 promise 结果的有序数组
var p1 = Promise.resolve(1)
var p2 = Promise.resolve(2)
var p3 = Promise.resolve(3)
Promise.all([p1, p2, p3]).then(function (results) {
  console.log(results) // [1, 2, 3]
})

race()

Promise.race(promiseArray);
使用该方法处理容错异步,和race单词一样哪个Promise快用哪个,哪个先返回用哪个。
  • 以最快返回的promise为准
  • 如果最快返加的状态为rejected 那整个promise为rejected执行cache
  • 如果参数不是promise,内部将自动转为promise
var p1 = Promise.resolve(1)
var p2 = Promise.resolve(2)
var p3 = Promise.resolve(3)
Promise.race([p1, p2, p3]).then(function (value) {
  console.log(value) // 1
})

10.Reflect

许多Object的方法被移植到了Reflect上,它本质上也是一个内置的对象。不能够实例化,也不能被构造。
Reflect的所有属性和方法都是静态的(就像Math对象)

Reflect.apply()

与 Function.prototype.apply()  功能类似  但更加简洁
对一个函数进行调用操作,并且显式地指定 this 变量和参数列表(arguments) ,参数列表可以是数组,或类似数组的对象。
语法:
Reflect.apply(target, thisArgument, argumentsList)
其中:
参数 含义 必选
target 目标函数 Y
thisArgument target函数调用时绑定的this对象 N
argumentsList target函数调用时传入的实参列表,该参数应该是一个类数组的对象 N
返回值是调用完带着指定参数和 this 值的给定的函数后返回的结果。
例子:
Reflect.apply(Math.floor, undefined, [1.75])
// 1;

Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111])
// "hello"

Reflect.apply(RegExp.prototype.exec, /ab/, ['confabulation']).index
// 4

Reflect.apply(''.charAt, 'ponies', [3])
// "i"
说白了就是 thisArgument.target(argumentsList) 返回值是最终的函数结果

Reflect.construct()

相当于执行 new target(...args),对构造函数进行 new 操作,也与使用Object.create()相同
语法:
Reflect.construct(target,argumentsList[,newTarget])
参数 含义 必选
target 被运行的目标函数 Y
argumentsList 调用构造函数的数组或者伪数组 Y
newTarget 该参数为构造函数, 参考 new.target 操作符,如果没有newTarget参数, 默认和target一样 N
返回值是一个对象实例:target(如果newTarget存在,则为newTarget)函数为构造函数,argumentList为其初始化参数的对象实例。
解释:target是被运行的函数 但newTarget是原型对象
如:
function OneClass(num) {
    this.name = 'one';
    this.id=num;
}

function OtherClass(num) {
    this.name = 'other';
    this.oid=num;
}

var d1 = Reflect.construct(OneClass, [1],OtherClass);
var d2 = Reflect.construct(OtherClass, [1],OneClass);
console.log(d1);//结果为 OtherClass {name: "one", id: 1}
console.log(d2);//结果为 OneClass {name: "other", oid: 1}
console.log(d1 instanceof OtherClass);//true
console.log(d2 instanceof OneClass);//true
在该方法出现之前,通过Object.create()来创建对象,分为两步:
1.明确指定构造函数 2.明确原型对象
如下面的d1 和 d2 分别的构造方法与前文的代码就是相同的
function OneClass(num) {
    this.name = 'one';
    this.id=num;
}

function OtherClass(num) {
    this.name = 'other';
    this.oid=num;
}

// 创建一个对象:
var d1 = Reflect.construct(OneClass, [1],OtherClass);
var d2 = Object.create(OneClass.prototype);
OtherClass.apply(d2,[1])
console.log(d1);//OtherClass {name: "one", id: 1}
console.log(d2);//OneClass {name: "other", oid: 1}

补充:apply方法

apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。
和 call()方法类似,但是call()方法接受的是参数列表,而apply()方法接受的是一个参数数组
语法:
func.apply(thisArg, [argsArray])
thisArg
在 func 函数运行时使用的 this 值
(即调用该函数的对象)
必选的
argsArray
一个数组或者类数组对象
其中的数组元素将作为单独的参数传给 func 函数
可选的
返回值:调用有指定this值和参数的函数的结果
描述:
this 指的就是调用函数的对象。在调用一个已经定义的函数时,必须指定 this 对象。这样使用 apply 方法,就可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。
说白了,就是相当于 thisArg.func(argsArray)  
//注意 此处传入的参数不是数组形式,当时作为单独的参数传进去
如:将 一个数组 添加到 另一个数组 中
可以使用  arrayObject.concat(arrayX,arrayX,......,arrayX)来进行类似的操作 但是concat返回一个全新数组,使用array可以直接对原数组进行更改:
var array = ['a', 'b'];
var elements = [0, 1, 2];
array.push.apply(array, elements);//相当于 array.push(0,1,2)
console.info(array); // ["a", "b", 0, 1, 2]
也可以使用apply来连接构造器,即前面写到的:
var d2 = Object.create(OneClass.prototype);//指定原型
OtherClass.apply(d2,[1])//使用构造函数 并 传入参数
或者如下例子:
Function.prototype.construct = function (aArgs) {
  var oNew = Object.create(this.prototype);
  this.apply(oNew, aArgs);
  return oNew;
};

充:理解this

this 不是固定不变的,它会随着执行环境的改变而改变。

  • 在方法中,this 表示该方法所属的对象。
  • 如果单独使用,this 表示全局对象。
  • 在函数中,this 表示全局对象。
  • 在函数中,在严格模式下,this 是未定义的(undefined)。
  • 在事件中,this 表示接收事件的元素。
  • 类似 call() 和 apply() 方法可以将 this 引用到任何对象。
只有函数执行的时候才能确定this到底指向谁实际上this的最终指向的是那个调用它的对象

Reflect.defineProperty()

静态方法 Reflect.defineProperty() 基本等同于 Object.defineProperty() 方法,唯一不同是返回 Boolean 值。
语法:
Reflect.defineProperty(target,propertyKey,attributes)
参数 含义 必选
target 目标对象 Y
propertyKey 要定义或修改的属性的名称 Y
attributes 要定义或修改的属性的描述 Y

Reflect.deleteProperty()

静态方法 Reflect.deleteProperty() 允许用于删除属性。它很像 delete operator ,但它是一个函数。
Reflect.deleteProperty(target, propertyKey)
参数 含义 必选
target 删除属性的目标对象 Y
propertyKey 将被删除的属性的名称 Y

Reflect.get()

Reflect.get()方法与从 对象 (target[propertyKey]) 中读取属性类似,但它是通过一个函数执行来操作的。
Reflect.get(target, propertyKey[, receiver])
参数 含义 必选
target 需要取值的目标对象 Y
propertyKey 需要获取的值的键值 Y
receiver 如果遇到 getter,此值将提供给目标调用 N

Reflect.getOwnPropertyDescriptor()

与 Object.getOwnPropertyDescriptor() 方法相似。如果在对象中存在,则返回给定的属性的属性描述符。否则返回 undefined。 
Reflect.getOwnPropertyDescriptor(target, propertyKey)
参数 含义 必选
target 需要寻找属性的目标对象 Y
propertyKey 获取自己的属性描述符的属性的名称 N
获得一堆详细信息

Reflect.getPrototypeOf()

静态方法 Reflect.getPrototypeOf() 与 Object.getPrototypeOf() 方法几乎是一样的。都是返回指定对象的原型(即内部的 [[Prototype]] 属性的值)。
Reflect.getPrototypeOf(target)
target:获取原型的目标对象。

Reflect.has()

Reflect.has 用于检查一个对象是否拥有某个属性, 相当于in 操作符
Reflect.has(target, propertyKey)
target获取原型的目标对象
propertyKey属性名,需要检查目标对象是否存在此属性

Reflect.isExtensible()

判断一个对象是否可扩展 (即是否能够添加新的属性)。与 Object.isExtensible() 方法相似,返回一个 Boolean 值
Reflect.isExtensible(target)

Reflect.ownKeys()

静态方法 Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组。如数组为空,则返回length
Reflect.ownKeys(target)
Reflect.ownKeys({z: 3, y: 2, x: 1}); // [ "z", "y", "x" ]
Reflect.ownKeys([]); // ["length"]

Reflect.preventExtensions()

该方法阻止新属性添加到对象 例如:防止将来对对象的扩展被添加到对象中)。该方法与 Object.preventExtensions()相似,返回一个Boolean值
Reflect.preventExtensions(target)

Reflect.set()

许你在对象上设置属性。它的作用是给属性赋值并且就像 property accessor 语法一样,但是它是以函数的方式。返回一个 Boolean 值表明是否成功设置属性
Reflect.set(target, propertyKey, value[, receiver])
参数 含义 必选
target 获取原型的目标对象 Y
propertyKey 设置的属性的名称 Y
value 设置的值 Y
receiver 如果遇到 setter,this 将提供给目标调用 N

Reflect.setPrototypeOf()

Reflect.setPrototypeOf 方法改变指定对象的原型 (即,内部的 [[Prototype]] 属性值)
Reflect.setPrototypeOf(target, prototype)

11.Proxy

Proxy提供了代理功能,可以把它理解为现实中的“中介”,它可以自定义一些常用行为如查找、赋值、枚举、函数调用等

Basic Syntax

let p = new Proxy(target, handler)
target 就是用来代理的“对象”,被代理之后它是不能直接被访问的
handler 就是实现代理的过程。
之后的操作便是对这个代理 p 进行
例子:
如下代码表示如果 o 对象有这个 key-value 则直接返回,如果没有一律返回 ' '
let o = {
  name: 'xiaoming',
  age: 20
}

let handler = {
  get(obj, key) {
    return Reflect.has(obj, key) ? obj[key] : ''
  }
}

let p = new Proxy(o, handler)

console.log(p.from)//输出为 ' '
如下代码表示对读写进行监控,规定读写,如果不符合规定便抛出相应的异常:
let validator = {
  set(target, key, value) {
    if (key === 'age') {
      if (typeof value !== 'number' || Number.isNaN(value)) {
        throw new TypeError('Age must be a number')
      }
      if (value <= 0) {
        throw new TypeError('Age must be a positive number')
      }
    }
    return true
  }
}
const person = { age: 27 }
const proxy = new Proxy(person, validator)
proxy.age = 'foo'
// <- TypeError: Age must be a number
proxy.age = NaN
// <- TypeError: Age must be a number
proxy.age = 0
// <- TypeError: Age must be a positive number
proxy.age = 28//正确

Revocable Proxies

可以使用 Proxy.revocable 创建临时的代理,即代理可以取消
let o = {
  name: 'xiaoming',
  price: 190
}

let d = Proxy.revocable(o, {
  get (target, key) {
    if (key === 'price') {
      return target[key] + 20
    } else {
      return target[key]
    }
  }
})

console.log(d.proxy.price, d)
setTimeout(function () {
  d.revoke()
  setTimeout(function () {
    console.log(d.proxy.price)
  }, 100)
}, 1000)
使用 Proxy.revocable 创造一个临时代理 
此时返回的 d 是:

其中revoke是代表销毁 使用d.revoke即可撤销代理

12.Generator

Generators 是可以用来控制迭代器的函数。它们可以暂停,然后在任何时候恢复

Basic Syntax

function * gen () {
  yield 1
  yield 2
  yield 3
}

let g = gen()
// "Generator { }"
在 Generators的定义中:
  1. 比普通函数多一个 *
  2. 函数内部用 yield 来控制程序的执行的“暂停”
  3. 函数的返回值通过调用 next 来“恢复”程序执行

yield 表达式

yield 关键字用来暂停和恢复一个生成器函数,一旦遇到 yield 表达式,生成器的代码将被暂停运行,直到生成器的 next() 方法被调用。每次调用生成器的next()方法时,生成器都会恢复执行
它具有以下几个特点:
  1. yield 表达式的返回值是 undefined,但是如果将参数传递给生成器的next()方法,则该值将成为生成器当前yield操作返回的值。
    function * gen () {
      let val
      val = yield 1
      console.log(`1:${val}`) // 1:undefined
      val = yield 2
      console.log(`2:${val}`) // 2:undefined
    }
    
    var g = gen()
    
    console.log(g.next()) // {value: 1, done: false}
    console.log(g.next()) // {value: 2, done: false}
    console.log(g.next()) // {value: undefined, done: true}
  2. yeild * 是委托给另一个遍历器对象或者可遍历对象
  3. Generator 对象的 next 方法,遇到 yield 就暂停,并返回一个对象,这个对象包括两个属性:value 和 done。其中:value属性是对yield表达式求值的结果,而donefalse,表示生成器函数尚未完全完成。

方法:

- next([value]):
除以上提及的知识点外, next 还可以接受参数,作为 yield 的返回值,从 Generator 外部给内部传递数据。
但要注意赋值的顺序,如下代码:
function * gen () {
  var val = 100
  while (true) {
    console.log(`before ${val}`)
    val = yield val
    console.log(`return ${val}`)
  }
}

var g = gen()
console.log(g.next(20).value)
// before 100
// 100
console.log(g.next(30).value)
// return 30
// before 30
// 30
console.log(g.next(40).value)
// return 40
// before 40
// 40
*******重点!!!!!分析上述程序*********
  1. g.next(20) 这句代码会执行 gen 内部的代码,遇到第一个 yield 暂停。所以 console.log(`before ${val}`) 执行输出了 before 100,此时的 val 是 100所以执行到 yield val 返回了 100但是!!!yield val 并没有赋值给 val!!!停在了给val赋值之前!!!

  2. g.next(30) 这句代码会继续执行 gen 内部的代码,也就是 val = yield val 这句,因为 next 传入了 30,所以 yield val 这个返回值就是 30,因此 val 被赋值 30,执行到console.log(`return ${val}`)输出了 30,此时没有遇到 yield 代码继续执行,也就是 while 的判断,继续执行console.log(`before ${val}`) 输出了 before 30,再执行遇到了yield val程序暂停。

  3. g.next(40) 重复步骤 2。
- return():
return 方法可以让 Generator 遍历终止,有点类似 for 循环的 break。也可以传入参数,作为返回的value值
function * gen () {
  yield 1
  yield 2
  yield 3
}

var g = gen()

console.log(g.next()) // {value: 1, done: false}
console.log(g.return(100)) // {value: 100, done: true}
console.log(g.next()) // {value: undefined, done: true}
- throw()
可以通过 throw 方法在 Generator 外部控制内部执行的“终断”。
function * gen () {
  while (true) {
    try {
      yield 42
    } catch (e) {
      console.log(e.message)
    }
  }
}

let g = gen()
console.log(g.next()) // { value: 42, done: false }
console.log(g.next()) // { value: 42, done: false }
console.log(g.next()) // { value: 42, done: false }
// 中断操作
g.throw(new Error('break'))

console.log(g.next()) // {value: undefined, done: true}

Scene Practice

结合抽奖的应用场景进行理解:
在一次抽奖中 抽一等奖1名,二等奖3名,三等奖5名
function * draw (first = 1, second = 3, third = 5) {
    let firstPrize = ['1A', '1B', '1C', '1D', '1E', '1F', '1G', '1H', '1I', '1J', '1K', '1L', '1M', '1N']// 满足1000积分的名单
    let secondPrize = ['2O', '2P', '2Q', '2R', '2S', '2T', '2U', '2V', '2W', '2X', '2Y', '2Z'] // 满足500积分的名单
    let thirdPrize = ['31', '32', '33', '34', '35', '36', '37', '38', '39'] // 满足50积分的名单
    let count = 0
    let random
    while (1) {
      if (count < first) {
        random = Math.floor(Math.random() * firstPrize.length)
        yield firstPrize[random]
        count++
        firstPrize.splice(random, 1)
      } else if (count < first + second) {
        random = Math.floor(Math.random() * secondPrize.length)
        yield secondPrize[random]
        count++
        secondPrize.splice(random, 1)
      } else if (count < first + second + third) {
        random = Math.floor(Math.random() * thirdPrize.length)
        yield thirdPrize[random]
        count++
        thirdPrize.splice(random, 1)
      } else {
        return false
      }
    }
  }
  let t = draw()
console.log(t.next().value)
console.log(t.next().value)
console.log(t.next().value)
console.log(t.next().value)
console.log(t.next().value)
console.log(t.next().value)
console.log(t.next().value)
console.log(t.next().value)
console.log(t.next().value)

13.Iterator

用来实现自定义遍历的接口,实现接口代码如下:
authors[Symbol.iterator] = function () {
  let allAuthors = this.allAuthors
  let keys = Reflect.ownKeys(allAuthors)
  let values = []
  return {
    next () {
      if (!values.length) {
        if (keys.length) {
          values = allAuthors[keys[0]]
          keys.shift()
        }
      }
      return {
        done: !values.length,
        value: values.shift()
      }
    }
  }
}
对authors这个数据结构实现接口后,就可以采用for……of来进行遍历

对于定义Iterator 的代码,有以下几个理解重点:

迭代器协议 next

首先,它是一个对象
其次,这个对象包含一个无参函数 next
最后,next 返回一个对象,对象包含 done 和 value 属性。其中 done 表示遍历是否结束(有些类似于Generator)

可迭代协议 [Symbol.iterator]

如果让一个对象是可遍历的,就要遵守可迭代协议,该协议要求对象要部署一个以 Symbol.iterator 为 key 的键值对,而 value 就是一个无参函数,这个函数返回的对象要遵守迭代器协议

而Generator就是一个天然满足迭代协议的接口,并且不需要显示的写迭代器协议了(next方法和包含 done、value 属性的返回对象)
因此,同样的问题也可以使用generator
authors[Symbol.iterator] = function * () {
  let allAuthors = this.allAuthors
  let keys = Reflect.ownKeys(allAuthors)
  let values = []
  while (1) {
    if (!values.length) {
      if (keys.length) {
        values = allAuthors[keys[0]]
        keys.shift()
        yield values.shift()
      } else {
        return false
      }
    } else {
      yield values.shift()
    }
  }
}

14.module

可以使用 import、export 来实现原生 JavaScript 的导入、导出了。

export:

可以导出变量常量、函数、Object,Class
修改导出名称:
const name = 'hello'
let addr = 'BeiJing City'
var list = [1, 2 , 3]
export {
  name as cname,
  addr as caddr,
  list
}
设置默认导出:
const name = 'hello'
let addr = 'BeiJing City'
var list = [1, 2 , 3]
export {
  name as cname,
  addr as caddr
}
export default list

import

直接导入:
import list, {cname, caddr} from A
修改导入名称:
import list, {cname as name, caddr} from A
批量导入:
import list, * as mod from A
console.log(list);
console.log(mod.cname);
console.log(mod.caddr);