爲了面試能經過,我要看完這75道面試題(下)

做者:Mark A
譯者:前端小智
來源:dev
點贊再看,微信搜索 【大遷世界】 關注這個沒有大廠背景,但有着一股向上積極心態人。本文 GitHub https://github.com/qq44924588... 上已經收錄,文章的已分類,也整理了不少個人文檔,和教程資料。

編程,建網站必備的阿里雲服務器居然免費送了!javascript

51. 什麼是 async/await 及其如何工做?

async/await是 JS 中編寫異步或非阻塞代碼的新方法。它創建在Promises之上,讓異步代碼的可讀性和簡潔度都更高。前端

async/await是 JS 中編寫異步或非阻塞代碼的新方法。 它創建在Promises之上,相對於 Promise 和回調,它的可讀性和簡潔度都更高。 可是,在使用此功能以前,咱們必須先學習Promises的基礎知識,由於正如我以前所說,它是基於Promise構建的,這意味着幕後使用仍然是Promisejava

使用 Promisegit

function callApi() {
  return fetch("url/to/api/endpoint")
    .then(resp => resp.json())
    .then(data => {
      //do something with "data"
    }).catch(err => {
      //do something with "err"
    });
}

使用async/awaitgithub

async/await,咱們使用 tru/catch 語法來捕獲異常。面試

async function callApi() {
  try {
    const resp = await fetch("url/to/api/endpoint");
    const data = await resp.json();
    //do something with "data"
  } catch (e) {
    //do something with "err"
  }
}

注意:使用 async關鍵聲明函數會隱式返回一個Promise編程

const giveMeOne = async () => 1;

giveMeOne()
  .then((num) => {
    console.log(num); // logs 1
  });

注意:await關鍵字只能在async function中使用。在任何非async function的函數中使用await關鍵字都會拋出錯誤。await關鍵字在執行下一行代碼以前等待右側表達式(多是一個Promise)返回。json

const giveMeOne = async () => 1;

function getOne() {
  try {
    const num = await giveMeOne();
    console.log(num);
  } catch (e) {
    console.log(e);
  }
}

// Uncaught SyntaxError: await is only valid in async function

async function getTwo() {
  try {
    const num1 = await giveMeOne(); // 這行會等待右側表達式執行完成
    const num2 = await giveMeOne(); 
    return num1 + num2;
  } catch (e) {
    console.log(e);
  }
}

await getTwo(); // 2

52. 展開(spread )運算符和 剩餘(Rest) 運算符有什麼區別?

展開運算符(spread)是三個點(...),能夠將一個數組轉爲用逗號分隔的參數序列。說的通俗易懂點,有點像化骨綿掌,把一個大元素給打散成一個個單獨的小元素。segmentfault

剩餘運算符也是用三個點(...)表示,它的樣子看起來和展開操做符同樣,可是它是用於解構數組和對象。在某種程度上,剩餘元素和展開元素相反,展開元素會「展開」數組變成多個元素,剩餘元素會收集多個元素和「壓縮」成一個單一的元素。api

function add(a, b) {
  return a + b;
};

const nums = [5, 6];
const sum = add(...nums);
console.log(sum);

在本例中,咱們在調用add函數時使用了展開操做符,對nums數組進行展開。因此參數a的值是5b的值是6,因此sum11

function add(...rest) {
  return rest.reduce((total,current) => total + current);
};

console.log(add(1, 2)); // 3
console.log(add(1, 2, 3, 4, 5)); // 15

在本例中,咱們有一個add函數,它接受任意數量的參數,並將它們所有相加,而後返回總數。

const [first, ...others] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(others); // [2,3,4,5]

這裏,咱們使用剩餘操做符提取全部剩餘的數組值,並將它們放入除第一項以外的其餘數組中。

53. 什麼是默認參數?

默認參數是在 JS 中定義默認變量的一種新方法,它在ES6或ECMAScript 2015版本中可用。

//ES5 Version
function add(a,b){
  a = a || 0;
  b = b || 0;
  return a + b;
}

//ES6 Version
function add(a = 0, b = 0){
  return a + b;
}
add(1); // returns 1

咱們還能夠在默認參數中使用解構。

function getFirst([first, ...rest] = [0, 1]) {
  return first;
}

getFirst();  // 0
getFirst([10,20,30]);  // 10

function getArr({ nums } = { nums: [1, 2, 3, 4] }){
    return nums;
}

getArr(); // [1, 2, 3, 4]
getArr({nums:[5,4,3,2,1]}); // [5,4,3,2,1]

咱們還可使用先定義的參數再定義它們以後的參數。

function doSomethingWithValue(value = "Hello World", callback = () => { console.log(value) }) {
  callback();
}
doSomethingWithValue(); //"Hello World"

54. 什麼是包裝對象(wrapper object)?

咱們如今複習一下JS的數據類型,JS數據類型被分爲兩大類,基本類型引用類型

基本類型:Undefined,Null,Boolean,Number,String,Symbol,BigInt

引用類型:Object,Array,Date,RegExp等,說白了就是對象。

其中引用類型有方法和屬性,可是基本類型是沒有的,但咱們常常會看到下面的代碼:

let name = "marko";

console.log(typeof name); // "string"
console.log(name.toUpperCase()); // "MARKO"

name類型是 string,屬於基本類型,因此它沒有屬性和方法,可是在這個例子中,咱們調用了一個toUpperCase()方法,它不會拋出錯誤,還返回了對象的變量值。

緣由是基本類型的值被臨時轉換或強制轉換爲對象,所以name變量的行爲相似於對象。 除nullundefined以外的每一個基本類型都有本身包裝對象。也就是:StringNumberBooleanSymbolBigInt。 在這種狀況下,name.toUpperCase()在幕後看起來以下:

console.log(new String(name).toUpperCase()); // "MARKO"

在完成訪問屬性或調用方法以後,新建立的對象將當即被丟棄。

55. 隱式和顯式轉換有什麼區別)?

隱式強制轉換是一種將值轉換爲另外一種類型的方法,這個過程是自動完成的,無需咱們手動操做。

假設咱們下面有一個例子。

console.log(1 + '6'); // 16
console.log(false + true); // 1
console.log(6 * '2'); // 12

第一個console.log語句結果爲16。在其餘語言中,這會拋出編譯時錯誤,但在 JS 中,1被轉換成字符串,而後與+運算符鏈接。咱們沒有作任何事情,它是由 JS 自動完成。

第二個console.log語句結果爲1,JS 將false轉換爲boolean 值爲 0,,true1,所以結果爲1

第三個console.log語句結果12,它將'2'轉換爲一個數字,而後乘以6 * 2,結果是12。

而顯式強制是將值轉換爲另外一種類型的方法,咱們須要手動轉換。

console.log(1 + parseInt('6'));

在本例中,咱們使用parseInt函數將'6'轉換爲number ,而後使用+運算符將16相加。

56. 什麼是NaN? 以及如何檢查值是否爲NaN?

NaN表示「非數字」是 JS 中的一個值,該值是將數字轉換或執行爲非數字值的運算結果,所以結果爲NaN

let a;

console.log(parseInt('abc')); // NaN
console.log(parseInt(null)); // NaN
console.log(parseInt(undefined)); // NaN
console.log(parseInt(++a)); // NaN
console.log(parseInt({} * 10)); // NaN
console.log(parseInt('abc' - 2)); // NaN
console.log(parseInt(0 / 0)); // NaN
console.log(parseInt('10a' * 10)); // NaN

JS 有一個內置的isNaN方法,用於測試值是否爲isNaN值,可是這個函數有一個奇怪的行爲。

console.log(isNaN()); // true
console.log(isNaN(undefined)); // true
console.log(isNaN({})); // true
console.log(isNaN(String('a'))); // true
console.log(isNaN(() => { })); // true

全部這些console.log語句都返回true,即便咱們傳遞的值不是NaN

ES6中,建議使用Number.isNaN方法,由於它確實會檢查該值(若是確實是NaN),或者咱們可使本身的輔助函數檢查此問題,由於在 JS 中,NaN是惟一的值,它不等於本身。

function checkIfNaN(value) {
  return value !== value;
}

57. 如何判斷值是否爲數組?

咱們可使用Array.isArray方法來檢查值是否爲數組。 當傳遞給它的參數是數組時,它返回true,不然返回false

console.log(Array.isArray(5));  // false
console.log(Array.isArray("")); // false
console.log(Array.isArray()); // false
console.log(Array.isArray(null)); // false
console.log(Array.isArray({ length: 5 })); // false

console.log(Array.isArray([])); // true

若是環境不支持此方法,則可使用polyfill實現。

function isArray(value){
 return Object.prototype.toString.call(value) === "[object Array]"
}

固然還可使用傳統的方法:

let a = []
if (a instanceof Array) {
  console.log('是數組')
} else {
  console.log('非數組')
}

58. 如何在不使用%模運算符的狀況下檢查一個數字是不是偶數?

咱們能夠對這個問題使用按位&運算符,&對其操做數進行運算,並將其視爲二進制值,而後執行與運算。

function isEven(num) {
  if (num & 1) {
    return false
  } else {
    return true
  }
}

0 二進制數是 000
1 二進制數是 001
2 二進制數是 010
3 二進制數是 011
4 二進制數是 100
5 二進制數是 101
6 二進制數是 110
7 二進制數是 111

以此類推...

與運算的規則以下:

a b a & b
0 0 0
0 1 0
1 1 1

所以,當咱們執行console.log(5&1)這個表達式時,結果爲1。首先,&運算符將兩個數字都轉換爲二進制,所以5變爲1011變爲001

而後,它使用按位懷運算符比較每一個位(01)。 101&001,從表中能夠看出,若是a & b1,因此5&1結果爲1

101 & 001
101
001
001
  • 首先咱們比較最左邊的1&0,結果是0
  • 而後咱們比較中間的0&0,結果是0
  • 而後咱們比較最後1&1,結果是1
  • 最後,獲得一個二進制數001,對應的十進制數,即1

    由此咱們也能夠算出console.log(4 & 1) 結果爲0。知道4的最後一位是0,而0 & 1 將是0。若是你很難理解這一點,咱們可使用遞歸函數來解決此問題。

    function isEven(num) {

    if (num < 0 || num === 1) return false;
     if (num == 0) return true;
     return isEven(num - 2);

    }

59. 如何檢查對象中是否存在某個屬性?

檢查對象中是否存在屬性有三種方法。

第一種使用 in 操做符號:

const o = { 
  "prop" : "bwahahah",
  "prop2" : "hweasa"
};

console.log("prop" in o); // true
console.log("prop1" in o); // false

第二種使用 hasOwnProperty 方法,hasOwnProperty() 方法會返回一個布爾值,指示對象自身屬性中是否具備指定的屬性(也就是,是否有指定的鍵)。

console.log(o.hasOwnProperty("prop2")); // true
console.log(o.hasOwnProperty("prop1")); // false

第三種使用括號符號obj["prop"]。若是屬性存在,它將返回該屬性的值,不然將返回undefined

console.log(o["prop"]); // "bwahahah"
console.log(o["prop1"]); // undefined

60. AJAX 是什麼?

即異步的 JavaScript 和 XML,是一種用於建立快速動態網頁的技術,傳統的網頁(不使用 AJAX)若是須要更新內容,必需重載整個網頁面。使用AJAX則不須要加載更新整個網頁,實現部份內容更新

用到AJAX的技術:

  • HTML - 網頁結構
  • CSS - 網頁的樣式
  • JavaScript - 操做網頁的行爲和更新DOM
  • XMLHttpRequest API - 用於從服務器發送和獲取數據
  • PHP,Python,Nodejs - 某些服務器端語言

61. 如何在 JS 中建立對象?

使用對象字面量:

const o = {
  name: "前端小智",
  greeting() {
    return `Hi, 我是${this.name}`;
  }
};

o.greeting(); // "Hi, 我是前端小智"

使用構造函數:

function Person(name) {
   this.name = name;
}

Person.prototype.greeting = function () {
   return `Hi, 我是${this.name}`;
}

const mark = new Person("前端小智");

mark.greeting(); // "Hi, 我是前端小智"

使用 Object.create 方法:

const n = {
   greeting() {
      return `Hi, 我是${this.name}`;
   }
};

const o = Object.create(n); 
o.name = "前端小智";

62. Object.seal 和 Object.freeze 方法之間有什麼區別?

Object.freeze()

Object.freeze() 方法能夠凍結一個對象。一個被凍結的對象不再能被修改;凍結了一個對象則不能向這個對象添加新的屬性,不能刪除已有屬性,不能修改該對象已有屬性的可枚舉性、可配置性、可寫性,以及不能修改已有屬性的值。此外,凍結一個對象後該對象的原型也不能被修改。freeze() 返回和傳入的參數相同的對象。

Object.seal()

Object.seal()方法封閉一個對象,阻止添加新屬性並將全部現有屬性標記爲不可配置。當前屬性的值只要可寫就能夠改變。

方法的相同點:

  1. ES5新增。
  2. 對象不可能擴展,也就是不能再添加新的屬性或者方法。
  3. 對象已有屬性不容許被刪除。
  4. 對象屬性特性不能夠從新配置。

方法不一樣點:

  • Object.seal方法生成的密封對象,若是屬性是可寫的,那麼能夠修改屬性值。

* Object.freeze方法生成的凍結對象,屬性都是不可寫的,也就是屬性值沒法更改。

63. in 運算符和 Object.hasOwnProperty 方法有什麼區別?

hasOwnPropert方法

hasOwnPropert()方法返回值是一個布爾值,指示對象自身屬性中是否具備指定的屬性,所以這個方法會忽略掉那些從原型鏈上繼承到的屬性。

看下面的例子:

Object.prototype.phone= '15345025546';

let obj = {
    name: '前端小智',
    age: '28'
}
console.log(obj.hasOwnProperty('phone')) // false
console.log(obj.hasOwnProperty('name')) // true

能夠看到,若是在函數原型上定義一個變量phonehasOwnProperty方法會直接忽略掉。

in 運算符

若是指定的屬性在指定的對象或其原型鏈中,則in 運算符返回true

仍是用上面的例子來演示:

console.log('phone' in obj) // true

能夠看到in運算符會檢查它或者其原型鏈是否包含具備指定名稱的屬性。

64. 有哪些方法能夠處理 JS 中的異步代碼?

  • 回調
  • Promise
  • async/await
  • 還有一些庫: async.js, bluebird, q, co

65. 函數表達式和函數聲明之間有什麼區別?

看下面的例子:

hoistedFunc();
notHoistedFunc();

function hoistedFunc(){
  console.log("注意:我會被提高");
}

var notHoistedFunc = function(){
  console.log("注意:我沒有被提高");
}

notHoistedFunc調用拋出異常:Uncaught TypeError: notHoistedFunc is not a function,而hoistedFunc調用不會,由於hoistedFunc會被提高到做用域的頂部,而notHoistedFunc 不會。

66. 調用函數,可使用哪些方法?

在 JS 中有4種方法能夠調用函數。

做爲函數調用——若是一個函數沒有做爲方法、構造函數、applycall 調用時,此時 this 指向的是 window 對象(非嚴格模式)

//Global Scope

  function add(a,b){
    console.log(this);
    return a + b;
  }  

  add(1,5); // 打印 "window" 對象和 6

  const o = {
    method(callback){
      callback();
    }
  }

  o.method(function (){
      console.log(this); // 打印 "window" 對象
  });

做爲方法調用——若是一個對象的屬性有一個函數的值,咱們就稱它爲方法。調用該方法時,該方法的this值指向該對象。

const details = {
  name : "Marko",
  getName(){
    return this.name;
  }
}

details.getName(); // Marko

做爲構造函數的調用-若是在函數以前使用new關鍵字調用了函數,則該函數稱爲構造函數。構造函數裏面會默認建立一個空對象,並將this指向該對象。

function Employee(name, position, yearHired) {
  // 建立一個空對象 {}
  // 而後將空對象分配給「this」關鍵字
  // this = {};
  this.name = name;
  this.position = position;
  this.yearHired = yearHired;
  // 若是沒有指定 return ,這裏會默認返回 this
};

const emp = new Employee("Marko Polo", "Software Developer", 2017);

使用applycall方法調用——若是咱們想顯式地指定一個函數的this值,咱們可使用這些方法,這些方法對全部函數均可用。

const obj1 = {
 result:0
};

const obj2 = {
 result:0
};


function reduceAdd(){
   let result = 0;
   for(let i = 0, len = arguments.length; i < len; i++){
     result += arguments[i];
   }
   this.result = result;
}


reduceAdd.apply(obj1, [1, 2, 3, 4, 5]);  // reduceAdd 函數中的 this 對象將是 obj1
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // reduceAdd 函數中的 this 對象將是 obj2

67. 什麼是緩存及它有什麼做用?

緩存是創建一個函數的過程,這個函數可以記住以前計算的結果或值。使用緩存函數是爲了不在最後一次使用相同參數的計算中已經執行的函數的計算。這節省了時間,但也有不利的一面,即咱們將消耗更多的內存來保存之前的結果。

68. 手動實現緩存方法]

function memoize(fn) {
  const cache = {};
  return function (param) {
    if (cache[param]) {
      console.log('cached');
      return cache[param];
    } else {
      let result = fn(param);
      cache[param] = result;
      console.log(`not cached`);
      return result;
    }
  }
}

const toUpper = (str ="")=> str.toUpperCase();

const toUpperMemoized = memoize(toUpper);

toUpperMemoized("abcdef");
toUpperMemoized("abcdef");

這個緩存函數適用於接受一個參數。 咱們須要改變下,讓它接受多個參數。

const slice = Array.prototype.slice;
function memoize(fn) {
  const cache = {};
  return (...args) => {
    const params = slice.call(args);
    console.log(params);
    if (cache[params]) {
      console.log('cached');
      return cache[params];
    } else {
      let result = fn(...args);
      cache[params] = result;
      console.log(`not cached`);
      return result;
    }
  }
}
const makeFullName = (fName, lName) => `${fName} ${lName}`;
const reduceAdd = (numbers, startingValue = 0) => numbers.reduce((total, cur) => total + cur, startingValue);

const memoizedMakeFullName = memoize(makeFullName);
const memoizedReduceAdd = memoize(reduceAdd);

memoizedMakeFullName("Marko", "Polo");
memoizedMakeFullName("Marko", "Polo");

memoizedReduceAdd([1, 2, 3, 4, 5], 5);
memoizedReduceAdd([1, 2, 3, 4, 5], 5);

69. 爲何typeof null 返回 object? 如何檢查一個值是否爲 null?

typeof null == 'object'老是返回true,由於這是自 JS 誕生以來null的實現。曾經有人提出將typeof null == 'object'修改成typeof null == 'null',可是被拒絕了,由於這將致使更多的bug

咱們可使用嚴格相等運算符===來檢查值是否爲null

function isNull(value){
  return value === null;
}

70. new 關鍵字有什麼做用?

new關鍵字與構造函數一塊兒使用以建立對象:

function Employee(name, position, yearHired) {
  this.name = name;
  this.position = position;
  this.yearHired = yearHired;
};

const emp = new Employee("Marko Polo", "Software Developer", 2017);

new關鍵字作了4件事:

  • 建立空對象 {}
  • 將空對象分配給 this
  • 將空對象的__proto__指向構造函數的prototype
  • 若是沒有使用顯式return語句,則返回this

看下面事例:

function Person() {
this.name = '前端小智'
}

根據上面描述的,new Person()作了:

  • 建立一個空對象:var obj = {}
  • 將空對象分配給 this 值:this = obj
  • 將空對象的__proto__指向構造函數的prototype:this.__proto__ = Person().prototype
  • 返回this:return this

71. 何時不使用箭頭函數? 說出三個或更多的例子?

不該該使用箭頭函數一些狀況:

  • 當想要函數被提高時(箭頭函數是匿名的)
  • 要在函數中使用this/arguments時,因爲箭頭函數自己不具備this/arguments,所以它們取決於外部上下文
  • 使用命名函數(箭頭函數是匿名的)
  • 使用函數做爲構造函數時(箭頭函數沒有構造函數)
  • 當想在對象字面是以將函數做爲屬性添加並在其中使用對象時,由於我們沒法訪問 this 即對象自己。

72. Object.freeze() 和 const 的區別是什麼?]

constObject.freeze是兩個徹底不一樣的概念。

const 聲明一個只讀的變量,一旦聲明,常量的值就不可改變:

const person = {
    name: "Leonardo"
};
let animal = {
    species: "snake"
};
person = animal; // ERROR "person" is read-only

Object.freeze適用於值,更具體地說,適用於對象值,它使對象不可變,即不能更改其屬性。

let person = {
    name: "Leonardo"
};
let animal = {
    species: "snake"
};
Object.freeze(person);
person.name = "Lima"; //TypeError: Cannot assign to read only property 'name' of object
console.log(person);

73. 如何在 JS 中「深凍結」對象?

若是我們想要確保對象被深凍結,就必須建立一個遞歸函數來凍結對象類型的每一個屬性:

沒有深凍結

let person = {
    name: "Leonardo",
    profession: {
        name: "developer"
    }
};
Object.freeze(person); 
person.profession.name = "doctor";
console.log(person); //output { name: 'Leonardo', profession: { name: 'doctor' } }

深凍結

function deepFreeze(object) {
    let propNames = Object.getOwnPropertyNames(object);
    for (let name of propNames) {
        let value = object[name];
        object[name] = value && typeof value === "object" ?
            deepFreeze(value) : value;
    }
    return Object.freeze(object);
}
let person = {
    name: "Leonardo",
    profession: {
        name: "developer"
    }
};
deepFreeze(person);
person.profession.name = "doctor"; // TypeError: Cannot assign to read only property 'name' of object

74. Iterator是什麼,有什麼做用?

遍歷器(Iterator)就是這樣一種機制。它是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署Iterator接口,就能夠完成遍歷操做(即依次處理該數據結構的全部成員)。

Iterator 的做用有三個:

  1. 爲各類數據結構,提供一個統一的、簡便的訪問接口;
  2. 使得數據結構的成員可以按某種次序排列;
  3. ES6 創造了一種新的遍歷命令for...of循環,Iterator 接口主要供for...of消費。

遍歷過程:

  1. 建立一個指針對象,指向當前數據結構的起始位置。也就是說,遍歷器對象本質上,就是一個指針對象。
  2. 第一次調用指針對象的next方法,能夠將指針指向數據結構的第一個成員。
  3. 第二次調用指針對象的next方法,指針就指向數據結構的第二個成員。
  4. 不斷調用指針對象的next方法,直到它指向數據結構的結束位置。

每一次調用next方法,都會返回數據結構的當前成員的信息。具體來講,就是返回一個包含valuedone兩個屬性的對象。其中,value屬性是當前成員的值,done屬性是一個布爾值,表示遍歷是否結束。

//obj就是可遍歷的,由於它遵循了Iterator標準,且包含[Symbol.iterator]方法,方法函數也符合標準的Iterator接口規範。
//obj.[Symbol.iterator]() 就是Iterator遍歷器
let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};

75. Generator 函數是什麼,有什麼做用?

若是說 JavaScrip 是 ECMAScript 標準的一種具體實現、Iterator遍歷器是Iterator的具體實現,那麼Generator函數能夠說是Iterator接口的具體實現方式。

執行Generator函數會返回一個遍歷器對象,每一次Generator函數裏面的yield都至關一次遍歷器對象的next()方法,而且能夠經過next(value)方法傳入自定義的value,來改變Generator函數的行爲。

Generator函數能夠經過配合Thunk 函數更輕鬆更優雅的實現異步編程和控制流管理。


代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

原文:

https://dev.to/macmacky/70-ja...


交流

文章每週持續更新,能夠微信搜索「 大遷世界 」第一時間閱讀和催更(比博客早一到兩篇喲),本文 GitHub https://github.com/qq44924588... 已經收錄,整理了不少個人文檔,歡迎Star和完善,你們面試能夠參照考點複習,另外關注公衆號,後臺回覆福利,便可看到福利,你懂的。

clipboard.png

相關文章
相關標籤/搜索