You-Dont-Know-JS 疑難彙總

最近我看了 You-Dont-Know-JS 的兩個小冊,在看書的過程當中,爲了方便之後索引與更深刻的瞭解,也爲了不遺忘,我對每一冊的較爲複雜的點作了總結,編輯以下。javascript

本文地址: blog.xiange.tech/post/js-puz…java

types & grammer

  1. 判斷如下結果git

    var s = 'abc';
    s[1] = 'B';
    
    console.log(s);
    
    var l = new String('abc');
    l[1] = 'B';
    console.log(l);
    複製代碼

    string 及其包裝對象 (Boxed Object) 是不可變 (immutable) 類型,所以不能改變它自己(modify in place),因此 String 的全部方法都是返回一個新的字符串,而不會改變自身。github

  2. 如何逆序一個字符串?數組

    s.split('').reverse().join('')瀏覽器

  3. 接上,爲何不能直接使用 Array.prototype.reverse.call(s) 逆序字符串?app

    當一個數組逆序時 l.reverse() 會改變 l 自己。正如第一題,string 不能改變自身。函數

  4. 判斷如下結果,爲何會出現這樣的狀況,如何作出正確的比較?post

    0.1 + 0.2 === 0.3;
    0.8 - 0.6 === 0.2;
    複製代碼

    浮點數根據 IEEE 754 標準存儲64 bit 雙精度,可以表示 2^64 個數,而浮點數是無窮的,表明有些浮點數必會有精度的損失,0.1,0.2 表示爲二進制會有精度的損失。比較時引入一個很小的數值 Number.EPSILON 容忍偏差,其值爲 2^-52ui

    function equal (a, b) {
      return Math.abs(a - b) < Number.EPSILON
    }
    複製代碼
  5. 如何判斷一個數值爲整數?

    // ES6
    Number.isInteger(num);
    
    // ES5
    if (!Number.isInteger) {
      Number.isInteger = function(num) {
        return typeof num == "number" && num % 1 == 0;
      };
    }
    複製代碼
  6. 如何判斷一個數值爲 +0?

    function isPosZero (n) {
      return n === 0 && 1 / n === Infinity
    }
    複製代碼
  7. 'abc'.toUpperCase() 中 'abc' 做爲 primitive value,如何訪問 toUpperCase 方法

    primitive value 訪問屬性或者方法時,會自動轉化爲它的包裝對象。另外也可使用 Object.prototype.valueOf() 解包裝(Unboxing)。

  8. 判斷如下結果 (Boxing Wrappers)

    function foo() {
      console.log(this)
    }
    
    foo.call(3);
    複製代碼

    Number(3)。理由如上。

  9. 判斷如下結果

    Array.isArray(Array.prototype)
    複製代碼

    true 內置對象的 prototype 都不是純對象,好比 Date.prototype 是 Date,Set.prototype 是 Set。

  10. 判斷如下結果

    Boolean(new Boolean(false));
    Boolean(document.all);
    
    [] == '';
    [3] == 3;
    [] == false;
    42 == true;
    複製代碼

    new Boolean() 返回 object,爲 true document.all,歷史問題,參考這裏 Falsy value 指會被強制轉化爲 false 的值,有如下五種。除此以外所有會轉化爲 true

    • undefined
    • null
    • false
    • +0, -0, and NaN
    • ""

    You-Dont-Know-JS#user-content-toboolean

  11. 找出如下代碼問題 (TDZ)

    var a = 3;
    let a;
    複製代碼

    這是暫時性死域(Temporal Dead Zone)的問題,let a 聲明以前,不能使用 a。

  12. 找出如下代碼問題 (TDZ)

    var x = 3;
    
    function foo (x=x) {
        // ..
    }
    
    foo()
    複製代碼

    一樣,在函數默認參數中,也有 TDZ。

scope & closures

  1. var a = 2 中,EngineScopeCompiler 作了什麼工做

  2. 判斷如下結果 (Lexical Scope)

    var scope = 'global scope';
    function checkScope () {
      var scope = 'local scope';
      function f() {
        return scope; 
      }
      return f;
    }
    
    checkScope()();
    複製代碼

    'local scope'

    因爲 js 爲詞法做用域(Lexical Scope),訪問某個變量時,先在當前做用域中查找,若是查找不到則在嵌套做用域中查找,直到找到。若是找不到,則報 ReferenceError

  3. 判斷如下結果 (Hoisting)

    console.log(a);
    var a = 3;
    複製代碼

    undefined

    以上代碼會被編譯器理解爲

    var a;
    console.log(a);
    a = 3;
    複製代碼
  4. 判斷如下結果 (Function First)

    var foo = 1;
    function foo () {
    
    }
    console.log(foo);
    複製代碼

    1。函數也會有提高,因此會被賦值覆蓋。

  5. 判斷如下結果 (IIFE & Function First)

    var foo = 1;
    (function () {
      foo = 2;
      function foo () {}
    
      console.log(foo);
    })()
    
    console.log(foo);
    複製代碼

    2,1

    以上代碼會被編譯器理解爲以下形式

    var foo = 1;
    (function () {
      var foo;
      function foo () {
      }
    
      foo = 2;
      console.log(foo);
    })()
    
    console.log(foo);
    複製代碼
  6. 判斷如下結果,如何按序輸出 (Closure)

    for (var i = 0; i < 10; i++) {
      setTimeout(function () {
        console.log(i);
      }, 1000)
    }
    複製代碼

    大約 1s 以後連續輸出 10 個 10。由於沒有塊級做用域,能夠把 var 改爲 let,也能夠給 setTimeout 包裝一層 IIFE。

this & object prototypes

注意:如下均爲瀏覽器環境中

  1. 判斷如下結果 (Default Binding)

    function foo() {
     "use strict";
      console.log( this.a );
    }
    
    var a = 2;
    
    foo();
    複製代碼

    會報錯,在函數的嚴格模式下,默認綁定其中的 this 指向 undefined。

  2. 判斷如下結果

    "use strict";
    var a = 2;
    let b = 3;
    
    console.log(this.a, this.b);
    複製代碼

    2, undefined

    在瀏覽器環境中 this 指向 window,而 var 聲明的變量會被掛在 window 上。而 let 聲明的變量不會掛在 window 上。

  3. 判斷如下結果 (Strict Mode & Default Binding)

    function foo() {
      console.log( this.a );
    }
    
    var a = 2;
    
    (function(){
     "use strict";
    
      foo();
    })();
    複製代碼

    2

    只有存在 this 的函數中設置嚴格模式,this 爲 undefined。所以會正常輸出。

  4. 判斷如下結果 (Hard Binding)

    function foo () {
      console.log(this.a);
    }
    
    const o1 = { a: 3 };
    const o2 = { a: 4 };
    
    foo.bind(o1).bind(o2)();
    複製代碼

    3

    bind 爲硬綁定,第一次綁定後 this 沒法再次綁定。

  5. 如何實現 Function.prototype.bindFunction.prototype.softBind

    bind 爲硬綁定,softBind 能夠屢次綁定 this。大體實現代碼以下。bind 第二個參數能夠預設函數參數,因此,bind 也是一個偏函數。另外,bind 也須要考慮 new 的狀況。但如下示例主要集中在硬綁定和軟綁定的差別之上。

    Function.prototype.fakeBind = function (obj) {
      var self = this;
      return function () {
        self.call(obj);
      }
    }
    
    Function.prototype.softBind = function(obj) {
      var self = this;
      return function () {
        self.call(this === window? obj : this);
      }
    };
    複製代碼
  6. new 的過程當中發生了什麼,判斷如下結果 (new)

    function F () {
      this.a = 3;
      return {
        a: 4;
      }
    }
    
    const f = new F();
    console.log(f.a);
    複製代碼

    4

    new 的過程大體爲以下幾個步驟

    1. 建立一個新的對象
    2. this 指向實例,而且執行函數
    3. 若是沒有顯式返回,則默認返回這個實例

    由於函數最後顯式返回了一個對象,因此打印爲 4

  7. 什麼是 data descriptoraccessor descriptor

    二者均經過 Object.defineProperty() 定義,有兩個公有的鍵值

    • configurable 設置該鍵是否能夠刪除
    • enumerable 設置是否可被遍歷

    數據描述符有如下鍵值

    • writable 該鍵是否能夠更改
    • value

    訪問器描述符有如下鍵值

    • set
    • get 另外,也能夠經過字面量的形式表示訪問器描述符
    const obj = {
      get a() {},
      set a(val) {}
    }
    複製代碼

    Vue中 computed 的內部原理即是get,而 watch 的內部原理是 set

  8. 如何訪問一個對象的屬性? ([[Get]])

    訪問對象的屬性會觸發 [[Get]] 操做,大體簡述以下

    1. 是否被 Proxy 攔截,若是攔截,查看攔截器的返回值,若是沒攔截,繼續下一步
    2. 檢查自身屬性,若是沒找到則繼續下一步
    3. 若是沒被找到,則在原型鏈上查找,若是沒找到,則返回 undefined

    查找過程與 Scope 查找變量很類似,只不過,對象屬性找不到,返回 undefined,而變量找不到報 Reference Error。

  9. 如何對一個對象的屬性賦值 ([[Put]])

    對一個對象的屬性賦值會觸發 [[Put]] 操做,大體簡述以下

    1. 檢查是否被 Proxy 攔截
    2. 若是該對象屬性爲自身屬性 (obj.hasOwnProperty('a') === true)
      1. 若是屬性是訪問描述符,則調用 setter 函數
      2. 若是屬性是 data descriptor,則檢查 writable 是否可寫
      3. 普通屬性,直接賦值
    3. 若是該對象屬性存在於原型鏈上
      1. 若是屬性是訪問描述符,則調用 setter 函數
      2. 若是屬性是 data descriptor,則檢查 writable 是否可寫。若是可寫,被自身屬性覆蓋,不然在嚴格模式下將會報錯
      3. 普通屬性,被自身屬性覆蓋
    4. 若是該對象不存在與原型鏈上,直接給自身屬性賦值
  10. 如何遍歷一個對象 ($$iterator)

    給對象設置 Symbol.iterator 屬性

  11. 如何實現一個繼承 (Object.create & call)

    在 ES6 時代能夠簡單的經過 class & extends 實現繼承,ES5 時代用以下方法

    function A () {}
    
    function B () {
      A.call(this)
    }
    
    B.prototype = Object.create(A.prototype)
    // B.prototype = new A() 不推薦
    複製代碼
  12. 如何實現 Object.create

    至於爲何在繼承的時候不推薦new,緣由在於你很難保證 A 是一個純函數,好比它會有自身屬性,有可能操做 DOM 等。如下是一個簡單版本的實現,省略了第二個參數。

    Object.create = function (o) {
      function F() {}
      F.prototype = o;
      return new F();
    }
    複製代碼

關注公衆號山月行,記錄個人技術成長,歡迎交流

歡迎關注公衆號山月行,記錄個人技術成長,歡迎交流
相關文章
相關標籤/搜索