書評-YDKJSthis與對象原型

第一章:this 是什麼

this 不是編寫時綁定,而是運行時綁定。它依賴於函數調用的上下文條件。 this 綁定與函數聲明的位置沒有任何關係,而與函數被調用的方式緊密相連。javascript

this 既不是函數自身的引用,也不是函數詞法做用域的引用。java

this 其實是在函數被調用時創建的一個綁定,它指向什麼是徹底由函數被調用的調用點來決定的。數組

首先展現一下 this 的動機和用途:安全

function identify() {
	return this.name.toUpperCase();
}

function speak() {
	var greeting = "Hello, I'm " + identify.call( this );
	console.log( greeting );
}

var me = {
	name: "Kyle"
};

var you = {
	name: "Reader"
};

identify.call( me ); // KYLE
identify.call( you ); // READER

speak.call( me ); // Hello, I'm KYLE
speak.call( you ); // Hello, I'm READER
複製代碼

與使用 this 相反地,你能夠明確地將環境對象傳遞給 identify()speak().數據結構

function identify(context) {
	return context.name.toUpperCase();
}

function speak(context) {
	var greeting = "Hello, I'm " + identify( context );
	console.log( greeting );
}

identify( you ); // READER
speak( me ); // Hello, I'm KYLE
複製代碼

this 機制提供了更優雅的方式來隱含地傳遞一個對象引用,致使更加乾淨的API設計和更容易的複用。app

第二章:this 豁然開朗

首先咱們展現一下調用棧和調用點:ide

function baz() {
    // 調用棧是: `baz`
    // 咱們的調用點是 global scope(全局做用域)

    console.log( "baz" );
    bar(); // <-- `bar` 的調用點
}

function bar() {
    // 調用棧是: `baz` -> `bar`
    // 咱們的調用點位於 `baz`

    console.log( "bar" );
    foo(); // <-- `foo` 的 call-site
}

function foo() {
    // 調用棧是: `baz` -> `bar` -> `foo`
    // 咱們的調用點位於 `bar`

    console.log( "foo" );
}

baz(); // <-- `baz` 的調用點
複製代碼

在分析代碼來尋找(從調用棧中)真正的調用點時要當心,由於它是影響 this 綁定的惟一因素。函數

那麼調用點是如何決定在函數執行期間 this 指向哪裏的。ui

咱們有四種 this 綁定規則:默認綁定、隱含綁定、明確綁定、new 綁定。this

斷定 this 的步驟:

  1. 咱們能夠先判斷函數是經過 new 被調用的嘛?若是是, this 就是新構建的函數。

  2. 而後判斷函數是經過 call 或 apply 被調用,甚至是隱藏在 bind 硬綁定中嗎? 若是是,this 就是那個被明確指定的對象。

  3. 而後判斷函數是經過環境對象被調用的嘛? 若是是,this 就是那個環境對象。

  4. 不然,使用默認的 this。若是在 strict mode 下,就是 undefined,不然就是 global 對象。

若是傳遞 null 或 undefined 做爲 call、 apply 或 bind 的 this 綁定參數,那麼這些值會被忽略掉,取而代之的是默認綁定規則將適用於這個調用。這樣會帶來一些潛在的危險。若是你這樣處理一些函數調用,並且那些函數確實使用了 this 引用,那麼默認綁定規則意味着它可能會不經意間引用 global 對象。很顯然,這樣的陷阱會致使多種很是難診斷和追蹤的 bug。安全的作法是經過 Object.create(null) 來做爲綁定參數。

咱們知道硬綁定是一種經過將函數強制綁定到特定的 this 上,來防止函數調用在不經意間退回到默認綁定的策略。問題是,硬綁定極大地下降了函數的靈活性,阻止咱們手動使用隱含綁定或後續的明確綁定來覆蓋 this。

軟化綁定的思路就是爲默認綁定提供不一樣的默認值,同時保持函數能夠經過隱含綁定或明確綁定技術來手動綁定 this。

下面咱們就分別展現硬綁定和軟化綁定的代碼實現 硬綁定

if (!Function.prototype.bind) {
	Function.prototype.bind = function(oThis) {
		if (typeof this !== "function") {
			// 可能的與 ECMAScript 5 內部的 IsCallable 函數最接近的東西,
			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(){
				return fToBind.apply(
					(
						this instanceof fNOP &&
						oThis ? this : oThis
					),
					aArgs.concat( Array.prototype.slice.call( arguments ) )
				);
			};

		fNOP.prototype = this.prototype;
		fBound.prototype = new fNOP();

		return fBound;
	};
}
複製代碼

軟綁定

if (!Function.prototype.softBind) {
	Function.prototype.softBind = function(obj) {
		var fn = this,
			curried = [].slice.call( arguments, 1 ),
			bound = function bound() {
				return fn.apply(
					(!this ||
						(typeof window !== "undefined" &&
							this === window) ||
						(typeof global !== "undefined" &&
							this === global)
					) ? obj : this,
					curried.concat.apply( curried, arguments )
				);
			};
		bound.prototype = Object.create( fn.prototype );
		return bound;
	};
}
複製代碼

第三章:對象

對象是大可能是 JS 程序依賴的基本構建塊兒。

一個常見的錯誤論斷是「JavaScript中的一切都是對象」。這明顯是不對的。好比 string、number、boolean、null、undefined 這幾個簡單基本類型自身不是 object。

一個 JSON 安全的對象能夠簡單地這樣複製:

var newObj = JSON.parse( JSON.stringify( someObj ) );
複製代碼

淺拷貝可使用 Object.assign()來實現。

全部的屬性都用屬性描述符來描述,經過Object.getOwnPropertyDescriptor()來查看,會發現一個對象不只只有 value 值,還有 writable、enumerable 和 configurable。

固然咱們也可使用Object.defineProperty()來添加新屬性或使用指望的性質來修改既存的屬性。

writable 控制着改變屬性值的能力。

configurable 控制着該對象是否可配置

enumerable 控制該對象是否可枚舉

防止擴展:Object.preventExtensions()能夠防止一個對象被添加新的屬性,同時保留其餘既存的對象屬性。

封印:Object.seal()既不能添加更多的屬性,也不能從新配置或刪除既存屬性,可是你依然能夠修改他們的值。

凍結:Object.freeze()阻止任何對對象或對象直屬屬性的改變。

屬性沒必要非要包含值 —— 它們也能夠是帶有 getter/setter 的「訪問器屬性」。

var myObject = {
	// 爲 `a` 定義 getter
	get a() {
		return this._a_;
	},

	// 爲 `a` 定義 setter
	set a(val) {
		this._a_ = val * 2;
	}
};

myObject.a = 2;

myObject.a; // 4
複製代碼

你也可使用 ES6 的 for..of 語法,在數據結構(數組,對象等)中迭代值,它尋找一個內建或自定義的 @@iterator 對象,這個對象由一個 next() 方法組成,經過這個 next() 方法每次迭代一個數據。

var myObject = {
	a: 2,
	b: 3
};

Object.defineProperty( myObject, Symbol.iterator, {
	enumerable: false,
	writable: false,
	configurable: true,
	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)
				};
			}
		};
	}
} );

// 手動迭代 `myObject`
var it = myObject[Symbol.iterator]();
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // { value:undefined, done:true }

// 用 `for..of` 迭代 `myObject`
for (var v of myObject) {
	console.log( v );
}
// 2
// 3
複製代碼

第四章:混合(淆)「類」的對象

類描述了一種特定的代碼組織和結構形式,他有實例化、繼承和多態等機制。

mixin 模式經常使用於在某種程度上模擬類的拷貝行爲,可是這一般致使像顯式假想多態那樣(OtherObj.methodName.call(this, ...))難看並且脆弱的語法,這樣的語法又常致使更難懂和更難維護的代碼。

明確的 mixin 和類拷貝又不徹底相同,由於對象(和函數!)僅僅是共享的引用被複制,不是對象/函數自身被複制。

第五章:原型

每一個普通的 [[prototype]] 鏈的最頂端,是內建的 Object.prototype。

咱們如今來考察 myObject.foo = "bar" 賦值的三種場景,當 foo 不直接存在於 myObject,但存在於 myObject 的 [[Prototype]] 鏈的更高層時:

  1. 若是一個普通的名爲 foo 的數據訪問屬性在 [[Prototype]] 鏈的高層某處被找到,並且沒有被標記爲只讀(writable:false),那麼一個名爲 foo 的新屬性就直接添加到 myObject 上,造成一個遮蔽屬性。

  2. 若是一個 foo 在 [[Prototype]] 鏈的高層某處被找到,可是它被標記爲只讀(writable:false) ,那麼設置既存屬性和在 myObject 上建立遮蔽屬性都是不容許的。若是代碼運行在 strict mode 下,一個錯誤會被拋出。不然,這個設置屬性值的操做會被無聲地忽略。不論怎樣,沒有發生遮蔽。

  3. 若是一個 foo 在 [[Prototype]] 鏈的高層某處被找到,並且它是一個 setter,那麼這個 setter 老是被調用。沒有 foo 會被添加到(也就是遮蔽在)myObject 上,這個 foo setter 也不會被重定義。

咱們經過 instanceof 或者 isPrototypeOf 方法來判斷一個對象是否存在某個原型鏈中。

proto 的代碼實現以下

Object.defineProperty( Object.prototype, "__proto__", {
	get: function() {
		return Object.getPrototypeOf( this );
	},
	set: function(o) {
		// ES6 的 setPrototypeOf(..)
		Object.setPrototypeOf( this, o );
		return o;
	}
} );
複製代碼

Object.create() 的代碼實現以下

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

第六章:行爲委託

行爲委託意味着對象彼此是對等的,在它們本身當中相互委託,而不是父類與子類的關係。JavaScript 的 [[Prototype]] 機制的設計本質,就是行爲委託機制。這意味着咱們能夠選擇掙扎着在 JS 上實現類機制,也能夠欣然接受 [[Prototype]] 做爲委託機制的本性。

面線對象和行爲委託的思惟模式比較以下: 面向對象的代碼形式:

function Foo(who) {
	this.me = who;
}
Foo.prototype.identify = function() {
	return "I am " + this.me;
};

function Bar(who) {
	Foo.call( this, who );
}
Bar.prototype = Object.create( Foo.prototype );

Bar.prototype.speak = function() {
	alert( "Hello, " + this.identify() + "." );
};

var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" );

b1.speak();
b2.speak();
複製代碼

面向行爲委託的代碼形式:

var Foo = {
	init: function(who) {
		this.me = who;
	},
	identify: function() {
		return "I am " + this.me;
	}
};

var Bar = Object.create( Foo );

Bar.speak = function() {
	alert( "Hello, " + this.identify() + "." );
};

var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );

b1.speak();
b2.speak();
複製代碼
相關文章
相關標籤/搜索