ES6高頻面試題目整理


本篇文章是根據以下內容進行的總結

1、https://segmentfault.com/a/1190000011344301

2、http://www.bslxx.com/a/mianshiti/tiku/2017/1019/953.html

3、http://www.bslxx.com/a/mianshiti/tiku/javascript/2017/1213/1505.html

 

前言

自從ES6發布以來,就受到了廣大開發者的歡迎。它的新特性解決了很多實際開發中的痛點,並且使得JavaScript逐步成為一門能夠開發大型企業應用的編程語言,基於這種技術環境下,很多公司都將ES6視為開發的其中一個標准,因此在招聘人才的時候,也會對其進行ES6知識的考察。下面就來看看哪些ES6知識是我們需要重點掌握的。

 

箭頭函數需要注意的地方

*當要求動態上下文的時候,就不能夠使用箭頭函數,也就是this的固定化。

1、在使用=>定義函數的時候,this的指向是定義時所在的對象,而不是使用時所在的對象;
2、不能夠用作構造函數,這就是說,不能夠使用new命令,否則就會拋出一個錯誤;
3、不能夠使用arguments對象;
4、不能使用yield命令;

下面來看一道面試題,重點說明下第一個知識點:

class Animal {
  constructor() {
    this.type = "animal";
  }
  say(val) {
    setTimeout(function () {
      console.log(this); //window
      console.log(this.type + " says " + val);
    }, 1000)
  }
}
var animal = new Animal();
animal.say("hi"); //undefined says hi
View Code

【拓展】

《JavaScript高級程序設計》第二版中,寫到:“超時調用的代碼都是在全局作用域中執行的,因此函數中this的值在非嚴格模式下指向window對象,在嚴格模式下是undefined”。也就是說在非嚴格模式下,setTimeout中所執行函數中的this,永遠指向window!!

 

我們再來看看箭頭函數(=>)的情況:

class Animal {
  constructor() {
    this.type = "animal";
  }
  say(val) {
    setTimeout(() => {
      console.log(this); //Animal
      console.log(this.type + ' says ' + val);
    }, 1000)
  }
}
var animal = new Animal();
animal.say("hi"); //animal says hi
View Code

【特點】

  • 不需要function關鍵字來創建函數
  • 省略return關鍵字
  • 繼承當前上下文的 this 關鍵字

 

 

let和const

 *let是更完美的var,不是全局變量,具有塊級函數作用域,大多數情況不會發生變量提升。const定義常量值,不能夠重新賦值,如果值是一個對象,可以改變對象里邊的屬性值。

1、let聲明的變量具有塊級作用域
2、let聲明的變量不能通過window.變量名進行訪問
3、形如for(let x..)的循環是每次迭代都為x創建新的綁定

下面是var帶來的不合理場景

var arr = [];
for (var i = 0; i < 10; i++) {
  arr[i] = function () {
    console.log(i);
  }
}
arr[5]() //10,a[5]輸出f(){console.log(i);},后面加個括號代表執行f()
View Code

在上述代碼中,變量i是var聲明的,在全局范圍類都有效,所以用來計數的循環變量泄露為全局變量。所以每一次循環,新的i值都會覆蓋舊值,導致最后輸出都是10。

而如果對循環使用let語句的情況,那么每次迭代都是為x創建新的綁定代碼如下:

var arr = [];
for (let i = 0; i < 10; i++) {
  arr[i] = function () {
    console.log(i);
  }
}
arr[5]() //5,a[5]輸出f(){console.log(i);},后面加個括號代表執行f()
View Code

【拓展】

當然,除了這種方式讓數組找中的各個元素分別是不同的函數,我們還可以采用ES5中的閉包和立即函數兩種方法。

1、采用閉包

function showNum(i) {
  return function () {
    console.log(i)
  }
}
var a = []
for (var i = 0; i < 5; i++) {
  a[i] = showNum(i)(); //循環輸出1,2,3,4
}
View Code

2、采用立即執行函數

var a = []
for (var i = 0; i < 5; i++) {
  a[i] = (function (i) {
    return function () {
      console.log(i)
    }
  })(i)
}
a[2](); //2
View Code

【面試】

把以下代碼使用兩種方法,依次輸出0-9

var funcs = []
for (var i = 0; i < 10; i++) {
  funcs.push(function () {
    console.log(i)
  })
}
funcs.forEach(function (func) {
  func(); //輸出十個10
})
View Code

方法一:使用立即執行函數

var funcs = []
for (var i = 0; i < 10; i++) {
  funcs.push((function (value) {
    return function () {
      console.log(value)
    }
  }(i)))
}
funcs.forEach(function (func) {
  func(); //依次輸出0-9
})
View Code

方法二:使用閉包

function show(i) {
  return function () {
    console.log(i)
  }
}
var funcs = []
for (var i = 0; i < 10; i++) {
  funcs.push(show(i))
}
funcs.forEach(function (func) {
  func(); //0 1 2 3 4 5 6 7 8 9
})
View Code

方法三:使用let

var funcs = []
for (let i = 0; i < 10; i++) {
  funcs.push(function () {
    console.log(i)
  })
}
funcs.forEach(function (func) {
  func(); //依次輸出0-9
})
View Code

其他知識點:forEach() 方法用於調用數組的每個元素,並將元素傳遞給回調函數。戳這里查看參考文章

 

 

Set數據結構

*es6方法,Set本身是一個構造函數,它類似於數組,但是成員值都是唯一的。

const set = new Set([1,2,3,4,4])
console.log([...set] )// [1,2,3,4]
console.log(Array.from(new Set([2,3,3,5,6]))); //[2,3,5,6]
View Code

 

 

Class的講解

*class語法相對原型、構造函數、繼承更接近傳統語法,它的寫法能夠讓對象原型的寫法更加清晰、面向對象編程的語法更加通俗
這是class的具體用法。

class Animal {
  constructor() {
    this.type = 'animal'
  }
  says(say) {
    console.log(this.type + 'says' + say)
  }
}
let animal = new Animal()
animal.says('hello') // animal says hello

class Cat extends Animal {
  constructor() {
    super()
    this.type = 'cat'
  }
}
let cat = new Cat()
cat.says('hello') // cat says hell
View Code

可以看出在使用extend的時候結構輸出是cat says hello 而不是animal says hello。說明contructor內部定義的方法和屬性是實例對象自己的,不能通過extends 進行繼承。在class cat中出現了super(),這是什么呢?因為在ES6中,子類的構造函數必須含有super函數,super表示的是調用父類的構造函數,雖然是父類的構造函數,但是this指向的卻是cat。

更詳細的參考文章

 

 

模板字符串

*就是這種形式${varible},在以往的時候我們在連接字符串和變量的時候需要使用這種方式'string' + varible + 'string'但是有了模版語言后我們可以使用string${varible}string這種進行連接。基本用途有如下:

1、基本的字符串格式化,將表達式嵌入字符串中進行拼接,用${}來界定。

//es5 
var name = 'lux';
console.log('hello' + name);
//es6
const name = 'lux';
console.log(`hello ${name}`); //hello lux
View Code

2、在ES5時我們通過反斜杠(\)來做多行字符串或者字符串一行行拼接,ES6反引號(``)直接搞定。

//ES5
var template = "hello \
world";
console.log(template); //hello world

//ES6
const template = `hello
world`;
console.log(template); //hello 空行 world
View Code

【拓展】

字符串的其他方法

// 1.includes:判斷是否包含然后直接返回布爾值
let str = 'hahay'
console.log(str.includes('y')) // true

// 2.repeat: 獲取字符串重復n次
let s = 'he'
console.log(s.repeat(3)) // 'hehehe'
View Code

 

 

重點“人物”:Promise!

概念:Promise是異步編程的一種解決方案,比傳統的解決方案(回調函數和事件)更合合理、強大。所謂Promise,簡單來說就是一個容器,里面保存着某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說,Promies是一個對象,從它可以獲取異步操作的消息。Promise提供統一的API,各種異步操作都可以用同樣的方法進行處理。處理過程流程圖:

【面試套路1】

手寫一個promise

var promise = new Promise((resolve, reject) => {
  if (操作成功) {
    resolve(value)
  } else {
    reject(error)
  }
})
promise.then(function (value) {
  // success
}, function (value) {
  // failure
})
View Code

【面試套路2】

怎么解決回調函數里面回調另一個函數,另一個函數的參數需要依賴這個回調函數。需要被解決的代碼如下:

$http.get(url).success(function (res) {
  if (success != undefined) {
    success(res);
  }
}).error(function (res) {
  if (error != undefined) {
    error(res);
  }
});

function success(data) {
  if( data.id != 0) {
    var url = "getdata/data?id=" + data.id + "";
    $http.get(url).success(function (res) {
      showData(res);
    }).error(function (res) {
      if (error != undefined) {
        error(res);
      }
    });
  }
}
View Code

【面試套路3】

以下代碼依次輸出的內容是?

setTimeout(function () {
  console.log(1)
}, 0);
new Promise(function executor(resolve) {
  console.log(2);
  for (var i = 0; i < 10000; i++) {
    i == 9999 && resolve();
  }
  console.log(3);
}).then(function () {
  console.log(4);
});
console.log(5);
View Code

 上述代碼解析:

首先先碰到一個 setTimeout,於是會先設置一個定時,在定時結束后將傳遞這個函數放到任務隊列里面,因此開始肯定不會輸出 1 。 

然后是一個 Promise,里面的函數是直接執行的,因此應該直接輸出 2 3 。 

然后,Promise 的 then 應當會放到當前 tick 的最后,但是還是在當前 tick 中。 

因此,應當先輸出 5,然后再輸出 4 , 最后在到下一個 tick,就是 1 。
View Code

【面試套路4】

jQuery的ajax返回的是promise對象嗎?

jquery的ajax返回的是deferred對象,通過promise的resolve()方法將其轉換為promise對象。

var jsPromise = Promise.resolve($.ajax('/whatever.json'));
View Code

【面試套路5】

 promise只有2個狀態,成功和失敗,怎么讓一個函數無論成功還是失敗都能被調用?

使用promise.all()

Promise.all方法用於將多個Promise實例,包裝成一個新的Promise實例。

Promise.all方法接受一個數組作為參數,數組里的元素都是Promise對象的實例,如果不是,就會先調用下面講到的Promise.resolve方法,將參數轉為Promise實例,再進一步處理。(Promise.all方法的參數可以不是數組,但必須具有Iterator接口,且返回的每個成員都是Promise實例。)

示例:
var p =Promise.all([p1,p2,p3]);
p的狀態由p1、p2、p3決定,分為兩種情況。
當該數組里的所有Promise實例都進入Fulfilled狀態:Promise.all**返回的實例才會變成Fulfilled狀態。並將Promise實例數組的所有返回值組成一個數組,傳遞給Promise.all返回實例的回調函數**。

當該數組里的某個Promise實例都進入Rejected狀態:Promise.all返回的實例會立即變成Rejected狀態。並將第一個rejected的實例返回值傳遞給Promise.all返回實例的回調函數。
View Code

【面試套路6】

一、分析下列程序代碼,得出運行結果,解釋其原因

const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})
promise.then(() => {
  console.log(3)
})
console.log(4)
View Code

運行結果及原因

運行結果:
1 2 4 3

原因:
Promise 構造函數是同步執行的,promise.then 中的函數是異步執行的。
View Code

 

二、分析下列程序代碼,得出運行結果,解釋其原因

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!')
})

console.log('promise1', promise1)
console.log('promise2', promise2)

setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 2000)
View Code

運行結果及原因

運行結果:
promise1 Promise { <pending> }
promise2 Promise { <pending> }
(node:50928) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: error!!!
(node:50928) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
promise1 Promise { 'success' }
promise2 Promise {
  <rejected> Error: error!!!
    at promise.then (...)
    at <anonymous> }


原因:
promise 有 3 種狀態:pending(進行中)、fulfilled(已完成,又稱為Resolved) 或 rejected(已失敗)。狀態改變只能是 pending->fulfilled 或者 pending->rejected,狀態一旦改變則不能再變。上面 promise2 並不是 promise1,而是返回的一個新的 Promise 實例。
View Code

 

三、分析下列程序代碼,得出運行結果,解釋其原因

const promise = new Promise((resolve, reject) => {
  resolve('success1')
  reject('error')
  resolve('success2')
})

promise
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })
View Code

運行結果及原因

運行結果:
then:success1

原因:
構造函數中的 resolve 或 reject 只有第一次執行有效,多次調用沒有任何作用,呼應代碼二結論:promise 狀態一旦改變則不能再變。
View Code

 

四、分析下列程序代碼,得出運行結果,解釋其原因

Promise.resolve(1)
  .then((res) => {
    console.log(res)
    return 2
  })
  .catch((err) => {
    return 3
  })
  .then((res) => {
    console.log(res)
  })
View Code

運行結果及原因

運行結果:
1  2

原因:
promise 可以鏈式調用。提起鏈式調用我們通常會想到通過 return this 實現,不過 Promise 並不是這樣實現的。promise 每次調用 .then 或者 .catch 都會返回一個新的 promise,從而實現了鏈式調用。
View Code

 

五、分析下列程序代碼,得出運行結果,解釋其原因

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('once')
    resolve('success')
  }, 1000)
})

const start = Date.now()
promise.then((res) => {
  console.log(res, Date.now() - start)
})
promise.then((res) => {
  console.log(res, Date.now() - start)
})
View Code

運行結果及原因

運行結果:
once
success 1001
success 1001

原因:
promise 的 .then 或者 .catch 可以被調用多次,但這里 Promise 構造函數只執行一次。或者說 promise 內部狀態一經改變,並且有了一個值,那么后續每次調用 .then 或者 .catch 都會直接拿到該值。
View Code

 

六、分析下列程序代碼,得出運行結果,解釋其原因

Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })
View Code

運行結果及原因

運行結果
then: Error: error!!!
    at Promise.resolve.then (...)
    at ...

原因
.then 或者 .catchreturn 一個 error 對象並不會拋出錯誤,所以不會被后續的 .catch 捕獲,需要改成其中一種:
return Promise.reject(new Error('error!!!'))
throw new Error('error!!!')

因為返回任意一個非 promise 的值都會被包裹成 promise 對象,即 return new Error('error!!!') 等價於 return Promise.resolve(new Error('error!!!'))。
View Code

 

七、分析下列程序代碼,得出運行結果,解釋其原因

const promise = Promise.resolve()
  .then(() => {
    return promise
  })
promise.catch(console.error)
View Code

運行結果及原因

運行結果
TypeError: Chaining cycle detected for promise #<Promise>
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
    at Function.Module.runMain (module.js:667:11)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:607:3

原因
.then 或 .catch 返回的值不能是 promise 本身,否則會造成死循環。
View Code

 

八、分析下列程序代碼,得出運行結果,解釋其原因

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)
View Code

運行結果及原因

運行結果
1

原因
.then 或者 .catch 的參數期望是函數,傳入非函數則會發生值穿透。
View Code

 

九、分析下列程序代碼,得出運行結果,解釋其原因

Promise.resolve()
  .then(function success (res) {
    throw new Error('error')
  }, function fail1 (e) {
    console.error('fail1: ', e)
  })
  .catch(function fail2 (e) {
    console.error('fail2: ', e)
  })
View Code

運行結果及原因

運行結果
fail2: Error: error
    at success (...)
    at ...

原因
.then 可以接收兩個參數,第一個是處理成功的函數,第二個是處理錯誤的函數。.catch 是 .then 第二個參數的簡便寫法,但是它們用法上有一點需要注意:.then 的第二個處理錯誤的函數捕獲不了第一個處理成功的函數拋出的錯誤,而后續的 .catch 可以捕獲之前的錯誤。
View Code

 

十、分析下列程序代碼,得出運行結果,解釋其原因

process.nextTick(() => {
  console.log('nextTick')
})
Promise.resolve()
  .then(() => {
    console.log('then')
  })
setImmediate(() => {
  console.log('setImmediate')
})
console.log('end')
View Code

運行結果及原因

運行結果
end
nextTick
then
setImmediate

原因
process.nextTick 和 promise.then 都屬於 microtask,而 setImmediate 屬於 macrotask,在事件循環的 check 階段執行。事件循環的每個階段(macrotask)之間都會執行 microtask,事件循環的開始會先執行一次 microtask。
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM