ES6

ES6主要相关问题重学

1、let、const 声明

let声明
1、ES6新增了let来声明变量,用法类似于var,但声明的变量,只在let命令所在的代码块内有效。也不同于var声明的变量“提升”现象(变量在声明前使用,值为undefined)。let所声明的变量一定要在声明后使用,不然会报错。

{
  let a = 10;
  var b = 1;    
}   
  alert(b);  // 1
  alert(a);  // a is not defind
console.log(foo); // 输出undefined
var foo = 2;

console.log(bar); // 谷歌下undefined
let bar = 2;

2、块级作用域内存在let所声明的变量会绑定在这个块级作用域内,外部影响不了。在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

var tmp = 123;
  if (true) {   
    tmp = 'abc';
    let tmp;
    alert(tmp);  //undefind  

    let str = 'ABC';
    alert(str); //ABC
    }

3、let不允许在相同作用域内,重复声明同一个变量。

function test(){
  var a = 10;
  var a = 1;  
}
function test(){
   let a = 10;
   var a = 1;  //Duplicate declaration "a"
}
function test(){
  let a = 10;
  let a = 1;  //Duplicate declaration "a"
}

4、let实际上为 JavaScript 新增了块级作用域,内层作用域可以定义外层作用域的同名变量,外层作用域无法读取内存作用域的变量

(function() {
  var n = 5;
  if (true) {
    var n = 10;
    var m =100;
  }
 alert(n);  // 10 
 alert(m);  // 100
})();

(function() {
  let n = 5;
  if (true) {
    let n = 10; //内层作用域可以定义外层作用域的同名变量
    let m =100;
  }
 alert(n);      // 5 
 alert(m);      // m is not defined
})();

由上面可以看出var和let的不同,let外层代码块不受内层代码块的影响。内层作用域可以定义外层作用域的同名变量,外层作用域无法读取内存作用域的变量,所以m才会not defined。ES6还允许块级作用域的任意嵌套,例如:

{ { { { {let insane = 'Hello World'}}}}};

5、ES6明确允许在块级作用域之中声明函数,ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

(function(){
    if(true){
        function func(){
            alert("我被执行了");
            }
        func();   // 我被执行了      
    }
    func();        // ES6环境下func is not defined 但是ES5环境下弹出"我被执行了"
})();

上面代码在 ES5 中运行,会得到“我被执行了”,因为在if内声明的函数func会被提升到函数头部,但是在ES6环境中,块级作用域内声明的函数类似于let,对作用域之外没有影响。

const声明
const声明一个只读的常量。一旦声明,要立即初始化,不能留到以后赋值。初始化后的常量的值不能改变。

const name = 'perter';
console.log(name);  //perter

name = 'Tom';       //"name" is read-only

只声明不赋值:const name ; //Unexpected token

const和let相同之处:

只在声明所在的块级作用域内有效;
声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用;
声明的常量一样不可重复声明

const的本质不是保证变量的值不变,而是要保证指向的那个内存地址是不得改动的,这就是常量。对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,这个指针式固定不可变的,但数据结构可以

如我用一个常量声明一个对象,常量保存的是指向这个对象的指针,对象属性的重写或添加是被允许的,但不能再把这个常量指向另一个对象:

const obj = {
    name: '张三',
    age:20  
}
console.log(obj.name);   //张三

obj.name = '李四';
console.log(obj.name);   //李四
    
obj = { name: '张三'};    //"obj" is read-only

如果要让属性也不能再重写或添加可以用Object.freeze方法,将常量obj指向一个冻结的对象:

const obj = Object.freeze({});
obj.name = '张三';     //Cannot add property name, object is not extensible

2、解构赋值

概念
解构赋值主要分为对象的解构和数组的解构,在没有解构赋值的时候,我们赋值是这样的

let arr = [0,1,2]
let a = arr[0]
let b = arr[1]
let c = arr[2]

这样写很繁琐,那么我们有没办法既声明,又赋值,更优雅的写法呢?肯定是有的,那就是解构赋值,解构赋值,简单理解就是等号的左边和右边相等。

一、数组的解构赋值

let arr = [0,1,2]
let [a,b,c] = arr
console.log(a) // 0
console.log(b) // 1
console.log(c) // 2

但是很多时候,数据并非一一对应的,并且我们希望得到一个默认值

let arr = [,1,2]
let [a='我是默认值',b,c] = arr
console.log(a) // '我是默认值'
console.log(b) // 1
console.log(c) // 2

从这个例子可以看出,在解构赋值的过程中,a=undefined时,会使用默认值 那么当a=null时呢?当a=null时,那么a就不会使用默认值,而是使用null

// 数组的拼接
let a = [0,1,2]
let b = [3,4,5]
let c = a.concat(b)
console.log(c) // [0,1,2,3,4,5]

let d = [...a,...b]
console.log(d) // [0,1,2,3,4,5]
// 数组的克隆
// 假如我们简单地把一个数组赋值给另外一个变量
let a = [0,1,2,3]
let b = a
b.push(4)
console.log(a) // [0,1,2,3,4]
console.log(b) // [0,1,2,3,4]

因为这只是简单的把引用地址赋值给b,而不是重新开辟一个内存地址,所以
a和b共享了同一个内存地址,该内存地址的更改,会影响到所有引用该地址的变量
那么用下面的方法,把数组进行克隆一份,互不影响

let a = [0,1,2,3]
let b = [...a]
b.push(4)
console.log(a) // [0,1,2,3]
console.log(b) // [0,1,2,3,4]

二、对象的解构赋值

对象的解构赋值和数组的解构赋值其实类似,但是数组的数组成员是有序的
而对象的属性则是无序的,所以对象的解构赋值简单理解是等号的左边和右边的结构相同

let {name,age} = {name:"swr",age:28}
console.log(name) // 'swr'
console.log(age) // 28

对象的解构赋值是根据key值进行匹配

// 这里可以看出,左侧的name和右侧的name,是互相匹配的key值
// 而左侧的name匹配完成后,再赋值给真正需要赋值的Name
let { name:Name,age } = { name:'swr',age:28 }
console.log(Name) // 'swr'
console.log(age) // 28

那么当变量已经被声明了呢?

let name,age
// 需要用圆括号,包裹起来
({name,age} = {name:"swr",age:28})
console.log(name) // 'swr'
console.log(age) // 28

变量能否也设置默认值?

let {name="swr",age} = {age:28}
console.log(name) // 'swr'
console.log(age) // 28
// 这里规则和数组的解构赋值一样,当name = undefined时,则会使用默认值
let [a] = [{name:"swr",age:28}]
console.log(a) // {name:"swr",age:28}

let { length } = "hello swr"
console.log(length) // 9
function ajax({method,url,type='params'}){
    console.log(method) // 'get'
    console.log(url) // '/'
    console.log(type) // 'params'
}

ajax({method:"get",url:"/"})

三、扩展运算符

我们先看下代码,在以往,我们给函数传不确定参数数量时,是通过arguments来获取的

function sum() {
  console.log(arguments) // { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6 }
                         // 我们可以看出,arguments不是一个数组,而是一个伪数组
  let total = 0
  let { length } = arguments
  for(let i = 0;i < length;i++){
    total += arguments[i]
  }
  return total
}

console.log(sum(1,2,3,4,5,6)) // 21

接下来我们用扩展运算符看看

function sum(...args){ // 使用...扩展运算符
    console.log(args) // [ 1, 2, 3, 4, 5, 6 ] args是一个数组
    return eval(args.join('+'))
}

console.log(sum(1,2,3,4,5,6)) // 21

得到的args是一个数组,直接对数组进行操作会比对伪数组进行操作更加方便,还有一些注意点需要注意

// 正确的写法 扩展运算符只能放在最后一个参数
function sum(a,b,...args){
    console.log(a) // 1
    console.log(b) // 2
    console.log(args) // [ 3, 4, 5, 6 ]
}

sum(1,2,3,4,5,6)

// 错误的写法 扩展运算符只能放在最后一个参数
function sum(...args,a,b){
    // 报错
}

sum(1,2,3,4,5,6)

我们可以对比下扩展运算符的方便之处

// 以往我们是这样拼接数组的
let arr1 = [1,2,3]
let arr2 = [4,5,6]
let arr3 = arr1.concat(arr2)
console.log(arr3) // [ 1, 2, 3, 4, 5, 6 ]

// 现在我们用扩展运算符看看
let arr1 = [1,2,3]
let arr2 = [4,5,6]
let arr3 = [...arr1,...arr2]
console.log(arr3) // [ 1, 2, 3, 4, 5, 6 ]
// 以往我们这样来取数组中最大的值
function max(...args){
    return Math.max.apply(null,args)
}
console.log(max(1,2,3,4,5,6)) // 6

// 现在我们用扩展运算符看看
function max(...args){
    return Math.max(...args) // 把args [1,2,3,4,5,6]展开为1,2,3,4,5,6
}
console.log(max(1,2,3,4,5,6)) // 6
// 扩展运算符可以把argument转为数组
function max(){
    console.log(arguments) // { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6 }
    let arr = [...arguments]
    console.log(arr) // [1,2,3,4,5,6]
}

max(1,2,3,4,5,6)

// 但是扩展运算符不能把伪数组转为数组(除了有迭代器iterator的伪数组,如arguments)
let likeArr = { "0":1,"1":2,"length":2 }
let arr = [...likeArr] // 报错 TypeError: likeArr is not iterable

// 但是可以用Array.from把伪数组转为数组
let likeArr = { "0":1,"1":2,"length":2 }
let arr = Array.from(likeArr)
console.log(arr) // [1,2]

对象也可以使用扩展运算符

// 以往我们这样合并对象
let name = { name:"邵威儒" }
let age = { age:28 }
let person = {}
Object.assign(person,name,age)
console.log(person) // { name: '邵威儒', age: 28 }

// 使用扩展运算符
let name = { name:"邵威儒" }
let age = { age:28 }
let person = {...name,...age}
console.log(person) // { name: '邵威儒', age: 28 }

需要注意的是,通过扩展运算符和Object.assign对对象进行合并的行为,是属于浅拷贝,那么我们在开发当中,经常需要对对象进行深拷贝,接下来我们看看如何进行深拷贝。

方法一:利用JSON.stringify和JSON.parse

let swr = {
    name:"邵威儒",
    age:28,
    pets:['小黄']
}

let swrcopy = JSON.parse(JSON.stringify(swr))
console.log(swrcopy) // { name: '邵威儒', age: 28, pets: [ '小黄' ] }
// 此时我们新增swr的属性
swr.pets.push('旺财')
console.log(swr) // { name: '邵威儒', age: 28, pets: [ '小黄', '旺财' ] }
// 但是swrcopy却不会受swr影响
console.log(swrcopy) // { name: '邵威儒', age: 28, pets: [ '小黄' ] }

这种方式进行深拷贝,只针对json数据这样的键值对有效
对于函数等等反而无效,不好用,接着继续看方法二、三。

方法二

function deepCopy(fromObj,toObj) { // 深拷贝函数
  // 容错
  if(fromObj === null) return null // 当fromObj为null
  if(fromObj instanceof RegExp) return new RegExp(fromObj) // 当fromObj为正则
  if(fromObj instanceof Date) return new Date(fromObj) // 当fromObj为Date

  toObj = toObj || {}
  
  for(let key in fromObj){ // 遍历
    if(typeof fromObj[key] !== 'object'){ // 是否为对象
      toObj[key] = fromObj[key] // 如果为普通值,则直接赋值
    }else{
      if(fromObj[key] === null){
        toObj[key] = null
      }else{
        toObj[key] = new fromObj[key].constructor // 如果为object,则new这个object指向的构造函数
        deepCopy(fromObj[key],toObj[key]) // 递归          
      }
    }
  }
  return toObj
}

let dog = {
  name:"小白",
  sex:"公",
  firends:[
    {
      name:"小黄",
      sex:"母"
    }
  ]
}

let dogcopy = deepCopy(dog)
// 此时我们把dog的属性进行增加
dog.firends.push({name:"小红",sex:"母"})
console.log(dog) // { name: '小白',
                      sex: '公',
                      firends: [ { name: '小黄', sex: '母' }, { name: '小红', sex: '母' } ] }
// 当我们打印dogcopy,会发现dogcopy不会受dog的影响
console.log(dogcopy) // { name: '小白',
                          sex: '公',
                          firends: [ { name: '小黄', sex: '母' } ] }

方法三

let dog = {
  name:"小白",
  sex:"公",
  firends:[
    {
      name:"小黄",
      sex:"母"
    }
  ]
}

function deepCopy(obj) {
  if(obj === null) return null
  if(typeof obj !== 'object') return obj
  if(obj instanceof RegExp) return new RegExp(obj)
  if(obj instanceof Date) return new Date(obj)
  let newObj = new obj.constructor
  for(let key in obj){
    newObj[key] = deepCopy(obj[key])
  }
  return newObj
}

let dogcopy = deepCopy(dog)
dog.firends.push({name:"小红",sex:"母"})
console.log(dogcopy)

3、声明类与继承:class、extend

简介
在编程的过程中,我们主要运用的有函数编程和面向对象编程,而在一些大型的项目中,大部分使用的都是面向对象编程,而这里也就有了类的继承和声明这些个问题出现。

类的声明

ES5中类的声明
在ES5中类的声明主要是通过构造函数来实现的,在声明过程中,主要是通过使用 this,将

let family = function(name){
	this.name = name;
	this.parent = function(){
        console.log('my parent');
    }
}
let son = new family('son')
let mother = new family('mother')

console.log(son); //family{name:"son",parent:f}
console.log(mother); //family{name:"mother",parent:f}

当然在在这个类里,也不是所有的属性跟方法都是挂载在实例化对象里面的,还有一些对于实例对象来说公有的方法或者属性,它是挂载在原型对象上的。

let family = function(name){
    this.name = name;
    this.parent = function(){
        console.log('my parent');
    }
}
// 实例对象通过原型链拿到 work 这个方法
family.prototype.work = function(){
    console.log('my work');
}

这里可以看出ES5里面想要声明一个类是比较麻烦的,那么在ES6里面,又是怎么做声明的呢

ES6中类的声明
在ES6里面,新增了一个叫做 class 的类的声明的语法糖,它的使用方法是这样的。

class family{
	constructor(name){
		this.name = name
	}
    work(){
        console.log('my work')   
    }
    
let son = new family('son')
console.log(son)

在ES6里面,新增了一个叫做 class 的类的声明的语法糖,它的使用方法是这样的。

class family{
	constructor(name){
		this.name = name
	}
    work(){
        console.log('my work')   
    }
    
let son = new family('son')
console.log(son)

这里,重点说明一个点就是,在使用 class 之后,对于想要将方法挂载在原型对象上的时候,已经不需要跟ES5一样那么麻烦了,只需要将方法直接写在constructor的外面就可以了。

在这里插入图片描述说完了ES6中类的声明,我们再来聊聊ES6中新增的另一个东西,它可以让类的属性变为只可以读取。

ES6中的get和set

当你在声明一个类的时候,有些属性,是不希望别人可以随意的对它进行更改的,也就是把它定义为 私有属性,在ES5的时候基本不可能做到,但是在ES6的时候是可以办到的,而这个就是通过 get 来实现。

let _age = 18
class family {
    constructor(name){
        this.name = name
    }
    work(){
        console.log('my work')   
    }
    get age() {
    	return _age
    }
}

let son = new family('son')
console.log(son.age); //18

在这里定义一个私有属性的时候,用户访问这个属性的入口,跟这个属性的值存储的位置,是不一样的。

而且这时候的age是不会被修改的,例如

son.age = 20
console.log(son.age); //18

但是,这里的age是不是完全没办法改变呢,其实也不是,如果想要改变它的值,就可以通过ES6中的另一个新东西—— set

它的使用方法跟 get 差不多。

let _age = 18
class family {
    constructor(name){
        this.name = name
    }
    work(){
        console.log('my work')   
    }
    get age() {
        return _age
    }
    set age(value){
        _age = value
    }
}
let son = new family('son')
son.age = 20
console.log(son.age); //20

其实本质上来说,它并不是直接改变age的值,而是通过改变_age的值,而间接的改变原age的值,有了这个方法之后,就可以有了新的玩法了,例如

    set age(value){
        if(value>18 && value<26){
            _age = value
        }
    }

类的静态方法的挂载
一般来说,在构造函数里面的方法,是用来操作实例对象的属性的,或者说它与实例对象的属性有关系,如果在类中一个方法跟实例对象的属性完全无关的时候,就可以将它变为静态方法。

这里有一个需要注意的地方:在实例对象上,是没办法找到类的静态方法的

ES5挂载静态方法
ES5中声明和使用类的静态方法是这样的

let family = function(name){
    this.name = name;
    this.parent = function(){
        console.log('my parent');
        family.love()
    }
}
family.love = function(){
    console.log('I love you ');
}
let son = new family('son')
son.parent();
//my parent I love you

ES6挂载静态方法(static)
在ES6中引入了一个新的东西,就是 static ,它就是用来在ES6中给类挂载静态方法的。

class family {
    constructor(name){
        this.name = name
    }
    parent(){
        console.log('my parent')
        family.love()   
    }
    static love(){
        console.log('I love you');
    }
}
let son = new family('son')
son.parent(); 
//my parent I love you

使用static的时候,同样的只需要把方法写在class里面,然后在方法的前面加一个 static 就可以了,在代码的阅读性上有了很大的提高。

class father{
    constructor(value){
        this.name = value
    }
    parent(){
        console.log('my parent');
    }
  static love(){
    console.log('love')
  }
	
}
father.prototype.parent()	//my parent
father.love()	//love

类的继承

在面向对象编程的过程中,最强大一个东西,就是类的继承,但是它又不太好理解,因为这里涉及到了原型链的问题,但是在ES6里,类的继承,同样也变的不再复杂。

ES5中类的继承
ES5中类的继承,方法有特别多,为了实现完整的继承,本质上就是使用原型链和构造函数结合起来的方式来继承。

let father = function(value){
    this.name = value;
    this.parent = function(){
        console.log('my parent');
    }
}

let children = function(value){
    father.call(this,value) //构造函数继承
}
children.prototype = father.prototype //原型链继承

let son = new children('son')
console.log(son); //children {name:"son",parent:f}

这里可以看出来ES5的继承是比较复杂的,需要涉及的面比较多,而且这种继承也不是最完美的写法。

在ES6中,有引入了两个新的东西,用来完善类的继承这个事情,它们就是 extends 和 super。

class father{
    constructor(value){
        this.name = value
    }
    parent(){
        console.log('my parent');
    }
}

class children extends father{
    constructor(value){
        super(value) //构造函数继承
        this.age = 18 
    }
}

这里也有一个需要注意的地方,就是super()必须写constructor里面的最上面,这里涉及到的一个问题是隐式原型和显式原型的问题,在这篇博文就不做过多的解释了。

隐式原型和显示原型的联系


4、Promise 的使用与实现(原理)

Promise标准

  1. Promise 规范有很多,如Promise/APromise/BPromise/D以及 Promise/A 的升级版 ,Promise/A+。ES6 中采用了Promise/A+ 规范。
    英文版规范:Promises/A+规范
    中文版规范:Promises/A+规范(中文)

  2. Promise标准解读
    (1) 一个promise的当前状态只能是pendingfulfilledrejected三种之一。状态改变只能是pendingfulfilled或者pendingrejected。状态改变不可逆。
    (2) promisethen方法接收两个可选参数,表示该promise状态改变时的回调(promise.then(onFulfilled, onRejected))。then方法返回一个promisethen 方法可以被同一个 promise 调用多次。

实现Promise

Promise标准仅描述了then方法的行为,未对catch、all、race方法进行描述,也未规范如何创建一个Promise对象。ES6中Promise提供了以下 API
(1) 构造函数

function Promise(resolver) {}

(2) 原型方法

Promise.prototype.then = function() {}
Promise.prototype.catch = function() {}

(3) 静态方法

Promise.resolve = function() {}
Promise.reject = function() {}
Promise.all = function() {}
Promise.race = function() {}

构造函数

  1. Promise/A+标准并没有指定如何构造一个Promise对象,我们以ES6原生Promise模块里通过构造函数创建Promise对象的方式实现Promise构造函数。ES6中通过构造函数创建Promise对象的简单用法如下:
const promise = new Promise((resolve) => {
    setTimeout(()=> {
        resolve(1);
    }, 2000);
});
promise.then(a=> alert(a));
promise.then(a => alert(a+1));

构造函数用法总结:
(1) 构造函数接收一个executor立即执行函数
(2) executor立即执行函数接收一个resolve函数
(3) promise对象的then方法绑定状态变为fulfilled时的回调
(4) resolve函数被调用时会触发then方法中的回调

构造函数的简单实现

function Promise(executor) {
    var self = this;
    self.status = 'pending'; //promise当前的状态
    self.data = undefined; //promise的值
    self.onResolvedCallback = [];
    //promise状态变为resolve时的回调函数集,可能有多个
   function resolve(value) {
       if(self.status === 'pending') {
           self.status = 'resolved';
           self.data = value;
           for(var i = 0; i < self.onResolvedCallback.length; i++) {
               self.onResolvedCallback[i](value);
           }
       }
   }
   executor(resolve);
};
Promise.prototype.then = function (resolve) {
    this.onResolvedCallback.push(resolve);
};

executor自执行函数接收的第二个参数为reject函数且reject函数在promise对象状态变为rejected时或executor抛出异常时触发。

function Promise(executor) {
    var self = this;
    self.status = 'pending'; //promise当前的状态
    self.data = undefined; //promise的值
    self.onResolvedCallback = [];
    //promise状态变为resolve时的回调函数集,可能有多个
    self.onRejectedCallback = [];
    //promise状态变为reject时的回调函数集,可能有多个
   function resolve(value) {
       if(self.status === 'pending') {
           self.status = 'resolved';
           self.data = value;
           for(var i = 0; i < self.onResolvedCallback.length; i++) {
               self.onResolvedCallback[i](value);
           }
       }
   }

   function reject(reason) {
        if(self.status === 'pending') {
            self.status = 'rejected';
            self.data = reason;
            for(var i = 0; i < self.onRejectedCallback.length; i++) {
                self.onRejectedCallback[i](reason);
            }
        }
   }

   try {
       executor(resolve, reject);
   } catch (e){
       reject(e);
   }
};
Promise.prototype.then = function (onResolve, onReject) {
    this.onResolvedCallback.push(onResolve);
    this.onRejectedCallback.push(onReject);
};

总结:①executor函数作为实参在创建Promise对象时传入Promise构造函数。②resolve和reject函数作为实参传入executor函数。③value作为实参传入resolve和reject函数。

如果executor自执行函数中的resolve函数立即触发时,发现Promise失效,例如:

const promise = new Promise((resolve) => {
    resolve(1);
});
promise.then((a) => alert(a));

解决办法:需要将promise的resolve和reject异步执行。

function resolve(value) {
    setTimeout(function () {
        if(self.status === 'pending') {
            self.status = 'resolved';
            self.data = value;
            for(var i = 0; i < self.onResolvedCallback.length; i++) {
                self.onResolvedCallback[i](value);
            }
        }
    })
}

function reject(reason) {
    setTimeout(function () {
        if(self.status === 'pending') {
            self.status = 'rejected';
            self.data = reason;
            for(var i = 0; i < self.onRejectedCallback.length; i++) {
                self.onRejectedCallback[i](reason);
            }
        }
    })
}

then方法

  1. 在构造函数中的示例中,then方法并没有返回一个promise对象,而 Promise/A+ 规范中规定then方法用来注册promise对象状态改变时的回调,且返回一个新的promise对象。
    (1)then方法返回一个新的promise对象。
    (2)executor自执行函数中的resolve参数调用时执行then方法的第一个回调函数onResolved。
    (3)executor自执行函数中的reject参数调用时执行then方法的第二个回调函数onRejected。
Promise.prototype.then = function (onResolved, onRejected) {
    var self = this;
    var promise2;
    onResolved = typeof onResolved === 'function' 
                ? onResolved 
                : function (value) {return value};
    onRejected = typeof onRejected === 'function' 
                ? onRejected 
                : function (reason) {throw reason};
    //promise对象当前状态为resolved
    if(self.status === 'resolved') {
        return promise2 = new Promise(function (resolve, reject) {
            try {
                //调用onResolve回调函数
                var x = onResolved(self.data);
                //如果onResolve回调函数返回值为一个promise对象
                if(x instanceof  Promise) {
                    //将它的结果作为promise2的结果
                    x.then(resolve, reject);
                } else {
                    resolve(x);//执行promise2的onResolve回调
           }        
            } catch (e) {
                reject(e); //执行promise2的onReject回调
            }
        })
    }
    //promise对象当前状态为rejected
    if(self.status === 'rejected') {
        return promise2 = new Promise(function (resolve, reject) {
            try {
                var x = onRejected(self.data);
                if (x instanceof Promise) {
                    x.then(resolve, reject)
                } else {
                    resolve(x)
                }
            } catch (e) {
                reject(e)
            }
        })
    }
    //promise对象当前状态为pending
    //此时并不能确定调用onResolved还是onRejected,需要等当前Promise状态确定。
    //所以需要将callBack放入promise1的回调数组中
    if(self.status === 'pending') {
        return promise2 = new Promise(function (resolve, reject) {
            self.onResolvedCallback.push(function (value) {
                try {
                    var x = onResolved(self.data);
                    if (x instanceof Promise) {
                        x.then(resolve, reject);
                    } else {
                        resolve(x);
          }
                } catch (e) {
                    reject(e);
                }
            })
            self.onRejectedCallback.push(function(reason) {
                try {
                    var x = onRejected(self.data);
                    if (x instanceof Promise) {
                        x.then(resolve, reject)
                    } else {
                        resolve(x);
                    }
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
};

参照Promise/A+标准对promise进行改写

function resolvePromise(promise2, x, resolve, reject) {
    var then
    var thenCalledOrThrow = false

    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise!'))
    }
    if (x instanceof Promise) {
        if (x.status === 'pending') { //because x could resolved by a Promise Object
            x.then(function(v) {
                resolvePromise(promise2, v, resolve, reject)
            }, reject)
        } else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
            x.then(resolve, reject)
        }
        return
    }

    if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
        try {
            then = x.then //because x.then could be a getter
            if (typeof then === 'function') {
                then.call(x, function rs(y) {
                    if (thenCalledOrThrow) return
                    thenCalledOrThrow = true
                    return resolvePromise(promise2, y, resolve, reject)
                }, function rj(r) {
                    if (thenCalledOrThrow) return
                    thenCalledOrThrow = true
                    return reject(r)
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            if (thenCalledOrThrow) return
            thenCalledOrThrow = true
            return reject(e)
        }
    } else {
        resolve(x)
    }
}

Promise.prototype.then = function(onResolved, onRejected) {
    var self = this
    var promise2
    onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
        return v
    }
    onRejected = typeof onRejected === 'function' ? onRejected : function(r) {
        throw r
    }

    if (self.status === 'resolved') {
        return promise2 = new Promise(function(resolve, reject) {
            setTimeout(function() { // 异步执行onResolved
                try {
                    var x = onResolved(self.data)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (reason) {
                    reject(reason)
                }
            })
        })
    }

    if (self.status === 'rejected') {
        return promise2 = new Promise(function(resolve, reject) {
            setTimeout(function() { // 异步执行onRejected
                try {
                    var x = onRejected(self.data)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (reason) {
                    reject(reason)
                }
            })
        })
    }

    if (self.status === 'pending') {
        // 这里之所以没有异步执行,是因为这些函数必然会被resolve或reject调用,而resolve或reject函数里的内容已是异步执行,构造函数里的定义
        return promise2 = new Promise(function(resolve, reject) {
            self.onResolvedCallback.push(function(value) {
                try {
                    var x = onResolved(value)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (r) {
                    reject(r)
                }
            })

            self.onRejectedCallback.push(function(reason) {
                try {
                    var x = onRejected(reason)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (r) {
                    reject(r)
                }
            })
        })
    }
}

完整代码

var Promise = (function() {
    function Promise(resolver) {
        if (typeof resolver !== 'function') { //resolver必须是函数
            throw new TypeError('Promise resolver ' + resolver + ' is not a function')
        }
        if (!(this instanceof Promise)) return new Promise(resolver)

        var self = this //保存this
        self.callbacks = [] //保存onResolve和onReject函数集合
        self.status = 'pending' //当前状态

        function resolve(value) {
            setTimeout(function() { //异步调用
                if (self.status !== 'pending') {
                    return
                }
                self.status = 'resolved' //修改状态
                self.data = value

                for (var i = 0; i < self.callbacks.length; i++) {
                    self.callbacks[i].onResolved(value)
                }
            })
        }

        function reject(reason) {
            setTimeout(function(){ //异步调用
                if (self.status !== 'pending') {
                    return
                }
                self.status = 'rejected' //修改状态
                self.data = reason

                for (var i = 0; i < self.callbacks.length; i++) {
                    self.callbacks[i].onRejected(reason)
                }
            })
        }

        try{
            resolver(resolve, reject) //执行resolver函数
        } catch(e) {
            reject(e)
        }
    }

    function resolvePromise(promise, x, resolve, reject) {
        var then
        var thenCalledOrThrow = false

        if (promise === x) {
            return reject(new TypeError('Chaining cycle detected for promise!'))
        }

        if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
            try {
                then = x.then
                if (typeof then === 'function') {
                    then.call(x, function rs(y) {
                        if (thenCalledOrThrow) return
                        thenCalledOrThrow = true
                        return resolvePromise(promise, y, resolve, reject)
                    }, function rj(r) {
                        if (thenCalledOrThrow) return
                        thenCalledOrThrow = true
                        return reject(r)
                    })
                } else {
                    return resolve(x)
                }
            } catch(e) {
                if (thenCalledOrThrow) return
                thenCalledOrThrow = true
                return reject(e)
            }
        } else {
            return resolve(x)
        }
    }

    Promise.prototype.then = function(onResolved, onRejected) {
        //健壮性处理,处理点击穿透
        onResolved = typeof onResolved === 'function' ? onResolved : function(v){return v}
        onRejected = typeof onRejected === 'function' ? onRejected : function(r){throw r}
        var self = this
        var promise2

        //promise状态为resolved
        if (self.status === 'resolved') {
            return promise2 = new Promise(function(resolve, reject) {
                setTimeout(function() {
                    try {
                        //调用then方法的onResolved回调
                        var x = onResolved(self.data)
                        //根据x的值修改promise2的状态
                        resolvePromise(promise2, x, resolve, reject)
                    } catch(e) {
                        //promise2状态变为rejected
                        return reject(e)
                    }
                })
            })
        }

        //promise状态为rejected
        if (self.status === 'rejected') {
            return promise2 = new Promise(function(resolve, reject) {
                setTimeout(function() {
                    try {
                        //调用then方法的onReject回调
                        var x = onRejected(self.data)
                        //根据x的值修改promise2的状态
                        resolvePromise(promise2, x, resolve, reject)
                    } catch(e) {
                        //promise2状态变为rejected
                        return reject(e)
                    }
                })
            })
        }

        //promise状态为pending
        //需要等待promise的状态改变
        if (self.status === 'pending') {
            return promise2 = new Promise(function(resolve, reject) {
                self.callbacks.push({
                    onResolved: function(value) {
                        try {
                            //调用then方法的onResolved回调
                            var x = onResolved(value)
                            //根据x的值修改promise2的状态
                            resolvePromise(promise2, x, resolve, reject)
                        } catch(e) {
                            //promise2状态变为rejected
                            return reject(e)
                        }
                    },
                    onRejected: function(reason) {
                        try {
                            //调用then方法的onResolved回调
                            var x = onRejected(reason)
                            //根据x的值修改promise2的状态
                            resolvePromise(promise2, x, resolve, reject)
                        } catch(e) {
                            //promise2状态变为rejected
                            return reject(e)
                        }
                    }
                })
            })
        }
    }

    //获取当前Promise传递的值
    Promise.prototype.valueOf = function() {
        return this.data
    }

    //由then方法实现catch方法
    Promise.prototype.catch = function(onRejected) {
        return this.then(null, onRejected)
    }

    //finally方法
    Promise.prototype.finally = function(fn) {
        return this.then(function(v){
            setTimeout(fn)
            return v
        }, function(r){
            setTimeout(fn)
            throw r
        })
    }

    Promise.prototype.spread = function(fn, onRejected) {
        return this.then(function(values) {
            return fn.apply(null, values)
        }, onRejected)
    }

    Promise.prototype.inject = function(fn, onRejected) {
        return this.then(function(v) {
            return fn.apply(null, fn.toString().match(/\((.*?)\)/)[1].split(',').map(function(key){
                return v[key];
            }))
        }, onRejected)
    }

    Promise.prototype.delay = function(duration) {
        return this.then(function(value) {
            return new Promise(function(resolve, reject) {
                setTimeout(function() {
                    resolve(value)
                }, duration)
            })
        }, function(reason) {
            return new Promise(function(resolve, reject) {
                setTimeout(function() {
                    reject(reason)
                }, duration)
            })
        })
    }

    Promise.all = function(promises) {
        return new Promise(function(resolve, reject) {
            var resolvedCounter = 0
            var promiseNum = promises.length
            var resolvedValues = new Array(promiseNum)
            for (var i = 0; i < promiseNum; i++) {
                (function(i) {
                    Promise.resolve(promises[i]).then(function(value) {
                        resolvedCounter++
                        resolvedValues[i] = value
                        if (resolvedCounter == promiseNum) {
                            return resolve(resolvedValues)
                        }
                    }, function(reason) {
                        return reject(reason)
                    })
                })(i)
            }
        })
    }

    Promise.race = function(promises) {
        return new Promise(function(resolve, reject) {
            for (var i = 0; i < promises.length; i++) {
                Promise.resolve(promises[i]).then(function(value) {
                    return resolve(value)
                }, function(reason) {
                    return reject(reason)
                })
            }
        })
    }

    Promise.resolve = function(value) {
        var promise = new Promise(function(resolve, reject) {
            resolvePromise(promise, value, resolve, reject)
        })
        return promise
    }

    Promise.reject = function(reason) {
        return new Promise(function(resolve, reject) {
            reject(reason)
        })
    }

    Promise.fcall = function(fn){
        // 虽然fn可以接收到上一层then里传来的参数,但是其实是undefined,所以跟没有是一样的,因为resolve没参数啊
        return Promise.resolve().then(fn)
    }

    Promise.done = Promise.stop = function(){
        return new Promise(function(){})
    }

    Promise.deferred = Promise.defer = function() {
        var dfd = {}
        dfd.promise = new Promise(function(resolve, reject) {
            dfd.resolve = resolve
            dfd.reject = reject
        })
        return dfd
    }

    try { // CommonJS compliance
        module.exports = Promise
    } catch(e) {}

    return Promise
})()


5、generator(异步编程、yield、next()、await 、async)

6、箭头函数 this 指向问题、拓展运算符

一、扩展运算符

二、箭头函数指向问题

箭头函数中的this

箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window; 箭头函数可以方便地让我们在 setTimeout ,setInterval中方便的使用this

要整明白这些, 我们需要首先了解一下作用域链:

当在函数中使用一个变量的时候,首先在本函数内部查找该变量,如果找不到则找其父级函数,

最后直到window,全局变量默认挂载在window对象下


7、module

8、map和set有没有用过,如何实现一个数组去重,map数据结构有什么优点?

set结构不会添加重复的值。

Array.from方法可以将Set结构转为数组,这就提供了去除数组重复成员的方法

function dedupe(array){
  return Array.from(new Set(array));
}
dedupe([1,1,2,3,2,4,4]);
//1,2,3,4

Map结构:javascript的对象,本质上是键值对的集合,但是传统上只能用字符串当做键。而Map数据结构的键的范围不限于字符串,各种类型

ES6_第1张图片
Map数据结构特性及方法


9、ES6 怎么编译成 ES5,css-loader 原理,过程

Babel 是一个 JavaScript 编译器
Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
css-loader原理,过程

loader用于对模块的源代码进行转换。loader可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。
css-loader是分析各个css文件的关系并合并成一个css
Babel使用指南
关于es6及以上的js编译成es5


10、ES6 转成 ES5 的常见例子

webpack 配合babel 将es6转成es5 超简单实例


11、使用 es5 实现 es6 的 class

使用 es5 实现 es6 的 class

你可能感兴趣的:(ES6,es6)