个人技术分享

目录

1 ES5、ES6和ES2015有什么区别?

2 babel是什么,有什么作用?

3 let有什么用,有了var为什么还要用let?

4 举一些ES6对String字符串类型做的常用升级优化?

5 举一些ES6对Array数组类型做的常用升级优化

6 举一些ES6对Number数字类型做的常用升级优化

7 举一些ES6对Object类型做的常用升级优化

8 举一些ES6对Function函数类型做的常用升级优化?

9 Symbol是什么,有什么作用?

10 Set是什么,有什么作用?

11 Map是什么,有什么作用?

12 Proxy是什么,有什么作用?

13 Reflect是什么,有什么作用?

14 Promise是什么,有什么作用?

15 Iterator是什么,有什么作用?

16 for...in 和for...of有什么区别?

17 Generator函数是什么,有什么作用?

generator 原理

Generator 实现

18 async函数是什么,有什么作用?

19 Class、extends是什么,有什么作用?

20 module、export、import是什么,有什么作用?

21 日常前端代码开发中,有哪些值得用ES6去改进的编程优化或者规范?

22 ES6的了解

23 说说你对Promise的理解

24 Promise 的构造函数

25 谈一谈你了解ECMAScript6的新特性?

26 Object.is() 与原来的比较操作符 ===、== 的区别?

27 什么是 Babel

28 symbol 有什么用处

29 模块化

30 箭头函数的特点

31 ES5 / ES6 的继承除了写法以外还有什么区别

32 全局作用域中,用 const 和 let 声明的变量不在 window 上,那到底在哪里?如何去获取?

33 介绍下 Set、Map、WeakSet 和 WeakMap 的区别

34 Promise.all() 和 Promise.allSettled()的比较

Promise.allSettled()

Promise.all()


ES2015 特指在2015 年发布的新一代JS 语言标准,ES6 泛指下一代JS 语言标准,包含ES2015 、ES2016 、ES2017 、ES2018 等。现阶段在绝大部分场景下,ES2015 默认等同ES6 。ES5 泛指上一代语言标准。ES2015 可以理解为ES5 和ES6 的时间分界线

babel 是一个 ES6 转码器,可以将 ES6 代码转为 ES5 代码,以便兼容那些还没支持ES6 的平台

ES6 之前,声明变量只能用var ,var 方式声明变量其实是很不合理的,准确的说,是因为ES5 里面没有块级作用域是很不合理的。没有块级作用域回来带很多难以理解的问题,比如for 循环var 变量泄露,变量覆盖等问题。let 声明的变量拥有自己的块级作用域,且修复了var 声明变量带来的变量提升问题。

优化部分

ES6 新增了字符串模板,在拼接大段字符串时,用反斜杠( )`取代以往的字符串相加的形式,能保留所有空格和换行,使得字符串拼接看起来更加直观,更加优雅

升级部分

ES6 在String 原型上新增了includes() 方法,用于取代传统的只能用indexOf 查找包含字符的方法(indexOf 返回-1 表示没查到不如includes 方法返回false 更明确,语义更清晰), 此外还新增了startsWith() , endsWith(), padStart() ,padEnd() ,repeat() 等方法,可方便的用于查找,补全字符串

优化部分

  • 数组解构赋值。ES6 可以直接以let [a,b,c] = [1,2,3] 形式进行变量赋值,在声明较多变量时,不用再写很多let(var), 且映射关系清晰,且支持赋默认值
  • 扩展运算符。ES6 新增的扩展运算符(... )(重要),可以轻松的实现数组和松散序列的相互转化,可以取代arguments 对象和apply 方法,轻松获取未知参数个数情况下的参数集合。(尤其是在ES5 中,arguments 并不是一个真正的数组,而是一个类数组的对象,但是扩展运算符的逆运算却可以返回一个真正的数组)。扩展运算符还可以轻松方便的实现数组的复制和解构赋值(let a = [2,3,4] ; let b = [...a] )

升级部分

ES6 在Array 原型上新增了find() 方法,用于取代传统的只能用indexOf 查找包含数组项目的方法,且修复了indexOf 查找不到NaN的bug([NaN].indexOf(NaN) === -1) .此外还新增了copyWithin() ,includes() , fill() ,flat() 等方法,可方便的用于字符串的查找,补全,转换等

优化部分

ES6在Number 原型上新增了isFinite() , isInteger(),isNaN() 方法,用来取代传统的全局isFinite(), isNaN() 方法检测数值是否有限、是否是NaN 。ES5 的isFinite() , isNaN() 方法都会先将非数值类型的参数转化为Number 类型再做判断,这其实是不合理的,最造成isNaN('NaN') === true 的奇怪行为--'NaN' 是一个字符串,但是isNaN 却说这就是NaN 。而Number.isFinite() 和Number.isNaN() 则不会有此类问题(Number.isNaN('NaN') === false )。(isFinite() 同上)

升级部分

ES6 在Math 对象上新增了Math.cbrt() ,trunc() ,hypot() 等等较多的科学计数法运算方法,可以更加全面的进行立方根、求和立方根等等科学计算

let a = 5;
 //isFinite 判断是不是数字,如果是数字,就返回true,不是就返回false。
 console.log(Number.isFinite(a))    // 结果 true

let bb = 452;
// isInteger 判断是不是整数,如果是整数就返回true,不是就false。
console.log(Number.isInteger(bb));

console.log(Number.isNaN('number'))
     //主要用于检测是不是NaN,如果是NaN就返回true,不是就返回false。
     //如果 x 是特殊的非数字值 NaN(或者能被转换为这样的值),返回的值就是 true。如果 x 是其他值,则返回 false。
    // isNaN() 函数可用于判断其参数是否是 NaN,该值表示一个非法的数字(比如被 0 除后得到的结果)。
    // 如果把 NaN 与任何值(包括其自身)相比得到的结果均是 false,所以要判断某个值是否是 NaN,不能使用 == 或 === 运算符。正因为如此,isNaN() 函数是必需的。
   


优化部分

对象属性变量式声明。ES6 可以直接以变量形式声明对象属性或者方法,。比传统的键值对形式声明更加简洁,更加方便,语义更加清晰

let [apple, orange] = ['red appe', 'yellow orange']
let myFruits = {
  apple,
  orange,
}
// let myFruits = {apple: 'red appe', orange: 'yellow orange'};

尤其在对象解构赋值(见优化部分b.)或者模块输出变量时,这种写法的好处体现的最为明显 

let { keys, values, entries } = Object
let MyOwnMethods = {
  keys,
  values,
  entries,
}
// let MyOwnMethods = {keys: keys, values: values, entries: entries}

可以看到属性变量式声明属性看起来更加简洁明了。方法也可以采用简洁写法

let es5Fun = {
  method: function () {},
}
let es6Fun = {
  method() {},
}

 对象的解构赋值。 ES6 对象也可以像数组解构赋值那样,进行变量的解构赋值

let { apple, orange } = {
  apple: 'red appe',
  orange: 'yellow orange',
}

 对象的扩展运算符(... )。 ES6对象的扩展运算符和数组扩展运算符用法本质上差别不大,毕竟数组也就是特殊的对象。对象的扩展运算符一个最常用也最好用的用处就在于可以轻松的取出一个目标对象内部全部或者部分的可遍历属性,从而进行对象的合并和分解

let { apple, orange, ...otherFruits } = {
  apple: 'red apple',
  orange: 'yellow orange',
  grape: 'purple grape',
  peach: 'sweet peach',
}
// otherFruits  {grape: 'purple grape', peach: 'sweet peach'}
// 注意: 对象的扩展运算符用在解构赋值时,扩展运算符只能用在最有一个参数(otherFruits后面不能再跟其他参数)
let moreFruits = {
  watermelon: 'nice watermelon',
}
let allFruits = {
  apple,
  orange,
  ...otherFruits,
  ...moreFruits,
}

 super 关键字。ES6 在Class 类里新增了类似this 的关键字super 。同this 总是指向当前函数所在的对象不同,super 关键字总是指向当前函数所在对象的原型对象

升级部分

ES6 在Object 原型上新增了is() 方法,做两个目标对象的相等比较,用来完善'===' 方法。'===' 方法中NaN === NaN //false 其实是不合理的,Object.is 修复了这个小bug 。(Object.is(NaN, NaN) // true)

ES6 在Object 原型上新增了assign() 方法,用于对象新增属性或者多个对象合并

const target = {
  a: 1,
}
const source1 = {
  b: 2,
}
const source2 = {
  c: 3,
}
Object.assign(target, source1, source2)
target // {a:1, b:2, c:3}

注意 : assign 合并的对象target 只能合并source1 、source2 中的自身属性,并不会合并source1 、source2 中的继承属性,也不会合并不可枚举的属性,且无法正确复制get和set属性(会直接执行get/set 函数,取return 的值)

  • ES6 在Object 原型上新增了getOwnPropertyDescriptors() 方法,此方法增强了ES5 中getOwnPropertyDescriptor() 方法,可以获取指定对象所有自身属性的描述对象。结合defineProperties() 方法,可以完美复制对象,包括复制get 和set 属性
  • ES6 在Object 原型上新增了getPrototypeOf() 和setPrototypeOf() 方法,用来获取或设置当前对象的prototype 对象。这个方法存在的意义在于,ES5 中获取设置prototype 对像是通过__proto__ 属性来实现的,然而__proto__ 属性并不是ES规范中的明文规定的属性,只是浏览器各大产商“私自”加上去的属性,只不过因为适用范围广而被默认使用了,再非浏览器环境中并不一定就可以使用,所以为了稳妥起见,获取或设置当前对象的prototype 对象时,都应该采用ES6新增的标准用法
  • ES6 在Object 原型上还新增了Object.keys() ,Object.values() ,Object.entries() 方法,用来获取对象的所有键、所有值和所有键值对数组

// Object.keys


// 处理对象,返回可枚举的属性数组
let person={
    name:'一只流浪的kk',
    age:20,
    eat:function(){}
}
    console.log(Object.keys(person));//    ['name','age','eat']

// 处理数组,返回索引值数组
 let arr=[1,2,3,4,5];
 console.log(Object.keys(arr));//['0','1','2','3','4','5']

// 处理字符串,返回索引值数组
let str='hello';
console.log(Object.keys(str));//['0','1','2','3','4']
// Object.values()

//返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值
let obj = {
    foo : "bar",
    baz : 20
};
console.log(Object.values(obj));  // ["bar", 20]

// 返回数组的成员顺序,与属性的遍历部分介绍的排列规则一致

const obj = {100 : "a", 2 : "b", 7 : "c"};
console.log(Object.values(obj));   //["b", "c", "a"]


// Object.values()只会遍历对象自身的可遍历属性
const obj = Object.create({},{p : {value : 10}});
console.log(Object.values(obj));    
console.log(Object.getOwnPropertyDescriptors(obj)); // []

 Object.create方法的第二个参数添加的对象属性(属性p),如果不显式声明,默认是不可遍历的,因为p的属性描述对象的enumerable默认是false,Object.values不会返回这个属性。
因此只要把enumerable改成true,Object.values就会返回属性p的值。

const obj = Object.create({},{p:{
     value : 10,
     enumerable : true,
     configurable : true,
     writable : true,
}})
console.log(Object.values(obj));    //[10]


//Object.values会过滤属性名为 Symbol 值的属性
//如果Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。
Object.values({ [Symbol()]: 123, foo: 'abc' });
console.log(Object.values('foo'));  //["f", "o", "o"]
// 如果参数不是对象,Object.values会先将其转为对象
console.log(Object.values(42));  // []
console.log(Object.values(true));  // []
console.log(Object.values(undefined));   //error
console.log(Object.values(null));   //error

Object.entries() 

var obj = { foo: 'bar', baz: 42 };
console.log(Object.entries(obj));

//如果原对象的属性名是一个 Symbol 值,该属性会被省略

console.log(Object.entries({ [Symbol()]: 123, foo: 'abc' }));  // [ [ 'foo', 'abc' ] ]
// 遍历对象的属性

let obj = {
    one : 1,
    two : 2,
}
for(let [k , v] of Object.entries(obj)){
     console.log(`${JSON.stringify(k)} : ${JSON.stringify(v)}`);
}

// 将对象转为真正的Map结构
const obj = {foo : "bar", baz : 10};
const map = new Map(Object.entries(obj));
console.log(map);
// 实现Object.entries方法
const entries = (obj) => {
     let result = [];
     const objType = typeof(obj);
     if(obj === undefined || obj === null){
          throw new TypeError();
     }
     if(objType === "number" || objType === "boolean"){
         return [];
     }
     for(let k of Object.keys(obj)){
         result.push([k,obj[k]]);
     }
     return result
}

优化部分

箭头函数(核心)。箭头函数是ES6核心的升级项之一,箭头函数里没有自己的this,这改变了以往JS函数中最让人难以理解的this运行机制。主要优化点

  • 箭头函数内的this指向的是函数定义时所在的对象,而不是函数执行时所在的对象。ES5函数里的this总是指向函数执行时所在的对象,这使得在很多情况下this 的指向变得很难理解,尤其是非严格模式情况下,this 有时候会指向全局对象,这甚至也可以归结为语言层面的bug之一。ES6的箭头函数优化了这一点,它的内部没有自己的this ,这也就导致了this 总是指向上一层的this ,如果上一层还是箭头函数,则继续向上指,直到指向到有自己this 的函数为止,并作为自己的this
  • 箭头函数不能用作构造函数,因为它没有自己的this ,无法实例化
  • 也是因为箭头函数没有自己的this,所以箭头函数 内也不存在arguments 对象。(可以用扩展运算符代替)
  • 函数默认赋值。ES6 之前,函数的形参是无法给默认值得,只能在函数内部通过变通方法实现。ES6 以更简洁更明确的方式进行函数默认赋值
function es6Fuc(x, y = 'default') {
  console.log(x, y)
}
es6Fuc(4)
// 4, default

升级部分

ES6新增了双冒号运算符,用来取代以往的bind ,call ,和apply 。(浏览器暂不支持,Babel 已经支持转码)

foo::bar;
// 等同于
bar.bind(foo);
 
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
 
const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
  return obj::hasOwnProperty(key);
}

箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法(call、apply、bind)。但是,箭头函数并不适用于所有场合,所以现在有一个提案,提出了“函数绑定”(function bind)运算符,用来取代call、apply、bind调用。

函数绑定运算符是并排的两个冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。

var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;
 
let log = ::console.log;
// 等同于
var log = console.log.bind(console);

Symbol 是ES6 引入的第七种原始数据类型(说法不准确,应该是第七种数据类型,Object不是原始数据类型之一,已更正),所有Symbol()生成的值都是独一无二的,可以从根本上解决对象属性太多导致属性名冲突覆盖的问题。对象中Symbol() 属性不能被for...in 遍历,但是也不是私有属性

Set 是ES6 引入的一种类似Array 的新的数据结构,Set 实例的成员类似于数组item 成员,区别是Set 实例的成员都是唯一,不重复的。这个特性可以轻松地实现数组去重

Map 是ES6 引入的一种类似Object 的新的数据结构,Map 可以理解为是Object 的超集,打破了以传统键值对形式定义对象,对象的key 不再局限于字符串,也可以是Object 。可以更加全面的描述对象的属性

Proxy 是ES6 新增的一个构造函数,可以理解为JS语言的一个代理,用来改变JS默认的一些语言行为,包括拦截默认的get/set 等底层方法,使得JS的使用自由度更高,可以最大限度的满足开发者的需求。比如通过拦截对象的get/set 方法,可以轻松地定制自己想要的key 或者value 。下面的例子可以看到,随便定义一个myOwnObj 的key ,都可以变成自己想要的函数`

function createMyOwnObj() {
  //想把所有的key都变成函数,或者Promise,或者anything
  return new Proxy(
    {},
    {
      get(target, propKey, receiver) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            let randomBoolean = Math.random() > 0.5
            let Message
            if (randomBoolean) {
              Message = `
                                            你的
                                            
                                                ${propKey}
                                            
                                            运气不错,成功了
                                            `
              resolve(Message)
            } else {
              Message = `
                                            你的
                                            
                                                ${propKey}
                                            
                                            运气不行,失败了
                                            `
              reject(Message)
            }
          }, 1000)
        })
      },
    }
  )
}
let myOwnObj = createMyOwnObj()
myOwnObj.hahaha
  .then((result) => {
    console.log(result)
    //你的hahaha运气不错,成功了
  })
  .catch((error) => {
    console.log(error)
    //你的hahaha运气不行,失败了
  })
myOwnObj.wuwuwu
  .then((result) => {
    console.log(result)
    //你的wuwuwu运气不错,成功了
  })
  .catch((error) => {
    console.log(error)
    //你的wuwuwu运气不行,失败了
  })

Proxy属性操作介绍

  • get(target, propKey, receiver) :拦截对象属性的读取
  • set(target, propKey, value, receiver) :拦截对象属性的设置返回一个布尔值。
  • has(target, propKey) :拦截propKey in proxy的操作,返回一个布尔值。
  • deleteProperty(target, propKey) :拦截delete proxy[propKey]的操作,返回一个布尔值。
  • ownKeys(target) :拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • getOwnPropertyDescriptor(target, propKey) :拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc) :拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target) :拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target) :拦截Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target) :拦截Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto) :拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • apply(target, object, args) :拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
  • construct(target, args) :拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。

 一些常用的Proxy属性用法示例

get(target, prop, receiver)

  • 作用:拦截对目标对象属性的读取操作。

  • 参数:

    • target:目标对象。
    • prop:要访问的属性名。
    • receiver:代理对象本身(或者说是拦截操作所在的环境)。
  • 示例:

const target = {
  name: 'Alice',
  age: 25
};
const proxy = new Proxy(target, {
  get(target, prop) {
    console.log(`Getting ${prop}`);
    return target[prop];
  }
});

console.log(proxy.name); // 输出:Getting name,Alice

set(target, prop, value, receiver)

  • 作用:拦截对目标对象属性的赋值操作。

  • 参数:

    • target:目标对象。
    • prop:要设置的属性名。
    • value:要设置的值。
    • receiver:代理对象本身(或者说是拦截操作所在的环境)。
  • 示例:

const target = {
  name: 'Alice',
  age: 25
};

const proxy = new Proxy(target, {
  set(target, prop, value) {
    console.log(`Setting ${prop} to ${value}`);
    target[prop] = value;
    return true;
  }
});

proxy.age = 30; // 输出:Setting age to 30
console.log(proxy.age); // 输出:30

has(target, prop)

  • 作用:拦截对in操作符的操作,判断属性是否存在。

  • 参数:

    • target:目标对象。
    • prop:要判断的属性名。
  • 示例:

const target = {
  name: 'Alice',
  age: 25
};

const proxy = new Proxy(target, {
  has(target, prop) {
    console.log(`Checking if ${prop} exists`);
    return prop in target;
  }
});

console.log('name' in proxy); // 输出:Checking if name exists,true
console.log('gender' in proxy); // 输出:Checking if gender exists,false

deleteProperty(target, prop)

  • 作用:拦截对delete操作符的操作,删除属性。

  • 参数:

    • target:目标对象。
    • prop:要删除的属性名。
  • 示例:

const target = {
  name: 'Alice',
  age: 25
};

const proxy = new Proxy(target, {
  deleteProperty(target, prop) {
    console.log(`Deleting ${prop}`);
    delete target[prop];
    return true;
  }
});

delete proxy.age; // 输出:Deleting age console.log(proxy.age); // 输出:undefined

apply(target, thisArg, argumentsList)

  • 作用:拦截对代理对象的函数调用。

  • 参数:

    • target:目标对象。
    • thisArg:函数调用时的this值。
    • argumentsList:函数调用时的参数列表。
  • 示例:

const target = function (name) {
  console.log(`Hello, ${name}`);
};

const proxy = new Proxy(target, {
  apply(target, thisArg, argumentsList) {
    console.log('Calling function');
    return target.apply(thisArg, argumentsList);
  }
});

proxy('Alice'); // 输出:Calling function,Hello, Alice

construct(target, argumentsList, newTarget)

  • 作用:拦截对代理对象的构造函数调用。

  • 参数:

    • target:目标对象。
    • argumentsList:构造函数调用时的参数列表。
    • newTarget:最初被调用的构造函数。
  • 示例:

const target = function (name) {
  this.name = name;
};

const proxy = new Proxy(target, {
  construct(target, argumentsList) {
    console.log('Constructing object');
    return new target(...argumentsList);
  }
});

const obj = new proxy('Alice'); // 输出:Constructing object
console.log(obj.name); // 输出:Alice

getPrototypeOf(target)

  • 作用:拦截对代理对象原型链上属性访问操作。

  • 参数:

    • target:目标对象。
  • 示例:

const target = {};
const proxy = new Proxy(target, {
getPrototypeOf(target) {
console.log('Getting prototype');
return Object.getPrototypeOf(target);
}
});

console.log(Object.getPrototypeOf(proxy)); // 输出:Getting prototype,{}

setPrototypeOf(target, prototype)

  • 作用:拦截对代理对象原型链上属性设置操作。
  • 参数:
    • target:目标对象。
    • prototype:要设置的原型对象。
  • 示例:
const target = {}
const proxy = new Proxy(target, {
  setPrototypeOf(target, prototype) {
    console.log('Setting prototype')
    return Object.setPrototypeOf(target, prototype)
  }
})

const proto = { name: 'Alice' }
Object.setPrototypeOf(proxy, proto) // 输出:Setting prototype
console.log(proxy.name) // 输出:Alice

Proxy是ES6中的一个重要特性,它提供了一种拦截和修改对象默认操作的能力。通过使用Proxy,我们可以实现访问控制、数据验证、属性劫持等功能。Proxy的原理是通过在代理对象上定义一组钩子函数来实现拦截和自定义处理。在实际应用中,我们可以根据需要使用Proxy来解决各种问题,提高代码的灵活性和可维护性。

Reflect 是ES6 引入的一个新的对象,他的主要作用有两点,一是将原生的一些零散分布在Object 、Function 或者全局函数里的方法(如apply 、delete 、get 、set 等等),统一整合到Reflect 上,这样可以更加方便更加统一的管理一些原生API 。其次就是因为Proxy 可以改写默认的原生API,如果一旦原生API 别改写可能就找不到了,所以Reflect 也可以起到备份原生API的作用,使得即使原生API 被改写了之后,也可以在被改写之后的API 用上默认的API

Promise 是ES6 引入的一个新的对象,他的主要作用是用来解决JS异步机制里,回调机制产生的“回调地狱”。它并不是什么突破性的API ,只是封装了异步回调形式,使得异步回调可以写的更加优雅,可读性更高,而且可以链式调用

  • Iterator 是ES6 中一个很重要概念,它并不是对象,也不是任何一种数据类型。因为ES6 新增了Set 、Map 类型,他们和Array 、Object 类型很像,Array 、Object 都是可以遍历的,但是Set 、Map 都不能用for 循环遍历,解决这个问题有两种方案,一种是为Set 、Map 单独新增一个用来遍历的API ,另一种是为Set 、Map 、Array 、Object 新增一个统一的遍历API ,显然,第二种更好,ES6 也就顺其自然的需要一种设计标准,来统一所有可遍历类型的遍历方式。Iterator 正是这样一种标准。或者说是一种规范理念
  • 就好像JavaScript 是ECMAScript 标准的一种具体实现一样,Iterator 标准的具体实现是Iterator 遍历器。Iterator 标准规定,所有部署了key 值为[Symbol.iterator] ,且[Symbol.iterator] 的value 是标准的Iterator 接口函数(标准的Iterator 接口函数: 该函数必须返回一个对象,且对象中包含next 方法,且执行next() 能返回包含value/done 属性的Iterator 对象)的对象,都称之为可遍历对象,next() 后返回的Iterator 对象也就是Iterator 遍历器
//obj就是可遍历的,因为它遵循了Iterator标准,且包含[Symbol.iterator]方法,方法函数也符合标准的Iterator接口规范。
//obj.[Symbol.iterator]() 就是Iterator遍历器
let obj = {
  data: ['hello', 'world'],
  [Symbol.iterator]() {
    const self = this
    let index = 0
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          }
        } else {
          return {
            value: undefined,
            done: true
          }
        }
      },
    }
  },
}

 ES6 给Set 、Map 、Array 、String 都加上了[Symbol.iterator] 方法,且[Symbol.iterator] 方法函数也符合标准的Iterator 接口规范,所以Set 、Map 、Array 、String 默认都是可以遍历的

//Array
let array = ['red', 'green', 'blue']
array[Symbol.iterator]()
//Iterator遍历器
array[Symbol.iterator]().next()
//{value: "red ", done: false}
//String
let string = '1122334455'
string[Symbol.iterator]()
//Iterator遍历器
string[Symbol.iterator]().next()
//{value: "1 ", done: false}
//set
let set = new Set(['red', 'green', 'blue'])
set[Symbol.iterator]()
//Iterator遍历器
set[Symbol.iterator]().next()
//{value: "red ", done: false}
//Map
let map = new Map()
let obj = {
  map: 'map',
}
map.set(obj, 'mapValue')
map[Symbol.iterator]().next()
// {
//   value: Array(2),
//   done: false
// }

所有部署了载了Iterator 接口的对象(可遍历对象)都可以通过for...of 去遍历,而for..in 仅仅可以遍历对象

  • 这也就意味着,数组也可以用for...of 遍历,这极大地方便了数组的取值,且避免了很多程序用for..in 去遍历数组的恶习
        
  • 如果说JavaScript 是ECMAScript 标准的一种具体实现、Iterator 遍历器是Iterator 的具体实现,那么Generator 函数可以说是Iterator 接口的具体实现方式。
  • 执行Generator 函数会返回一个遍历器对象,每一次Generator 函数里面的yield 都相当一次遍历器对象的next() 方法,并且可以通过next(value) 方法传入自定义的value,来改变Generator 函数的行为。
  • Generator 函数可以通过配合Thunk 函数更轻松更优雅的实现异步编程和控制流管理。
  • Generator 是 ES6 中新增的语法,和 Promise 一样,都可以用来异步编程
// 使用 * 表示这是一个 Generator 函数
// 内部可以通过 yield 暂停代码
// 通过调用 next 恢复执行
function* test() {
  let a = 1 + 2
  yield 2
  yield 3
}
let b = test()
console.log(b.next())
// >{ value: 2, done: false }
console.log(b.next())
// >{ value: 3, done: false }
console.log(b.next())
// >{ value: undefined, done: true }

从以上代码可以发现,加上 * 的函数执行后拥有了 next 函数,也就是说函数执行后返回了一个对象。每次调用 next 函数可以继续执行被暂停的代码。以下是 Generator 函数的简单实现 

// cb 也就是编译过的 test 函数
function generator(cb) {
  return (function () {
    var object = {
      next: 0,
      stop: function () {},
    }
    return {
      next: function () {
        var ret = cb(object)
        if (ret === undefined)
          return {
          value: undefined,

          done: true
        }
        return{
          value: ret,
          done: false
        }
      },
    }
  })()
}
// 如果你使用 babel 编译后可以发现 test 函数变成了这样
function test() {
  var a
  return generator(function (_context) {
    while (1) {
      switch ((_context.prev = _context.next)) {
        // 可以发现通过 yield 将代码分割成几块
        // 每次执行 next 函数就执行一块代码
        // 并且表明下次需要执行哪块代码
        case 0:
          a = 1 + 2
          _context.next = 4
          return 2
        case 4:
          _context.next = 6
          return
          3
        // 执行完毕
        case 6:
        case 'end ':
          return _context.stop()
      }
    }
  })
}

  • Generator 是 ES6 中新增的语法,和 Promise 一样,都可以用来异步编程
// 使用 * 表示这是一个 Generator 函数
// 内部可以通过 yield 暂停代码
// 通过调用 next 恢复执行
function* test() {
  let a = 1 + 2
  yield 2
  yield 3
}
let b = test()
console.log(b.next())
// >{ value: 2, done: false }
console.log(b.next())
// >{ value: 3, done: false }
console.log(b.next())
// >{ value: undefined, done: true }

从以上代码可以发现,加上 * 的函数执行后拥有了 next 函数,也就是说函数执行后返回了一个对象。每次调用 next 函数可以继续执行被暂停的代码。以下是 Generator 函数的简单实现

// cb 也就是编译过的 test 函数
function generator(cb) {
  return (function () {
    var object = {
      next: 0,
      stop: function () {},
    }
    return {
      next: function () {
        var ret = cb(object)
        if (ret === undefined)
          return {
            value: undefined,
            done: true,
          }
        return {
          value: ret,
          done: false,
        }
      },
    }
  })()
}
// 如果你使用 babel 编译后可以发现 test 函数变成了这样
function test() {
  var a
  return generator(function (_context) {
    while (1) {
      switch ((_context.prev = _context.next)) {
        // 可以发现通过 yield 将代码分割成几块
        // 每次执行 next 函数就执行一块代码
        // 并且表明下次需要执行哪块代码
        case 0:
          a = 1 + 2
          _context.next = 4
          return 2
        case 4:
          _context.next = 6
          return 3
        // 执行完毕
        case 6:
        case 'end ':
          return _context.stop()
      }
    }
  })
}

async 函数可以理解为内置自动执行器的Generator 函数语法糖,它配合ES6 的Promise 近乎完美的实现了异步编程解决方案

async、await 优缺点

async 和 await 相比直接使用 Promise 来说,优势在于处理 then 的调用链,能够更清晰准确的写出代码。缺点在于滥用 await 可能会导致性能问题,因为 await 会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发性

下面来看一个使用 await 的代码。

var a = 0
var b = async () => {
  a = a + (await 10)
  console.log('2', a)
  // ->'2' 10
  a = (await 10) + a
  console.log('3', a)
  // ->'3' 20
}
b()
a++
console.log('1', a)
// ->'1' 1

  • 首先函数b 先执行,在执行到 await 10 之前变量 a 还是 0 ,因为在 await 内部实现了 generators ,generators 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
  • 因为 await 是异步操作,遇到await 就会立即返回一个pending 状态的Promise 对象,暂时返回执行代码的控制权,使得函数外的代码得以继续执行,所以会先执行 console.log('1', a)
  • 这时候同步代码执行完毕,开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 10
  • 然后后面就是常规执行代码了
  • 19 Class、extends是什么,有什么作用?
  • ES6 的class 可以看作只是一个ES5 生成实例对象的构造函数的语法糖。它参考了java 语言,定义了一个类的概念,让对象原型写法更加清晰,对象实例化更像是一种面向对象编程。Class 类可以通过extends 实现继承。它和ES5构造函数的不同点

    类的内部定义的所有方法,都是不可枚举的

///ES5
function ES5Fun(x, y) {
  this.x = x
  this.y = y
}
ES5Fun.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')'
}
var p = new ES5Fun(1, 3)
p.toString()
Object.keys(ES5Fun.prototype)
//['toString']
//ES6
class ES6Fun {
  constructor(x, y) {
    this.x = x
    this.y = y
  }
  toString() {
    return '(' + this.x + ', ' + this.y + ')'
  }
}
Object.keys(ES6Fun.prototype)
//[]
  • ES6 的class 类必须用new 命令操作,而ES5 的构造函数不用new 也可以执行。
  • ES6 的class 类不存在变量提升,必须先定义class 之后才能实例化,不像ES5 中可以将构造函数写在实例化之后。
  • ES5 的继承,实质是先创造子类的实例对象this ,然后再将父类的方法添加到this 上面。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this 上面(所以必须先调用super 方法),然后再用子类的构造函数修改this 。

  • module 、export 、import 是ES6 用来统一前端模块化方案的设计思路和实现方案。export 、import 的出现统一了前端模块化的实现方案,整合规范了浏览器/服务端的模块化方法,用来取代传统的AMD/CMD 、requireJS 、seaJS 、commondJS 等等一系列前端模块不同的实现方案,使前端模块化更加统一规范,JS 也能更加能实现大型的应用程序开发。
  • import 引入的模块是静态加载(编译阶段加载)而不是动态加载(运行时加载)。
  • import 引入export 导出的接口值是动态绑定关系,即通过该接口,可以取到模块内部实时的值
  • 常用箭头函数来取代var self = this ;的做法。
  • 常用let 取代var 命令。
  • 常用数组/对象的结构赋值来命名变量,结构更清晰,语义更明确,可读性更好。
  • 在长字符串多变量组合场合,用模板字符串来取代字符串累加,能取得更好地效果和阅读体验。
  • Class 类取代传统的构造函数,来生成实例化对象。
  • 在大型应用开发中,要保持module 模块化开发思维,分清模块之间的关系,常用import 、export 方法。

新增模板字符串(为JavaScript提供了简单的字符串插值功能)、箭头函数(操作符左边为输入的参数,而右边则是进行的操作以及返回的值Inputs=>outputs。)、for-of(用来遍历数据—例如数组中的值。)arguments对象可被不定参数和默认参数完美代替。ES6将promise对象纳入规范,提供了原生的Promise对象。增加了let和const命令,用来声明变量。增加了块级作用域。let命令实际上就增加了块级作用域。ES6规定,var命令和function命令声明的全局变量,属于全局对象的属性;let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。还有就是引入module模块的概念

  • 依照 Promise/A+ 的定义,Promise 有四种状态:
    • pending : 初始状态, 非 fulfilled 或 rejected.
    • fulfilled : 成功的操作.
    • rejected : 失败的操作.
    • settled : Promise 已被fulfilled 或rejected ,且不是pending
  • 另外, fulfilled 与 rejected 一起合称 settled
  • Promise 对象用来进行延迟(deferred ) 和异步(asynchronous ) 计算
  • 可以把 Promise 看成一个状态机。初始是 pending 状态,可以通过函数 resolve 和 reject ,将状态转变为 resolved 或者 rejected 状态,状态一旦改变就不能再次变化。
  • then 函数会返回一个 Promise 实例,并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态,其他状态是不可以改变的,如果返回的是一个相同实例的话,多个 then 调用就失去意义了

构造一个 Promise ,最基本的用法如下:

        

// 创建一个新的 Promise 对象
let myPromise = new Promise((resolve, reject) => {
    // 异步操作的代码
    let success = true; // 假设这是操作的成功标志
 
    if (success) {
        // 如果操作成功,调用 resolve 并传入结果
        resolve('操作成功完成');
    } else {
        // 如果操作失败,调用 reject 并传入错误信息
        reject(new Error('操作失败'));
    }
});
 
// 使用 then 方法来处理 Promise 的成功情况
// 使用 catch 方法来处理 Promise 的失败情况
myPromise.then((result) => {
    console.log(result); // 输出: 操作成功完成
}).catch((error) => {
    console.error(error.message); // 输出: 操作失败
});

什么是 Promise ?

  • Promise 就是一个对象,用来表示并传递异步操作的最终结果
  • Promise 最主要的交互方式:将回调函数传入 then 方法来获得最终结果或出错原因
  • Promise 代码书写上的表现:以“链式调用”代替回调函数层层嵌套(回调地狱)

  • 块级作用区域 let a = 1;
  • 可定义常量 const PI = 3.141592654;
  • 变量解构赋值 var [a, b, c] = [1, 2, 3];
  • 字符串的扩展(模板字符串) var sum = ${a + b};
  • 数组的扩展(转换数组类型) Array.from($('li'));
  • 函数的扩展(扩展运算符) [1, 2].push(...[3, 4, 5]);
  • 对象的扩展(同值相等算法) Object.is(NaN, NaN);
  • 新增数据类型(Symbol) let uid = Symbol('uid');
  • 新增数据结构(Map) let set = new Set([1, 2, 2, 3]);
  • for...of循环 for(let val of arr){};
  • Promise对象 var promise = new Promise(func);
  • Generator函数 function* foo(x){yield x; return x*x;}
  • 引入Class(类) class Foo {}
  • 引入模块体系 export default func;
  • 引入async函数[ES7]
async function asyncPrint(value, ms) {
  await timeout(ms)
  console.log(value)
}

  • == 相等运算符,比较时会自动进行数据类型转换
  • === 严格相等运算符,比较时不进行隐式类型转换
  • Object.is 同值相等算法,在 === 基础上对 0 和 NaN 特别处理
;+0 === -0
//true
NaN === NaN
// false
Object.is(+0, -0)
// false
Object.is(NaN, NaN)
// true

  • Babel 是一个 JS 编译器,自带一组 ES6 语法转化器,用于转化 JS 代码。 这些转化器让开发者提前使用最新的 JS语法(ES6/ES7),而不用等浏览器全部兼容。
  • Babel 默认只转换新的 JS 句法(syntax),而不转换新的API。

可以用来表示一个独一无二的变量防止命名冲突。但是面试官问还有吗?我没想出其他的用处就直接答我不知道了,还可以利用 symbol 不会被常规的方法(除了 Object.getOwnPropertySymbols 外)遍历到,所以可以用来模拟私有变量。

主要用来提供遍历接口,布置了 symbol.iterator 的对象才可以使用 for···of 循环,可以统一处理数据结构。调用之后回返回一个遍历器对象,包含有一个 next 方法,使用 next 方法后有两个返回值 value 和 done 分别表示函数当前执行位置的值和是否遍历完毕。

Symbol.for() 可以在全局访问 symbol

在有 Babel 的情况下,我们可以直接使用 ES6 的模块化

// file a.js
export function a() {}
export function b() {}
// file b.js
export default function () {}
import { a, b } from './a.js'
import XXX from './b.js'

CommonJS

CommonJs 是 Node 独有的规范,浏览器中使用就需要用到 Browserify 解析了。

// a.js
module.exports = {
  a: 1,
}
// or
exports.a = 1
// b.js
var module = require('./a.js')
module.a // ->log 1

在上述代码中,module.exports 和 exports 很容易混淆,让我们来看看大致内部实现 

var module = require('./a.js')
module.a

// 这里其实就是包装了一层立即执行函数,这样就不会污染全局变量了,
// 重要的是 module 这里,module 是 Node 独有的一个变量
module.exports = {
  a: 1,
}
// 基本实现
var module = {
  exports: {},
  // exports 就是个空对象
}
// 这个是为什么 exports 和 module.exports 用法相似的原因
var exports = module.exports
var load = function (module) {
  // 导出的东西
  var a = 1
  module.exports = a
  return module.exports
}

再来说说 module.exports 和exports ,用法其实是相似的,但是不能对 exports 直接赋值,不会有任何效果。

对于 CommonJS 和 ES6 中的模块化的两者区别是:

  • 前者支持动态导入,也就是 require(${path}/xx.js) ,后者目前不支持,但是已有提案,前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。
  • 而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
  • 前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。
  • 但是后者采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
  • 后者会编译成 require/exports 来执行的

AMD

AMD 是由 RequireJS 提出的

// AMD
define(['./a', './b'], function (a, b) {
  a.do()
  b.do()
})
define(function (require, exports, module) {
  var a = require('./a')
  a.doSomething()
  var b = require('./b')
  b.doSomething()
})

function a() {
  return () => {
    return () => {
      console.log(this)
    }
  }
}
console.log(a()()())

箭头函数其实是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this 。在这个例子中,因为调用 a 符合前面代码中的第一个情况,所以 this 是window 。并且 this 一旦绑定了上下文,就不会被任何代码改变

普通函数、箭头函数的区别

  • 箭头函数不绑定 arguments ,可以使用 ...args 代替
  • 箭头函数没有 prototype 属性,不能进行 new 实例化,亦不能通过 call 、apply 等绑定 this
  • 箭头函数的this 指向创建时父级的this

  • ES5 的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到 this 上(Parent.apply(this))
  • ES6 的继承机制完全不同,实质上是先创建父类的实例对象 this(所以必须先调用父类的 super()方法),然后再用子类的构造函数修改 this。
  • ES5 的继承时通过原型或构造函数机制来实现。
  • ES6 通过 class 关键字定义类,里面有构造方法,类之间通过 extends 关键字实现继承。
  • 子类必须在 constructor 方法中调用 super 方法,否则新建实例报错。因为子类没有自己的 this 对象,而是继承了父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类得不到 this 对象。
  • 注意 super 关键字指代父类的实例,即父类的 this 对象。
  • 注意:在子类构造函数中,调用 super 后,才可使用 this 关键字,否则报错。function 声明会提升,但不会初始化赋值。Foo 进入暂时性死区,类似于 let、const 声明变量
// 在 ES5 中,顶层对象的属性和全局变量是等价的,var 命令和 function 命令声明的全局变量,自然也是顶层对象。
var a = 12
function f() {}
console.log(window.a)
// 12console.log(window.f);
// f(){}
// 但 ES6 规定,var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性,但 let 命令、const 命令、class 命令声明的全局变量,不属于顶层对象的属性。
let aa = 1
const bb = 2
console.log(window.aa)
// undefined console.log(window.bb);
// undefined
// 在哪里?怎么获取?通过在设置断点,看看浏览器是怎么处理的:
//通过上图也可以看到,在全局作用域中,用 let 和 const 声明的全局变量并没
//有在全局对象中,只是一个块级作用域(Script)中怎么获取?在定义变量的块级作用域中就能获取啊,既然不属于顶层对象,那就不加 window(global)呗。
let aa = 1
const bb = 2
console.log(aa)
// 1console.log(bb);
// 2

Set

  • 成员不能重复
  • 只有健值,没有健名,有点类似数组
  • 可以遍历,方法有add , delete ,has

weakSet

  • 成员都是对象
  • 成员都是弱引用,随时可以消失。 可以用来保存DOM节点,不容易造成内存泄漏
  • 不能遍历,方法有add , delete ,has

Map

  • 本质上是健值对的集合,类似集合
  • 可以遍历,方法很多,可以干跟各种数据格式转换

weakMap

  • 直接受对象作为健名(null 除外),不接受其他类型的值作为健名
  • 健名所指向的对象,不计入垃圾回收机制
  • 不能遍历,方法同get ,set ,has ,delete
  • 34 Promise.all() 和 Promise.allSettled()的比较
  • 接受的结果与入参时的promise实例一一对应,且结果的每一项都是一个对象,告诉你结果和值,对象内都有一个属性叫“status”,用来明确知道对应的这个promise实例的状态(fulfilled或rejected),fulfilled时,对象有value属性,rejected时有reason属性,对应两种状态的返回值。

        

const resolved = Promise.resolve(42)
const rejected = Promise.reject(-1)
const allSettledPromise = Promise.allSettled([resolved, rejected])
allSettledPromise.then(function (results) {
  console.log(results)
})
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]

重要的一点是,他不论接受入参的promise本身的状态,会返回所有promise的结果,但这一点Promise.all 做不到,如果你需要知道所有入参的异步操作的所有结果,或者需要知道这些异步操作是否全部结束,应该使用promise.allSettled() 

  • 只有当所有入参的promise实例都是fulfilled 状态,才会在Promise.all().then() 方法中结果,返回结果也是与入参一一对应,结果中只包含实际的resolve 的结果,不包含类似allSettled 的status 和value 属性。
const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('Promise 1 finished'), 1000);
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('Promise 2 finished'), 2000);
});

Promise.all([promise1, promise2])
  .then(results => {
    console.log(results); // 输出: ["Promise 1 finished", "Promise 2 finished"]
    // 所有异步请求都已完成,可以进行后续操作
  })
  .catch(error => {
    console.error(error);
    // 如果有任何一个 Promise 被 rejected,则会在这里捕获到错误
  });