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
首先咱們展現一下調用棧和調用點: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 的步驟:
咱們能夠先判斷函數是經過 new 被調用的嘛?若是是, this 就是新構建的函數。
而後判斷函數是經過 call 或 apply 被調用,甚至是隱藏在 bind 硬綁定中嗎? 若是是,this 就是那個被明確指定的對象。
而後判斷函數是經過環境對象被調用的嘛? 若是是,this 就是那個環境對象。
不然,使用默認的 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]] 鏈的更高層時:
若是一個普通的名爲 foo 的數據訪問屬性在 [[Prototype]] 鏈的高層某處被找到,並且沒有被標記爲只讀(writable:false),那麼一個名爲 foo 的新屬性就直接添加到 myObject 上,造成一個遮蔽屬性。
若是一個 foo 在 [[Prototype]] 鏈的高層某處被找到,可是它被標記爲只讀(writable:false) ,那麼設置既存屬性和在 myObject 上建立遮蔽屬性都是不容許的。若是代碼運行在 strict mode 下,一個錯誤會被拋出。不然,這個設置屬性值的操做會被無聲地忽略。不論怎樣,沒有發生遮蔽。
若是一個 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();
複製代碼