前言:
2020年是多災(zāi)多難的一年,疫情持續(xù)至今,到目前岳颇,全世界的經(jīng)濟(jì)都受到不同程序的影響照捡,各大公司裁員,在這樣一片嚴(yán)峻的形式下话侧,找工作更是難上加難栗精。
企業(yè)的門檻提高,第一瞻鹏,對(duì)于學(xué)歷的要求悲立,必須學(xué)信網(wǎng)可查的統(tǒng)招本科;第二新博,對(duì)于技術(shù)的掌握程序薪夕,更多的是底層原理,項(xiàng)目經(jīng)驗(yàn)赫悄,等等寥殖。
下面是面試幾周以來,總結(jié)的一些面試中常被問到的題目涩蜘,還有吸取的一些前輩們分享的貼子,全部系統(tǒng)的羅列出來熏纯,希望能夠幫到正在面試的人同诫。
JavaScript原理和底層是最重要,也是最常問的樟澜,包括ES6误窖。
1. 基礎(chǔ)類型和類型檢測(cè)
- 簡(jiǎn)單類型:Undefined, Null, boolean, number, string。 存儲(chǔ)結(jié)構(gòu)-棧
- 復(fù)雜類型:Object, Array, Date, Function, RegExp (Symbol秩贰, Set, Map)存儲(chǔ)結(jié)構(gòu)-堆
- 基本包裝類型:Boolean, Number, String )存儲(chǔ)結(jié)構(gòu)-堆
- 類型檢測(cè)
1.typeof: 區(qū)分不了引用類型(typeof null === Object)
2.instanceof: 繁瑣霹俺,但還能用于區(qū)分擁有原型鏈的類型
3.constructor: 容易篡改
4.Object.prototype.toString.call():完美(ES6的也能區(qū)分)
1. 判斷基本類型
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(“abc”);// "[object String]"
Object.prototype.toString.call(123);// "[object Number]"
Object.prototype.toString.call(true);// "[object Boolean]"
2. 判斷原生引用類型
**函數(shù)類型**
Function fn(){
console.log(“test”);
}
Object.prototype.toString.call(fn); // "[object Function]"
**日期類型**
var date = new Date();
Object.prototype.toString.call(date); // "[object Date]"
**數(shù)組類型**
var arr = [1,2,3];
Object.prototype.toString.call(arr); // "[object Array]"
**正則表達(dá)式**
var reg = /[hbc]at/gi;
Object.prototype.toString.call(reg); // "[object RegExp]"
**自定義類型**
function Person(name, age) {
this.name = name;
this.age = age;
}
var person = new Person("Rose", 18);
Object.prototype.toString.call(arr); // "[object Object]"
很明顯這種方法不能準(zhǔn)確判斷person是Person類的實(shí)例,而只能用instanceof 操作符來進(jìn)行判斷毒费,如下所示:
console.log(person instanceof Person); // true
3. 判斷原生JSON對(duì)象
var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON);
console.log(isNativeJSON);// 輸出結(jié)果為”[object JSON]”說明JSON是原生的丙唧,否則不是;
2. 類型轉(zhuǎn)換
- undefined: undefined 的字面意思就是未定義的值觅玻,這個(gè)值的語義是想际,希望表示一個(gè)變量最原始的狀態(tài),而非人為操作的結(jié)果
- null: null 的字面意思是空值溪厘,這個(gè)值的語義是胡本,希望表示 一個(gè)對(duì)象被人為的重置為空對(duì)象,而非一個(gè)變量最原始的狀態(tài) 規(guī)定 undefined == null 表示其行為相似性
- void 0 === undefined
3. == 與 ===
=== 嚴(yán)格相等
1.類型相等
2.普通類型值相等畸悬, 引用類型的地址相等== 相同 (帶類型轉(zhuǎn)換)
1.undefined == null
2.使用toPrimitive轉(zhuǎn)換成原始值后比較
4. valueOf 和 toString
toString: 將值轉(zhuǎn)換為字符串形式并返回侧甫,不同類型的toString方法各有不同
valueOf:返回類型的值
5. 原型和原型鏈
原型
1.每一個(gè)Js對(duì)象(null除外)在創(chuàng)建的時(shí)候都會(huì)關(guān)聯(lián)另一個(gè)對(duì)象,這個(gè)對(duì)象就是我們所說的原型,每一個(gè)對(duì)象都會(huì)從原型"繼承"屬性和方法披粟。
2.__proto__
是對(duì)象實(shí)例才有的屬性咒锻,指向?qū)ο蟮脑汀?br> 3.prototype
是構(gòu)造函數(shù)才有的屬性,該屬性指向了一個(gè)對(duì)象僻爽,這個(gè)對(duì)象正是調(diào)用該構(gòu)造函數(shù)而創(chuàng)建的實(shí)例的原型虫碉。
4.實(shí)例的__proto__
屬性 和 構(gòu)造函數(shù)的prototype
都指向該對(duì)象原型。
5.Function
的prototype
和__proto__
屬性都指向f ()
匿名函數(shù)胸梆。
6.Object
作為構(gòu)造函數(shù)時(shí)敦捧,它的prototype
指向Object.prototype
對(duì)象原型,作為實(shí)例時(shí)碰镜,他的__proto__
指向匿名函數(shù)兢卵。我們可以認(rèn)為Function
實(shí)例和Object
實(shí)例都是繼承于該匿名函數(shù)。
7.匿名函數(shù)作為“頂級(jí)構(gòu)造函數(shù)”绪颖,他不需要prototype屬性秽荤,即prototype=undefined,當(dāng)作為對(duì)象時(shí)柠横,他的對(duì)象原型是Object.prototype
窃款。
8.Object.prototype
作為“頂級(jí)構(gòu)造對(duì)象”,它的__proto__
等于null牍氛,表示繼承于一個(gè)空的對(duì)象晨继。它沒有prototype
屬性。原型鏈
1.用__proto__
鏈接起來的就是原型鏈搬俊。原型鏈用于查找對(duì)象上的屬性紊扬,如果未能從當(dāng)前的對(duì)象上獲取到,就會(huì)繼續(xù)從該原型鏈上查找唉擂,直到查到相應(yīng)的屬性餐屎。
2.原型鏈的頂層指向window,嚴(yán)格模式下不會(huì)指向window而是undefined
6. Class和繼承
Class
1.ES6之前實(shí)例化對(duì)象是通過構(gòu)造函數(shù)實(shí)現(xiàn)的玩祟,ES6后可以通過關(guān)鍵字class創(chuàng)建類(可以認(rèn)為是一種語法糖)
2.class中的constructor就是在實(shí)例化對(duì)象調(diào)用的構(gòu)造函數(shù)腹缩,該構(gòu)造函數(shù)可不寫。
3.實(shí)例對(duì)象必須使用new 關(guān)鍵字生成
4.class不可以當(dāng)做函數(shù)執(zhí)行
5.class不存在變量提升
6.class中定義的屬性和方法都掛在原型上空扎,所有的實(shí)例對(duì)象都有這些屬性和方法庆聘。構(gòu)造函數(shù)中定義的是實(shí)例的屬性和方法。
6.class中可以通過static定義靜態(tài)方法勺卢,靜態(tài)變量需在類外聲明(calss.staticName==staticValue)伙判。靜態(tài)屬性和方法只可以通過class來調(diào)用,實(shí)例不可調(diào)用繼承
1.繼承屬性黑忱,方法宴抚,靜態(tài)方法
(1)ES6繼承: 通過extends關(guān)鍵字
(2)ES5繼承: 通過修改原型鏈實(shí)現(xiàn)繼承
2.本質(zhì)
(1)ES5 的繼承勒魔,實(shí)質(zhì)是先創(chuàng)造子類的實(shí)例對(duì)象this,然后再將父類的方法添加到this上面(Parent.apply(this))菇曲。
(2)ES6 的繼承機(jī)制完全不同冠绢,實(shí)質(zhì)是先將父類實(shí)例對(duì)象的屬性和方法,加到this上面(所以必須先調(diào)用super方法)常潮,然后再用子類的構(gòu)造函數(shù)修改this弟胀。繼承的幾種方法:
1.原型鏈繼承:替換子類型的原型
缺點(diǎn):
(1)包含引用類型的原型屬性會(huì)被所有實(shí)例共享
(2)在創(chuàng)建子類型的實(shí)例時(shí),不能向超類型的構(gòu)造函數(shù)中傳遞參數(shù)
2.經(jīng)典繼承(借用構(gòu)造函數(shù)):為了避免實(shí)例共享原型屬性而帶來的技術(shù)
缺點(diǎn):
(1)無法做到函數(shù)復(fù)用
(2)不能繼承超類型在原型上定義的方法
3.組合繼承:融合了原型鏈繼承和經(jīng)典繼承喊式,避免了他們的缺陷
缺點(diǎn):需要調(diào)用兩次超類型的構(gòu)造函數(shù)
4.原型繼承:基于已有的對(duì)象創(chuàng)建新的對(duì)象
缺點(diǎn):包含引用類型的原型屬性會(huì)被所有實(shí)例共享
5.寄生式繼承:思路和工廠模式類似孵户,即創(chuàng)建一個(gè)僅用于繼承過程的函數(shù)
缺點(diǎn):無法做到函數(shù)復(fù)用
6.寄生式組合繼承:通過構(gòu)造函數(shù)來繼承屬性,通過原型鏈的混成形式來繼承方法
主要實(shí)現(xiàn)原理:
PersonB.prototype = Object.create(PersonA.prototype)
實(shí)現(xiàn)來繼承PersonA的原型
當(dāng)我們通過new關(guān)鍵字實(shí)例化的對(duì)象身上就有了PersonB自身的屬性和方法岔留,也有了PersonA的原型方法
當(dāng)實(shí)例化對(duì)象調(diào)用某個(gè)方法時(shí)會(huì)先在自身和原型上查找夏哭,然后是在_proto_
上一層層查找,這種方式就是原型鏈献联。
7. 閉包
簡(jiǎn)單來說就是函數(shù)嵌套函數(shù)竖配,內(nèi)部函數(shù)引用來外部函數(shù)的變量,從而導(dǎo)致了垃圾回收機(jī)制沒有生效里逆,變量被保存了下來进胯。
可能產(chǎn)生閉包的二種情況:
1.函數(shù)作為返回值,
2.函數(shù)作為參數(shù)傳遞優(yōu)點(diǎn):
1.可以讀取函數(shù)內(nèi)部的變量
2.另一個(gè)就是讓這些變量的值始終保持在內(nèi)存中原押,不會(huì)在函數(shù)調(diào)用后被自動(dòng)清除龄减,同時(shí)這也算是個(gè)缺點(diǎn)。(在函數(shù)中return一個(gè)函數(shù)出來)
3.可用于模擬私有變量和方法缺點(diǎn):
1.消耗內(nèi)存班眯,影響網(wǎng)頁性能,造成頁面卡頓等現(xiàn)象烁巫。
2.可能會(huì)引起內(nèi)存泄漏(不再用到的內(nèi)存署隘,但是沒有及時(shí)釋放,就叫做內(nèi)存泄漏)
8. this
this指的是當(dāng)前的執(zhí)行環(huán)境
一般時(shí)指向window, 嚴(yán)格模式下this綁定到undefined
對(duì)象調(diào)用函數(shù)的情況下亚隙,指向調(diào)用者
構(gòu)造函數(shù)下磁餐,指向?qū)嵗ū热纾篤ue中的this指向的是Vue實(shí)例)
9. 修改this指向的幾種方法
call: call(this, arg1, arg2, ...)
,適用于參數(shù)個(gè)數(shù)確定情況apply: apply(this, firstArg | argArray[])
阿弃,適用于參數(shù)個(gè)數(shù)不確定的情況bind: bind(this, firstArg | argArray[])
诊霹,返回一個(gè)函數(shù),函數(shù)內(nèi)的this指向傳入的thiswith: with (expression) { statement }
with語句將某個(gè)對(duì)象添加到作用域鏈的頂部(window之下渣淳,沒有切斷作用域鏈脾还,在expression中找不到定義的,仍會(huì)往window上尋找)入愧,在嚴(yán)格模式該標(biāo)簽禁止使用
10. new的原理
創(chuàng)建一個(gè)空對(duì)象鄙漏,構(gòu)造函數(shù)中的this指向這個(gè)空對(duì)象
這個(gè)新對(duì)象的proto設(shè)置為即構(gòu)造函數(shù)的prototype
執(zhí)行構(gòu)造函數(shù)方法嗤谚,屬性和方法被添加到this引用的對(duì)象中
如果構(gòu)造函數(shù)中沒有返回其它對(duì)象,那么返回this怔蚌,即創(chuàng)建的這個(gè)的新對(duì)象巩步,否則,返回構(gòu)造函數(shù)中返回的對(duì)象桦踊。
11. 實(shí)現(xiàn)一個(gè)new函數(shù)
let _new = function(factory, ...rest) {
let o = {
"__proto__": factory.prototype
}
let res = factory.apply(o, rest)
return typeof res === 'object' ? res : o;
}
12. let椅野、const、var
let和var都用于聲明變量籍胯,而const必須初始化竟闪,且用于聲明常量,這個(gè)常量指的是普通類型的值不變和復(fù)雜類型的內(nèi)存地址不變芒炼。
var存在變量提升瘫怜,而let,const存在“暫時(shí)性死區(qū)”本刽,即在變量聲明之前就訪問變量的話鲸湃,會(huì)直接提示ReferenceError,而不像var那樣使用默認(rèn)值undefined
let,const只有塊級(jí)作用域子寓,而var只有全局作用域和函數(shù)作用域概念
13. 箭頭函數(shù)
首先語法更簡(jiǎn)化
不綁定this暗挑, 它會(huì)捕獲其所在(即定義的位置)上下文的this值, 作為自己的this值斜友,這也意味著使用call和apply是無法傳遞this炸裆,第一個(gè)參數(shù)就是需要傳遞的參數(shù)
不能使用new 關(guān)鍵字,因?yàn)榧^函數(shù)不是一個(gè)構(gòu)造函數(shù)
沒有prototype屬性
yield 關(guān)鍵字通常不能在箭頭函數(shù)中使用(除非是嵌套在允許使用的函數(shù)內(nèi))鲜屏。因此烹看,箭頭函數(shù)不能用作生成器。
arguments洛史,即沒有函數(shù)的參數(shù)arguments惯殊,但可以使用剩余參數(shù)...args替代
14. Promise詳解
Promise
是異步編程的一種解決方案,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件(回調(diào)地獄)——更合理和更強(qiáng)大也殖。Promise
對(duì)象有以下兩個(gè)特點(diǎn)
1.對(duì)象的狀態(tài)不受外界影響土思。Promise
對(duì)象代表一個(gè)異步操作,有三種狀態(tài):pending
(進(jìn)行中)忆嗜、fulfilled
(已成功)和rejected
(已失敿喝濉)。只有異步操作的結(jié)果捆毫,可以決定當(dāng)前是哪一種狀態(tài)闪湾,任何其他操作都無法改變這個(gè)狀態(tài)。這也是Promise這個(gè)名字的由來绩卤,它的英語意思就是“承諾”响谓,表示其他手段無法改變损合。
2.一旦狀態(tài)改變,就不會(huì)再變娘纷,任何時(shí)候都可以得到這個(gè)結(jié)果嫁审。Promise
對(duì)象的狀態(tài)改變,只有兩種可能:從pending
變?yōu)?code>fulfilled和從pending
變?yōu)?code>rejected赖晶。只要這兩種情況發(fā)生律适,狀態(tài)就凝固了,不會(huì)再變了遏插,會(huì)一直保持這個(gè)結(jié)果捂贿,這時(shí)就稱為resolved
(已定型)。如果改變已經(jīng)發(fā)生了胳嘲,你再對(duì)Promise
對(duì)象添加回調(diào)函數(shù)厂僧,也會(huì)立即得到這個(gè)結(jié)果。這與事件(Event)完全不同了牛,事件的特點(diǎn)是颜屠,如果你錯(cuò)過了它,再去監(jiān)聽鹰祸,是得不到結(jié)果的甫窟。基本用法
Promise
對(duì)象是一個(gè)構(gòu)造函數(shù),用來生成Promise
實(shí)例蛙婴。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
- 常用方法:
1.Promise.prototype.then()
Promise
實(shí)例具有then
方法粗井,也就是說,then
方法是定義在原型對(duì)象Promise.prototype
上的街图。它的作用是為Promise
實(shí)例添加狀態(tài)改變時(shí)的回調(diào)函數(shù)浇衬。
then
方法的第一個(gè)參數(shù)是resolved
狀態(tài)的回調(diào)函數(shù),第二個(gè)參數(shù)(可選)是rejected
狀態(tài)的回調(diào)函數(shù)餐济。
then
方法返回的是一個(gè)新的Promise實(shí)例(注意耘擂,不是原來那個(gè)Promise
實(shí)例)。因此可以采用鏈?zhǔn)綄懛ú椋碻then方法后面再調(diào)用另一個(gè)then方法。
promise.then(()=>{
// ...
}).then(()=> {
});
2.Promise.prototype.catch()
Promise.prototype.catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的別名赞赖,用于指定發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)滚朵。
promise.then(()=>{
// ...
}).catch((error)=> {
// 處理 getJSON 和 前一個(gè)回調(diào)函數(shù)運(yùn)行時(shí)發(fā)生的錯(cuò)誤
console.log('發(fā)生錯(cuò)誤!', error);
});
下面代碼中前域,第二種寫法要好于第一種寫法辕近,理由是第二種寫法可以捕獲前面then
方法執(zhí)行中的錯(cuò)誤,也更接近同步的寫法(try/catch
)匿垄。因此移宅,建議總是使用catch()
方法归粉,而不使用then()
方法的第二個(gè)參數(shù)。
跟傳統(tǒng)的try/catch
代碼塊不同的是漏峰,如果沒有使用catch()
方法指定錯(cuò)誤處理的回調(diào)函數(shù)糠悼,Promise
對(duì)象拋出的錯(cuò)誤不會(huì)傳遞到外層代碼,即不會(huì)有任何反應(yīng)浅乔。
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
3.Promise.prototype.finally()
finally()
方法用于指定不管 Promise
對(duì)象最后狀態(tài)如何倔喂,都會(huì)執(zhí)行的操作【肝【ES2018】
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
上面代碼中席噩,不管promise
最后的狀態(tài),在執(zhí)行完then
或catch
指定的回調(diào)函數(shù)以后贤壁,都會(huì)執(zhí)行finally
方法指定的回調(diào)函數(shù)悼枢。
finally
方法的回調(diào)函數(shù)不接受任何參數(shù),這意味著沒有辦法知道脾拆,前面的 Promise
狀態(tài)到底是fulfilled
還是rejected
馒索。這表明,finally
方法里面的操作假丧,應(yīng)該是與狀態(tài)無關(guān)的双揪,不依賴于 Promise
的執(zhí)行結(jié)果。
finally
本質(zhì)上是then
方法的特例包帚。
4.Promise.all()
Promise.all()
方法用于將多個(gè) Promise
實(shí)例渔期,包裝成一個(gè)新的 Promise
實(shí)例。
const p = Promise.all([p1, p2, p3]);
上面代碼中渴邦,Promise.all()
方法接受一個(gè)數(shù)組作為參數(shù)疯趟,p1、p2谋梭、p3
都是 Promise
實(shí)例信峻,如果不是,就會(huì)先調(diào)用Promise.resolve
方法瓮床,將參數(shù)轉(zhuǎn)為 Promise
實(shí)例盹舞,再進(jìn)一步處理。另外隘庄,Promise.all()
方法的參數(shù)可以不是數(shù)組踢步,但必須具有 Iterator
接口,且返回的每個(gè)成員都是 Promise
實(shí)例丑掺。
p
的狀態(tài)由p1约急、p2即彪、p3
決定驳庭,分成兩種情況。
(1)只有p1玻孟、p2、p3
的狀態(tài)都變成fulfilled
鳍征,p
的狀態(tài)才會(huì)變成fulfilled
黍翎,此時(shí)p1、p2蟆技、p3
的返回值組成一個(gè)數(shù)組玩敏,傳遞給p的回調(diào)函數(shù)。
(2)只要p1质礼、p2旺聚、p3
之中有一個(gè)被rejected
,p
的狀態(tài)就變成rejected
眶蕉,此時(shí)第一個(gè)被reject
的實(shí)例的返回值砰粹,會(huì)傳遞給p
的回調(diào)函數(shù)。
5.Promise.race()
Promise.race()
方法同樣是將多個(gè) Promise
實(shí)例造挽,包裝成一個(gè)新的 Promise
實(shí)例碱璃。
const p = Promise.race([p1, p2, p3]);
上面代碼中,只要p1饭入、p2嵌器、p3
之中有一個(gè)實(shí)例率先改變狀態(tài),p
的狀態(tài)就跟著改變谐丢。那個(gè)率先改變的 Promise
實(shí)例的返回值爽航,就傳遞給p
的回調(diào)函數(shù)。
Promise.race()
是異步操作競(jìng)賽乾忱,值返回最快的一個(gè)
6.Promise.allSettled()
Promise.allSettled()
方法接受一組 Promise
實(shí)例作為參數(shù)讥珍,包裝成一個(gè)新的 Promise
實(shí)例。只有等到所有這些參數(shù)實(shí)例都返回結(jié)果窄瘟,不管是fulfilled
還是rejected
衷佃,包裝實(shí)例才會(huì)結(jié)束√愦校【ES2020】
有時(shí)候氏义,我們不關(guān)心異步操作的結(jié)果,只關(guān)心這些操作有沒有結(jié)束图云。這時(shí)惯悠,Promise.allSettled()
方法就很有用。如果沒有這個(gè)方法琼稻,想要確保所有操作都結(jié)束吮螺,就很麻煩饶囚。Promise.all()
方法無法做到這一點(diǎn)帕翻。
7.Promise.any()
Promise.any()
方法接受一組 Promise
實(shí)例作為參數(shù)鸠补,包裝成一個(gè)新的 Promise
實(shí)例。只要參數(shù)實(shí)例有一個(gè)變成fulfilled
狀態(tài)嘀掸,包裝實(shí)例就會(huì)變成fulfilled
狀態(tài)紫岩;如果所有參數(shù)實(shí)例都變成rejected
狀態(tài),包裝實(shí)例就會(huì)變成rejected
狀態(tài)睬塌。
Promise.any()
跟Promise.race()
方法很像泉蝌,只有一點(diǎn)不同,就是不會(huì)因?yàn)槟硞€(gè) Promise
變成rejected
狀態(tài)而結(jié)束揩晴。
const promises = [
fetch('/endpoint-a').then(() => 'a'),
fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
console.log(first);
} catch (error) {
console.log(error);
}
上面代碼中勋陪,Promise.any()
方法的參數(shù)數(shù)組包含三個(gè) Promise
操作。其中只要有一個(gè)變成fulfilled
硫兰,Promise.any()
返回的 Promise
對(duì)象就變成fulfilled
诅愚。如果所有三個(gè)操作都變成rejected
,那么await
命令就會(huì)拋出錯(cuò)誤劫映。
8.Promise.resolve()
有時(shí)需要將現(xiàn)有對(duì)象轉(zhuǎn)為 Promise
對(duì)象违孝,Promise.resolve()
方法就起到這個(gè)作用。
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
上面代碼將 jQuery
生成的deferred
對(duì)象泳赋,轉(zhuǎn)為一個(gè)新的 Promise
對(duì)象雌桑。
Promise.resolve()
等價(jià)于下面的寫法。
Promise.resolve('foo')
// 等價(jià)于
new Promise(resolve => resolve('foo'))
9.Promise.reject()
Promise.reject(reason)
方法也會(huì)返回一個(gè)新的 Promise
實(shí)例祖今,該實(shí)例的狀態(tài)為rejected
校坑。
const p = Promise.reject('出錯(cuò)了');
// 等同于
const p = new Promise((resolve, reject) => reject('出錯(cuò)了'))
p.then(null, function (s) {
console.log(s)
});
// 出錯(cuò)了
上面代碼生成一個(gè) Promise
對(duì)象的實(shí)例p
,狀態(tài)為rejected
衅鹿,回調(diào)函數(shù)會(huì)立即執(zhí)行撒踪。
注意,Promise.reject()
方法的參數(shù)大渤,會(huì)原封不動(dòng)地作為reject
的理由制妄,變成后續(xù)方法的參數(shù)。這一點(diǎn)與Promise.resolve
方法不一致泵三。
10.Promise.try()
讓同步函數(shù)同步執(zhí)行耕捞,異步函數(shù)異步執(zhí)行,并且讓它們具有統(tǒng)一的 API 烫幕,簡(jiǎn)言之就是俺抽,讓同步操作也可以像異步一樣執(zhí)行。
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
15. ES6新特性
嚴(yán)格模式
1.變量必須聲明后较曼,才能使用
2.函數(shù)的參數(shù)不能有同名屬性, 否則報(bào)錯(cuò)
3.禁止this指向全局對(duì)象
4.增加了保留字(比如protected磷斧、static和interface)關(guān)于let和const新增的變量聲明
變量的解構(gòu)賦值
字符串的擴(kuò)展
1.includes():返回布爾值,表示是否找到了參數(shù)字符串。
2.startsWith():返回布爾值弛饭,表示參數(shù)字符串是否在原字符串的頭部冕末。
3.endsWith():返回布爾值,表示參數(shù)字符串是否在原字符串的尾部侣颂。數(shù)值的擴(kuò)展
1.Number.isFinite()用來檢查一個(gè)數(shù)值是否為有限的(finite)档桃。
2.Number.isNaN()用來檢查一個(gè)值是否為NaN。函數(shù)的擴(kuò)展憔晒,函數(shù)參數(shù)指定默認(rèn)值
數(shù)組的擴(kuò)展藻肄,擴(kuò)展運(yùn)算符
對(duì)象的擴(kuò)展,對(duì)象的解構(gòu)
新增symbol數(shù)據(jù)類型
1.ES6 引入了一種新的原始數(shù)據(jù)類型Symbol
拒担,表示獨(dú)一無二的值嘹屯。它是JavaScript
語言的第七種數(shù)據(jù)類型,前六種是:undefined
从撼、null
抚垄、布爾值(Boolean
)、字符串(String
)谋逻、數(shù)值(Number
)呆馁、對(duì)象(Object
)。
2.Symbol
值通過Symbol
函數(shù)生成毁兆。這就是說浙滤,對(duì)象的屬性名現(xiàn)在可以有兩種類型,一種是原來就有的字符串气堕,另一種就是新增的Symbol
類型纺腊。凡是屬性名屬于Symbol
類型,就都是獨(dú)一無二的茎芭,可以保證不會(huì)與其他屬性名產(chǎn)生沖突揖膜。
let s = Symbol();
Set 和 Map 數(shù)據(jù)結(jié)構(gòu)
1.ES6 提供了新的數(shù)據(jù)結(jié)構(gòu) Set。它類似于數(shù)組梅桩,但是成員的值都是唯一的壹粟,沒有重復(fù)的值。Set 本身是一個(gè)構(gòu)造函數(shù)宿百,用來生成 Set 數(shù)據(jù)結(jié)構(gòu)趁仙。
2.Map它類似于對(duì)象,也是鍵值對(duì)的集合垦页,但是“鍵”的范圍不限于字符串雀费,各種類型的值(包括對(duì)象)都可以當(dāng)作鍵。Proxy
1.Proxy
可以理解成痊焊,在目標(biāo)對(duì)象之前架設(shè)一層“攔截”盏袄,外界對(duì)該對(duì)象的訪問忿峻,都必須先通過這層攔截,因此提供了一種機(jī)制辕羽,可以對(duì)外界的訪問進(jìn)行過濾和改寫炭菌。
2.Proxy
這個(gè)詞的原意是代理,用在這里表示由它來“代理”某些操作逛漫,可以譯為“代理器”。
3.Vue3.0使用了proxy
Promise
1.Promise
是異步編程的一種解決方案赘艳,比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強(qiáng)大酌毡。
2.對(duì)象的狀態(tài)不受外界影響。
3.一旦狀態(tài)改變蕾管,就不會(huì)再變枷踏,任何時(shí)候都可以得到這個(gè)結(jié)果。async和await函數(shù)
1.async
表示函數(shù)里有異步操作掰曾,await
表示緊跟在后面的表達(dá)式需要等待結(jié)果旭蠕。
2.正常情況下,await
命令后面是一個(gè)Promise
對(duì)象旷坦。如果不是掏熬,會(huì)被轉(zhuǎn)成一個(gè)立即resolve
的 Promise 對(duì)象。
3.async
函數(shù)返回一個(gè)Promise
對(duì)象秒梅,可以使用then
方法添加回調(diào)函數(shù)旗芬。當(dāng)函數(shù)執(zhí)行的時(shí)候,一旦遇到await
就會(huì)先返回捆蜀,等到異步操作完成疮丛,再接著執(zhí)行函數(shù)體內(nèi)后面的語句。Class
-
class
跟let辆它、const
一樣:不存在變量提升誊薄、不能重復(fù)聲明 - ES6 的
class
可以看作只是一個(gè)語法糖,它的絕大部分功能 - ES5 都可以做到锰茉,新的
class
寫法只是讓對(duì)象原型的寫法更加清晰呢蔫、更像面向?qū)ο缶幊痰恼Z法而已。
- Module
1.ES6 的模塊自動(dòng)采用嚴(yán)格模式飒筑,不管你有沒有在模塊頭部加上"use strict";咐刨。
2.import
和export
命令以及export
和export default
的區(qū)別
3.export:
(1)export
導(dǎo)出應(yīng)該是一種接口或是理解為一種定義,而不應(yīng)該是值
(2)export
導(dǎo)出的接口扬霜,與其對(duì)應(yīng)的值是動(dòng)態(tài)綁定關(guān)系定鸟,即通過該接口,可以取到模塊內(nèi)部實(shí)時(shí)的值著瓶。
(3)export
命令可以出現(xiàn)在模塊的任何位置联予,只要處于模塊頂層就可以。
4.export default:
(1)本質(zhì)上,export default
就是在Module
上輸出一個(gè)叫做default
的變量或方法沸久,和export
完全不同季眷,所以它后面不能跟變量聲明語句,但表達(dá)式卷胯,function子刮,class
除外。
5.import:
(1)import
命令輸入的變量都是只讀的窑睁,因?yàn)樗谋举|(zhì)是輸入接口挺峡。也就是說,不允許在加載模塊的腳本里面担钮,改寫接口橱赠。
(2)對(duì)于export
導(dǎo)出的接口應(yīng)該使用import { interface1 }
的方式
(3)對(duì)于export default
導(dǎo)出的變量應(yīng)該使用import interface1
的方式
(4)import
命令具有提升效果,會(huì)提升到整個(gè)模塊的頭部箫津,首先執(zhí)行狭姨。
(5)如果多次重復(fù)執(zhí)行同一句import
語句,那么只會(huì)執(zhí)行一次苏遥,而不會(huì)執(zhí)行多次饼拍。
16. CommonJs
CommonJS
是為服務(wù)器提供的一種模塊形式的優(yōu)化,CommonJS
模塊建議指定一個(gè)簡(jiǎn)單的用于聲明模塊服務(wù)器端的API田炭,并且不像AMD那樣嘗試去廣泛的操心諸如io
惕耕,文件系統(tǒng),約定以及更多的一攬子問題诫肠。它有以下特點(diǎn):主要運(yùn)用在服務(wù)端
js
司澎,如node
全局對(duì)象:
global
一個(gè)文件就是一個(gè)模塊,擁有單獨(dú)的作用域栋豫,所有代碼都運(yùn)行在模塊作用域挤安,不會(huì)污染全局作用域;模塊可以多次加載丧鸯,但只會(huì)在第一次加載的時(shí)候運(yùn)行一次蛤铜,然后運(yùn)行結(jié)果就被緩存了,以后再加載丛肢,就直接讀取緩存結(jié)果围肥;(你可以暴露一個(gè)時(shí)間戳來測(cè)試)
模塊的加載順序,按照代碼的出現(xiàn)順序蜂怎,
同步加載
通過
require
來加載模塊:require
基本功能:讀取并執(zhí)行一個(gè)JS文件穆刻,然后返回該模塊的module.exports
對(duì)象,如果沒有發(fā)現(xiàn)指定模塊會(huì)報(bào)錯(cuò)通過
exports
和module.exports
來暴露模塊中的內(nèi)容那么
exports
和module.exports
有什么區(qū)別呢杠步?
1.模塊內(nèi)的exports
:為了方便氢伟,node
為每個(gè)模塊提供一個(gè)exports
變量榜轿,其指向module.exports
,相當(dāng)于在模塊頭部加了這句話:var exports = module.exports
朵锣,在對(duì)外輸出時(shí)谬盐,可以給module.exports
對(duì)象添加方法
2.module.exports
方法還可以單獨(dú)返回一個(gè)數(shù)據(jù)類型(String、Number诚些、Object...
)飞傀,而exports
只能返回一個(gè)Object
對(duì)象。所有的exports
對(duì)象最終都是通過module.exports
傳遞執(zhí)行诬烹,因此可以更確切地說砸烦,exports
是給module.exports
添加屬性和方法。
17. CommonJS和ES6模塊的區(qū)別椅您?
CommonJS模塊是運(yùn)行時(shí)加載,ES6 Modules是編譯時(shí)輸出接口
CommonJS輸出是值的拷貝寡键;ES6 Modules輸出的是值的引用掀泳,被輸出模塊的內(nèi)部的改變會(huì)影響引用的改變
CommonJs導(dǎo)入的模塊路徑可以是一個(gè)表達(dá)式,因?yàn)樗褂玫氖莚equire()方法西轩;而ES6 - Modules只能是字符串
CommonJS this指向當(dāng)前模塊员舵,ES6 Modules this指向undefined
且ES6 Modules中沒有這些頂層變量:arguments、require藕畔、module马僻、exports、__filename注服、__dirname
18. 什么是UMD韭邓?AMD和CMD?
UMD是為了讓同一個(gè)代碼模塊在使用 CommonJs溶弟、CMD 甚至是 AMD 的項(xiàng)目中運(yùn)行女淑。為了實(shí)現(xiàn)兼容,所以有點(diǎn)“丑陋”辜御。
AMD 是 RequireJS 在推廣過程中對(duì)模塊定義的規(guī)范化而產(chǎn)出的鸭你。
CMD 是 SeaJS 在推廣過程中對(duì)模塊定義的規(guī)范化而產(chǎn)出的。
對(duì)于依賴的模塊擒权,AMD可以提前執(zhí)行袱巨,也可以延遲執(zhí)行,CMD則是延遲執(zhí)行碳抄。
AMD推崇依賴前置愉老,CMD則推崇就近依賴。(可以說剖效,CMD就是個(gè)"懶人")
AMD支持全局require俺夕、局部require裳凸,但是CMD則不支持全局require,所以CMD沒有全局API劝贸,而AMD有姨谷。
19. 浮點(diǎn)數(shù)計(jì)算精確度問題
- 因?yàn)楦↑c(diǎn)數(shù)在計(jì)算機(jī)內(nèi)是以二進(jìn)制存儲(chǔ)和計(jì)算的,所以在浮點(diǎn)數(shù)上計(jì)算會(huì)存在精度問題如:
0.1 + 0.2 = 0.30000000000000004
1.0 - 0.9 = 0.09999999999999998
- 解決:
1.使用toFixed
進(jìn)行“四舍五入”
2.將數(shù)擴(kuò)大至整數(shù)映九,再進(jìn)行計(jì)算
3.使用例如number-precision
等第三庫(kù)進(jìn)行計(jì)算
20. 最大安全數(shù)
- 最大安全值為
2^53-1
最大安全值為-2^53+1
21. Array對(duì)象
- 構(gòu)造函數(shù)梦湘,Array是 JavaScript 的原生對(duì)象,同時(shí)也是一個(gè)構(gòu)造函數(shù)件甥,可以用它生成新的數(shù)組捌议。
// bad
var arr = new Array(1, 2);
// good 字面量法,更好的聲明方式
var arr = [1, 2];
- Array.isArray()
這是一個(gè)靜態(tài)方法引有,返回一個(gè)布爾值瓣颅,表示參數(shù)是否為數(shù)組。它可以彌補(bǔ)typeof
運(yùn)算符的不足譬正。
var arr = [1, 2, 3];
typeof arr // "object"
Array.isArray(arr) // true
以下都是實(shí)例方法
- Array.valueOf()
valueOf
方法是一個(gè)所有對(duì)象都擁有的方法宫补,表示對(duì)該對(duì)象求值。不同對(duì)象的valueOf
方法不盡一致曾我,數(shù)組的valueOf
方法返回?cái)?shù)組本身粉怕。
var arr = [1, 2, 3];
arr.valueOf() // [1, 2, 3]
- Array.toString()
toString
方法也是對(duì)象的通用方法,數(shù)組的toString
方法返回?cái)?shù)組的字符串形式抒巢。
var arr = [1, 2, 3];
arr.toString() // "1,2,3"
var arr = [1, 2, 3, [4, 5, 6]];
arr.toString() // "1,2,3,4,5,6"
- Array.push()
push
方法用于在數(shù)組的末端添加一個(gè)或多個(gè)元素贫贝,并返回添加新元素后的數(shù)組長(zhǎng)度。注意蛉谜,該方法會(huì)改變?cè)瓟?shù)組稚晚。
var arr = [];
arr.push(1) // 1
arr.push('a') // 2
arr.push(true, {}) // 4
arr // [1, 'a', true, {}]
- Array.pop()
pop
方法用于刪除數(shù)組的最后一個(gè)元素,并返回該元素型诚。注意蜈彼,該方法會(huì)改變?cè)瓟?shù)組。
var arr = ['a', 'b', 'c'];
arr.pop() // 'c'
arr // ['a', 'b']
// 對(duì)空數(shù)組使用pop方法俺驶,不會(huì)報(bào)錯(cuò)幸逆,而是返回undefined。
[].pop() // undefined
// push()和pop()結(jié)合使用暮现,就構(gòu)成了“后進(jìn)先出”的棧結(jié)構(gòu)(stack)还绘。
var arr = [];
arr.push(1, 2);
arr.push(3);
arr.pop();
arr // [1, 2]
// 上面代碼中,3是最后進(jìn)入數(shù)組的栖袋,但是最早離開數(shù)組拍顷。
- Array.shift()
shift
方法用于刪除數(shù)組的第一個(gè)元素,并返回該元素塘幅。注意昔案,該方法會(huì)改變?cè)瓟?shù)組尿贫。
var a = ['a', 'b', 'c'];
a.shift() // 'a'
a // ['b', 'c']
// shift方法可以遍歷并清空一個(gè)數(shù)組。
var list = [1, 2, 3, 4, 5, 6];
var item;
while (item = list.shift()) {
console.log(item);
}
list // []
push和shift結(jié)合使用踏揣,就構(gòu)成了“先進(jìn)先出”的隊(duì)列結(jié)構(gòu)(queue)庆亡。
- Array.unshift()
unshift
方法用于在數(shù)組的第一個(gè)位置添加元素,并返回添加新元素后的數(shù)組長(zhǎng)度捞稿。注意又谋,該方法會(huì)改變?cè)瓟?shù)組。
var a = ['a', 'b', 'c'];
a.unshift('x'); // 4
a // ['x', 'a', 'b', 'c']
// unshift方法可以接受多個(gè)參數(shù)娱局,這些參數(shù)都會(huì)添加到目標(biāo)數(shù)組頭部彰亥。
var arr = [ 'c', 'd' ];
arr.unshift('a', 'b') // 4
arr // [ 'a', 'b', 'c', 'd' ]
- Array.join()
join
方法以指定參數(shù)作為分隔符,將所有數(shù)組成員連接為一個(gè)字符串返回衰齐。如果不提供參數(shù)任斋,默認(rèn)用逗號(hào)分隔。
var a = [1, 2, 3, 4];
a.join(' ') // '1 2 3 4'
a.join(' | ') // "1 | 2 | 3 | 4"
a.join() // "1,2,3,4"
// 如果數(shù)組成員是undefined或null或空位耻涛,會(huì)被轉(zhuǎn)成空字符串
[undefined, null].join('#')
// '#'
['a',, 'b'].join('-')
// 'a--b'
// 通過call方法废酷,這個(gè)方法也可以用于字符串或類似數(shù)組的對(duì)象。
Array.prototype.join.call('hello', '-')
// "h-e-l-l-o"
var obj = { 0: 'a', 1: 'b', length: 2 };
Array.prototype.join.call(obj, '-')
// 'a-b'
- Array.concat()
concat
方法用于多個(gè)數(shù)組的合并犬第。它將新數(shù)組的成員锦积,添加到原數(shù)組成員的后部芒帕,然后返回一個(gè)新數(shù)組歉嗓,原數(shù)組不變。
['hello'].concat(['world'])
// ["hello", "world"]
['hello'].concat(['world'], ['!'])
// ["hello", "world", "!"]
[].concat({a: 1}, {b: 2})
// [{ a: 1 }, { b: 2 }]
[2].concat({a: 1})
// [2, {a: 1}]
// 除了數(shù)組作為參數(shù)背蟆,concat也接受其他類型的值作為參數(shù)鉴分,添加到目標(biāo)數(shù)組尾部
[1, 2, 3].concat(4, 5, 6)
// [1, 2, 3, 4, 5, 6]
// 如果數(shù)組成員包括對(duì)象,concat方法返回當(dāng)前數(shù)組的一個(gè)淺拷貝带膀。所謂“淺拷貝”志珍,指的是新數(shù)組拷貝的是對(duì)象的引用
var obj = { a: 1 };
var oldArray = [obj];
var newArray = oldArray.concat();
obj.a = 2;
newArray[0].a // 2
上面代碼中,原數(shù)組包含一個(gè)對(duì)象垛叨,concat方法生成的新數(shù)組包含這個(gè)對(duì)象的引用伦糯。所以,改變?cè)瓕?duì)象以后嗽元,新數(shù)組跟著改變敛纲。
- Array.reverse()
reverse
方法用于顛倒排列數(shù)組元素,返回改變后的數(shù)組剂癌。注意淤翔,該方法將改變?cè)瓟?shù)組。
var a = ['a', 'b', 'c'];
a.reverse() // ["c", "b", "a"]
a // ["c", "b", "a"]
- Array.slice()
slice
方法用于提取目標(biāo)數(shù)組的一部分佩谷,返回一個(gè)新數(shù)組旁壮,原數(shù)組不變监嗜。
arr.slice(start, end);
第一個(gè)參數(shù)為起始位置(從0開始),第二個(gè)參數(shù)為終止位置(但該位置的元素本身不包括在內(nèi))抡谐。如果省略第二個(gè)參數(shù)裁奇,則一直返回到原數(shù)組的最后一個(gè)成員
var a = ['a', 'b', 'c'];
a.slice(0) // ["a", "b", "c"]
a.slice(1) // ["b", "c"]
a.slice(1, 2) // ["b"]
a.slice(2, 6) // ["c"]
a.slice() // ["a", "b", "c"] 最后一個(gè)例子沒有參數(shù),實(shí)際上等于返回一個(gè)原數(shù)組的拷貝
// 如果slice方法的參數(shù)是負(fù)數(shù)童叠,則表示倒數(shù)計(jì)算的位置
var a = ['a', 'b', 'c'];
a.slice(-2) // ["b", "c"]
a.slice(-2, -1) // ["b"] -2表示倒數(shù)計(jì)算的第二個(gè)位置框喳,-1表示倒數(shù)計(jì)算的第一個(gè)位置
// 如果第一個(gè)參數(shù)大于等于數(shù)組長(zhǎng)度,或者第二個(gè)參數(shù)小于第一個(gè)參數(shù)厦坛,則返回空數(shù)組
var a = ['a', 'b', 'c'];
a.slice(4) // []
a.slice(2, 1) // []
slice
方法的一個(gè)重要應(yīng)用五垮,是將類似數(shù)組的對(duì)象轉(zhuǎn)為真正的數(shù)組
Array.prototype.slice.call({
0: 'a',
1: 'b',
length: 2
})
// ['a', 'b']
Array.prototype.slice.call(document.querySelectorAll("div"));
Array.prototype.slice.call(arguments);
上面代碼的參數(shù)都不是數(shù)組,但是通過call
方法杜秸,在它們上面調(diào)用slice
方法放仗,就可以把它們轉(zhuǎn)為真正的數(shù)組。
- Array.splice()
splice
方法用于刪除原數(shù)組的一部分成員撬碟,并可以在刪除的位置添加新的數(shù)組成員诞挨,返回值是被刪除的元素。注意呢蛤,該方法會(huì)改變?cè)瓟?shù)組惶傻。
// 第一個(gè)參數(shù)是刪除的起始位置(從0開始),第二個(gè)參數(shù)是被刪除的元素個(gè)數(shù)其障。如果后面還有更多的參數(shù)银室,則表示這些就是要被插入數(shù)組的新元素
arr.splice(start, count, addElement1, addElement2, ...);
// 從原數(shù)組4號(hào)位置,刪除了兩個(gè)數(shù)組成員
var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(4, 2) // ["e", "f"]
a // ["a", "b", "c", "d"]
// 除了刪除成員励翼,還插入了兩個(gè)新成員
var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(4, 2, 1, 2) // ["e", "f"]
a // ["a", "b", "c", "d", 1, 2]
// 起始位置如果是負(fù)數(shù)蜈敢,就表示從倒數(shù)位置開始刪除
var a = ['a', 'b', 'c', 'd', 'e', 'f'];
a.splice(-4, 2) // ["c", "d"] 倒數(shù)第四個(gè)位置c開始刪除兩個(gè)成員
// 如果只是單純地插入元素,splice方法的第二個(gè)參數(shù)可以設(shè)為0汽抚。
var a = [1, 1, 1];
a.splice(1, 0, 2) // []
a // [1, 2, 1, 1]
// 如果只提供第一個(gè)參數(shù)抓狭,等同于將原數(shù)組在指定位置拆分成兩個(gè)數(shù)組。
var a = [1, 2, 3, 4];
a.splice(2) // [3, 4]
a // [1, 2]
- Array.sort()
sort
方法對(duì)數(shù)組成員進(jìn)行排序造烁,默認(rèn)是按照字典順序排序否过。排序后,原數(shù)組將被改變惭蟋。
['d', 'c', 'b', 'a'].sort()
// ['a', 'b', 'c', 'd']
[4, 3, 2, 1].sort()
// [1, 2, 3, 4]
[11, 101].sort()
// [101, 11]
[10111, 1101, 111].sort()
// [10111, 1101, 111]
上面代碼的最后兩個(gè)例子苗桂,需要特殊注意。sort
方法不是按照大小排序敞葛,而是按照字典順序誉察。也就是說,數(shù)值會(huì)被先轉(zhuǎn)成字符串惹谐,再按照字典順序進(jìn)行比較持偏,所以101排在11的前面驼卖。
// 如果想讓sort方法按照自定義方式排序,可以傳入一個(gè)函數(shù)作為參數(shù)
[10111, 1101, 111].sort(function (a, b) {
return a - b;
})
// [111, 1101, 10111]
上面代碼中鸿秆,sort
的參數(shù)函數(shù)本身接受兩個(gè)參數(shù)酌畜,表示進(jìn)行比較的兩個(gè)數(shù)組成員。如果該函數(shù)的返回值大于0
卿叽,表示第一個(gè)成員排在第二個(gè)成員后面桥胞;其他情況下,都是第一個(gè)元素排在第二個(gè)元素前面考婴。
[
{ name: "張三", age: 30 },
{ name: "李四", age: 24 },
{ name: "王五", age: 28 }
].sort(function (o1, o2) {
return o1.age - o2.age;
})
// [
// { name: "李四", age: 24 },
// { name: "王五", age: 28 },
// { name: "張三", age: 30 }
// ]
- Array.map()
map
方法將數(shù)組的所有成員依次傳入?yún)?shù)函數(shù)贩虾,然后把每一次的執(zhí)行結(jié)果組成一個(gè)新數(shù)組返回。
var numbers = [1, 2, 3];
numbers.map(function (n) {
return n + 1;
});
// [2, 3, 4]
numbers
// [1, 2, 3]
上面代碼中沥阱,numbers
數(shù)組的所有成員依次執(zhí)行參數(shù)函數(shù)缎罢,運(yùn)行結(jié)果組成一個(gè)新數(shù)組返回旧噪,原數(shù)組沒有變化辈赋。
map
方法接受一個(gè)函數(shù)作為參數(shù)。該函數(shù)調(diào)用時(shí)莱革,map
方法向它傳入三個(gè)參數(shù):當(dāng)前成員崇棠、當(dāng)前位置和數(shù)組本身咽袜。
[1, 2, 3].map(function(elem, index, arr) {
return elem * index;
});
// [0, 2, 6]
上面代碼中,map
方法的回調(diào)函數(shù)有三個(gè)參數(shù)枕稀,elem
為當(dāng)前成員的值询刹,index
為當(dāng)前成員的位置,arr
為原數(shù)組([1, 2, 3]
)抽莱。
map
方法還可以接受第二個(gè)參數(shù)范抓,用來綁定回調(diào)函數(shù)內(nèi)部的this
變量骄恶。
var arr = ['a', 'b', 'c'];
[1, 2].map(function (e) {
return this[e];
}, arr)
// ['b', 'c']
上面代碼通過map
方法的第二個(gè)參數(shù)食铐,將回調(diào)函數(shù)內(nèi)部的this
對(duì)象,指向arr
數(shù)組僧鲁。
如果數(shù)組有空位虐呻,map
方法的回調(diào)函數(shù)在這個(gè)位置不會(huì)執(zhí)行,會(huì)跳過數(shù)組的空位寞秃。
var f = function (n) { return 'a' };
[1, undefined, 2].map(f) // ["a", "a", "a"]
[1, null, 2].map(f) // ["a", "a", "a"]
[1, , 2].map(f) // ["a", , "a"]
上面代碼中斟叼,map
方法不會(huì)跳過undefined
和null
,但是會(huì)跳過空位春寿。
- Array.forEach()
forEach
方法與map
方法很相似朗涩,也是對(duì)數(shù)組的所有成員依次執(zhí)行參數(shù)函數(shù)。但是绑改,forEach
方法不返回值谢床,只用來操作數(shù)據(jù)兄一。這就是說,如果數(shù)組遍歷的目的是為了得到返回值识腿,那么使用map
方法出革,否則使用forEach
方法。
forEach
的用法與map
方法一致渡讼,參數(shù)是一個(gè)函數(shù)骂束,該函數(shù)同樣接受三個(gè)參數(shù):當(dāng)前值、當(dāng)前位置成箫、整個(gè)數(shù)組展箱。
function log(element, index, array) {
console.log('[' + index + '] = ' + element);
}
[2, 5, 9].forEach(log);
// [0] = 2
// [1] = 5
// [2] = 9
上面代碼中,forEach
遍歷數(shù)組不是為了得到返回值蹬昌,而是為了在屏幕輸出內(nèi)容析藕,所以不必使用map
方法。
forEach
方法也可以接受第二個(gè)參數(shù)凳厢,綁定參數(shù)函數(shù)的this變量账胧。
var out = [];
[1, 2, 3].forEach(function(elem) {
this.push(elem * elem);
}, out);
out // [1, 4, 9]
上面代碼中,空數(shù)組out
是forEach
方法的第二個(gè)參數(shù)先紫,結(jié)果治泥,回調(diào)函數(shù)內(nèi)部的this
關(guān)鍵字就指向out
。
注意遮精,forEach
方法無法中斷執(zhí)行居夹,總是會(huì)將所有成員遍歷完。如果希望符合某種條件時(shí)本冲,就中斷遍歷准脂,要使用for
循環(huán)。
var arr = [1, 2, 3];
for (var i = 0; i < arr.length; i++) {
if (arr[i] === 2) break;
console.log(arr[i]);
}
// 1
上面代碼中檬洞,執(zhí)行到數(shù)組的第二個(gè)成員時(shí)狸膏,就會(huì)中斷執(zhí)行。forEach
方法做不到這一點(diǎn)添怔。
forEach
方法也會(huì)跳過數(shù)組的空位湾戳。
var log = function (n) {
console.log(n + 1);
};
[1, undefined, 2].forEach(log)
// 2
// NaN
// 3
[1, null, 2].forEach(log)
// 2
// 1
// 3
[1, , 2].forEach(log)
// 2
// 3
上面代碼中,forEach
方法不會(huì)跳過undefined
和null
广料,但會(huì)跳過空位砾脑。
- Array.filter()
filter
方法用于過濾數(shù)組成員,滿足條件的成員組成一個(gè)新數(shù)組返回艾杏。
它的參數(shù)是一個(gè)函數(shù)韧衣,所有數(shù)組成員依次執(zhí)行該函數(shù),返回結(jié)果為true
的成員組成一個(gè)新數(shù)組返回。該方法不會(huì)改變?cè)瓟?shù)組畅铭。
[1, 2, 3, 4, 5].filter(function (elem) {
return (elem > 3);
})
// [4, 5] 上面代碼將大于3的數(shù)組成員萧求,作為一個(gè)新數(shù)組返回。
var arr = [0, 1, 'a', false];
arr.filter(Boolean)
// [1, "a"] 上面代碼中顶瞒,filter方法返回?cái)?shù)組arr里面所有布爾值為true的成員夸政。
filter
方法的參數(shù)函數(shù)可以接受三個(gè)參數(shù):當(dāng)前成員,當(dāng)前位置和整個(gè)數(shù)組
[1, 2, 3, 4, 5].filter(function (elem, index, arr) {
return index % 2 === 0;
});
// [1, 3, 5] 上面代碼返回偶數(shù)位置的成員組成的新數(shù)組榴徐。
filter
方法還可以接受第二個(gè)參數(shù)守问,用來綁定參數(shù)函數(shù)內(nèi)部的this
變量。
var obj = { MAX: 3 };
var myFilter = function (item) {
if (item > this.MAX) return true;
};
var arr = [2, 8, 3, 4, 1, 3, 2, 9];
arr.filter(myFilter, obj) // [8, 4, 9]
上面代碼中坑资,過濾器myFilter
內(nèi)部有this變量耗帕,它可以被filter
方法的第二個(gè)參數(shù)obj
綁定,返回大于3
的成員袱贮。
- Array.some()仿便,Array.every()
這兩個(gè)方法類似“斷言”(assert),返回一個(gè)布爾值攒巍,表示判斷數(shù)組成員是否符合某種條件嗽仪。
它們接受一個(gè)函數(shù)作為參數(shù),所有數(shù)組成員依次執(zhí)行該函數(shù)柒莉。該函數(shù)接受三個(gè)參數(shù):當(dāng)前成員闻坚、當(dāng)前位置和整個(gè)數(shù)組,然后返回一個(gè)布爾值兢孝。
some
方法是只要一個(gè)成員的返回值是true
窿凤,則整個(gè)some
方法的返回值就是true
,否則返回false
跨蟹。(一真即真)
var arr = [1, 2, 3, 4, 5];
arr.some(function (elem, index, arr) {
return elem >= 3;
});
// true 上面代碼中雳殊,如果數(shù)組arr有一個(gè)成員大于等于3,some方法就返回true窗轩。
every
方法是所有成員的返回值都是true
夯秃,整個(gè)every
方法才返回true
,否則返回false
品姓。(一假即假)
var arr = [1, 2, 3, 4, 5];
arr.every(function (elem, index, arr) {
return elem >= 3;
});
// false 上面代碼中寝并,數(shù)組arr并非所有成員大于等于3箫措,所以返回false腹备。
注意,對(duì)于空數(shù)組斤蔓,some
方法返回false
植酥,every
方法返回true
,回調(diào)函數(shù)都不會(huì)執(zhí)行。
function isEven(x) { return x % 2 === 0 }
[].some(isEven) // false
[].every(isEven) // true
some
和every
方法還可以接受第二個(gè)參數(shù)友驮,用來綁定參數(shù)函數(shù)內(nèi)部的`this變量漂羊。
- Array.reduce(),Array.reduceRight()
reduce
方法和reduceRight
方法依次處理數(shù)組的每個(gè)成員卸留,最終累計(jì)為一個(gè)值走越。它們的差別是,reduce
是從左到右處理(從第一個(gè)成員到最后一個(gè)成員)耻瑟,reduceRight
則是從右到左(從最后一個(gè)成員到第一個(gè)成員)旨指,其他完全一樣。
[1, 2, 3, 4, 5].reduce(function (a, b) {
console.log(a, b);
return a + b;
})
// 1 2
// 3 3
// 6 4
// 10 5
//最后結(jié)果:15
上面代碼中喳整,reduce
方法求出數(shù)組所有成員的和谆构。第一次執(zhí)行,a是數(shù)組的第一個(gè)成員1框都,b是數(shù)組的第二個(gè)成員2搬素。第二次執(zhí)行,a為上一輪的返回值3魏保,b為第三個(gè)成員3熬尺。第三次執(zhí)行,a為上一輪的返回值6谓罗,b為第四個(gè)成員4猪杭。第四次執(zhí)行,a為上一輪返回值10妥衣,b為第五個(gè)成員5皂吮。至此所有成員遍歷完成,整個(gè)方法的返回值就是最后一輪的返回值15税手。
reduce
方法和reduceRight
方法的第一個(gè)參數(shù)都是一個(gè)函數(shù)蜂筹。該函數(shù)接受以下四個(gè)參數(shù)。
1.累積變量芦倒,默認(rèn)為數(shù)組的第一個(gè)成員
2.當(dāng)前變量艺挪,默認(rèn)為數(shù)組的第二個(gè)成員
3.當(dāng)前位置(從0開始)
4.原數(shù)組
這四個(gè)參數(shù)之中,只有前兩個(gè)是必須的兵扬,后兩個(gè)則是可選的麻裳。
如果要對(duì)累積變量指定初值,可以把它放在reduce
方法和reduceRight
方法的第二個(gè)參數(shù)器钟。
[1, 2, 3, 4, 5].reduce(function (a, b) {
return a + b;
}, 10);
// 25
上面代碼指定參數(shù)a的初值為10津坑,所以數(shù)組從10開始累加,最終結(jié)果為25傲霸。注意疆瑰,這時(shí)b是從數(shù)組的第一個(gè)成員開始遍歷眉反。
上面的第二個(gè)參數(shù)相當(dāng)于設(shè)定了默認(rèn)值,處理空數(shù)組時(shí)尤其有用穆役。
function add(prev, cur) {
return prev + cur;
}
[].reduce(add)
// TypeError: Reduce of empty array with no initial value
[].reduce(add, 1)
// 1
上面代碼中寸五,由于空數(shù)組取不到初始值,reduce
方法會(huì)報(bào)錯(cuò)耿币。這時(shí)梳杏,加上第二個(gè)參數(shù),就能保證總是會(huì)返回一個(gè)值淹接。
下面是一個(gè)reduceRight
方法的例子秘狞。
function substract(prev, cur) {
return prev - cur;
}
[3, 2, 1].reduce(substract) // 0
[3, 2, 1].reduceRight(substract) // -4
上面代碼中,reduce
方法相當(dāng)于3減去2再減去1蹈集,reduceRight
方法相當(dāng)于1減去2再減去3烁试。
由于這兩個(gè)方法會(huì)遍歷數(shù)組,所以實(shí)際上還可以用來做一些遍歷相關(guān)的操作拢肆。比如减响,找出字符長(zhǎng)度最長(zhǎng)的數(shù)組成員。
function findLongest(entries) {
return entries.reduce(function (longest, entry) {
return entry.length > longest.length ? entry : longest;
}, '');
}
findLongest(['aaa', 'bb', 'c']) // "aaa"
上面代碼中郭怪,reduce
的參數(shù)函數(shù)會(huì)將字符長(zhǎng)度較長(zhǎng)的那個(gè)數(shù)組成員支示,作為累積值。這導(dǎo)致遍歷所有成員之后鄙才,累積值就是字符長(zhǎng)度最長(zhǎng)的那個(gè)成員颂鸿。
- indexOf(),lastIndexOf()
indexOf
方法返回給定元素在數(shù)組中第一次出現(xiàn)的位置攒庵,如果沒有出現(xiàn)則返回-1
嘴纺。
var a = ['a', 'b', 'c'];
a.indexOf('b') // 1
a.indexOf('y') // -1
indexOf
方法還可以接受第二個(gè)參數(shù),表示搜索的開始位置浓冒。
['a', 'b', 'c'].indexOf('a', 1) // -1
// 上面代碼從1號(hào)位置開始搜索字符a栽渴,結(jié)果為`-1`,表示沒有搜索到稳懒。
lastIndexOf
方法返回給定元素在數(shù)組中最后一次出現(xiàn)的位置闲擦,如果沒有出現(xiàn)則返回-1
。
var a = [2, 5, 9, 2];
a.lastIndexOf(2) // 3
a.lastIndexOf(7) // -1
注意场梆,這兩個(gè)方法不能用來搜索NaN
的位置墅冷,即它們無法確定數(shù)組成員是否包含NaN
。
[NaN].indexOf(NaN) // -1
[NaN].lastIndexOf(NaN) // -1
這是因?yàn)檫@兩個(gè)方法內(nèi)部或油,使用嚴(yán)格相等運(yùn)算符(===)進(jìn)行比較寞忿,而NaN
是唯一一個(gè)不等于自身的值。
- 鏈?zhǔn)绞褂?br> 上面這些數(shù)組方法之中装哆,有不少返回的還是數(shù)組罐脊,所以可以鏈?zhǔn)绞褂谩?/li>
var users = [
{name: 'tom', email: 'tom@example.com'},
{name: 'peter', email: 'peter@example.com'}
];
users
.map(function (user) {
return user.email;
})
.filter(function (email) {
return /^t/.test(email);
})
.forEach(console.log);
// "tom@example.com"
上面代碼中定嗓,先產(chǎn)生一個(gè)所有 Email 地址組成的數(shù)組蜕琴,然后再過濾出以t開頭的 Email 地址萍桌。
22. 數(shù)組的扁平化處理(降維)
- 數(shù)組的扁平化處理, 將多層的數(shù)組轉(zhuǎn)成一維數(shù)組,例如將
[1, [2], [[3]]] => [1,2,3]
1.使用Array.prototype.flat(depth)凌简。depth不能小于數(shù)組的深度
arr.flat(3)
2.遍歷
function flat1(arr) {
while(arr.some(item=>Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
3.遞歸實(shí)現(xiàn)
function flat2(arr1) {
return arr1.reduce((acc, val) => Array.isArray(val) ? acc.concat(flat2(val)) : acc.concat(val), []);
}
4.非遞歸
function stackFlatten(input) {
const stack = [...input];
const res = [];
while (stack.length) {
const next = stack.pop();
if (Array.isArray(next)) {
stack.push(...next);
} else {
res.push(next);
}
}
return res.reverse();
}
- 對(duì)象的扁平化上炎,只包含普通類型,數(shù)組和對(duì)象雏搂。
{
num: 1,
arr: [1, 2]
obj: {
name: 'name'
}
}
// 偏平化后=>
{
num: 1,
arr.[0]: 1,
arr.[1]: 2,
obj.name: 'name'
}
23. 深拷貝淺拷貝
首先:淺拷貝和深拷貝都只針對(duì)于Object藕施, Array這樣的復(fù)雜對(duì)象。
區(qū)別:淺拷貝只復(fù)制對(duì)象的第一層屬性凸郑、深拷貝可以對(duì)對(duì)象的屬性進(jìn)行遞歸復(fù)制
深拷貝:
1.通過利用JSON.parse(JSON.stringify(Object))
來達(dá)到深拷貝的目的
2.缺點(diǎn):是undefined
和function
還有symbol
類型是無法進(jìn)行深拷貝的
3.原理:增加一個(gè)指針裳食,重新申請(qǐng)一塊內(nèi)存空間,指針指向新的這塊內(nèi)存空間芙沥。
4.場(chǎng)景:當(dāng)我們需要復(fù)制原對(duì)象而又不能修改元對(duì)象的時(shí)候诲祸,深拷貝就是一個(gè),也是唯一的選擇而昨。淺拷貝:
1.通過ES6新特性Object.assign({}, target) 或 {...target}
來達(dá)到淺拷貝的目的
2.優(yōu)點(diǎn):可以解決JSON不能處理或是無法拷貝的問題
3.缺點(diǎn):只能深拷貝最頂上的一層救氯,不能拷貝原型鏈上的屬性
4.原理:增加一個(gè)指針,指向已存在的內(nèi)存空間歌憨,只拷貝了內(nèi)存地址着憨,子類的屬性被修改時(shí),父類的屬性也會(huì)隨之修改务嫡。
24. 防抖和節(jié)流
防抖和節(jié)流是針對(duì)響應(yīng)跟不上觸發(fā)頻率這類問題的兩種解決方案甲抖。
函數(shù)防抖: debounce
定義:多次觸發(fā)事件后,事件處理函數(shù)只執(zhí)行一次心铃,并且是在觸發(fā)操作結(jié)束時(shí)執(zhí)行惧眠。
function debounce(fn, delay) {
let timer;
return function(...rest) {
timer && clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, rest), delay)
}
}
- 函數(shù)節(jié)流: throttle
1.定義:觸發(fā)函數(shù)事件后,短時(shí)間間隔內(nèi)無法連續(xù)調(diào)用于个,只有上一次函數(shù)執(zhí)行后氛魁,過了規(guī)定的時(shí)間間隔,才能進(jìn)行下一次的函數(shù)調(diào)用厅篓。
2.通過時(shí)間戳或者定時(shí)器的方式實(shí)現(xiàn)節(jié)流秀存。
function throttle(fn, delay) {
let start
return function(...rest) {
let now = Date.now()
!start && (start = now)
if (now - start >= delay) {
fn.apply(this, rest)
start = now
}
}
}
function throttle2(fn, delay){
let timer
return function(...rest){
if(!timer){
timer = setTimeout(() => {
fn.apply(this, rest);
timer = null;
}, delay)
}
}
}
25. 原生ajax
創(chuàng)建xhr實(shí)例
open鏈接(請(qǐng)求方法,url, 同步異步)
設(shè)置請(qǐng)求參數(shù)
監(jiān)聽onreadystatechange事件
發(fā)送
var xhr=new XMLHttpRequest();
xhr.open('POST',url,false);
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xhr.onreadystatechange=function(){
// readyState == 4說明請(qǐng)求已完成
if(xhr.readyState==4){
if(xhr.status==200 || xhr.status==304){
console.log(xhr.responseText);
fn.call(xhr.responseText);
}
}
}
xhr.send();
26. 函數(shù)柯里化
- 柯里化(Currying)是把接受多個(gè)參數(shù)的函數(shù)變換成接受一個(gè)單一參數(shù)(最初函數(shù)的第一個(gè)參數(shù))的函數(shù)羽氮,并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)或链。
function curry(fn, ...args) {
return args.length < fn.length ? (...arguments) => curry(fn, ...args, ...arguments) : fn(...args)
}
- 特點(diǎn):
1.延遲參數(shù)傳遞,參數(shù)復(fù)用
2.代碼短小档押,優(yōu)雅澳盐,函數(shù)化祈纯,有點(diǎn)不好理解
27. map和forEach的區(qū)別
都支持三個(gè)參數(shù),參數(shù)分別為
item
(當(dāng)前每一項(xiàng))叼耙,index
(索引值)腕窥,arr
(原數(shù)組)forEach
允許callback
更改原始數(shù)組的元素,無返回值筛婉。map
則返回新的數(shù)組簇爆,表現(xiàn)上可認(rèn)為是淺拷貝后進(jìn)行操作。forEach爽撒,filter入蛆,every,some
會(huì)跳過空位硕勿,map
會(huì)跳過空位哨毁,但是會(huì)保留這個(gè)值。
28. Cookie和Session的區(qū)別
通俗講源武,Cookie是訪問某些網(wǎng)站以后在本地存儲(chǔ)的一些網(wǎng)站相關(guān)的信息扼褪,下次再訪問的時(shí)候減少一些步驟。另外一個(gè)更準(zhǔn)確的說法是:Cookies是服務(wù)器在本地機(jī)器上存儲(chǔ)的小段文本并隨每一個(gè)請(qǐng)求發(fā)送至同一個(gè)服務(wù)器软能,是一種在客戶端保持狀態(tài)的方案迎捺。
Session是存在服務(wù)器的一種用來存放用戶數(shù)據(jù)的類HashTable結(jié)構(gòu)。
區(qū)別
1.通過上面的簡(jiǎn)單敘述查排,很容易看出來最明顯的不同是一個(gè)在客戶端一個(gè)在服務(wù)端凳枝。因?yàn)镃ookie存在客戶端所以用戶可以看見,所以也可以編輯偽造跋核,不是十分安全岖瑰。
2.Session過多的時(shí)候會(huì)消耗服務(wù)器資源,所以大型網(wǎng)站會(huì)有專門的Session服務(wù)器砂代,而Cookie存在客戶端所以沒什么問題蹋订。
3.域的支持范圍不一樣,比方說a.com
的Cookie在a.com
下都能用刻伊,而www.a.com
的Session在api.a.com
下都不能用露戒,解決這個(gè)問題的辦法是JSONP或者跨域資源共享
29. 事件捕獲流,冒泡流和事件委托
事件流描述的是從頁面中接收事件的順序捶箱。
類型
1.事件冒泡流:事件的傳播是從最特定的事件目標(biāo)到最不特定的事件目標(biāo)智什。即從DOM樹的葉子到根。(IE)
2.事件捕獲流:事件的傳播是從最不特定的事件目標(biāo)到最特定的事件目標(biāo)丁屎。即從DOM樹的根到葉子荠锭。(網(wǎng)景公司)DOM標(biāo)準(zhǔn)規(guī)定事件流包括三個(gè)階段:事件捕獲階段、處于目標(biāo)階段和事件冒泡階段晨川。
事件捕獲階段:實(shí)際目標(biāo)(<text>)在捕獲階段不會(huì)接收事件证九。也就是在捕獲階段删豺,事件從window到document再到body就停止了。
處于目標(biāo)階段:事件在<text>上發(fā)生并處理愧怜。但是事件處理會(huì)被看成是冒泡階段的一部分呀页。
冒泡階段:事件又傳播回文檔。
事件委托又叫事件代理叫搁,是根據(jù)事件冒泡流赔桌,讓父元素代理響應(yīng)函數(shù)減少DOM的訪問
注意以下事件不支持冒泡:
1.blur
2.focus
3.load
4.unload
5.以及自定義的事件供炎。
6.原因是在于:這些事件僅發(fā)生于自身上渴逻,而它的任何父節(jié)點(diǎn)上的事件都不會(huì)產(chǎn)生,所有不會(huì)冒泡如何阻止事件捕獲或冒泡
//阻止冒泡
e.stopPropagation() || return false音诫。
window.e.cancelBubble=true; // IE
// 阻止捕獲
e.stopImmediatePropagation() // 阻止捕獲和其他事件
// 阻止默認(rèn)事件: 事件處理過程中惨奕,不阻擊事件冒泡,但阻擊默認(rèn)行為
e.preventDefault()
window.e.returnValue=false; || return false;// IE
30. 作用域竭钝、作用域鏈
- 某個(gè)變量有( 起 ) 作用的范圍
- 塊級(jí)作用域, 在別的語言里有塊級(jí)作用域, 但是在js中沒有塊級(jí)作用域
-
js
中的作用域
1.script
構(gòu)成了全局作用域
2.在js
中函數(shù)是唯一一個(gè)可以創(chuàng)建作用域的對(duì)象 - 詞法作用域 - 動(dòng)態(tài)作用域
1.詞法作用域:在變量聲明的時(shí)候梨撞,它的作用域就已經(jīng)確定了
2.動(dòng)態(tài)作用域:在程序運(yùn)行的時(shí)候,由程序的當(dāng)前上下文(執(zhí)行環(huán)境)決定的
3.js
屬于詞法作用域 - 詞法作用域的訪問規(guī)則:
先在當(dāng)前作用域中查找香罐,如果找到就直接使用,如果沒有找到卧波,那么就到上一級(jí)作用域中查找,如果還沒有找到那么就重復(fù)這個(gè)查找的過程庇茫,直到全局作用域 - 作用域鏈
1.js
中函數(shù)可以創(chuàng)建作用域
2.js
中的函數(shù)中可以聲明函數(shù)
3.函數(shù)內(nèi)部的函數(shù)中又可以聲明函數(shù)
4.以上,會(huì)形成一個(gè)鏈?zhǔn)降慕Y(jié)構(gòu),這個(gè)是作用域鏈
31. 數(shù)組去重
關(guān)鍵考你是用內(nèi)存空間換時(shí)間呢港粱,還是用時(shí)間來換內(nèi)存空間呢?
這句話是什么意思呢旦签?意思就是用數(shù)組去重的方法不是單一的查坪,有很多種去重的辦法,但是這些方法之中呢宁炫,有幾種方法是特別耗費(fèi)性能的偿曙,而且去重的時(shí)間計(jì)算,如果你對(duì)每種去重方法的時(shí)間做一個(gè)比較的話羔巢,就會(huì)發(fā)現(xiàn)有的特別快望忆,有的特別慢。特別快的方法竿秆,肯定是用內(nèi)存換來的時(shí)間上的提升启摄,而特別慢的方法呢,肯定是用時(shí)間換來的空間上的提升袍辞。你這么去權(quán)衡這個(gè)利弊呢鞋仍?
<script>
// 這里要把方法添加到原型上,這樣實(shí)例對(duì)象可以共享
// 因?yàn)閷?shí)例對(duì)象不能調(diào)靜態(tài)方法搅吁,構(gòu)造函數(shù)才可以調(diào)靜態(tài)方法
// indexOf() 方法可返回某個(gè)指定的字符串值在字符串中首次出現(xiàn)的位置威创。如果有則返回對(duì)象元素的索引落午,如果沒有則返回-1
var arr = [1, 2, 2, 3, 4, 5, 3, 4, 5];
// 方法1
Array.prototype.only1 = function () {
var customArr = [];
for (var i = 0; i < this.length; i++) {
// 如果當(dāng)前數(shù)組的第i已經(jīng)保存進(jìn)了臨時(shí)數(shù)組,那么跳過肚豺,
// 否則把當(dāng)前項(xiàng)push到臨時(shí)數(shù)組里面
if (customArr.indexOf(this[i]) == -1) customArr.push(this[i]);
}
return customArr;
}
console.log(arr.only1());
// 方法2 把數(shù)組里的元素作為對(duì)象的key來判斷 也稱為哈希表
Array.prototype.only2 = function () {
// hash表 自定義一個(gè)空數(shù)組
var obj = {}, customArr = [];
// 遍歷數(shù)組
for (var i = 0; i < this.length; i++) {
if (!obj[this[i]]) {
obj[this[i]] = true; // 存入hash表中
customArr.push(this[i]); // 把當(dāng)前數(shù)組的當(dāng)前項(xiàng)添加到自定義數(shù)組里面
}
}
return customArr;
}
console.log(arr.only2());
// 方法3
Array.prototype.only3 = function () {
var custom = [this[0]]; // 結(jié)果數(shù)組
for (var i = 1; i < this.length; i++) { // 從第二項(xiàng)開始遍歷
// 如果當(dāng)前數(shù)組的第i項(xiàng)在當(dāng)前數(shù)組中第一次出現(xiàn)的位置不是i溃斋,
// 那么表示第i項(xiàng)是重復(fù)的,忽略掉吸申。否則存入結(jié)果數(shù)組
if (this.indexOf(this[i]) == i) custom.push(this[i]);
}
return custom;
}
console.log(arr.only3());
</script>
其中第1種和第3種方法都用到了數(shù)組的indexOf方法梗劫。此方法的目的是尋找存入?yún)?shù)在數(shù)組中第一次出現(xiàn)的位置。很顯然截碴,js引擎在實(shí)現(xiàn)這個(gè)方法的時(shí)候會(huì)遍歷數(shù)組直到找到目標(biāo)為止梳侨。所以此函數(shù)會(huì)浪費(fèi)掉很多時(shí)間。
而第2中方法用的是hash表日丹。把已經(jīng)出現(xiàn)過的通過下標(biāo)的形式存入一個(gè)object內(nèi)走哺。下標(biāo)的引用要比用indexOf搜索數(shù)組快的多。
為了判斷這三種方法的效率如何哲虾,我做了一個(gè)測(cè)試程序丙躏,生成一個(gè)10000長(zhǎng)度的隨機(jī)數(shù)組來去重測(cè)試執(zhí)行時(shí)間。 結(jié)果表明第二種方法遠(yuǎn)遠(yuǎn)快于其他兩種方法束凑。 但是內(nèi)存占用方面應(yīng)該第二種方法比較多晒旅,因?yàn)槎嗔艘粋€(gè)hash表。**這就是所謂的空間換時(shí)間汪诉。 **
- 第四種方法
Array.prototype.only4= function()
{
this.sort();
var re=[this[0]];
for(var i = 1; i < this.length; i++)
{
if( this[i] !== re[re.length-1])
{
re.push(this[i]);
}
}
return re;
}
console.log(arr.only3());
這個(gè)方法的思路是先把數(shù)組排序废恋,然后比較相鄰的兩個(gè)值。 排序的時(shí)候用的JS原生的sort方法摩瞎,JS引擎內(nèi)部應(yīng)該是用的快速排序吧拴签。 最終測(cè)試的結(jié)果是此方法運(yùn)行時(shí)間平均是第二種方法的三倍左右,不過比第一種和第三種方法快了不少旗们。
然而當(dāng)你提到排序的時(shí)候蚓哩,面試官就會(huì)繼續(xù)追問你,除了sort()
上渴,你還會(huì)那種排序岸梨。顯然這是一個(gè)無底洞,當(dāng)你回答上來桶排序稠氮,冒泡排序曹阔,希爾排序的時(shí)候,他會(huì)繼續(xù)問你隔披,你對(duì)那種比較熟悉赃份,現(xiàn)在能寫的出來么?
下面是十大排序
冒泡排序最為穩(wěn)定 但是幾乎沒有什么卵用
<script>
function bubbleSort(arr) {
var len = arr.length;
for (var i = 0; i < len; i++) {
for (var j = 0; j < len - 1; j++) {
if (arr[j] > arr[j + 1]) { // 相鄰兩元素進(jìn)行比較
var temp = arr[j + 1]; // 元素交換
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
var sortArr = [1, 23, 43, 5, 42, 4, 32, 53, 64, 6];
console.log(bubbleSort(sortArr));;
</script>