看完這幾道 JavaScript 面試題,讓你與考官對答如流(中)
接上篇《看完這幾道 JavaScript 面試題,讓你與考官對答如流(上)》
26. 什么是 IIFE,它的用途是什么?
IIFE或立即調用的函數表達式是在創建或聲明后將被調用或執行的函數。創建IIFE的語法是,將function (){}包裹在在括號()內,然后再用另一個括號()調用它,如:(function(){})()
- (function(){...}());(function(){...})();(functionnamed(params){...})();(()=>{});(function(global){...})(window);constutility=(function(){return{...}})
這些示例都是有效的IIFE。倒數第二個救命表明我們可以將參數傳遞給IIFE函數。最后一個示例表明,我們可以將IIFE的結果保存到變量中,以便稍后使用。
IIFE的一個主要作用是避免與全局作用域內的其他變量命名沖突或污染全局命名空間,來個例子。
- <scriptsrcscriptsrc="https://cdnurl.com/somelibrary.js"></script>
假設我們引入了一個omelibr.js的鏈接,它提供了一些我們在代碼中使用的全局函數,但是這個庫有兩個方法我們沒有使用:createGraph和drawGraph,因為這些方法都有bug。我們想實現自己的createGraph和drawGraph方法。
解決此問題的一種方法是直接覆蓋:
- <scriptsrcscriptsrc="https://cdnurl.com/somelibrary.js"></script><script>functioncreateGraph(){//createGraphlogichere}functiondrawGraph(){//drawGraphlogichere}</script>
當我們使用這個解決方案時,我們覆蓋了庫提供給我們的那兩個方法。
另一種方式是我們自己改名稱:
- <scriptsrcscriptsrc="https://cdnurl.com/somelibrary.js"></script><script>functionmyCreateGraph(){//createGraphlogichere}functionmyDrawGraph(){//drawGraphlogichere}</script>
當我們使用這個解決方案時,我們把那些函數調用更改為新的函數名。
還有一種方法就是使用IIFE:
- <scriptsrcscriptsrc="https://cdnurl.com/somelibrary.js"></script><script>constgraphUtility=(function(){functioncreateGraph(){//createGraphlogichere}functiondrawGraph(){//drawGraphlogichere}return{createGraph,drawGraph}})</script>
在此解決方案中,我們要聲明了graphUtility 變量,用來保存IIFE執行的結果,該函數返回一個包含兩個方法createGraph和drawGraph的對象。
IIFE 還可以用來解決一個常見的面試題:
- varli=document.querySelectorAll('.list-group>li');for(vari=0,len=li.length;i<len;i++){li[i].addEventListener('click',function(e){console.log(i);})
假設我們有一個帶有list-group類的ul元素,它有5個li子元素。當我們單擊單個li元素時,打印對應的下標值。但在此外上述代碼不起作用,這里每次點擊 li 打印 i 的值都是5,這是由于閉包的原因。
閉包只是函數記住其當前作用域,父函數作用域和全局作用域的變量引用的能力。當我們在全局作用域內使用var關鍵字聲明變量時,就創建全局變量i。因此,當我們單擊li元素時,它將打印5,因為這是稍后在回調函數中引用它時i的值。
使用 IIFE 可以解決此問題:
- varli=document.querySelectorAll('.list-group>li');for(vari=0,len=li.length;i<len;i++){(function(currentIndex){li[currentIndex].addEventListener('click',function(e){console.log(currentIndex);})})(i);}
該解決方案之所以行的通,是因為IIFE會為每次迭代創建一個新的作用域,我們捕獲i的值并將其傳遞給currentIndex參數,因此調用IIFE時,每次迭代的currentIndex值都是不同的。
27. Function.prototype.apply 方法的用途是什么?
apply() 方法調用一個具有給定this值的函數,以及作為一個數組(或類似數組對象)提供的參數。
- constdetails={message:'HelloWorld!'};functiongetMessage(){returnthis.message;}getMessage.apply(details);//'HelloWorld!'
call()方法的作用和 apply() 方法類似,區別就是call()方法接受的是參數列表,而apply()方法接受的是一個參數數組。
- constperson={name:"MarkoPolo"};functiongreeting(greetingMessage){return`${greetingMessage}${this.name}`;}greeting.apply(person,['Hello']);//"HelloMarkoPolo!"
28. `Function.prototype.call` 方法的用途是什么?
call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。
- constdetails={message:'HelloWorld!'};functiongetMessage(){returnthis.message;}getMessage.call(details);//'HelloWorld!'
注意:該方法的語法和作用與 apply() 方法類似,只有一個區別,就是call() 方法接受的是一個參數列表,而 apply() 方法接受的是一個包含多個參數的數組。
- constperson={name:"MarkoPolo"};functiongreeting(greetingMessage){return`${greetingMessage}${this.name}`;}greeting.call(person,'Hello');//"HelloMarkoPolo!"
29. Function.prototype.apply 和 Function.prototype.call 之間有什么區別?
apply()方法可以在使用一個指定的 this 值和一個參數數組(或類數組對象)的前提下調用某個函數或方法。call()方法類似于apply(),不同之處僅僅是call()接受的參數是參數列表。
- constobj1={result:0};constobj2={result:0};functionreduceAdd(){letresult=0;for(leti=0,len=arguments.length;i<len;i++){result+=arguments[i];}this.result=result;}reduceAdd.apply(obj1,[1,2,3,4,5]);//15reduceAdd.call(obj2,1,2,3,4,5);//15
30. Function.prototype.bind 的用途是什么?
bind() 方法創建一個新的函數,在 bind() 被調用時,這個新函數的this 被指定為 bind() 的第一個參數,而其余參數將作為新函數的參數,供調用時使用。
- importReactfrom'react';classMyComponentextendsReact.Component{constructor(props){super(props);this.state={value:""}thisthis.handleChange=this.handleChange.bind(this);//將“handleChange”方法綁定到“MyComponent”組件}handleChange(e){//dosomethingamazinghere}render(){return(<><inputtypeinputtype={this.props.type}value={this.state.value}onChange={this.handleChange}/></>)}}
31. 什么是函數式編程? JavaScript 的哪些特性使其成為函數式語言的候選語言?
函數式編程(通常縮寫為FP)是通過編寫純函數,避免共享狀態、可變數據、副作用 來構建軟件的過程。數式編程是聲明式 的而不是命令式 的,應用程序的狀態是通過純函數流動的。與面向對象編程形成對比,面向對象中應用程序的狀態通常與對象中的方法共享和共處。
函數式編程是一種編程范式 ,這意味著它是一種基于一些基本的定義原則(如上所列)思考軟件構建的方式。當然,編程范示的其他示例也包括面向對象編程和過程編程。
函數式的代碼往往比命令式或面向對象的代碼更簡潔,更可預測,更容易測試 - 但如果不熟悉它以及與之相關的常見模式,函數式的代碼也可能看起來更密集雜亂,并且 相關文獻對新人來說是不好理解的。
JavaScript支持閉包和高階函數是函數式編程語言的特點。
32. 什么是高階函數?
高階函數只是將函數作為參數或返回值的函數。
- functionhigherOrderFunction(param,callback){returncallback(param);}
33. 為什么函數被稱為一等公民?
在JavaScript中,函數不僅擁有一切傳統函數的使用方式(聲明和調用),而且可以做到像簡單值一樣賦值(var func = function(){})、傳參(function func(x,callback){callback();})、返回(function(){return function(){}}),這樣的函數也稱之為第一級函數(First-class Function)。不僅如此,JavaScript中的函數還充當了類的構造函數的作用,同時又是一個Function類的實例(instance)。這樣的多重身份讓JavaScript的函數變得非常重要。
34. 手動實現 `Array.prototype.map 方法`
map() 方法創建一個新數組,其結果是該數組中的每個元素都調用一個提供的函數后返回的結果。
- functionmap(arr,mapCallback){//首先,檢查傳遞的參數是否正確。if(!Array.isArray(arr)||!arr.length||typeofmapCallback!=='function'){return[];}else{letresult=[];//每次調用此函數時,我們都會創建一個result數組//因為我們不想改變原始數組。for(leti=0,len=arr.length;i<len;i++){result.push(mapCallback(arr[i],i,arr));//將mapCallback返回的結果push到result數組中}returnresult;}}
35. 手動實現`Array.prototype.filter`方法
filter() 方法創建一個新數組, 其包含通過所提供函數實現的測試的所有元素。
- functionfilter(arr,filterCallback){//首先,檢查傳遞的參數是否正確。if(!Array.isArray(arr)||!arr.length||typeoffilterCallback!=='function'){return[];}else{letresult=[];//每次調用此函數時,我們都會創建一個result數組//因為我們不想改變原始數組。for(leti=0,len=arr.length;i<len;i++){//檢查filterCallback的返回值是否是真值if(filterCallback(arr[i],i,arr)){//如果條件為真,則將數組元素push到result中result.push(arr[i]);}}returnresult;//returntheresultarray}}
36. 手動實現`Array.prototype.reduce`方法
reduce() 方法對數組中的每個元素執行一個由您提供的reducer函數(升序執行),將其結果匯總為單個返回值。
- functionreduce(arr,reduceCallback,initialValue){//首先,檢查傳遞的參數是否正確。if(!Array.isArray(arr)||!arr.length||typeofreduceCallback!=='function'){return[];}else{//如果沒有將initialValue傳遞給該函數,我們將使用第一個數組項作為initialValueinitialValuelethasInitialValue=initialValue!==undefined;letvalue=hasInitialValue?initialValue:arr[0];、//如果有傳遞initialValue,則索引從1開始,否則從0開始for(leti=hasInitialValue?0:1,len=arr.length;i<len;i++){value=reduceCallback(value,arr[i],i,arr);}returnvalue;}}
37. arguments 的對象是什么?
arguments對象是函數中傳遞的參數值的集合。它是一個類似數組的對象,因為它有一個length屬性,我們可以使用數組索引表示法arguments[1]來訪問單個值,但它沒有數組中的內置方法,如:forEach、reduce、filter和map。
我們可以使用Array.prototype.slice將arguments對象轉換成一個數組。
- functionone(){returnArray.prototype.slice.call(arguments);}
注意:箭頭函數中沒有arguments對象。
- functionone(){returnarguments;}consttwo=function(){returnarguments;}constthree=functionthree(){returnarguments;}constfour=()=>arguments;four();//Throwsanerror-argumentsisnotdefined
當我們調用函數four時,它會拋出一個ReferenceError: arguments is not defined error。使用rest語法,可以解決這個問題。
- constfour=(...args)=>args;
這會自動將所有參數值放入數組中。
38. 如何創建一個沒有 prototype(原型)的對象?
我們可以使用Object.create方法創建沒有原型的對象。
- consto1={};console.log(o1.toString());//[objectObject]consto2=Object.create(null);console.log(o2.toString());//throwsanerroro2.toStringisnotafunction
39. 為什么在調用這個函數時,代碼中的`b`會變成一個全局變量?
- functionmyFunc(){leta=b=0;}myFunc();
原因是賦值運算符是從右到左的求值的。這意味著當多個賦值運算符出現在一個表達式中時,它們是從右向左求值的。所以上面代碼變成了這樣:
- functionmyFunc(){leta=(b=0);}myFunc();
首先,表達式b = 0求值,在本例中b沒有聲明。因此,JS引擎在這個函數外創建了一個全局變量b,之后表達式b = 0的返回值為0,并賦給新的局部變量a。
我們可以通過在賦值之前先聲明變量來解決這個問題。
- functionmyFunc(){leta,b;a=b=0;}myFunc();
40. ECMAScript 是什么?
ECMAScript 是編寫腳本語言的標準,這意味著JavaScript遵循ECMAScript標準中的規范變化,因為它是JavaScript的藍圖。
ECMAScript 和 Javascript,本質上都跟一門語言有關,一個是語言本身的名字,一個是語言的約束條件
只不過發明JavaScript的那個人(Netscape公司),把東西交給了ECMA(European Computer Manufacturers Association),這個人規定一下他的標準,因為當時有java語言了,又想強調這個東西是讓ECMA這個人定的規則,所以就這樣一個神奇的東西誕生了,這個東西的名稱就叫做ECMAScript。
javaScript = ECMAScript + DOM + BOM(自認為是一種廣義的JavaScript)
ECMAScript說什么JavaScript就得做什么!
JavaScript(狹義的JavaScript)做什么都要問問ECMAScript我能不能這樣干!如果不能我就錯了!能我就是對的!
——突然感覺JavaScript好沒有尊嚴,為啥要搞個人出來約束自己,
那個人被創造出來也好委屈,自己被創造出來完全是因為要約束JavaScript。
41. ES6或ECMAScript 2015有哪些新特性?
- 箭頭函數
- 類
- 模板字符串
- 加強的對象字面量
- 對象解構
- Promise
- 生成器
- 模塊
- Symbol
- 代理
- Set
- 函數默認參數
- rest 和展開
- 塊作用域
42. `var`,`let`和`const`的區別是什么?
var聲明的變量會掛載在window上,而let和const聲明的變量不會:
- vara=100;console.log(a,window.a);//10100100letb=10;console.log(b,window.b);//110undefinedconstc=1;console.log(c,window.c);//1undefined
var聲明變量存在變量提升,let和const不存在變量提升:
- console.log(a);//undefined===>a已聲明還沒賦值,默認得到undefined值vara=100;console.log(b);//報錯:b isnotdefined===>找不到b這個變量letb=10;console.log(c);//報錯:c isnotdefined===>找不到c這個變量constc=10;
let和const聲明形成塊作用域
- if(1){vara=100;letb=10;}console.log(a);//100console.log(b)//報錯:b is not defined ===>找不到b這個變量-------------------------------------------------------------if(1){vara=100;constc=1;}console.log(a);//100console.log(c)//報錯:c is not defined ===>找不到c這個變量
同一作用域下let和const不能聲明同名變量,而var可以
- vara=100;console.log(a);//10100vara=10;console.log(a);//10-------------------------------------leta=100;leta=10;//控制臺報錯:Identifier 'a' has already been declared ===>標識符a已經被聲明了。
暫存死區
- vara=100;if(1){a=10;//在當前塊作用域中存在a使用let/const聲明的情況下,給a賦值10時,只會在當前作用域找變量a,//而這時,還未到聲明時候,所以控制臺Error:aisnotdefinedleta=1;}
const
- /** 1、一旦聲明必須賦值,不能使用null占位。** 2、聲明后不能再修改** 3、如果聲明的是復合類型數據,可以修改其屬性***/consta=100;constlist=[];list[0]=10;console.log(list); //[10]constobj={a:100};obj.name='apple';obj.a=10000;console.log(obj); //{a:10000,name:'apple'}
43. 什么是箭頭函數?
箭頭函數表達式的語法比函數表達式更簡潔,并且沒有自己的this,arguments,super或new.target。箭頭函數表達式更適用于那些本來需要匿名函數的地方,并且它不能用作構造函數。
- //ES5VersionvargetCurrentDate=function(){returnnewDate();}//ES6VersionconstgetCurrentDate=()=>newDate();
在本例中,ES5 版本中有function(){}聲明和return關鍵字,這兩個關鍵字分別是創建函數和返回值所需要的。在箭頭函數版本中,我們只需要()括號,不需要 return 語句,因為如果我們只有一個表達式或值需要返回,箭頭函數就會有一個隱式的返回。
- //ES5Versionfunctiongreet(name){return'Hello'+name+'!';}//ES6Versionconstgreet=(name)=>`Hello${name}`;constgreet2=name=>`Hello${name}`;
我們還可以在箭頭函數中使用與函數表達式和函數聲明相同的參數。如果我們在一個箭頭函數中有一個參數,則可以省略括號。
- constgetArgs=()=>argumentsconstgetArgs2=(...rest)=>rest
箭頭函數不能訪問arguments對象。所以調用第一個getArgs函數會拋出一個錯誤。相反,我們可以使用rest參數來獲得在箭頭函數中傳遞的所有參數。
- constdata={result:0,nums:[1,2,3,4,5],computeResult(){//這里的“this”指的是“data”對象constaddAll=()=>{returnthis.nums.reduce((total,cur)=>total+cur,0)};this.result=addAll();}};
箭頭函數沒有自己的this值。它捕獲詞法作用域函數的this值,在此示例中,addAll函數將復制computeResult 方法中的this值,如果我們在全局作用域聲明箭頭函數,則this值為 window 對象。
44. 什么是類?
類(class)是在 JS 中編寫構造函數的新方法。它是使用構造函數的語法糖,在底層中使用仍然是原型和基于原型的繼承。
- //ES5VersionfunctionPerson(firstName,lastName,age,address){this.firstName=firstName;this.lastName=lastName;this.age=age;this.address=address;}Person.self=function(){returnthis;}Person.prototype.toString=function(){return"[objectPerson]";}Person.prototype.getFullName=function(){returnthis.firstName+""+this.lastName;}//ES6VersionclassPerson{constructor(firstName,lastName,age,address){this.lastName=lastName;this.firstName=firstName;this.age=age;this.address=address;}staticself(){returnthis;}toString(){return"[objectPerson]";}getFullName(){return`${this.firstName}${this.lastName}`;}}
重寫方法并從另一個類繼承。
- //ES5VersionEmployee.prototype=Object.create(Person.prototype);functionEmployee(firstName,lastName,age,address,jobTitle,yearStarted){Person.call(this,firstName,lastName,age,address);this.jobTitle=jobTitle;this.yearStarted=yearStarted;}Employee.prototype.describe=function(){return`Iam${this.getFullName()}andIhaveapositionof${this.jobTitle}andIstartedat${this.yearStarted}`;}Employee.prototype.toString=function(){return"[objectEmployee]";}//ES6VersionclassEmployeeextendsPerson{//Inheritsfrom"Person"classconstructor(firstName,lastName,age,address,jobTitle,yearStarted){super(firstName,lastName,age,address);this.jobTitle=jobTitle;this.yearStarted=yearStarted;}describe(){return`Iam${this.getFullName()}andIhaveapositionof${this.jobTitle}andIstartedat${this.yearStarted}`;}toString(){//Overridingthe"toString"methodof"Person"return"[objectEmployee]";}}
所以我們要怎么知道它在內部使用原型?
- classSomething{}functionAnotherSomething(){}constas=newAnotherSomething();consts=newSomething();console.log(typeofSomething);//"function"console.log(typeofAnotherSomething);//"function"console.log(as.toString());//"[objectObject]"console.log(as.toString());//"[objectObject]"console.log(as.toString===Object.prototype.toString);//trueconsole.log(s.toString===Object.prototype.toString);//true
45. 什么是模板字符串?
模板字符串是在 JS 中創建字符串的一種新方法。我們可以通過使用反引號使模板字符串化。
- //ES5Versionvargreet='HiI\'mMark';//ES6Versionletgreet=`HiI'mMark`;
在 ES5 中我們需要使用一些轉義字符來達到多行的效果,在模板字符串不需要這么麻煩:
- //ES5VersionvarlastWords='\n'+'I\n'+'Am\n'+'IronMan\n';//ES6VersionletlastWords=`IAmIronMan`;
在ES5版本中,我們需要添加\n以在字符串中添加新行。在模板字符串中,我們不需要這樣做。
- //ES5Versionfunctiongreet(name){return'Hello'+name+'!';}//ES6Versionfunctiongreet(name){return`Hello${name}!`;}
在 ES5 版本中,如果需要在字符串中添加表達式或值,則需要使用+運算符。在模板字符串s中,我們可以使用${expr}嵌入一個表達式,這使其比 ES5 版本更整潔。
46. 什么是對象解構?
對象析構是從對象或數組中獲取或提取值的一種新的、更簡潔的方法。假設有如下的對象:
- constemployee={firstName:"Marko",lastName:"Polo",position:"SoftwareDeveloper",yearHired:2017};
從對象獲取屬性,早期方法是創建一個與對象屬性同名的變量。這種方法很麻煩,因為我們要為每個屬性創建一個新變量。假設我們有一個大對象,它有很多屬性和方法,用這種方法提取屬性會很麻煩。
- varfirstName=employee.firstName;varlastName=employee.lastName;varposition=employee.position;varyearHired=employee.yearHired;
使用解構方式語法就變得簡潔多了:
- {firstName,lastName,position,yearHired}=employee;
我們還可以為屬性取別名:
- let{firstName:fName,lastName:lName,position,yearHired}=employee;
當然如果屬性值為 undefined 時,我們還可以指定默認值:
- let{firstName="Mark",lastName:lName,position,yearHired}=employee;
47. 什么是 ES6 模塊?
模塊使我們能夠將代碼基礎分割成多個文件,以獲得更高的可維護性,并且避免將所有代碼放在一個大文件中。在 ES6 支持模塊之前,有兩個流行的模塊。
- CommonJS-Node.js
- AMD(異步模塊定義)-瀏覽器
基本上,使用模塊的方式很簡單,import用于從另一個文件中獲取功能或幾個功能或值,同時export用于從文件中公開功能或幾個功能或值。
(1) 導出
使用 ES5 (CommonJS)
- //使用ES5CommonJS-helpers.jsexports.isNull=function(val){returnval===null;}exports.isUndefined=function(val){returnval===undefined;}exports.isNullOrUndefined=function(val){returnexports.isNull(val)||exports.isUndefined(val);}
使用 ES6 模塊
- //使用ES6Modules-helpers.jsexportfunctionisNull(val){returnval===null;}exportfunctionisUndefined(val){returnval===undefined;}exportfunctionisNullOrUndefined(val){returnisNull(val)||isUndefined(val);}
在另一個文件中導入函數
- //使用ES5(CommonJS)-index.jsconsthelpers=require('./helpers.js');//helpershelpersisanobjectconstisNull=helpers.isNull;constisUndefined=helpers.isUndefined;constisNullOrUndefined=helpers.isNullOrUndefined;//orifyourenvironmentsupportsDestructuringconst{isNull,isUndefined,isNullOrUndefined}=require('./helpers.js');-------------------------------------------------------//ES6Modules-index.jsimport*ashelpersfrom'./helpers.js';//helpersisanobject//orimport{isNull,isUndefined,isNullOrUndefinedasisValid}from'./helpers.js';//using"as"forrenamingnamedexports
(2) 在文件中導出單個功能或默認導出
使用 ES5 (CommonJS)
- //使用ES5(CommonJS)-index.jsclassHelpers{staticisNull(val){returnval===null;}staticisUndefined(val){returnval===undefined;}staticisNullOrUndefined(val){returnthis.isNull(val)||this.isUndefined(val);}}module.exports=Helpers;
使用ES6 Modules
- //使用ES6Modules-helpers.jsclassHelpers{staticisNull(val){returnval===null;}staticisUndefined(val){returnval===undefined;}staticisNullOrUndefined(val){returnthis.isNull(val)||this.isUndefined(val);}}exportdefaultHelpers
(3) 從另一個文件導入單個功能
使用ES5 (CommonJS)
- //使用ES5(CommonJS)-index.jsconstHelpers=require('./helpers.js');console.log(Helpers.isNull(null));
使用 ES6 Modules
- importHelpersfrom'.helpers.js'console.log(Helpers.isNull(null));
48. 什么是`Set`對象,它是如何工作的?
Set 對象允許你存儲任何類型的唯一值,無論是原始值或者是對象引用。
我們可以使用Set構造函數創建Set實例。
- constset1=newSet();constset2=newSet(["a","b","c","d","d","e"]);
我們可以使用add方法向Set實例中添加一個新值,因為add方法返回Set對象,所以我們可以以鏈式的方式再次使用add。如果一個值已經存在于Set對象中,那么它將不再被添加。
- set2.add("f");set2.add("g").add("h").add("i").add("j").add("k").add("k");//后一個“k”不會被添加到set對象中,因為它已經存在了
我們可以使用has方法檢查Set實例中是否存在特定的值。
- set2.has("a")//trueset2.has("z")//true
我們可以使用size屬性獲得Set實例的長度。
- set2.size//returns10
可以使用clear方法刪除 Set 中的數據。
- set2.clear();
我們可以使用Set對象來刪除數組中重復的元素。
- constnumbers=[1,2,3,4,5,6,6,7,8,8,5];constuniqueNums=[...newSet(numbers)];//[1,2,3,4,5,6,7,8]
49. 什么是回調函數?
回調函數是一段可執行的代碼段,它作為一個參數傳遞給其他的代碼,其作用是在需要的時候方便調用這段(回調函數)代碼。
在JavaScript中函數也是對象的一種,同樣對象可以作為參數傳遞給函數,因此函數也可以作為參數傳遞給另外一個函數,這個作為參數的函數就是回調函數。
- constbtnAdd=document.getElementById('btnAdd');btnAdd.addEventListener('click',functionclickCallback(e){//dosomethinguseless});
在本例中,我們等待id為btnAdd的元素中的click事件,如果它被單擊,則執行clickCallback函數。回調函數向某些數據或事件添加一些功能。
數組中的reduce、filter和map方法需要一個回調作為參數。回調的一個很好的類比是,當你打電話給某人,如果他們不接,你留下一條消息,你期待他們回調。調用某人或留下消息的行為是事件或數據,回調是你希望稍后發生的操作。
50. Promise 是什么?
Promise 是異步編程的一種解決方案:從語法上講,promise是一個對象,從它可以獲取異步操作的消息;從本意上講,它是承諾,承諾它過一段時間會給你一個結果。promise有三種狀態:pending(等待態),fulfiled(成功態),rejected(失敗態);狀態一旦改變,就不會再變。創造promise實例后,它會立即執行。
- fs.readFile('somefile.txt',function(e,data){if(e){console.log(e);}console.log(data);});
如果我們在回調內部有另一個異步操作,則此方法存在問題。我們將有一個混亂且不可讀的代碼。此代碼稱為“回調地獄”。
- //回調地獄fs.readFile('somefile.txt',function(e,data){//yourcodeherefs.readdir('directory',function(e,files){//yourcodeherefs.mkdir('directory',function(e){//yourcodehere})})})
如果我們在這段代碼中使用promise,它將更易于閱讀、理解和維護。
- promReadFile('file/path').then(data=>{returnpromReaddir('directory');}).then(data=>{returnpromMkdir('directory');}).catch(e=>{console.log(e);})
promise有三種不同的狀態:
- pending:初始狀態,完成或失敗狀態的前一個狀態
- fulfilled:操作成功完成
- rejected:操作失敗
pending 狀態的 Promise 對象會觸發 fulfilled/rejected 狀態,在其狀態處理方法中可以傳入參數/失敗信息。當操作成功完成時,Promise 對象的 then 方法就會被調用;否則就會觸發 catch。如:
- constmyFirstPromise=newPromise((resolve,reject)=>{setTimeout(function(){resolve("成功!");},250);});myFirstPromise.then((data)=>{console.log("Yay!"+data);}).catch((e)=>{...});
由于篇幅過長,我將此系列分成上中下三篇,下篇我們在見。