Javascript知识零散学习(面试篇)

Promise

首先来说一下应该明白Promise是做什么的,JS是一门典型的异步单线程的编程语言,单线程就不说了,异步如何理解呢?异步编程可以理解成在执行指令之后不能马上得到结果,而是继续执行后面的指令,等到特定的事件触发之后才得到想要的结果。
也可以和同步对比理解,同步就是一步步执行指令,而异步在遇到特殊任务(异步任务)时,并不会等待该任务执行完成之后再去执行下一步,而是跳过等待,先去执行下一步任务,等到某个时机该特殊任务(异步任务)执行完成之后再返回来对其进行处理。
比如:

console.log(1);
setTimeout(()=>{
  console.log(2)
},1000)
console.log(3);

该段代码不会按照顺序输出1,2,3而是先输出1,3之后再输出2。你可能会说,最后输出2是因为它是延时1s之后才输出的。那么请看以下代码:

console.log(1);
setTimeout(()=>{
  console.log(2)
},0)
console.log(3);

以上代码输出顺序依旧是1,3,2。实际上并不是因为延时导致的2最后输出而是因为这就是一个典型的异步任务,它是在同步任务之后才去执行的。

说起异步肯定首先会想到ajax,因为ajax天生就是异步操作,先看ajax伪代码

var xhr = new XMLHttpRequest();
xhr.open(method,url);
xhr.send();
xhr.onreadystatechange = function(){
  if(xhr.readyState == 4 && xhr.status == 200){
    document.getElementById('app').innerHTML = xhr.responseText
  }
}

由于ajax是异步操作,所以我们只能将从服务器获取数据之后再渲染页面的整个过程放在ajax请求成功之后监听函数onreadystatechange里,这样做有些糟糕,会导致代码比较乱。

因此有一种操作对于异步编程来说比较优雅就是回调函数

还拿ajax举例:

//简易封装ajax
function getAppJson(cb){
  var xhr = new XMLHttpRequest();
  xhr.open(method,url);
  xhr.send();
  xhr.onreadystatechange = function(){
    if(xhr.readyState == 4 && xhr.status == 200){
      cb(responseText)
    }
  }
}
//回调函数
function initHtml(val){
  document.getElementById('app').innerHTML = val
}
// 将initHtml函数作为参数传入getAppJson中并在xhr.onreadystatechange 方法中执行并把后台所得数据作为参数传递给initHtml;
getAppJson(initHtml);

上述代码使用回调函数优雅的解决了ajax异步操作。

上述代码简化之后就是下面这个样子的:

function app(cb){
    cb(1)
}
function init(val){
    console.log(val) //1
}
app(init)

回调函数是一种很好地异步编程思想。但是有一种被人们称为回调地狱的情况让人们不得不使用Promise去替代回调函数。

回调地狱就是回调函数里嵌套回调函数,比如:

var sayhello = function (name, callback) {
    console.log(name);
    callback();
}
sayhello("first", function () {
  sayhello("second", function () {
    sayhello("third", function () {
      console.log("end");
    });
  });
});
// first second third end

比如当第一个ajax获取到的id需要作为第二个ajax的请求参数使用这样也会形成回调地狱。

为了解决回调地狱的恐怖,因此呢,Promise便横空出世。
Promise 是异步编程的一种解决方案,比传统的回调函数更合理,更强大。在ES6中被标准化。
Promise的好处就是链式调用(chaining)

Promise的使用

  1. Promise的状态
    Promise的状态表示此时异步执行的状态。Promise一共有三种状态:
  • pending:初始状态
  • fulfilled:操作成功状态
  • rejected:操作失败状态

特点:
Promise三个状态不受外界影响,一旦状态改变,就不会再发生变化,Promise对象的状态改变只有两种可能,从pening变为fulfilled和从pending变为rejected.

  1. 基本用法
    ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
let promise = new Promise((resolve,reject)=>{
  //...异步代码
  if(/* 异步成功 */){
     resolve(value)
  }else{
    reject(error)
  }
})

Promise构造函数接收一个函数作为参数,该函数有两个参数分别为resolvereject。它们是两个函数,由JavaScript引擎提供,不用自己部署。
其中 resolve的作用是将Promise对象的状态从“未完成”变为“成功”(即从pending变为resolved),在异步操作成功时调用,并将异步操作的结果作为参数传递出去。

reject函数的作用是将Promise对象的状态从“未完成”变为“失败”(即从pending变为rejected),在异步操作失败时调用,并将异步操作报出的错误作为参数传递出去。

var flag  = false;
var p = new Promise((resolve,reject)=>{
    if(flag){
        resolve(1)
    }else{
        reject(2)
    }
})

p.then((val)=>{
    console.log(val,'then')
}).catch((val)=>{
    console.log(val,'catch') //2 "catch"
})

上述代码以flag模拟异步结果成功或失败,当成功时(flag为true),调用resolve并将结果1作为参数传递出去,Promise的实例化p调用Promise原型上的方法then。
then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法,默认一般都只写一个参数。
当执行resolve时可以在then的函数参数中接收从resolve中传递过来的参数
当执行reject时可以在catch的函数参数中接收从reject中传递的参数。

由于Promise.prototype.then(onFulfilled, onRejected)Promise.prototype.catch(onRejected)都会返回一个新的Promise对象,所以可以实现链式调用。

比如:用Promise实现两个数相加,再拿两个相加的数的结果减去一个数

function Add(a,b){
    return new Promise((resolve,reject)=>{
        resolve(a+b)
    })
}

function Min(c){
    return new Promise((resolve,reject)=>{
        resolve(c-1)
    })
}

Add(1,2).then((val)=>{
    return Min(val)
})
.then((val)=>{
    console.log(val)
})

以上可以很直观的看出Promise的链式调用。

Promise原型上还有一个常用方法叫finally.

var flag  = false;
var p = new Promise((resolve,reject)=>{
    if(flag){
        resolve(1)
    }else{
        reject(2)
    }
})

p.then((val)=>{
    console.log(val,'then')
}).catch((val)=>{
    console.log(val,'catch')
}).finally(()=>{
    console.log(12345)
})

finally方法接收一个参数是一个函数,该函数无参数,该方法表示无论最终Promise对象执行resolve还是reject. finally都会执行,它表示Promise对象执行结束了。

  1. Promise 方法
Promise.all(iterable)
const p = Promise.all([p1,p2,p3])
let p1 = new Promise((resolve,reject)=>{
  resolve(1)
})
let p2 = new Promise((resolve,reject)=>{
  resolve(2)
})
let p3 = new Promise((resolve,reject)=>{
  resolve(3)
})
Promise.all([p1,p2,p3]).then((val)=>{
    console.log(val) //[1,2,3]
})

Promise.all()方法接收一个数组作为参数,参数也可以不是数组,但必须具有迭代器接口,且返回的每个成员都是Promise实例。
p的状态有p1,p2,p3决定,分成两种情况,
(1)当p1,p2,p3的状态都为fulfilled,p的状态才会变成fulfilled,此时p1,p2,p3的返回值组成一个数组,传递给p的then方法第一个函数参数的参数。

(2)当p1,p2,p3中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的catch方法函数参数的参数。
如下:

let p1 = new Promise((resolve,reject)=>{
  resolve(1)
})
let p2 = new Promise((resolve,reject)=>{
  reject(2)
})
let p3 = new Promise((resolve,reject)=>{
  resolve(3)
})
Promise.all([p1,p2,p3]).then((val)=>{
    console.log(val)
}).catch((err)=>{
    console.log(err) //2
})

总结:只有Promise.all()中所有的参数状态都是fulfilled,p的状态即为成功,当其中有一个rejected,p的状态即为失败。

Promise.race()

Promise.race(iterable)

Promise.race()方法同样将多个Promise实例包装成一个新的Promise实例,当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

let p1 = new Promise((resolve,reject)=>{
  resolve(1)
})
let p2 = new Promise((resolve,reject)=>{
  resolve(2)
})
let p3 = new Promise((resolve,reject)=>{
  resolve(3)
})
Promise.race([p1,p2,p3]).then((val)=>{
    console.log(val)  //1
}).catch((err)=>{
    console.log(err)
})

由于p1排在第一个因此率先执行,所以当p1执行完成之后就直接停止执行p2,p3。

总结:race顾名思义比赛,执行最快的那个首先执行被返回,此时Promise状态已被确定。

Promise.allSettled()

Promise.allSettled(iterable);
类似于Promise.all()方法接受一个可迭代对象。不同的是它将所有的执行结果都会返回回来,不会像Promise.all()一样造成短路,成功时,它会返回成功的状态和返回值,失败时,会返回失败状态和失败原因。也就是说,当每个Promise执行完成之后不管成功与否都会拿到最终的所有结果。

let flag = true;
let p = new Promise((resolve, reject)=>{
    if(!flag){resolve('p')}else{reject('err')}
})

let p1 = new Promise((resolve, reject)=>{
    if(flag){resolve('p1')}else{reject('err')}
})

let p2 = new Promise((resolve, reject)=>{
    if(flag){resolve('p2')}else{reject('err')}
})

Promise.allSettled([p,p1,p2]).then((data)=>{
    console.log(data)
})

结果:

0: {status: "rejected", reason: "err"}
1: {status: "fulfilled", value: "p1"}
2: {status: "fulfilled", value: "p2"}

可参考: Promise 中的三兄弟 .all(), .race(), .allSettled()

Promise.resolve(value)

将一个普通的value转换成Promise对象。
返回一个状态由给定value决定的Promise对象。如果该值是thenable(即,带有then方法的对象),返回的Promise对象的最终状态由then方法执行决定;否则的话(该value为空,基本类型或者不带then方法的对象),返回的Promise对象状态为fulfilled,并且将该value传递给对应的then方法。

Promise.resolve("foo")
//等价于
new Promise(resolve=>resolve('foo'))

最后再来看一个Promise封装的Ajax:

function getJson(){
  return new Promise((resolve,reject)=>{
    var xhr = new XMLHttpRequest();
      xhr.open(method,url);
      xhr.send();
      xhr.onreadystatechange = function(){
      if(xhr.readyState == 4 && xhr.status == 200){
       resolve(xhr.responseText)
      }
    }
  })
}

getJson.then((data)=>{
  document.getElementById("app").innerHTML = data;
})
面试题:

Promise.all()中有一个异步抛出异常,最先抛出的异常会被.then的第二个参数或者.catch补捕获。并终止程序。并且获取不到Promise.all()其他正常的结果。那么如何获取成功的请求结果呢?

(1). 在异步请求中定义自己的catch方法,一旦它被rejected,并不会触发Promise.all()的catch方法。

var flag = true;
    
var p1 = new Promise((resolve,reject)=>{ //p1resolved
    if(flag){
        resolve("p1")
    }else{
        reject("p1 err")
    }
})
var p2 = new Promise((resolve,reject)=>{   //p2rejected
    if(!flag){
        resolve("p2")
    }else{
        reject("p2 err")
    }
}).then((data)=>{
    console.log(data,'p2 then')
}).catch((data)=>{
    console.log(data,'p2 catch')  //p2 err p2 catch
})

var p3 = new Promise((resolve,reject)=>{ //p3resolved
    if(flag){
        resolve("p3")
    }else{
        reject("p3 err")
    }
})

Promise.all([p1,p2,p3]).
then((data)=>{
    console.log(data); // ["p1", undefined, "p3"]
})
.catch((data)=>{
    console.log(data)
})

上面代码中,p2会rejected,但是p2有自己的catch方法,该方法返回的是一个新的Promise实例,p2实际指向的是这个实例。该实例执行完catch方法后,也会resolved,导致Promise.all()方法参数里面的三个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回到函数。

上面代码Promise.all()的then中输出["p1", undefined, "p3"]从中可以获取到执行成功的p1结果,p3结果。

如果p2没有自己的catch方法,那么就会调用Promise.all()的catch方法。

(2). 在Promise.all()的所有参数中无论resolved还是rejected都执行resolve.

var flag = true;
    
var p1 = new Promise((resolve,reject)=>{
    if(flag){
        resolve("p1")
    }else{
        reject("p1 err")
    }
})
var p2 = new Promise((resolve,reject)=>{
    if(!flag){
        resolve("p2")
    }else{
        resolve("error")
    }
})

var p3 = new Promise((resolve,reject)=>{
    if(flag){
        resolve("p3")
    }else{
        reject("p3 err")
    }
})

Promise.all([p1,p2,p3]).
then((data)=>{
    console.log(data); //  ["p1", "error", "p3"]
})
.catch((data)=>{
    console.log(data)
})

这个很好理解,在p2中无论成功失败都走resolve,这样就顺理成章的走到了Promise.all()的then中,此时可以获取到所有的结果,从中获取返回正常的结果。

上面代码结果是["p1", "error", "p3"],从中过滤掉失败的就好了。

面试题:利用Promise实现一个Promise.all()方法

思路:将传入数组在Promise实例中遍历执行,将then的结果存入数组,设一个变量来记录调用了几次then,当调用then的次数等于传入数组的length时,那么就在Promise实例的then方法中调用resolve方法,否则就直接将捕获到的错误reject出去。

Promise.myAll = function(arr){
    let result = [];
    let len = 0;
    return new Promise((resolve, reject) => {
        arr.forEach((promise) => {
            promise.then((res) => {
                result.push(res);
                len ++;
                if(len === arr.length) {
                    resolve(result);
                }
            }, (e) => {
                reject(e);
            })
        })
    })
}
let a = new Promise((resolve, reject) => {
    resolve(1);
});
let b = Promise.resolve(2);

Promise.myAll([a, b]).then((res) => {
    console.log(res, 'ok');
}, (err) => {
    console.log(err, 'err')
})

参考文章:
MDN Promise
深入理解 ES6 Promise

async/await

async/await和Promise一样目的都是为了异步调用的“扁平化”。async/await是非常好用的语法糖。可以认为是基于Promise的针对异步更优雅的解决方案。

用法:

  1. async用于申明一个函数是异步的,它的返回值是一个Promise对象。如果在函数中return 一个直接量,async会把这个直接;量通过Promise.resolve()封装成Promise对象。
//在函数前加上async关键字,表明该函数是异步函数
async function testAsync(){
  return "async"
}
const result = testAsync();
console.log(result);//Promise {: "async"}

result.then((data)=>{
  console.log(data) //async
})
  1. 配合await
    await只能出现在async函数中。
    await表示等待。等待异步方法执行结束。
    async函数返回一个Promise对象,所以await等待async函数的返回值。也可以等待任意表达式的结果。
    例如:
function func(){ //普通函数
  return "hello world"
}

async function testAsync(){ //async函数
  return Promise.resolve("hello async")
}

async function test(){
  const result1 = await func();
  const result2 = await testAsync();
  console.log(result1,result2)
}
test(); // hello world hello async

await等待的值为一个Promise对象,或者其它值
await 是个运算符,用于组成表达式,await表达式的运算结果取决于它等的东西。

如果它等到的不是一个Promise对象,那await表达式的运算结果就是它等到的东西。
如果它等到的是一个Promise对象,那么await就会阻塞后面代码,等着Promise对象状态改变,比如resolve,然后得到resolve的值,作为await表达式的运算结果。
  1. async/await 写法与Promise比较
//Promise
function setTime(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve("setTime")
    },1000)
  })
}
setTime().then((data)=>{
  console.log(data); // setTime  1s之后会输出
})
//async/await
function setTime(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve("setTime")
    },1000)
  })
}

async function test(){
  const result = await setTime(); // 该段代码执行结束之前后面的代码是不会执行的。
  console.log(1); // 1 1s之后输出
  console.log(result) // setTime 1s之后输出
}
test();

怎么说呢,async/await就是一个语法糖,看起来貌似很难,很高深,其实用起来了也就慢慢熟悉了。感觉async看起来更直观一些吧。

思考题:

async function foo() {
    console.log('foo')
}
async function bar() {
    console.log('bar start')
    await foo()
    console.log('bar end')
}
console.log('script start')
setTimeout(function () {
    console.log('setTimeout')
}, 0)
bar();
new Promise(function (resolve) {
    console.log('promise executor')
    resolve();
}).then(function () {
    console.log('promise then')
})
console.log('script end')

参考:
阮一峰 async 函数的含义和用法
理解 JavaScript 的 async/await

展开(spread)运算符和剩余(Rest)运算符

展开运算符用三个点(...)表示,可以将数组转为逗号分隔的参数序列。化少为多。

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

可用于数组合并

var arr1 = [1,2,3]
var arr2 = [4,5,6]
var arr3 = [...arr1, ...arr2];
console.log(arr3); //[1,2,3,4,5,6]

可用于对象合并

var obj1 = {a:1,b:2}
var obj2 = {c:3,d:4}
var obj3 = {...obj1,...obj2}
console.log(obj3)//{a:1,b:2,c:3,d:4}

剩余运算符也是用三个点表示(...),剩余运算符会将多余元素收集压缩成一个单一的元素。
可以用来表示形参。

function func(a,...args){
  console.log(a); //1 
  console.log(args); //[2,3,4]
}
func(1,2,3,4);

解构赋值:

const [num,...other] = [1,2,3,4,5];
console.log(num);// 1
console.log(other); [2,3,4,5]

纯函数

定义:一个函数的返回结果只依赖他的参数,并且在执行过程中没有副作用,我们就把这个函数叫做纯函数。

由定义可知纯函数有两个重要点:

  • 函数的返回结果只依赖它的参数
  • 函数执行过程中没有副作用
var a = 1;
function foo (b){
  return a+b
}
foo(2) // 3

foo函数不是一个纯函数,因为它返回的结果依赖外部变量a,我们在不知道a的值得情况下,并不能保证foo(2)的返回值是3。虽然foo函数的代码实现并没有变化,传入的参数并没有变化,但他的返回值却是不可预料的,因为函数依赖的外部值a是不可预料的,它是1,也可能在函数foo执行之前在其他逻辑中被修改。

修改一下函数foo的逻辑:

var a = 1;
function foo (x,b){
  return x+b
}
foo(1,2) // 3

现在foo的返回结果只依赖于它的参数x和b,foo(1,2)的返回结果永远是3,不管外部代码怎么变化,foo(1,2)永远是3。只要foo代码不改变,只要foo代码不改变,你传入的参数是确定的,那么foo(1,2)的值永远是可预料的。

这就是纯函数的第一个条件,一个函数的返回结果只依赖于它的参数

再来看看第二个特点,函数执行过程没有副作用。
一个函数执行过程中对函数外部产生了可观察的变化,那么就说这个函数是有副作用的。

var a = 1;
var obj = {x:2}
function foo (obj,b){
  obj.x = b
  return obj.x+b
}
foo(obj ,3) // 6
obj // {x:3}

对象obj的属性x默认为2,foo执行后obj.x成为了foo函数的第二个参数,因此obj最终由{x:2}变为了{x:3},因此foo函数的执行对外部的obj产生了影响,它产生了副作用,因为它修改了外部传进来的对象,因此现在它不是一个纯函数。

function foo(b){
  const obj = {x:1}
  obj.x = 2;
  return obj.x + b
}

以上函数foo虽然内部修改了变量obj,但obj是内部变量,外部程序观察不到,修改obj并不会产生外部可观察变化,这个函数没有副作用,因此它是一个纯函数

除了修改外部的变量,一个函数在执行过程中还有很多方式产生外部可观察的变化,比如说调用DOM API修改页面,或者发送Ajax请求,调用BOM API等,甚至使用console.log()往控制台打印数据也是副作用。

纯函数很严格,除了计算数据以外什么都不能干,计算的时候还不能依赖除了函数参数以外的数据。

总结:一个函数的返回结果只依赖于他的参数,并且在执行过程中没有副作用,那么我们就称这个函数为纯函数。

纯函数的好处就是它非常靠谱,执行一个纯函数,它的执行结果一般都是在你的预料之中,不必担心一个纯函数会干什么坏事,他不会产生不可预料的行为,也不会对外部产生影响,不管何时何地,你给它什么,它就吐出什么。

纯函数也是函数式编程的一个很重要的概念,很多类库,框架也都有使用到,比如redux,react高阶组件因此需要了解其概念。

严格模式

使用:"use strict",可以在整个代码块前加“use strict”使用,也可以只在函数中局部添加“use strict”去使用。

特点:

  1. 变量必须声明才可使用。
  2. 创设eval作用域,除了全局作用域,函数作用域外,新增eval作用域。
  3. with()被禁用,with语句用于设置代码在特定对象中的作用域。
  4. caller/callee被禁用。
  5. delete使用在var 声明的变量活挂在window的变量上会报错。
  6. delete不可删除属性的对象时会报错。
  7. 对一个对象的只读属性进行赋值会报错。
  8. 对象有重名属性将报错。
  9. 函数有重名的参数会报错。
  10. arguments严格定义为参数,不再与形参绑定。
  11. 函数中的this不再指向window。

参考:
阮一峰 - Javascript 严格模式详解

高阶函数

什么是高阶函数:

  1. 如果一个函数的参数是一个函数(回调函数)
  2. 如果一个函数的返回值是一个函数,当前这个函数也是一个高阶函数

应用场景:扩展业务代码

function Eat(a,b){ //核心业务代码
  console.log(a,b)
}

Function.prototype.before = function(callback){     //高阶函数
  return (...args)=>{ //使用rest运算符接收
    callback();
    this(...args);  //使用展开运算符传入
  }
}

let beforeEat = Eat.before(function(){ //自己扩展业务代码
  console.log("before eat")
})
beforeEat("666","999") //传参

函数柯里化
例子:判断数据类型

  1. typeof 不能判断对象类型
  2. constructor 判断该数据是由谁构造出来
  3. instanceof 判断谁是谁的实例proto
  4. Object.prototype.toString.call() 缺陷不能细分谁是谁的实例
function isType(value,type){
  return Object.prototype.toString.call(value) === `[object ${type}]`
}
//调用
isType([],"Array") // 判断数据 [] 的类型是否似乎数组

isType("","Array") // 判断数据 "" 的类型是否是数组 

上述实现有缺陷,就是每次判断数据时都需要重复去指定type类型。

接下来就是一个函数柯里化的简单应用

function isType(type){
    return function(value){
        return Object.prototype.toString.call(value) === `[object ${type}]`
    }
}
    
const isArray = isType("Array");
const isString = isType("String");
console.log(isArray([])); //true
console.log(isArray("")); //false
console.log(isString("")); //true

//相当于就是这么写
isType("Array")([])

这样是不是比较高大上呢 !!!

那现在来自己封装一个可以实现柯里化函数的方法

首先分析一下,举个例子,看下面代码

function add(a,b,c,d){
   return a+b+c+d
}

假如我们定义了一个函数Curry这个函数可以将普通函数转变成柯里化函数。
正常调用add(1,2,3,4),用柯里化的方式应该是这样的Curry(add)(1)(2)(3)(4)

通过我们自己封装的柯里化函数的方法,将原函数传进去之后就可以化多参为单参,然后依次去调用

那么Curry的返回值肯定是个函数,就像这样

function Curry(){
  return function(){
    //逻辑
  }
}

我们需要将我们的目标函数传入Curry中

function Curry(fn){ // fn表示就是add函数
  return function(...args){ // args 表示调用之后传入的参数 就像1,2,3,4
    
  }
}

这样整体逻辑了解了,函数的参数是依次传进来的,我们需要在所有参数传递进来之后再去执行函数并返回结果,那么就需要对参数个数进行判断。这里有一个方法需要了解一下,那就是函数的length属性,比如

function fn(){}
console.log(fn.length) // 0

function fn1(a,b){}
console.log(fn1.length) //2

函数的length属性表示“第一个具有默认值之前的参数个数” 具体可以参考文章 《JS 中函数的 length 属性》

回到分析中,我们需要知道当函数接收的参数个数等于函数的形参个数时,才可以进行执行并返回结果,否则,继续递归进行函数柯里化。

function Curry(fn,arr = []){   // fn表示就是add函数
  let len = fn.length          // 获取函数fn的形参个数
  let argsArr = []             // 用一个数组将fn的参数存起来
  return function(...args){    // args 表示调用之后传入的参数 就像1,2,3,4
    argsArr = [...arr,...args]
    if(argsArr.length < len){  // 当传入参数length小于len时递归柯里化函数
      return Curry(fn,argsArr)
    }else{                     // 当传入参数length等于len时直接执行函数
      return fn(...argsArr)
    }
  }
}

注意点:每次进行递归时,需要将参数携带着,在Curry中接收,然后每次进行拼接。这里大量使用了ES6展开运算符和REST剩余运算符,如果阅读困难,可以查看前文关于展开运算符和剩余运算符。

关于ES6 Class

ES6和ES5继承区别:
ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

关于super关键字

class Parent{
  constructor(x,y){
    this.x = x;
    this.y = y;
  }
  parentY(){
      return 12
  }
}

class Child extends Parent {
  constructor(x,y){
      super(x,y)
  }
  sayParams(){
    console.log(this.x, super.parentY()) // 1,12
  }
}

new Child(1,2).sayParams() 

super关键字即可以当函数使用,也可以当对象使用。如上代码

super当函数调用时代表父类的构造函数,ES6要求,子类的构造函数必须执行一次super函数。

super作为对象使用时指向父类的原型对象。(由于super指向的时父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的,比如在上面方法sayParams中访问super.y那么就访问不到)

Class的静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为"静态方法"。

class Foo{
    static sayName(){
        console.log("name")
    }
}
Foo.sayName() // name
new Foo().sayName() // 报错 syaName is not a function

父类的静态方法可以被子类继承

class Foo{
    static sayName(){
        console.log("name")
    }
}
class Child extends Foo{
}
Child.sayName() // name

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

class Foo{}
Foo.prop = 1

以上代码中prop是类Foo的静态属性。采用直接赋值的形式。还可以这样写:

class Foo{
  static prop = 2
}
Foo.prop // 2
new Foo().prop // undefined

直接在类中进行赋值,并在前加上static关键字。

实例属性就是可以通过new操作符对类实例之后访问的属性,可以用等式直接写入类的定义中

class Foo{
  prop = 12
}
new Foo().prop // 12
Foo.prop // undefined

以前定义实例属性只能写在类的constructor方法里面,有了新的写法之后,就可以不写在constructor中了。

参考:http://caibaojian.com/es6/class.html

手写一个函数实现new的功能

首先我们需要明白new干了什么事情,new一个函数之后,返回了一个对象,该对象能够访问函数的原型。

function myNew(func, ...args){
  if(typeof func !== 'function'){
    return;
  }
  let obj = {};
  func.call(obj, ...args); // 核心,改变this指向
  obj.__proto__ = func.prototype; // 改变原型,让obj可以访问func的原型
  return obj;
}

手写一个instanceof.

instanceof主要是判断一个对象是否是某个类的实例。
换句话说就是判断某个对象的原型链上有没有某个某个类的prototype

function isInstance(ins, target){
  if(!ins || !target || !ins.__proto__ || !target.prototype){
    return false;
  }
  let current = ins.__proto__;
  while(current){
    if(current === target.prototype){
       return true;
    }
    current = current.__proto__;
  }
  return false;
}

使用setTimeout模拟setInteval

let timer  = null;
function simulation(func, wait) {
  let intval = function () {
     func();
     timer = setTimeout(intval, wait);
  }
  timer = setTimeout(intval, wait);
}

simulation(function() {
  console.log(1);
}, 1000);

clearTimeout(timer);

参考:https://mp.weixin.qq.com/s/vYHSqv_6ttWLK4qdSS6V1w

待续......

写在最后:文中内容大多为自己平时从各种途径学习总结,文中参考文章大多收录在我的个人博客里,欢迎阅览http://www.tianleilei.cn

你可能感兴趣的:(Javascript知识零散学习(面试篇))