Javascript知識零散學習(面試篇)

Promise

首先來說一下應該明白Promise是做什么的,JS是一門典型的異步單線程的編程語言,單線程就不說了,異步如何理解呢?異步編程可以理解成在執(zhí)行指令之后不能馬上得到結果,而是繼續(xù)執(zhí)行后面的指令,等到特定的事件觸發(fā)之后才得到想要的結果。
也可以和同步對比理解,同步就是一步步執(zhí)行指令,而異步在遇到特殊任務(異步任務)時,并不會等待該任務執(zhí)行完成之后再去執(zhí)行下一步,而是跳過等待,先去執(zhí)行下一步任務,等到某個時機該特殊任務(異步任務)執(zhí)行完成之后再返回來對其進行處理。
比如:

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最后輸出而是因為這就是一個典型的異步任務,它是在同步任務之后才去執(zhí)行的。

說起異步肯定首先會想到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是異步操作,所以我們只能將從服務器獲取數(shù)據(jù)之后再渲染頁面的整個過程放在ajax請求成功之后監(jiān)聽函數(shù)onreadystatechange里,這樣做有些糟糕,會導致代碼比較亂。

因此有一種操作對于異步編程來說比較優(yōu)雅就是回調函數(shù)。

還拿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)
    }
  }
}
//回調函數(shù)
function initHtml(val){
  document.getElementById('app').innerHTML = val
}
// 將initHtml函數(shù)作為參數(shù)傳入getAppJson中并在xhr.onreadystatechange 方法中執(zhí)行并把后臺所得數(shù)據(jù)作為參數(shù)傳遞給initHtml;
getAppJson(initHtml);

上述代碼使用回調函數(shù)優(yōu)雅的解決了ajax異步操作。

上述代碼簡化之后就是下面這個樣子的:

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

回調函數(shù)是一種很好地異步編程思想。但是有一種被人們稱為回調地獄的情況讓人們不得不使用Promise去替代回調函數(shù)。

回調地獄就是回調函數(shù)里嵌套回調函數(shù),比如:

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

比如當?shù)谝粋€ajax獲取到的id需要作為第二個ajax的請求參數(shù)使用這樣也會形成回調地獄。

為了解決回調地獄的恐怖,因此呢,Promise便橫空出世。
Promise 是異步編程的一種解決方案,比傳統(tǒng)的回調函數(shù)更合理,更強大。在ES6中被標準化。
Promise的好處就是鏈式調用(chaining)

Promise的使用

  1. Promise的狀態(tài)
    Promise的狀態(tài)表示此時異步執(zhí)行的狀態(tài)。Promise一共有三種狀態(tài):
  • pending:初始狀態(tài)
  • fulfilled:操作成功狀態(tài)
  • rejected:操作失敗狀態(tài)

特點:
Promise三個狀態(tài)不受外界影響,一旦狀態(tài)改變,就不會再發(fā)生變化,Promise對象的狀態(tài)改變只有兩種可能,從pening變?yōu)閒ulfilled和從pending變?yōu)閞ejected.

  1. 基本用法
    ES6規(guī)定,Promise對象是一個構造函數(shù),用來生成Promise實例。
let promise = new Promise((resolve,reject)=>{
  //...異步代碼
  if(/* 異步成功 */){
     resolve(value)
  }else{
    reject(error)
  }
})

Promise構造函數(shù)接收一個函數(shù)作為參數(shù),該函數(shù)有兩個參數(shù)分別為resolvereject。它們是兩個函數(shù),由JavaScript引擎提供,不用自己部署。
其中 resolve的作用是將Promise對象的狀態(tài)從“未完成”變?yōu)椤俺晒Α保磸膒ending變?yōu)閞esolved),在異步操作成功時調用,并將異步操作的結果作為參數(shù)傳遞出去。

reject函數(shù)的作用是將Promise對象的狀態(tài)從“未完成”變?yōu)椤笆 保磸膒ending變?yōu)閞ejected),在異步操作失敗時調用,并將異步操作報出的錯誤作為參數(shù)傳遞出去。

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作為參數(shù)傳遞出去,Promise的實例化p調用Promise原型上的方法then。
then方法包含兩個參數(shù):onfulfilled 和 onrejected,它們都是 Function 類型。當Promise狀態(tài)為fulfilled時,調用 then 的 onfulfilled 方法,當Promise狀態(tài)為rejected時,調用 then 的 onrejected 方法,默認一般都只寫一個參數(shù)。
當執(zhí)行resolve時可以在then的函數(shù)參數(shù)中接收從resolve中傳遞過來的參數(shù)
當執(zhí)行reject時可以在catch的函數(shù)參數(shù)中接收從reject中傳遞的參數(shù)。

由于Promise.prototype.then(onFulfilled, onRejected)Promise.prototype.catch(onRejected)都會返回一個新的Promise對象,所以可以實現(xiàn)鏈式調用。

比如:用Promise實現(xiàn)兩個數(shù)相加,再拿兩個相加的數(shù)的結果減去一個數(shù)

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方法接收一個參數(shù)是一個函數(shù),該函數(shù)無參數(shù),該方法表示無論最終Promise對象執(zhí)行resolve還是reject. finally都會執(zhí)行,它表示Promise對象執(zhí)行結束了。

  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()方法接收一個數(shù)組作為參數(shù),參數(shù)也可以不是數(shù)組,但必須具有迭代器接口,且返回的每個成員都是Promise實例。
p的狀態(tài)有p1,p2,p3決定,分成兩種情況,
(1)當p1,p2,p3的狀態(tài)都為fulfilled,p的狀態(tài)才會變成fulfilled,此時p1,p2,p3的返回值組成一個數(shù)組,傳遞給p的then方法第一個函數(shù)參數(shù)的參數(shù)。

(2)當p1,p2,p3中有一個被rejected,p的狀態(tài)就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的catch方法函數(shù)參數(shù)的參數(shù)。
如下:

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()中所有的參數(shù)狀態(tài)都是fulfilled,p的狀態(tài)即為成功,當其中有一個rejected,p的狀態(tài)即為失敗。

Promise.race()

Promise.race(iterable)

Promise.race()方法同樣將多個Promise實例包裝成一個新的Promise實例,當iterable參數(shù)里的任意一個子promise被成功或失敗后,父promise馬上也會用子promise的成功返回值或失敗詳情作為參數(shù)調用父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排在第一個因此率先執(zhí)行,所以當p1執(zhí)行完成之后就直接停止執(zhí)行p2,p3。

總結:race顧名思義比賽,執(zhí)行最快的那個首先執(zhí)行被返回,此時Promise狀態(tài)已被確定。

Promise.allSettled()

Promise.allSettled(iterable);
類似于Promise.all()方法接受一個可迭代對象。不同的是它將所有的執(zhí)行結果都會返回回來,不會像Promise.all()一樣造成短路,成功時,它會返回成功的狀態(tài)和返回值,失敗時,會返回失敗狀態(tài)和失敗原因。也就是說,當每個Promise執(zhí)行完成之后不管成功與否都會拿到最終的所有結果。

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對象。
返回一個狀態(tài)由給定value決定的Promise對象。如果該值是thenable(即,帶有then方法的對象),返回的Promise對象的最終狀態(tài)由then方法執(zhí)行決定;否則的話(該value為空,基本類型或者不帶then方法的對象),返回的Promise對象狀態(tài)為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的第二個參數(shù)或者.catch補捕獲。并終止程序。并且獲取不到Promise.all()其他正常的結果。那么如何獲取成功的請求結果呢?

(1). 在異步請求中定義自己的catch方法,一旦它被rejected,并不會觸發(fā)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實際指向的是這個實例。該實例執(zhí)行完catch方法后,也會resolved,導致Promise.all()方法參數(shù)里面的三個實例都會resolved,因此會調用then方法指定的回調函數(shù),而不會調用catch方法指定的回到函數(shù)。

上面代碼Promise.all()的then中輸出["p1", undefined, "p3"]從中可以獲取到執(zhí)行成功的p1結果,p3結果。

如果p2沒有自己的catch方法,那么就會調用Promise.all()的catch方法。

(2). 在Promise.all()的所有參數(shù)中無論resolved還是rejected都執(zhí)行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實現(xiàn)一個Promise.all()方法

思路:將傳入數(shù)組在Promise實例中遍歷執(zhí)行,將then的結果存入數(shù)組,設一個變量來記錄調用了幾次then,當調用then的次數(shù)等于傳入數(shù)組的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是非常好用的語法糖??梢哉J為是基于Promise的針對異步更優(yōu)雅的解決方案。

用法:

  1. async用于申明一個函數(shù)是異步的,它的返回值是一個Promise對象。如果在函數(shù)中return 一個直接量,async會把這個直接;量通過Promise.resolve()封裝成Promise對象。
//在函數(shù)前加上async關鍵字,表明該函數(shù)是異步函數(shù)
async function testAsync(){
  return "async"
}
const result = testAsync();
console.log(result);//Promise {<resolved>: "async"}

result.then((data)=>{
  console.log(data) //async
})
  1. 配合await
    await只能出現(xiàn)在async函數(shù)中。
    await表示等待。等待異步方法執(zhí)行結束。
    async函數(shù)返回一個Promise對象,所以await等待async函數(shù)的返回值。也可以等待任意表達式的結果。
    例如:
function func(){ //普通函數(shù)
  return "hello world"
}

async function testAsync(){ //async函數(shù)
  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對象狀態(tài)改變,比如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(); // 該段代碼執(zhí)行結束之前后面的代碼是不會執(zhí)行的。
  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 函數(shù)的含義和用法
理解 JavaScript 的 async/await

展開(spread)運算符和剩余(Rest)運算符

展開運算符用三個點(...)表示,可以將數(shù)組轉為逗號分隔的參數(shù)序列?;贋槎?。

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

可用于數(shù)組合并

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]

純函數(shù)

定義:一個函數(shù)的返回結果只依賴他的參數(shù),并且在執(zhí)行過程中沒有副作用,我們就把這個函數(shù)叫做純函數(shù)。

由定義可知純函數(shù)有兩個重要點:

  • 函數(shù)的返回結果只依賴它的參數(shù)
  • 函數(shù)執(zhí)行過程中沒有副作用
var a = 1;
function foo (b){
  return a+b
}
foo(2) // 3

foo函數(shù)不是一個純函數(shù),因為它返回的結果依賴外部變量a,我們在不知道a的值得情況下,并不能保證foo(2)的返回值是3。雖然foo函數(shù)的代碼實現(xiàn)并沒有變化,傳入的參數(shù)并沒有變化,但他的返回值卻是不可預料的,因為函數(shù)依賴的外部值a是不可預料的,它是1,也可能在函數(shù)foo執(zhí)行之前在其他邏輯中被修改。

修改一下函數(shù)foo的邏輯:

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

現(xiàn)在foo的返回結果只依賴于它的參數(shù)x和b,foo(1,2)的返回結果永遠是3,不管外部代碼怎么變化,foo(1,2)永遠是3。只要foo代碼不改變,只要foo代碼不改變,你傳入的參數(shù)是確定的,那么foo(1,2)的值永遠是可預料的。

這就是純函數(shù)的第一個條件,一個函數(shù)的返回結果只依賴于它的參數(shù)。

再來看看第二個特點,函數(shù)執(zhí)行過程沒有副作用。
一個函數(shù)執(zhí)行過程中對函數(shù)外部產(chǎn)生了可觀察的變化,那么就說這個函數(shù)是有副作用的。

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執(zhí)行后obj.x成為了foo函數(shù)的第二個參數(shù),因此obj最終由{x:2}變?yōu)榱?code>{x:3},因此foo函數(shù)的執(zhí)行對外部的obj產(chǎn)生了影響,它產(chǎn)生了副作用,因為它修改了外部傳進來的對象,因此現(xiàn)在它不是一個純函數(shù)。

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

以上函數(shù)foo雖然內部修改了變量obj,但obj是內部變量,外部程序觀察不到,修改obj并不會產(chǎn)生外部可觀察變化,這個函數(shù)沒有副作用,因此它是一個純函數(shù)。

除了修改外部的變量,一個函數(shù)在執(zhí)行過程中還有很多方式產(chǎn)生外部可觀察的變化,比如說調用DOM API修改頁面,或者發(fā)送Ajax請求,調用BOM API等,甚至使用console.log()往控制臺打印數(shù)據(jù)也是副作用。

純函數(shù)很嚴格,除了計算數(shù)據(jù)以外什么都不能干,計算的時候還不能依賴除了函數(shù)參數(shù)以外的數(shù)據(jù)。

總結:一個函數(shù)的返回結果只依賴于他的參數(shù),并且在執(zhí)行過程中沒有副作用,那么我們就稱這個函數(shù)為純函數(shù)。

純函數(shù)的好處就是它非??孔V,執(zhí)行一個純函數(shù),它的執(zhí)行結果一般都是在你的預料之中,不必擔心一個純函數(shù)會干什么壞事,他不會產(chǎn)生不可預料的行為,也不會對外部產(chǎn)生影響,不管何時何地,你給它什么,它就吐出什么。

純函數(shù)也是函數(shù)式編程的一個很重要的概念,很多類庫,框架也都有使用到,比如redux,react高階組件因此需要了解其概念。

嚴格模式

使用:"use strict",可以在整個代碼塊前加“use strict”使用,也可以只在函數(shù)中局部添加“use strict”去使用。

特點:

  1. 變量必須聲明才可使用。
  2. 創(chuàng)設eval作用域,除了全局作用域,函數(shù)作用域外,新增eval作用域。
  3. with()被禁用,with語句用于設置代碼在特定對象中的作用域。
  4. caller/callee被禁用。
  5. delete使用在var 聲明的變量活掛在window的變量上會報錯。
  6. delete不可刪除屬性的對象時會報錯。
  7. 對一個對象的只讀屬性進行賦值會報錯。
  8. 對象有重名屬性將報錯。
  9. 函數(shù)有重名的參數(shù)會報錯。
  10. arguments嚴格定義為參數(shù),不再與形參綁定。
  11. 函數(shù)中的this不再指向window。

參考:
阮一峰 - Javascript 嚴格模式詳解

高階函數(shù)

什么是高階函數(shù):

  1. 如果一個函數(shù)的參數(shù)是一個函數(shù)(回調函數(shù))
  2. 如果一個函數(shù)的返回值是一個函數(shù),當前這個函數(shù)也是一個高階函數(shù)

應用場景:擴展業(yè)務代碼

function Eat(a,b){ //核心業(yè)務代碼
  console.log(a,b)
}

Function.prototype.before = function(callback){     //高階函數(shù)
  return (...args)=>{ //使用rest運算符接收
    callback();
    this(...args);  //使用展開運算符傳入
  }
}

let beforeEat = Eat.before(function(){ //自己擴展業(yè)務代碼
  console.log("before eat")
})
beforeEat("666","999") //傳參

函數(shù)柯里化
例子:判斷數(shù)據(jù)類型

  1. typeof 不能判斷對象類型
  2. constructor 判斷該數(shù)據(jù)是由誰構造出來
  3. instanceof 判斷誰是誰的實例proto
  4. Object.prototype.toString.call() 缺陷不能細分誰是誰的實例
function isType(value,type){
  return Object.prototype.toString.call(value) === `[object ${type}]`
}
//調用
isType([],"Array") // 判斷數(shù)據(jù) [] 的類型是否似乎數(shù)組

isType("","Array") // 判斷數(shù)據(jù) "" 的類型是否是數(shù)組 

上述實現(xiàn)有缺陷,就是每次判斷數(shù)據(jù)時都需要重復去指定type類型。

接下來就是一個函數(shù)柯里化的簡單應用

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")([])

這樣是不是比較高大上呢 !!!

那現(xiàn)在來自己封裝一個可以實現(xiàn)柯里化函數(shù)的方法

首先分析一下,舉個例子,看下面代碼

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

假如我們定義了一個函數(shù)Curry這個函數(shù)可以將普通函數(shù)轉變成柯里化函數(shù)。
正常調用add(1,2,3,4),用柯里化的方式應該是這樣的Curry(add)(1)(2)(3)(4)

通過我們自己封裝的柯里化函數(shù)的方法,將原函數(shù)傳進去之后就可以化多參為單參,然后依次去調用

那么Curry的返回值肯定是個函數(shù),就像這樣

function Curry(){
  return function(){
    //邏輯
  }
}

我們需要將我們的目標函數(shù)傳入Curry中

function Curry(fn){ // fn表示就是add函數(shù)
  return function(...args){ // args 表示調用之后傳入的參數(shù) 就像1,2,3,4
    
  }
}

這樣整體邏輯了解了,函數(shù)的參數(shù)是依次傳進來的,我們需要在所有參數(shù)傳遞進來之后再去執(zhí)行函數(shù)并返回結果,那么就需要對參數(shù)個數(shù)進行判斷。這里有一個方法需要了解一下,那就是函數(shù)的length屬性,比如

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

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

函數(shù)的length屬性表示“第一個具有默認值之前的參數(shù)個數(shù)” 具體可以參考文章 《JS 中函數(shù)的 length 屬性》

回到分析中,我們需要知道當函數(shù)接收的參數(shù)個數(shù)等于函數(shù)的形參個數(shù)時,才可以進行執(zhí)行并返回結果,否則,繼續(xù)遞歸進行函數(shù)柯里化。

function Curry(fn,arr = []){   // fn表示就是add函數(shù)
  let len = fn.length          // 獲取函數(shù)fn的形參個數(shù)
  let argsArr = []             // 用一個數(shù)組將fn的參數(shù)存起來
  return function(...args){    // args 表示調用之后傳入的參數(shù) 就像1,2,3,4
    argsArr = [...arr,...args]
    if(argsArr.length < len){  // 當傳入?yún)?shù)length小于len時遞歸柯里化函數(shù)
      return Curry(fn,argsArr)
    }else{                     // 當傳入?yún)?shù)length等于len時直接執(zhí)行函數(shù)
      return fn(...argsArr)
    }
  }
}

注意點:每次進行遞歸時,需要將參數(shù)攜帶著,在Curry中接收,然后每次進行拼接。這里大量使用了ES6展開運算符和REST剩余運算符,如果閱讀困難,可以查看前文關于展開運算符和剩余運算符。

關于ES6 Class

ES6和ES5繼承區(qū)別:
ES5的繼承,實質是先創(chuàng)造子類的實例對象this,然后再將父類的方法添加到this上面(Parent.apply(this))。
ES6的繼承機制完全不同,實質是先創(chuàng)造父類的實例對象this(所以必須先調用super方法),然后再用子類的構造函數(shù)修改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關鍵字即可以當函數(shù)使用,也可以當對象使用。如上代碼

super當函數(shù)調用時代表父類的構造函數(shù),ES6要求,子類的構造函數(shù)必須執(zhí)行一次super函數(shù)。

super作為對象使用時指向父類的原型對象。(由于super指向的時父類的原型對象,所以定義在父類實例上的方法或屬性,是無法通過super調用的,比如在上面方法sayParams中訪問super.y那么就訪問不到)

Class的靜態(tài)方法
類相當于實例的原型,所有在類中定義的方法,都會被實例繼承。如果在一個方法前加上static關鍵字,就表示該方法不會被實例繼承,而是直接通過類來調用,這就稱為"靜態(tài)方法"。

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

父類的靜態(tài)方法可以被子類繼承

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

Class的靜態(tài)屬性和實例屬性
靜態(tài)屬性指的是Class本身的屬性,即Class.propname,而不是定義在實例對象(this)上的屬性。

class Foo{}
Foo.prop = 1

以上代碼中prop是類Foo的靜態(tài)屬性。采用直接賦值的形式。還可以這樣寫:

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

手寫一個函數(shù)實現(xiàn)new的功能

首先我們需要明白new干了什么事情,new一個函數(shù)之后,返回了一個對象,該對象能夠訪問函數(shù)的原型。

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

待續(xù)......

寫在最后:文中內容大多為自己平時從各種途徑學習總結,文中參考文章大多收錄在我的個人博客里,歡迎閱覽http://www.tianleilei.cn

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容