JavaScript (JS) 是一種編程語言,為通常用于客戶端(client-side)的網頁動態腳本,不過,也常通過像Node.js這樣的包,用于 服務器端(server-side)。
今天,發一篇關于Js基礎知識點的文章,為更多的新人指路??倳腥嗽谀愕那胺綖槟闾铰?,前行之路,你不孤單~
先來個目錄結構
───1、變量聲明 │?└───JavaScript?的數據類型分類和判斷 │?└───引用類型和值類型 ───2、原型與原型鏈(繼承) │?└───原型和原型鏈 ───3、作用域和閉包 │?└───作用域 │?└───什么是閉包,如何形成? ───4、如何理解同步和異步 │?└───同步?vs?異步 │?└───異步和單線程 │?└───前端異步的場景描述 ───5、簡單描述一下對?ES6/ES7?的了解 │?└───解構賦值 │?└───箭頭函數 │?└───Promise?對象 │?└───Set?和?Map?數據結構 復制代碼
1、變量聲明
1-1、JavaScript 的數據類型分類和判斷
在 JavaScript 中,共有7種基本類型:
string,
number,
bigint,
boolean,
null,
undefined,
symbol (ECMAScript 2016新增)。
其中string、number、Boolean、undefined、Null、symbol是6種原始類型。
值得注意的是:原始類型中不包含 Object。
類型判斷用到哪些方法?
1、typeof
typeof xxx 得到的值有以下幾種類型: undefined boolean number string object function symbol。
例如:
console.log(typeof?42); //?expected?output:?"number" console.log(typeof?'blubber'); //?expected?output:?"string" console.log(typeof?true); //?expected?output:?"boolean" console.log(typeof?declaredButUndefinedVariable); //?expected?output:?"undefined"; 復制代碼
typeof null 結果是 object ,JavaScript 誕生以來便如此,由于 null 代表的是空指針(大多數平臺下值為 0x00),因此,null 的類型標簽是 0,typeof null 也因此返回 "object"。
typeof [1, 2] 結果是 object ,結果中沒有array 這一項,引用類型除了function其他的全部都是 object
typeof Symbol() 用 typeof 獲取 symbol 類型的值得到的是 symbol ,Symbol實例是唯一且不可改變的這是 ES6 新增的知識點.
2、instanceof
用于實例和構造函數的對應。例如判斷一個變量是否是數組,使用 typeof 無法判斷,但可 以使用 [1, 2] instanceof Array 來判斷,返回true。因為, [1, 2] 是數組,它的構造函數就是 Array 。同理:
function?Car(make,?model,?year)?{ ?this.make?=?make; ?this.model?=?model; ?this.year?=?year; } var?auto?=?new?Car('Honda',?'Accord',?1998); console.log(auto?instanceof?Car); //?expected?output:?true console.log([1,?2]?instanceof?Array); //?expected?output:?true 復制代碼
1-2、引用類型和值類型
除了原始類型,JS 還有引用類型,上面提到的 typeof 識別出來的類型中,只有 object 和 function 是引用類型,其他都是值類型。
根據 JavaScript 中的變量類型傳遞方式,又分為值類型和引用類型,值類型變量包括 Boolean、String、Number、Undefined、Null,引用類型包括了 Object 類的所有,如 Date、Array、Function 等。在參數傳遞方式上,值類型是按值傳遞,引用類型是按共享 傳遞。
//?值類型 var?a?=?1; var?b?=?a; b?=?3 console.log(a)?//?1 console.log(b)?//?3 //?a?b?都是值類型,兩者分別修改賦值,相互之間沒有任何影響。 復制代碼 //?引用類型 var?a?=?{x:?10,?y:?20} var?b?=?a b.x?=?100 b.y?=?200 console.log(a)?//?{x:?100,?y:?200} console.log(b)?//?{x:?100,?y:?200} 復制代碼
a 和 b 都是引用類型。在執行了 b = a 之后,修改 b 的屬性值, a 的也跟著 變化。因為 a 和 b 都是引用類型,指向了同一個內存地址,即兩者引用的是同一個值,因 此 b 修改屬性時, a 的值隨之改動。
2、原型與原型鏈(繼承)
JavaScript 常被描述為一種基于原型的語言 (prototype-based language)——每個對象擁有一個原型對象,對象以其原型為模板、從原型繼承方法和屬性。原型對象也可能擁有原型,并從中繼承方法和屬性,一層一層、以此類推。這種關系常被稱為原型鏈 (prototype chain),它解釋了為何一個對象會擁有定義在其他對象中的屬性和方法。
注意: 理解對象的原型(可以通過Object.getPrototypeOf(obj)或者已被棄用的__proto__屬性獲得)與構造函數的prototype屬性之間的區別是很重要的。前者是每個實例上都有的屬性,后者是構造函數的屬性。也就是說,Object.getPrototypeOf(new Foobar())和Foobar.prototype指向著同一個對象。
在javascript中,函數可以有屬性。 每個函數都有一個特殊的屬性叫作原型(prototype),正如下面所展示的。請注意,下面的代碼是獨立的一段(在網頁中沒有其他代碼的情況下,這段代碼是安全的)。為了最好的學習體驗,你最好打開一個控制臺 (在Chrome和Firefox中,可以按Ctrl+Shift+I 來打開)切換到"Console"選項卡, 復制粘貼下面的JavaScript代碼,然后按回車來運行。
function?doSomething(){} console.log(?doSomething.prototype?); //?不管您如何聲明函數,javascript中的函數總是有一個默認的原型屬性 var?doSomething?=?function(){};? console.log(?doSomething.prototype?); 復制代碼
正如上面所看到的, doSomething 函數有一個默認的原型屬性,它在控制臺上面呈現了出來. 運行這段代碼之后,控制臺上面應該出現了像這樣的一個對象.
{ ?constructor:???doSomething(), ?__proto__:?{ ?constructor:???Object(), ?hasOwnProperty:???`hasOwnProperty`(), ?isPrototypeOf:???`isPrototypeOf`(), ?propertyIsEnumerable:???`propertyIsEnumerable`(), ?toLocaleString:???`toLocaleString`(), ?toString:???`toString`(), ?valueOf:???`valueOf`() ?} } 復制代碼
現在,我們可以添加一些屬性到 doSomething 的原型上面,如下所示:
function?doSomething(){} doSomething.prototype.foo?=?"bar"; console.log(?doSomething.prototype?); 復制代碼
輸出:
{ ?foo:?"bar", ?constructor:???doSomething(), ?__proto__:?{ ?constructor:???Object(), ?hasOwnProperty:???`hasOwnProperty`(), ?isPrototypeOf:???`isPrototypeOf`(), ?propertyIsEnumerable:???`propertyIsEnumerable`(), ?toLocaleString:???`toLocaleString`(), ?toString:???`toString`(), ?valueOf:???`valueOf`() ?} } 復制代碼
然后,我們可以使用 new 運算符來在現在的這個原型基礎之上,創建一個 doSomething 的實例。
function?doSomething(){} doSomething.prototype.foo?=?"bar";?//?add?a?property?onto?the?prototype var?doSomeInstancing?=?new?doSomething(); doSomeInstancing.prop?=?"some?value";?//?add?a?property?onto?the?object console.log(?doSomeInstancing?); 復制代碼
輸出:
{ ?prop:?"some?value", ?__proto__:?{ ?foo:?"bar", ?constructor:???doSomething(), ?__proto__:?{ ?constructor:???Object(), ?hasOwnProperty:???`hasOwnProperty`(), ?isPrototypeOf:???`isPrototypeOf`(), ?propertyIsEnumerable:???`propertyIsEnumerable`(), ?toLocaleString:???`toLocaleString`(), ?toString:???`toString`(), ?valueOf:???`valueOf`() ?} ?} } 復制代碼
就像上面看到的, doSomeInstancing 的 __proto__ 屬性就是doSomething.prototype. 但是這又有什么用呢? 好吧,當你訪問 doSomeInstancing 的一個屬性, 瀏覽器首先查找 doSomeInstancing 是否有這個屬性. 如果 doSomeInstancing 沒有這個屬性, 然后瀏覽器就會在 doSomeInstancing 的 __proto__ 中查找這個屬性(也就是 doSomething.prototype). 如果 doSomeInstancing 的 __proto__ 有這個屬性, 那么 doSomeInstancing 的 __proto__ 上的這個屬性就會被使用. 否則, 如果 doSomeInstancing 的 __proto__ 沒有這個屬性, 瀏覽器就會去查找 doSomeInstancing 的 __proto__ 的 __proto__ ,看它是否有這個屬性. 默認情況下, 所有函數的原型屬性的 __proto__ 就是 window.Object.prototype. 所以 doSomeInstancing 的 __proto__ 的 __proto__ (也就是 doSomething.prototype 的 __proto__ (也就是 Object.prototype)) 會被查找是否有這個屬性. 如果沒有在它里面找到這個屬性, 然后就會在 doSomeInstancing 的 __proto__ 的 __proto__ 的 __proto__ 里面查找. 然而這有一個問題: doSomeInstancing 的 __proto__ 的 __proto__ 的 __proto__ 不存在. 最后, 原型鏈上面的所有的 __proto__ 都被找完了, 瀏覽器所有已經聲明了的 __proto__ 上都不存在這個屬性,然后就得出結論,這個屬性是 undefined.
function?doSomething(){} doSomething.prototype.foo?=?"bar"; var?doSomeInstancing?=?new?doSomething(); doSomeInstancing.prop?=?"some?value"; console.log("doSomeInstancing.prop:?"?+?doSomeInstancing.prop); console.log("doSomeInstancing.foo:?"?+?doSomeInstancing.foo); console.log("doSomething.prop:?"?+?doSomething.prop); console.log("doSomething.foo:?"?+?doSomething.foo); console.log("doSomething.prototype.prop:?"?+?doSomething.prototype.prop); console.log("doSomething.prototype.foo:?"?+?doSomething.prototype.foo); 復制代碼
輸出:
doSomeInstancing.prop:?some?value doSomeInstancing.foo:?bar doSomething.prop:?undefined doSomething.foo:?undefined doSomething.prototype.prop:?undefined doSomething.prototype.foo:?bar 復制代碼
是不是看的頭大了,別擔心??纯催@個:
所有的引用類型(數組、對象、函數),都具有對象特性,即可自由擴展屬性( null除外)
所有的引用類型(數組、對象、函數),都有一個 __proto__ 屬性,屬性值是一個普通的對象
所有的函數,都有一個 prototype 屬性,屬性值也是一個普通的對象
所有的引用類型(數組、對象、函數), __proto__ 屬性值指向它的構造函數的prototype 屬性值
//?要點一:自由擴展屬性 var?obj?=?{};?obj.a?=?100; var?arr?=?[];?arr.a?=?100; function?fn?()?{} fn.a?=?100; //?要點二:__proto__ console.log(obj.__proto__); console.log(arr.__proto__); console.log(fn.__proto__); //?要點三:函數有?prototype console.log(fn.prototype) //?要點四:引用類型的?__proto__?屬性值指向它的構造函數的?prototype?屬性值 console.log(obj.__proto__?===?Object.prototype) 復制代碼
2-1、原型和原型鏈
原型
//?構造函數 function?Foo(name,?age)?{ ?this.name?=?name } Foo.prototype.alertName?=?function?()?{ ?alert(this.name) } //?創建示例 var?f?=?new?Foo('zhangsan') f.printName?=?function?()?{ ?console.log(this.name) } //?測試 f.printName() f.alertName() 復制代碼
執行 printName 時很好理解,但是執行 alertName 時發生了什么?這里再記住一個重點 當 試圖得到一個對象的某個屬性時,如果這個對象本身沒有這個屬性,那么會去它的 __proto__ (即它的構造函數的 prototype )中尋找,因此 f.alertName 就會找到 Foo.prototype.alertName 。
那么如何判斷這個屬性是不是對象本身的屬性呢?使用 hasOwnProperty ,常用的地方是遍 歷一個對象的時候。
var?item for?(item?in?f)?{ ?//?高級瀏覽器已經在?for?in?中屏蔽了來自原型的屬性,但是這里建議大家還是加上這個判斷,保證程序正常輸出 ?if?(f.hasOwnProperty(item))?{ ?console.log(item) ?} } 復制代碼
原型鏈
還是接著上面的示例,如果執行 f.toString() 時,又發生了什么?
f.printName() 復制代碼
因為 f 本身沒有 toString() ,并且 f.__proto__ (即 Foo.prototype )中也沒有 toString 。這個問題還是得拿出剛才那句話——當試圖得到一個對象的某個屬性時,如果 這個對象本身沒有這個屬性,那么會去它的 __proto__ (即它的構造函數的 prototype ) 中尋找。
如果在 f.proto?中沒有找到 toString ,那么就繼續去 f.proto.proto?中尋 找,因為 f.proto就是一個普通的對象而已嘛!
f.__proto__ 即 Foo.prototype ,沒有找到 toString ,繼續往上找
f.__proto__.__proto__ 即 Foo.prototype.__proto__ 。 Foo.prototype 就是一個普通 的對象,因此 Foo.prototype.__proto__ 就是 Object.prototype ,在這里可以找到toString
因此 f.toString 最終對應到了 Object.prototype.toString
這樣一直往上找,你會發現是一個鏈式的結構,所以叫做“原型鏈”。如果一直找到最上 層都沒有找到,那么就宣告失敗,返回 undefined 。最上層是什么 ——Object.prototype.__proto__ === null.原型鏈并不是無限的,原型鏈最終指向null。
參考文章:簡單粗暴地理解js原型鏈--js面向對象編程
微信請點擊閱讀原文更好的查看。
3、作用域和閉包
作用域和閉包是前端面試中,最可能考查的知識點
3-1、作用域
作用域就是一個獨立的地盤,讓變量不會外泄、暴露出去。
變量的作用域無非就是兩種:全局變量和局部變量。
全局作用域:
最外層函數定義的變量擁有全局作用域,即對任何內部函數來說,都是可以訪問的
var?outerVar?=?"outer"; function?fn(){ ?console.log(outerVar); } fn();?//?result:outer 復制代碼
局部作用域:
和全局作用域相反,局部作用域一般只在固定的代碼片段內可訪問到,而對于函數外部是無法訪問的,最常見的例如函數內部
function?fn(){ ?var?innerVar?=?"inner"; } fn(); console.log(innerVar);?//?ReferenceError:?innerVar?is?not?defined 復制代碼
這就是為何 jQuery、Zepto 等庫的源碼,所有的代碼都會放在 (function(){....})() 中。因為放在里面的所有變量,都不會被外泄和暴露,不會污染到外面,不會對其他的庫 或者 JS 腳本造成影響。這是函數作用域的一個體現。
注意: ES6 中開始加入了塊級作用域,使用 let 定義變量即可,如下:
if?(true)?{ ?let?name?=?'Tom' } console.log(name)?//?報錯,因為let定義的name是在if這個塊級作用域 復制代碼
作用域鏈?如下代碼中, console.log(a) 要得到 a 變量,但是在當前的作用域中沒有定義 a,一層一層向上尋找,直到找到全局作用域還是沒找到,就宣布放棄。這種一層一層的關系,就是?作用域鏈。
var?a?=?5 function?fn()?{ ?var?b?=?10 ?console.log(a) ?console.log(b) } fn() 復制代碼
3-2、 什么是閉包,如何形成
那么什么叫閉包?觀點很多,出現頻率最高的有以下兩個觀點:
函數套函數。
在函數外獲取函數內變量的技術。
function?F1()?{ ?var?a?=?100 ?return?function?()?{ ?console.log(a) ?} } var?f1?=?F1() var?a?=?200 f1() 復制代碼
閉包主要有兩個應用場景:
函數作為返回值,上面的例子就是
函數作為參數傳遞,看以下例子
function?F1()?{ ?var?a?=?100 ?return?function?()?{ ?console.log(a) ?} } function?F2(f1)?{ ?var?a?=?200 ?console.log(f1()) } var?f1?=?F1() F2(f1) 復制代碼
關于this對象
var?name?=?"The?Window"; var?object?=?{ ?name?:?"My?Object", ?getNameFunc?:?function(){ ?return?function(){ ?return?this.name; ?}; ?} }; alert(object.getNameFunc()());?//?result:The?Window 復制代碼
this對象是在運行時基于函數的執行環境綁定的:在全局函數中,this等于window,而當函數被作為某個對象調用時,this等于那個對象。不過,匿名函數具有全局性,因此this對象同常指向window。
4、如何理解同步和異步
4-1、同步 vs 異步
先看下面的栗子,根據程序閱讀起來表達的意思,應該是先打印 100 ,1秒鐘之后打印 200 ,最后打印 300 。但是實際運行根本不是那么回事。
console.log(100) setTimeout(function?()?{ ?console.log(200) },?1000) console.log(300) 復制代碼
再對比以下程序。先打印 100 ,再彈出 200 (等待用戶確認),最后打印 300 。這個運行 效果就符合預期要求。
console.log(100) alert(200)?//?1秒鐘之后點擊確認 console.log(300) 復制代碼
這倆到底有何區別?—— 第一個示例中間的步驟根本沒有阻塞接下來程序的運行,而第二 個示例卻阻塞了后面程序的運行。前面這種表現就叫做 異步(后面這個叫做 同步 ),即?不會阻塞后面程序的運行。
4-2、異步和單線程
setTimeout(function(){ ?a?=?false; },?100) while(a){ ?console.log('while執行了') } 復制代碼
因為JS是單線程的,一次只能做一件事情,所以進入while循環之后,沒有「時間」(線程)去跑定時器了,所以這個代碼跑起來是個死循環!
4-3、前端異步的場景描述
定時任務:setTimeout, setInterval
綁定事件:addEventListener(click等等)
網絡請求:ajax和 img 動態加載
5、簡單描述一下對 ES6/ES7 的了解
5-1、解構賦值
ES6允許按照一定模式,從數組和對象中提取值,對變量進行賦值,這被稱為解構(Destructuring)。
以前,為變量賦值,只能直接指定值。
let?a?=?1; let?b?=?2; let?c?=?3; 復制代碼
ES6 允許寫成下面這樣。
let?[a,?b,?c]?=?[1,?2,?3]; 復制代碼
賦值的代碼大大減少了,不需要分別把變量a,b,c分別聲明定義和賦值,只需要將變量a,b,c作為一個數組的元素,然后將數組[1,2,3]賦值給數組[a,b,c]即可,變量a,b,c即可分別得到對應的值。
1、結構賦值可以嵌套的
let?[?a,b,[?c1,c2?]?]?=?[?1,2,[?3.1,3.2?]?]; console.log(c1);//?c1的值為3.1 console.log(c2);//?c2的值為3.2 復制代碼
2、不完全解構
let?[a,?b,?c]?=?[1,?2]; console.log(a);//?a的值為1 console.log(b);//?b的值為2 復制代碼
3.解構不成功,變量的值就等于undefined。
let?[a,b,c]?=?[1,2]; console.log(a);//?a的值為1 console.log(b);//?b的值為2 console.log(c);//?結果:c的值為undefined 復制代碼
4.解構賦值允許指定默認值
let?[foo?=?true]?=?[]; foo?//?true let?[x,?y?=?'b']?=?['a'];?//?x='a',?y='b' let?[x,?y?=?'b']?=?['a',?undefined];?//?x='a',?y='b' 復制代碼
注意,ES6 內部使用嚴格相等運算符(===),判斷一個位置是否有值。所以,只有當一個數組成員嚴格等于undefined,默認值才會生效。
對象的解構賦值
var?{?a,b,c?}?=?{"a":1,"c":3,"b":2}; ?console.log(a);//結果:a的值為1 ?console.log(b);//結果:b的值為2 ?console.log(c);//結果:c的值為3 復制代碼
字符串的解構賦值
var?[a,b,c,d,e,f]?=?"我是一只小鳥"; ?console.log(a);//我 ?console.log(b);//是 ?console.log(c);//一 ?console.log(d);//只 ?console.log(e);//小 ?console.log(f);//鳥 復制代碼
解構賦值的用途
一、交換變量的值
?var?x?=?1; ?var?y?=?2; ?[x,y]?=?[y,x]; 復制代碼
二、提取函數返回的多個值
function?demo(){ ?return?{"name":?"張三","age":?21} } var?{name,age}?=?demo(); console.log(name);//?結果:張三 console.log(age);//?結果:21 復制代碼
三、定義函數參數
function?demo({a,b,c}){ ?console.log("姓名:"+?a); ?console.log("身高:"+?b); ?console.log("體重:"+?c); } demo({a:"唐三",b:"1.72m",c:"50kg",d:"8000"}); /*?通過這種寫法,?很方便就能提取JSON對象中想要的參數, 例如案例中,我們只需要獲取實參中的:a,b,c, 而不需要關其他的參數,比如:d或者其他更多的參數。*/ 復制代碼
四、提取 JSON 數據
let?jsonData?=?{ ?id:?42, ?status:?"OK", ?data:?[867,?5309] }; let?{?id,?status,?data:?number?}?=?jsonData; console.log(id,?status,?number); //?42,?"OK",?[867,?5309] 復制代碼
五、輸入模塊的指定方法
加載模塊時,往往需要指定輸入哪些方法。解構賦值使得輸入語句非常清晰。
const?{?SourceMapConsumer,?SourceNode?}?=?require("source-map"); 復制代碼
5-2、Module 的語法
歷史上,JavaScript 一直沒有模塊(module)體系,無法將一個大程序拆分成互相依賴的小文件,再用簡單的方法拼裝起來。其他語言都有這項功能,比如 Ruby 的require、Python 的import,甚至就連 CSS 都有@import,但是 JavaScript 任何這方面的支持都沒有,這對開發大型的、復雜的項目形成了巨大障礙。
//?CommonJS模塊 let?{?stat,?exists,?readFile?}?=?require('fs'); //?等同于 let?_fs?=?require('fs'); let?stat?=?_fs.stat; let?exists?=?_fs.exists; let?readfile?=?_fs.readfile; 復制代碼
上面代碼的實質是整體加載fs模塊(即加載fs的所有方法),生成一個對象(_fs),然后再從這個對象上面讀取 3 個方法。這種加載稱為“運行時加載”,因為只有運行時才能得到這個對象,導致完全沒辦法在編譯時做“靜態優化”。
導出Export:作為一個模塊,它可以選擇性地給其他模塊暴露(提供)自己的屬性和方法,供其他模塊使用。
導入Import:作為一個模塊,可以根據需要,引入其他模塊的提供的屬性或者方法,供自己模塊使用。
模塊化實現
//---module-B.js文件--- //導出變量:name export?var?name?=?"模塊化";? 復制代碼
模塊B我們使用關鍵字export關鍵字,對外暴露了一個屬性:name的值為:字符串 “模塊化”。一個關鍵字,一句代碼就實現了,是不是很簡單。
//---module-A.js文件--- //導入?模塊B的屬性?name import?{?name?}?from?"./module-B.js"; console.log(name) //打印結果:模塊化 復制代碼
模塊A我們使用關鍵字import導入了模塊B的name屬性,并且賦值給變量name。關鍵字from的作用是指定你想要引入的模塊,我們這里指定的是module-B.js文件,也就是上面的模塊B。打印結果:“模塊化”正是模塊B的對外暴露的屬性。
5-3、箭頭函數
箭頭函數中的this指向的是定義時的this,而不是執行時的this。
//定義一個對象 var?obj?=?{ ?x:100,//屬性x ?show(){ ?//延遲500毫秒,輸出x的值 ?setTimeout( ?//不同處:箭頭函數 ?()?=>?{?console.log(this.x)}, ?500 ?); ?} }; obj.show();//打印結果:100 復制代碼
當定義obj的show( )方法的時候,我們在箭頭函數編寫this.x,此時的this是指的obj,所以this.x指的是obj.x。而在show()被調用的時候,this依然指向的是被定義時候所指向的對象,也就是obj對象,故打印出:100。
5-4、Promise 對象
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。
ES6 規定,Promise對象是一個構造函數,用來生成Promise實例。
Promise對象有三種狀態:
pending:剛剛創建一個Promise實例的時候,表示初始狀態;
fulfilled:resolve方法調用的時候,表示操作成功;
rejected:reject方法調用的時候,表示操作失??;
狀態只能從 初始化 -> 成功 或者 初始化 -> 失敗,不能逆向轉換,也不能在成功fulfilled 和失敗rejected之間轉換。
const?pro?=?new?Promise(function(resolve,?reject)?{ ?//?...?some?code ?if?(/*?異步操作成功?*/){ ?resolve(value); ?}?else?{ ?reject(error); ?} }); 復制代碼
了解了Promise的創建和狀態,我們來學習一個最重要的實例方法:then( )方法。
pro.then(function?(res)?{ ?//操作成功的處理程序 },function?(error)?{ ?//操作失敗的處理程序 }); //?參數是兩個函數,第一個用于處理操作成功后的業務,第二個用于處理操作異常后的業務。 復制代碼
catch( )方法
pro.catch(function?(error)?{ ?//操作失敗的處理程序 }); 復制代碼
之所以能夠使用鏈式調用,是因為then方法和catch方法調用后,都會返回promise對象。
如果你之前一點都沒接觸過Promise的話,現在一定很懵逼,沒關系,下面我們用一個案例來串聯前面的知識點,演示一下,認真閱讀注釋:
//用new關鍵字創建一個Promise實例 ?let?pro?=?new?Promise(function(resolve,reject){ ?//假設condition的值為true ?let?condition?=?true; ?if(condition){ ?//調用操作成功方法 ?resolve('操作成功'); ?//狀態:pending->fulfilled ?}else{ ?//調用操作異常方法 ?reject('操作異常'); ?//狀態:pending->rejected ?} ?}); ?//用then處理操作成功,catch處理操作異常 ?pro.then(function?(res)?{ ?//操作成功的處理程序 ?console.log(res) ?}).catch(function?(error)?{ ?//操作失敗的處理程序 ?console.log(error) ?}); ?//控制臺輸出:操作成功 復制代碼
上面案例的注釋十分詳細,串聯起了上面介紹的所有知識點:創建實例,狀態轉換,then方法和catch方法的使用。
由于我們設置了變量condition的值為true,所以執行后控制臺輸出的結果是:“操作成功”。
上面就是Promise用于處理操作異常的這個過程;但是,正如文章開頭講到的,如果多個操作之間層層依賴,我們用Promise又是怎么處理的呢?
?let?pro?=?new?Promise(function(resolve,reject){ ?if(true){ ?//調用操作成功方法 ?resolve('操作成功'); ?}else{ ?//調用操作異常方法 ?reject('操作異常'); ?} ?}); ?//用then處理操作成功,catch處理操作異常 ?pro.then(requestA) ?.then(requestB) ?.then(requestC) ?.catch(requestError); ?function?requestA(){ ?console.log('請求A成功'); ?return?'請求B,下一個就是你了'; ?} ?function?requestB(res){ ?console.log('上一步的結果:'+res); ?console.log('請求B成功'); ?return?'請求C,下一個就是你了'; ?} ?function?requestC(res){ ?console.log('上一步的結果:'+res); ?console.log('請求C成功'); ?} ?function?requestError(){ ?console.log('請求失敗'); ?} ?//打印結果: ?//請求A成功 ?//上一步的結果:請求B,下一個就是你了 ?//請求B成功 ?//上一步的結果:請求C,下一個就是你了 ?//請求C成功 復制代碼
案例中,先是創建一個實例,還聲明了4個函數,其中三個是分別代表著請求A,請求B,請求C;有了then方法,三個請求操作再也不用層層嵌套了。我們使用then方法,按照調用順序,很直觀地完成了三個操作的綁定,并且,如果請求B依賴于請求A的結果,那么,可以在請求A的程序用使用return語句把需要的數據作為參數,傳遞給下一個請求,案例中我們就是使用return實現傳遞參數給下一步操作的。
更直觀的圖解
Promise.all( )方法
Promise.all( )方法:接受一個數組作為參數,數組的元素是Promise實例對象,當參數中的實例對象的狀態都為fulfilled時,Promise.all( )才會有返回。
//創建實例pro1 ?let?pro1?=?new?Promise(function(resolve){ ?setTimeout(function?()?{ ?resolve('實例1操作成功'); ?},5000); ?}); ? ?//創建實例pro2 ?let?pro2?=?new?Promise(function(resolve){ ?setTimeout(function?()?{ ?resolve('實例2操作成功'); ?},1000); ?}); ? ?Promise.all([pro1,pro2]).then(function(result){ ?console.log(result); ?}); ?//打印結果:["實例1操作成功",?"實例2操作成功"] 復制代碼
Promise.race( )方法
另一個類似的方法是Promise.race()方法:它的參數要求跟Promise.all( )方法一樣,不同的是,它參數中的promise實例,只要有一個狀態發生變化(不管是成功fulfilled還是異常rejected),它就會有返回,其他實例中再發生變化,它也不管了。
//初始化實例pro1 ?let?pro1?=?new?Promise(function(resolve){ ?setTimeout(function?()?{ ?resolve('實例1操作成功'); ?},4000); ?}); ?//初始化實例pro2 ?let?pro2?=?new?Promise(function(resolve,reject){ ?setTimeout(function?()?{ ?reject('實例2操作失敗'); ?},2000); ?}); ?Promise.race([pro2,pro1]).then(function(result){ ?console.log(result); ?}).catch(function(error){ ?console.log(error); ?}); ?//打印結果:實例2操作失敗 復制代碼
同樣是兩個實例,實例pro1不變,不同的是實例pro2,這次我們調用的是失敗函數reject。
由于pro2實例中2000毫秒之后就執行reject方法,早于實例pro1的4000毫秒,所以最后輸出的是:實例2操作失敗。
以上就是對Promise對象的內容講解,上面提到了一個概念:回調地獄;指的是過多地使用回調函數嵌套,使得調試和維護起來極其的不便。
JavaScript (JS) 是一種編程語言,為通常用于客戶端(client-side)的網頁動態腳本,不過,也常通過像Node.js這樣的包,用于 服務器端(server-side)。
今天,發一篇關于Js基礎知識點的文章,為更多的新人指路??倳腥嗽谀愕那胺綖槟闾铰?,前行之路,你不孤單~
先來個目錄結構
───1、變量聲明 │?└───JavaScript?的數據類型分類和判斷 │?└───引用類型和值類型 ───2、原型與原型鏈(繼承) │?└───原型和原型鏈 ───3、作用域和閉包 │?└───作用域 │?└───什么是閉包,如何形成? ───4、如何理解同步和異步 │?└───同步?vs?異步 │?└───異步和單線程 │?└───前端異步的場景描述 ───5、簡單描述一下對?ES6/ES7?的了解 │?└───解構賦值 │?└───箭頭函數 │?└───Promise?對象 │?└───Set?和?Map?數據結構 復制代碼
1、變量聲明
1-1、JavaScript 的數據類型分類和判斷
在 JavaScript 中,共有7種基本類型:
string,
number,
bigint,
boolean,
null,
undefined,
symbol (ECMAScript 2016新增)。
其中string、number、Boolean、undefined、Null、symbol是6種原始類型。
值得注意的是:原始類型中不包含 Object。
類型判斷用到哪些方法?
1、typeof
typeof xxx 得到的值有以下幾種類型: undefined boolean number string object function symbol。
例如:
console.log(typeof?42); //?expected?output:?"number" console.log(typeof?'blubber'); //?expected?output:?"string" console.log(typeof?true); //?expected?output:?"boolean" console.log(typeof?declaredButUndefinedVariable); //?expected?output:?"undefined"; 復制代碼
typeof null 結果是 object ,JavaScript 誕生以來便如此,由于 null 代表的是空指針(大多數平臺下值為 0x00),因此,null 的類型標簽是 0,typeof null 也因此返回 "object"。
typeof [1, 2] 結果是 object ,結果中沒有array 這一項,引用類型除了function其他的全部都是 object
typeof Symbol() 用 typeof 獲取 symbol 類型的值得到的是 symbol ,Symbol實例是唯一且不可改變的這是 ES6 新增的知識點.
2、instanceof
用于實例和構造函數的對應。例如判斷一個變量是否是數組,使用 typeof 無法判斷,但可 以使用 [1, 2] instanceof Array 來判斷,返回true。因為, [1, 2] 是數組,它的構造函數就是 Array 。同理:
function?Car(make,?model,?year)?{ ?this.make?=?make; ?this.model?=?model; ?this.year?=?year; } var?auto?=?new?Car('Honda',?'Accord',?1998); console.log(auto?instanceof?Car); //?expected?output:?true console.log([1,?2]?instanceof?Array); //?expected?output:?true 復制代碼
1-2、引用類型和值類型
除了原始類型,JS 還有引用類型,上面提到的 typeof 識別出來的類型中,只有 object 和 function 是引用類型,其他都是值類型。
根據 JavaScript 中的變量類型傳遞方式,又分為值類型和引用類型,值類型變量包括 Boolean、String、Number、Undefined、Null,引用類型包括了 Object 類的所有,如 Date、Array、Function 等。在參數傳遞方式上,值類型是按值傳遞,引用類型是按共享 傳遞。
//?值類型 var?a?=?1; var?b?=?a; b?=?3 console.log(a)?//?1 console.log(b)?//?3 //?a?b?都是值類型,兩者分別修改賦值,相互之間沒有任何影響。 復制代碼 //?引用類型 var?a?=?{x:?10,?y:?20} var?b?=?a b.x?=?100 b.y?=?200 console.log(a)?//?{x:?100,?y:?200} console.log(b)?//?{x:?100,?y:?200} 復制代碼
a 和 b 都是引用類型。在執行了 b = a 之后,修改 b 的屬性值, a 的也跟著 變化。因為 a 和 b 都是引用類型,指向了同一個內存地址,即兩者引用的是同一個值,因 此 b 修改屬性時, a 的值隨之改動。
2、原型與原型鏈(繼承)
JavaScript 常被描述為一種基于原型的語言 (prototype-based language)——每個對象擁有一個原型對象,對象以其原型為模板、從原型繼承方法和屬性。原型對象也可能擁有原型,并從中繼承方法和屬性,一層一層、以此類推。這種關系常被稱為原型鏈 (prototype chain),它解釋了為何一個對象會擁有定義在其他對象中的屬性和方法。
注意: 理解對象的原型(可以通過Object.getPrototypeOf(obj)或者已被棄用的__proto__屬性獲得)與構造函數的prototype屬性之間的區別是很重要的。前者是每個實例上都有的屬性,后者是構造函數的屬性。也就是說,Object.getPrototypeOf(new Foobar())和Foobar.prototype指向著同一個對象。
在javascript中,函數可以有屬性。 每個函數都有一個特殊的屬性叫作原型(prototype),正如下面所展示的。請注意,下面的代碼是獨立的一段(在網頁中沒有其他代碼的情況下,這段代碼是安全的)。為了最好的學習體驗,你最好打開一個控制臺 (在Chrome和Firefox中,可以按Ctrl+Shift+I 來打開)切換到"Console"選項卡, 復制粘貼下面的JavaScript代碼,然后按回車來運行。
function?doSomething(){} console.log(?doSomething.prototype?); //?不管您如何聲明函數,javascript中的函數總是有一個默認的原型屬性 var?doSomething?=?function(){};? console.log(?doSomething.prototype?); 復制代碼
正如上面所看到的, doSomething 函數有一個默認的原型屬性,它在控制臺上面呈現了出來. 運行這段代碼之后,控制臺上面應該出現了像這樣的一個對象.
{ ?constructor:???doSomething(), ?__proto__:?{ ?constructor:???Object(), ?hasOwnProperty:???`hasOwnProperty`(), ?isPrototypeOf:???`isPrototypeOf`(), ?propertyIsEnumerable:???`propertyIsEnumerable`(), ?toLocaleString:???`toLocaleString`(), ?toString:???`toString`(), ?valueOf:???`valueOf`() ?} } 復制代碼
現在,我們可以添加一些屬性到 doSomething 的原型上面,如下所示:
function?doSomething(){} doSomething.prototype.foo?=?"bar"; console.log(?doSomething.prototype?); 復制代碼
輸出:
{ ?foo:?"bar", ?constructor:???doSomething(), ?__proto__:?{ ?constructor:???Object(), ?hasOwnProperty:???`hasOwnProperty`(), ?isPrototypeOf:???`isPrototypeOf`(), ?propertyIsEnumerable:???`propertyIsEnumerable`(), ?toLocaleString:???`toLocaleString`(), ?toString:???`toString`(), ?valueOf:???`valueOf`() ?} } 復制代碼
然后,我們可以使用 new 運算符來在現在的這個原型基礎之上,創建一個 doSomething 的實例。
function?doSomething(){} doSomething.prototype.foo?=?"bar";?//?add?a?property?onto?the?prototype var?doSomeInstancing?=?new?doSomething(); doSomeInstancing.prop?=?"some?value";?//?add?a?property?onto?the?object console.log(?doSomeInstancing?); 復制代碼
輸出:
{ ?prop:?"some?value", ?__proto__:?{ ?foo:?"bar", ?constructor:???doSomething(), ?__proto__:?{ ?constructor:???Object(), ?hasOwnProperty:???`hasOwnProperty`(), ?isPrototypeOf:???`isPrototypeOf`(), ?propertyIsEnumerable:???`propertyIsEnumerable`(), ?toLocaleString:???`toLocaleString`(), ?toString:???`toString`(), ?valueOf:???`valueOf`() ?} ?} } 復制代碼
就像上面看到的, doSomeInstancing 的 __proto__ 屬性就是doSomething.prototype. 但是這又有什么用呢? 好吧,當你訪問 doSomeInstancing 的一個屬性, 瀏覽器首先查找 doSomeInstancing 是否有這個屬性. 如果 doSomeInstancing 沒有這個屬性, 然后瀏覽器就會在 doSomeInstancing 的 __proto__ 中查找這個屬性(也就是 doSomething.prototype). 如果 doSomeInstancing 的 __proto__ 有這個屬性, 那么 doSomeInstancing 的 __proto__ 上的這個屬性就會被使用. 否則, 如果 doSomeInstancing 的 __proto__ 沒有這個屬性, 瀏覽器就會去查找 doSomeInstancing 的 __proto__ 的 __proto__ ,看它是否有這個屬性. 默認情況下, 所有函數的原型屬性的 __proto__ 就是 window.Object.prototype. 所以 doSomeInstancing 的 __proto__ 的 __proto__ (也就是 doSomething.prototype 的 __proto__ (也就是 Object.prototype)) 會被查找是否有這個屬性. 如果沒有在它里面找到這個屬性, 然后就會在 doSomeInstancing 的 __proto__ 的 __proto__ 的 __proto__ 里面查找. 然而這有一個問題: doSomeInstancing 的 __proto__ 的 __proto__ 的 __proto__ 不存在. 最后, 原型鏈上面的所有的 __proto__ 都被找完了, 瀏覽器所有已經聲明了的 __proto__ 上都不存在這個屬性,然后就得出結論,這個屬性是 undefined.
function?doSomething(){} doSomething.prototype.foo?=?"bar"; var?doSomeInstancing?=?new?doSomething(); doSomeInstancing.prop?=?"some?value"; console.log("doSomeInstancing.prop:?"?+?doSomeInstancing.prop); console.log("doSomeInstancing.foo:?"?+?doSomeInstancing.foo); console.log("doSomething.prop:?"?+?doSomething.prop); console.log("doSomething.foo:?"?+?doSomething.foo); console.log("doSomething.prototype.prop:?"?+?doSomething.prototype.prop); console.log("doSomething.prototype.foo:?"?+?doSomething.prototype.foo); 復制代碼
輸出:
doSomeInstancing.prop:?some?value doSomeInstancing.foo:?bar doSomething.prop:?undefined doSomething.foo:?undefined doSomething.prototype.prop:?undefined doSomething.prototype.foo:?bar 復制代碼
是不是看的頭大了,別擔心??纯催@個:
所有的引用類型(數組、對象、函數),都具有對象特性,即可自由擴展屬性( null除外)
所有的引用類型(數組、對象、函數),都有一個 __proto__ 屬性,屬性值是一個普通的對象
所有的函數,都有一個 prototype 屬性,屬性值也是一個普通的對象
所有的引用類型(數組、對象、函數), __proto__ 屬性值指向它的構造函數的prototype 屬性值
//?要點一:自由擴展屬性 var?obj?=?{};?obj.a?=?100; var?arr?=?[];?arr.a?=?100; function?fn?()?{} fn.a?=?100; //?要點二:__proto__ console.log(obj.__proto__); console.log(arr.__proto__); console.log(fn.__proto__); //?要點三:函數有?prototype console.log(fn.prototype) //?要點四:引用類型的?__proto__?屬性值指向它的構造函數的?prototype?屬性值 console.log(obj.__proto__?===?Object.prototype) 復制代碼
2-1、原型和原型鏈
原型
//?構造函數 function?Foo(name,?age)?{ ?this.name?=?name } Foo.prototype.alertName?=?function?()?{ ?alert(this.name) } //?創建示例 var?f?=?new?Foo('zhangsan') f.printName?=?function?()?{ ?console.log(this.name) } //?測試 f.printName() f.alertName() 復制代碼
執行 printName 時很好理解,但是執行 alertName 時發生了什么?這里再記住一個重點 當 試圖得到一個對象的某個屬性時,如果這個對象本身沒有這個屬性,那么會去它的 __proto__ (即它的構造函數的 prototype )中尋找,因此 f.alertName 就會找到 Foo.prototype.alertName 。
那么如何判斷這個屬性是不是對象本身的屬性呢?使用 hasOwnProperty ,常用的地方是遍 歷一個對象的時候。
var?item for?(item?in?f)?{ ?//?高級瀏覽器已經在?for?in?中屏蔽了來自原型的屬性,但是這里建議大家還是加上這個判斷,保證程序正常輸出 ?if?(f.hasOwnProperty(item))?{ ?console.log(item) ?} } 復制代碼
原型鏈
還是接著上面的示例,如果執行 f.toString() 時,又發生了什么?
f.printName() 復制代碼
因為 f 本身沒有 toString() ,并且 f.__proto__ (即 Foo.prototype )中也沒有 toString 。這個問題還是得拿出剛才那句話——當試圖得到一個對象的某個屬性時,如果 這個對象本身沒有這個屬性,那么會去它的 __proto__ (即它的構造函數的 prototype ) 中尋找。
如果在 f.proto?中沒有找到 toString ,那么就繼續去 f.proto.proto?中尋 找,因為 f.proto就是一個普通的對象而已嘛!
f.__proto__ 即 Foo.prototype ,沒有找到 toString ,繼續往上找
f.__proto__.__proto__ 即 Foo.prototype.__proto__ 。 Foo.prototype 就是一個普通 的對象,因此 Foo.prototype.__proto__ 就是 Object.prototype ,在這里可以找到toString
因此 f.toString 最終對應到了 Object.prototype.toString
這樣一直往上找,你會發現是一個鏈式的結構,所以叫做“原型鏈”。如果一直找到最上 層都沒有找到,那么就宣告失敗,返回 undefined 。最上層是什么 ——Object.prototype.__proto__ === null.原型鏈并不是無限的,原型鏈最終指向null。
參考文章:簡單粗暴地理解js原型鏈--js面向對象編程
微信請點擊閱讀原文更好的查看。
3、作用域和閉包
作用域和閉包是前端面試中,最可能考查的知識點
3-1、作用域
作用域就是一個獨立的地盤,讓變量不會外泄、暴露出去。
變量的作用域無非就是兩種:全局變量和局部變量。
全局作用域:
最外層函數定義的變量擁有全局作用域,即對任何內部函數來說,都是可以訪問的
var?outerVar?=?"outer"; function?fn(){ ?console.log(outerVar); } fn();?//?result:outer 復制代碼
局部作用域:
和全局作用域相反,局部作用域一般只在固定的代碼片段內可訪問到,而對于函數外部是無法訪問的,最常見的例如函數內部
function?fn(){ ?var?innerVar?=?"inner"; } fn(); console.log(innerVar);?//?ReferenceError:?innerVar?is?not?defined 復制代碼
這就是為何 jQuery、Zepto 等庫的源碼,所有的代碼都會放在 (function(){....})() 中。因為放在里面的所有變量,都不會被外泄和暴露,不會污染到外面,不會對其他的庫 或者 JS 腳本造成影響。這是函數作用域的一個體現。
注意: ES6 中開始加入了塊級作用域,使用 let 定義變量即可,如下:
if?(true)?{ ?let?name?=?'Tom' } console.log(name)?//?報錯,因為let定義的name是在if這個塊級作用域 復制代碼
作用域鏈?如下代碼中, console.log(a) 要得到 a 變量,但是在當前的作用域中沒有定義 a,一層一層向上尋找,直到找到全局作用域還是沒找到,就宣布放棄。這種一層一層的關系,就是?作用域鏈。
var?a?=?5 function?fn()?{ ?var?b?=?10 ?console.log(a) ?console.log(b) } fn() 復制代碼
3-2、 什么是閉包,如何形成
那么什么叫閉包?觀點很多,出現頻率最高的有以下兩個觀點:
函數套函數。
在函數外獲取函數內變量的技術。
function?F1()?{ ?var?a?=?100 ?return?function?()?{ ?console.log(a) ?} } var?f1?=?F1() var?a?=?200 f1() 復制代碼
閉包主要有兩個應用場景:
函數作為返回值,上面的例子就是
函數作為參數傳遞,看以下例子
function?F1()?{ ?var?a?=?100 ?return?function?()?{ ?console.log(a) ?} } function?F2(f1)?{ ?var?a?=?200 ?console.log(f1()) } var?f1?=?F1() F2(f1) 復制代碼
關于this對象
var?name?=?"The?Window"; var?object?=?{ ?name?:?"My?Object", ?getNameFunc?:?function(){ ?return?function(){ ?return?this.name; ?}; ?} }; alert(object.getNameFunc()());?//?result:The?Window 復制代碼
this對象是在運行時基于函數的執行環境綁定的:在全局函數中,this等于window,而當函數被作為某個對象調用時,this等于那個對象。不過,匿名函數具有全局性,因此this對象同常指向window。
4、如何理解同步和異步
4-1、同步 vs 異步
先看下面的栗子,根據程序閱讀起來表達的意思,應該是先打印 100 ,1秒鐘之后打印 200 ,最后打印 300 。但是實際運行根本不是那么回事。
console.log(100) setTimeout(function?()?{ ?console.log(200) },?1000) console.log(300) 復制代碼
再對比以下程序。先打印 100 ,再彈出 200 (等待用戶確認),最后打印 300 。這個運行 效果就符合預期要求。
console.log(100) alert(200)?//?1秒鐘之后點擊確認 console.log(300) 復制代碼
這倆到底有何區別?—— 第一個示例中間的步驟根本沒有阻塞接下來程序的運行,而第二 個示例卻阻塞了后面程序的運行。前面這種表現就叫做 異步(后面這個叫做 同步 ),即?不會阻塞后面程序的運行。
4-2、異步和單線程
setTimeout(function(){ ?a?=?false; },?100) while(a){ ?console.log('while執行了') } 復制代碼
因為JS是單線程的,一次只能做一件事情,所以進入while循環之后,沒有「時間」(線程)去跑定時器了,所以這個代碼跑起來是個死循環!
4-3、前端異步的場景描述
定時任務:setTimeout, setInterval
綁定事件:addEventListener(click等等)
網絡請求:ajax和 img 動態加載
5、簡單描述一下對 ES6/ES7 的了解
5-1、解構賦值
ES6允許按照一定模式,從數組和對象中提取值,對變量進行賦值,這被稱為解構(Destructuring)。
以前,為變量賦值,只能直接指定值。
let?a?=?1; let?b?=?2; let?c?=?3; 復制代碼
ES6 允許寫成下面這樣。
let?[a,?b,?c]?=?[1,?2,?3]; 復制代碼
賦值的代碼大大減少了,不需要分別把變量a,b,c分別聲明定義和賦值,只需要將變量a,b,c作為一個數組的元素,然后將數組[1,2,3]賦值給數組[a,b,c]即可,變量a,b,c即可分別得到對應的值。
1、結構賦值可以嵌套的
let?[?a,b,[?c1,c2?]?]?=?[?1,2,[?3.1,3.2?]?]; console.log(c1);//?c1的值為3.1 console.log(c2);//?c2的值為3.2 復制代碼
2、不完全解構
let?[a,?b,?c]?=?[1,?2]; console.log(a);//?a的值為1 console.log(b);//?b的值為2 復制代碼
3.解構不成功,變量的值就等于undefined。
let?[a,b,c]?=?[1,2]; console.log(a);//?a的值為1 console.log(b);//?b的值為2 console.log(c);//?結果:c的值為undefined 復制代碼
4.解構賦值允許指定默認值
let?[foo?=?true]?=?[]; foo?//?true let?[x,?y?=?'b']?=?['a'];?//?x='a',?y='b' let?[x,?y?=?'b']?=?['a',?undefined];?//?x='a',?y='b' 復制代碼
注意,ES6 內部使用嚴格相等運算符(===),判斷一個位置是否有值。所以,只有當一個數組成員嚴格等于undefined,默認值才會生效。
對象的解構賦值
var?{?a,b,c?}?=?{"a":1,"c":3,"b":2}; ?console.log(a);//結果:a的值為1 ?console.log(b);//結果:b的值為2 ?console.log(c);//結果:c的值為3 復制代碼
字符串的解構賦值
var?[a,b,c,d,e,f]?=?"我是一只小鳥"; ?console.log(a);//我 ?console.log(b);//是 ?console.log(c);//一 ?console.log(d);//只 ?console.log(e);//小 ?console.log(f);//鳥 復制代碼
解構賦值的用途
一、交換變量的值
?var?x?=?1; ?var?y?=?2; ?[x,y]?=?[y,x]; 復制代碼
二、提取函數返回的多個值
function?demo(){ ?return?{"name":?"張三","age":?21} } var?{name,age}?=?demo(); console.log(name);//?結果:張三 console.log(age);//?結果:21 復制代碼
三、定義函數參數
function?demo({a,b,c}){ ?console.log("姓名:"+?a); ?console.log("身高:"+?b); ?console.log("體重:"+?c); } demo({a:"唐三",b:"1.72m",c:"50kg",d:"8000"}); /*?通過這種寫法,?很方便就能提取JSON對象中想要的參數, 例如案例中,我們只需要獲取實參中的:a,b,c, 而不需要關其他的參數,比如:d或者其他更多的參數。*/ 復制代碼
四、提取 JSON 數據
let?jsonData?=?{ ?id:?42, ?status:?"OK", ?data:?[867,?5309] }; let?{?id,?status,?data:?number?}?=?jsonData; console.log(id,?status,?number); //?42,?"OK",?[867,?5309] 復制代碼
五、輸入模塊的指定方法
加載模塊時,往往需要指定輸入哪些方法。解構賦值使得輸入語句非常清晰。
const?{?SourceMapConsumer,?SourceNode?}?=?require("source-map"); 復制代碼
5-2、Module 的語法
歷史上,JavaScript 一直沒有模塊(module)體系,無法將一個大程序拆分成互相依賴的小文件,再用簡單的方法拼裝起來。其他語言都有這項功能,比如 Ruby 的require、Python 的import,甚至就連 CSS 都有@import,但是 JavaScript 任何這方面的支持都沒有,這對開發大型的、復雜的項目形成了巨大障礙。
//?CommonJS模塊 let?{?stat,?exists,?readFile?}?=?require('fs'); //?等同于 let?_fs?=?require('fs'); let?stat?=?_fs.stat; let?exists?=?_fs.exists; let?readfile?=?_fs.readfile; 復制代碼
上面代碼的實質是整體加載fs模塊(即加載fs的所有方法),生成一個對象(_fs),然后再從這個對象上面讀取 3 個方法。這種加載稱為“運行時加載”,因為只有運行時才能得到這個對象,導致完全沒辦法在編譯時做“靜態優化”。
導出Export:作為一個模塊,它可以選擇性地給其他模塊暴露(提供)自己的屬性和方法,供其他模塊使用。
導入Import:作為一個模塊,可以根據需要,引入其他模塊的提供的屬性或者方法,供自己模塊使用。
模塊化實現
//---module-B.js文件--- //導出變量:name export?var?name?=?"模塊化";? 復制代碼
模塊B我們使用關鍵字export關鍵字,對外暴露了一個屬性:name的值為:字符串 “模塊化”。一個關鍵字,一句代碼就實現了,是不是很簡單。
//---module-A.js文件--- //導入?模塊B的屬性?name import?{?name?}?from?"./module-B.js"; console.log(name) //打印結果:模塊化 復制代碼
模塊A我們使用關鍵字import導入了模塊B的name屬性,并且賦值給變量name。關鍵字from的作用是指定你想要引入的模塊,我們這里指定的是module-B.js文件,也就是上面的模塊B。打印結果:“模塊化”正是模塊B的對外暴露的屬性。
5-3、箭頭函數
箭頭函數中的this指向的是定義時的this,而不是執行時的this。
//定義一個對象 var?obj?=?{ ?x:100,//屬性x ?show(){ ?//延遲500毫秒,輸出x的值 ?setTimeout( ?//不同處:箭頭函數 ?()?=>?{?console.log(this.x)}, ?500 ?); ?} }; obj.show();//打印結果:100 復制代碼
當定義obj的show( )方法的時候,我們在箭頭函數編寫this.x,此時的this是指的obj,所以this.x指的是obj.x。而在show()被調用的時候,this依然指向的是被定義時候所指向的對象,也就是obj對象,故打印出:100。
5-4、Promise 對象
Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了Promise對象。
ES6 規定,Promise對象是一個構造函數,用來生成Promise實例。
Promise對象有三種狀態:
pending:剛剛創建一個Promise實例的時候,表示初始狀態;
fulfilled:resolve方法調用的時候,表示操作成功;
rejected:reject方法調用的時候,表示操作失??;
狀態只能從 初始化 -> 成功 或者 初始化 -> 失敗,不能逆向轉換,也不能在成功fulfilled 和失敗rejected之間轉換。
const?pro?=?new?Promise(function(resolve,?reject)?{ ?//?...?some?code ?if?(/*?異步操作成功?*/){ ?resolve(value); ?}?else?{ ?reject(error); ?} }); 復制代碼
了解了Promise的創建和狀態,我們來學習一個最重要的實例方法:then( )方法。
pro.then(function?(res)?{ ?//操作成功的處理程序 },function?(error)?{ ?//操作失敗的處理程序 }); //?參數是兩個函數,第一個用于處理操作成功后的業務,第二個用于處理操作異常后的業務。 復制代碼
catch( )方法
pro.catch(function?(error)?{ ?//操作失敗的處理程序 }); 復制代碼
之所以能夠使用鏈式調用,是因為then方法和catch方法調用后,都會返回promise對象。
如果你之前一點都沒接觸過Promise的話,現在一定很懵逼,沒關系,下面我們用一個案例來串聯前面的知識點,演示一下,認真閱讀注釋:
//用new關鍵字創建一個Promise實例 ?let?pro?=?new?Promise(function(resolve,reject){ ?//假設condition的值為true ?let?condition?=?true; ?if(condition){ ?//調用操作成功方法 ?resolve('操作成功'); ?//狀態:pending->fulfilled ?}else{ ?//調用操作異常方法 ?reject('操作異常'); ?//狀態:pending->rejected ?} ?}); ?//用then處理操作成功,catch處理操作異常 ?pro.then(function?(res)?{ ?//操作成功的處理程序 ?console.log(res) ?}).catch(function?(error)?{ ?//操作失敗的處理程序 ?console.log(error) ?}); ?//控制臺輸出:操作成功 復制代碼
上面案例的注釋十分詳細,串聯起了上面介紹的所有知識點:創建實例,狀態轉換,then方法和catch方法的使用。
由于我們設置了變量condition的值為true,所以執行后控制臺輸出的結果是:“操作成功”。
上面就是Promise用于處理操作異常的這個過程;但是,正如文章開頭講到的,如果多個操作之間層層依賴,我們用Promise又是怎么處理的呢?
?let?pro?=?new?Promise(function(resolve,reject){ ?if(true){ ?//調用操作成功方法 ?resolve('操作成功'); ?}else{ ?//調用操作異常方法 ?reject('操作異常'); ?} ?}); ?//用then處理操作成功,catch處理操作異常 ?pro.then(requestA) ?.then(requestB) ?.then(requestC) ?.catch(requestError); ?function?requestA(){ ?console.log('請求A成功'); ?return?'請求B,下一個就是你了'; ?} ?function?requestB(res){ ?console.log('上一步的結果:'+res); ?console.log('請求B成功'); ?return?'請求C,下一個就是你了'; ?} ?function?requestC(res){ ?console.log('上一步的結果:'+res); ?console.log('請求C成功'); ?} ?function?requestError(){ ?console.log('請求失敗'); ?} ?//打印結果: ?//請求A成功 ?//上一步的結果:請求B,下一個就是你了 ?//請求B成功 ?//上一步的結果:請求C,下一個就是你了 ?//請求C成功 復制代碼
案例中,先是創建一個實例,還聲明了4個函數,其中三個是分別代表著請求A,請求B,請求C;有了then方法,三個請求操作再也不用層層嵌套了。我們使用then方法,按照調用順序,很直觀地完成了三個操作的綁定,并且,如果請求B依賴于請求A的結果,那么,可以在請求A的程序用使用return語句把需要的數據作為參數,傳遞給下一個請求,案例中我們就是使用return實現傳遞參數給下一步操作的。
更直觀的圖解
Promise.all( )方法
Promise.all( )方法:接受一個數組作為參數,數組的元素是Promise實例對象,當參數中的實例對象的狀態都為fulfilled時,Promise.all( )才會有返回。
//創建實例pro1 ?let?pro1?=?new?Promise(function(resolve){ ?setTimeout(function?()?{ ?resolve('實例1操作成功'); ?},5000); ?}); ? ?//創建實例pro2 ?let?pro2?=?new?Promise(function(resolve){ ?setTimeout(function?()?{ ?resolve('實例2操作成功'); ?},1000); ?}); ? ?Promise.all([pro1,pro2]).then(function(result){ ?console.log(result); ?}); ?//打印結果:["實例1操作成功",?"實例2操作成功"] 復制代碼
Promise.race( )方法
另一個類似的方法是Promise.race()方法:它的參數要求跟Promise.all( )方法一樣,不同的是,它參數中的promise實例,只要有一個狀態發生變化(不管是成功fulfilled還是異常rejected),它就會有返回,其他實例中再發生變化,它也不管了。
//初始化實例pro1 ?let?pro1?=?new?Promise(function(resolve){ ?setTimeout(function?()?{ ?resolve('實例1操作成功'); ?},4000); ?}); ?//初始化實例pro2 ?let?pro2?=?new?Promise(function(resolve,reject){ ?setTimeout(function?()?{ ?reject('實例2操作失敗'); ?},2000); ?}); ?Promise.race([pro2,pro1]).then(function(result){ ?console.log(result); ?}).catch(function(error){ ?console.log(error); ?}); ?//打印結果:實例2操作失敗 復制代碼
同樣是兩個實例,實例pro1不變,不同的是實例pro2,這次我們調用的是失敗函數reject。
由于pro2實例中2000毫秒之后就執行reject方法,早于實例pro1的4000毫秒,所以最后輸出的是:實例2操作失敗。
以上就是對Promise對象的內容講解,上面提到了一個概念:回調地獄;指的是過多地使用回調函數嵌套,使得調試和維護起來極其的不便。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。