最近我看了 You-Dont-Know-JS 的兩個小冊,在看書的過程當中,爲了方便之後索引與更深刻的瞭解,也爲了不遺忘,我對每一冊的較爲複雜的點作了總結,編輯以下。javascript
本文地址: blog.xiange.tech/post/js-puz…java
判斷如下結果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
如何逆序一個字符串?數組
s.split('').reverse().join('')
瀏覽器
接上,爲何不能直接使用 Array.prototype.reverse.call(s)
逆序字符串?app
當一個數組逆序時
l.reverse()
會改變 l 自己。正如第一題,string
不能改變自身。函數
判斷如下結果,爲何會出現這樣的狀況,如何作出正確的比較?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^-52
。uifunction equal (a, b) { return Math.abs(a - b) < Number.EPSILON } 複製代碼
如何判斷一個數值爲整數?
// ES6
Number.isInteger(num);
// ES5
if (!Number.isInteger) {
Number.isInteger = function(num) {
return typeof num == "number" && num % 1 == 0;
};
}
複製代碼
如何判斷一個數值爲 +0?
function isPosZero (n) {
return n === 0 && 1 / n === Infinity
}
複製代碼
'abc'.toUpperCase()
中 'abc' 做爲 primitive value,如何訪問 toUpperCase
方法
當
primitive value
訪問屬性或者方法時,會自動轉化爲它的包裝對象。另外也可使用Object.prototype.valueOf()
解包裝(Unboxing)。
判斷如下結果 (Boxing Wrappers)
function foo() {
console.log(this)
}
foo.call(3);
複製代碼
Number(3)。理由如上。
判斷如下結果
Array.isArray(Array.prototype)
複製代碼
true 內置對象的 prototype 都不是純對象,好比
Date.prototype
是 Date,Set.prototype
是 Set。
判斷如下結果
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
- ""
找出如下代碼問題 (TDZ)
var a = 3;
let a;
複製代碼
這是暫時性死域(Temporal Dead Zone)的問題,let a 聲明以前,不能使用 a。
找出如下代碼問題 (TDZ)
var x = 3;
function foo (x=x) {
// ..
}
foo()
複製代碼
一樣,在函數默認參數中,也有 TDZ。
var a = 2
中,Engine
,Scope
,Compiler
作了什麼工做
判斷如下結果 (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
。
判斷如下結果 (Hoisting)
console.log(a);
var a = 3;
複製代碼
undefined
以上代碼會被編譯器理解爲
var a; console.log(a); a = 3; 複製代碼
判斷如下結果 (Function First)
var foo = 1;
function foo () {
}
console.log(foo);
複製代碼
1。函數也會有提高,因此會被賦值覆蓋。
判斷如下結果 (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); 複製代碼
判斷如下結果,如何按序輸出 (Closure)
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i);
}, 1000)
}
複製代碼
大約 1s 以後連續輸出 10 個 10。由於沒有塊級做用域,能夠把 var 改爲 let,也能夠給 setTimeout 包裝一層 IIFE。
注意:如下均爲瀏覽器環境中
判斷如下結果 (Default Binding)
function foo() {
"use strict";
console.log( this.a );
}
var a = 2;
foo();
複製代碼
會報錯,在函數的嚴格模式下,默認綁定其中的 this 指向 undefined。
判斷如下結果
"use strict";
var a = 2;
let b = 3;
console.log(this.a, this.b);
複製代碼
2, undefined
在瀏覽器環境中 this 指向 window,而 var 聲明的變量會被掛在 window 上。而 let 聲明的變量不會掛在 window 上。
判斷如下結果 (Strict Mode & Default Binding)
function foo() {
console.log( this.a );
}
var a = 2;
(function(){
"use strict";
foo();
})();
複製代碼
2
只有存在 this 的函數中設置嚴格模式,this 爲 undefined。所以會正常輸出。
判斷如下結果 (Hard Binding)
function foo () {
console.log(this.a);
}
const o1 = { a: 3 };
const o2 = { a: 4 };
foo.bind(o1).bind(o2)();
複製代碼
3
bind 爲硬綁定,第一次綁定後 this 沒法再次綁定。
如何實現 Function.prototype.bind
與 Function.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); } }; 複製代碼
new
的過程當中發生了什麼,判斷如下結果 (new)
function F () {
this.a = 3;
return {
a: 4;
}
}
const f = new F();
console.log(f.a);
複製代碼
4
new
的過程大體爲以下幾個步驟
- 建立一個新的對象
- this 指向實例,而且執行函數
- 若是沒有顯式返回,則默認返回這個實例
由於函數最後顯式返回了一個對象,因此打印爲 4
什麼是 data descriptor
和 accessor descriptor
二者均經過
Object.defineProperty()
定義,有兩個公有的鍵值
- configurable 設置該鍵是否能夠刪除
- enumerable 設置是否可被遍歷
數據描述符有如下鍵值
- writable 該鍵是否能夠更改
- value
訪問器描述符有如下鍵值
- set
- get 另外,也能夠經過字面量的形式表示訪問器描述符
const obj = { get a() {}, set a(val) {} } 複製代碼
Vue中
computed
的內部原理即是get
,而watch
的內部原理是set
如何訪問一個對象的屬性? ([[Get]])
訪問對象的屬性會觸發 [[Get]] 操做,大體簡述以下
- 是否被 Proxy 攔截,若是攔截,查看攔截器的返回值,若是沒攔截,繼續下一步
- 檢查自身屬性,若是沒找到則繼續下一步
- 若是沒被找到,則在原型鏈上查找,若是沒找到,則返回 undefined
查找過程與 Scope 查找變量很類似,只不過,對象屬性找不到,返回 undefined,而變量找不到報 Reference Error。
如何對一個對象的屬性賦值 ([[Put]])
對一個對象的屬性賦值會觸發 [[Put]] 操做,大體簡述以下
- 檢查是否被 Proxy 攔截
- 若是該對象屬性爲自身屬性 (obj.hasOwnProperty('a') === true)
- 若是屬性是訪問描述符,則調用 setter 函數
- 若是屬性是 data descriptor,則檢查 writable 是否可寫
- 普通屬性,直接賦值
- 若是該對象屬性存在於原型鏈上
- 若是屬性是訪問描述符,則調用 setter 函數
- 若是屬性是 data descriptor,則檢查 writable 是否可寫。若是可寫,被自身屬性覆蓋,不然在嚴格模式下將會報錯
- 普通屬性,被自身屬性覆蓋
- 若是該對象不存在與原型鏈上,直接給自身屬性賦值
如何遍歷一個對象 ($$iterator)
給對象設置 Symbol.iterator 屬性
如何實現一個繼承 (Object.create & call)
在 ES6 時代能夠簡單的經過 class & extends 實現繼承,ES5 時代用以下方法
function A () {} function B () { A.call(this) } B.prototype = Object.create(A.prototype) // B.prototype = new A() 不推薦 複製代碼
如何實現 Object.create
至於爲何在繼承的時候不推薦
new
,緣由在於你很難保證 A 是一個純函數,好比它會有自身屬性,有可能操做 DOM 等。如下是一個簡單版本的實現,省略了第二個參數。Object.create = function (o) { function F() {} F.prototype = o; return new F(); } 複製代碼
關注公衆號山月行,記錄個人技術成長,歡迎交流