「JavaScript初級面試題目」相關筆記

總結

整理了今年 7 月參加的 JS 模擬面試之題目與相關筆記。 感謝丹尼哥無償出借時間陪大家練習。

第一週摘要:scope、浮點數陷阱、hoist、強制轉型、null vs undefined、IIFE 第二週摘要:closure、變數型別、by value & by reference、深淺拷貝、this 第三週摘要:箭頭函數、運算子、event loop、promise、micro task vs macro task

第一週題目串

var、const 與 let 之差異

// functional scope
(() => {
  {
    var s = 'hello world';
  }
  console.log(s);
})();
// 'hello world'
// block scope
(() => {
  {
    let s = 'hello world';
  }
  console.log(s);
})();
// Uncaught ReferenceError: s is not defined
console.log(a); // Uncaught ReferenceError: b is not defined
let a = 'hello world';

console.log(b); // undefined
var b = 'hello world';

請調整以下程式碼,使其每隔一秒依序印出數列 01234

(() => {
  for (var i = 0; i < 5; i++) {
    setTimeout(() => {
      console.log(i);
    }, 1000 * i);
  }
})();
(() => {
  for (var i = 0; i < 5; i++) {
    setTimeout(
      ((i) => {
        return () => {
          console.log(i);
        };
      })(i),
      1000 * i
    );
  }
})();

第一週:以下程式碼的輸出結果為?

{
  (function () {
    var a = (b = 'hello world');
  })();

  console.log(`a defined? ${typeof a !== 'undefined'}`);
  console.log(`b defined? ${typeof b !== 'undefined'}`);
}
{
  (function () {
    b = 'hello world';
    var a = b;
  })();
}

備註:typeof可處理未被定義的(var)變數而不會報錯,參考 MDN:

Before ECMAScript 2015, typeof was always guaranteed to return a string for any operand it was supplied with. Even with undeclared identifiers, typeof will return undefined. Using typeof could never generate an error. However, with the addition of block-scoped let and const, using typeof on let and const variables (or using typeof on a class) in a block before they are declared will throw a ReferenceError. Block scoped variables are in a ”temporal dead zone” from the start of the block until the initialization is processed, during which, it will throw an error if accessed.

typeof aLet;
let aLet;
// Uncaught ReferenceError: Cannot access 'aLet' before initialization
typeof aConst;
const aConst = 'hello world';
// Uncaught ReferenceError: Cannot access 'aConst' before initialization
typeof aConst2
const aConst2
// Uncaught SyntaxError: Missing initializer in const declaration

console.log((0.1+0.2) === 0.3)的結果是什麼?

解決方式:可以先(乘以 10)變為整數後再進行運算(直接避免使用浮點數做運算)

console.log((0.1 * 10 + 0.2 * 10) / 10 === 0.3); // true

或是運算完畢後使用 .toPrecision(1) 讓結果為小數點後一位,再使用 parseFloat() 將型別轉回 Number

console.log(parseFloat((0.1 + 0.2).toPrecision(1)) === 0.3); // true

或是使用現有的套件,比如mathjs

什麼是 hoist?

console.log(v); // 輸出undefined,不會報錯
var v = 'hello world';

console.log(c); // Uncaught ReferenceError: c is not defined
const c = 'hello again';

備註:參考 MDN,各家瀏覽器的錯誤訊息有所差異;分別是 ReferenceError: Cannot access 'c' before initialization (Edge)ReferenceError: can't access lexical declaration 'c' before initialization (Firefox)ReferenceError: 'c' is not defined (Chrome)

undefined、null 與 not defined 之差異?

以下兩種宣告函數的方式有何差異?

console.log(bar());
console.log(foo());

function bar() {
  return 'bar';
}

var foo = () => {
  return 'foo';
};

請列出以下輸出結果

let bar = true;
console.log(bar + 0);
console.log(bar + 'hello world');
console.log(bar + true);
console.log(bar + false);

參考 MDN 範例:

// String + String -> concatenation
'foo' + 'bar'; // "foobar"

// Number + String -> concatenation
5 + 'foo'; // "5foo"

// String + Boolean -> concatenation
'foo' + false; // "foofalse"
// Number + Number -> addition
1 + 2; // 3

// Boolean + Number -> addition
true + 1; // 2

// Boolean + Boolean -> addition
false + false; // 0

轉型成 string 就左右串連起來,如果是 number 就左右相加。

備註:if statement、while statement 與 == 也會觸發隱性的強制轉型,也可有以下操作:

let n = '123';
console.log(typeof +n); // number

何謂 IIFE

(() => {
  console.log('hello world');
})();
// 會直接輸出'hello world'

第二週題目串

何謂閉包(closure)

// 經典範例:function return function
function add(a) {
  return function (b) {
    return a + b;
  };
}
const addFunction = add(3);
console.log(addFunction(4)); // 7

需注意閉包使用過度會有 memory leak 的問題,因為 JS 引擎不會主動處理掉閉包

寫出一函數,使 console.log(mul(2)(3)(4))為 24,console.log(mul(4)(3)(4))為 48

解法如下,closure 與 scope 概念的應用:

const mul = (a) => (b) => (c) => a * b * c;
// 從 const mul = (a) => (b) => (c) => { return a * b * c } 優化而來

function mul(a) {
  return function (b) {
    return function (c) {
      return a * b * c;
    };
  };
}

如何檢查一個變數的型別?

使用 typeof

// 但須注意地雷
console.log(typeof []); // object
console.log(typeof null); // object
console.log(typeof function f() {}); // function,並非object
console.log(typeof undefined); // undefined

使用 Object.prototype.toString.call()

console.log(Object.prototype.toString.call([])); // [object Array]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(function f() {})); // [object Function]
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]

call by reference 與 call by value 之差異

如何拷貝一個陣列或物件

const arr1 = [1, 2, 3];
const arr2 = arr1.slice();
arr1.push(4);
console.log(arr2); // [1, 2, 3]
const arr1 = [1, 2, 3];
const arr2 = arr1.concat();
arr1.push(4);
console.log(arr2); // [1, 2, 3]
const obj1 = { a: 'apple' };
const obj2 = Object.assign({}, obj1);
obj1.b = 'banana';
console.log(obj2); // { a: 'apple' }

const arr1 = [1, 2, 3];
const arr2 = arr1.map((x) => x);
arr1.push(4);
console.log(arr2); // [1, 2, 3]
const obj1 = { a: 'apple' };
const obj2 = JSON.parse(JSON.stringify(arr1));
obc1.b = 'banana';
console.log(obj2); // { a: "apple" }

請回答以下程式碼輸出結果

let pay = '1000';

(() => {
  console.log(`origin pay is ${pay}`);
  var pay = '5000';
  console.log(`new pay is ${pay}`);
})();

什麼是 this

console.log(this); // Window

const obj = {
  firstName: 'Charlie',
  lastName: 'Wang',
  greet: function () {
    console.log(`Hello ${this.firstName}`);
    function nestInGreet() {
      console.log(`this in nestInGreet: ${this}`);
    }
    nestInGreet();
  },
  greetArrow: () => {
    console.log(`Hello ${this.firstName}`);
  },
};

obj.greet();
// 'Hello Charlie'
// 'this in nestInGreet: [object Window]'
// undefined

apply()、bind()與 call()的差異

const obj = {
  firstName: 'Charlie',
  lastName: 'Wang',
};

function greet(a, b) {
  return `${a} ${this.firstName}, ${b}`;
}

console.log(greet.call(obj, 'Hello', 'how are you?')); // Hello Charlie, how are you?
console.log(greet.apply(obj, ['Hello', 'how are you?'])); // Hello Charlie, how are you?
const bindGreet = greet.bind(obj);
console.log(bindGreet('Hello', 'how are you?')); // Hello Charlie, how are you?

第三週題目串

第三週:以下程式碼的輸出結果為?

var hero = {
  _name: 'John Doe',
  getSecretIdentity: function () {
    return this._name;
  },
};

var stoleSecretIdentity = hero.getSecretIdentity;
console.log(stoleSecretIdentity());
console.log(hero.getSecretIdentity());

箭頭函數的特性

(function (a) {
  console.log(arguments);
})()(
  // Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ]

  (b) => {
    console.log(arguments);
  }
)();
// Uncaught ReferenceError: arguments is not defined

console.log(0 && 1 || 1 && 2)的輸出結果為何

參考 MDN 說明:

Logical AND (expr1 && expr2): If expr1 can be converted to true, returns expr2; else, returns expr1.

Logical OR (expr1 || expr2): If expr1 can be converted to true, returns expr1; else, returns expr2.

JS 如何處理非同步?

什麼是 promise?

const myPromise = new Promise((resolve, reject) => {
  const r = Math.floor(Math.random() * 10);
  if (r > 5) {
    resolve('成功');
  } else {
    reject('失敗');
  }
});

myPromise
  .then((result) => console.log(result))
  .catch((error) => console.log(error));

以下程式碼的輸出結果為何?

第一題

function promiseF(num, time = 500) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      num ? resolve(`${num}, success`) : reject('fail');
    }, time);
  });
}

promiseF(0)
  .then((value) => console.log(value))
  .catch((error) => console.log(error));

第二題

const p = new Promise((resolve, reject) => {
  console.log(1);
  resolve(5);
  console.log(2);
  reject(6);
});

p.then((value) => {
  console.log(value);
  console.log(3);
}).catch((error) => {
  console.log(error);
});

console.log(4);

第三題

setTimeout(() => alert('timeout!'));
Promise.resolve().then(() => alert('promise!'));
alert('global alert!');

第四題

console.log(1);

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => console.log(3));
}, 0);

new Promise((resolve, reject) => {
  console.log(4);
  resolve(5);
}).then((data) => console.log(data));

setTimeout(() => {
  console.log(6);
}, 0);

console.log(7);

Browser event loop chart

參考文件