'你不知道的javascript(上卷)讀後感(二)'

JavaScript 程序員新視界 2019-08-11
"

this詞法

熟悉ES6語法的開發者,箭頭函數在涉及this綁定時的行為和普通函數的行為完全不一致。跟普通this綁定規則不一樣,它使用了當前的詞法作用域覆蓋了this本來的值。

誤解

this理解成指向函數本身,函數對象的屬性(Fun.X)不是this.X 【X】

this指向函數的作用域 【X】

this是運行時進行綁定的,而不是編寫時綁定的,它的執行上下文取決於函數調用時的各種條件,this的綁定和函數聲明的位置沒有任何關係,只取決於函數的調用方式(調用位置),具有動態性,簡單地說,函數執行過程中的調用位置決定了this的綁定對象。

何為執行上下文?

函數被調用時,會創建一個活動記錄,這個也稱作執行上下文,這個記錄包含了函數在哪裡被調用(調用棧)、函數調用方式、傳入的參數等信息,而this就是執行上下文的一個屬性,函數調用時會被用到。

this綁定規則

  • 默認綁定,this指向全局對象(嚴格模式,不允許使用)
  • 隱式綁定,會把函數調用的this綁定到所在的上下文對象,對於隱式綁定,常常伴隨著隱式丟失的問題,函數別名、傳入回調函數、函數傳入內置函數均會造成此問題
  • 顯式綁定,就是常見call、apply方法,仍無法解決綁定丟失問題,但有個變種方法,叫做硬綁定

└── 硬綁定,函數bar中強制綁定(顯示綁定)綁定foo在obj中執行

├── 包裹函數
├── 輔助函數(bind基本實現原理,下面有詳細實現)
  • new綁定,過程如下(發生在構造函數調用時):

1、創建(或者說構造)一個新對象

2、這個新對象會被執行[[Porototype]]連接

3、這個新對象會被綁定到函數調用的this

4、如果函數沒有返回其他的對象,那麼new表達式的函數調用會自動返回新對象

規則優先級:默認綁定 < 隱式綁定 < 顯式綁定 < new綁定

☆☆☆☆☆☆ 輔助函數與bind函數的實現 ☆☆☆☆☆☆

 function bind(fn, obj) {
return function() {
fn.apply(obj, arguments);
}
}

摘自MDN:

if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
// this instanceof fBound === true時,說明返回的fBound被當做new的構造函數調用
// 關鍵代碼,如果是new調用,就是使用新創建的this替換硬綁定的this,說明了new綁定優先級大於硬綁定
return fToBind.apply(this instanceof fBound
? this
: oThis,
// 獲取調用時(fBound)的傳參.bind 返回的函數入參往往是這麼傳遞的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
// 維護原型關係
if (this.prototype) {
fNOP.prototype = this.prototype;
}
// 下行的代碼使fBound.prototype是fNOP的實例,因此
// 返回的fBound若作為new的構造函數,new生成的新對象作為this傳入fBound,新對象的__proto__就是fNOP的實例
fBound.prototype = new fNOP();
return fBound;
};
}

更安全的this

bind輔助函數修改改變this的同時,可以使用Object.create(null),如.call/.apply(Object.create(null), ...argument),這樣可以有效防止修改全局對象。

被忽略的this

null/undefined作為this的綁定對象傳入call、apply,使用的是默認綁定規則。

軟綁定

上述對硬綁定的介紹,會強制把this強制綁定到指定的對象上,防止了函數調用應用默認綁定規則,但是造成的問題就是,不夠靈活,使用不了隱式綁定和顯式綁定來修改this,於此同時,軟綁定便出現了。

if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this;
// 捕獲所有curried參數
var curried = [].slice.call(arguments, 1);
var bound = function() {
return fn.apply(
(!this || this === (window || global)) ? obj : this,
curried.concat.apply(curried, arguments)
);
}
bound.prototype = Object.create(fn.prototype);
return bound;
}
}

軟綁定DEMO:

function foo() {
console.log("name:" + this.name);
}
var obj = { name: "obj" };
var obj2 = { name: "obj2" };
var obj3 = { name: "obj3" };
var fooBj = foo.softBind(obj);
fooBj(); // name: obj
obj2.foo = foo.softBind(obj2);
obj2.foo(); // name: obj2
fooBj.call(obj3); // name: obj3
setTimeout(obj2.foo, 10); // name: obj

基本類型、內置對象

基本類型:string、number、boolean、null、undefined、object

內置對象(也是內置函數): String、Number、Boolean、Object、Function、Array、Date、RegExp、Error

日常開發,大家可能會有疑問,比如var a = 'i am string'中a變量,它可以使用a.length、a.charAt(0)等屬性和方法。它本來是一個字符串,為什麼會有類似對象的特性呢,這裡會涉及到一個基本包裝對象,可以怎麼理解這樣的一個概念性知識,一瞬間的引用類型,用完即毀,真正的引用類型會一直存在內存中,而基本包裝對象只會存在一瞬間。

衍生方法:typeof、instanceof、Object.prototype.toString.call

對象

對象的內容是由一些存儲在特定命名位置的值組成的,叫做屬性(指針),有兩種訪問方式,myObject['a']、myObject.a,對象屬性同時也是無序的。

ES6可計算屬性名:

const prefix = 'a';
const myObject = {
[prefix + 'bar']: "xxxx"
}

記住,函數永遠不屬於一個對象,只是一個引用,只是函數的this,會因為隱性綁定,在上下文做一層綁定而已。

複製對象、屬性描述符

  • 深複製,JSON.parse(JSON.stringify(obj))
  • 淺複製,Object.assign(...),會遍歷一個或多個源對象的所有可枚舉的自由鍵到目標對象,源對象的一些特性(如writable)不會被複制

屬性描述符,即writable、configuable、enumerable、value

獲得屬性描述符,Object.getOwnPropertyDescriptor(myObject, 'a')

設置屬性描述符,Object.defineProperty(myObject, "a", {...})

configuable配置為false,不能使用delete關鍵字

enumerable控制屬性是否可枚舉

訪問描述符,(Getter/Setter),可以改寫默認的value,

var myObject = {
get a() {
return 2;
}
}
myObject.a // 2
Object.defineProperty(myObject, "b", {
get: function() {
return this.a * 2 // 指向當前對象
}
});
myObject.b // 4

遍歷對象屬性的幾種方法

  • for...in,遍歷對象自身及原型上可枚舉的屬性
  • Object.keys(),遍歷對象自身可枚舉的屬性
  • Object.getOwnPropertyNames,遍歷對象自身的屬性
  • Object.getOwnPropertySymbol,遍歷對象自身Symbol類型屬性
  • Reflect.ownkeys,遍歷對象自身的屬性(包含不可枚舉屬性,Symbol類型屬性)

對象不可變性

如果你希望對象屬性或是對象是不可改變,可以通過配置對象的屬性描述符來實現,但是這種不可變是一種淺不可變,如果對象中屬性的引用是對象、數組、函數,那麼它們是可變的,實現方式有如下:

  • 對象常量,writable:false | configuable:false
  • 禁止擴展,Object.preventExtensions(obj)
  • 密封,Object.seal(obj)調用禁止擴展,且不能重新配置,及刪除屬性,及configuable:false
  • 凍結,Object.freeze(obj),在密封的基礎上將writable:false

存在性

  • in操作符,檢查屬性是否存在對象裡或是[[Prototype]]原型鏈上
  • Object.hasOwnProperty,只會檢查對象

問題:myObject.hasOwnProperty()可能會報錯,myObject可能是Object.create(null)生成不帶[[Prototype]]原型鏈,而Object.hasOwnProperty是由[[Prototype]]委託,所以可以這樣,Object.prototype.hasOwnProperty.call(myObject, "a")

有坑:

4 in [2,4,6] // false
4 in [2,2,6,8,0] // true

判斷是否可枚舉 myObject.propertyIsEnumerable('a')

@@iterator迭代器對象

for...of被訪問的對象請求一個迭代器對象,然後通過.next()方法遍歷所有返回來的值

數組內置有@@iterator,所以可以直接使用for...of。

使用內置@@iterator遍歷數組:

var myArray = [1,2,3];
var it = myArray[Symbol.iterator](); // 返回迭代器函數
it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }
it.next(); // { value: 3, done: false }
it.next(); // { done: true }

普通對象不含有@@iterator,無法使用for...of,可以進行改造,

var myObject = { a: 2, b: 3 };
Object.defineProperty(myObject, Symbol.iterator, {
enumerable: false,
writable: false,
configuabale: false,
value: function() {
var o = this;
var idx = 0;
var ks = Object.keys(o);
return {
next: function() {
return {
value: o[ks[idx++]],
done: (idx > ks.length)
};
}
}
}
});
vat it = myObject[Symbol.iterator]();
it.next(); // { value: 2, done: false }
it.next(); // { value: 3, done: false }
it.next(); // { done: true }

個人博客地址

"

相關推薦

推薦中...