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

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

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

服務器如何搭建博客html

考題列表

1.undefined 和 null 有什麼區別?

在理解undefinednull之間的差別以前,咱們先來看看它們的類似類。前端

它們屬於 JavaScript 的 7 種基本類型。java

let primitiveTypes = ['string','number','null','undefined','boolean','symbol', 'bigint'];

它們是屬於虛值,可使用Boolean(value)!!value將其轉換爲布爾值時,值爲falsereact

console.log(!!null); // false
console.log(!!undefined); // false

console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false

接着來看看它們的區別。git

undefined是未指定特定值的變量的默認值,或者沒有顯式返回值的函數,如:console.log(1),還包括對象中不存在的屬性,這些 JS 引擎都會爲其分配 undefined 值。github

let _thisIsUndefined;
const doNothing = () => {};
const someObj = {
  a : "ay",
  b : "bee",
  c : "si"
};

console.log(_thisIsUndefined); // undefined
console.log(doNothing()); // undefined
console.log(someObj["d"]); // undefined

null「不表明任何值的值」null是已明肯定義給變量的值。 在此示例中,當fs.readFile方法未引起錯誤時,咱們將得到null值。面試

fs.readFile('path/to/file', (e,data) => {
   console.log(e); // 當沒有錯誤發生時,打印 null
   if(e){
     console.log(e);
   }
   console.log(data);
 });

在比較nullundefined時,咱們使用==時獲得true,使用===時獲得false:算法

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

2. && 運算符能作什麼

&& 也能夠叫邏輯與,在其操做數中找到第一個虛值表達式並返回它,若是沒有找到任何虛值表達式,則返回最後一個真值表達式。它採用短路來防止沒必要要的工做。編程

console.log(false && 1 && []); // false
console.log(" " && true && 5); // 5

使用if語句

const router: Router = Router();

router.get('/endpoint', (req: Request, res: Response) => {
   let conMobile: PoolConnection;
   try {
      //do some db operations
   } catch (e) {
   if (conMobile) {
    conMobile.release();
   }
  }
});

使用&&操做符

const router: Router = Router();

router.get('/endpoint', (req: Request, res: Response) => {
  let conMobile: PoolConnection;
  try {
     //do some db operations
  } catch (e) {
    conMobile && conMobile.release()
  }
});

3. || 運算符能作什麼

||也叫或邏輯或,在其操做數中找到第一個真值表達式並返回它。這也使用了短路來防止沒必要要的工做。在支持 ES6 默認函數參數以前,它用於初始化函數中的默認參數值。

console.log(null || 1 || undefined); // 1

function logName(name) {
  var n = name || "Mark";
  console.log(n);
}

logName(); // "Mark"

4. 使用 + 或一元加運算符是將字符串轉換爲數字的最快方法嗎?

根據MDN文檔+是將字符串轉換爲數字的最快方法,由於若是值已是數字,它不會執行任何操做。

5. DOM 是什麼?

DOM 表明文檔對象模型,是 HTML 和 XML 文檔的接口(API)。當瀏覽器第一次讀取(解析)HTML文檔時,它會建立一個大對象,一個基於 HTM L文檔的很是大的對象,這就是DOM。它是一個從 HTML 文檔中建模的樹狀結構。DOM 用於交互和修改DOM結構或特定元素或節點。

假設咱們有這樣的 HTML 結構:

<!DOCTYPE html>
<html lang="en">

<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <meta http-equiv="X-UA-Compatible" content="ie=edge">
   <title>Document Object Model</title>
</head>

<body>
   <div>
      <p>
         <span></span>
      </p>
      <label></label>
      <input>
   </div>
</body>

</html>

等價的DOM是這樣的:

clipboard.png

JS 中的document對象表示DOM。它爲咱們提供了許多方法,咱們可使用這些方法來選擇元素來更新元素內容,等等。

6. 什麼是事件傳播?

事件發生在DOM元素上時,該事件並不徹底發生在那個元素上。 在「冒泡階段」中,事件冒泡或向上傳播至父級,祖父母,祖父母或父級,直到到達window爲止;而在「捕獲階段」中,事件從window開始向下觸發元素 事件或event.target

事件傳播有三個階段:

  1. 捕獲階段–事件從 window 開始,而後向下到每一個元素,直到到達目標元素。
  2. 目標階段–事件已達到目標元素。
  3. 冒泡階段–事件從目標元素冒泡,而後上升到每一個元素,直到到達 window

clipboard.png

7. 什麼是事件冒泡?

事件發生在DOM元素上時,該事件並不徹底發生在那個元素上。 在冒泡階段,事件冒泡,或者事件發生在它的父代,祖父母,祖父母的父代,直到到達window爲止。

假設有以下的 HTML 結構:

<div class="grandparent">
  <div class="parent">
    <div class="child">1</div>
  </div>
</div>

對應的 JS 代碼:

function addEvent(el, event, callback, isCapture = false) {
  if (!el || !event || !callback || typeof callback !== 'function') return;
  if (typeof el === 'string') {
    el = document.querySelector(el);
  };
  el.addEventListener(event, callback, isCapture);
}

addEvent(document, 'DOMContentLoaded', () => {
  const child = document.querySelector('.child');
  const parent = document.querySelector('.parent');
  const grandparent = document.querySelector('.grandparent');

  addEvent(child, 'click', function (e) {
    console.log('child');
  });

  addEvent(parent, 'click', function (e) {
    console.log('parent');
  });

  addEvent(grandparent, 'click', function (e) {
    console.log('grandparent');
  });

  addEvent(document, 'click', function (e) {
    console.log('document');
  });

  addEvent('html', 'click', function (e) {
    console.log('html');
  })

  addEvent(window, 'click', function (e) {
    console.log('window');
  })

});

addEventListener方法具備第三個可選參數useCapture,其默認值爲false,事件將在冒泡階段中發生,若是爲true,則事件將在捕獲階段中發生。 若是單擊child元素,它將分別在控制檯上記錄childparentgrandparenthtmldocumentwindow,這就是事件冒泡。

8. 什麼是事件捕獲?

當事件發生在 DOM 元素上時,該事件並不徹底發生在那個元素上。在捕獲階段,事件從window開始,一直到觸發事件的元素。

假設有以下的 HTML 結構:

<div class="grandparent">
  <div class="parent">
    <div class="child">1</div>
  </div>
</div>

對應的 JS 代碼:

function addEvent(el, event, callback, isCapture = false) {
  if (!el || !event || !callback || typeof callback !== 'function') return;
  if (typeof el === 'string') {
    el = document.querySelector(el);
  };
  el.addEventListener(event, callback, isCapture);
}

addEvent(document, 'DOMContentLoaded', () => {
  const child = document.querySelector('.child');
  const parent = document.querySelector('.parent');
  const grandparent = document.querySelector('.grandparent');

  addEvent(child, 'click', function (e) {
    console.log('child');
  });

  addEvent(parent, 'click', function (e) {
    console.log('parent');
  });

  addEvent(grandparent, 'click', function (e) {
    console.log('grandparent');
  });

  addEvent(document, 'click', function (e) {
    console.log('document');
  });

  addEvent('html', 'click', function (e) {
    console.log('html');
  })

  addEvent(window, 'click', function (e) {
    console.log('window');
  })

});

addEventListener方法具備第三個可選參數useCapture,其默認值爲false,事件將在冒泡階段中發生,若是爲true,則事件將在捕獲階段中發生。 若是單擊child元素,它將分別在控制檯上打印windowdocumenthtmlgrandparentparent,這就是事件捕獲

9. event.preventDefault() 和 event.stopPropagation()方法之間有什麼區別?

event.preventDefault() 方法可防止元素的默認行爲。 若是在表單元素中使用,它將阻止其提交。 若是在錨元素中使用,它將阻止其導航。 若是在上下文菜單中使用,它將阻止其顯示或顯示。 event.stopPropagation()方法用於阻止捕獲和冒泡階段中當前事件的進一步傳播。

10. 如何知道是否在元素中使用了event.preventDefault()方法?

咱們能夠在事件對象中使用event.defaultPrevented屬性。 它返回一個布爾值用來代表是否在特定元素中調用了event.preventDefault()

11. 爲何此代碼 obj.someprop.x 會引起錯誤?

const obj = {};
console.log(obj.someprop.x);

顯然,因爲咱們嘗試訪問someprop屬性中的x屬性,而 someprop 並無在對象中,因此值爲 undefined。 記住對象自己不存在的屬性,而且其原型的默認值爲undefined。由於undefined沒有屬性x,因此試圖訪問將會報錯。

12. 什麼是 event.target ?

簡單來講,event.target是發生事件的元素或觸發事件的元素。

假設有以下的 HTML 結構:

<div onclick="clickFunc(event)" style="text-align: center;margin:15px;
border:1px solid red;border-radius:3px;">
    <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;">
        <div style="margin:25px;border:1px solid skyblue;border-radius:3px;">
          <button style="margin:10px">
             Button
          </button>
        </div>
    </div>
 </div>

JS 代碼以下:

function clickFunc(event) {
  console.log(event.target);
}

若是單擊 button,即便咱們將事件附加在最外面的div上,它也將打印 button 標籤,所以咱們能夠得出結論event.target是觸發事件的元素。

13. 什麼是 event.currentTarget??

event.currentTarget是咱們在其上顯式附加事件處理程序的元素。

假設有以下的 HTML 結構:

<div onclick="clickFunc(event)" style="text-align: center;margin:15px;
border:1px solid red;border-radius:3px;">
    <div style="margin: 25px; border:1px solid royalblue;border-radius:3px;">
        <div style="margin:25px;border:1px solid skyblue;border-radius:3px;">
          <button style="margin:10px">
             Button
          </button>
        </div>
    </div>
 </div>

JS 代碼以下:

function clickFunc(event) {
  console.log(event.currentTarget);
}

若是單擊 button,即便咱們單擊該 button,它也會打印最外面的div標籤。 在此示例中,咱們能夠得出結論,event.currentTarget是附加事件處理程序的元素。

14. == 和 === 有什麼區別?

==用於通常比較,===用於嚴格比較,==在比較的時候能夠轉換數據類型,===嚴格比較,只要類型不匹配就返回flase

先來看看 == 這兄弟:

強制是將值轉換爲另外一種類型的過程。 在這種狀況下,==會執行隱式強制。 在比較兩個值以前,==須要執行一些規則。

假設咱們要比較x == y的值。

  1. 若是xy的類型相同,則 JS 會換成===操做符進行比較。
  2. 若是xnull, yundefined,則返回true
  3. 若是xundefinedynull,則返回true
  4. 若是x的類型是number, y的類型是string,那麼返回x == toNumber(y)
  5. 若是x的類型是string, y的類型是number,那麼返回toNumber(x) == y
  6. 若是x爲類型是boolean,則返回toNumber(x)== y
  7. 若是y爲類型是boolean,則返回x == toNumber(y)
  8. 若是xstringsymbolnumber,而yobject類型,則返回x == toPrimitive(y)
  9. 若是xobjectystringsymbol 則返回toPrimitive(x) == y
  10. 剩下的 返回 false

注意:toPrimitive首先在對象中使用valueOf方法,而後使用toString方法來獲取該對象的原始值。

舉個例子。

x y x == y
5 5 true
1 '1' true
null undefined true
0 false true
'1,2' [1,2] true
'[object Object]' {} true

這些例子都返回true

第一個示例符合條件1,由於xy具備相同的類型和值。

第二個示例符合條件4,在比較以前將y轉換爲數字。

第三個例子符合條件2

第四個例子符合條件7,由於yboolean類型。

第五個示例符合條件8。 使用toString()方法將數組轉換爲字符串,該方法返回1,2

最後一個示例符合條件8。 使用toString()方法將對象轉換爲字符串,該方法返回[object Object]

x y x === y
5 5 true
1 '1' false
null undefined false
0 false false
'1,2' [1,2] false
'[object Object]' {} false

若是使用===運算符,則第一個示例之外的全部比較將返回false,由於它們的類型不一樣,而第一個示例將返回true,由於二者的類型和值相同。

具體更多規則能夠對參考我以前的文章:

我對 JS 中相等和全等操做符轉化過程一直很迷惑,直到有了這份算法

15. 爲何在 JS 中比較兩個類似的對象時返回 false?

先看下面的例子:

let a = { a: 1 };
let b = { a: 1 };
let c = a;

console.log(a === b); // 打印 false,即便它們有相同的屬性
console.log(a === c); // true

JS 以不一樣的方式比較對象和基本類型。在基本類型中,JS 經過值對它們進行比較,而在對象中,JS 經過引用或存儲變量的內存中的地址對它們進行比較。這就是爲何第一個console.log語句返回false,而第二個console.log語句返回trueac有相同的引用地址,而ab沒有。

16. !! 運算符能作什麼?

!!運算符能夠將右側的值強制轉換爲布爾值,這也是將值轉換爲布爾值的一種簡單方法。

console.log(!!null); // false
console.log(!!undefined); // false
console.log(!!''); // false
console.log(!!0); // false
console.log(!!NaN); // false
console.log(!!' '); // true
console.log(!!{}); // true
console.log(!![]); // true
console.log(!!1); // true
console.log(!![].length); // false

17. 如何在一行中計算多個表達式的值?

可使用逗號運算符在一行中計算多個表達式。 它從左到右求值,並返回右邊最後一個項目或最後一個操做數的值。

let x = 5;

x = (x++ , x = addFive(x), x *= 2, x -= 5, x += 10);

function addFive(num) {
  return num + 5;
}

上面的結果最後獲得x的值爲27。首先,咱們將x的值增長到6,而後調用函數addFive(6)並將6做爲參數傳遞並將結果從新分配給x,此時x的值爲11。以後,將x的當前值乘以2並將其分配給xx的更新值爲22。而後,將x的當前值減去5並將結果分配給x x更新後的值爲17。最後,咱們將x的值增長10,而後將更新的值分配給x,最終x的值爲27

18. 什麼是提高?

提高是用來描述變量和函數移動到其(全局或函數)做用域頂部的術語。

爲了理解提高,須要來了解一下執行上下文執行上下文是當前正在執行的「代碼環境」。執行上下文有兩個階段:編譯執行

編譯-在此階段,JS 引薦獲取全部函數聲明並將其提高到其做用域的頂部,以便咱們稍後能夠引用它們並獲取全部變量聲明(使用var關鍵字進行聲明),還會爲它們提供默認值: undefined

執行——在這個階段中,它將值賦給以前提高的變量,並執行或調用函數(對象中的方法)。

注意:只有使用var聲明的變量,或者函數聲明纔會被提高,相反,函數表達式或箭頭函數,letconst聲明的變量,這些都不會被提高。

假設在全局使用域,有以下的代碼:

console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));

function greet(name){
  return 'Hello ' + name + '!';
}

var y;

上面分別打印:undefined,1, Hello Mark!

上面代碼在編譯階段實際上是這樣的:

function greet(name) {
  return 'Hello ' + name + '!';
}

var y; // 默認值 undefined

// 等待「編譯」階段完成,而後開始「執行」階段

/*
console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));
*/

編譯階段完成後,它將啓動執行階段調用方法,並將值分配給變量。

function greet(name) {
  return 'Hello ' + name + '!';
}

var y;

//start "execution" phase

console.log(y);
y = 1;
console.log(y);
console.log(greet("Mark"));

19. 什麼是做用域?

JavaScript 中的做用域是咱們能夠有效訪問變量或函數的區域。JS 有三種類型的做用域:全局做用域函數做用域塊做用域(ES6)

  • 全局做用域——在全局命名空間中聲明的變量或函數位於全局做用域中,所以在代碼中的任何地方均可以訪問它們。
//global namespace
var g = "global";

function globalFunc(){
  function innerFunc(){
    console.log(g); // can access "g" because "g" is a global variable
  }
 innerFunc();
}
  • 函數做用域——在函數中聲明的變量、函數和參數能夠在函數內部訪問,但不能在函數外部訪問。
function myFavoriteFunc(a) {
  if (true) {
    var b = "Hello " + a;
  }
  return b;
}

myFavoriteFunc("World");

console.log(a); // Throws a ReferenceError "a" is not defined
console.log(b); // does not continue here
  • 塊做用域-在塊{}中聲明的變量(let,const)只能在其中訪問。
function testBlock(){
   if(true){
     let z = 5;
   }
   return z; 
 }

 testBlock(); // Throws a ReferenceError "z" is not defined

做用域也是一組用於查找變量的規則。 若是變量在當前做用域中不存在,它將向外部做用域中查找並搜索,若是該變量不存在,它將再次查找直到到達全局做用域,若是找到,則可使用它,不然引起錯誤,這種查找過程也稱爲做用域鏈

/* 做用域鏈
     
     內部做用域->外部做用域-> 全局做用域
  */

  // 全局做用域
  var variable1 = "Comrades";   
  var variable2 = "Sayonara";

  function outer(){
  // 外部做用域
    var variable1 = "World";
    function inner(){
    // 內部做用域
      var variable2 = "Hello";
      console.log(variable2 + " " + variable1);
    }
    inner();
  }  
  outer(); // Hello World

clipboard.png

20. 什麼是閉包?

這多是全部問題中最難的一個問題,由於閉包是一個有爭議的話題,這裏從我的角度來談談,若是不妥,多多海涵。

閉包就是一個函數在聲明時可以記住當前做用域、父函數做用域、及父函數做用域上的變量和參數的引用,直至經過做用域鏈上全局做用域,基本上閉包是在聲明函數時建立的做用域。

看看小例子:

// 全局做用域
   var globalVar = "abc";

   function a(){
     console.log(globalVar);
   }

   a(); // "abc"

在此示例中,當咱們聲明a函數時,全局做用域是a閉包的一部分。

clipboard.png

變量globalVar在圖中沒有值的緣由是該變量的值能夠根據調用函數a的位置和時間而改變。可是在上面的示例中,globalVar變量的值爲abc

來看一個更復雜的例子:

var globalVar = "global";
var outerVar = "outer"

function outerFunc(outerParam) {
  function innerFunc(innerParam) {
    console.log(globalVar, outerParam, innerParam);
  }
  return innerFunc;
}

const x = outerFunc(outerVar);
outerVar = "outer-2";
globalVar = "guess"
x("inner");

clipboard.png

上面打印結果是 guess outer inner

當咱們調用outerFunc函數並將返回值innerFunc函數分配給變量x時,即便咱們爲outerVar變量分配了新值outer-2outerParam也繼續保留outer值,由於從新分配是在調用outerFunc以後發生的,而且當咱們調用outerFunc函數時,它會在做用域鏈中查找outerVar的值,此時的outerVar的值將爲 "outer"

如今,當咱們調用引用了innerFuncx變量時,innerParam將具備一個inner值,由於這是咱們在調用中傳遞的值,而globalVar變量值爲guess,由於在調用x變量以前,咱們將一個新值分配給globalVar

下面這個示例演示沒有理解好閉包所犯的錯誤:

const arrFuncs = [];
for(var i = 0; i < 5; i++){
  arrFuncs.push(function (){
    return i;
  });
}
console.log(i); // i is 5

for (let i = 0; i < arrFuncs.length; i++) {
  console.log(arrFuncs[i]()); // 都打印 5
}

因爲閉包,此代碼沒法正常運行。var關鍵字建立一個全局變量,當咱們 push 一個函數時,這裏返回的全局變量i。 所以,當咱們在循環後在該數組中調用其中一個函數時,它會打印5,由於咱們獲得i的當前值爲5,咱們能夠訪問它,由於它是全局變量。

由於閉包在建立變量時會保留該變量的引用而不是其值。 咱們可使用IIFES或使用 let 來代替 var 的聲明。

21. JavaScript 中的虛值是什麼?

const falsyValues = ['', 0, null, undefined, NaN, false];

簡單的來講虛值就是是在轉換爲布爾值時變爲 false 的值。

22. 如何檢查值是否虛值?

使用 Boolean 函數或者 !! 運算符。

23. 'use strict' 是幹嗎用的?

"use strict"ES5 特性,它使咱們的代碼在函數或整個腳本中處於嚴格模式嚴格模式幫助咱們在代碼的早期避免 bug,併爲其添加限制。

嚴格模式的一些限制:

  1. 變量必須聲明後再使用
  2. 函數的參數不能有同名屬性,不然報錯
  3. 不能使用with語句
  4. 不能對只讀屬性賦值,不然報錯
  5. 不能使用前綴 0 表示八進制數,不然報錯
  6. 不能刪除不可刪除的屬性,不然報錯
  7. 不能刪除變量delete prop,會報錯,只能刪除屬性delete global[prop]
  8. eval不能在它的外層做用域引入變量
  9. evalarguments不能被從新賦值
  10. arguments不會自動反映函數參數的變化
  11. 不能使用arguments.callee
  12. 不能使用arguments.caller
  13. 禁止this指向全局對象
  14. 不能使用fn.callerfn.arguments獲取函數調用的堆棧
  15. 增長了保留字(好比protectedstaticinterface

設立」嚴格模式」的目的,主要有如下幾個:

  1. 消除Javascript語法的一些不合理、不嚴謹之處,減小一些怪異行爲;
  2. 消除代碼運行的一些不安全之處,保證代碼運行的安全;
  3. 提升編譯器效率,增長運行速度;
  4. 爲將來新版本的Javascript作好鋪墊。

24. JavaScript 中 this 值是什麼?

基本上,this指的是當前正在執行或調用該函數的對象的值。this值的變化取決於咱們使用它的上下文和咱們在哪裏使用它。

const carDetails = {
  name: "Ford Mustang",
  yearBought: 2005,
  getName(){
    return this.name;
  },
  isRegistered: true
};

console.log(carDetails.getName()); // Ford Mustang

這一般是咱們指望結果的,由於在getName方法中咱們返回this.name,在此上下文中,this指向的是carDetails對象,該對象當前是執行函數的「全部者」對象。

接下咱們作些奇怪的事情:

var name = "Ford Ranger";
var getCarName = carDetails.getName;

console.log(getCarName()); // Ford Ranger

上面打印Ford Ranger,這很奇怪,由於在第一個console.log語句中打印的是Ford Mustang。這樣作的緣由是getCarName方法有一個不一樣的「全部者」對象,即window對象。在全局做用域中使用var關鍵字聲明變量會在window對象中附加與變量名稱相同的屬性。請記住,當沒有使用「use strict」時,在全局做用域中this指的是window對象。

console.log(getCarName === window.getCarName); // true
console.log(getCarName === this.getCarName); // true

本例中的thiswindow引用同一個對象。

解決這個問題的一種方法是在函數中使用applycall方法。

console.log(getCarName.apply(carDetails)); // Ford Mustang
console.log(getCarName.call(carDetails));  // Ford Mustang

applycall方法指望第一個參數是一個對象,該對象是函數內部this的值。

IIFE當即執行的函數表達式,在全局做用域內聲明的函數,對象內部方法中的匿名函數和內部函數的this具備默認值,該值指向window對象。

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

   function iHateThis(){
      console.log(this);
   }

   iHateThis(); // 打印 "window" 對象

   const myFavoriteObj = {
     guessThis(){
        function getName(){
          console.log(this.name);
        }
        getName();
     },
     name: 'Marko Polo',
     thisIsAnnoying(callback){
       callback();
     }
   };


   myFavoriteObj.guessThis(); // 打印 "window" 對象
   myFavoriteObj.thisIsAnnoying(function (){
     console.log(this); // 打印 "window" 對象
   });

若是咱們要獲取myFavoriteObj對象中的name屬性(即Marko Polo)的值,則有兩種方法能夠解決此問題。

一種是將 this 值保存在變量中。

const myFavoriteObj = {
 guessThis(){
  const self = this; // 把 this 值保存在 self 變量中
  function getName(){
    console.log(self.name);
  }
  getName();
 },
 name: 'Marko Polo',
 thisIsAnnoying(callback){
   callback();
  }
};

第二種方式是使用箭頭函數

const myFavoriteObj = {
  guessThis(){
     const getName = () => { 
       console.log(this.name);
     }
     getName();
  },
  name: 'Marko Polo',
  thisIsAnnoying(callback){
   callback();
  }
};

箭頭函數沒有本身的 this。它複製了這個封閉的詞法做用域中this值,在這個例子中,this值在getName內部函數以外,也就是myFavoriteObj對象。

25. 對象的 prototype(原型) 是什麼?

簡單地說,原型就是對象的藍圖。若是它存在當前對象中,則將其用做屬性和方法的回退。它是在對象之間共享屬性和功能的方法,這也是JavaScript實現繼承的核心。

const o = {};
console.log(o.toString()); // logs [object Object]

即便o對象中不存在o.toString方法,它也不會引起錯誤,而是返回字符串[object Object]。 當對象中不存在屬性時,它將查看其原型,若是仍然不存在,則將其查找到原型的原型,依此類推,直到在原型鏈中找到具備相同屬性的屬性爲止。 原型鏈的末尾是Object.prototype

console.log(o.toString === Object.prototype.toString); // logs true

26. 什麼是 IIFE,它的用途是什麼?

IIFE或當即調用的函數表達式是在建立或聲明後將被調用或執行的函數。 建立IIFE的語法是,將function (){}包裹在在括號()內,而後再用另外一個括號()調用它,如:(function(){})()

(function(){
  ...
} ());

(function () {
  ...
})();

(function named(params) {
  ...
})();

(() => {

});

(function (global) {
  ...
})(window);

const utility = (function () {
  return {
    ...
  }
})

這些示例都是有效的IIFE。 倒數第二個救命代表咱們能夠將參數傳遞給IIFE函數。 最後一個示例代表,咱們能夠將IIFE的結果保存到變量中,以便稍後使用。

IIFE的一個主要做用是避免與全局做用域內的其餘變量命名衝突或污染全局命名空間,來個例子。

<script src="https://cdnurl.com/somelibrary.js"></script>

假設咱們引入了一個omelibr.js的連接,它提供了一些咱們在代碼中使用的全局函數,可是這個庫有兩個方法咱們沒有使用:createGraphdrawGraph,由於這些方法都有bug。咱們想實現本身的createGraphdrawGraph方法。

解決此問題的一種方法是直接覆蓋:

<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
   function createGraph() {
      // createGraph logic here
   }
   function drawGraph() {
      // drawGraph logic here
   }
</script>

當咱們使用這個解決方案時,咱們覆蓋了庫提供給咱們的那兩個方法。

另外一種方式是咱們本身更名稱:

<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
   function myCreateGraph() {
      // createGraph logic here
   }
   function myDrawGraph() {
      // drawGraph logic here
   }
</script>

當咱們使用這個解決方案時,咱們把那些函數調用更改成新的函數名。

還有一種方法就是使用IIFE

<script src="https://cdnurl.com/somelibrary.js"></script>
<script>
   const graphUtility = (function () {
      function createGraph() {
         // createGraph logic here
      }
      function drawGraph() {
         // drawGraph logic here
      }
      return {
         createGraph,
         drawGraph
      }
   })
</script>

在此解決方案中,咱們要聲明瞭graphUtility 變量,用來保存IIFE執行的結果,該函數返回一個包含兩個方法createGraphdrawGraph的對象。

IIFE 還能夠用來解決一個常見的面試題:

var li = document.querySelectorAll('.list-group > li');
for (var i = 0, len = li.length; i < len; i++) {
   li[i].addEventListener('click', function (e) {
      console.log(i);
   })

假設咱們有一個帶有list-group類的ul元素,它有5li子元素。 當咱們單擊單個li元素時,打印對應的下標值。但在此外上述代碼不起做用,這裏每次點擊 li 打印 i 的值都是5,這是因爲閉包的緣由。

閉包只是函數記住其當前做用域,父函數做用域和全局做用域的變量引用的能力。 當咱們在全局做用域內使用var關鍵字聲明變量時,就建立全局變量i。 所以,當咱們單擊li元素時,它將打印5,由於這是稍後在回調函數中引用它時i的值。

使用 IIFE 能夠解決此問題:

var li = document.querySelectorAll('.list-group > li');
for (var i = 0, len = li.length; i < len; i++) {
   (function (currentIndex) {
      li[currentIndex].addEventListener('click', function (e) {
         console.log(currentIndex);
      })
   })(i);
}

該解決方案之因此行的通,是由於IIFE會爲每次迭代建立一個新的做用域,咱們捕獲i的值並將其傳遞給currentIndex參數,所以調用IIFE時,每次迭代的currentIndex值都是不一樣的。

27. Function.prototype.apply 方法的用途是什麼?

apply() 方法調用一個具備給定this值的函數,以及做爲一個數組(或相似數組對象)提供的參數。

const details = {
  message: 'Hello World!'
};

function getMessage(){
  return this.message;
}

getMessage.apply(details); // 'Hello World!'
call()方法的做用和 apply() 方法相似,區別就是 call()方法接受的是參數列表,而 apply()方法接受的是一個參數數組。
const person = {
  name: "Marko Polo"
};

function greeting(greetingMessage) {
  return `${greetingMessage} ${this.name}`;
}

greeting.apply(person, ['Hello']); // "Hello Marko Polo!"

28. Function.prototype.call 方法的用途是什麼?

call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。

const details = {
  message: 'Hello World!'
};

function getMessage(){
  return this.message;
}

getMessage.call(details); // 'Hello World!'

注意:該方法的語法和做用與 apply() 方法相似,只有一個區別,就是 call() 方法接受的是一個參數列表,而 apply() 方法接受的是一個包含多個參數的數組。

const person = {
  name: "Marko Polo"
};

function greeting(greetingMessage) {
  return `${greetingMessage} ${this.name}`;
}

greeting.call(person, 'Hello'); // "Hello Marko Polo!"

29. Function.prototype.apply 和 Function.prototype.call 之間有什麼區別?

apply()方法能夠在使用一個指定的 this 值和一個參數數組(或類數組對象)的前提下調用某個函數或方法。call()方法相似於apply(),不一樣之處僅僅是call()接受的參數是參數列表。

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]); // 15
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // 15

30. Function.prototype.bind 的用途是什麼?

bind() 方法建立一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定爲 bind() 的第一個參數,而其他參數將做爲新函數的參數,供調用時使用。

import React from 'react';

class MyComponent extends React.Component {
     constructor(props){
          super(props); 
          this.state = {
             value : ""
          }  
          this.handleChange = this.handleChange.bind(this); 
          // 將 「handleChange」 方法綁定到 「MyComponent」 組件
     }

     handleChange(e){
       //do something amazing here
     }

     render(){
        return (
              <>
                <input type={this.props.type}
                        value={this.state.value}
                     onChange={this.handleChange}                      
                  />
              </>
        )
     }
}

31. 什麼是函數式編程? JavaScript 的哪些特性使其成爲函數式語言的候選語言?

函數式編程(一般縮寫爲FP)是經過編寫純函數,避免共享狀態、可變數據、反作用 來構建軟件的過程。數式編程是聲明式 的而不是命令式 的,應用程序的狀態是經過純函數流動的。與面向對象編程造成對比,面向對象中應用程序的狀態一般與對象中的方法共享和共處。

函數式編程是一種編程範式 ,這意味着它是一種基於一些基本的定義原則(如上所列)思考軟件構建的方式。固然,編程範示的其餘示例也包括面向對象編程和過程編程。

函數式的代碼每每比命令式或面向對象的代碼更簡潔,更可預測,更容易測試 - 但若是不熟悉它以及與之相關的常見模式,函數式的代碼也可能看起來更密集雜亂,而且 相關文獻對新人來講是很差理解的。

JavaScript支持閉包和高階函數是函數式編程語言的特色。

32. 什麼是高階函數?

高階函數只是將函數做爲參數或返回值的函數。

function higherOrderFunction(param,callback){
    return callback(param);
}

33. 爲何函數被稱爲一等公民?

在JavaScript中,函數不只擁有一切傳統函數的使用方式(聲明和調用),並且能夠作到像簡單值同樣賦值(var func = function(){})、傳參(function func(x,callback){callback();})、返回(function(){return function(){}}),這樣的函數也稱之爲第一級函數(First-class Function)。不只如此,JavaScript中的函數還充當了類的構造函數的做用,同時又是一個Function類的實例(instance)。這樣的多重身份讓JavaScript的函數變得很是重要。

34. 手動實現 Array.prototype.map 方法

map() 方法建立一個新數組,其結果是該數組中的每一個元素都調用一個提供的函數後返回的結果。

function map(arr, mapCallback) {
  // 首先,檢查傳遞的參數是否正確。
  if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') { 
    return [];
  } else {
    let result = [];
    // 每次調用此函數時,咱們都會建立一個 result 數組
    // 由於咱們不想改變原始數組。
    for (let i = 0, len = arr.length; i < len; i++) {
      result.push(mapCallback(arr[i], i, arr)); 
      // 將 mapCallback 返回的結果 push 到 result 數組中
    }
    return result;
  }
}

35. 手動實現Array.prototype.filter方法

filter() 方法建立一個新數組, 其包含經過所提供函數實現的測試的全部元素。

function filter(arr, filterCallback) {
  // 首先,檢查傳遞的參數是否正確。
  if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function') 
  {
    return [];
  } else {
    let result = [];
     // 每次調用此函數時,咱們都會建立一個 result 數組
     // 由於咱們不想改變原始數組。
    for (let i = 0, len = arr.length; i < len; i++) {
      // 檢查 filterCallback 的返回值是不是真值
      if (filterCallback(arr[i], i, arr)) { 
      // 若是條件爲真,則將數組元素 push 到 result 中
        result.push(arr[i]);
      }
    }
    return result; // return the result array
  }
}

36. 手動實現Array.prototype.reduce方法

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

function reduce(arr, reduceCallback, initialValue) {
  // 首先,檢查傳遞的參數是否正確。
  if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function') 
  {
    return [];
  } else {
    // 若是沒有將initialValue傳遞給該函數,咱們將使用第一個數組項做爲initialValue
    let hasInitialValue = initialValue !== undefined;
    let value = hasInitialValue ? initialValue : arr[0];
   、

    // 若是有傳遞 initialValue,則索引從 1 開始,不然從 0 開始
    for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) {
      value = reduceCallback(value, arr[i], i, arr); 
    }
    return value;
  }
}

37. arguments 的對象是什麼?

arguments對象是函數中傳遞的參數值的集合。它是一個相似數組的對象,由於它有一個length屬性,咱們可使用數組索引表示法arguments[1]來訪問單個值,但它沒有數組中的內置方法,如:forEachreducefiltermap

咱們可使用Array.prototype.slicearguments對象轉換成一個數組。

function one() {
  return Array.prototype.slice.call(arguments);
}

注意:箭頭函數中沒有arguments對象。

function one() {
  return arguments;
}
const two = function () {
  return arguments;
}
const three = function three() {
  return arguments;
}

const four = () => arguments;

four(); // Throws an error  - arguments is not defined

當咱們調用函數four時,它會拋出一個ReferenceError: arguments is not defined error。使用rest語法,能夠解決這個問題。

const four = (...args) => args;

這會自動將全部參數值放入數組中。

38. 如何建立一個沒有 prototype(原型)的對象?

咱們可使用Object.create方法建立沒有原型的對象。

const o1 = {};
console.log(o1.toString()); // [object Object]

const o2 = Object.create(null);
console.log(o2.toString());
// throws an error o2.toString is not a function

39. 爲何在調用這個函數時,代碼中的b會變成一個全局變量?

function myFunc() {
  let a = b = 0;
}

myFunc();

緣由是賦值運算符是從右到左的求值的。這意味着當多個賦值運算符出如今一個表達式中時,它們是從右向左求值的。因此上面代碼變成了這樣:

function myFunc() {
  let a = (b = 0);
}

myFunc();

首先,表達式b = 0求值,在本例中b沒有聲明。所以,JS引擎在這個函數外建立了一個全局變量b,以後表達式b = 0的返回值爲0,並賦給新的局部變量a

咱們能夠經過在賦值以前先聲明變量來解決這個問題。

function myFunc() {
  let a,b;
  a = b = 0;
}
myFunc();

40. ECMAScript 是什麼?

ECMAScript 是編寫腳本語言的標準,這意味着JavaScript遵循ECMAScript標準中的規範變化,由於它是JavaScript的藍圖。

ECMAScript 和 Javascript,本質上都跟一門語言有關,一個是語言自己的名字,一個是語言的約束條件
只不過發明JavaScript的那我的(Netscape公司),把東西交給了ECMA(European Computer Manufacturers Association),這我的規定一下他的標準,由於當時有java語言了,又想強調這個東西是讓ECMA這我的定的規則,因此就這樣一個神奇的東西誕生了,這個東西的名稱就叫作ECMAScript。

javaScript = ECMAScript + DOM + BOM(自認爲是一種廣義的JavaScript)

ECMAScript說什麼JavaScript就得作什麼!

JavaScript(狹義的JavaScript)作什麼都要問問ECMAScript我能不能這樣幹!若是不能我就錯了!能我就是對的!

——忽然感受JavaScript好沒有尊嚴,爲啥要搞我的出來約束本身,

那我的被創造出來也好委屈,本身被創造出來徹底是由於要約束JavaScript。

因爲篇幅過長,我將此係列分紅上下二篇,下篇咱們在見。


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

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


交流

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

clipboard.png

相關文章
相關標籤/搜索