阿川私房教材:學程式,拿 offer!

63 個專案實戰,直接上手!
無需補習,按步驟打造你的面試作品。

立即解鎖你的轉職秘笈

嗨大家好,新年快樂:煙火::煙火::煙火:!

這是一篇很長的文章,所以請耐心聽我一秒鐘或一個小時。每個問題的每個答案都有一個向上箭頭連結,可讓您返回到問題列表,這樣您就不會浪費時間上下滾動。

問題

66-函數可以有多少種方式被呼叫)

1. undefinednull有什麼差別?

^在了解undefinednull之間的差異之前,我們必須先了解它們之間的相似之處。

  • 它們屬於JavaScript 的7 種基本型別。
 let primitiveTypes = ['string','number','null','undefined','boolean','symbol', 'bigint'];
  • 它們是虛假的價值觀。使用Boolean(value)!!value將其轉換為布林值時計算結果為 false 的值。
   console.log(!!null); //logs false
   console.log(!!undefined); //logs false

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

好吧,我們來談談差異。

  • undefined是尚未指派特定值的變數的預設值。或一個沒有明確回傳值的函數。 console.log(1) 。或物件中不存在的屬性。 JavaScript 引擎為我們完成了指派undefined值的任務。
  let _thisIsUndefined;
  const doNothing = () => {};
  const someObj = {
    a : "ay",
    b : "bee",
    c : "si"
  };

  console.log(_thisIsUndefined); //logs undefined
  console.log(doNothing()); //logs undefined
  console.log(someObj["d"]); //logs undefined
  • null「代表無值的值」null是已明確定義給變數的值。在此範例中,當fs.readFile方法未引發錯誤時,我們得到null值。
  fs.readFile('path/to/file', (e,data) => {
     console.log(e); //it logs null when no error occurred
     if(e){
       console.log(e);
     }
     console.log(data);
   });

當比較nullundefined時,使用==時我們得到true ,使用===時得到false 。您可以在此處閱讀原因。

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

2. &&運算子的作用是什麼?

^ &&邏輯 AND運算子在其運算元中尋找第一個表達式並傳回它,如果沒有找到任何表達式,則傳回最後一個表達式。它採用短路來防止不必要的工作。在我的一個專案中關閉資料庫連線時,我在catch區塊中使用了它。

   console.log(false && 1 && []); //logs false
   console.log(" " && true && 5); //logs 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. ||是什麼意思?運營商做什麼?

|| or邏輯 OR運算子尋找其運算元中的第一個真值表達式並傳回它。這也採用短路來防止不必要的工作。在ES6預設函數參數被支援之前,它被用來初始化函數中的預設參數值。

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

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

logName(); //logs "Mark"

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

^根據MDN 文件, +是將字串轉換為數字的最快方法,因為如果該值已經是數字,它不會對該值執行任何操作。

5.什麼是DOM

^ DOM代表文件物件模型,是 HTML 和 XML 文件的介面 ( API )。當瀏覽器第一次讀取(解析)我們的 HTML 文件時,它會建立一個大物件,一個基於 HTML 文件的非常大的物件,這就是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應該是這樣的。

DOM 等效項

JavaScript中的document物件代表DOM 。它為我們提供了許多方法,我們可以用來選擇元素來更新元素內容等等。

6.什麼是事件傳播

當某個事件發生在DOM元素上時,該事件並非完全發生在該元素上。在冒泡階段事件向上冒泡,或到達其父級、祖父母、祖父母的父級,直到一直到達window ,而在捕獲階段,事件從window開始向下到達觸發的元素事件或<a href="#12-what-is-eventtarget-">event.target</a>

事件傳播分為三個階段。

  1. 捕獲階段-事件從window開始,然後向下到達每個元素,直到到達目標元素。

  2. 目標階段– 事件已到達目標元素。

  3. 冒泡階段-事件從目標元素冒起,然後向上移動到每個元素,直到到達window

事件傳播

7.什麼是事件冒泡

當某個事件發生在DOM元素上時,該事件並非完全發生在該元素上。在冒泡階段事件向上冒泡,或到達其父級、祖父母、祖父母的父級,直到一直到達window

如果我們有一個像這樣的範例標記。

 <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元素,它會分別在控制台上記錄childparent元素、 grandparenthtmldocumentwindow 。這就是事件冒泡

8. 什麼是事件擷取

當某個事件發生在DOM元素上時,該事件並非完全發生在該元素上。在捕獲階段,事件從window開始一直到觸發事件的元素。

如果我們有一個像這樣的範例標記。

 <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');
  }, true);

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

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

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

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

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

});

addEventListener方法有第三個可選參數useCapture ,預設值為false事件將在冒泡階段發生,如果為true ,事件將在捕獲階段發生。如果我們點擊child元素,它會分別在控制台上記錄windowdocumenthtmlgrandparentparentchild 。這就是事件捕獲

9. event.preventDefault()event.stopPropagation()方法有什麼差別?

event.preventDefault()方法阻止元素的預設行為。如果在form元素中使用,它會阻止其提交。如果在anchor元素中使用,它會阻止其導航。如果在contextmenu中使用,它會阻止其顯示或顯示。而event.stopPropagation()方法會停止事件的傳播或停止事件在冒泡捕獲階段發生。

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

我們可以使用事件物件中的event.defaultPrevented屬性。它傳回一個boolean ,指示是否在特定元素中呼叫了event.preventDefault()

11. 為什麼這段程式碼obj.someprop.x會拋出錯誤?

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

^顯然,由於我們嘗試存取 a 的原因,這會引發錯誤

someprop屬性中的x屬性具有undefined值。請記住,物件中的屬性本身並不存在,且其原型具有預設值undefinedundefined沒有屬性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>

JavaScript 範例。

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

如果您單擊按鈕,它會記錄按鈕標記,即使我們將事件附加在最外部的div上,它也會始終記錄按鈕,因此我們可以得出結論, event.target是觸發事件的元素。

13.什麼是event.currentTarget

event.currentTarget是我們明確附加事件處理程序的元素。

複製問題 12中的標記。

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);
}

如果您按一下該按鈕,即使我們按一下該按鈕,它也會記錄最外層的div標記。在此範例中,我們可以得出結論, event.currentTarget是我們附加事件處理程序的元素。

14. =====有什麼差別?

^ == __(抽象相等)__ 和=== __(嚴格相等)__ 之間的區別在於==強制轉換後按進行比較,而===在不進行強制轉換的情況下按類型進行比較。

讓我們更深入地研究== 。那麼首先我們來談談強制

強制轉換是將一個值轉換為另一種類型的過程。在本例中, ==進行隱式強制轉換。在比較兩個值之前, ==需要執行一些條件。

假設我們必須比較x == y值。

  1. 如果xy具有相同的類型。

然後將它們與===運算子進行比較。

  1. 如果xnully undefined ,則傳回true

  2. 如果x undefinedynull則傳回true

  3. 如果xnumber類型, ystring類型

然後回傳x == toNumber(y)

  1. 如果xstring類型, ynumber類型

然後返回toNumber(x) == y

  1. 如果xboolean類型

然後返回toNumber(x) == y

  1. 如果yboolean類型

然後回傳x == toNumber(y)

  1. 如果xstringsymbolnumbery是 type object

然後回傳x == toPrimitive(y)

  1. 如果xobjectxstringsymbol

然後返回toPrimitive(x) == y

  1. 返回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

第一個範例屬於條件一,因為xy具有相同的類型和值。

第二個範例轉到條件四,在比較之前將y轉換為number

第三個例子涉及條件二

第四個範例轉到條件七,因為yboolean

第五個範例適用於條件八。使用toString()方法將陣列轉換為string ,該方法傳回1,2

最後一個例子適用於條件十。使用傳回[object Object]toString()方法將該物件轉換為string

| 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 ,因為兩者俱有相同的類型和值。

15. 為什麼在 JavaScript 中比較兩個相似的物件時回傳false

^假設我們有下面的例子。

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

console.log(a === b); // logs false even though they have the same property
console.log(a === c); // logs true hmm

JavaScript以不同的方式比較物件基元。在基元中,它透過來比較它們,而在物件中,它透過引用儲存變數的記憶體位址來比較它們。這就是為什麼第一個console.log語句回傳false而第二個console.log語句回傳true的原因。 ac有相同的引用,而ab則不同。

16. !!是什麼意思?運營商做什麼?

雙非運算子或!!將右側的值強制轉換為布林值。基本上,這是一種將值轉換為布林值的奇特方法。

console.log(!!null); //logs false
console.log(!!undefined); //logs false
console.log(!!''); //logs false
console.log(!!0); //logs false
console.log(!!NaN); //logs false
console.log(!!' '); //logs true
console.log(!!{}); //logs true
console.log(!![]); //logs true
console.log(!!1); //logs true
console.log(!![].length); //logs 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 作為參數傳遞,並將結果分配給xx的新值將是11 。之後,我們將x的當前值乘以2並將其分配給xx的更新值將是22 。然後,我們將x的當前值減去 5 並將結果指派給x ,更新後的值將是17 。最後,我們將x的值增加 10 並將更新後的值指派給x ,現在x的值將是27

18.什麼是吊裝

^提升是一個術語,用於描述將變數函數移動到其(全域或函數)作用域的頂部(即我們定義該變數或函數的位置)。

要理解提升,我必須解釋執行上下文

執行上下文是目前正在執行的「程式碼環境」。執行上下文有兩個階段:編譯執行

編譯- 在此階段,它獲取所有函數聲明並將它們提升到作用域的頂部,以便我們稍後可以引用它們並獲取所有*變數聲明(使用 var 關鍵字聲明) ,並將它們提升並給它們一個默認值未定義*的 .

執行- 在此階段,它將值指派給先前提升的變數,並執行呼叫函數(物件中的方法)

注意:只有使用var關鍵字宣告的函數宣告和變數才會被提升,而不是函數表達式箭頭函數letconst關鍵字。

好吧,假設我們在下面的全域範圍內有一個範例程式碼。

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

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

var y;

此程式碼記錄undefined1Hello Mark!分別。

所以編譯階段看起來像這樣。

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

var y; //implicit "undefined" assignment

//waiting for "compilation" phase to finish

//then start "execution" phase
/*
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 中的作用域是我們可以有效存取變數或函數的區域。 JavaScript 有三種類型的作用域。全域作用域函數作用域區塊作用域(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 
  • 區塊作用域- 在區塊{}內宣告的變數letconst只能在區塊內存取。
 function testBlock(){
   if(true){
     let z = 5;
   }
   return z; 
 }

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

範圍也是一組查找變數的規則。如果一個變數在當前作用域中不存在,它會在外部作用域中查找並蒐索該變數,如果不存在,它會再次查找,直到到達全域作用域。如果該變數存在,那麼我們可以使用它,如果不存在,我們可以使用它來拋出錯誤。它搜尋最近的變數,一旦找到它就停止搜尋尋找。這稱為作用域鏈

   /* Scope Chain
   Inside inner function perspective

   inner's scope -> outer's scope -> global's scope
  */

  //Global Scope
  var variable1 = "Comrades";   
  var variable2 = "Sayonara";

  function outer(){
  //outer's scope
    var variable1 = "World";
    function inner(){
    //inner's scope
      var variable2 = "Hello";
      console.log(variable2 + " " + variable1);
    }
    inner();
  }  
  outer(); 
// logs Hello World 
// because (variable2 = "Hello") and (variable1 = "World") are the nearest 
// variables inside inner's scope.

範圍

20.什麼是閉包

^這可能是所有這些問題中最難的問題,因為閉包是一個有爭議的話題。那我就從我的理解來解釋。

閉包只是函數在宣告時記住其當前作用域、其父函數作用域、其父函數的父函數作用域上的變數和參數的引用的能力,直到在作用域鏈的幫助下到達全域作用域。基本上它是聲明函數時建立的作用域

例子是解釋閉包的好方法。

   //Global's Scope
   var globalVar = "abc";

   function a(){
   //testClosures's Scope
     console.log(globalVar);
   }

   a(); //logs "abc" 
   /* Scope Chain
      Inside a function perspective

      a's scope -> global's scope  
   */ 

在此範例中,當我們宣告a函數時,全域作用域是a's閉包的一部分。

a的閉包

變數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");

複雜的

這將列印“猜測外部內部”。對此的解釋是,當我們呼叫outerFunc函數並將innerFunc函數的回傳值指派給變數x時,即使我們將新值outer-2指派給outerVar變數, outerParam也會具有outer值,因為

重新分配發生在呼叫outer函數之後,當我們呼叫outerFunc函數時,它會在作用域鏈中尋找outerVar的值,而outerVar的值為「outer」 。現在,當我們呼叫引用了innerFuncx變數時,

innerParam的值為inner,因為這是我們在呼叫中傳遞的值,而globalVar變數的值為猜測,因為在呼叫x變數之前,我們為globalVar分配了一個新值,並且在呼叫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]()); // all logs "5"
}

由於Closures的原因,此程式碼無法按我們的預期工作。

var關鍵字建立一個全域變數,當我們推送一個函數時

我們返回全域變數i 。因此,當我們在循環之後呼叫該陣列中的其中一個函數時,它會記錄5 ,因為我們得到

i的目前值為5 ,我們可以存取它,因為它是全域變數。因為閉包保留該變數的引用,而不是其建立時的。我們可以使用IIFES或將var關鍵字變更為let來解決此問題,以實現區塊作用域。

21. JavaScript中的值是什麼?

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

值是轉換為布林值時變成false 的值。

22. 如何檢查一個值是否為假值

使用布林函數或雙非運算符!!

23. "use strict"有什麼作用?

^ "use strict"JavaScript中的 ES5 功能,它使我們的程式碼在函數整個腳本中處於嚴格模式嚴格模式幫助我們避免程式碼早期出現錯誤並為其加入限制。

嚴格模式給我們的限制。

  • 分配或存取未宣告的變數。
 function returnY(){
    "use strict";
    y = 123;
    return y;
 }
  • 為唯讀或不可寫的全域變數賦值;
   "use strict";
   var NaN = NaN;
   var undefined = undefined;
   var Infinity = "and beyond";
  • 刪除不可刪除的屬性。
   "use strict";
   const obj = {};

   Object.defineProperty(obj, 'x', {
      value : '1'
   });  

   delete obj.x;
  • 參數名稱重複。
   "use strict";

   function someFunc(a, b, b, c){

   }
  • 使用eval函數建立變數。
 "use strict";

 eval("var x = 1;");

 console.log(x); //Throws a Reference Error x is not defined
  • 值的預設值是undefined
  "use strict";

  function showMeThis(){
    return this;
  }

  showMeThis(); //returns undefined

嚴格模式的限制遠不止這些。

24. JavaScript 中this的值是什麼?

基本上, this是指目前正在執行或呼叫函數的物件的值。我說目前是因為的值會根據我們使用它的上下文和使用它的位置而改變。

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

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

這是我們通常所期望的,因為在getName方法中我們傳回this.name ,在此上下文中this指的是carDetails物件,該物件目前是正在執行的函數的「擁有者」物件。

好吧,讓我們加入一些程式碼讓它變得奇怪。在console.log語句下面加入這三行程式碼

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

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

第二個console.log語句印製了「Ford Ranger」一詞,這很奇怪,因為在我們的第一個console.log語句中它印了「Ford Mustang」 。原因是getCarName方法有一個不同的「擁有者」物件,即window物件。在全域作用域中使用var關鍵字聲明變數會在window物件中附加與變數同名的屬性。請記住,當未使用"use strict"時,全域範圍內的this指的是window物件。

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

本例中的thiswindow指的是同一個物件。

解決此問題的一種方法是使用函數中的<a href="#27-what-is-the-use-functionprototypeapply-method">apply</a><a href="#28-what-is-the-use-functionprototypecall-method">call</a>方法。

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

applycall方法期望第一個參數是一個物件,該物件將是該函數內this的值。

IIFE (即立即呼叫函數表達式) 、在全域作用域中宣告的函數、物件內部方法中的匿名函數和內部函數都有一個指向window物件的預設

   (function (){
     console.log(this);
   })(); //logs the "window" object

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

   iHateThis(); //logs the "window" object  

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

   myFavoriteObj.guessThis(); //logs the "window" object
   myFavoriteObj.thisIsAnnoying(function (){
     console.log(this); //logs the "window" object
   });

如果我們想要取得myFavoriteObj物件中的name屬性(Marko Polo)的值,有兩種方法可以解決這個問題。

首先,我們將this的值保存在變數中。

   const myFavoriteObj = {
     guessThis(){
         const self = this; //saves the this value to the "self" variable
         function getName(){
           console.log(self.name);
         }
         getName();
     },
     name: 'Marko Polo',
     thisIsAnnoying(callback){
       callback();
     }
   };

在此圖像中,我們保存this的值,該值將是myFavoriteObj物件。所以我們可以在getName內部函數中存取它。

其次,我們使用ES6箭頭函數

   const myFavoriteObj = {
     guessThis(){
         const getName = () => { 
           //copies the value of "this" outside of this arrow function
           console.log(this.name);
         }
         getName();
     },
     name: 'Marko Polo',
     thisIsAnnoying(callback){
       callback();
     }
   };

箭頭函數沒有自己的this 。它複製封閉詞法範圍的this值,或複製getName內部函數外部的this值(即myFavoriteObj物件)。我們也可以根據函數的呼叫方式來決定this的值。

25. 物件的prototype是什麼?

最簡單的prototype是一個物件的藍圖。如果目前物件中確實存在它,則將其用作屬性方法的後備。這是在物件之間共享屬性和功能的方式。這是 JavaScript原型繼承的核心概念。

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

即使o.toString方法不存在於o物件中,它也不會拋出錯誤,而是傳回字串[object Object] 。當物件中不存在屬性時,它會尋找其原型,如果仍然不存在,則會尋找原型的原型,依此類推,直到在原型鏈中找到具有相同屬性的屬性。原型鏈的末尾在Object.prototype之後為null

   console.log(o.toString === Object.prototype.toString); // logs true
   // which means we we're looking up the Prototype Chain and it reached 
   // the Object.prototype and used the "toString" method.

26. 什麼是IIFE ,它有什麼用?

^ IIFE立即呼叫函數表達式是在建立或宣告後將被呼叫或執行的函數。建立IIFE的語法是,我們將function (){}包裝在括號()分組運算子內,以將函數視為表達式,然後用另一個括號()呼叫它。所以IIFE看起來像這樣(function(){})()

(function () {

}());

(function () {

})();

(function named(params) {

})();

(() => {

})();

(function (global) {

})(window);

const utility = (function () {
   return {
      //utilities
   };
})();

這些範例都是有效的IIFE 。倒數第二個範例顯示我們可以將參數傳遞給IIFE函數。最後一個範例表明我們可以將IIFE的結果保存到變數中,以便稍後引用它。

IIFE的最佳用途是進行初始化設定功能,並避免與全域範圍內的其他變數發生命名衝突或污染全域名稱空間。讓我們舉個例子。

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

假設我們有一個指向庫somelibrary.js的連結,該庫公開了我們可以在程式碼中使用的一些全域函數,但該庫有兩個我們不使用createGraphdrawGraph方法,因為這些方法中有錯誤。我們想要實作我們自己的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>

在此解決方案中,我們建立一個實用程式變數,它是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);
   })
}

假設我們有一個ul元素,其類別為list-group ,並且它有 5 個li子元素。當我們點擊單一li元素時,我們希望console.log i的值。

但我們想要的程式碼中的行為不起作用。相反,它會在對li元素的任何點擊中記錄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); // returns 'Hello World!'

這個方法的工作方式類似於<a href="#28-what-is-the-use-functionprototypecall-method">Function.prototype.call</a>唯一的差異是我們傳遞參數的方式。在apply中,我們將參數作為陣列傳遞。

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

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

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

28. Function.prototype.call方法有什麼用?

^call呼叫一個函數,指定呼叫時該函數的this或「擁有者」物件。

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

function getMessage(){
  return this.message;
}

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

這個方法的工作方式類似於<a href="#27-what-is-the-use-functionprototypeapply-method">Function.prototype.apply</a>唯一的差異是我們傳遞參數的方式。在call中,我們直接傳遞參數,對於每個參數,用逗號分隔它們。

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

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

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

29. Function.prototype.applyFunction.prototype.call有什麼差別?

applycall之間的唯一區別是我們如何在被呼叫的函數中傳遞參數。在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]); // returns 15
reduceAdd.call(obj2, 1, 2, 3, 4, 5); // returns 15

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

bind方法傳回一個新綁定的函數

到特定的this值或“所有者”物件,因此我們可以稍後在程式碼中使用它。 callapply方法立即呼叫函數,而不是像bind方法那樣傳回一個新函數。

import React from 'react';

class MyComponent extends React.Component {
     constructor(props){
          super(props); 
          this.state = {
             value : ""
          }  
          this.handleChange = this.handleChange.bind(this); 
          // Binds the "handleChange" method to the "MyComponent" component
     }

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

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

31.什麼是函數式程式設計JavaScript的哪些特性使其成為函數式語言的候選者?

^函數式程式設計是一種聲明式程式設計範式或模式,它介紹如何使用表達式來計算值而不改變傳遞給它的參數的函數來建立應用程式。

JavaScript陣列具有mapfilterreduce方法,這些方法是函數式程式設計世界中最著名的函數,因為它們非常有用,而且它們不會改變或改變陣列,這使得這些函數變得純粹,並且JavaScript 支援閉包高階函數,它們是函數式程式語言的一個特徵。

  • map方法建立一個新陣列,其中包含對陣列中每個元素呼叫提供的回調函數的結果。
const words = ["Functional", "Procedural", "Object-Oriented"];

const wordsLength = words.map(word => word.length);
  • filter方法會建立一個新陣列,其中包含透過回調函數中測試的所有元素。
const data = [
  { name: 'Mark', isRegistered: true },
  { name: 'Mary', isRegistered: false },
  { name: 'Mae', isRegistered: true }
];

const registeredUsers = data.filter(user => user.isRegistered);
  • reduce方法對累加器和陣列中的每個元素(從左到右)套用函數,將其減少為單一值。
const strs = ["I", " ", "am", " ", "Iron", " ", "Man"];
const result = strs.reduce((acc, currentStr) => acc + currentStr, "");

32.什麼是高階函數

^高階函數是可以傳回函數或接收具有函數值的一個或多個參數的函數。

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

33.為什麼函數被稱為First-class Objects

^ JavaScript 中的 __Functions__ 是一流物件,因為它們被視為該語言中的任何其他值。它們可以分配給變數,可以是稱為方法的物件的屬性,可以是陣列中的專案,可以作為參數傳遞給函數,也可以作為函數的值返回。函數與JavaScript中任何其他值之間的唯一區別是函數可以被呼叫。

34. 手動實作Array.prototype.map方法。

function map(arr, mapCallback) {
  // First, we check if the parameters passed are right.
  if (!Array.isArray(arr) || !arr.length || typeof mapCallback !== 'function') { 
    return [];
  } else {
    let result = [];
    // We're making a results array every time we call this function
    // because we don't want to mutate the original array.
    for (let i = 0, len = arr.length; i < len; i++) {
      result.push(mapCallback(arr[i], i, arr)); 
      // push the result of the mapCallback in the 'result' array
    }
    return result; // return the result array
  }
}

正如Array.prototype.map方法的MDN描述。

map() 方法建立一個新陣列,其中包含對呼叫陣列中的每個元素呼叫所提供函數的結果。

35. 手動實作Array.prototype.filter方法。

function filter(arr, filterCallback) {
  // First, we check if the parameters passed are right.
  if (!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function') 
  {
    return [];
  } else {
    let result = [];
    // We're making a results array every time we call this function
    // because we don't want to mutate the original array.
    for (let i = 0, len = arr.length; i < len; i++) {
      // check if the return value of the filterCallback is true or "truthy"
      if (filterCallback(arr[i], i, arr)) { 
      // push the current item in the 'result' array if the condition is true
        result.push(arr[i]);
      }
    }
    return result; // return the result array
  }
}

正如Array.prototype.filter方法的 MDN 描述。

filter() 方法建立一個新陣列,其中包含透過所提供函數實現的測試的所有元素。

36. 手動實作Array.prototype.reduce方法。

``js

函數reduce(arr,reduceCallback,initialValue){

// 首先,我們檢查傳遞的參數是否正確。

if (!Array.isArray(arr) || !arr.length || typeof reduceCallback !== 'function')

{

return [];

} 別的 {

// If no initialValue has been passed to the function we're gonna use the 
let hasInitialValue = initialValue !== undefined;
let value = hasInitialValue ? initialValue : arr[0];
// first array item as the initialValue
// Then we're gonna start looping at index 1 if there is no 
// initialValue has been passed to the function else we start at 0 if 
// there is an initialValue.
for (let i = hasInitialValue ? 0 : 1, len = arr.length; i < len; i++) {
  // Then for every iteration we assign the result of the 
  // reduceCallback to the variable value.
  value = reduceCallback(value, arr[i], i, arr); 
}
return value;

}

}

As the MDN description of the <code>Array.prototype.reduce</code> method.

__The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in a single output value.__

###37. What is the __arguments__ object?
[&uarr;](#the-questions "Back To Questions") The __arguments__ object is a collection of parameter values pass in a function. It's an __Array-like__ object because it has a __length__ property and we can access individual values using array indexing notation <code>arguments[1]</code> but it does not have the built-in methods in an array <code>forEach</code>,<code>reduce</code>,<code>filter</code> and <code>map</code>.
It helps us know the number of arguments pass in a function.

We can convert the <code>arguments</code> object into an array using the <code>Array.prototype.slice</code>.

函數一(){

返回 Array.prototype.slice.call(參數);

}

Note: __the <code>arguments</code> object does not work on ES6 arrow functions.__

函數一(){

返回參數;

}

常數二 = 函數 () {

返回參數;

}

常量三 = 函數三() {

返回參數;

}

const 四 = () => 參數;

四(); // 拋出錯誤 - 參數未定義

When we invoke the function <code>four</code> it throws a <code>ReferenceError: arguments is not defined</code> error. We can solve this problem if your enviroment supports the __rest syntax__.

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

This puts all parameter values in an array automatically.

###38. How to create an object without a __prototype__?
[&uarr;](#the-questions "Back To Questions") We can create an object without a _prototype_ using the <code>Object.create</code> method.

常數 o1 = {};

console.log(o1.toString());

// Logs [object Object] 取得此方法到Object.prototype

const o2 = Object.create(null);

// 第一個參數是物件「o2」的原型,在此

// case 將為 null 指定我們不需要任何原型

console.log(o2.toString());

// 拋出錯誤 o2.toString 不是函數


###39. Why does <code>b</code> in this code become a global variable when you call this function?
[&uarr;](#the-questions "Back To Questions")

函數 myFunc() {

令a = b = 0;

}

myFunc();

The reason for this is that __assignment operator__ or __=__ has right-to-left __associativity__ or __evaluation__. What this means is that when multiple assignment operators appear in a single expression they evaluated from right to left. So our code becomes likes this.

函數 myFunc() {

令 a = (b = 0);

}

myFunc();

First, the expression <code>b = 0</code> evaluated and in this example <code>b</code> is not declared. So, The JS Engine makes a global variable <code>b</code> outside this function after that the return value of the expression <code>b = 0</code> would be 0 and it's assigned to the new local variable <code>a</code> with a <code>let</code> keyword.

We can solve this problem by declaring the variables first before assigning them with value.

函數 myFunc() {

令 a,b;

a = b = 0;

}

myFunc();


###40. <div id="ecmascript">What is __ECMAScript__</div>?

[&uarr;](#the-questions "Back To Questions") __ECMAScript__ is a standard for making scripting languages which means that __JavaScript__ follows the specification changes in __ECMAScript__ standard because it is the __blueprint__ of __JavaScript__.

###41. What are the new features in __ES6__ or __ECMAScript 2015__?
[&uarr;](#the-questions "Back To Questions")
* [Arrow Functions](#43-what-are-arrow-functions)

* [Classes](#44-what-are-classes)

* [Template Strings](#45-what-are-template-literals)

* __Enhanced Object literals__

* [Object Destructuring](#46-what-is-object-destructuring)

* [Promises](#50-what-are-promises)

* __Generators__

* [Modules](#47-what-are-es6-modules) 

* Symbol

* __Proxies__

* [Sets](#48-what-is-the-set-object-and-how-does-it-work)

* [Default Function parameters](#53-what-are-default-parameters)

* [Rest and Spread](#52-whats-the-difference-between-spread-operator-and-rest-operator)

* [Block Scoping with <code>let</code> and <code>const</code>](#42-whats-the-difference-between-var-let-and-const-keywords)

###42. What's the difference between <code>var</code>, <code>let</code> and <code>const</code> keywords?
[&uarr;](#the-questions "Back To Questions") Variables declared with <code>var</code> keyword are _function scoped_.
What this means that variables can be accessed across that function even if we declare that variable inside a block.

函數給MeX(showX) {

如果(顯示X){

var x = 5;

}

返回x;

}

console.log(giveMeX(false));

console.log(giveMeX(true));

The first <code>console.log</code> statement logs <code>undefined</code>
and the second <code>5</code>. We can access the <code>x</code> variable due
to the reason that it gets _hoisted_ at the top of the function scope. So our function code is intepreted like this.

函數給MeX(showX) {

變數 x; // 有一個預設值未定義

如果(顯示X){

x = 5;

}

返回x;

}

If you are wondering why it logs <code>undefined</code> in the first <code>console.log</code> statement remember variables declared without an initial value has a default value of <code>undefined</code>.

Variables declared with <code>let</code> and <code>const</code>  keyword are _block scoped_. What this means that variable can only be accessed on that block <code>{}</code> on where we declare it.

函數給MeX(showX) {

如果(顯示X){

let x = 5;

}

返回x;

}

函數給MeY(顯示Y){

如果(顯示Y){

let y = 5;

}

返回y;

}

If we call this functions with an argument of <code>false</code> it throws a <code>Reference Error</code> because we can't access the <code>x</code> and <code>y</code> variables outside that block and those variables are not _hoisted_.

There is also a difference between <code>let</code> and <code>const</code> we can assign new values using <code>let</code> but we can't in <code>const</code> but <code>const</code> are mutable meaning. What this means is if the value that we assign to a <code>const</code> is an object we can change the values of those properties but can't reassign a new value to that variable.

###43. What are __Arrow functions__?

[&uarr;](#the-questions "Back To Questions") __Arrow Functions__ are a new way of making functions in JavaScript. __Arrow Functions__ takes a little time in making functions and has a cleaner syntax than a __function expression__ because we omit the <code>function</code> keyword in making them.

//ES5版本

var getCurrentDate = 函數 (){

返回新日期();

}

//ES6版本

const getCurrentDate = () => new Date();

In this example, in the ES5 Version have <code>function(){}</code> declaration and <code>return</code> keyword needed to make a function and return a value respectively. In the __Arrow Function__ version we only need the <code>()</code> parentheses and we don't need a <code>return</code> statement because __Arrow Functions__ have a implicit return if we have only one expression or value to return.

//ES5版本

函數問候(名稱){

return '你好' + 名字 + '!';

}

//ES6版本

const 問候 = (name) => Hello ${name} ;

constgreet2 = 名稱 => Hello ${name} ;

We can also parameters in __Arrow functions__ the same as the __function expressions__ and __function declarations__. If we have one parameter in an __Arrow Function__ we can omit the parentheses it is also valid.

const getArgs = () => 參數

const getArgs2 = (...休息) => 休息

__Arrow functions__ don't have access to the <code>arguments</code> object. So calling the first <code>getArgs</code> func will throw an Error. Instead we can use the __rest parameters__ to get all the arguments passed in an arrow function.

常量資料 = {

結果:0,

數字:[1,2,3,4,5],

計算結果() {

// "this" here refers to the "data" object
const addAll = () => {
  // arrow functions "copies" the "this" value of 
  // the lexical enclosing function
  return this.nums.reduce((total, cur) => total + cur, 0)
};
this.result = addAll();

}

};

__Arrow functions__ don't have their own <code>this</code> value. It captures or gets the <code>this</code> value of  lexically enclosing function or in this example, the <code>addAll</code> function copies the <code>this</code> value of the <code>computeResult</code> method and if we declare an arrow function in the global scope the value of <code>this</code> would be the <code>window</code> object.

###44. What are __Classes__?
[&uarr;](#the-questions "Back To Questions") __Classes__ is the new way of writing _constructor functions_ in __JavaScript__. It is _syntactic sugar_ for using _constructor functions_, it still uses __prototypes__ and __Prototype-Based Inheritance__ under the hood.

//ES5版本

函數人(名字,姓氏,年齡,地址){

  this.firstName = firstName;
  this.lastName = lastName;
  this.age = age;
  this.address = address;

}

Person.self = 函數(){

 return this;

}

Person.prototype.toString = function(){

 return "[object Person]";

}

Person.prototype.getFullName = function (){

 return this.firstName + " " + this.lastName;

}

//ES6版本

類人{

    constructor(firstName, lastName, age, address){
        this.lastName = lastName;
        this.firstName = firstName;
        this.age = age;
        this.address = address;
    }
    static self() {
       return this;
    }
    toString(){
       return "[object Person]";
    }
    getFullName(){
       return `${this.firstName} ${this.lastName}`;
    }

}


__Overriding Methods__ and __Inheriting from another class__.

//ES5版本

Employee.prototype = Object.create(Person.prototype);

函數 Employee(名字, 姓氏, 年齡, 地址, 職位名稱, 開始年份) {

Person.call(this, 名字, 姓氏, 年齡, 地址);

this.jobTitle = jobTitle;

this.yearStarted = YearStarted;

}

Employee.prototype.describe = function () {

return I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}

}

Employee.prototype.toString = function () {

返回“[物件員工]”;

}

//ES6版本

class Employee extends Person { //繼承自「Person」類

建構函數(名字,姓氏,年齡,地址,工作標題,開始年份){

super(firstName, lastName, age, address);
this.jobTitle = jobTitle;
this.yearStarted = yearStarted;

}

描述() {

return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`;

}

toString() { // 重寫「Person」的「toString」方法

return "[object Employee]";

}

}

So how do we know that it uses _prototypes_ under the hood?

類別東西{

}

函數 AnotherSomething(){

}

const as = new AnotherSomething();

const s = new Something();

console.log(typeof Something); // 記錄“函數”

console.log(AnotherSomething 類型); // 記錄“函數”

console.log(as.toString()); // 記錄“[物件物件]”

console.log(as.toString()); // 記錄“[物件物件]”

console.log(as.toString === Object.prototype.toString);

console.log(s.toString === Object.prototype.toString);

// 兩個日誌都回傳 true 表示我們仍在使用

// 底層原型,因為 Object.prototype 是

// 原型鏈的最後一部分和“Something”

// 和「AnotherSomething」都繼承自Object.prototype

###45. What are __Template Literals__?
[&uarr;](#the-questions "Back To Questions") __Template Literals__ are a new way of making __strings__ in JavaScript. We can make __Template Literal__ by using the backtick or back-quote symbol.

//ES5版本

vargreet = '嗨,我是馬克';

//ES6版本

讓問候 = Hi I'm Mark

In the ES5 version, we need to escape the <code>'</code> using the <code>\\</code>  to _escape_ the normal functionality of that symbol which in this case is to finish that string value. In Template Literals, we don't need to do that.

//ES5版本

var 最後一個字 = '\n'

  • ' 在'

  • '我\n'

  • '鋼鐵人\n';

//ES6版本

讓最後一個單字=`

I
Am

鋼鐵人

`;

In the ES5 version, we need to add this <code>\n</code> to have a new line in our string. In Template Literals, we don't need to do that.

//ES5版本

函數問候(名稱){

return '你好' + 名字 + '!';

}

//ES6版本

const 問候 = 名稱 => {

返回Hello ${name} ! ;

}

In the ES5 version, If we need to add an expression or value in a string we need to use the <code>+</code> or string concatenation operator. In Template Literals, we can embed an expression using <code>${expr}</code> which makes it cleaner than the ES5 version.

###46. What is __Object Destructuring__?
[&uarr;](#the-questions "Back To Questions") __Object Destructuring__ is a new and cleaner way of __getting__ or __extracting__ values from an object or an array.

Suppose we have an object that looks like this.

常量僱員 = {

名字:“馬可”,

姓氏:“波羅”,

職位:“軟體開發人員”,

聘用年份:2017

};


The old way of getting properties from an object is we make a variable t

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。

阿川私房教材:學程式,拿 offer!

63 個專案實戰,直接上手!
無需補習,按步驟打造你的面試作品。

立即解鎖你的轉職秘笈