你真的瞭解 JS 嗎

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

本文地址: https://shanyue.tech/post/js-puzzles/git

types & grammer

  1. 判斷如下結果github

    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 的全部方法都是返回一個新的字符串,而不會改變自身。web

    • You-Dont-Know-JS [1]
  2. 如何逆序一個字符串?面試

    s.split('').reverse().join('')前端工程化

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

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

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

    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^-52微信

    function equal (a, b) {
    return Math.abs(a - b) < Number.EPSILON
    }
    • You-Dont-Know-JS [2]
    • 知乎 [3]
  5. 如何判斷一個數值爲整數?

    // ES6
    Number.isInteger(num);

    // ES5
    if (!Number.isInteger) {
    Number.isInteger = function(num) {
    return typeof num == "number" && num % 1 == 0;
    };
    }
    • You-Dont-Know-JS#user-content-testing-for-integers [4]
  1. 如何判斷一個數值爲 +0?

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

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

    • You-Dont-Know-JS#user-content-boxing-wrappers [5]
  3. 判斷如下結果 (Boxing Wrappers)

    function foo() {
    console.log(this)
    }

    foo.call(3);

    Number(3)。理由如上。

  4. 判斷如下結果

    Array.isArray(Array.prototype)

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

    • You-Dont-Know-JS#user-content-native-prototypes [6]
  5. 判斷如下結果

    Boolean(new Boolean(false));
    Boolean(document.all);

    [] == '';
    [3] == 3;
    [] == false;
    42 == true;

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

    You-Dont-Know-JS#user-content-toboolean[8]

    • undefined
    • null
    • false
    • +0, -0, and NaN
    • ""
  1. 找出如下代碼問題 (TDZ)

    var a = 3;
    let a;

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

  2. 找出如下代碼問題 (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 的過程大體爲以下幾個步驟

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

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

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

    數據描述符有如下鍵值

    訪問器描述符有如下鍵值

    const obj = {
    get a() {},
    set a(val) {}
    }

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

    • set
    • get 另外,也能夠經過字面量的形式表示訪問器描述符
    • writable 該鍵是否能夠更改
    • value
    • configurable 設置該鍵是否能夠刪除
    • enumerable 設置是否可被遍歷
  8. 如何訪問一個對象的屬性?([[Get]])

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

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

    1. 是否被 Proxy 攔截,若是攔截,查看攔截器的返回值,若是沒攔截,繼續下一步
    2. 檢查自身屬性,若是沒找到則繼續下一步
    3. 若是沒被找到,則在原型鏈上查找,若是沒找到,則返回 undefined
  9. 如何對一個對象的屬性賦值 ([[Put]])

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

    1. 若是屬性是訪問描述符,則調用 setter 函數
    2. 若是屬性是 data descriptor,則檢查 writable 是否可寫。若是可寫,被自身屬性覆蓋,不然在嚴格模式下將會報錯
    3. 普通屬性,被自身屬性覆蓋
    4. 若是屬性是訪問描述符,則調用 setter 函數
    5. 若是屬性是 data descriptor,則檢查 writable 是否可寫
    6. 普通屬性,直接賦值
    7. 檢查是否被 Proxy 攔截
    8. 若是該對象屬性爲自身屬性 (obj.hasOwnProperty('a') === true)
    9. 若是該對象屬性存在於原型鏈上
    10. 若是該對象不存在與原型鏈上,直接給自身屬性賦值
  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() 不推薦
  1. 如何實現 Object.create

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

    Object.create = function (o) {
    function F() {}
    F.prototype = o;
    return new F();
    }

關注我

點擊閱讀原文,方便訪問本篇文章頁面連接。能夠添加我微信 shanyue94 交流,備註崗位與來源信息。也能夠添加我微信加羣,備註加羣。

若是你對全棧面試,前端工程化,graphql,devops,我的服務器運維以及微服務感興趣的話,能夠關注我

參考資料

[1]

You-Dont-Know-JS: https://github.com/getify/You-Dont-Know-JS/blob/master/types%20%26%20grammar/ch2.md#user-content-strings

[2]

You-Dont-Know-JS: https://github.com/getify/You-Dont-Know-JS/blob/master/types%20%26%20grammar/ch2.md#user-content-small-decimal-values

[3]

知乎: https://www.zhihu.com/question/28551135

[4]

You-Dont-Know-JS#user-content-testing-for-integers: https://github.com/getify/You-Dont-Know-JS/blob/master/types%20%26%20grammar/ch2.md#user-content-testing-for-integers

[5]

You-Dont-Know-JS#user-content-boxing-wrappers: https://github.com/getify/You-Dont-Know-JS/blob/master/types%20%26%20grammar/ch3.md#user-content-boxing-wrappers

[6]

You-Dont-Know-JS#user-content-native-prototypes: https://github.com/getify/You-Dont-Know-JS/blob/master/types%20%26%20grammar/ch3.md#user-content-native-prototypes

[7]

這裏: https://stackoverflow.com/questions/10350142/why-is-document-all-falsy

[8]

You-Dont-Know-JS#user-content-toboolean: https://github.com/getify/You-Dont-Know-JS/blob/master/types%20%26%20grammar/ch4.md#user-content-toboolean


本文分享自微信公衆號 - 壹前端(yiqianduan)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索