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的使用
- Promise的狀態
Promise的狀態表示此時異步執行的狀態。Promise一共有三種狀態:
- pending:初始狀態
- fulfilled:操作成功狀態
- rejected:操作失敗狀態
特點:
Promise三個狀態不受外界影響,一旦狀態改變,就不會再發生變化,Promise對象的狀態改變只有兩種可能,從pening變為fulfilled和從pending變為rejected.
- 基本用法
ES6規定,Promise對象是一個構造函數,用來生成Promise實例。
let promise = new Promise((resolve,reject)=>{
//...異步代碼
if(/* 異步成功 */){
resolve(value)
}else{
reject(error)
}
})
Promise構造函數接收一個函數作為參數,該函數有兩個參數分別為resolve
和reject
。它們是兩個函數,由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對象執行結束了。
- 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的針對異步更優雅的解決方案。
用法:
- 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
})
- 配合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表達式的運算結果。
- 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')
展開(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”去使用。
特點:
- 變量必須聲明才可使用。
- 創設eval作用域,除了全局作用域,函數作用域外,新增eval作用域。
- with()被禁用,with語句用于設置代碼在特定對象中的作用域。
- caller/callee被禁用。
- delete使用在var 聲明的變量活掛在window的變量上會報錯。
- delete不可刪除屬性的對象時會報錯。
- 對一個對象的只讀屬性進行賦值會報錯。
- 對象有重名屬性將報錯。
- 函數有重名的參數會報錯。
- arguments嚴格定義為參數,不再與形參綁定。
- 函數中的this不再指向window。
高階函數
什么是高階函數:
- 如果一個函數的參數是一個函數(回調函數)
- 如果一個函數的返回值是一個函數,當前這個函數也是一個高階函數
應用場景:擴展業務代碼
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") //傳參
函數柯里化
例子:判斷數據類型
- typeof 不能判斷對象類型
- constructor 判斷該數據是由誰構造出來
- instanceof 判斷誰是誰的實例proto
- 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中了。
手寫一個函數實現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