2020前端面試 - JavaScript1.0篇

前言:

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.Functionprototype__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指向傳入的this

  • with: 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í)行完thencatch指定的回調(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è)被rejectedp的狀態(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

  • classlet辆它、const一樣:不存在變量提升誊薄、不能重復(fù)聲明
  • ES6 的class可以看作只是一個(gè)語法糖,它的絕大部分功能
  • ES5 都可以做到锰茉,新的class寫法只是讓對(duì)象原型的寫法更加清晰呢蔫、更像面向?qū)ο缶幊痰恼Z法而已。
  • Module
    1.ES6 的模塊自動(dòng)采用嚴(yán)格模式飒筑,不管你有沒有在模塊頭部加上"use strict";咐刨。
    2.importexport命令以及exportexport 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ò)

  • 通過 exportsmodule.exports 來暴露模塊中的內(nèi)容

  • 那么 exportsmodule.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ì)跳過undefinednull,但是會(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ù)組outforEach方法的第二個(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ì)跳過undefinednull广料,但會(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

someevery方法還可以接受第二個(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):是undefinedfunction還有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)在能寫的出來么?

下面是十大排序

**十大經(jīng)典排序**

冒泡排序最為穩(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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市抓韩,隨后出現(xiàn)的幾起案子纠永,更是在濱河造成了極大的恐慌,老刑警劉巖谒拴,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尝江,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡英上,警方通過查閱死者的電腦和手機(jī)炭序,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苍日,“玉大人惭聂,你說我怎么就攤上這事∫浊玻” “怎么了彼妻?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵嫌佑,是天一觀的道長(zhǎng)豆茫。 經(jīng)常有香客問我,道長(zhǎng)屋摇,這世上最難降的妖魔是什么揩魂? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮炮温,結(jié)果婚禮上火脉,老公的妹妹穿的比我還像新娘。我一直安慰自己柒啤,他們只是感情好倦挂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著担巩,像睡著了一般方援。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涛癌,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天犯戏,我揣著相機(jī)與錄音,去河邊找鬼拳话。 笑死先匪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的弃衍。 我是一名探鬼主播呀非,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼镜盯!你這毒婦竟也來了岸裙?” 一聲冷哼從身側(cè)響起坦冠,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哥桥,沒想到半個(gè)月后辙浑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拟糕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年判呕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片送滞。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侠草,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出犁嗅,到底是詐尸還是另有隱情边涕,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布褂微,位于F島的核電站功蜓,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宠蚂。R本人自食惡果不足惜式撼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望求厕。 院中可真熱鬧著隆,春花似錦、人聲如沸呀癣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽项栏。三九已至浦辨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間忘嫉,已是汗流浹背荤牍。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留庆冕,地道東北人康吵。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像访递,于是被迫代替她去往敵國(guó)和親晦嵌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355