摘要:解除引用的最佳手段是將對(duì)象變量設(shè)置為。字面形式允許你在不需要使用操作符和構(gòu)造函數(shù)顯示創(chuàng)建對(duì)象的情況下生成引用值。函數(shù)就是值可以像使用對(duì)象一樣使用函數(shù)因?yàn)楹瘮?shù)本來就是對(duì)象,構(gòu)造函數(shù)更加容易說明。
JavaScript(ES5)的面向?qū)ο缶?/b>
標(biāo)簽: JavaScript 面向?qū)ο?讀書筆記
2016年1月16日-17日兩天看完了《JavaScript面向?qū)ο缶罚▍⒓赢惒缴鐓^(qū)的活動(dòng)送的),這本書雖然不夠100頁,但都是精華,不愧是《JavaScript高級(jí)程序設(shè)計(jì)》作者 Nicholas C.Zakas 的最新力作。
下面是我的讀書筆記(ES5):
1.原始類型和引用類型 1.1 什么是類型原始類型 保存為簡單數(shù)據(jù)值。
引用類型 保存為對(duì)象,其本質(zhì)是指向內(nèi)存位置的引用。
為了讓開發(fā)者能夠把原始類型和引用類型按相同的方式處理,JavaScript花費(fèi)了很大的努力來保證語言的一致性。
其他編程語言用棧存原始類型,用對(duì)存儲(chǔ)引用類型。而JavaScript則完全不同:它使用一個(gè)變量對(duì)象追蹤變量的生存期。原始值被直接保存在變量對(duì)象內(nèi),而引用值則作為一個(gè)指針保存在變量對(duì)象內(nèi),該指針指向?qū)嶋H對(duì)象在內(nèi)存中的存儲(chǔ)位置。
1.2 原始類型原始類型代表照原樣保存的一些簡單數(shù)據(jù)。
JavaScript共有 5 種原始類型:
boolean 布爾,值為 true or false
number 數(shù)字,值為任何整型或浮點(diǎn)數(shù)值
string 字符串,值為由單引號(hào)或雙引號(hào)括住的單個(gè)字符或連續(xù)字符
null 空類型,僅有一個(gè)值:null
undefined 未定義,只有一個(gè)值:undefined(undefined會(huì)被賦給一個(gè)還沒有初始化的變量)
JavaScript和許多其他語言一樣,原始類型的變量直接保存原始值(而不是一個(gè)指向?qū)ο蟮闹羔槪?/p>
var color1 = "red"; var color2 = color1; console.log(color1); // "red" console.log(color2); // "red" color1 = "blue"; console.log(color1); // "blue" console.log(color2); // "red"鑒別原始類型
鑒別原始類型的最佳方式是使用 typeof 操作符。
console.log(typeof "Nicholas"); // "string" console.log(typeof 10); // "number" console.log(typeof true); // "boolean" console.log(typeof undefined); // "undefined"
至于空類型(null)則有些棘手。
console.log(typeof null); // "object"
對(duì)于 typeof null,結(jié)果是"object"。(其實(shí)這已被設(shè)計(jì)和維護(hù)JavaScript的委員會(huì)TC39認(rèn)定是一個(gè)錯(cuò)誤。在邏輯上,你可以認(rèn)為 null 是一個(gè)空的對(duì)象指針,所以結(jié)果為"object",但這還是很令人困惑。)
判斷一個(gè)值是否為空類型(null)的最佳方式是直接和 null 比較:
console.log(value === null); // true or false
注意:以上這段代碼使用了三等號(hào)(全等===),因?yàn)槿忍?hào)(全等)不會(huì)將變量強(qiáng)制轉(zhuǎn)換為另一種類型。
console.log("5" == 5); // true console.log("5" === 5); // false console.log(undefined == null); // true console.log(undefined === null); // false原始方法
雖然字符串、數(shù)字和布爾值是原始類型,但是它們也擁有方法(null和undefined沒有方法)。
var name = "Nicholas"; var lowercaseName = name.toLowerCase(); // 轉(zhuǎn)為小寫 var count = 10; var fixedCount = count.toFixed(2); // 轉(zhuǎn)為10.00 var flag = true; var stringFlag = flag.toString(); // 轉(zhuǎn)為"true" console.log("YIBU".charAt(0)); // 輸出"Y"
1.3 引用類型盡管原始類型擁有方法,但它們不是對(duì)象。JavaScript使它們看上去像對(duì)象一樣,以此來提高語言上的一致性體驗(yàn)。
引用類型是指JavaScript中的對(duì)象,同時(shí)也是你在該語言中能找到最接近類的東西。
引用值是引用類型的實(shí)例,也是對(duì)象的同義詞(后面將用對(duì)象指代引用值)。對(duì)象是屬性的無序列表。屬性包含鍵(始終是字符串)和值。如果一個(gè)屬性的值是函數(shù),它就被稱為方法。除了函數(shù)可以運(yùn)行以外,一個(gè)包含數(shù)組的屬性和一個(gè)包含函數(shù)的屬性沒有什么區(qū)別。
有時(shí)候,把JavaScript對(duì)象想象成哈希表可以幫助你更好地理解對(duì)象結(jié)構(gòu)。
JavaScript 有好幾種方法可以創(chuàng)建對(duì)象,或者說實(shí)例化對(duì)象。第一種是使用 new 操作符和構(gòu)造函數(shù)。
構(gòu)造函數(shù)就是通過 new 操作符來創(chuàng)建對(duì)象的函數(shù)——任何函數(shù)都可以是構(gòu)造函數(shù)。根據(jù)命名規(guī)范,JavaScript中的構(gòu)造函數(shù)用首字母大寫來跟非構(gòu)造函數(shù)進(jìn)行區(qū)分。
var object = new Object();
因?yàn)橐妙愋筒辉僮兞恐兄苯颖4鎸?duì)象,所以本例中的 object 變量實(shí)際上并不包含對(duì)象的實(shí)例,而是一個(gè)指向內(nèi)存中實(shí)際對(duì)象所在位置的指針(或者說引用)。這是對(duì)象和原始值之間的一個(gè)基本差別,原始值是直接保存在變量中。
當(dāng)你將一個(gè)對(duì)象賦值給變量時(shí),實(shí)際是賦值給這個(gè)變量一個(gè)指針。這意味著,將一個(gè)變量賦值給另外一個(gè)變量時(shí),兩個(gè)變量各獲得了一份指針的拷貝,指向內(nèi)存中的同一個(gè)對(duì)象。
var obj1 = new Object(); var obj2 = obj1;對(duì)象引用解除
JavaScript語言有垃圾收集的功能,因此當(dāng)你使用引用類型時(shí)無需擔(dān)心內(nèi)存分配。但最好在不使用對(duì)象時(shí)將其引用解除,讓垃圾收集器對(duì)那塊內(nèi)存進(jìn)行釋放。解除引用的最佳手段是將對(duì)象變量設(shè)置為 null。
var obj1 = new Object(); // dosomething obj1 = null; // dereference添加刪除屬性
在JavaScript中,你可以隨時(shí)添加和刪除其屬性。
var obj1 = new Object(); var obj2 = obj1; obj1.myCustomProperty = "Awsome!"; console.log(obj2.myCustomProperty); // "Awsome!" 因?yàn)閛bj1和obj2指向同一個(gè)對(duì)象。1.4 內(nèi)建類型實(shí)例化
內(nèi)建類型如下:
Array 數(shù)組類型,以數(shù)字為索引的一組值的有序列表
Date 日期和時(shí)間類型
Error 運(yùn)行期錯(cuò)誤類型
Function 函數(shù)類型
Object 通用對(duì)象類型
RegExp 正則表達(dá)式類型
可使用 new 來實(shí)例化每一個(gè)內(nèi)建引用類型:
var items = new Array(); var new = new Date(); var error = new Error("Something bad happened."); var func = new Function("console.log("HI");"); var object = new Object(); var re = new RegExp();字面形式
內(nèi)建引用類型有字面形式。字面形式允許你在不需要使用 new 操作符和構(gòu)造函數(shù)顯示創(chuàng)建對(duì)象的情況下生成引用值。屬性的鍵可以是標(biāo)識(shí)符或字符串(若含有空格或其他特殊字符)
var book = { name: "Book_name", year: 2016 }
上面代碼與下面這段代碼等價(jià):
var book = new Object(); book.name = "Book_name"; book.year = 2016;
1.5 訪問屬性雖然使用字面形式并沒有調(diào)用 new Object(),但是JavaScript引擎背后做的工作和 new Object()一樣,除了沒有調(diào)用構(gòu)造函數(shù)。其他引用類型的字面形式也是如此。
可通過 . 和 中括號(hào) 訪問對(duì)象的屬性。
中括號(hào)[]在需要?jiǎng)討B(tài)決定訪問哪個(gè)屬性時(shí),特別有用。因?yàn)槟憧梢杂?strong>變量而不是字符串字面形式來指定訪問的屬性。
函數(shù)是最容易鑒別的引用類型,因?yàn)閷?duì)函數(shù)使用 typeof 操作符時(shí),返回"function"。
function reflect(value){ return value; } console.log(typeof reflect); // "function"
對(duì)其他引用類型的鑒別則較為棘手,因?yàn)閷?duì)于所有非函數(shù)的引用類型,typeof 返回 object。為了更方便地鑒別引用類型,可以使用 JavaScript 的 instanceof 操作符。
var items = []; var obj = {}; function reflect(value){ return value; } console.log(items instanceof Array); // true; console.log(obj instanceof Object); // true; console.log(reflect instanceof Function); // true;
instanceof 操作符可鑒別繼承類型。這意味著所有對(duì)象都是 Oject 的實(shí)例,因?yàn)樗幸妙愋投祭^承自 Object。
1.8 原始封裝類型雖然 instanceof 可以鑒別對(duì)象類型(如數(shù)組),但是有一個(gè)列外。JavaScript 的值可以在同一個(gè)網(wǎng)頁的不用框架之間傳來傳去。由于每個(gè)網(wǎng)頁擁有它自己的全局上下文——Object、Array以及其他內(nèi)建類型的版本。所以當(dāng)你把一個(gè)對(duì)象(如數(shù)組)從一個(gè)框架傳到另外一個(gè)框架時(shí),instanceof就無法識(shí)別它。
原始封裝類型有 3 種:String、Number 和 Boolean。
當(dāng)讀取字符串、數(shù)字或布爾值時(shí),原始封裝類型將被自動(dòng)創(chuàng)建。
var name = "Nicholas"; var firstChar = name.charAt(0); // "N"
這在背后發(fā)生的事情如下:
var name = "Nichola"; var temp = new String(name); var firstChar = temp.charAt(0); temp = null;
由于第二行把字符串當(dāng)成對(duì)象使用,JavaScript引擎創(chuàng)建了一個(gè)字符串的實(shí)體讓 charAt(0) 可以工作。字符串對(duì)象的存在僅用于該語句并在隨后銷毀(一種被稱為自動(dòng)打包的過程)。為了測試這一點(diǎn),試著給字符串添加一個(gè)屬性看看它是不是對(duì)象。
var name = "Nicholas"; name.last = "Zakas"; console.log(name.last); // undefined;
下面是在JavaScript引擎中實(shí)際發(fā)生的事情:
var name = "Nicholas"; var temp = new String(name); temp.last = "Zakas"; temp = null; // temporary object destroyed var temp = new String(name); console.log(temp.last); temp = null;
新屬性 last 實(shí)際上是在一個(gè)立刻就被銷毀的臨時(shí)對(duì)象上而不是字符串上添加。之后當(dāng)你試圖訪問該屬性時(shí),另一個(gè)不同的臨時(shí)對(duì)象被創(chuàng)建,而新屬性并不存在。
雖然原始封裝類型會(huì)被自動(dòng)創(chuàng)建,在這些值上進(jìn)行 instanceof 檢查對(duì)應(yīng)類型的返回值卻是 false。
這是因?yàn)?strong>臨時(shí)對(duì)象僅在值被讀取時(shí)創(chuàng)建。instanceof 操作符并沒有真的讀取任何東西,也就沒有臨時(shí)對(duì)象的創(chuàng)建。
當(dāng)然你也可以手動(dòng)創(chuàng)建原始封裝類型。
var str = new String("me"); str.age = 18; console.log(typeof str); // object console.log(str.age); // 18
如你所見,手動(dòng)創(chuàng)建原始封裝類型實(shí)際會(huì)創(chuàng)建出一個(gè) object。這意味著 typeof 無法鑒別出你實(shí)際保存的數(shù)據(jù)的類型。
另外,手動(dòng)創(chuàng)建原始封裝類型和使用原始值是有一定區(qū)別的。所以盡量避免使用。
var found = new Boolean(false); if(found){ console.log("Found"); // 執(zhí)行到了,盡管對(duì)象的值為 false }
這是因?yàn)橐粋€(gè)對(duì)象(如 {} )在條件判斷語句中總被認(rèn)為是 true;
1.9 總結(jié)MDN:Any object whose value is not undefined or null, including a Boolean oject whose value is false, evaluates to true when passed to a conditional statement.
第一章的東西都是我們一些比較熟悉的知識(shí)。但是也有一些需要注意的地方:
正確區(qū)分原始類型和引用類型
對(duì)于 5 種原始類型都可以用typeof來鑒別,而空類型必須直接跟 null 進(jìn)行全等比較。
函數(shù)也是對(duì)象,可用 typeof 鑒別。其它引用類型,可用 instanceof 和一個(gè)構(gòu)造函數(shù)來鑒別。(當(dāng)然可以用 Object.prototype.toString.call() 鑒別,它會(huì)返回[object Array]之類的)。
為了讓原始類型看上去更像引用類型,JavaScript提供了 3 種封裝類型。JavaScript會(huì)在背后創(chuàng)建這些對(duì)象使得你能夠像使用普通對(duì)象那樣使用原始值。但這些臨時(shí)對(duì)象在使用它們的語句結(jié)束時(shí)就立刻被銷毀。雖然可手動(dòng)創(chuàng)建,但不建議。
2. 函數(shù)函數(shù)也是對(duì)象,使對(duì)象不同于其它對(duì)象的決定性特點(diǎn)是函數(shù)存在一個(gè)被稱為 [[Call]] 的內(nèi)部屬性。
內(nèi)部屬性無法通過代碼訪問而是定義了代碼執(zhí)行時(shí)的行為。ECMAScript為JavaScript的對(duì)象定義了多種內(nèi)部屬性,這些內(nèi)部屬性都用雙重中括號(hào)來標(biāo)注。
[[Call]]屬性是函數(shù)獨(dú)有的,表明該對(duì)象可以被執(zhí)行。由于僅函數(shù)擁有該屬性,ECMAScript 定義typeof操作符對(duì)任何具有[[Call]]屬性的對(duì)象返回"function"。過去因某些瀏覽器曾在正則表達(dá)式中包含 [[Call]] 屬性,導(dǎo)致正則表達(dá)式被錯(cuò)誤鑒別為函數(shù)。
2.1 聲明還是表達(dá)式兩者的一個(gè)重要區(qū)別是:函數(shù)聲明會(huì)被提升至上下文(要么是該函數(shù)被聲明時(shí)所在的函數(shù)范圍,要么是全局范圍)的頂部。
2.2 函數(shù)就是值可以像使用對(duì)象一樣使用函數(shù)(因?yàn)楹瘮?shù)本來就是對(duì)象,F(xiàn)unction構(gòu)造函數(shù)更加容易說明)。
2.3 參數(shù)函數(shù)參數(shù)保存在類數(shù)組對(duì)象 argument (Array.isArray(arguments) 返回 false)中??梢越邮杖我鈹?shù)量的參數(shù)。
函數(shù)的 length 屬性表明其期望的參數(shù)個(gè)數(shù)。
大多數(shù)面向?qū)ο笳Z言支持函數(shù)重載,它能讓一個(gè)函數(shù)具有多個(gè)簽名。函數(shù)簽名由函數(shù)的名字、參數(shù)的個(gè)數(shù)及其類型組成。
而JavaScript可以接收任意數(shù)量的參數(shù)且參數(shù)類型完全沒有限制。這說明JavaScript函數(shù)根本就沒有簽名,因此也不存在重載。
function sayMessage(message){ console.log(message); } function sayMessage(){ console.log("Default Message"); } sayMessage("Hello!"); // 輸出"Default Message";
在Javscript里,當(dāng)你試圖定義多個(gè)同名的函數(shù)時(shí),只有最后的定義有效,之前的函數(shù)聲明被完全刪除(函數(shù)也是對(duì)象,變量只是存指針)。
var sayMessage = new Function("message", "console.log(message)"); var sayMessage = new Function("console.log("Default Message");"); sayMessage("Hello!");
當(dāng)然,你可以根據(jù)傳入?yún)?shù)的數(shù)量來模仿重載。
2.5 對(duì)象方法對(duì)象的值是函數(shù),則該屬性被稱為方法。
2.5.1 this對(duì)象JavaScript 所有的函數(shù)作用域內(nèi)都有一個(gè) this 對(duì)象代表調(diào)用該函數(shù)的對(duì)象。在全局作用域中,this 代表全局對(duì)象(瀏覽器里的window)。當(dāng)一個(gè)函數(shù)作為對(duì)象的方法調(diào)用時(shí),默認(rèn) this 的值等于該對(duì)象。
this在函數(shù)調(diào)用時(shí)才被設(shè)置。
function sayNameForAll(){ console.log(this.name); } var person1 = { name: "Nicholas", sayName: sayNameForAll } var name = "Jack"; person1.sayName(); // 輸出 "Nicholas" sayNameforAll(); // 輸出 "Jack"2.5.2 改變this
有 3 種函數(shù)方法運(yùn)行你改變 this 值。
fun.call(thisArg[, arg1[, arg2[, ...]]]);
fun.apply(thisArg, [argsArray]);
fun.bind(thisArg[, arg1[, arg2[, ...]]])
使用 call 或 apply 方法,就不需要將函數(shù)加入每個(gè)對(duì)象——你顯示地指定了 this 的值而不是讓JavaScript引擎自動(dòng)指定。
call 與 apply 的不同地方是,call 需要把所有參數(shù)一個(gè)個(gè)列出來,而 apply 的參數(shù)需要一個(gè)數(shù)組或者類似數(shù)組的對(duì)象(如 arguments 對(duì)象)。
bind 是ECMAScript 5 新增的,它會(huì)創(chuàng)建一個(gè)新函數(shù)返回。其參數(shù)與 call 類似,而且其所有參數(shù)代表需要被永久設(shè)置在新函數(shù)中的命名參數(shù)(綁定了的參數(shù)(沒綁定的參數(shù)依然可以傳入),就算調(diào)用時(shí)再傳入其它參數(shù),也不會(huì)影響這些綁定的參數(shù))。
function sayNameForAll(label){ console.log(label + ":" + this.name); } var person = { name: "Nicholas" } var sayNameForPerson = sayNameForAll.bind(person); sayNameForPerson("Person"); // 輸出"Person:Nicholas" var sayName = sayNameForAll.bind(person, "Jc"); sayName("change"); // 輸出"Jc:Nicholas" 因?yàn)榻壎ǖ男螀?,?huì)忽略調(diào)用時(shí)再傳入?yún)?shù)2.6 總結(jié)
函數(shù)也是對(duì)象,所以它可以被訪問、復(fù)制和覆蓋。
函數(shù)與其他對(duì)象最大的區(qū)別在于它們有一個(gè)特殊的內(nèi)部屬性 [[Call]],包含了該函數(shù)的執(zhí)行指令。
函數(shù)聲明會(huì)被提升至上下文的頂部。
函數(shù)是對(duì)象,所以存在一個(gè) Function 構(gòu)造函數(shù)。但這會(huì)使你的代碼難以理解和調(diào)試,除非函數(shù)的真實(shí)形式要直到運(yùn)行時(shí)才能確定的時(shí)候才會(huì)利用它。
理解對(duì)象JavaScript中的對(duì)象是動(dòng)態(tài)的,可在代碼執(zhí)行的任意時(shí)刻發(fā)生改變?;陬惖恼Z言會(huì)根據(jù)類的定義鎖定對(duì)象。
3.1 定義屬性當(dāng)一個(gè)屬性第一次被添加到對(duì)象時(shí),JavaScript會(huì)在對(duì)象上調(diào)用一個(gè)名為 [[Put]] 的內(nèi)部方法。[[Put]] 方法會(huì)在對(duì)象上創(chuàng)建一個(gè)新節(jié)點(diǎn)來保存屬性。
當(dāng)一個(gè)已有的屬性被賦予一個(gè)新值時(shí),調(diào)用的是一個(gè)名為 [[Set]] 的方法。
檢查對(duì)象是否已有一個(gè)屬性。JavaScript開發(fā)新手錯(cuò)誤地使用以下模式檢測屬性是否存在。
if(person.age){ // do something with ag }
上面的問題在于JavaScript的類型強(qiáng)制會(huì)影響該模式的輸出結(jié)果。
當(dāng)if判斷中的值如下時(shí),會(huì)判斷為真:
對(duì)象
非空字符串
非零
true
當(dāng)if判斷中的值如下時(shí),會(huì)判斷為假:
null
undefined
0
false
NaN
空字符串
因此判斷屬性是否存在的方法是使用 in 操作符。
in 操作符會(huì)檢查自有屬性和原型屬性。
所有的對(duì)象都擁有的 hasOwnProperty() 方法(其實(shí)是 Object.prototype 原型對(duì)象的),該方法在給定的屬性存在且為自有屬性時(shí)返回 true。
var person = { name: "Nicholas" } console.log("name" in person); // true console.log(person.hasOwnpropert("name")); // true console.log("toString" in person); // true console.log(person.hasOwnproperty("toString")); // false3.3 刪除屬性
設(shè)置一個(gè)屬性的值為 null 并不能從對(duì)象中徹底移除那個(gè)屬性,這只是調(diào)用 [[Set]] 將 null 值替換了該屬性原來的值而已。
delete 操作符針對(duì)單個(gè)對(duì)象屬性調(diào)用名為 [[Delete]] 的內(nèi)部方法。刪除成功時(shí),返回 true。
var person = { name: "Nicholas" } person.name = null; console.log("name" in person); // true delete person.name; console.log(person.name); // undefined 訪問一個(gè)不存在的屬性將返回 undefined console.log("name" in person); // false3.4 屬性枚舉
所有人為添加的屬性默認(rèn)都是可枚舉的。可枚舉的內(nèi)部特征 [[Enumerable]] 都被設(shè)置為 true。
for-in 循環(huán)會(huì)枚舉一個(gè)對(duì)象所有的可枚舉屬性。
我的備注:在Chrome中,對(duì)象屬性會(huì)按ASCII表排序,而不是定義時(shí)的順序。
ECMAScript 5 的 Object() 方法可以獲取可枚舉屬性的名字的數(shù)組。
var person = { name: "Ljc", age: 18 } Object.keys(person); // ["name", "age"];
for-in 與 Object.keys() 的一個(gè)區(qū)別是:前者也會(huì)遍歷原型屬性,而后者返回自有(實(shí)例)屬性。
實(shí)際上,對(duì)象的大部分原生方法的 [[Enumerable]] 特征都被設(shè)置為 false??捎?propertyIsEnumerable() 方法檢查一個(gè)屬性是否為可枚舉的。
var arr = ["abc", 2]; console.log(arr.propertyIsEnumerable("length")); // false3.5 屬性類型
屬性有兩種類型:數(shù)據(jù)屬性和訪問器屬性。
數(shù)據(jù)屬性包含一個(gè)值。[[Put]] 方法的默認(rèn)行為是創(chuàng)建數(shù)據(jù)屬性。
訪問器屬性不包含值而是定義了一個(gè)當(dāng)屬性被讀取時(shí)調(diào)用的函數(shù)(稱為getter)和一個(gè)當(dāng)屬性被寫入時(shí)調(diào)用的函數(shù)(稱為setter)。訪問器屬性僅需要 getter 或 setter 兩者中的任意一個(gè),當(dāng)然也可以兩者。
// 對(duì)象字面形式中定義訪問器屬性有特殊的語法: var person = { _name: "Nicholas", get name(){ console.log("Reading name"); return this._name; }, set name(value){ console.log("Setting name to %s", value); this._name = value; } }; console.log(person.name); // "Reading name" 然后輸出 "Nicholas" person.name = "Greg"; console.log(person.name); // "Setting name to Greg" 然后輸出 "Greg"
前置下劃線_ 是一個(gè)約定俗成的命名規(guī)范,表示該屬性是私有的,實(shí)際上它還是公開的。
訪問器就是定義了我們在對(duì)象讀取或設(shè)置屬性時(shí),觸發(fā)的動(dòng)作(函數(shù)),_name 相當(dāng)于一個(gè)內(nèi)部變量。
當(dāng)你希望賦值(讀?。┎僮鲿?huì)觸發(fā)一些行為,訪問器就會(huì)非常有用。
3.6 屬性特征當(dāng)只定義getter或setter其一時(shí),該屬性就會(huì)變成只讀或只寫。
在ECMAScript 5 之前沒有辦法指定一個(gè)屬性是否可枚舉。實(shí)際上根本沒有方法訪問屬性的任何內(nèi)部特征。為了改變這點(diǎn),ECMAScript 5引入了多種方法來和屬性特征值直接互動(dòng)。
3.6.1 通用特征數(shù)據(jù)屬性和訪問器屬性均由以下兩個(gè)屬性特制:
[[Enumerable]] 決定了是否可以遍歷該屬性;
[[Configurable]] 決定了該屬性是否可配置。
所有人為定義的屬性默認(rèn)都是可枚舉、可配置的。
可以用 Object.defineProperty() 方法改變屬性特征。
其參數(shù)有三:擁有該屬性的對(duì)象、屬性名和包含需要設(shè)置的特性的屬性描述對(duì)象。
var person = { name: "Nicholas" } Object.defineProperty(person, "name", { enumerable: false }) console.log("name" in person); // true console.log(person.propertyIsEnumerable("name")); // false var properties = Object.keys(person); console.log(properties.length); // 0 Object.defineProperty(person, "name",{ configurable: false }) delete person.name; // false console.log("name" in person); // true Object.defineProperty(person, "name",{ // error! // 在 chrome:Uncaught TypeError: Cannot redefine property: name configurable: true })
3.6.2 數(shù)據(jù)屬性特征無法將一個(gè)不可配置的屬性變?yōu)榭膳渲?,相反則可以。
數(shù)據(jù)屬性額外擁有兩個(gè)訪問器屬性不具備的特征。
[[Value]] 包含屬性的值(哪怕是函數(shù))。
[[Writable]] 布爾值,指示該屬性是否可寫入。所有屬性默認(rèn)都是可寫的。
var person = {}; Object.defineProperty(person, "name", { value: "Nicholas", enumerable: true, configurable: true, writable: true })
在 Object.defineProperty() 被調(diào)用時(shí),如果屬性本來就有,則會(huì)按照新定義屬性特征值去覆蓋默認(rèn)屬性特征(enumberable、configurable 和 writable 均為 true)。但如果用該方法定義新的屬性時(shí),沒有為所有的特征值指定一個(gè)值,則所有布爾值的特征值會(huì)被默認(rèn)設(shè)置為 false。即不可枚舉、不可配置、不可寫的。
當(dāng)你用 Object.defineProperty() 改變一個(gè)已有的屬性時(shí),只有你指定的特征會(huì)被改變。
訪問器屬性額外擁有兩個(gè)特征。[[Get]] 和 [[Set]],內(nèi)含 getter 和 setter 函數(shù)。
使用訪問其屬性特征比使用對(duì)象字面形式定義訪問器屬性的優(yōu)勢在于:可以為已有的對(duì)象定義這些屬性。而后者只能在創(chuàng)建時(shí)定義訪問器屬性。
var person = { _name: "Nicholas" }; Object.defineProperty(person, "name", { get: function(){ return this._name; }, set: function(value){ this._name = value; }, enumerable: true, configurable: true }) for(var x in person){ console.log(x); // _name (換行) name(訪問器屬性) }
設(shè)置一個(gè)不可配置、不可枚舉、不可以寫的屬性:
Object.defineProperty(person, "name",{ get: function(){ return this._name; } })
對(duì)于一個(gè)新的訪問器屬性,沒有顯示設(shè)置值為布爾值的屬性,默認(rèn)為 false。
3.6.4 定義多重屬性Object.defineProperties() 方法可以定義任意數(shù)量的屬性,甚至可以同時(shí)改變已有的屬性并創(chuàng)建新屬性。
var person = {}; Object.defineProperties(person, { // data property to store data _name: { value: "Nicholas", enumerable: true, configurable: true, writable: true }, // accessor property name: { get: function(){ return this._name; }, set: function(value){ this._name = value; } } })3.6.5 獲取屬性特征
Object.getOwnPropertyDescriptor() 方法。該方法接受兩個(gè)參數(shù):對(duì)象和屬性名。如果屬性存在,它會(huì)返回一個(gè)屬性描述對(duì)象,內(nèi)涵4個(gè)屬性:configurable 和 enumerable,另外兩個(gè)屬性則根據(jù)屬性類型決定。
var person = { name: "Nicholas" } var descriptor = Object.getOwnPropertyDescriptor(person, "name"); console.log(descriptor.enumerable); // true console.log(descriptor.configuable); // true console.log(descriptor.value); // "Nicholas" console.log(descriptor.wirtable); // true3.7 禁止修改對(duì)象
對(duì)象和屬性一樣具有指導(dǎo)其行為的內(nèi)部特性。其中, [[Extensible]] 是布爾值,指明該對(duì)象本身是否可以被修改。默認(rèn)是 true。當(dāng)值為 false 時(shí),就能禁止新屬性的添加。
3.7.1 禁止擴(kuò)展建議在 "use strict"; 嚴(yán)格模式下進(jìn)行。
Object.preventExtensions() 創(chuàng)建一個(gè)不可擴(kuò)展的對(duì)象(即不能添加新屬性)。
Object.isExtensible() 檢查 [[Extensible]] 的值。
var person = { name: "Nocholas" } Object.preventExtensions(person); person.sayName = function(){ console.log(this.name) } console.log("sayName" in person); // false3.7.2 對(duì)象封印
一個(gè)被封印的對(duì)象是不可擴(kuò)展的且其所有屬性都是不可配置的(即不能添加、刪除屬性或修改其屬性類型(從數(shù)據(jù)屬性變成訪問器屬性或相反))。只能讀寫它的屬性。
Object.seal()。調(diào)用此方法后,該對(duì)象的 [[Extensible]] 特征被設(shè)置為 false,其所有屬性的 [[configurable]] 特征被設(shè)置為 false。
Object.isSealed() 判斷一個(gè)對(duì)象是否被封印。
被凍結(jié)的對(duì)象不能添加或刪除屬性,不能修改屬性類型,也不能寫入任何數(shù)據(jù)屬性。簡言而之,被凍結(jié)對(duì)象是一個(gè)數(shù)據(jù)屬性都為只讀的被封印對(duì)象。
Object.freeze() 凍結(jié)對(duì)象。
Object.isFrozen() 判斷對(duì)象是否被凍結(jié)。
in 操作符檢測自有屬性和原型屬性,而 hasOwnProperty() 只檢查自有屬性。
用 delete 操作符刪除對(duì)象屬性。
屬性有兩種類型:數(shù)據(jù)屬性和訪問器屬性。
所有屬性都有一些相關(guān)特征。[[Enumerable]] 和 [[Configurable]] 的兩種屬性都有的,而數(shù)據(jù)屬性還有 [[Value]] 和 [[Writable]],訪問器屬性還有 [[Get]] 和 [[Set]]??赏ㄟ^ Object.defineProperty() 和 Object.defineProperties() 改變這些特征。用 Object.getOwnPropertyDescriptor() 獲取它們。
有 3 種可以鎖定對(duì)象屬性的方式。
4. 構(gòu)造函數(shù)和原型對(duì)象由于JavaScript(ES5)缺乏類,但可用構(gòu)造函數(shù)和原型對(duì)象給對(duì)象帶來與類相似的功能。
4.1 構(gòu)造函數(shù)構(gòu)造函數(shù)的函數(shù)名首字母應(yīng)大寫,以此區(qū)分其他函數(shù)。
當(dāng)沒有需要給構(gòu)造函數(shù)傳遞參數(shù),可忽略小括號(hào):
var Person = { // 故意留空 } var person = new Person;
盡管 Person 構(gòu)造函數(shù)沒有顯式返回任何東西,但 new 操作符會(huì)自動(dòng)創(chuàng)建給定類型的對(duì)象并返回它們。
每個(gè)對(duì)象在創(chuàng)建時(shí)都自動(dòng)擁有一個(gè)構(gòu)造函數(shù)屬性(constructor,其實(shí)是它們的原型對(duì)象上的屬性),其中包含了一個(gè)指向其構(gòu)造函數(shù)的引用。
通過對(duì)象字面量形式({})或Object構(gòu)造函數(shù)創(chuàng)建出來的泛用對(duì)象,其構(gòu)造函數(shù)屬性(constructor)指向 Object;而那些通過自定義構(gòu)造函數(shù)創(chuàng)建出來的對(duì)象,其構(gòu)造函數(shù)屬性指向創(chuàng)建它的構(gòu)造函數(shù)。
console.log(person.constructor === Person); // true console.log(({}).constructor === Object); // true console.log(([1,2,3]).constructor === Object); // true // 證明 constructor是在原型對(duì)象上 console.log(person.hasOwnPrototype("constructor")); // false console.log(person.constructor.prototype.hasOwnPrototype("constructor")); // true
盡管對(duì)象實(shí)例及其構(gòu)造函數(shù)之間存在這樣的關(guān)系,但還是建議使用 instanceof 來檢查對(duì)象類型。這是因?yàn)闃?gòu)造函數(shù)屬性可以被覆蓋。(person.constructor = "")。
當(dāng)你調(diào)用構(gòu)造函數(shù)時(shí),new 會(huì)自動(dòng)自動(dòng)創(chuàng)建 this 對(duì)象,且其類型就是構(gòu)造函數(shù)的類型(構(gòu)造函數(shù)就好像類,相當(dāng)于一種數(shù)據(jù)類型)。
你也可以在構(gòu)造函數(shù)中顯式調(diào)用 return。如果返回值是一個(gè)對(duì)象,它會(huì)代替新創(chuàng)建的對(duì)象實(shí)例而返回,如果返回值是一個(gè)原始類型,它會(huì)被忽略,新創(chuàng)建的對(duì)象實(shí)例會(huì)被返回。
始終確保要用 new 調(diào)用構(gòu)造函數(shù);否則,你就是在冒著改變?nèi)謱?duì)象的風(fēng)險(xiǎn),而不是創(chuàng)建一個(gè)新的對(duì)象。
var person = Person("Nicholas"); // 缺少 new console.log(person instanceof Person); // false console.log(person); // undefined,因?yàn)闆]用 new,就相當(dāng)于一個(gè)普通函數(shù),默認(rèn)返回 undefined console.log(name); // "Nicholas"
當(dāng)Person不是被 new 調(diào)用時(shí),構(gòu)造函數(shù)中的 this 對(duì)象等于全局 this 對(duì)象。
在嚴(yán)格模式下,會(huì)報(bào)錯(cuò)。因?yàn)閲?yán)格模式下,并沒有為全局對(duì)象設(shè)置 this,this 保持為 undefined。
以下代碼,通過 new 實(shí)例化 100 個(gè)對(duì)象,則會(huì)有 100 個(gè)函數(shù)做相同的事。因此可用 prototype 共享同一個(gè)方法會(huì)更高效。
var person = { name: "Nicholas", sayName: function(){ console.log(this.name); } }4.2 原型對(duì)象
可以把原型對(duì)象看作是對(duì)象的基類。幾乎所有的函數(shù)(除了一些內(nèi)建函數(shù))都有一個(gè)名為 prototype 的屬性,該屬性是一個(gè)原型對(duì)象用來創(chuàng)建新的對(duì)象實(shí)例。所有創(chuàng)建的對(duì)象實(shí)例(同一構(gòu)造函數(shù),當(dāng)然,可能訪問上層的原型對(duì)象)共享該原型對(duì)象,且這些對(duì)象實(shí)例可以訪問原型對(duì)象的屬性。例如,hasOwnProperty()定義在 Object 的原型對(duì)象中,但卻可被任何對(duì)象當(dāng)作自己的屬性訪問。
var book = { title : "book_name" } "hasOwnProperty" in book; // true book.hasOwnProperty("hasOwnProperty"); // false Object.property.hasOwnProperty("hasOwnProperty"); // true
鑒別一個(gè)原型屬性
function hasPrototypeProperty(object, name){ return name in object && !object.hasOwnProperty(name); }4.2.1 [[Prototype]] 屬性
一個(gè)對(duì)象實(shí)例通過內(nèi)部屬性 [[Prototype]] 跟蹤其原型對(duì)象。該屬性是一個(gè)指向該實(shí)例使用的原型對(duì)象的指針。當(dāng)你用 new 創(chuàng)建一個(gè)新的對(duì)象時(shí),構(gòu)造函數(shù)的原型對(duì)象就會(huì)被賦給該對(duì)象的 [[Prototype]] 屬性。
由上圖可以看出,[[Prototype]] 屬性是如何讓多個(gè)對(duì)象實(shí)例引用同一個(gè)原型對(duì)象來減少重復(fù)代碼。
Object.getPrototypeOf() 方法可讀取 [[Prototype]] 屬性的值。
var obj = {}; var prototype = Object.getPrototypeOf(Object); console.log(prototype === Object.prototype); // true
大部分JavaScript引擎在所有對(duì)象上都支持一個(gè)名為 _proto_ 的屬性。該屬性使你可以直接讀寫 [[Prototype]] 屬性。
isPrototypeOf() 方法會(huì)檢查某個(gè)對(duì)象是否是另一個(gè)對(duì)象的原型對(duì)象,該方法包含在所有對(duì)象中。
var obj = {} console.log(Object.prototype.isPrototypeOf(obj)); // true
當(dāng)讀取一個(gè)對(duì)象的屬性時(shí),JavaScript 引擎首先在該對(duì)象的自有屬性查找屬性名。如果找到則返回。否則會(huì)搜索 [[Prototype]] 中的對(duì)象,找到則返回,找不到則返回 undefined。
var obj = new Object(); console.log(obj.toString()); // "[object Object]" obj.toString = function(){ return "[object Custom]"; } console.log(obj.toString()); // "[object Custom]" delete obj.toString; // true console.log(obj.toString()); // "[object Object]" delete obj.toString; // 無效,delete不能刪除一個(gè)對(duì)象從原型繼承而來的屬性 cconsole.log(obj.toString()); // // "[object Object]"
MDN:delete 操作符不能刪除的屬性有:①顯式聲明的全局變量不能被刪除,該屬性不可配置(not configurable); ②內(nèi)置對(duì)象的內(nèi)置屬性不能被刪除; ③不能刪除一個(gè)對(duì)象從原型繼承而來的屬性(不過你可以從原型上直接刪掉它)。
一個(gè)重要概念:無法給一個(gè)對(duì)象的原型屬性賦值。我認(rèn)為是無法直接添加吧,在chrome和Edge中,都無法讀取_proto_屬性,但我們可以通過 obj.constructor.prototype.sayHi = function(){console.log("Hi!")} 向原型對(duì)象添加屬性。
(圖片中間可以看出,為對(duì)象obj添加的toString屬性代替了原型屬性)
開發(fā)中需要注意原型對(duì)象的數(shù)據(jù)是否共享。
function Person(name){ this.name = name } Person.prototype.sayName = function(){ console.log(this.name); } Person.prototype.position = "school"; Person.prototype.arr = []; var person1 = new Person("xiaoming"); var person2 = new Person("Jc"); console.log("原始類型") console.log(person1.position); // "school" console.log(person2.position); // "school" person1.position = 2; // 這是在當(dāng)前屬性設(shè)置position,引用類型同理 console.log(person1.hasOwnProperty("position")); // true console.log(person2.hasOwnProperty("position")); // false console.log("引用類型"); person1.arr.push("pizza"); // 這是在原型對(duì)象上設(shè)置,而不是直接在對(duì)象上 person2.arr.push("quinoa"); // 這是在原型對(duì)象上設(shè)置 console.log(person1.hasOwnProperty("arr")); // false console.log(person2.hasOwnProperty("arr")); // false console.log(person1.arr); // ["pizza", "quinoa"] console.log(person2.arr); // ["pizza", "quinoa"]
上面是在原型對(duì)象上一一添加屬性,下面一種更簡潔的方式:以一個(gè)對(duì)象字面形式替換原型對(duì)象
function Person(name){ this.name } Person.prototype = { sayName: function(){ console.log(this.name); }, toString: function(){ return "[Person ]" + this.name + "]"; } }
這種方式有一種副作用:因?yàn)樵蛯?duì)象上具有一個(gè) constructor 屬性,這是其他對(duì)象實(shí)例所沒有的。當(dāng)一個(gè)函數(shù)被創(chuàng)建時(shí),它的 prototype 屬性也會(huì)被創(chuàng)建,且該原型對(duì)象的 constructor 屬性指向該函數(shù)。當(dāng)使用字面量時(shí),因沒顯式設(shè)置原型對(duì)象的 constructor 屬性,因此其 constructor 屬性是指向 Object 的。
因此,當(dāng)通過此方式設(shè)置原型對(duì)象時(shí),可手動(dòng)設(shè)置 constructor 屬性。
function Person(name){ this.name } // 建議第一個(gè)屬性就是設(shè)置其 constructor 屬性。 Person.prototype = { constructor: Person, sayName: function(){ console.log(this.name); }, toString: function(){ return "[Person ]" + this.name + "]"; } }
構(gòu)造函數(shù)、原型對(duì)象和對(duì)象實(shí)例之間的關(guān)系最有趣的一方面也許是:
對(duì)象實(shí)例和構(gòu)造函數(shù)直接沒有直接聯(lián)系。(對(duì)象實(shí)例只有 [[Prototype]] 屬性(自己測試時(shí)不能讀?。?b>_proto_))指向其相應(yīng)的原型對(duì)象,而原型對(duì)象的 constructor 屬性指向構(gòu)造函數(shù),而構(gòu)造函數(shù)的 prototype 指向原型對(duì)象)
因?yàn)槊總€(gè)對(duì)象的 [[Prototype]] 只是一個(gè)指向原型對(duì)象的指針,所以原型對(duì)象的改動(dòng)會(huì)立刻反映到所有引用它的對(duì)象。
當(dāng)對(duì)一個(gè)對(duì)象使用封印 Object.seal() 或凍結(jié) Object.freeze() 時(shí),完全是在操作對(duì)象的自有屬性,但任然可以通過在原型對(duì)象上添加屬性來擴(kuò)展這些對(duì)象實(shí)例。
String.prototype.capitalize = function(){ return this.charAt(0).toUpperCase() + this.substring(1); }總結(jié)
構(gòu)造函數(shù)就是用 new 操作符調(diào)用的普通函數(shù)??捎眠^ instanceof 操作符或直接訪問 constructor(實(shí)際上是原型對(duì)象的屬性) 來鑒別對(duì)象是被哪個(gè)構(gòu)造函數(shù)所創(chuàng)建的。
每個(gè)函數(shù)都有一個(gè) prototype 對(duì)象,它定義了該構(gòu)造函數(shù)創(chuàng)建的所有對(duì)象共享的屬性。而 constructor 屬性實(shí)際上是定義在原型對(duì)象里,供所有對(duì)象實(shí)例共享。
每個(gè)對(duì)象實(shí)例都有 [[Prototype]] 屬性,它是指向原型對(duì)象的指針。當(dāng)訪問對(duì)象的某個(gè)屬性時(shí),先從對(duì)象自身查找,找不到的話就到原型對(duì)象上找。
內(nèi)建對(duì)象的原型對(duì)象也可被修改
5. 繼承 5.1 原型對(duì)象鏈和 Object.prototypeJavaScript內(nèi)建的繼承方法被稱為 原型對(duì)象鏈(又叫原型對(duì)象繼承)。
原型對(duì)象的屬性可經(jīng)由對(duì)象實(shí)例訪問,這就是繼承的一種形式。對(duì)象實(shí)例繼承了原型對(duì)象的屬性,而原型對(duì)象也是一個(gè)對(duì)象,它也有自己的原型對(duì)象并繼承其屬性,以此類推。這就是原型對(duì)象鏈。
所有對(duì)象(包括自義定的)都自動(dòng)繼承自 Object,除非你另有指定。更確切地說,所有對(duì)象都繼承自 Object.prototype。任何以對(duì)象字面量形式定義的對(duì)象,其 [[Prototype]] 的值都被設(shè)為 Object.prototype,這意味著它繼承 Object.prototype 的屬性。
5.1.1 繼承自 Object.prototype 的方法Object.prototype 一般有以下幾個(gè)方法
hasOwnProperty() 檢測是否存在一個(gè)給定名字的自有屬性
propertyIsemumerable() 檢查一個(gè)自有屬性是否可枚舉
isPrototypeOf 檢查一個(gè)對(duì)象是否是另一個(gè)對(duì)象的原型對(duì)象
valueOf() 返回一個(gè)對(duì)象的值表達(dá)
toString() 返回一個(gè)對(duì)象的字符串表達(dá)
這 5 種方法經(jīng)由繼承出現(xiàn)在所有對(duì)象中。
因?yàn)樗袑?duì)象都默認(rèn)繼承自 Object.prototype,所以改變它就會(huì)影響所有的對(duì)象。所以不建議。
對(duì)象繼承是最簡單的繼承類型。你唯需要做的是指定哪個(gè)對(duì)象是新對(duì)象的 [[Prototype]]。對(duì)象字面量形式會(huì)隱式指定 Object.prototype 為其 [[Protoype]]。當(dāng)然我們可以用 ES5 的 Object.create() 方法顯式指定。該方法接受兩個(gè)參數(shù),第一個(gè)是新對(duì)象的的 [[Prototype]] 所指向的對(duì)象。第二個(gè)參數(shù)是可選的一個(gè)屬性描述對(duì)象,其格式與 Object.definePrototies()一樣。
var obj = { name: "Ljc" }; // 等同于 var obj = Object.create(Object.prototype, { name: { value: "Ljc", configurable: true, enumberable: true, writable: true } });
下面是繼承其它對(duì)象:
var person = { name: "Jack", sayName: function(){ console.log(this.name); } } var student = Object.create(person, { name:{ value: "Ljc" }, grade: { value: "fourth year of university", enumerable: true, configurable: true, writable: true } }); person.sayName(); // "Jack" student.sayName(); // "Ljc" console.log(person.hasOwnProperty("sayName")); // true console.log(person.isPrototypeOf(student)); // true console.log(student.hasOwnProperty("sayName")); // false console.log("sayName" in student); // true
當(dāng)訪問一個(gè)對(duì)象屬性時(shí),JavaScript引擎會(huì)執(zhí)行一個(gè)搜索過程。如果在對(duì)象實(shí)例存在該自有屬性,則返回,否則,根據(jù)其私有屬性 [[Protoype]] 所指向的原型對(duì)象進(jìn)行搜索,找到返回,否則繼承上述操作,知道繼承鏈末端。末端通常是 Object.prototype,其 [[Prototype]] 是 null。
當(dāng)然,也可以用 Object.create() 常見一個(gè) [[Prototype]] 為 null 的對(duì)象。
var obj = Object.create(null); console.log("toString" in obj); // false
該對(duì)象是一個(gè)沒有原型對(duì)象鏈的對(duì)象,即是一個(gè)沒有預(yù)定義屬性的白板。
5.3 構(gòu)造函數(shù)繼承JavaScript 中的對(duì)象繼承也是構(gòu)造函數(shù)繼承的基礎(chǔ)。
第四章提到,幾乎所有函數(shù)都有 prototype 屬性,它可被修改或替換。該 prototype 屬性被自動(dòng)設(shè)置為一個(gè)新的繼承自 Object.prototype 的泛用對(duì)象,該對(duì)象(原型對(duì)象)有一個(gè)自有屬性 constructor。實(shí)際上,JavaScript 引擎為你做了下面的事情。
// 你寫成這樣 function YourConstructor(){ // initialization } // JavaScript引擎在背后為你做了這些處理 YourConstructor.prototype = Object.create(Object.prototype, { constructor: { configurable: true, enumerable: true, value: YourConstructor, writable: true } })
你不需要做額外的工作,這段代碼幫你把構(gòu)造函數(shù)的 prototype 屬性設(shè)置為一個(gè)繼承自 Object.prototype 的對(duì)象。這意味著 YourConstructor 創(chuàng)建出來的任何對(duì)象都繼承自 Object.prototype。
由于 prototype 可寫,你可以通過改變它來改變原型對(duì)象鏈。
MDN:instanceof 運(yùn)算符可以用來判斷某個(gè)構(gòu)造函數(shù)的 prototype 屬性是否存在另外一個(gè)要檢測對(duì)象的原型鏈上。
function Rectangle(length, width){ this.length = length; this.width = width } Rectangle.prototype.getArea = function(){ return this.length * this.width } Rectangle.prototype.toString = function(){ return "[Rectangle " + this.length + "x" + this.width + "]"; }
// inherits from Rectangle function Square(size){ this.length = size; this.width = size; } Square.prototype = new Rectangle(); // 盡管是 Square.prototype 是指向了 Rectangle 的對(duì)象實(shí)例,即Square的實(shí)例對(duì)象也能訪問該實(shí)例的屬性(如果你提前聲明了該對(duì)象,且給該對(duì)象新增屬性)。 // Square.prototype = Rectangle.prototype; // 這種實(shí)現(xiàn)沒有上面這種好,因?yàn)镾quare.prototype 指向了 Rectangle.prototype,導(dǎo)致修改Square.prototype時(shí),實(shí)際就是修改Rectangle.prototype。 console.log(Square.prototype.constructor); // 輸出 Rectangle 構(gòu)造函數(shù) Square.prototype.constructor = Square; // 重置回 Square 構(gòu)造函數(shù) console.log(Square.prototype.constructor); // 輸出 Square 構(gòu)造函數(shù) Square.prototype.toString = function(){ return "[Square " + this.length + "x" + this.width + "]"; } var rect = new Rectangle(5, 10); var square = new Square(6); console.log(rect.getArea()); // 50 console.log(square.getArea()); // 36 console.log(rect.toString()); // "[Rectangle 5 * 10]", 但如果是Square.prototype = Rectangle.prototype,則這里會(huì)"[Square 5 * 10]" console.log(square.toString()); // "[Square 6 * 6]" console.log(square instanceof Square); // true console.log(square instanceof Rectangle); // true console.log(square instanceof Object); // true
Square.prototype 并不真的需要被改成為一個(gè) Rectangle 對(duì)象。事實(shí)上,是 Square.prototype 需要指向 Rectangle.prototype 使得繼承得以實(shí)現(xiàn)。這意味著可以用 Object.create() 簡化例子。
// inherits from Rectangle function Square(size){ this.length = size; this.width = size; } Square.prototype= Object.create(Rectangle.prototype, { constructor: { configurable: true, enumerable: true, value: Square, writable: true } })
5.4 構(gòu)造函數(shù)竊取在對(duì)原型對(duì)象添加屬性前要確保你已經(jīng)改成了原型對(duì)象,否則在改寫時(shí)會(huì)丟失之前添加的方法(因?yàn)槔^承是將被繼承對(duì)象賦值給需要繼承的原型對(duì)象,相當(dāng)于重寫了需要繼承的原型對(duì)象)。
由于JavaScript中的繼承是通過原型對(duì)象鏈來實(shí)現(xiàn)的,因此不需要調(diào)用對(duì)象的父類的構(gòu)造函數(shù)。如果確實(shí)需要在子類構(gòu)造函數(shù)中調(diào)用父類構(gòu)造函數(shù),那就可以在子類的構(gòu)造函數(shù)中利用 call、apply方法調(diào)用父類的構(gòu)造函數(shù)。
// 在上面的代碼基礎(chǔ)上作出修改 // inherits from Rectangle function Square(size){ Rectangle.call(this, size, size); // optional: add new properties or override existing ones here }
一般來說,需要修改 prototyp 來繼承方法并用構(gòu)造函數(shù)竊取來設(shè)置屬性,由于這種做法模仿了那些基于類的語言的類繼承,所以這通常被稱為偽類繼承。
5.5 訪問父類方法其實(shí)也是通過指定 call 或 apply 的子對(duì)象調(diào)用父類方法。
6 對(duì)象模式 6.1 私有成員和特權(quán)成員JavaScipt 對(duì)象的所有屬性都是公有的,沒有顯式的方法指定某個(gè)屬性不能被外界訪問。
6.1.1 模塊模式模塊模式是一種用于創(chuàng)建擁有私有數(shù)據(jù)的單件對(duì)象的模式。
基本做法是使用立即調(diào)用函數(shù)表達(dá)式(IIFE)來返回一個(gè)對(duì)象。原理是利用閉包。
var yourObj = (function(){ // private data variables return { // public methods and properties } }());
模塊模式還有一個(gè)變種叫暴露模塊模式,它將所有的變量和方法都放在 IIFE 的頭部,然后將它們設(shè)置到需要被返回的對(duì)象上。
// 一般寫法 var yourObj = (function(){ var age = 25; return { name: "Ljc", getAge: function(){ return agel } } }()); // 暴露模塊模式 var yourObj = (function(){ var age = 25; function getAge(){ return agel }; return { name: "Ljc", getAge: getAge } }());6.1.2 構(gòu)造函數(shù)的私有成員(不能通過對(duì)象直接訪問)
模塊模式在定義單個(gè)對(duì)象的私有屬性十分有效,但對(duì)于那些同樣需要私有屬性的自定義類型呢?你可以在構(gòu)造函數(shù)中使用類似的模式來創(chuàng)建每個(gè)實(shí)例的私有數(shù)據(jù)。
function Person(name){ // define a variable only accessible inside of the Person constructor var age = 22; this.name = name; this.getAge = function(){ return age; }; this.growOlder = function(){ age++; } } var person = new Person("Ljc"); console.log(person.age); // undefined person.age = 100; console.log(person.getAge()); // 22 person.growOlder(); console.log(person.getAge()); // 23
這里有個(gè)問題:如果你需要對(duì)象實(shí)例擁有私有數(shù)據(jù),就不能將相應(yīng)方法放在 prototype 上。
如果你需要所有實(shí)例共享私有數(shù)據(jù)。則可結(jié)合模塊模式和構(gòu)造函數(shù),如下:
var Person = (function(){ var age = 22; function InnerPerson(name){ this.name = name; } InnerPerson.prototype.getAge = function(){ return age; } InnerPerson.prototype.growOlder = function(){ age++; }; return InnerPerson; }()); var person1 = new Person("Nicholash"); var person2 = new Person("Greg"); console.log(person1.name); // "Nicholash" console.log(person1.getAge()); // 22 console.log(person2.name); // "Greg" console.log(person2.getAge()); // 22 person1.growOlder(); console.log(person1.getAge()); // 23 console.log(person2.getAge()); // 236.2 混入
這是一種偽繼承。一個(gè)對(duì)象在不改變原型對(duì)象鏈的情況下得到了另外一個(gè)對(duì)象的屬性被稱為“混入”。因此,和繼承不同,混入讓你在創(chuàng)建對(duì)象后無法檢查屬性來源。
純函數(shù)實(shí)現(xiàn):
function mixin(receiver, supplier){ for(var property in supplier){ if(supplier.hasOwnProperty(property)){ receiver[property] = supplier[property]; } } }
這是淺拷貝,如果屬性的值是一個(gè)引用,那么兩者將指向同一個(gè)對(duì)象。
6.3 作用域安全的構(gòu)造函數(shù)構(gòu)造函數(shù)也是函數(shù),所以不用 new 也能調(diào)用它們來改變 this 的值。在非嚴(yán)格模式下, this 被強(qiáng)制指向全局對(duì)象。而在嚴(yán)格模式下,構(gòu)造函數(shù)會(huì)拋出一個(gè)錯(cuò)誤(因?yàn)閲?yán)格模式下沒有為全局對(duì)象設(shè)置 this,this 保持為 undefined)。
而很多內(nèi)建構(gòu)造函數(shù),例如 Array、RegExp 不需要 new 也能正常工作,這是因?yàn)樗鼈儽辉O(shè)計(jì)為作用域安全的構(gòu)造函數(shù)。
當(dāng)用 new 調(diào)用一個(gè)函數(shù)時(shí),this 指向的新創(chuàng)建的對(duì)象是屬于該構(gòu)造函數(shù)所代表的自定義類型。因此,可在函數(shù)內(nèi)用 instanceof 檢查自己是否被 new 調(diào)用。
function Person(name){ if(this instanceof Person){ // called with "new" }else{ // called without "new" } }
具體案例:
function Person(name){ if(this instanceof Person){ this.name = name; }else{ return new Person(name); } }總結(jié)
看了兩天的書,做了兩天的筆記。當(dāng)然這只是ES5的。過幾天 ES6 新書又來了。最后感謝 異步社區(qū) 送我這本好書 《JavaScript面向?qū)ο缶?,讓我的前端根基更加穩(wěn)固,希望自己的前端之路越走越順。
對(duì)應(yīng) GitHub。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/78521.html
摘要:面向?qū)ο缶x書筆記下面代碼的實(shí)際執(zhí)行過程是什么使用原始值和原始封裝類型是有區(qū)別的因?yàn)槭潜唤馕龀梢粋€(gè)對(duì)象的,所以肯定是真的函數(shù)是對(duì)象,函數(shù)有兩種字面形式,第一種是函數(shù)聲明,以關(guān)鍵字開頭后面跟函數(shù)名字。 Javascript面向?qū)ο缶x書筆記 1、下面代碼的實(shí)際執(zhí)行過程是什么? var name = fan var str = name.charAt(0) console.l...
摘要:使用時(shí),會(huì)自動(dòng)創(chuàng)建對(duì)象,其類型為構(gòu)造函數(shù)類型,指向?qū)ο髮?shí)例缺少關(guān)鍵字,指向全局對(duì)象。構(gòu)造函數(shù)本身也具有屬性指向原型對(duì)象。 在JavaScript面向?qū)ο缶?一)中講解了一些與面向?qū)ο笙嚓P(guān)的概念和方法,這篇講講原型和繼承。 構(gòu)造函數(shù)和原型對(duì)象 構(gòu)造函數(shù)也是函數(shù),用new創(chuàng)建對(duì)象時(shí)調(diào)用的函數(shù),與普通函數(shù)的一個(gè)區(qū)別是,其首字母應(yīng)該大寫。但如果將構(gòu)造函數(shù)當(dāng)作普通函數(shù)調(diào)用(缺少new關(guān)鍵字...
摘要:使函數(shù)不同于其他對(duì)象的決定性特性是函數(shù)存在一個(gè)被稱為的內(nèi)部屬性。其中,是一個(gè)布爾值,指明改對(duì)象本身是否可以被修改值為。注意凍結(jié)對(duì)象和封印對(duì)象均要在嚴(yán)格模式下使用。 數(shù)據(jù)類型 在JavaScript中,數(shù)據(jù)類型分為兩類: 原始類型:保存一些簡單數(shù)據(jù),如true,5等。JavaScript共有5中原始類型: boolean:布爾,值為true或false number:數(shù)字,值...
摘要:原文第一章主要介紹的大概情況基本語法。通過和來引用對(duì)象屬性或數(shù)組元素的值就構(gòu)成一個(gè)表達(dá)式。 原文:https://keelii.github.io/2016/06/16/javascript-definitive-guide-note-0/ 第一章 主要介紹 JavaScript 的大概情況、基本語法。之前沒有 JavaScript 基礎(chǔ)的看不懂也沒關(guān)系,后續(xù)章節(jié)會(huì)有進(jìn)一步的詳細(xì)說明...
閱讀 2118·2021-11-24 10:28
閱讀 1143·2021-10-12 10:12
閱讀 3350·2021-09-22 15:21
閱讀 691·2021-08-30 09:44
閱讀 1907·2021-07-23 11:20
閱讀 1155·2019-08-30 15:56
閱讀 1767·2019-08-30 15:44
閱讀 1490·2019-08-30 13:55