自用前端面试题( ES6&TS)

194 阅读19分钟

1. ES6 箭头函数中的 this 和普通函数中的有什么不同?

普通函数中的this:

  1. this总是代表它的直接调用者, 例如 obj.func ,那么func中的this就是obj

  2. 在默认情况(非严格模式下,未使用 'use strict'),没找到直接调用者,则this指的是 window

  3. 在严格模式下,没有直接调用者的函数中的this是 undefined

  4. 使用call,apply,bind(ES5新增)绑定的,this指的是 绑定的对象

箭头函数中的this

默认指向在定义它时,它所处的对象,而不是执行时的对象, 例如定义它的时候,可能环境是window(即继承父级的this)

2.ES6模块化如何使用?

如果只是输出一个唯一的对象,使用 export default 即可,代码如下:

// 创建 util1.js 文件,内容如 
export default {
a: 100 
}
// 创建 index.js 文件,内容如
import obj from './util1.js' 
console.log(obj)

如果想要输出许多个对象,就不能用 default 了,且 import 时候要加 {...} ,代码如下:

// 创建 util2.js 文件,内容如 
export function fn1() {

alert('fn1') }

export function fn2() { alert('fn2')

}

// 创建 index.js 文件,内容如  
import { fn1, fn2 } from './util2.js' 
fn1()  
fn2()

3.ES6 class 和普通构造函数的区别?(TODO)

  1. 严格模式类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式
  2. 变量提升 类不存在变量提升,这一点与 ES5 完全不同
  3. 枚举方法ES6 中的 class,它的方法(包括静态方法和实例方法)默认是不可枚举的,而构造函数默认是可枚举的。
  4. 调用方式
    • class 内的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有[[construct]],不能使用 new 来调用
    • class 必须使用 new 调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用 new 也可以执行
  5. 继承
    • ES5 和 ES6 子类 this 生成顺序不同
    • ES5 的继承先生成了子类实例,再调用父类的构造函数修饰子类实例。ES6 的继承先 生成父类实例,再调用子类的构造函数修饰父类实例。这个差别使得 ES6 可以继承内置对象。
    • ES6可以继承静态方法,而构造函数不能

4.箭头函数 和普通函数有什么区别?

  1. this指向不同 普通函数this指向为方法调用的对象,可以通过call、apply、bind改变this指向,箭头函数不会创建自己的this,只会从自己的作用域的上一层继承this,call、apply、bind只能调用传递参数,不可修改this指向;
  2. 箭头函数不可以当做构造函数,不能用new 命令
  3. 箭头函数不可以使用yield 命令,不能作为Generator 函数;
  4. 箭头函数不可以使用arguments对象,可用rest参数【也叫剩余参数】代替

5.ES6中新增的数据类型有哪些?

  1. Symbol类型
  2. BigInt类型
  3. Set类型
  4. Map类型
  5. weakSet类型、WeakMap类型
  6. TypedArray类型

详见:blog.csdn.net/weixin_6054…

6.什么是Promise?什么作用?有什么优缺点?

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

优点

  1. 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

  2. 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

  3. 有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易

缺点

  1. 无法取消Promise,一旦新建它就会立即执行,无法中途取消;
  2. 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部;
  3. 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
  4. 不适用于某些事件不断地反复发生的情况

7.ES5的继承和ES6的继承有什么区别?

ES5的继承时通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。

ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this

具体的:ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。

ps:super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可使用this关键字,否则报错

8.ES6语法知识

juejin.cn/post/684490…

9.ES6、ES7、ES8特性

juejin.cn/post/684490…
juejin.cn/post/684490…

10.实现ES6的class语法

function inherit(subType, superType) {
    subType.prototype = Object.create(superType.prototype, {
        constructor: {
            enumerable: false,
            configurable: true,
            writable: true,
            value: subType
        }
    })
    Object.setPrototypeOf(subType, superType)
}

ES6 的 class 内部是基于寄生组合式继承,它是目前最理想的继承方式,通过 Object.create 方法创造一个空对象,并将这个空对象继承 Object.create 方法的参数,再让子类(subType)的原型对象等于这个空对象,就可以实现子类实例的原型等于这个空对象,而这个空对象的原型又等于父类原型对象(superType.prototype)的继承关系

而 Object.create 支持第二个参数,即给生成的空对象定义属性和属性描述符/访问器描述符,我们可以给这个空对象定义一个 constructor 属性更加符合默认的继承行为,同时它是不可枚举的内部属性(enumerable:false)

而 ES6 的 class 允许子类继承父类的静态方法和静态属性,而普通的寄生组合式继承只能做到实例与实例之间的继承,对于类与类之间的继承需要额外定义方法,这里使用 Object.setPrototypeOf 将 superType 设置为 subType 的原型,从而能够从父类中继承静态方法和静态属性。

11.实现偏函数

const compose = (...args) => x => args.reduceRight((result, cb) => cb(res), x);
var f = compose(fn1, fn2, fn3, fn4, fn5);

12.ES6代码转成ES5代码的实现思路是什么?

使用工具

行业内一般是使用babel进行代码转化

自行实现

思路:

  • 将代码字符串解析成抽象语法树,即所谓的 AST
  • 对 AST 进行处理,在这个阶段可以对 ES6 代码进行相应转换,即转成 ES5 代码
  • 根据处理后的 AST 再生成代码字符串

12.new ()=>{}会返回什么?什么时候使用箭头函数?

new ()=>{}会报错。

箭头函数使用场景:

  1. 箭头函数适合于无复杂逻辑或无副作用的纯函数场景下,例如,map、reduce、filter的回调函数定义中
  2. 不适应与有多层嵌套的情况下使用
  3. 箭头函数不具备普通函数里常见的this、arguments等,不能用call(),apply()、bind()去改变this的指向
  4. 不适合定义对象的方法(如:对象字面量方法、对象原型方法、构造器方法)
  5. 不适合定义结合动态上下文的回调函数(如:事件绑定函数),箭头函数在声明的时候会绑定静态上下文

13.es6的class和es5的prototype有啥区别?

ES5:继承:

  • ES5通过原型链实现继承,子类的prototype为父类对象的一个实例,因此子类的原型对象包含指向父类的原型对象的指针,父类的实例属性成为子类原型属性

  • ES6 class的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))

ES6为了进一步的缩减代码的编写,和简化代码的逻辑,引入了关键词 class。但class的实现也是在prototype的基础上,做了一层语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

14.说一下TS对比JS的特性

TypeScript 是一种给 JavaScript 添加特性的语言扩展。主要有以下特性:

  1. 类型批注和编译时类型检查
  2. 接口
  3. 模块
  4. 装饰器

15.说一下websocket的使用场景

先总结:高即时性服务。

  1. websocket社交订阅

  2. websocket多玩家游戏

  3. websocket协同编辑/编程

  4. websocket收集点击流数据

  5. 股票基金报价

  6. 体育实况更新

  7. 多媒体聊天

  8. 基于位置的应用

  9. 在线教育

10.论坛的消息广播 blog.csdn.net/qq_43842093…

16.JS的类和c++、java的类有什么区别?(TODO)

C++和java都是面向对象的语言,他们的类是对一类“事物”的属性与行为的抽象,是具备某些共同特征的实体的集合,它是一种抽象的数据类型,它是对所具有相同特征实体的抽象。
JS的类则是在原型链机制基础上增加的语法糖,虽然在使用上体现了强类型的特点,但是本质上和面向对象语言的类是不同的。

17.什么是暂时性死区?产生原因?

定义

当程序的控制流程在新的作用域(module function 或 block 作用域)进行实例化时,在此作用域中用let/const声明的变量会先在作用域中被创建出来,但因此时还未进行词法绑定,所以是不能被访问的,如果访问就会抛出错误。因此,在这运行流程进入作用域创建变量,到变量可以被访问之间的这一段时间,就称之为暂时死区。

产生原因

ES6新增的let、const关键字声明的变量会产生块级作用域,如果变量在当前作用域中被创建之前被创建出来,由于此时还未完成语法绑定,如果我们访问或使用该变量,就会产生暂时性死区的问题,由此我们可以得知,从变量的创建到语法绑定之间这一段空间,我们就可以理解为‘暂时性死区’。

let/const关键字未出现之前,typeof运算符是百分之百安全的,现在也会引发暂时性死区的发生,像import关键字引入公共模块、使用new class创建类的方式,也会引发暂时性死区,究其原因还是变量的声明先于使用

18.什么是变量的解构赋值?如何使用?

定义

解构赋值语法是一种 Javascript 表达式。通过解构赋值,可以将属性/值从对象/数组中取出,赋值给其他变量。

对象和数组的定义过程,就是使用一种简单的方式定义一个特定的数据组,定义的数据组结构,即为等式右侧的结构

var x = [1, 2, 3, 4, 5];
var y = {a: 10, b: 20};

解构赋值则提供了一种从原数据组中简单提取出对应变量的方法,提取时使用定义相同的语法,在表达式左边,数据组的对应位置,放置存储提取值的变量名。

var [x, y] = [1, 2, 3, 4, 5];
console.log(x, y); // 1 2
var {a: c, b: d} = {a: 10, b: 20, c: 30};
console.log(c, d); // 10 20

使用

数组解构赋值

基本用法

可以声明并赋值新变量,也可先声明,再用于解构赋值

var foo = ["one", "two", "three"];
var [one, two, three] = foo;
console.log(one, two, three); // one two three

var a, b;
[a, b] = [1, 2];
console.log(a, b); // 1 2
默认值

解构赋值提供设置默认值的写法。默认值在右侧对应位置取出值为 undefined 时生效,使用*=== undefined*判断。即为其他值时不会使用默认值,比如 nullfalse

var [a = 1, b = 2, c = 3, d = 4, e = 5, f = 6] = [1, null, false, '', undefined];
console.log(a, b, c, d, e, f); // 1 null false '' 5 6

默认值也可以为表达式,该表达式仅在使用到时才会求值。也可以引用其他变量作为默认值,但使用时需确保该变量以声明

function f() {
	console.log("computed");
}
let [x = f()] = [1]; // 无输出

let [x = y, y = 1] = []; // ReferenceError
var [x = y, y = 1] = []; 
console.log(x, y); // undefined 1

默认值可以引用其他变量,但是顺序为先解构,再触发默认值

let [x=1,y=x] = [] // x=1;y=1
let [x=1,y=x] = [2] //x=2;y=2
let [x=1,y=x] = [1,2] // x=1;y=2
let [x=y,y=1] = [] // ReferenceError: y is not defined
忽略部分值

在不需要提取的位置留出空位置,即可在该位置占位,而不提取值

var [a, , b] = [1, 2, 3];
console.log(a, b);
取出剩余值

可以使用展开语法将数组的剩余部分赋值给一个变量,这个变量为一个数组。
注意:剩余元素必须为数组最后一个元素,即后面不可以再跟逗号,否则会抛出SyntaxError

var [a, ...rest] = [1, 2, 3];
console.log(rest); // [2, 3]

var [a, ...b,] = [1, 2, 3]; // SyntaxError: rest element may not have a trailing comma
其他

只要某种数据结构具有Iterator接口,都可以采用数组形式的结构赋值

function* fib(end = Infinity) {
  let a = 0;
  let b = 1;
  let index = 0;
  while(index <= end) {
    yield b;
    [a, b] = [b, a + b];
    index++;
  }
} 
var [, x, y, z] = fib(2); // 1 2 undefined

可以对字符串进行数组解构,解构时字符串转换为数组

var [a, b, ...rest] = 'hello';
console.log(a, b, rest); // h e ['l', 'l', 'o']

对象结构赋值

基本用法
  • 解构也可用于对象,同样可以声明时赋值或先声明再赋值。先声明,再赋值时,注意括号与分号的使用。
  • 解构赋值也可以取出对象方法。
  • 同时解构对象时,用于存储的变量名与对象中原键名相同时,可简写
  • 解构时,属性名也可使用变量
var {p, q: r} = {p: 1, q: 2};
var { log } = console;
log(p, r); // 1 2

var p = 0
p = p + 1
;({p} = {p: 1, q: 2})
console.log(p) // 1

var key = 'name';
var {[key]: mName} = {name: 'Tom'};
console.log(mName); // Tom
默认值

与数组类似,解构对象时可设置默认值,在简写和非简写情况下,写法如下

var {a = 5, b: bb = 10} = {p: 1, q: 2};
console.log(a, bb); // 5 10
查找原型链属性

解构对象时,若属性不存在,则会沿原型链继续查找

var obj = {self: 'me'};
obj.__proto__.prot = 'proto';
var {self, prot} = obj;
console.log(self, prot); // me proto
对其他类型进行解构

使用其他基本数据类型时,若数据能转化为对象,则也可使用对象的结构赋值。

numberboolean类型可以解构,undefinednull则不能

var { toString: s1 } = Infinity;
var { toString: s2} = false;
console.log(s1 === Number.prototype.toString, s2 === Boolean.prototype.toString); // true true
var { prop: x } = undefined; // TypeError
var { prop: y } = null; // TypeError

数组作为一种特殊的对象,也可使用对象的解构,将序号作为属性名

var arr = [0, 1, 2];
var {1: one, 4: four, length} = arr;
console.log(one, four, length); // 1 undefined 3

嵌套解构

数组和对象可多层嵌套解构,但在嵌套时,要保证提取属性的父属性是存在的,否则会报错

var metadata = {
  title: "Scratchpad",
  translations: [
    {
      locale: "de",
      localization_tags: [],
      last_edit: "2014-04-14T08:43:37",
      url: "/de/docs/Tools/Scratchpad",
      title: "JavaScript-Umgebung",
    },
  ],
  url: "/en-US/docs/Tools/Scratchpad",
};

var {
  title: englishTitle,
  translations: [
    {
      title,
    },
  ],
} = metadata;
console.log(englishTitle, title); // Scratchpad JavaScript-Umgebung

var { translations: [ , { title }]} = metadata; // TypeError

19.讲一下Symbol的概念及作用

概念

symbol 是一种基本数据类型 (primitive data type)。Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:“new Symbol()”。

作用

  1. 使用Symbol来替代常量,保证了常量的唯一性,避免不必要的错误
  2. 使用Symbol来作为对象属性名(key), 避免重名冲突,Symbol类型的key是不能通过Object.keys()或者for...in来枚举的,它未被包含在对象自身的属性名集合(property names)之中。所以,利用该特性,我们可以把一些不需要对外操作和访问的属性使用Symbol来定义
  3. 具有不能枚举的特性,当使用JSON.stringify()将对象转换成JSON字符串的时候,Symbol属性也会被排除在输出内容之外

用法

1.可以在调用Symbol()函数时传入一个可选的字符串参数,相当于给你创建的Symbol实例一个描述信息

//创建Symbol
let s = Symbol(); //函数
console.log(s, typeof s); //23-Symbol的基本使用.html:15 Symbol() 'symbol'
let str = 'another symbol';
let s1 = Symbol(str); //相当于let s1 = Symbol('another symbol')
let s2 = Symbol('YY');
let s3 = Symbol('YY');
console.log(s1, s2,s3); //Symbol(another symbol) Symbol(YY) Symbol(YY)
console.log(s2 == s3); //false

2.Symbol.for() 不管在哪里调用,都会被注册登记到全局环境。

Symbol为我们提供了一个方法:Symbol.for(),它接收一个字符串作为参数(可选),然后会在全局中搜索有没有以该参数作为描述的Symbol值,如果有则直接返回该Symbol,否则将以该参数作为描述创建一个新的Symbol值,并将其注册的全局环境供搜索(另外: Symbol.for() 在创建Symbol值时是登记在全局环境中的, 不管有没有在全局环境运行)。

let s4 = Symbol.for('SXL'); //函数对象
let s5 = Symbol.for('SXL'); 
let s6 = Symbol.for();
console.log(s4, s5, s6); //Symbol(SXL) Symbol(SXL) Symbol(undefined)
console.log(s4 == s5); //true
console.log(s4 === s5); //true
console.log(s4 == s6); //false

Symbol.keyFor(sym) 方法用来获取全局symbol 注册表中与某个 symbol 关联的键。 如果全局注册表中查找到该symbol,则返回该symbol的key值,返回值为字符串类型。否则返回undefined

// 创建一个全局 Symbol
var globalSymbol = Symbol.for("global");
console.log(typeof Symbol.keyFor(globalSymbol), Symbol.keyFor(globalSymbol)); //string global
let sy = Symbol('yy')
console.log(Symbol.keyFor(sy)); //undefined

3.不能与其他数据进行运算或者是隐式转换

let s7 = Symbol();
let re1 = s7 + 100; // Cannot convert a Symbol value to a number
let re2 = s7 > 100; // Cannot convert a Symbol value to a number
let re3 = s7 + ''; // Cannot convert a Symbol value to a string
let re4 = s7 + s7; // Cannot convert a Symbol value to a number

4.toString()、String()、Boolean()可以进行强制转换

let s8 = Symbol('symbol');
console.log(typeof s8.toString(), s8.toString()); //string Symbol(symbol)
console.log(typeof String(s8), String(s8)); //string Symbol(symbol)
console.log(typeof Boolean(s8), Boolean(s8)); //boolean true

5.Symbol.hasInstance

对象的Symbol.hasInstance属性,指向一个内部方法。当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。比如,obj instanceof Obj在语言内部,实际调用的是ObjSymbol.hasInstance

class Person {}
var person = new Person()
console.log(person instanceof Person); // true
console.log(Person[Symbol.hasInstance](person));//true

重写Symbol.hasInstance方法,实际是重写了instanceof方法

class Person {
    constructor(age) {
        this.age = age;
    };
    static[Symbol.hasInstance](param) {
        return param % 2 === 0; //自己控制类型检测   true|false
    }
}
let person = new Person('2');
console.log(person instanceof Person);
console.log(person.age instanceof Person);
console.log(2 instanceof Person);
console.log(Person[Symbol.hasInstance](person));
console.log(Person[Symbol.hasInstance](person.age));
console.log(Person[Symbol.hasInstance](2));

6.Symbol.split、Symbol.match、Symbol.replace、Symbol.search(四个字符串方法)

Symbol.split方法对此构造函数原型里的split方法进行重写,故只在此构造函数中生效

function Split() {}
Split.prototype[Symbol.split] = function(string) {
    return string
}
let split = new Split();
console.log(Split.prototype[Symbol.split]('aaa')); //aaa
console.log(split[Symbol.split]('aaa')); //aaa
console.log('str'.split(split)); //str
console.log('str'.split('t')); //['s', 'r']

Symbol.match方法对此构造函数原型里的split方法进行重写,故只在此构造函数中生效

class Match {
    [Symbol.match](string) {
        return [...string]
    }
}
let match = new Match();
console.log(Match.prototype[Symbol.match]('aaa')); //['a', 'a', 'a']
console.log(match[Symbol.match]('aaa')); //['a', 'a', 'a']
console.log('str'.match(match)); //['s', 't', 'r']
console.log('str'.match('t')); //['t', index: 1, input: 'str', groups: undefined]

Symbol.replace、Symbol.search也是同理

7 Symbol.isConcatSpreadable作为内置属性用于配置某对象作为Array.prototype.concat()方法的参数时是否展开其数组元素

//是否可以展开
//如果对象有长度和数字键,并在参与concat,调用时分离出值
let obj = {
    0: 'yy',
    1: 21,
    length: 2
}
obj[Symbol.isConcatSpreadable] = true;
console.log([].concat(obj)); // ['yy', 21]

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1[Symbol.isConcatSpreadable] = true;
arr2[Symbol.isConcatSpreadable] = false;
console.log(arr1.concat(arr2)); // [1, 2, 3, Array(3)]

8.Symbol.toPrimitive是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。(param参数的取值是 “number”、“string” 和 “default” 中的任意一个)

const obj1 = {
    [Symbol.toPrimitive](param) {
        if (param === 'string') {
            return 'string-' + param;
        } else if (param === 'number') {
            return 123;
        } else if (param === 'default') {
            return undefined;
        }
    }
};


console.log(JSON.stringify(obj1)); //{}
console.log(obj1.toString()); //[object Object]
console.log(String(obj1)); //string-string

console.log(Number(obj1)); //123
console.log(parseInt(obj1)); //NaN
console.log(parseFloat(obj1)); //NaN
console.log(+obj1); //123
console.log(-obj1); //-123

console.log(obj1 + ''); //undefined

console.log(Boolean(obj1)); //true
console.log(obj1 == 1); //false

19.讲一下Generator及其异步方面的应用

概念

在Javascript中,一个函数一旦开始执行,就会运行到最后或遇到return时结束,运行期间不会有其它代码能够打断它,也不能从外部再传入值到函数体内而Generator函数(生成器)的出现使得打破函数的完整运行成为了可能,其语法行为与传统函数完全不同。

Generator函数是ES6提供的一种异步编程解决方案,形式上也是一个普通函数,但有几个显著的特征:

  • function关键字与函数名之间有一个星号 "*" (推荐紧挨着function关键字)
  • 函数体内使用 yield 表达式,定义不同的内部状态 (可以有多个yield)
  • 直接调用 Generator函数并不会执行,也不会返回运行结果,而是返回一个遍历器对象(Iterator Object)
  • 依次调用遍历器对象的next方法,遍历 Generator函数内部的每一个状态

实现异步

Generator函数非常适合将异步任务同步化

function ajax(url) {
    return new Promise(function(resolve, reject){
        var xhr = new XMLHttpRequest();
        xhr.open('GET',url);
        xhr.responseType = 'json';
        xhr.onload = function() {
            if(this.status == 200) {
                resolve(this.response);
            } else {
                reject(this.statusText);
            }
        }
        xhr.send();

    })
}

const foo = function * () {
    let result1  = yield ajax("/api/book.json");
    console.log(result1);

    let result2  = yield ajax("/api/user.json");
    console.log(result2);
};

const generator = foo();
generator.next().value.then(result =>{
    return generator.next(result).value
}).then(result =>{
    return generator.next(result).value
})

20.class的基本语法与继承

  1. 在类的实例上调用方法,实际上就是调用原型上的方法。

  2. constructor方法是类的默认方法,通过new命令生成对象实例时自动调用该方法。一个类必须有constructor,若没有显示定义,会默认添加。

  3. 类和模块的内部默认使用严格模式。

  4. 类必须使用new来调用,否则会报错。

  5. 类不存在变量提升

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

  7. 类的name属性就是紧跟在Class关键字后面的类名。

  8. 类的内部可以用get和set关键字对某个属性设置存值函数和取值函数,拦截该属性的存取行为。(存值函数和取值函数是定义在HTML属性的描述对象(Descriptor)上面)

  9. 静态方法(有static关键字的函数)不会被实例继承,而是直接通过类调用。

  10. 父类的静态方法可以被子类继承。静态方法可以从super对象上调用。

  11. 静态属性指Class本身的属性,即Class.propname,而不是定义在实例对象上的属性。

  12. new.target属性可用于确定构造函数是怎么调用的。Class内部调用new.target返回当前Class,子类继承父类时new.target会返回子类,在外部使用new.target会报错。

  13. Class可以通过extends关键字实现继承。super作为函数调用时代表父类的构造函数。 子类的构造函数必须执行一次super函数(返回父类实例)。
    ES6的继承机制:先创建父类的实例对象this(所以必须调用super函数),然后再用子类的构造函数修改this。
    ES5的继承机制:先创造子类的实例对象this,然后再将父类的方法添加到this上面。super()等价于父类.prototype.constructor.call(this),Object.getPrototypeOf方法可以用来从子类上获取父类。

  14. super()只能用在子类的构造函数中。super作为对象时在普通方法中指向父类的原型对象;在静态方法中指向父类,无法访问父类实例上的方法或属性。通过super调用父类的方法时,super会绑定子类this。

21.ESModule和CommonJS的加载原理

CommonJS

CommonJS是在运行时加载模块,CommonJS 模块就是对象,模块输出的是对象的一个拷贝,通过module.exports命令导出对象,通过require命令导入对象,输入时必须查找对象属性。

ESModule

ESModule是使用静态化的设计思想,在编译时就能确定模块的依赖关系,以及输入和输出的变量。通过export命令导出,import命令导入。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。

22.说一下ES6增加的数组API

静态方法

  • Array.of(…args): 使用指定的数组项创建一个新数组
  • Array.from(arg): 通过给定的类数组 或 可迭代对象 创建一个新的数组

实例方法

  • find(callback): 用于查找满足条件的第一个元素
  • findIndex(callback):用于查找满足条件的第一个元素的下标
  • fill(data):用指定的数据填充满数组所有的内容
  • copyWithin(target, start?, end?): 在数组内部完成复制
  • includes(data):判断数组中是否包含某个值