JS面試題(二)

語言: CN / TW / HK
ead>

theme: qklhk-chocolate

非同步

MDN:非同步指兩個或兩個以上的物件或事件不同時存在或發生(或多個相關事物的發生無需等待其前一事物的完成)。

如何理解非同步和同步

同步:同步是指一個程序在執行某個請求的時候,如果該請求需要一段時間才能返回資訊,那麼這個程序會一直等待下去,直到收到返回資訊才繼續執行下去。

非同步:非同步是指程序不需要一直等待下去,而是繼續執行下面的操作,不管其他程序的狀態,當有資訊返回的時候會通知程序進行處理,這樣就可以提高執行的效率了,即非同步是我們發出的一個請求,該請求會在後臺自動發出並獲取資料,然後對資料進行處理,在此過程中,我們可以繼續做其他操作,不管它怎麼發出請求,不關心它怎麼處理資料。

js是單執行緒語言,但是有時候需要等到某個時機才會執行某些操作,也就是非同步操作。比如:

js console.log('one'); setTimeout(() => { console.log('two'); setTimeout(() => { console.log('three') }, 2000); }, 2000); console.log('six') // one // six // two // three

如果不斷需要延時回撥,就會出現一列回撥小火箭,我們稱之為回撥地獄。雖然功能上不存在問題,但是難以維護。

promise

prmoise是一個物件,專門用於處理非同步操作,該物件有三個狀態:進行中(Pending)、已完成(Resolved)、已失敗(Rejected),當前狀態僅由非同步操作的結果決定,不受任何其他操作控制,且一旦執行就無法取消。

Promise 有兩個特點: - 物件狀態不受外界影響; - 一旦狀態改變了就不會再變。

也就是說,Promise任何時候都只有一種狀態。

```js function timeout(data){ return new Promise((resolve,reject) => { setTimeout(function() { if(data){ resolve(data) }else{ reject(data) } }, 2000); }) }

timeout("hello").then(function(){ console.log("resolve"); },function(){ console.log("reject") }) // resolve

timeout().then(function(){ console.log("resolve"); },function(){ console.log("reject") }) // reject ```

Promise基本用法

可以通過Promise建構函式建立Promise物件,Promise建構函式接受兩個引數:resolve和reject,由JS引擎提供。當Promise物件轉移到成功的時候,呼叫resolve函式並將操作結果作為引數傳遞出去,當Promise物件狀態變成失敗時,reject函式將報出的錯誤作為引數傳遞出去。

只有Promise物件中呼叫resolve和reject的時候,才會執行then裡邊的內容。

js function greet() { var promise = new Promise(function (resolve, reject) { var greet = "hello world"; resolve(greet); }); return promise; } greet().then((v) => { console.log('resolve',v); },(s)=>{ console.log('reject',s) });

注意:建立一個Promise物件會立即執行裡邊的程式碼,所以為了更好控制程式碼執行的時刻,可以將其包含在一個函式中,並將這個promise返回。

Promise的then方法有三個引數:成功回撥,失敗回撥,前進回撥。一般情況下只實現第一個,後邊的是可選的。

catch用法

```js function greet() { var promise = new Promise(function (resolve, reject) { var greet = "hello world"; reject(greet); }); return promise; }

greet() .then((v) => { console.log("resolve", v); }) .catch(function () { console.log("catch"); }); ```

這個時候catch執行的是和reject一樣的,也就是說如果Promise的狀態變為reject時,會被catch捕捉到,不過需要特別注意的是如果前面設定了reject方法的回撥函式,則catch不會捕捉到狀態變為reject的情況。catch還有一點不同的是,如果在resolve或者reject發生錯誤的時候,會被catch捕捉到,這與java,c++的錯誤處理時一樣的,這樣就能避免程式卡死在回撥函式中了。

promise的api

  • Promise.all()中的Promise序列會全部執行通過才認為是成功,否則認為是失敗;
  • Promise.race()中的Promise序列中第一個執行完畢的是通過,則認為成功,如果第一個執行完畢的Promise是拒絕,則認為失敗;
  • Promise.any()中的Promise序列只要有一個執行通過,則認為成功,如果全部拒絕,則認為失敗;
  • Promise.allSettled()只有等到引數陣列的所有 Promise 物件都發生狀態變更(不管是fulfilled還是rejected),返回的 Promise 物件才會發生狀態變更。

實現符合 Promise/A+ 規範的 Promise

參考

Promise/A+規範

Promise/A+規範詳情看這裡

  1. promise 有三個狀態:pending,fulfilled,or rejected;「規範 Promise/A+ 2.1」
  2. new promise時, 需要傳遞一個executor()執行器,執行器立即執行;
  3. executor接受兩個引數,分別是resolve和reject;
  4. promise 的預設狀態是 pending;
  5. promise 有一個value儲存成功狀態的值,可以是undefined/thenable/promise;「規範 Promise/A+ 1.3」
  6. promise 有一個reason儲存失敗狀態的值;「規範 Promise/A+ 1.5」
  7. promise 只能從pending到rejected, 或者從pending到fulfilled,狀態一旦確認,就不會再改變;
  8. promise 必須有一個then方法,then 接收兩個引數,分別是 promise 成功的回撥 onFulfilled, 和 promise 失敗的回撥 onRejected;「規範 Promise/A+ 2.2」
  9. 如果呼叫 then 時,promise 已經成功,則執行onFulfilled,引數是promise的value;
  10. 如果呼叫 then 時,promise 已經失敗,那麼執行onRejected, 引數是promise的reason;
  11. 如果 then 中丟擲了異常,那麼就會把這個異常作為引數,傳遞給下一個 then 的失敗的回撥onRejected;

基本的Promise

針對以上規範,我們大體上可以總結出如下promise實現的方案:

```js const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected';

class Promise { // 4.傳入一個executor執行器 constructor(executor) { // 1.存放狀態 this.status = 'pending'; // 2.存放成功狀態的值 this.value = undefined; // 3.存放失敗狀態的值 this.reason = undefined;

    // 6.為executor提供resolve函式
    let resolve = (value) => {
        // 狀態為 PENDING 時才可以更新狀態,防止 executor 中呼叫了兩次 resovle/reject 方法
        if (this.status === PENDING) {
            this.status = FULFILLED;
            this.value = value
        }
    }

    // 7.為executor提供reject函式
    let reject = (reason) => {
        // 狀態為 PENDING 時才可以更新狀態,防止 executor 中呼叫了兩次 resovle/reject 方法
        if (this.status === PENDING) {
            this.status === REJECTED;
            this.reason = reason
        }
    }

    try {
        // 5.立即執行executor,將resolve和reject傳遞給使用者
        executor(resolve, reject)
    } catch (error) {
        // 發生異常時執行失敗邏輯
        reject(error)
    }
}
// 8.promise必須有then方法,呼叫成功和失敗時對應的方法
then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
        onFulfilled(this.value)
    }
    if (this.status === REJECTED) {
        onRejected(this.reason)
    }
}

} ```

簡單測試一下: js // test const promise = new Promise((resolve, reject) => { resolve('成功'); }).then( (data) => { console.log('success', data) }, (err) => { console.log('faild', err) } ) // 控制檯輸出--->success 成功

如果executor是非同步操作呢: ```js const promise = new Promise((resolve, reject) => { // 傳入一個非同步操作 setTimeout(() => { resolve('成功'); }, 1000); }).then( (data) => {e console.log('success', data) }, (err) => { console.log('faild', err) } )

``` 控制檯什麼都沒有列印。原因是當呼叫then方法時,promise還處於pending狀態,所以並沒有執行onFulfilled或者onRejected。

所以,我們應當在執行then函式時,將成功或者失敗的回撥函式儲存起來,在executor()的非同步任務被執行時,觸發 resolve 或 reject,依次呼叫成功或失敗的回撥。

滿足非同步的Promise

```js const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected';

class Promise { // 4.傳入一個executor執行器 constructor(executor) { // 1.存放狀態 this.status = 'pending'; // 2.存放成功狀態的值 this.value = undefined; // 3.存放失敗狀態的值 this.reason = undefined;

    this.onResolvedCallbacks = []
    this.onRejectedCallbacks = []

    // 6.為executor提供resolve函式
    let resolve = (value) => {
        // 狀態為 PENDING 時才可以更新狀態,防止 executor 中呼叫了兩次 resovle/reject 方法
        if (this.status === PENDING) {
            this.status = FULFILLED;
            this.value = value;
            this.onResolvedCallbacks.forEach(fn => fn())
        }
    }

    // 7.為executor提供reject函式
    let reject = (reason) => {
        // 狀態為 PENDING 時才可以更新狀態,防止 executor 中呼叫了兩次 resovle/reject 方法
        if (this.status === PENDING) {
            this.status === REJECTED;
            this.reason = reason;
            this.onRejectedCallbacks.forEach(fn => fn())
        }
    }

    try {
        // 5.立即執行executor,將resolve和reject傳遞給使用者
        executor(resolve, reject)
    } catch (error) {
        // 發生異常時執行失敗邏輯
        reject(error)
    }
}
// 8.promise必須有then方法,呼叫成功和失敗時對應的方法
then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
        onFulfilled(this.value)
    }
    if (this.status === REJECTED) {
        onRejected(this.reason)
    }
    if (this.status === PENDING) {
        // 如果promise的狀態是 pending,需要將 onFulfilled 和 onRejected 函式存放起來,等待狀態確定後,再依次將對應的函式執行
        this.onResolvedCallbacks.push(() => {
            onFulfilled(this.value)
        });

        // 如果promise的狀態是 pending,需要將 onFulfilled 和 onRejected 函式存放起來,等待狀態確定後,再依次將對應的函式執行
        this.onRejectedCallbacks.push(() => {
            onRejected(this.reason);
        })
    }
}

}

```

測試一下: ```js

// test

const promise = new Promise((resolve, reject) => { // 傳入一個非同步操作 setTimeout(() => { resolve('成功'); }, 3000); }).then( (data) => { console.log('success', data) }, (err) => { console.log('faild', err) } )

``` 三秒之後控制檯列印內容:success 成功

then 的鏈式呼叫&值穿透特性

在我們使用 Promise 的時候,當 then 函式中 return 了一個值,不管是什麼值,我們都能在下一個 then 中獲取到,這就是所謂的then 的鏈式呼叫。而且,當我們不在 then 中放入引數,例:promise.then().then(),那麼其後面的 then 依舊可以得到之前 then 返回的值,這就是所謂的值的穿透

for example: 鏈式呼叫: js const promise = new Promise((resolve, reject) => { resolve('promise resolve') }).then( (data) => { console.log('success', data) return '第一個then resolve' }, (err) => { console.log('faild', err) } ).then(res=>{ console.log(res) }) // success promise resolve // 第一個then resolve

穿透性: js const promise = new Promise((resolve, reject) => { // 傳入一個非同步操作 resolve('promise resolve') }).then() .then(res => { console.log(res) }) // promise resolve

那具體如何實現鏈式呼叫和穿透性呢?簡單思考一下,如果每次呼叫 then 的時候,我們都重新建立一個 promise 物件,並把上一個 then 的返回結果傳給這個新的 promise 的 then 方法,不就可以一直 then 下去了麼?

結合 Promise/A+ 規範梳理一下思路:

  1. then 的引數 onFulfilled 和 onRejected 可以預設,如果 onFulfilled 或者 onRejected不是函式,將其忽略,且依舊可以在下面的 then 中獲取到之前返回的值;「規範 Promise/A+ 2.2.1、2.2.1.1、2.2.1.2」
  2. promise 可以 then 多次,每次執行完 promise.then 方法後返回的都是一個“新的promise";「規範 Promise/A+ 2.2.7」
  3. 如果 then 的返回值 x 是一個普通值,那麼就會把這個結果作為引數,傳遞給下一個 then 的成功的回撥中;
  4. 如果 then 中丟擲了異常,那麼就會把這個異常作為引數,傳遞給下一個 then 的失敗的回撥中;「規範 Promise/A+ 2.2.7.2」
  5. 如果 then 的返回值 x 是一個 promise,那麼會等這個 promise 執行完,promise 如果成功,就走下一個 then 的成功;如果失敗,就走下一個 then 的失敗;如果丟擲異常,就走下一個 then 的失敗;「規範 Promise/A+ 2.2.7.3、2.2.7.4」
  6. 如果 then 的返回值 x 和 promise 是同一個引用物件,造成迴圈引用,則丟擲異常,把異常傳遞給下一個 then 的失敗的回撥中;「規範 Promise/A+ 2.3.1」
  7. 如果 then 的返回值 x 是一個 promise,且 x 同時呼叫 resolve 函式和 reject 函式,則第一次呼叫優先,其他所有呼叫被忽略;「規範 Promise/A+ 2.3.3.3.3」

async

promise物件的鏈式串聯方式雖然解決了回撥函式的層層巢狀問題,但是then操作過多過長時還是會出現冗餘臃腫的情況,此時可以使用async函式。

```js function timeout(data){ //async 和 promise 結合使用 return new Promise((resolve,reject) => { setTimeout(function(){ if(data){ resolve(data); }else{ reject(data); } }, 2000); }) }

async function run(){ console.log('aget run start'); await timeout('agent'); console.log('agent run end'); }

console.log('no_await');

run();

// no_await // aget run start // agent run end ```

async 函式是 Generator 函式的語法糖。使用 關鍵字 async 來表示,在函式內部使用 await來表示非同步,await關鍵字只能用在async定義的函式內。async函式就是將 Generator 函式的星號(*)替換成async,將yield替換成await。比Generator更語義化。

相對於promise的優勢

  • 語法簡潔,同步程式碼執行非同步操作。
  • 錯誤處理

promise中,try/catch不能處理promise內部的錯誤,因為promise物件丟擲的錯誤不會傳遞到外層。所以捕獲異常需要使用.catch,這樣錯誤處理程式碼非常冗餘。並且,在我們的實際生產程式碼會更加複雜。

```js //promise捕獲錯誤 const makeRequest = () => { try { getJSON() .then(result => { // JSON.parse可能會出錯 const data = JSON.parse(result) console.log(data) }) // 處理非同步程式碼的錯誤 .catch((err) => { console.log(err) }) } catch (err) { //此處catch無效 console.log(err) } }

//async捕獲錯誤 const makeRequest = async () => { try { // this parse may fail const data = JSON.parse(await getJSON()) console.log(data) } catch (err) { console.log(err) } } ```

這裡有一點需要注意,當 async 函式中只要一個 await 出現 reject 狀態,則後面的 await 都不會被執行。所以要把第一個await放在try…catch結構裡面,這樣不管這個非同步操作是否成功,第二個await都會執行。如果有多個await命令,可以統一放在try…catch結構中

js async function main() { try { const val1 = await fetchDataA(); const val2 = await fetchDataB(); const val3 = await fetchDataC(); } catch (err) { console.error(err); } }

async/await 的執行順序

async返回什麼

先看看看以下程式碼的輸出值:

```js async function asyncReturn() { return 'async返回的是什麼?' }

var result = asyncReturn(); console.log(result); //Promise { 'async返回的是什麼?' } ```

從結果中可以看到async函式返回的是一個promise物件,如果在函式中 return 一個直接量,async 會把這個直接量通過 Promise.resolve()封裝成 Promise 物件。

如果async函式沒有返回值:

js async function testAsync1() { console.log("hello async"); } let result1 = testAsync1(); console.log(result1); //Promise {<fulfilled>: undefined}

await做了什麼處理

從字面意思上看await就是等待,await 等待的是一個表示式,這個表示式的返回值可以是一個promise物件也可以是其他值。

很多人以為await會一直等待之後的表示式執行完之後才會繼續執行後面的程式碼,實際上await是一個讓出執行緒的標誌。await後面的函式會先執行一遍,然後就會跳出整個async函式來執行後面js棧的程式碼。等本輪事件迴圈執行完了之後又會跳回到async函式中等待await後面表示式的返回值,如果返回值為非promise則繼續執行async函式後面的程式碼,否則將返回的promise放入promise佇列(Promise的Job Queue)。

```js function testSometing() { console.log("2-執行testSometing"); //3--> 列印2-執行testSometing return "5-testSometing"; //4--> 返回值,await讓出執行緒,向下執行 }

async function testAsync() { console.log("6-執行testAsync"); //11-->列印6-執行testAsync return Promise.resolve("8-hello async"); //12-->返回值,await讓出執行緒,執行別的程式碼(promise) }

async function test() { //整體從呼叫test開始 console.log("1-test start..."); //1-->列印1-test start... const v1 = await testSometing(); //2-->執行到這去執行testSometing() console.log(v1); //9-->列印v1:5-testSometing const v2 = await testAsync(); //10-->執行testAsync() console.log(v2); //14-->列印8-hello async console.log(v1, v2); //15-->列印5-testSometing,8-hello async,結束 }

test();

var promise = new Promise((resolve)=> { //5-->執行promise console.log("3-promise start.."); //6--> 列印3-promise start.. resolve("7-promise");});//關鍵點2 //7-->將promise放入promise的佇列 promise.then((val)=> console.log(val)); //13-->列印7-promise,返回test()

console.log("4-test end...") //8-->列印4-test end... 本輪事件迴圈執行結束,跳回到async函式test()中。 ```

加上seTimeout看看結果如何: ```js async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); await async3() await async4() }

async function async3() { console.log("async3") }

async function async4() { console.log('async4') }

async function async2() { console.log('async2'); }

console.log("script start");

setTimeout(function () { console.log("settimeout"); }, 0);

async1();

new Promise(function (resolve) { console.log("promise1"); resolve(); }).then(function () { console.log("promise2"); });

console.log('script end');
/ script start async1 start async2 promise1 script end async1 end async3 promise2 async4 settimeout/ ```

再看一個例子: ```js var a = 0 var b = async () => { a = a + await 10 console.log('2', a) // -> '2' 10 a = (await 10) + a console.log('3', a) // -> '3' 20 } b() a++ console.log('1', a) // -> '1' 1

輸出結果: 1 1 2 10 3 20 ```

解析: - 首先函式 b 先執行,在執行到 await 10 之前變數 a 還是 0,因為在 await 內部實現了 generators ,generators 會保留堆疊中東西,所以這時候 a = 0 被儲存了下來 - 因為 await 是非同步操作,遇到await就會立即返回一個pending狀態的Promise物件,暫時返回執行程式碼的控制權,使得函式外的程式碼得以繼續執行,所以會先執行 console.log('1', a) - 這時候同步程式碼執行完畢,開始執行非同步程式碼,將儲存下來的值拿出來使用,這時候 a = 10 - 然後後面就是常規執行程式碼了

generator

Generator 函式是 ES6 提供的一種非同步程式設計解決方案,語法行為與傳統函式完全不同

特徵: - function 命令與函式名之間有一個星號 - 函式體內部使用 yield 語句定義不同的內部狀態

```js function *f(){ yield "hello"; yield "world"; return "!" }

let fg=f();

// 呼叫遍歷器物件的 next 方法,使得指標移向下一個狀態,直到遇到下一條 yield 語句(或 return 語句)為止 // done 為 true 時遍歷結束

console.log(fg.next()); console.log(fg.next()); console.log(fg.next()); // { value: 'hello', done: false } // { value: 'world', done: false } // { value: '!', done: true } ```

模組化

我們都知道在早期JavaScript模組這一概念,都是通過script標籤引入js檔案程式碼。當然這寫基本簡單需求沒有什麼問題,但當我們的專案越來越龐大時,我們引入的js檔案就會越多,這時就會出現以下問題:

  • js檔案作用域都是頂層,這會造成變數汙染
  • js檔案多,變得不好維護
  • js檔案依賴問題,稍微不注意順序引入錯,程式碼全報錯

為了解決以上問題JavaScript社群出現了CommonJs,CommonJs是一種模組化的規範,包括現在的NodeJs裡面也採用了部分CommonJs語法在裡面。那麼在後來Es6版本正式加入了Es Module模組,這兩種都是解決上面問題,那麼都是解決什麼問題呢。

  • 解決變數汙染問題,每個檔案都是獨立的作用域,所以不存在變數汙染
  • 解決程式碼維護問題,一個檔案裡程式碼非常清晰
  • 解決檔案依賴問題,一個檔案裡可以清楚的看到依賴了那些其它檔案

CommonJS

CommonJS的一個模組就是一個指令碼檔案,通過執行該檔案來載入模組。CommonJS規範規定,每個模組內部,module變數代表當前模組。這個變數是一個物件,他的exports屬性(即module.exports)是對外介面。載入某個模組,其實就是載入該模組的module.exports屬性。

我們見過這樣模組引用:

js var myModule = require('module'); myModule.sayHello();

這是因為我們把模組的方法定義在了模組的屬性上:

js //module.js module.exports.sayHello = function(){ console.log('Hello'); }

還可以換另外一種形式:

js module.exports = sayHello; //呼叫 var sayHello = require('module'); sayHello();

require命令第一次載入該指令碼時就會執行整個指令碼,然後在記憶體中生成一個物件(模組可以多次載入,但是在第一次載入時才會執行,結果被快取),這個結果長這樣: ```js

{ id:'...', exports:{...}, loaded:true, ... } 常用形式:js //myModel.js var name = 'Byron';

function printName(){
console.log(name);
}

function printFullName(firstName){
console.log(firstName + name);
}

module.exports = {
printName: printName,
printFullName: printFullName }

//引用 var nameModule = require('./myModel.js'); nameModule.printName(); ```

Node.js的模組機制實現就是參照CommonJS的標準。但是Node.js額外做了一件事,即為每個模組提供了一個exports變數,以指向module.exports,這相當於在每個模組最開始,寫這麼一行程式碼:

js var exports = module.exports;

CommonJS的特點:

  • 所有程式碼都執行在模組作用域,不會汙染全域性作用域。
  • 獨立性是模組的重要特點,模組內部最好不與程式的其他部分直接互動。
  • 模組可以多次載入,但是隻會在第一次載入時執行一次,然後執行結果就被快取了,以後再載入,就直接讀取快取結果。要想讓模組再次執行,必須清除快取。
  • 模組載入的順序,按照其在程式碼中出現的順序。

ES Modules

ES Modules 的模組化能力由 export 和 import 組成,export 命令用於規定模組的對外介面,import 命令用於輸入其他模組提供的功能。我們可以這樣定義一個模組:

```js // 第一種方式 export var firstName = 'Michael'; export var lastName = 'Jackson'; export var year = 1958;

// 第二種方式 var firstName = 'Michael'; var lastName = 'Jackson'; var year = 1958;

export { firstName, lastName, year }; ```

然後再這樣引入他們:

js import { firstName, lastName, year } from 'module'; import { firstName as newName } from 'module'; import * as moduleA from 'module';

我們常用的方式: ```js // file a.js export function a() {} export function b() {} // file b.js export default function() {}

import {a, b} from './a.js' import XXX from './b.js' ```

CommonJs和Es Module的區別

CommonJs

  • CommonJs可以動態載入語句,程式碼發生在執行時
  • CommonJs混合匯出,還是一種語法,只不過不用宣告前面物件而已,當我匯出引用物件時之前的匯出就被覆蓋了
  • CommonJs匯出值是拷貝,可以修改匯出的值,這在程式碼出錯時,不好排查引起變數汙染

Es Module

  • Es Module是靜態的,不可以動態載入語句,只能宣告在該檔案的最頂部,程式碼發生在編譯時
  • Es Module混合匯出,單個匯出,預設匯出,完全互不影響
  • Es Module匯出是引用值之前都存在對映關係,並且值都是可讀的,不能修改

參考連結

防抖和節流

防抖和節流的作用都是防止函式多次呼叫。區別在於,假設一個使用者一直觸發這個函式,且每次觸發函式的間隔小於wait,防抖的情況下只會呼叫一次,而節流的情況會每隔一定時間(引數wait)呼叫函式。

防抖技術即是可以把多個順序的呼叫合併成一次,也就是在一定時間內,控制事件被觸發的次數。比如,500ms內沒有連續觸發兩次scroll事件時才會觸發函式,即一直滾動過程中不會一直觸發,只有停頓500ms時才可以觸發。

但是防抖函式也存在問題。比如我們希望下滑過程中圖片被不斷加載出來,而不是停下時才被載入。又或者下滑時候資料的ajax請求也是同理。這類場景我們就會用到另一種技巧,節流函式。

節流函式允許一個函式在X秒內執行一次。與防抖相比多了一個mustRun屬性,代表mustRun毫秒內,必然會觸發一次函式,大概功能就是如果在一段時間內 scroll 觸發的間隔一直短於 500ms ,那麼能保證事件我們希望呼叫的 handler 至少在 1000ms 內會觸發一次。

防抖

```js

Debouncing 防抖

div1
div2

```

防抖技術即是可以把多個順序的呼叫合併成一次,也就是在一定時間內,控制事件被觸發的次數。上邊例子中,500ms內沒有連續觸發兩次scroll事件時才會觸發函式,即一直滾動過程中不會一直觸發,只有停頓500ms時才可以觸發。

但是防抖函式也存在問題。比如我們希望下滑過程中圖片被不斷加載出來,而不是停下時才被載入。又或者下滑時候資料的ajax請求也是同理。這類場景我們就會用到另一種技巧,節流函式。

節流

節流函式允許一個函式在X秒內執行一次。與防抖相比多了一個mustRun屬性,代表mustRun毫秒內,必然會觸發一次函式,看簡單例項:

```js

throttling 節流

div1
div2

``` 大概功能就是如果在一段時間內 scroll 觸發的間隔一直短於 500ms ,那麼能保證事件我們希望呼叫的 handler 至少在 1000ms 內會觸發一次。

map方法

map 方法會給原陣列中的每個元素都按順序呼叫一次callback 函式。callback 每次執行後的返回值(包括 undefined)組合起來形成一個新陣列。 callback 函式只會在有值的索引上被呼叫;那些從來沒被賦過值或者使用 delete 刪除的索引則不會被呼叫。

```js var arr = [1,2,3]; var new_arr = arr.map(e => e*2); console.log(new_arr); //[ 2, 4, 6 ]

var another_arr =arr.forEach(e => e*3) console.log(another_arr) //undefined console.log(arr)//[ 1, 2, 3 ] ``` map生成一個新陣列,當你不打算使用返回的新陣列卻使用map是違背設計初衷的,請用forEach或者for-of替代。

不該使用map: - 你不打算使用返回的新陣列 - 你沒有從回撥函式中返回值。

```js callback 函式會被自動傳入三個引數:陣列元素,元素索引,原陣列本身。

var arr = [1,2,3]; var map_canshu = arr.map((a,b,c) => { console.log('a-->',a); console.log('b-->',b); console.log('c-->',c); })

/ a--> 1 b--> 0 c--> [ 1, 2, 3 ] a--> 2 b--> 1 c--> [ 1, 2, 3 ] a--> 3 b--> 2 c--> [ 1, 2, 3 ] / ```

flatMap方法

FlatMap 和 map 的作用幾乎是相同的,但是對於多維陣列來說,會將原陣列降維。

```js var arr1 = [1, 2, 3, 4];

arr1.map(x => [x * 2]); // [[2], [4], [6], [8]]

arr1.flatMap(x => [x * 2]); // [2, 4, 6, 8] ``` 目前該函式在瀏覽器中還不支援。

reduce方法

reduce()方法對陣列中的每個元素執行一個由您提供的reducer函式(升序執行),將其結果彙總為單個返回值。

```js var redArr = [1,2,3,4]; var reducer = (accumulator, currentValue) => accumulator + currentValue;

console.log(redArr.reduce(reducer)); //10

``` reducer 函式接收4個引數: - Accumulator (acc) (累計器) - Current Value (cur) (當前值) - Current Index (idx) (當前索引) - Source Array (src) (源陣列)

reducer函式的返回值分配給累計器,該返回值在陣列的每個迭代中被記住,並最後成為最終的單個結果值

proxy

什麼是Proxy

Proxy 也就是代理,可以幫助我們完成很多事情,例如對資料的處理,對建構函式的處理,對資料的驗證,說白了,就是在我們訪問物件前添加了一層攔截,可以過濾很多操作,而這些過濾,由你來定義。

語法

js let p = new Proxy(target , handler) - target :需要使用Proxy包裝的目標物件(可以是任何型別的物件,包括原生陣列,函式,甚至另一個代理)

  • handler: 一個物件,其屬性是當執行一個操作時定義代理的行為的函式(可以理解為某種觸發器)。

方法

handler.get()

該方法用於攔截物件的讀取屬性操作。

js var p = new Proxy(target, { get: function(target, property, receiver) { } }); 引數: - target:目標物件。 - property:被獲取的屬性名。 - receiver:Proxy或者繼承Proxy的物件 - 返回值:可以返回任意值

約束:如果違背了以下的約束,proxy會丟擲 TypeError:

  • 如果要訪問的目標屬性是不可寫以及不可配置的,則返回的值必須與該目標屬性的值相同。
  • 如果要訪問的目標屬性沒有配置訪問方法,即get方法是undefined的,則返回值必須為undefined。

示例: ```js var p = new Proxy({}, { get: function(target, prop, receiver) { console.log("called: " + prop); return 10; } });

console.log(p.a); // "called: a" ``` 違反約束情況:

```js var obj = {};

Object.defineProperty(obj, "a", { configurable: false, enumerable: false, value: 10, writable: false });

var p = new Proxy(obj, { get: function(target, prop) { return 20; } });

p.a; //會丟擲TypeError

```

handler.set()

該方法用於攔截設定屬性值的操作。

語法:

js new Proxy(target, { set: function(target, property, value, receiver) { } }); 引數:

  • target:目標物件。
  • property:被設定的屬性名。
  • value:被設定的新值。
  • receiver:最初被呼叫的物件。通常是proxy本身,但handler的set方法也有可能在原型鏈上或以其他方式被間接地呼叫(因此不一定是proxy本身)。比如,假設有一段程式碼執行 obj.name ="jen",obj不是一個proxy且自身不含name屬性,但它的原型鏈上有一個proxy,那麼那個proxy的set攔截函式會被呼叫,此時obj會作為receiver引數傳進來。
  • 返回值:set方法應該返回一個布林值,返回true代表此次設定屬性成功了,如果返回false且設定屬性操作發生在嚴格模式下,那麼會丟擲一個TypeError。

約束:如果違背以下的約束條件,proxy會丟擲一個TypeError:

  • 若目標屬性是不可寫及不可配置的,則不能改變它的值。
  • 如果目標屬性沒有配置儲存方法,即set方法是undefined的,則不能設定它的值。
  • 在嚴格模式下,若set方法返回false,則會丟擲一個 TypeError 異常。

示例: ```js var p = new Proxy({}, { set: function(target, prop, value, receiver) { target[prop] = value; console.log('property set: ' + prop + ' = ' + value); return true; } })

console.log('a' in p); // false

p.a = 10; // "property set: a = 10" console.log('a' in p); // true console.log(p.a); // 10 ```

基礎事例 ```js var handler = { get:function(target,name){ return name in target ? target[name] : 37; } };

var p = new Proxy({},handler); p.a = 1; p.b = undefined; console.log(p.a,p.b); //1 undefined console.log('c' in p, p.c); //false 37 ```

無操作轉發代理

在以下例子中,我們使用了一個原生 JavaScript物件,代理會將所有應用到它的操作轉發到這個物件上。

```js let target = {}; let p = new Proxy(target, {});

p.a = 37; // 操作轉發到目標

console.log(target.a); // 37. 操作已經被正確地轉發 ``` 代理p將所有應用於他的操作轉發到target

驗證

通過代理,你可以輕鬆地驗證向一個物件的傳值。這個例子使用了set。

```js let handler = { set: function (obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('The age is not an integer'); } if (value > 200) { throw new RangeError('The age seems invalid'); } }

    // The default behavior to store the value
    obj[prop] = value;

    // 表示成功
    return true;
}

};

    let person = new Proxy({}, handler);

    person.age = 100;

    console.log(person.age);
    // 100

    person.age = 'young';
    // 丟擲異常: Uncaught TypeError: The age is not an integer

    person.age = 300;
    // 丟擲異常: Uncaught RangeError: The age seems invalid

```

上邊示例相當於,每當向person裡邊新增屬性時,會攔截操作,判斷屬性值是否滿足要求,滿足要求就存起來,否則就丟擲異常。

0.1+0.2!==0.3

先來個示例: js console.log(0.1 + 0.2 == 0.3) //false console.log(0.1 + 0.2 > 0.3) //true 原因在於在JS中採用的IEEE 754的雙精度標準,將數字儲存為雙精度浮點數,計算機內部儲存資料的編碼的時候,0.1在計算機內部根本就不是精確的0.1,而是一個有舍入誤差的0.1。當代碼被編譯或解釋後,0.1已經被四捨五入成一個與之很接近的計算機內部數字,以至於計算還沒開始,一個很小的舍入錯誤就已經產生了。這也就是 0.1 + 0.2 不等於0.3 的原因。

另外要注意,不是所有浮點數都有舍入誤差。二進位制能精確地表示位數有限且分母是2的倍數的小數,比如0.5,0.5在計算機內部就沒有舍入誤差。所以0.5 + 0.5 === 1

如何解決這類問題

用整數表示

最好的方法就是我們想辦法規避掉這類小數計算時的精度問題就好了,那麼最常用的方法就是將浮點數轉化成整數計算。因為整數都是可以精確表示的。通常的解決辦法 就是 把計算數字提升 10 的N次方倍,再除以 10的N次方。一般都用 1000 就行了。

js console.log((0.1*1000 + 0.2 * 1000)/1000 == 0.3) //true

bignumber.js

bignumber.js會在一定精度內,讓浮點數計算結果符合我們的期望。 ```js { let x = new BigNumber(0.1); let y = new BigNumber(0.2) let z = new BigNumber(0.3)

console.log(z.equals(x.add(y))) // 0.3 === 0.1 + 0.2, true console.log(z.minus(x).equals(y)) // true console.log(z.minus(y).equals(x)) // true } ```

原生

js parseFloat((0.1 + 0.2).toFixed(10)); console.log(parseFloat((0.1 + 0.2).toFixed(10))) //0.3

script標籤中的defer和async

(1)defer屬性規定是否延遲執行指令碼,直到頁面載入為止。async屬性規定指令碼一旦可用,就非同步執行。

(2)defer並行載入JavaScript檔案,會按照頁面上script標籤的順序執行。async並行載入JavaScript檔案,下載完成立即執行,不會按照頁面上script標籤的順序執行。

具體如下圖:

defer&async.png

藍色線代表網路讀取,紅色線代表執行時間,這倆都是針對指令碼的;綠色線代表 HTML 解析。

也就是說async是亂序的,而defer是順序執行,這也就決定了async比較適用於百度分析或者谷歌分析這類不依賴其他指令碼的庫。從圖中可以看到一個普通的<script> 標籤的載入和解析都是同步的,會阻塞DOM的渲染,這也就是我們經常會把<script>寫在<body>底部的原因之一,為了防止載入資源而導致的長時間的白屏,另一個原因是js可能會進行DOM操作,所以要在DOM全部渲染完後再執行。

js和css是怎樣阻塞DOM解析和頁面渲染的

參考連結

css不會阻塞DOM的解析,會阻塞頁面渲染

瀏覽器是解析DOM生成DOM Tree,結合CSS生成的CSS Tree,最終組成render tree,再渲染頁面。由此可見,在此過程中CSS完全無法影響DOM Tree,因而無需阻塞DOM解析。然而,DOM Tree和CSS Tree會組合成render tree。所以也可以得到,css是會阻塞頁面渲染的。

看接下來程式碼: ```js

```

答案是瀏覽器會轉圈圈三秒,但此過程中不會列印任何東西,之後呈現出一個淺藍色的div(common.css),再打印出null。結果好像CSS不單阻塞了頁面渲染,還阻塞了DOM 的解析。

其實阻塞DOM解析的是js,如果js指令碼中要獲取元素的樣式,寬高等css控制的屬性,瀏覽器是需要計算的,也就是依賴於css的,只好等所有的樣式載入完了之後再執行js。

<script><link>同時在頭部的話,<script>在上可能會更好,之所以是可能,是因為如果<link>的內容下載更快的話,是沒影響的,但反過來的話,JS就要等待了,然而這些等待的時間是完全不必要的。

js阻塞DOM的解析和頁面的渲染

瀏覽器並不知道指令碼的內容是什麼,如果先行解析下面的DOM,萬一指令碼內全刪了後面的DOM,瀏覽器就白乾活了。更別談喪心病狂的document.write。瀏覽器無法預估裡面的內容,那就乾脆全部停住,等指令碼執行完再幹活就好了。

對此的優化其實也很顯而易見,具體分為兩類:

  • 如果JS檔案體積太大,同時你確定沒必要阻塞DOM解析的話,不妨按需要加上defer或者async屬性,此時指令碼下載的過程中是不會阻塞DOM解析的。
  • 如果是檔案執行時間太長,不妨分拆一下程式碼,不用立即執行的程式碼,可以使用一下以前的黑科技:setTimeout()。當然,現代的瀏覽器很聰明,它會“偷看”之後的DOM內容,碰到如<link>、<script>和<img>等標籤時,它會幫助我們先行下載裡面的資源,不會傻等到解析到那裡時才下載。

結論:

  • CSS 不會阻塞 DOM 的解析,但會阻塞頁面渲染。
  • JS 阻塞 DOM 解析,但瀏覽器會”偷看”DOM,預先下載相關資源。
  • 瀏覽器遇到 <script>且沒有defer或async屬性的 標籤時,會觸發頁面渲染(瀏覽器不知道指令碼的內容,因而碰到指令碼時,只好先渲染頁面,確保指令碼能獲取到最新的DOM元素資訊,儘管指令碼可能不需要這些資訊),因而如果前面CSS資源尚未載入完畢時,瀏覽器會等待它載入完畢在執行指令碼。
  • <script>最好放底部,<link>最好放頭部,如果頭部同時有<script><link>的情況下,最好將<script>放在<link>上面

嵌入js和外部js的差別

內部js會阻塞整個DOM的解析,外部js只會阻塞其後邊js的解析:

```js

var count = 0; for (var i = 0; i < 100000; i++) { for (var j = 0; j < 10000; j++) { count++; } } console.log(count);

Title

div1
div2
div3

Title

div1
div2
div3

Title

div1

Title

div1

```

encodeURL和decodeURL

encodeURL()用於將URL轉換為十六進位制編碼

decodeURL()用於將編碼的URL轉換為正常的URL

瀏覽器多個標籤頁之間的通訊

呼叫localStorage

在一個標籤頁裡面使用 localStorage.setItem(key,value)新增(修改、刪除)內容;

在另一個標籤頁裡面監聽 storage 事件。即可得到 localstorge 儲存的值,實現不同標籤頁之間的通訊。 ```html

Title

Title

```

呼叫cookie + setInterval()

將要傳遞的資訊儲存在cookie中,每隔一定時間讀取cookie資訊,即可隨時獲取要傳遞的資訊。 ```html

Title

Title

```

null 和undefined

首先看一個判斷題:null和undefined 是否相等

js console.log(null==undefined)//true console.log(null===undefined)//false

觀察可以發現:null和undefined 兩者相等,但是當兩者做全等比較時,兩者又不等。

null型別,代表“空值”,代表一個空物件指標,使用typeof運算得到object,可以認為它是一個特殊的物件值。null用來表示尚未存在的物件,常用來表示函式企圖返回一個不存在的物件。是不應該有值。

undefined型別,當一個聲明瞭一個變數未初始化時,得到的就是undefined。是應該有值,但是尚未賦值

null表示”沒有物件”,即該處不應該有值。典型用法是: - 作為函式的引數,表示該函式的引數不是物件。 - 作為物件原型鏈的終點。

undefined表示”缺少值”,就是此處應該有一個值,但是還沒有定義。典型用法是: - 變數被聲明瞭,但沒有賦值時,就等於undefined。 - 呼叫函式時,應該提供的引數沒有提供,該引數等於undefined。 - 物件沒有賦值的屬性,該屬性的值為undefined。 - 函式沒有返回值時,預設返回undefined。

JavaScript中不同型別的錯誤有幾種

Load time errors :該錯誤發生於載入網頁時,例如出現語法錯誤等狀況,稱為載入時間錯誤,並且會動態生成錯誤。

Run time errors :由於在HTML語言中濫用命令而導致的錯誤。

Logical Errors :這是由於在具有不同操作的函式上執行了錯誤邏輯而發生的錯誤。

  1. EvalError - Eval錯誤 該物件表示全域性函式 eval()中發生的錯誤。可以通過建構函式建立這個物件的例項。

  2. ReferenceError-引用錯誤 該物件會在引用未定義的變數時觸發,也可以通過建構函式建立這個物件的例項。

  3. RangeError-範圍錯誤 該錯誤物件會在值超過有效範圍時觸發,也可以通過建構函式建立這個物件的例項。

  4. SyntaxError-語法錯誤 該錯誤物件在使用不合法的語法結構時觸發,也可以通過建構函式建立這個物件的例項。

  5. TypeError-型別錯誤 該物件會在物件用來表示值的型別非預期型別時觸發,也可以通過建構函式建立這個物件的例項。

  6. URIError-URI錯誤 該錯誤會在錯誤使用全域性URI函式 如encodeURI()、decodeURI()等時觸發。也可以通過建構函式建立該物件的例項。

為什麼會出現ES6新特性

每一次標準的誕生都意味著語言的完善,功能的加強。JavaScript語言本身也有一些令人不滿意的地方。

  • ES6的目標,是使得JavaScript語言可以用來編寫大型的複雜的應用程式,成為企業級開發語言。

  • 變數提升特性增加了程式執行時的不可預測性

  • 語法過於鬆散,實現相同的功能,不同的人可能會寫出不同的程式碼

set和map區別

  1. 初始化需要的值不一樣,Map需要的是一個二維陣列,而Set 需要的是一維 Array 陣列
  2. Map 是鍵值對的存在,值不作為健;而 Set 沒有 value 只有 key,value 就是 key;
  3. Map 和 Set 都不允許鍵重複
  4. Map的鍵是不能修改,但是鍵對應的值是可以修改的;Set不能通過迭代器來改變Set的值,因為Set的值就是鍵。

基本資料型別和引用資料型別的區別

  • 儲存位置不同
  • 複製結果不同
  • 訪問方式不同

不用promise,等待所有請求回來後執行回撥

  • 設定一個flag變數,然後在各自的ajax的成功回撥內去維護這個變數數量(比如flag++),當滿足條件時,我們來觸發後續函式
  • 使⽤Generator ```js // 使⽤Generator順序執⾏三次非同步操作 function* r(num) { yield ajax1(num); yield ajax2(); yield ajax3(); }

    // ajax為非同步操作,結合Promise使⽤可以輕鬆實現非同步操作佇列 function ajax1(num) { return new Promise(resolve => { setTimeout(() => { console.log('第1個非同步請求'); // 輸出處理結果 resolve() }, 1000); }); }

    function ajax2() { return new Promise(resolve => { setTimeout(() => { console.log('第2個非同步請求'); // 輸出處理結果 resolve(); // 操作成功 }, 1000); }); }

    function ajax3() { return new Promise(resolve => { setTimeout(() => { console.log('第3個非同步請求'); // 輸出處理結果 resolve(); // 操作成功 }, 1000); }); }

    // 不使⽤遞迴函式調⽤ let it = r();

    // 修改為可處理Promise的next function next(data) { let { value, done } = it.next(data); // 啟動 if (!done) { value.then(() => { next(); }); } else { console.log('執行完畢!'); } }

    next(); ```

new出來物件、建構函式、普通函式、object之間原型和原型鏈的區別和聯絡

new出來的物件原型指向建構函式的原型物件

建構函式的原型指向其建構函式的原型物件,最終指向Object的建構函式的原型物件,原型鏈的頂端都是null

Number創建出來的數值,instanceof判斷出來是什麼型別

instanceof這個運算子是用來測試一個物件的原型鏈上是否有該原型的建構函式,即instanceof左表示式要是一個物件,右側表示式要是一個建構函式,並且左側是右側例項化出來的物件才會返回true

js 123 instanceof Number new Number(123) instanceof Number Number(123) instanceof Number

第一個 首先左側為Number型別,並不是一個物件,更不是由Number例項化出來的(基本包裝型別),所以為false

第二個 左側使用Number構造例項化物件 右側為Number構造 ,所以為true

第三個 左側沒有使用new所以並不是使用建構函式例項化 而是使用Number這個函式返回了一個數字, 所以為false

箭頭函式和普通函式的區別

  • 箭頭函式不能作為建構函式
  • 箭頭函式不繫結arguments,取而代之用rest引數解決
  • this取決於父作用域的this
  • 箭頭函式的this不通過call,apply,bind繫結
  • 箭頭函式沒有prototype

箭頭函式為什麼不能作為建構函式

new一個建構函式的時候通常會經過以下幾步:

  • 建立空物件
  • __proto __指向建構函式的prototype
  • 確定this指向
  • 向物件中新增屬性
  • 返回物件

首先箭頭函式沒有prototype,所以不能確定原型指向;

其次箭頭函式的this是取決於父作用域的this,並且不能通過call,apply或者bind進行繫結的

字串轉換成json物件

  1. javascript函式eval() 語法:

js var obj = eval ("(" + txt + ")"); //必須把文字包圍在括號中,這樣才能避免語法錯誤

eval() 函式可計算某個字串,並執行其中的的 JavaScript 程式碼。由於 JSON語法是 JavaScript 語法的子集,JavaScript 函式eval()可用於將 JSON 文字轉換為 JavaScript 物件。

注意:當字串中包含表示式時,eval() 函式也會編譯並執行,轉換會存在安全問題。

  1. 瀏覽器自帶物件JSON.parse() 語法:

js var obj = JSON.parse(text[, reviver]) //text:必需, 一個有效的 JSON 字串。解析前要確保你的資料是標準的 JSON 格式,否則會解析出錯。 //reviver: 可選,一個轉換結果的函式, 將為物件的每個成員呼叫此函式。

JSON.parse()比eval()安全,而且速度更快。支援主流瀏覽器:Firefox 3.5,IE 8,Chrome,Opera 10,Safari 4。

注意:IE8相容模式,IE 7,IE 6,會存在相容性問題。

json和xml兩者的區別

JSON 與 XML 的相同之處:

  • JSON 和 XML 資料都是 "自我描述" ,都易於理解。
  • JSON 和 XML 資料都是有層次的結構
  • JSON 和 XML 資料可以被大多數程式語言使用
  • JSON 和 XML 都用於接收 web 服務端的資料。

JSON 與 XML 的不同之處:

  • JSON 不需要結束標籤
  • JSON 更加簡短
  • JSON 讀寫速度更快
  • JSON 可以使用陣列

最大的不同是:XML 需要使用 XML 解析器來解析,JSON 可以使用標準的 JavaScript 函式來解析。

Promise先resolve再reject,promise是什麼狀態;Promise先resolve後列印東西會成功嗎

示例一:

js const promise = new Promise((resolve, reject) => { setTimeout(() => { reject(); },1000) }); console.log(promise);//rejected

示例2:

```js const promise = new Promise((resolve, reject) => { setTimeout(() => { console.log('resolve前Promise的狀態:',promise);//pending resolve(); console.log('resolve後Promise的狀態:',promise);//fulfilled }) });

```

示例3:

js const promise = new Promise((resolve, reject) => { setTimeout(() => { console.log("Promise的狀態:resolve前:",promise);//pending resolve(); console.log("Promise的狀態:resolve後:",promise); //fulfilled reject(); console.log("Promise的狀態:reject後:",promise);//fulfilled }) });

示例4:

js const promise = new Promise((resolve, reject) => { setTimeout(() => { console.log("Promise的狀態:reject前:",promise);//pending reject(); console.log("Promise的狀態:reject後:",promise);//rejected resolve(); console.log("Promise的狀態:resolve後:",promise);//rejected }) }); console.log('最終狀態',promise)//pending

Promise怎麼處理異常

promise.catch()可以捕獲promise所有狀態的異常。包括:

  1. 執行resolve()和reject()對應的promise.then(()=>{},()=>{}) 中的倆回撥函式中的異常
  2. Promise.resolve(err)觸發的
  3. Promise.reject(err)觸發的

```js // 定義Promise const initPromise = (status) => { return new Promise((resolve, reject) => { // status 成功 200,失敗 其它 if (status === 200) { resolve(); // 由"pending"變為"fulfilled" } else { reject('reject reason'); // 由"pending"變為"rejected" } }); };

// 例項化並呼叫promise let testPromise = (status) => { const promise = initPromise(status); try { promise.then( () => { // resolve走這個回撥 console.log('resolve回撥') throw new Error('error from then resolve'); }, (err) => { // rejected走這個回撥 console.log('reject回撥',err) throw new Error('error from then reject'); }) .catch(e => { console.log('promise catch捕獲:' + e); }); } catch (e) { console.log('try catch捕獲:' + e); } }

testPromise(100) //reject回撥 reject reason //promise catch捕獲:Error: error from then reject

testPromise(200) //resolve回撥 //promise catch捕獲:Error: error from then resolve ```

```js // 定義Promise const initPromise = (status) => { return new Promise((resolve, reject) => { // status 成功 200,失敗 其它 setTimeout(() => { if (status === 200) { resolve(); // 由"pending"變為"fulfilled" } else { reject('reject reason'); // 由"pending"變為"rejected" } }, 0) }); };

// 例項化並呼叫promise let testPromise = (status) => { const promise = initPromise(status); try { promise.then( () => { console.log('resolve回撥') throw new Error('error from then resolve');

        },
        (err) => {
            // rejected走這個回撥
            console.log('reject回撥', err)
            throw new Error('error from then reject');
        })
        .catch(e => {
            console.log('promise catch捕獲:' + e);
        });


} catch (e) {
    console.log('try catch捕獲:' + e);
}

}

testPromise(200) //resolve回撥 //promise catch捕獲:Error: error from then resolve ```

由上可以總結出:

  1. promise.catch可以處理resolve和reject回撥函式丟擲的異常;setTimeout中的 throw new Error不可以處理,但是可以處理setTimeout中的reject
  2. try...catch只能捕獲try中丟擲的異常

try...catch 無法捕獲 setTimeout 和ajax請求中的非同步任務中的錯誤。可以用window.onerror處理

```js // 定義Promise const initPromise = (status) => { return new Promise((resolve, reject) => { // status 成功 200,失敗 其它 if (status === 200) { resolve(); // 由"pending"變為"fulfilled" } else { reject('reject reason'); // 由"pending"變為"rejected" } }); };

// 例項化並呼叫promise let testPromise = (status) => { const promise = initPromise(status); try { promise.then( () => { // ------------------------- try { // resolve走這個回撥 setTimeout(() => { console.log('resolve回撥') throw new Error('error from then resolve'); }, 0) } catch (e) { console.log('resolve裡邊的catch') } // ------------------------- }, (err) => { // rejected走這個回撥 console.log('reject回撥', err) throw new Error('error from then reject'); }) .catch(e => { console.log('promise catch捕獲:' + e); });

} catch (e) {
    console.log('try catch捕獲:' + e);
}

}

testPromise(200) //resolve回撥 //Uncaught Error: error from then resolve

```

```js // 定義Promise const initPromise = (status) => { return new Promise((resolve, reject) => { throw new Error('error!!!') }); };

// 例項化並呼叫promise let testPromise = (status) => { const promise = initPromise(status); try { promise.then( () => {

        },
        (err) => {
            // rejected走這個回撥
            console.log('reject回撥', err)
            throw new Error('error from then reject');
        })
        .catch(e => {
            console.log('promise catch捕獲:' + e);
        });

} catch (e) {
    console.log('try catch捕獲:' + e);
}

}

testPromise(200) //reject回撥 Error: error!!! //index1.js:32 promise catch捕獲:Error: error from then reject ```

try...catch可不可以捕獲promise異常

不能,try...catch只能捕獲同步異常,對於非同步異常不能捕獲;

promise中的異常可以用promise.catch進行處理;

async/await

js async function run() { try { await Promise.reject(new Error("Oops!")); } catch (error) { console.log(error.message)// "Oops!" } } run()

TypeScript

typeScripttypeinterface的區別

選擇:能用interface就用interface,不能用的用type

相同點:

  • 都可以用來描述一個函式或者物件

```typescript interface User { name: string age: number }

interface SetUser { (name: string, age: number): void; }

type User = { name: string age: number };

type SetUser = (name: string, age: number)=> void;

```

  • 都允許拓展:但是語法不同

```typescript //interface extends interface interface Name { name: string; } interface User extends Name { age: number; }

// type extends type type Name = { name: string; } type User = Name & { age: number };

// interface extends type type Name = { name: string; } interface User extends Name { age: number; }

//type extends interface interface Name { name: string; } type User = Name & { age: number; }

```

不同點:

  1. type可以而interface不行

  2. type 可以宣告基本類型別名,聯合型別,元組等型別

    ```typescript // 基本類型別名 type Name = string

    // 聯合型別 interface Dog { wong(); } interface Cat { miao(); }

    type Pet = Dog | Cat

    // 具體定義陣列每個位置的型別 type PetList = [Dog, Pet]

    ```

  • type 語句中還可以使用 typeof 獲取例項的 型別進行賦值

typescript // 當你想獲取一個變數的型別時,使用 typeof let div = document.createElement('div'); type B = typeof div

  1. interface 可以而 type 不行

  2. interface 能夠合併重複宣告,而重複宣告 type ,會報錯

    ```typescript interface User { name: string age: number }

    interface User { sex: string }

    / User 介面為 { name: string age: number sex: string } /

    ```

typeScript定義掛載在window上的變數型別

在index.d.ts檔案中宣告:

typescript declare global{ interface Window{ val:string } }

.d.ts檔案中宣告的變數或者模組,在其他檔案中不需要使用import匯入,可以直接使用。

.d.ts檔案中我們常常可以看到declaredeclare左右就是告訴TS編譯器你擔保這些變數和模組存在,並聲明瞭相應型別,編譯的時候不需要提示錯誤!

declare 定義的型別只會用於編譯時的檢查,編譯結果中會被刪除。

我們在編寫 TS 的時候會定義很多的型別,但是主流的庫都是 JS編寫的,並不支援型別系統。這個時候你不能用TS重寫主流的庫,這個時候我們只需要編寫僅包含型別註釋的 d.ts 檔案,然後從您的 TS 程式碼中,可以在仍然使用純 JS 庫的同時,獲得靜態型別檢查的 TS 優勢。