this和對象原型正則表達式
第一章 關於thischrome
1.1 爲何要用this設計模式
this 提供了一種更優雅的方式來隱式「傳遞」一個對象引用,所以能夠將 API 設計 得更加簡潔而且易於複用。顯式傳遞上下文對象會讓代碼變得愈來愈混亂,使用 this 則不會這樣。數組
1.2 誤解瀏覽器
1.2.1 指向自身安全
除了函數對象,還有更多更適合存儲狀態的地方。 函數表達式須要引用自身時(好比使用遞歸,或者定義本身的屬性和方法),應當要給函數表達式具名,而不是使用arguments.callee(已經被棄用和批判的用法)。app
function foo(num) { console.log( "foo: " + num ); this.count++; // 記錄 foo 被調用的次數 // 記錄 foo 被調用的次數改成 // foo.count++;,能夠獲得正確答案4,但迴避了this問題 } foo.count = 0; var i; for (i=0; i<10; i++) { if (i > 5) { foo( i ); // 使用 call(..) 能夠確保 this 指向函數對象 foo 自己 // foo.call( foo, i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // foo 被調用了多少次? console.log( foo.count ); // 0 -- WTF? *執行 foo.count = 0 時,的確向函數對象 foo 添加了一個屬性 count。可是函數內部代碼 this.count 中的 this 並非指向那個函數對象,因此雖然屬性名相同,根對象卻並不相 同,困惑隨之產生。
若是要從函數對象內部引用它自身,那隻使用 this 是不夠的。通常來講你須要經過一個指 向函數對象的詞法標識符(變量)來引用它。dom
1.2.2 它的做用域異步
this在任何狀況下都不指向函數的詞法做用域。ide
做用域「對象」沒法經過 JavaScript 代碼訪問,它存在於 JavaScript 引擎內部。
1.3 this究竟是什麼
this是在運行時綁定的,而不是在編寫時綁定,它的上下文取決於函數調用時的各類條件。this 的綁定和函數聲明的位置沒有任何關係,this只取決於函數的調用方式。
當一個函數被調用時,會建立一個活動記錄(有時候也稱爲執行上下文)。這個記錄會包 含函數在哪裏被調用(調用棧)、函數的調用方法、傳入的參數等信息。this 就是記錄的 其中一個屬性,會在函數執行的過程當中用到。
第二章 this全面解析
2.1 調用位置
調用位置是函數在代碼中被調用的位置,最重要的是要分析調用棧。 好比
function baz(){ // 當前調用棧是baz,調用位置是全局做用域 console.log(this); // window || global bar(); } function bar(){ // 當前調用棧是baz -> bar,調用位置在baz中 // 能夠拿到baz詞法做用域的變量,可是和this無關 console.log(this); // window || global foo(); } function foo(){ // 當前調用棧是baz -> bar -> foo,調用位置在bar中 // 能夠拿到bar和baz詞法做用域的變量,可是和this無關 console.log(this) // window || global } baz(); // baz的調用位置
2.2 綁定規則
2.2.1 默認綁定
this默認指向全局對象。 嚴格模式下,若是this找不到具體對象,就是undefined。 但若是函數聲明的區域不是嚴格模式的,即便在嚴格模式下調用,也不影響它將默認的this指向全局。
function foo() { console.log( this.a ); } var a = 2; foo(); // 2 //當調用 foo() 時,this.a 被解析成了全局變量 a。函數調用時應用了 this 的默認綁定,所以 this 指向全局對象。 //。在代碼中,foo() 是直接使用不帶任何修飾的函數引用進行調用的,所以只能使用 默認綁定,沒法應用其餘規則。 //若是使用嚴格模式(strict mode),那麼全局對象將沒法使用默認綁定 //此 this 會綁定 到 undefined: function foo() { "use strict"; console.log( this.a ); } var a = 2; foo(); // TypeError: this is undefined
2.2.2 隱式綁定
this隱式綁定的,是整個調用鏈上,處於方法上一層的對象。
function foo(){ console.log(this.a) } var obj = { a: 2, foo: foo, }; obj.foo(); //2 //其實等效於,JS的函數調用其實更像是一種語法糖 obj.foo.call(obj); //當函數引 用有上下文對象時,隱式綁定規則會把函數調用中的 this 綁定到這個上下文對象。由於調 用 foo() 時 this 被綁定到 obj,所以 this.a 和 obj.a 是同樣的。
隱式丟失
須要特別注意的是,回調函數丟失this綁定是很是常見的
function foo() { console.log( this.a ); } function doFoo(fn) { // fn 其實引用的是 foo fn(); // <-- 調用位置! } var obj = { a: 2, foo: foo }; var a = "oops, global"; // a 是全局對象的屬性 doFoo( obj.foo ); // "oops, global" //參數傳遞其實就是一種隱式賦值,所以咱們傳入函數時也會被隱式賦值
由於調用它的操做一般不包含隱式綁定和顯式綁定。 這個時候能夠選擇:
2.2.3 顯式綁定
call和apply的效果同樣,傳入參數的方式不同。 call從第二個參數開始,逐個傳入。 apply的第二個參數是一個Array,參數放在Array裏。 bind的參數形式相似於call,可是返回的是顯式綁定後的函數對象,須要後續去調用執行。 bind經常使用於柯里化一個函數對象。
硬綁定
建立了函數 bar(),並在它的內部手動調用 了 foo.call(obj),所以強制把 foo 的 this 綁定到了 obj。不管以後如何調用函數 bar,它 總會手動在 obj 上調用 foo。這種綁定是一種顯式的強制綁定,所以咱們稱之爲硬綁定。
【硬綁定的典型應用場景】就是建立一個包裹函數,傳入全部的參數並返回接收到的全部值;另外一種使用方法是建立一個 i 能夠重複使用的輔助函數
function foo(something) { console.log( this.a, something ); return this.a + something; } var obj = { a:2 }; var bar = function() { return foo.apply( obj, arguments ); }; var b = bar( 3 ); // 2 3 console.log( b ); // 5 // ES6寫法 Function.prototype._bind = function(thisArg, ...args){ return (...newArgs) => this.call(thisArg, ...args, ...newArgs); } // ES5寫法 Function.prototype._bind = function(thisArg){ var args = [].slice.call(arguments, 1); var fn = this; return function(){ var newArgs = [].slice.call(arguments, 0); return fn.apply(thisArg, args.concat(newArgs)); }; } //MDN提供的polyfill if(!Function.prototype.bind) { Function.prototype.bind = function(oThis) { // 類型判斷 if(typeof this !== 'function'){ throw new TypeError('Function.prototype.bind - what is trying to be found 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; } }
2.2.4 new綁定
實際上並不存在所謂的「構造函數」,只有對於函數的「構造調用」。
用new來調用函數,會自動執行下面的操做:
若是函數沒有返回其餘對象,那麼new表達式中的函數調用會自動返回這個新對象。
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
//new 來調用 foo(..) 時,咱們會構造一個新對象並把它綁定到 foo(..) 調用中的 this 上
2.3 優先級
2.4 綁定例外
2.4.1 被忽略的this (空對象)
顯式綁定中,若是對this不敏感,能夠傳入null,可是可能有反作用。 更安全的作法是,傳入一個空對象,即var empty = Object.create(null),它連指向Object.prototype的proto都沒有,比{}更空。
function foo(a,b) { console.log( "a:" + a + ", b:" + b ); } // 咱們的 DMZ 空對象 var ø = Object.create( null ); // 把數組展開成參數 foo.apply( ø, [2, 3] ); // a:2, b:3 // 使用 bind(..) 進行柯里化 var bar = foo.bind( ø, 2 ); bar( 3 ); // a:2, b:3 //使用變量名 ø 不只讓函數變得更加「安全」,並且能夠提升代碼的可讀性,由於 ø 表示 「我但願 this 是空」,這比 null 的含義更清楚。
2.4.2 間接引用
(p.foo = o.foo)()這裏返回的引用是foo,而不是p.foo,嚴格模式下,它的this指向會指向undefined。
2.4.3 軟綁定
硬綁定會大大下降函數的靈活性,使 用硬綁定以後就沒法使用隱式綁定或者顯式綁定來修改 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; }; }
2.5 this詞法
lambda表達式(箭頭函數)的this是根據外層做用域來決定。 lambda表達式經常使用於回調函數,好比eventListener和setTimeout操做中。
function foo() { // 返回一個箭頭函數 return (a) => { // 這裏的this實際上是foo中的this console.log(this.a); }; } var obj1 = {a:2}, obj2 = {a:3}; // bar的this指向foo的this,foo的this此時顯式綁定了obj1 var bar = foo.call(obj1); // 雖然顯式綁定了bar的this,可是這種方法對箭頭函數不起做用 // 它的this依然指向先前foo被顯式綁定的obj1 bar.call(obj2); //2
箭頭函數最經常使用於回調函數中,例如事件處理器或者定時器
ES6 中的箭頭函數並不會使用四條標準的綁定規則,而是根據當前的詞法做用域來決定 this,具體來講,箭頭函數會繼承外層函數調用的 this 綁定(不管 this 綁定到什麼)。這 其實和 ES6 以前代碼中的 self = this 機制同樣。
第三章 對象
3.1 語法
//文字語法(聲明形式)(建立簡單對象時推薦使用) var myObj = { key: value, }; //構造形式 var myObj = new Object(); myObj.key = value;
3.2 類型
JS一共有七種主要類型(語言類型):
注意,簡單基本類型(string、boolean、number、null 和 undefined)自己並非對象。 null 有時會被看成一種對象類型,可是這其實只是語言自己的一個 bug,即對 null 執行 typeof null 時會返回字符串 "object"。1 實際上,null 自己是基本類型。
❤ 注 1: 原理是這樣的,不一樣的對象在底層都表示爲二進制,在 JavaScript 中二進制前三位都爲 0 的話會被判 斷爲 object 類型, null 的二進制表示是全 0,天然前三位也是 0,因此執行 typeof 時會返回「object」。
console.log(typeof null); //object console.log(typeof string); //undefined console.log(typeof number); //undefined console.log(typeof String); //function console.log(typeof Number);//function
內置對象(注意第一個字母大寫,這些都是構造函數):
在 JavaScript 中,它們實際上只是一些內置函數。這些內置函數能夠看成構造函數 (由 new 產生的函數調用——參見第 2 章)來使用,從而能夠構造一個對應子類型的新對 象。
補充:js中的基本類型和引用類型
var strPrimitive = "I am a string"; typeof strPrimitive; // "string" strPrimitive instanceof String; // false //typeof:肯定變量是字符串、數值、布爾值仍是undefined的最佳工具。 //instanceof :判斷是不是某個對象類型。 var strObject = new String( "I am a string" ); typeof strObject; // "object" strObject instanceof String; // true // 檢查 sub-type 對象 Object.prototype.toString.call( strObject ); // [object String]
3.3 內容
3.3.1 可計算屬性名
ES6支持在鍵訪問中傳入一個表達式來看成屬性名,好比myObj[prefix + 'foo']。在文字形式中使用 [] 包裹一個表達式來看成屬性名
var prefix = "foo"; var myObject = { [prefix + "bar"]:"hello", }; myObject["foobar"]; // hello
3.3.2 屬性與方法
屬於對象的函數一般稱爲方法。
即便你在對象的文字形式中聲明一個函數表達式,這個函數也不會「屬於」這個對象—— 它們只是對於相同函數對象的多個引用
3.3.3 數組
數組經過數字下標[索引]訪問。 數組能夠添加命名屬性,可是不會改變其length值。
var myArray = [ "foo", 42, "bar" ]; myArray.baz = "baz"; myArray.length; // 3 myArray.baz; // "baz"
注意:若是你試圖向數組添加一個屬性,可是屬性名「看起來」像一個數字,那它會變成 一個數值下標(所以會修改數組的內容而不是添加一個屬性):
var myArray = [ "foo", 42, "bar" ]; myArray["3"] = "baz"; myArray.length; // 4 myArray[3]; // "baz"
3.3.4 複製對象
ES6中可使用Object.assign(目標對象,源對象)方法(淺複製)
因爲 Object.assign(..) 就是使用 = 操做符來賦值,所 以源對象屬性的一些特性(好比 writable)不會被複制到目標對象。
const cloned = Object.assign(
// 生成原型鏈
Object.create(Object.getPrototypeOf(target)),
target
);
須要注意的是,PropertyDescriptor不會被按原樣複製,而是保持默認值。
3.3.5 屬性描述符
從 ES5 開始,全部的屬性都具有了屬性描述符。
var myObject = { a:2 }; Object.getOwnPropertyDescriptor( myObject, "a" ); { value: 2, writable: true, //writable(可寫) enumerable: true, //enumerable(可枚舉) configurable: true // configurable(可配置) } //也可使用 Object.defineProperty(..) 來添加一個新屬性或者修改一個已有屬性(若是它是 configurable)並對特性進行設置 var myObject = {}; Object.defineProperty( myObject, "a", { value: 2, writable: true, configurable: true, enumerable: true } ); //只要屬性是可配置的,就可使用 defineProperty(..)
一個descriptor有6個可能的屬性,同時只能擁有其中4個(分爲基本類型和引用類型):
3.3.6 不變性
結合writable: false和configurable: false就能夠建立一個真正的常量屬性。
var myObject = {};
Object.defineProperty( myObject, 'Favorite_Number', {
value: 22,
writable: false,
configurable: false,
});
凍結,使用Object.freeze( targetObject ),在seal基礎上在將屬性改成writable: false,只讀。(「深度凍結」一個對象,具體方法爲,首先在這個對象上調用 Object.freeze(..), 而後遍歷它引用的全部對象並在這些對象上調用 Object.freeze(..)。)
3.3.7 [[Get]]
3.3.8 [[Put]]
給對象的屬性賦值會觸發 [[Put]] 來設置或者建立這個屬性(實際狀況複雜)。
若是屬性已經存在,Put會檢查:
3.3.9 Getter和Setter
在 ES5 中可使用 getter 和 setter 部分改寫默認操做,可是隻能應用在單個屬性上,沒法 應用在整個對象上。
getter 是一個隱藏函數,會在獲取屬性值時調用。setter 也是一個隱藏 函數,會在設置屬性值時調用。
當一個屬性定義 getter、setter 或者二者都有時,這個屬性會被定義爲「訪問描述 符」(和「數據描述符」相對)。對於訪問描述符來講,JavaScript 會忽略它們的 value 和 writable 特性,取而代之的是關心 set 和 get(還有 configurable 和 enumerable)特性。
定義方式
// 直接式 var myObject = { // 給a定義一個getter,此時再去訪問a,只會得到2 get a(){ return 2; }, // 不是單純的setter,而會取兩倍的值 set a(val){ this._a_ = val * 2; } //即使有合法的 setter,因爲咱們自定義的 getter 只會返回 2,因此 set 操做是 沒有意義的。 } // descriptor式 Object.defineProperty(myObject, 'b', { get: function(){ return this._b_; //名稱 _b_ 只是一種慣例,沒有任何特殊的行爲——和其餘普通屬性 同樣。 }, set: function(val){ this._b_ = val*2; }, enumerable: true,//確保b會出如今對象的屬性列表中 }); myObject.b = 2 console.log(myObject.b)//4
3.3.10 存在性(如何區分undefined)
("a" in myObject); // true
("b" in myObject); // false
myObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "b" ); // false
hasOwnProperty只會檢查屬性是否在對象中,不會檢查原型鏈。
3.3.11 可枚舉性
可枚舉,至關於能夠出如今對象屬性的遍歷中。 propertyIsEnumerable會檢查給定的屬性名是否直接存在於對象中(而不是原型鏈中),而且知足enumerable: true。
var myObject = { }; Object.defineProperty( myObject, "a", // 讓 a 像普通屬性同樣能夠枚舉 { enumerable: true, value: 2 } ); Object.defineProperty( myObject, "b", // 讓 b 不可枚舉 { enumerable: false, value: 3 } ); myObject.b; // 3 ("b" in myObject); // true myObject.hasOwnProperty( "b" ); // true //in 和 hasOwnProperty(..) 的區別在因而否查找 [[Prototype]] 鏈 for (var k in myObject) { console.log( k, myObject[k] ); } // "a" 2 //myObject.b 確實存在而且有訪問值,可是卻不會出如今 for..in 循環中 myObject.propertyIsEnumerable( "a" ); // true myObject.propertyIsEnumerable( "b" ); // false Object.keys( myObject ); // ["a"] //Object.keys(..) 會返回一個數組,包含全部可枚舉屬性 Object.getOwnPropertyNames( myObject ); // ["a", "b"] //Object.getOwnPropertyNames(..) 會返回一個數組,包含全部屬性,不管它們是否可枚舉。
3.4 遍歷
使用Symbol.iterator對象,能夠用來定義對象的@@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 },這個機制和 ES6 中發生器函數的語義相 關
和數組不一樣,普通的對象沒有內置的 @@iterator,因此沒法自動完成 for..of 遍歷。簡單來講,這樣作是爲了不影響將來的對象 類型。,咱們能夠給須要遍歷的對象定義@@iterator
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), }; }, }; } }); 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 定義一個無限迭代器,如產生隨機數等
var randoms = { [Symbol.iterator]: function() { return { next: function() { return { value: Math.random() }; } }; } }; var randoms_pool = []; for (var n of randoms) { randoms_pool.push( n ); // 防止無限運行! if (randoms_pool.length === 100) break; }
第四章 混合對象「類」
面向類的設計模式:實例化(instantiation),繼承(inheritance),(相對)多態(polymorphism)。
4.1 類理論
類理論包括:
4.4.1 類設計模式
面向對象的設計模式有,迭代器模式、觀察者模式、工廠模式、單例模式等。
4.4.2 JavaScript中的類
JS中的類是構造函數與原型的結合,與其餘語言的類不一樣。
4.2 類的機制
4.2.1 建造
爲了得到真正能夠交互的對象,咱們必須按照類來建造一個東西,這個東西一般被稱爲實例。有須要的話,咱們能夠直接在實例上調用方法並訪問其全部公有數據屬性。
4.2.2 構造函數
類實例是由一個特殊的類方法構造的,這個方法名和類名相同,被稱爲構造函數。這個方法的任務是初始化實例所須要的全部信息及屬性。
function CoolGuy() { this.name = 'John'; }
構造函數須要用new來調用,這樣引擎纔會去構造一個新的類實例。
4.3 類的繼承
子類須要繼承父類的屬性與原型方法。
4.3.1 多態
子類能夠重寫父類方法。 子類重寫的方法在子類的prototype上,不會改變父類的prototype,只是子類在尋找方法時,會跟隨原型鏈,率先找處處於自身原型上的方法,從而調用。 ES6用super代指父類的構造函數,從而能讓子類經過這種方式找到父類的原型及其方法。
4.3.2 多重繼承
JS的繼承是單向的。 JS不支持多重繼承,所以使用混入模式實現多重繼承。
4.4 混入
JavaScript 中只有對象,並不存在能夠被實例化的「類」。一個對象並不會被複制到其餘對 象,它們會被關聯起來。JavaScript 開發者也想出了一個方法來 模擬類的複製行爲,這個方法就是混入。
4.4.1 顯式混入
JavaScript 中的函數沒法(用標準、可靠的方法)真正地複製,因此你只能複製對共享 函數對象的引用(函數就是對象;參見第 3 章)。若是你修改了共享的函數對象(好比 ignition()),好比添加了一個屬性,那 Vehicle 和 Car 都會受到影響
// 不覆蓋屬性 function mixinWithoutOverwrite(source, target) { for (var key in source){ // 只在不存在的狀況下複製 if( !(key in target) ){ target[key] = source[key]; } } return target; } //覆蓋屬性 //若是咱們是先進行復制而後對 Car 進行特殊化的話,就能夠跳過存在性檢查。不過這種方 法並很差用而且效率更低,因此不如第一種方法經常使用: function mixinWithOverwrite(source, target) { for (var key in source){ target[key] = source[key]; } return target; } //。從技術角度來講,函數實際上沒有 被複制,複製的是函數引用(對象數組)。
另外一種變體 — 是寄生式繼承,將父類的實例混入子類。它既是顯式的又是隱式的
function Super() { this.name = 'father'; } Super.prototype.say = function() { console.log('Method from father'); } function Sub() { // 父類實例 var father = new Super(); father.name = 'son'; // 保存引用 var extendedSay = father.say; // 多態重寫 father.say = function() { extendedSay.call(this); console.log('Method from son'); } return father; } // 調用時無需用new。是由於咱們沒有使用這個對象而是返回了咱們本身的 car 對象,所 以最初被建立的這個對象會被丟棄,所以能夠不使用 new 關鍵字調用 Car(),能夠避免建立並丟棄多餘的對象 var son = Sub();
4.4.2隱式混入
隱式混入其實就是把其餘對象的方法拿過來使用,使用call、apply、或bind,將其this指針指向自身。(儘可能避免使用這樣 的結構,以保證代碼的整潔和可維護性。)
var landLord = { name: 'himself', checkMoney: function() { console.log('Landlord is giving money to ' + this.name); }, } var robinHood = { name: 'Robin Hood', rob: function() { // 把其餘對象的方法拿過來使用 landLord.checkMoney.call(this); }, } robinHood.rob(); //Landlord is giving money to Robin Hood
第五章 原型
5.1 [[Prototype]]
幾乎全部的對象在建立時 [[Prototype]] 屬性都會被賦予一個非空的值。
對於默認的Get操做來講,若是沒法在對象自己找到須要的屬性或方法,就會繼續訪問對象的Prototype鏈。
var another = { a:2 }; //建立一個關聯到anotherObject的對象 var myObj = Object.create(another); // 能找到,可是在myObj.__proto__上找到 myObj.a; //2 // Object.create(..)會建立一個對象並把這個對象的 [[Prototype]] 關聯到指定的對象。
5.1.1 Object.prototype
通常原型鏈都會最終指向Object.prototype,而Object.prototype的__proto__爲null。
5.1.2 屬性設置和屏蔽
使用屏蔽得不償失,因此應當儘可能避免使用
!!有些狀況下會隱式產生屏蔽,必定要小心。
var anotherObject = { a:2 };
var myObject = Object.create( anotherObject );
anotherObject.a; // 2
myObject.a; // 2
anotherObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "a" ); // false
myObject.a++; // 隱式屏蔽!
myObject.a; // 3
myObject.hasOwnProperty( "a" ); // true
++ 操做至關於 myObject.a = myObject.a + 1。所以 ++ 操做首先會經過 [[Prototype]] 查找屬性 a 並從 anotherObject.a 獲取當前屬性值 2,而後給這個值加 1,接着用 [[Put]] 將值 3 賦給 myObject 中新建的屏蔽屬性 a,天吶!
5.2 類
5.2.1 類函數
全部函數默認都會擁有一個名爲prototype的公有且不可枚舉屬性,它其實指向另外一個對象。 同一個構造函數,及其構造的實例,它們對該函數prototype的引用其實指向同一段內存地址,修改這個prototype的屬性及方法,會影響到這個構造函數及其全部實例。
5.5.2 構造函數
prototype默認有一個公有且不可枚舉的屬性constructor,這個屬性引用的是對象關聯的函數。
function foo() { // ... } foo.prototype.constructor === foo; // true var a = new foo(); // 並非a有這個屬性,它是在原型鏈上找到的 a.constructor === foo; // true
須要注意的是,構造函數自己仍是函數,new操做符只是將其調用方式變成「構造函數調用」,本質上仍是須要去執行它。
在 JavaScript 中對於「構造函數」最準確的解釋是,全部帶 new 的函數調用。
5.2.3 技術
prototype中的constructor引用是很是不可靠,且不安全的,要避免使用。.constructor 並非一個不可變屬性。它是不可枚舉的,可是它的值 是可寫的(能夠被修改)。
實例對象 並無 .constructor 屬性,因此它會委託__proto__鏈上的 Foo. prototype。
實際上,Foo 和你程序中的其餘函數沒有任何區別。函數自己並非構造函數,然而,當 你在普通的函數調用前面加上 new 關鍵字以後,就會把這個函數調用變成一個「構造函數 調用」。
new 會劫持全部普通函數並用構造對象的形式來調用它。
5.3 (原型)繼承
要建立一個合適的關聯對象,咱們必須使用 Object.create(..) 而不是使用具備副 做用的 Foo(..)。會建立一個新對象,再賦予當前對象
bar = Object.create( foo ); 中
Object.create(..),會建立一個新對象給bar,可是此時bar中並無內容,仍是指向的foo,能夠引用foo中的屬性;當foo中參數被修改時,bar的引用也會被修改;
可是當bar重寫屬性時,在當前實例中建立這個屬性,重寫的值不會改變foo的值!!!此後foo再次改變此屬性不會再影響bar的屬性值
ES6 添加了輔助函數 Object.setPrototypeOf(..),能夠用標準而且可靠的方法來修 改關聯。
// ES6 以前須要拋棄默認的 Bar.prototype Bar.ptototype = Object.create( Foo.prototype ); // ES6 開始能夠直接修改現有的 Bar.prototype Object.setPrototypeOf( Bar.prototype, Foo.prototype )
a instanceof Foo; // true
instanceof 操做符的左操做數是一個普通的對象,右操做數是一個函數。instanceof 回答 的問題是:在 a 的整條 [[Prototype]] 鏈中是否有指向 Foo.prototype 的對象?
反過來操做就是:Foo.prototype.isPrototypeOf(a):foo是否出如今a的原型鏈中
//ES5 function Super(){ this.name = 'father'; } Super.prototype.say = function() { console.log('I am ' + this.name); }; function Sub(){ // 執行父類構造函數,得到屬性 Super(); // 添加或覆蓋屬性 this.name = 'son'; } // 造成原型鏈 Sub.prototype = Object.create(Super.prototype); // 修復constructor指向 Sub.prototype.constructor = Sub; // 或者更直接的原型鏈 Sub.prototype.__proto__ = Super.prototype; //ES6提供一種新的操做方式 Object.setPrototypeOf(Sub.prototype, Super.prototype); // ES6 class A { constructor(){ this.name = 'father' } say() { console.log('Method from father: I am ' + this.name); } } class B extends A { constructor(){ super(); this.name = 'son' } say() { console.log(`Method from son: I am ${this.name}.`); super.say(); } }
另外一個須要注意的是非標準的proto,它實際上是一套getter/setter。
Object.defineProperty(Object.prototype, "__proto__", { get: function() { return Object.getPrototypeOf(this); }, set: function(o) { Object.setPrototypeOf(this, o); return o; }, });
5.4 對象關聯
5.4.1 建立關聯
//Object.create()的polyfill if(!Object.create){ Object.create = function(o) { function F(){} F.prototype = o; return new F(); }; }
5.4.2 關聯關係是備用?
原型的實現應當遵循委託設計模式,API在原型鏈上出現的位置要參考現實中的狀況。
第六章 行爲委託
6.1 面向委託的設計
6.1.1 類理論
類設計模式鼓勵你在繼承時使用方法重寫和多態。許多行爲能夠先抽象到父類,而後再用子類進行特殊化。
6.1.2 委託理論
首先定義對象,而不是類。經過Object.create()來建立委託對象,賦值給另外一個對象,從而讓該對象出如今另外一個對象的原型鏈上。 好比var bar = Object.create(foo);,使得bar.__proto__===foo,從而bar能夠經過原型鏈得到foo的全部屬性和方法。 這種設計模式被稱爲對象關聯。委託行爲意味着某些對象在找不到屬性或者方法引用時,會把這個請求委託給另外一個對象。
須要注意的是,禁止兩個對象互相委託,不然當引用一個二者都不存在的屬性或方法,會產生無限遞歸的循環。
function Foo() {} var a1 = new Foo(); a1; // Foo {} (chrome中) ; Object {} (Firefox中) //「{} 是一個空對象,由名爲 Foo 的函數構造」 ,由於 Chrome 會動態跟蹤並把 實際執行構造過程的函數名看成一個內置屬性,可是其餘瀏覽器並不會跟蹤這些額外的信息
6.1.3 比較思惟模型
對象關聯風格相對於類風格更爲簡潔,由於它只關注對象之間的關聯關係。
Foo = { init: function(who) { this.me = who; }, identify: function() { return 'I am ' + this.me; }, } Bar = Object.create(Foo); Bar.speak = function() { console.log('Hello, ' + this.identify() + '.'); }; var b1 = Object.create(Bar); var b2 = Object.create(Bar); // 從b1.__proto__.__proto__上獲得init方法 b1.init('b1'); b2.init('b2'); // 從b1.__proto__上獲得Bar的spaak方法 // 從b1.__proto__.__proto__上獲得identify方法 b1.speak(); // Hello, b1. b2.speak(); // Hello, b2.
6.2.2 委託控件對象
委託模式能夠防止上一節中不太合理的僞多態形式調用Widget.prototype.render.call(this, $where);。 同時init和setup兩個初始化函數更具語義表達能力,同時因爲將構建和初始化分開,使得初始化的時機變得更靈活,容許異步調用。 對象關聯能夠更好地支持關注分離原則,建立和初始化不須要合併爲一個步驟。
6.3 更簡潔的設計
對象關聯除了能讓代碼看起來更簡潔,更具擴展性,還能夠經過委託模式簡化代碼結構。
6.4 更好的語法
ES6中使用Object.setPrototypeOf(target, obj)的方式來簡化target = Object.create(obj)的寫法。
6.5 內省
內省就是檢查實例的類型。 instanceof實際上檢查的是目標對象與構造器原型的關係,對於經過委託互相關聯的對象(Foo與Bar互相關聯),可使用: