前言

上篇博客,咱们理解了javascript函数执行过程,本篇章次要讲述函数,如参数、回调、同/异步、箭头函数等,并论述了对ES6中class类的意识以及继承。

面试答复

1.ES6新个性:新增了一种值类型Symbol以及Set和Map的数据结构。减少了块级作用域,比方let,const。减少了变量的解构赋值。新增了扩大运算符。提供了类的语法糖class。新增了箭头函数。函数参数容许设置默认值,引入了rest参数,新增了一些API,比方isArray、keys()。

2.异步编程史:异步编程有四个阶段,别离是回调函数、Promise、Generator、async/await,回调函数的毛病在于容易呈现回调天堂,不能用try/catch捕捉谬误,不能return。Promise构造函数内的代码是同步执行,then办法中的代码是异步执行的,能够通过构造函数的参数reslove进入then回调中,不过毛病是无奈勾销,谬误须要回调来捕捉。Generator能够用来管制函数,比方暂停、复原执行。async/await是Promise的语法糖,它是为优化then链而开发进去的,解决了then链过长的问题,await的性能根本等同于then,它能实现的成果都能用then来实现,只不过then办法是微工作,属于异步工作,而await后的办法等同于Promise的构造函数,这个办法之后的代码等同于then回调。

3.箭头函数:箭头函数没有原型,所以不能作为构造函数。它的this指向为以后所在环境的this,且箭头函数不反对重命名参数,也没有arguments对象。

4.闭包:闭包可能使外部函数读取内部函数的变量,让变量长期驻扎在内存当中,从而让该变量不被垃圾回收机制回收,当不再须要闭包时,把外部函数赋值为null即可。益处是可能防止因为作用域问题而把变量定义在全局作用域外面,造成全局变量的净化。闭包的原理是利用了函数作用域链的个性,外部函数会将内部函数的流动对象增加到作用域链里,当内部函数执行结束,作用域链会被销毁,但流动对象因为仍被外部函数的作用域链援用,所以不会被销毁。当初闭包很少用到,以前的话会用闭包解决封装模块以及循环中作用域的问题。

5.手写Promise:Promise有状态、构造函数、then办法、catch办法、finally办法、all等办法,首先定义三个状态,pending、resolved、rejected以及一些变量,比方status、data、reason、callback用来贮存状态、resolve返回、reject返回和then办法的回调。而后实现构造函数以及reject、resolved两个办法,构造函数个别用try/catch办法间接接管或抛出,reject、resolved这两个办法是创立实例的时候作为构造函数的参数传入,且两个办法都蕴含对状态的判断以及批改,并且解决回调。接着实现then办法,因为then办法是微工作且最初会返回一个Promise,所以咱们要new一个新实例而后判断状态,再执行queueMicrotask办法,最初就是一些catch办法、finally等办法的解决,这两个都能够通过间接返回之前实现的then办法实现,只不过finally不接管参数。Promise的all办法,同样最初会返回一个Promise,不同的是Promise.all办法接管的参数是数组,可能蕴含多个申请,须要用let...of...对参数进行遍历,遍历后判断它是否为Promise对象,如果是则间接调用then办法,如果不是则将参数保留到后果变量中,最初一起返回这个后果变量。

6.闭包:闭包可能使外部函数读取内部函数的变量,让变量长期驻扎在内存当中,从而让该变量不被垃圾回收机制回收,当不再须要闭包时,把外部函数赋值为null即可。益处是可能防止因为作用域问题而把变量定义在全局作用域外面,造成全局变量的净化。闭包的原理是利用了函数作用域链的个性,外部函数会将内部函数的流动对象增加到作用域链里,当内部函数执行结束,作用域链会被销毁,但流动对象因为仍被外部函数的作用域链援用,所以不会被销毁。当初闭包很少用到,比拟典型的就是vue中的data数据,以前的话会用闭包解决封装模块以及循环中作用域的问题。

知识点

1.参数

JavaScript 函数有个内置的对象,arguments对象,argument对象蕴含了函数调用时的由所有参数组成的数组,arguments对象能够利用于函数调用时参数太多(超过申明)的情景。

function test() {    let arg = arguments    if(arg.length !== 0) console.log(arg)}test(1, 123, 500, 115, 44, 88)

参数默认值

function test(x, y = 10) {    return x + y;} test(0, 2)    // 2test(5)     //15

rest参数(...)

function test(a, ...b) {    console.log(a)    console.log(b)} test(1, 2, 3, 4, 5)//1//[ 2, 3, 4 ,5]

2.Generator函数

yield是JS为了解决异步调用的命令。示意程序执行到这里会交出执行权,期待后果返回。它须要在Generator函数中运行,与一般function的区别就是函数名后面多了一个星号*,这里只做最简略的用法,有趣味的同学能够持续钻研~

function *generatorForLoop(num) {  for (let i = 0; i < num; i += 1) {    yield console.log(i);  }}const genForLoop = generatorForLoop(5);genForLoop.next(); // 0genForLoop.next(); // 1genForLoop.next(); // 2genForLoop.next(); // 3genForLoop.next(); // 4

3.箭头函数

箭头函数没有原型,自身没有this,在箭头函数中应用this,它的指向为以后所在环境的this,所以不能够当作构造函数,new一个箭头函数也会抛出一个谬误。箭头函数不反对重命名函数参数,对于箭头函数的this指向问题曾经在上一篇博客中了解过了,这边就不着重形容了,咱们分为部分函数环境、全局环境来看。

全局环境

全局环境下,箭头函数的this都会指向window,应用aguments会报错,举例:

let foo = ()=>{console.log(this)};foo(); //window

部分函数环境

依据上一篇this指向的了解,在部分函数环境,箭头函数的this指向它的外层一般函数时,它的arguments指向外层一般函数的arguments,取而代之用rest参数...。箭头函数自身的this指向不能扭转,也不能应用call、apply去扭转,但扭转它所在的上下文(也就是外层函数)的this指向,举例:

//状况一:上面的箭头函数this的指向是function b (固定的),而function b的this指向依据一般函数的this指向规定,是指向调用它的对象,也就是obj,所以箭头函数--> function b --> obj,也就是箭头函数的this指向,指向obj,obj.a=2var a = 1var obj = {    a:2,    b:function(){        return ()=>{            console.log(this.a)        }    }}obj.b()()    //2//状况二:这时候想要扭转箭头函数this指向,能够在箭头函数外包一层匿名函数即可(匿名函数默认指向window),所以对于箭头函数的this指向在创立阶段确定,且无奈扭转的说法是没问题的。var a = 1var obj = {    a:2,    b:function(){        return function(){            return ()=>{                console.log(this.a)            }        }         }}obj.b()()()    //1   obj.b()等于return的function,而obj.b()()等于调用return的function即window.b(),所以指向window//状况三:或者间接应用call扭转外层函数的this指向var a = 1var obj = {    a:2,    b:function(){        return ()=>{            console.log(this.a)        }    }}obj.b.call(window)()    //1

4.class类

先来看一下ES5的类,

function Animal(word,food){    this.word = word     this.food = food    this.eat = function(){        console.log('I eat',this.food)    }}

再看一下ES6残缺的例子,记得有疑难的中央要标记一下哦:

class Animal {    //#privateAnimal = 'Animal Animal'                    //参考A.1.公有成员    constructor(word, food) {                            //参考A.2.构造函数        this.word = word        this.food = food    }    static say(word){                                    //参考A.1.动态成员        console.log(word)    }    eat(){                                                //参考A.1.私有成员        console.log('I eat',this.food)    }    testSuper(){                                        //参考A.2.构造函数super        console.log('super')    }    //#privateCon(){                                    //参考A.1.公有成员        //console.log(this.#privateCat)    //}}class Cat extends Animal {                                //参考A.4.继承    constructor(word, food) {        super()                                            //参考A.2.构造函数super        this.word = word        this.food = food    }    ['play'+'bool'](){                                    //参考A.3.计算属性名称        console.log('Cat play bool')    }                                                        //get、set 参考A.3.拜访器属性    get age(){        return 'getter'    }    set age(val){        console.log('setter',val)    }    getSuper(){                                            //参考A.2.构造函数super            console.log(super.testSuper())    }}//参考A.1.动态成员Animal.say('Hello Animal')Cat.say('Hello Cat')//参考A.1.公有成员//Animal.#privateCon()    // error,不可拜访let animal = new Animal('Hello Animal','meet');animal.eat()let cat = new Cat('Hello Cat','fish');cat.eat()cat.playbool()//参考A.2.构造函数super    cat.getSuper()//get、set 参考A.2.拜访器属性cat.age = 3cat.age

A.1.根底概念

成员:成员分为公有成员、私有成员以及动态成员,无论属性还是函数只有加上关键字都能够成为对应的成员。

公有成员:关键字#,公有成员有以下几个特点,

  • class外部不同办法间能够应用,因而this要指向实例化对象;
  • 不能被内部拜访,因而实例化对象既不能取得值,也不能设定值;
  • 不能被继承,因而extends后子类不具备该属性

动态成员:是指在办法名或属性名后面加上static关键字,和一般办法不一样的是,静态方法不能在实例中拜访,只能在类中拜访;动态成员也能够通过派生类拜访,但不能通过派生类的实例拜访,如果静态方法蕴含this关键字,这个this指的是类,而不是实例。

私有成员:除去公有成员,动态成员,其余成员属于私有成员。

派生类:通过extends关键字来实现继承的性能,如Animal是Cat的基类,Cat是Animal 的派生类,Cat继承了Animal的根本能力,在派生类中定义重名函数会笼罩掉基类中的原始函数。

原型链:之前曾经对原型链的概念进行了解,Class类同时有prototype属性和__proto__属性,因而同时存在两条继承链。 大抵了解如下:

1.子类实例(cat)的__proto__属性,总是指向cat原型,而cat原型的__proto__属性总是指向父类(Animal)的原型,父类实例(animal)的__proto__属性则也是指向他本身的原型。也就是说,cat.__proto__.__proto__===animal.__proto__。子类实例的原型的原型,是父类实例的原型。

2.子类(Cat)prototype属性指向的是他本身的原型,而后子类(Cat)原型的原型又是指向父类的原型,也就是说,Cat.prototype.__proto__=== Animal.prototype,即总是指向父类(Animal)的prototype属性。

如若还有疑难,能够参考博客文章四。

A.2.构造函数

Cat类中能够看到有一个constructor办法,这个就是构造函数。constructor办法是类的默认办法,通过new命令生成对象实例时,主动调用该办法。一个类必须有constructor办法,如果没有定义,constructor办法会被默认增加。

在派生类中,如果应用了构造方法,且用到了this,就必须应用super(),且super()也只能在派生类中应用,在构造函数中拜访之前肯定要调用super(),它此时代表父类的构造函数,负责this的初始化。super作为对象时,在一般办法中,指向父类的原型对象;在静态方法中,指向父类。

A.3.函数

拜访器属性:类反对在原型上定义拜访器属性。只管应该在类的构造函数中创立本人的属性,然而类也反对间接在原型上定义拜访器属性。创立getter时,须要在关键字get后紧跟一个空格和响应的标识符;创立setter时,只需把关键字get替换为set即可。

计算属性名称:类和对象字面量有很多相似之处,类办法和拜访器属性也反对应用可计算名称,用方括号包裹一个表达式,即应用计算名称。

A.4.继承

extends关键字后只有是一个有prototype属性的函数,就能被继承,至于原型链相干的知识点这里就不赘述了,不分明的同学能够参考博客四,这里顺便聊一下JS中常见的继承形式。

原型链继承:父类的实例作为子类的原型,长处是简略易于实现,父类的新增的实例与属性子类都能拜访;毛病是能够在子类中减少实例属性,如果要新减少原型属性和办法须要在new父类构造函数的前面,无奈实现多继承,创立子类实例时,不能向父类构造函数中传参数。

function Cat(){ }Cat.prototype= new Animal()let Cat = new Cat()

结构继承:复制父类的实例属性给子类,长处是解决了子类构造函数向父类构造函数中传递参数,能够实现多继承(call或者apply多个父类);毛病是办法都在构造函数中定义,无奈复用,不能继承原型属性/办法,只能继承父类的实例属性和办法。

function Cat(){ //继承了Animal  Animal.call(this)}let cat = new Cat();

组合继承(原型链+构造函数):调用父类构造函数,继承父类的属性,通过将父类实例作为子类原型,实现函数复用。长处是函数能够复用,不存在援用属性问题,能够继承属性和办法,并且能够继承原型的属性和办法;毛病是因为调用了两次父类,所以产生了两份实例。

function Aniaml(food){  this.food = food}Aniaml.prototype.eat = function(){  return this.food}function Cat(food){  Aniaml.call(this,food)}Cat.prototype = new Aniaml();Cat.prototype.constructor = Cat;let cat = new Cat('fish');cat.eat(); 

寄生组合继承:通过寄生的形式来修复组合式继承的有余,完满的实现继承

function Animal(food,age){  this.food = food || ''}Animal.prototype.eat = function(){  return this.food}function Cat(food){  //继承父类属性  Animal.call(food)}//继承父类办法(function(){  // 创立空类  let super = function(){}  super.prototype = Animal.prototype  //父类的实例作为子类的原型  Cat.prototype = new super()})();//修复构造函数指向问题Cat.prototype.constructor = Catlet cat = new Cat()

实例继承:长处是不限度调用形式,简略,易实现;毛病是不能屡次继承 。

function Cat(){  let animal = new Animal()  return animal}let cat = new Cat()

ES6继承形式:最优

class Animal{  constructor(food=''){    this.food = food  }  eat(){    console.log(this.food)  }}//继承父类class Cat extends Animal{    constructor(food = 'meet'){      //继承父类属性     super(food);   }     eat(){      //继承父类办法      super.eat()     } } let cat=new Cat('fish'); cat.eat();

5.Promise

Promise构造函数内是同步工作,then办法中属于异步工作的微工作。Promise的状态有三个值:pending、resolve、rejected,且Promise的状态只会产生一次扭转。接下来,咱们先看一下Promise的根底用法:

new Promise((reslove,reject)=>{    console.log(1)    reslove(2)//reslove后会进入then    reject(3)//reject后会进入catch,但Promise的状态只会扭转一次,所以这行不会执行,然而上面那行还是会打印。    console.log(4)}).then(res=>{  console.log(res)}).catch(error=>{    console.log(error)})

Promise.all():用于屡次申请,不过如果呈现一个接口申请异样,整个Promise.all就会被认定为失败,进入catch回调。

//getData1,getData2,getData3别离为三个接口申请Promise.all([getData1,getData2,getData3]).then(res=>{    const [res1,res2,res3] = res    //用到了解构数组})

Promise.race():与Promise.all()返回全副数据不同,Promise.race()下面代码中,只有有一个实例率先扭转状态,状态就跟着扭转,返回的为率先扭转的Promise实例的返回值。

const a = new Promise((resolve,reject) => {    setTimeout(() => {        console.log("a")        resolve(1)    },1000)})const b = new Promise((resolve,reject) => {    setTimeout(() => {        console.log("b")        resolve(2)    },2000)})Promise.race([a,b]).then(v => {    console.log(v)})// 输入:a1b

1.Promise执行程序

先看一下常见的Promise所对应的面试题来理解执行程序:

console.log(1)setTimeout(()=>{   console.log(6) },0)new Promise((reslove,reject)=>{    console.log(2)    reslove('zxp')    //reject(2)}).then(res=>{    console.log(3)    console.log(res)}).then(res=>{    console.log(4)}).catch(err=>{    console.log(5)//没报错,不打印    console.log(err)})setTimeout(()=>{   console.log(7) },0)//1 2 3 zxp 4 6 7

2.手写promise

手写Promise是高频面试题,特地是在口试题中,同时通过手写Promise也有助于咱们进一步了解意识Promise,参考资料:https://juejin.cn/post/7175516630972104760#heading-6

class myPromise {    constructor(func) {        //1.定义状态以及正确、异样的返回及回调,状态对应pending、fulfilled、rejected        this.state = 'pending'        this.data = undefined        this.reason = undefined        this.resolveCallbacks = []        this.rejectCallbacks = []        // 2.实现构造函数,并传入自定义的resolve、reject        try {            func(this.resolve, this.reject)        } catch (e) {            this.reject(e)        }              }    //3.实现reslove和reject的,两者均是先判断是否为pending状态,而后更改为对应状态、赋值返回、执行所有的回调(能够通过while+数组长度+shift来实现),不同的是返回和回调不同。    resolve = (data) => {        // 一旦状态扭转,就不能再变        if (this.state === 'pending') {            this.state = 'fulfilled'            this.data = data            // 顺次调用胜利回调,直到successCallback为空            while (this.resolveCallbacks.length) {                // 删除successCallback第一个数组元素,并执行这个被删除的元素,并传入参数。(arr.shift()返回值为被删除的元素)                this.resolveCallbacks.shift()()            }        }    }    reject = (reason) => {        if (this.state === 'pending') {            this.state = 'rejected'            this.reason = reason            while (this.rejectCallbacks.length) {                this.rejectCallbacks.shift()()            }        }    }    //4.实现then回调:判断参数是否为函数,返回新的Promise来反对链式调用,判断状态后,用queueMicrotask办法实现微工作的执行,执行内容为调用传入的resolve或reject,而后调用resolvePromise办法。    then(onResolve, onRejected) {        //判断是否为函数,若不是则增加默认函数        onResolve = typeof onResolve === 'function' ? onResolve : value => value        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }        //返回一个新的promise,来反对链式调用。        const newPromise = new myPromise((resolve, reject) => {            if (this.state === 'fulfilled') {                queueMicrotask(() => {                    try {                        const value = onResolve(this.data)                        //传入的参数别离代表,最新的Promise,最新的值,最新的resolve,最新的reject                        this.resolvePromise(newPromise, value, resolve, reject)                    } catch (e) {                        reject(e)                    }                })            } else if (this.state === 'rejected') {                queueMicrotask(() => {                    try {                        const value = onRejected(this.reason)                        this.resolvePromise(newPromise, value, resolve, reject)                    } catch (e) {                        reject(e)                    }                })            } else if (this.state === 'pending') {                // 首次进入then办法,将回调函数存入数组                this.resolveCallbacks.push(() => {                    queueMicrotask(() => {                        try {                            const value = onResolve(this.data)                            this.resolvePromise(newPromise, value, resolve, reject)                        } catch (e) {                            reject(e)                        }                    })                })            }        })        return newPromise    }        //5.实现Promise解决程序:为了合乎Promise A+的规定    //传入的参数别离代表,最新的Promise,最新的值,最新的resolve,最新的reject    resolvePromise = (newPromise, value, resolve, reject) => {        //这里有三种状况:        //1.如果value和promise是同一个对象,则调用reject办法,其参数为new TypeError()        //2.如果value是对象或函数,且then是一个函数,新的reslove和reject函数作为参数。        //  如果value.then不是函数,就间接调用resolve办法解决Promise,参数为value        //3.如果以上两种情况都没有呈现,调用resolve办法解决Promise,其参数为value        if (value === newPromise) {            // 循环调用报错            reject(new TypeError())        } else if ((value !== null && typeof value === 'object') || typeof value === 'function') {            if (typeof value.then === 'function') {                value.then(                    (newValue) => {                        resolvePromise(newPromise, newValue, resolve, reject)                    },                    (r) => {                        reject(r)                    }                )            } else {                resolve(value)            }        } else {            resolve(value)        }    }        //6.实现catch,catch函数只是没有给fulfilled状态预留参数地位的then办法    catch (onRejected) {        return this.then(undefined, onRejected);    }    //8.实现Promise.resolve()办法,如果是promise就间接返回value,否则就new一个promise,默认resolve传入value    static resolve (value) {        return value instanceof myPromise          ? value          : new myPromise(resolve => resolve(value));    }    //9.实现Promise.reject()办法    static reject (reason) {        return new myPromise ((resolve, reject) => {          reject(reason);        });    }    //10.实现finally,相当于最初再执行then办法去调用静态方法reslove    finally (callback) {        return this.then (          data => {            return myPromise.resolve(callback().then (() => data));          },          err => {            return myPromise.resolve(callback()).then (() => {              throw err;            });          }        );    }    //11.实现all办法    static all(promises) {        return new myPromise((resolve, reject) => {            let times = 0; // 记录执行次数            let result = []; // 保留执行后果            function addData (key, value) {                // 记录后果                times++;                result[key] = value;                times === promises.length && resolve (result);            }                promises.forEach((element, index) => {                if (element instanceof myPromise) {                     //promises数组中的元素是promise                    element.then(                        value => addData(index,data),                        err => reject(err)                    )                } else {                    addData(index,element)                }            })        })    }    }new myPromise((resolve,reject)=>{    console.log(1)    resolve(3)    console.log(2)}).then(res=>{    console.log(res)}).then(res=>{    debugger    console.log(res)    console.log(4)})

6.async/await

async/await其实是Promise的语法糖,它是为优化then链而开发进去的。它能实现的成果都能用then来实现, 能够视await前面的办法为Promise的构造函数,之后的代码等同于then回调。

async的用法,它作为一个关键字放到函数后面,示意函数是一个异步函数,async函数返回的是一个promise对象,能够应用then办法增加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作实现,再接着执行函数体内前面的语句。

await 前面应该写一个Promise对象,如果不是Promise对象,那么会被转成一个立刻resolve的Promise(Promise.resolve())。 await 命令前面的Promise对象,运行后果可能是rejected,所以最好把await命令放在 try...catch 代码块中。

样例:

//函数申明async function test(){}//函数表达式const res = async function(){}//箭头函数const res = async ()=>{}

次要利用场景:

样例1:须要期待接口一步步执行async function getData(){    try{        let res1= await getA()    //getA为Promise,是一个接口申请        if(res1){            let res2= await getB()    //getB为Promise,是一个接口申请            return res2        }    }catch(e){        console.log(e)    }}样例2:只须要单个接口期待,其余接口能够异步async function getData(){     getA().then(async (res)=>{         try{            //须要res2数据,能力继续执行             let res2= await getB()    //getB为Promise,是一个接口申请             console.log(res)         }catch(e){            console.log(e)         }     })}

7.闭包

在JavaScript高级程序设计(第3版)中是这样形容闭包:闭包是指有权拜访另一个函数作用域中的变量的函数。

闭包作用:闭包既能够短暂的保留变量又不会造成全局净化。

闭包毛病:占用更多内存;不容易被开释,不会随着函数的完结而主动销毁。

闭包用法:

1、定义外层函数,封装被爱护的局部变量。

2、定义内层函数,执行对外部函数变量的操作。

3、外层函数返回内层函数的对象,并且外层函数被调用,后果保留在一个全局的变量中。

function outTest(){    let a = 1    return function insideTest(){        a++        return a    }}outTest()()//outTest()的执行后果返回的是insideTest函数,咱们须要再加一个()执行insideTest函数//换个调用形式,后果就不一样了var func = outTest()outTest()    //第一次后果都是2雷同,而这种调用形式每次+1,而下面的形式始终不变,这是因为闭包中的变量没有被开释的起因。

8.递归

简略来说,所谓的递归函数就是在函数体内调用n次本函数,直到达到某个条件从而进行。

递归利用的场景须要具备以下三种因素:

1、存在递归终止条件:递归进口

2、一个问题能够合成为更小的问题用同样的办法解决:递归表达式(法则)

3、合成后的子问题求解形式一样,不同的是数据规模变小

//斐波那切数列:本身等于前两项之和,即n = (n-1) +(n-2)//1、1、2、3、5、8、13function fibonacci(n){    if(n<=2){        return 1    }    return fibonacci(n-1)+fibonacci(n-2)}console.log(fibonacci(6))

9.高阶函数

高阶函数是指至多满足下列条件之一的函数:

1、函数能够作为参数被传递

2、函数能够作为返回值输入。

所以一个函数就能够接管另一个函数作为参数,这种函数就称之为高阶函数。

最罕用的高阶函数,即咱们日常接口所用的回调或者是对数据的应用,比方map、reduce、filter、sort 或如下:

//假如getData是一个接口申请getData().then(res=>{    console.log(res)})getData().then(function(res){    console.log(res)})const arr = [1,2,3]const arr2 = arr.map(item=>item*2)const arr3 = arr.map(function(item){    return item+10})

最初

走过路过,不要错过,点赞、珍藏、评论三连~