目錄編程
JavaScript 存在着兩種數據類型:「基本數據類型」 與 「引用數據類型」。
「引用數據類型」 是一種數據結構,它將數據(屬性)與功能(方法)組織在一塊兒,引用類型的值就是「引用類型」的實例。數組
var obj = new Object();
其中 Object
就是一種「引用類型」,而 obj
則是對應 Object
類型的值(實例),實例繼承着對應引用類型的屬性和方法。ECMAScript 還提供了不少其它的引用類型,例如:Array、Date、Math、Number、String、RegExp、Boolean、Function 等。數據結構
在 JavaScript 中一切皆對象,這是由於其它的對象類型都繼承自 Object
,固然也包括着上述的全部「引用類型」,所以 Object
在 JavaScript 中是一切對象的基礎對象。更直白的說,Window,Location、JSON、XMLHttpRequest、Array,Date 等全部其它對象在原型鏈上都繼承着 Object
,均可以看做是對 Object
的擴展對象。編程語言
Location.__proto__.__proto__ Array.__proto__.__proto__ JSON.__proto__ /* { constructor : ƒ Object() hasOwnProperty:ƒ hasOwnProperty() isPrototypeOf:ƒ isPrototypeOf() propertyIsEnumerable:ƒ propertyIsEnumerable() toLocaleString:ƒ toLocaleString() toString:ƒ toString() valueOf:ƒ valueOf() get __proto__:ƒ __proto__() set __proto__:ƒ __proto__() } */
Object
在 JavaScript 中有局部與全局兩種含義,全局層面 JavaScript 中一切皆對象,都是繼承至 OBJECT
都是 OBJECT
的擴展,不管是 Object、Location、Array、JSON ... 本質上都是 OBJECT
。而做爲局部的含義 Object
就是單純指 Object
這個對象,既經過 Object()
這個構造方法實例獲得的鍵值對的無序組合體。函數式編程
對於 「引用數據類型」 和「對象」關係,「引用類型」 必然是對象,可是對象不必定就是「引用數據類型」,所以 Location、XMLHttpRequest 等雖然是對象,但並非一種「引用數據類型」。函數
雖然在 ECMAScript 中 Object
的實例並不具有多少功能,但對於應用程序中數據的存儲與傳輸而言,則是很是理想的選擇。this
JavaScript 中的「對象」與 Java 這樣面嚮對象語言中的「類」,仍是有很大的區別,簡要能夠歸納出三點:編碼
建立prototype
Java中的對象必須由「類」來實例化,而 JavaScript 中對象能夠由字面量格式很是簡單的定義與建立,在 JavaScript 中對象就是很單純的數據與功能的集合。code
調用
Java 中類是不能夠直接調用的,只是用來實例化對象,就算是調用方法也只能調用類上的靜態方法,而在 JavaScript 中 充當類的構造函數是能夠做爲函數來調用的,例如:
Object(); //{} String(); //"" Number(); //0
另外構造器方法上的靜態方法也能夠直接使用,例如:String.fromCharCode(65)
。所以能夠看出 JS 對函數式編程支持更好!
繼承
在面向對象編程語言中 「類」 的重要概念就是繼承,由「類」實例化的對象便會繼承該類的方法與屬性,這種繼承關係更相似於派生以及父子關係,而在 JS 中繼承被更多的看做成具備相互關聯的關係,而這個關係的聯接就是咱們常說的「原型鏈(proto)」。
var Behavior = { eat: () => { alert('eat') } }; var Tom = { name: 'my name is Tom', __proto__: Behavior }; var Bob = { name: 'my name is Bob', __proto__: Behavior }; Tom.name; // my name is Tom; Tom.eat(); Bob.eat();
固然 JS 也支持面向對象開發語言的 new
運算符,例如常見的:
var obj = new Object();
但實際上這個 new
運算符更多的充當着語法糖角色,大體上來講,new
運算符主要有如下功能:
Constructor {}
,而後將構造函數中的 this
對象複製到新建的實例對象 this
上。prototype
對象複製到新建對象的 __proto__
屬性上,以便繼承該構造器 prototype
對象上的屬性與方法,例如:var obj = new Object(); obj.__proto__ === Object.prototype; // true;
實例化獲得的 obj 對象其 __proto__
屬性所指向關聯(繼承)的就是 Object()
構造器的 prototype
屬性(原型屬性)。
再好比一個完整的例子:
function Student(name) { this.name = name; } Student.prototype.say = function() { //將實例對象的 __proto__ 與自身的 prototype 進行了關聯。 alert(this.name); //複製了this到實例對象上 } var Tom = new Student('Tom'); Tom.say(); //Tome
此時 new
運算符作了兩件事,第一把 Student
中的 this
複製到新的實例對象 Tom 上因此 Tom 上也存在 name
屬性,而且值就是實例化過程當中傳入的 Tom
。第二則是把 Student
的 prorotype
對象複製到 Tom 這個實例化對象的 __proto__
屬性上。
console.log(Student.prototype); console.log(Tom.__proto__); /* { say: ƒ() constructor: ƒ Student(name) __proto__: Object } */
從上例能夠看出 Tom.__proto__
與 Student.prototype
指向的都是同一個原型對象,因此就很是好理解經過 new
運算符是如何實現繼承關係的了。
下面是該原型對象上的具體屬性與方法:
prototype
訪問原型對象,原型對象再經過 constructor
屬性訪問所依附的構造函數 )。通常來講實例對象的 __proto__
屬性保存的是其構造函數的 prototype
原型對象,而構造函數的原型對象自己也經過 __proto__
來講明其自身的繼承關係,一樣的,原型對象的繼承對象也含有 __proto__
屬性,依次類推咱們即可以得出一個 __proto__
屬性串聯出的 「繼承鏈—>原型鏈」。
以一個實例的數組對象爲例:
Array() length: 0 __proto__: Array(0){ map:ƒ map() filter:ƒ filter() push:ƒ push() indexOf:ƒ indexOf() sort:ƒ sort() ... constructor:ƒ Array() __proto__:Object{ constructor:ƒ Object() hasOwnProperty:ƒ hasOwnProperty() isPrototypeOf:ƒ isPrototypeOf() ... __proto__ : null } }
從中咱們能夠看出 JavaScript Object 對象是一切其它對象的最底層最基礎的對象,其它對象都是繼承自 Object,而 Object 繼承的則是 null。
從示例中 Array()
方法產生的匿名實例數組,其 __proto__
屬性指向的是 Array
構造函數的 prototype
對象,而 Array
構造函數的 prototype
對象又繼承自 Object()
構造函數的 prototype
原型對象。因此這種鏈式的繼承關係能夠以下圖所示:
目前咱們能夠簡單的總結下:在 JS 中構造函數相似與面向對象中「類」的關鍵點就在於方法(函數)具備 prototype
原型對象,它能夠做爲一個方法與屬性的公共載體,用於該構造函數的實例對象經過 __proto__
屬性進行繼承,而且其自己也經過一個 constructor
屬性再循環訪問到自身所依附的構造函數。因爲普通對象不存在 prototype
對象,因此也就沒法充當「類」的角色,可是對象自身 __proto__
屬性天生就是用來主動繼承的,因此經過手動修改對象的 __proto__
屬性也能夠很靈活的調整對象與對象之間的繼承關係。
前面提到過 Object
類型的實例是數據與方法的集合,由無序的鍵值對(key/value)構成,所以對其中每條 key/value 就能夠看作成是這個對象實例的成員,而 key 則是對象的屬性名或方法名,value 則是對象的屬性值或具體的功能方法。
在 ECMAScript 中 key 名能夠由任何屬於 Unicode 編碼的字符組成,但經常使用的仍是數字與字母。
在 ECMAScript 中成員的訪問主要有兩種方式:
它們的區別是 「點」 表示法更通用,更直觀,更快捷。但缺點則是沒法訪問含有特殊字符、關鍵字、保留字的成員名。
Person.first name // Syntax Error
此時,括號表示法的強大就體現了出來。
Person ['first name']; Person ['first' + 'name'];
constructor
保存着建立當前實例對象的構造函數。
new Object().constructor; // "ƒ Object() { [native code] }"
這說明當前對象的構造函數就是 Object()。
hasOwnProperty()
接收一個參數 key
,判斷這個成員是否爲實例對象私有的方法與屬性,而非繼承着原型。
function Test() {} Test.prototype.custom = 1; var test = new Test(); test.custom_private = 1; console.log(test.hasOwnProperty('custom')); // false; console.log(test.hasOwnProperty('custom_private')); // true;
isPrototypeOf()
接收一個對象做爲參數,判斷這個對象是否繼承於指定的原型對象。
Test.prototype.isPrototypeOf(test) // true
propertyIsEnumerable()
接收一個參數 key
,判斷這個成員在指定的對象上是否可枚舉(遍歷訪問)。
console.log(test.propertyIsEnumerable('custom')); // false; console.log(test.propertyIsEnumerable('custom_private')); // true;
toLocaleString()
把對象轉換爲符合本地區格式的字符串。
toString()
把對象轉換爲字符串。
test.toString(); // "[object Object]"
在 JavaScript 中也只有 Object 的 toString()
方法纔會返回這種格式,所以利用這個特性,咱們能夠在轉換格式統一的基礎上進行類型的判斷。
Object.prototype.toString.call({}) //"[object Object]" Object.prototype.toString.call([]) //"[object Array]" Object.prototype.toString.call("") //"[object String]" Object.prototype.toString.call(1) //"[object Number]" Object.prototype.toString.call(true) //"[object Boolean]" Object.prototype.toString.call(null) //"[object Null]" Object.prototype.toString.call(undefined)//"[object Object]"
valueOf() 返回對象自身。