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是非常好用的語法糖??梢哉J為是基于Promise的針對異步更優雅的解決方案。

用法:

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

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

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()往控制臺打印數據也是副作用。

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

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

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

純函數也是函數式編程的一個很重要的概念,很多類庫,框架也都有使用到,比如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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373