JavaScript 前端面試

progressive enhancement

從最基本的功能出發,在保證系統在任何環境中的可用性的基礎上,逐步增加功能及提高使用者經驗。

graceful degradation

在一開始建構一個系統或網站時,就針對最新、最完善的環境來設計,然後針對其它環境進行測時與修復。使用這個方案時,我們首先會挑選一個較完善的平台完成所有的功能和經驗,然後再針對無法支援所有功能的平台或環境撰寫候選方案,讓那些較舊的平台不致於無法使用主要的功能。

Flash of Unstyled Content (FOUC)

網頁的內容已經讀取、顯示,但樣式尚未載入完成的那一瞬間。

Solution

<html>
	<head>
		<style>
			body {
        display: none;
      }
		</style>
	</head>
  <body>
    <script>
    	window.onload(() => {
        //拿掉一開始設定的 display: none
      })
    </script>
  </body>
</html>

this 如何作用

const a = {
  whoIsThis() {
		console.log(this);
	}
}

a.whoIsThis() // a;

const b = {};
b.whoIsThis = a.whoIsThis;
b.whoIsThis() // b;

prototype 如何運作

當 function 創建時,JavaScript Engine 會為其增加一 prototype Object 屬性, 而 prototype 中有一 constructor 屬性, 指向 function。

function somethingAboutPrototype () {};

console.log(typeof somethingAboutPrototype.prototype) // object;
console.log(somethingAboutPrototype.prototype.constructor) // somethingAboutPrototype;
console.log(somethingAboutPrototype.prototype.constructor === somethingAboutPrototype) // true
function Animal () {
  this.meterPerSecond = 0;
}
Animal.prototyp.move = function (time) {
  console.log(`move ${this.meterPerSecond * time}`);
}

小知識

所有東西其實都是繼承自 Object

如何 new

Animal 為例:

const a = new Animal();

// Equal To

const a = new Object();
a.__proto__ = Animal.prototype;
Animal.call(a);

原型鍊

以 a 為例:呼叫 a.move 會先搜尋 a 身上是否有 move 存在,如果沒有則搜尋 a.__proto__.move 是否存在。

AMD vs CommonJs vs ES6

CommonJs

sync

// root/somemodule.js

exports = {
  hello() {
    console.log("hello");
  }
}

// root/index.js

const SomeModule = import("./somemodule");
SomeModule.hello();

NodeJs

sync

// root/somemodule.js

module.exports = {
  hello() {
    console.log("hello");
  }
}

// root/index.js

const SomeModule = import("./somemodule");
SomeModule.hello();

AMD (Asynchronous Module Definition)

async

// root/somemodule.js

define(() => {
  return {
    hello() {
      console.log("hello");
    }
  }
});

// root/index.js

require(["/somemodule"], (SomeModule) => {
  SomeModule.hello();
})

ES6

async

// root/somemodule.js

export hello() {
  console.log("hello");
}

// root/index.js

import SomeModule from "./somemodule";
SomeModule.hello();

IIFE (Immediately Invoked Function Expression)

定義完馬上就執行的 function

undefined null undeclared

undefined

  • 未定義、未賦值
  • 沒有定義 return 值的 function return undefined

null

  • 沒有值 (often retrieved in a place where an object can be expected but no object is relevant. )

NaN

  • Not a Number

undeclared

  • 語法錯誤,表示變數尚未被宣告

Closure

function generateHello () {
  // every variables undeclared in this scope can only accessed in returned function
  const dialog = `Hello!`;
  
  return function hello (name) {
    // dialog can only access in hello function;
    console.log(dialog + name + "!!");
  }
}

const hello = generateHello("Albert") // Hello!Albert!

generateHello.dialog // variable dialog is undeclared
dialog // variable dialog is undeclared

Native Object vs. Host Object

Native Object

ECMAScript 的實作,例如 Math、Date

Host Object

由運行環境提供的 Object 以完整執行 ECMAScript,例如 window

Document.write()

把參數中的字符串視為 DOM 插入文件中。

呼叫 Document.write() 時,會自動呼叫 Document.open();如果是在已經呼叫完 Document.close() 時呼叫 Document.write(),則文件中已存在的所有內容將被清除。

常規的呼叫流程:

document.open();
document.write("<h1>HaHa</h1");
document.close();

用在何時?

載入第三方的 script,例如 Google Analyst。因為使用 document.write 插入的 <script/> 不會影響 (block) web 的載入。

Feature Detection, Feature inference, UA String

Feature Detection

檢查 Browser 是否支援某功能。

if ("geolocation" in navigator) {
  navigator.geolocation.getCurrentPosition(function(position) {
    // show the location on a map, perhaps using the Google Maps API
  });
} else {
  // Give the user a choice of static maps instead perhaps
}

Feature inference

如果 A 存在,則 B 應該也存在。

UA String

navigator.userAgent 為字符串,說明當前的環境,例如 safari 10.2。但有些 Browser 騙人,給出不符合當前環境的值。

Ajax (Asynchronous JavaScript and XML)

在不重新整理整個頁面的情況下與 Server 互動,取得需要的資料。

  • 使用 XMLHttpRequest Object 與 Server 互動:

    const httpRequest = XMLHttpRequest();
    
    /**
     * @param {string} method of HTTP request
     * @param {string} url
     * @param {boolean} asyc
     */
    httpRequest.open("GET", "url", true);
    
    /**
     * @param {any?} arg 
     */
    httpRequest.send();
    
  • 如果要 "POST",必須設定 MIME Header:

    httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    
  • 與 Server 回傳值互動:

    httpRequest.onreadystatechange = function () {
      if (httpRequest.readyState === XMLHttpRequest.DONE) {
        if (httpRequest.status === 200) {
          console.log(httpRequest.responseText || httpRequest.responseXML);
        }
      }
    }
    
  • CORS (Cross-Origin Resource Sharing):

    • 不同 domain、http / https、port 的資源不能存取。
    • Frontend 可以發起 Request、但無法透過 JavaScript 去存取 Response。
    • 如果要順利存取資源,則 Server 必須在 Header 中加上 Access-Control-Allow-Origin
      • 可以用 JSONP 跳過。
    • 非簡易 method 需要額外的 Preflighted Request。
      • 簡易 method:GET、HEAD、POST。

JSONP (Json with Padding)

html 中的 <script> 是同源政策的例外(也就是可以獲取不同 Domain 的內容)。

  • 以此方式獲得的資料在 Browser 是作為 JavaScript 處理。
  • 只能用 GET
// In HTML: 通常視需求動態插入 document

<script type="text/javascript" src="url/callback=parseResponse"></script>

// In Javascript

function parseResponse(data) {
  console.log(data);
}

Hoisting

JavaScript 在執行前會先將宣告放入記憶體。

hello(); // 宣告在後卻可以正常執行

function hello () {
  console.log("Hello!");
}

== vs. === vs. Object.is()

== Abstract Equality Comparison

  • 不同類型:false

  • new String("0") === "0"false

  • -0 === +0true

=== Strict Equality Comparison

  • null == undefinedtrue
  • 0 == "0"true
  • new String("0") == "0"true

Object.is()

Object.is('foo', 'foo');     // true
Object.is(window, window);   // true

Object.is('foo', 'bar');     // false
Object.is([], []);           // false

var foo = { a: 1 };
var bar = { a: 1 };
Object.is(foo, foo);         // true
Object.is(foo, bar);         // false

Object.is(null, null);       // true

// Special Cases
Object.is(0, -0);            // false
Object.is(-0, -0);           // true
Object.is(NaN, 0/0);         // true

Same-Origin Policy

document, script 只能存取相同來源的資源

  • 同 domain 視為 same-origin
  • 同 domian 不同 port 視為 different-origin

SEO for Singal Page Application

Server Side Render

Immutable, Mutable

  • Primitive Type 都是 Immutable, 數值被修改時會在記憶體指派個新位置。
  • Reference Type 都是 Mutable。
  • Object 中的 property 如果是使用 Object.defineProperty() 添加的,預設為 immutable
  • Immutable 可以:
    • 降低記憶體使用量
    • 改善效能
    • Thread Safty (但 JavaScript 為 Single Thread)

Event Loop

  • 分成 Stack 跟 Queue
    • Step 1 -- 執行 Main thread。
    • Step 2 -- 呼叫 method 後堆到 Stack 中,並執行 method 中的邏輯;執行結束移出 Stack。
    • Step 3 -- Stack 都跑完+Main thread 執行完後跑最早放進的 Queue,回到 Step 1
  • 堆到 Stack:
    • Function call
    • new Promise() 的 callback
  • 堆到 Queue:Main Thread 跑完後會馬上接著跑 MicroTask;MicroTask 跑完後才會再早放進把 MacroTask 的 queue 拿出來執行。
    • MacroTask:Event TriggersetTimeoutsetIntervalsetImmediaterequestAnimationFrame
    • MicroTask:Promise.prototype.thenPromise.prototype.catchObject.observeMutationObserver

Block UI with MicroTask

由於 MicroTask 是緊接著 Main Thread 且先於 MacroTask 的,因此會造成 UI Block

JavaScript 直譯器

graph LR;

subgraph Main Thread
o1[原始碼]
end

subgraph 直譯器
c1(語法基本單元化)
c2(抽象結構樹 AST) 
c3(程式生成)
end

subgraph 執行
e1(預編譯)
e2(執行)
end

o1-->c1
c1-->c2
c2-->c3
c3-->e1
e1-->e2

預編譯

  • 語法基本單元化 Tokenzing:

    // before
    var a = 1
    
    // after
    ['var', 'a', '=', 'b']
    
  • 抽象結構樹 AST Abstract Syntax Tree:

    // before
    
    var a = 1
    
    // after
    {
      type: 'Program',
      sourceType: 'script',
      body: [
        {
          type: 'VariableDeclarator',
          kind: 'var',
          id: {
            type: 'Indicator',
            name: 'a'
          },
          init: {
            type: 'Literal',
            value: 1,
            raw: '1'
          }
        }
      ]
    }
    

執行

  • 預編譯:依據 AST 編譯 JS,先宣告 variables、再宣告 methods。
  • 執行:逐行執行預編譯的結果。

Arrow Function VS Function

FunctionArrow Function
scope有;this 跟著外層最近一層 object無;this 跟著宣告時的 scope
arguments