在JavaScript中,對象是屬性的無序集合,每一個屬性存放一個原始值、對象或函數。javascript
在JavaScript中建立對象的兩種方法:
① 字面上:html
var myObj = { key: value, // ... };
② 面向對象:java
var myObj = new Object(); myObj.key = value;
注意:在對象中,屬性名永遠都是字符串。若是你使用string
(字面量)之外的其餘值做爲屬性名,那它首先會被轉換爲一個字符串。es6
JavaScript中的內置對象分爲構造器對象和普通對象。
構造器對象:編程
Object
Boolean
String
Number
Function
Array
RegExp
Date
Error
其餘對象:app
Math
JSON
內置對象,其實也叫內置構造器,它們能夠經過new
的方式建立一個新的實例對象。函數
先上圖。
this
__proto__
、prototype
和constructor
在JavaScript中,每一個對象都擁有一個原型對象prototype
,而指向該原型對象的內部指針則是__proto__
屬性。
__proto__
屬性先後各兩個下劃線,表示它本質是一個內部屬性,而不是一個正式的對外的API。prototype
{}
建立的對象var myObj = {}; console.log(myObj.__proto__ === Object.prototype); // true
JavaScript中的函數也是對象,它的原型是Function.prototype
。指針
var Foo = function() {} Foo.__proto__ === Function.prototype // true
經過函數構造的實例對象,其原型指針__proto__
會指向該函數的prototype
屬性。
function Foo(){} var a = new Foo(); console.log(a.__proto__ === Foo.prototype); // true console.log(Foo.prototype.__proto__ === Object.prototype); // true // constructor console.log(Foo.prototype.constructor === Foo); // true
原型對象都包含一個指向構造函數的指針constructor
。
注意:constructor
屬性並非實例對象的屬性,而是構造函數的原型對象的constructor
屬性。
console.log(a.constructor === Foo); // true console.log(a.constructor === Foo.prototype.constructor); // true console.log(a.hasOwnProperty("constructor")); //false console.log(Foo.prototype.hasOwnProperty("constructor")); //true
a.constructor
經過原型鏈往上找到其原型對象Foo.prototype
的constructor
屬性。
var a = new Array(); a.__proto__ === Array.prototype // true
Array.prototype
自己也是一個對象,也有繼承的原型:
a.__proto__.__proto__ === Object.prototype // true // 等同於 Array.prototype.__proto__ === Object.prototype
那麼Object
的原型指向的是誰呢?
a.__proto__.__proto__.__proto__ === null // true // 等同於 Object.prototype.__proto__ === null
總結:
原型鏈的意義在於,當讀取對象的某個屬性時,JavaScript引擎先尋找對象自己的屬性,若是找不到,就到它的原型去找,若是仍是找不到,就到原型的原型去找。以此類推,若是直到最頂層的Object.prototype
仍是找不到,則返回undefined
。(全部對象的原型鏈的頂端是Object.prototype
)
JavaScript語言的傳統方法是經過構造函數,定義並生成新對象。下面是一個例子。
function Point(x, y) { this.x = x; this.y = y; } // 下面覆蓋了原型鏈上Object.prototype.toString方法 Point.prototype.toString = function () { return '(' + this.x + ', ' + this.y + ')'; }; var p = new Point(1, 2); console.log(p.toString()); // (1, 2)
ES6引入了Class這個概念,經過class
關鍵字能夠定義類。
ES6的class
能夠看做是一種語法糖,它的絕大部分功能,ES5均可以作到,新的class
寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。
//定義類 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } console.log(typeof Point); // "function" console.log(Point === Point.prototype.constructor); // true var p = new Point(1, 2); console.log(p.toString()); // (1, 2)
上面代碼代表,類的數據類型就是函數,構造函數的prototype
屬性,在ES6的"類"上面繼續存在。
事實上,類的全部方法都定義在類的prototype
屬性上面。
class Point { constructor() {} toString() {} toValue(){} } // 等同於 Point.prototype = { toString(){}, toValue(){} };
constructor
方法constructor
方法是類的默認方法,經過new
命令生成對象實例時,自動調用該方法。一個類必須有constructor
方法,若是沒有顯式定義,一個空的constructor
方法會被默認添加。
constructor() {}
constructor
方法默認返回實例對象(即this
),徹底能夠指定返回另一個對象。
class Foo { // constructor() {} } console.log(new Foo() instanceof Foo); // true
class Foo { constructor() { return Object.create(null); } } console.log(new Foo() instanceof Foo); // false
類必須使用new
來調用,從而生成類的實例對象。
實例的屬性除非顯式定義在其自己(即定義在this
對象上),不然都是定義在原型上(即定義在class
上)。
//定義類 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } } var point = new Point(2, 3); console.log(point.hasOwnProperty('x')); // true console.log(point.hasOwnProperty('y')); // true console.log(point.hasOwnProperty('toString')); // false console.log(point.__proto__.hasOwnProperty('toString')); // true
類的全部實例共享一個原型對象。這也意味着,經過實例的__proto__
屬性能夠爲原型對象添加方法。
var p1 = new Point(2,3); var p2 = new Point(3,2); console.log(p1.__proto__ === p2.__proto__); // true p1.__proto__.printName = function () { return 'Oops' }; p1.printName() // "Oops" p2.printName() // "Oops"
上面代碼使用實例的__proto__
屬性改寫原型,必須至關謹慎,不推薦使用,由於這會改變Class的原始定義,影響到全部實例。
Class之間能夠經過extends
關鍵字實現繼承,這比ES5的經過修改原型鏈實現繼承,要清晰和方便不少。
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 調用父類的constructor(x, y) this.color = color; } toString() { return this.color + ' ' + super.toString(); // 調用父類的toString() } }
上面代碼中,constructor
方法和toString
方法之中,都出現了super
關鍵字,它在這裏表示父類的構造函數,用來新建父類的this
對象。
子類必須在constructor
方法中調用super
方法,不然新建實例時會報錯。這是由於子類沒有本身的this
對象,而是繼承父類的this
對象,而後對其進行加工。若是不調用super
方法,子類就得不到this
對象。
ES5的繼承,實質是先創造子類的實例對象this
,而後再將父類的方法添加到this
上面(Parent.apply(this)
)。ES6的繼承機制徹底不一樣,實質是先創造父類的實例對象this
(因此必須先調用super
方法),而後再用子類的構造函數修改this
。
注意:在子類的構造函數中,只有調用super
以後,纔可使用this
關鍵字,不然會報錯。這是由於子類實例的構建,是基於對父類實例加工,只有super
方法才能返回父類實例。
class Point { constructor(x, y) { this.x = x; this.y = y; } } class ColorPoint extends Point { constructor(x, y, color) { this.color = color; // ReferenceError super(x, y); this.color = color; // 正確 } }
__proto__
和prototype
類同時有__proto__
屬性和prototype
屬性,同時存在兩條繼承鏈。
__proto__
屬性,表示構造函數的繼承,老是指向父類。prototype
屬性的__proto__
屬性,表示方法的繼承,老是指向父類的prototype
屬性。class A { } class B extends A { } B.__proto__ === A // true B.prototype.__proto__ === A.prototype // true
這樣的結果是由於,類的繼承是按照下面的模式實現的。
class A { } class B { } // B的實例繼承A的實例 Object.setPrototypeOf(B.prototype, A.prototype); // B繼承A的靜態屬性 Object.setPrototypeOf(B, A); Object.setPrototypeOf = function (obj, proto) { obj.__proto__ = proto; return obj; }
這裏只介紹JavaScript的原型鏈相關的內容,若是想了解關於ES6 Class的更多內容請閱讀ES6文檔。
參考: