《JavaScript面向對象精要》之四:構造函數和原型對象

這裏有一份簡潔的前端知識體系等待你查收,看看吧,會有驚喜哦~若是以爲不錯,懇求star哈~前端


因爲 JavaScript(ES5) 缺少類,但可用構造函數和原型對象給對象帶來與類類似的功能。git

4.1 構造函數

構造函數的函數名首字母應大寫,以此區分其餘函數。github

當沒有須要給構造函數傳遞參數,可忽略小括號:api

var Person = {
  // 故意留空
}
var person = new Person;
複製代碼

儘管 Person 構造函數沒有顯式返回任何東西,但 new 操做符會自動建立給定類型的對象並返回它們。數組

每一個對象在建立時都自動擁有一個構造函數屬性(constructor,實際上是它們的原型對象上的屬性),其中包含了一個指向其構造函數的引用。bash

經過對象字面量形式({})或 Object 構造函數建立出來的泛用對象,其構造函數屬性(constructor)指向 Object;而那些經過自定義構造函數建立出來的對象,其構造函數屬性指向建立它的構造函數。函數

console.log(person.constructor === Person); // true
console.log(({}).constructor === Object); // true
console.log(([1,2,3]).constructor === Object); // false

// 證實 constructor 是在原型對象上
console.log(person.hasOwnProperty("constructor")); // false
console.log(person.constructor.prototype.hasOwnProperty("constructor")); // true
複製代碼

儘管對象實例及其構造函數之間存在這樣的關係,但仍是建議使用 instanceof 來檢查對象類型。這是由於構造函數屬性能夠被覆蓋。(person.constructor = "")。ui

當你調用構造函數時,new 會自動自動建立 this 對象,且其類型就是構造函數的類型(構造函數就像類,至關於一種數據類型)。this

你也能夠在構造函數中顯式調用 return。若是返回值是一個對象,它會代替新建立的對象實例而返回,若是返回值是一個原始類型,它會被忽略,新建立的對象實例會被返回。spa

始終確保要用 new 調用構造函數;不然,你就是在冒着改變全局對象的風險,而不是建立一個新的對象。

var person = Person("Nicholas"); // 缺乏 new

console.log(person instanceof Person); // false
console.log(person); // undefined,由於沒用 new,就至關於一個普通函數,默認返回 undefined
console.log(name); // "Nicholas"
複製代碼

當 Person 不是被 new 調用時,構造函數中的 this 對象等於全局 this 對象。

在嚴格模式下,會報錯。由於嚴格模式下,並無爲全局對象設置 this,this 保持爲 undefined。

如下代碼,經過 new 實例化 100 個對象,則會有 100 個函數作相同的事。所以可用 prototype 共享同一個方法會更高效。

var person = {
  name: "Nicholas",
  sayName: function(){
    console.log(this.name);
  }
}
複製代碼

4.2 原型對象

能夠把原型對象看做是對象的基類。幾乎全部的函數(除了一些內建函數)都有一個名爲 prototype 的屬性,該屬性是一個原型對象用來建立新的對象實例。

全部建立的對象實例(同一構造函數,固然,可能訪問上層的原型對象)共享該原型對象,且這些對象實例能夠訪問原型對象的屬性。例如,hasOwnProperty() 定義在 Object 的原型對象中,但卻可被任何對象看成本身的屬性訪問。

var book = {
  title : "book_name"
}

"hasOwnProperty" in book; // true
book.hasOwnProperty("hasOwnProperty"); // false
Object.property.hasOwnProperty("hasOwnProperty"); // true
複製代碼

鑑別一個原型屬性

function hasPrototypeProperty(object, name){
  return name in object && !object.hasOwnProperty(name);
}
複製代碼

4.2.1 [[Prototype]] 屬性

一個對象實例經過內部屬性 [[Prototype]] 跟蹤其原型對象。

該屬性是一個指向該實例使用的原型對象的指針。當你用 new 建立一個新的對象時,構造函數的原型對象就會被賦給該對象的 [[Prototype]] 屬性。

Object.getPrototypeOf() 方法可讀取 [[Prototype]] 屬性的值。

var obj = {};
var prototype = Object.getPrototypeOf(obj);

console.log(prototype === Object.prototype); // true
複製代碼

大部分 JavaScript 引擎在全部對象上都支持一個名爲 __proto__ 的屬性。該屬性使你能夠直接讀寫 [[Prototype]] 屬性。

isPrototypeOf() 方法會檢查某個對象是不是另外一個對象的原型對象,該方法包含在全部對象中。

var obj = {}
console.log(Object.prototype.isPrototypeOf(obj)); // true
複製代碼

當讀取一個對象的屬性時,JavaScript 引擎首先在該對象的自有屬性查找屬性名。若是找到則返回。不然會搜索 [[Prototype]] 中的對象,找到則返回,找不到則返回 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不能刪除一個對象從原型繼承而來的屬性
cconsole.log(obj.toString()); // // "[object Object]"
複製代碼

delete 操做符不能刪除的屬性有:①顯式聲明的全局變量不能被刪除,該屬性不可配置(not configurable); ②內置對象的內置屬性不能被刪除; ③不能刪除一個對象從原型繼承而來的屬性(不過你能夠從原型上直接刪掉它)。

一個重要概念:沒法給一個對象的原型屬性賦值。但咱們能夠經過 obj.constructor.prototype.sayHi = function(){console.log("Hi!")} 向原型對象添加屬性。

(圖片中間能夠看出,爲對象 obj 添加的 toString 屬性代替了原型屬性)

4.2.2 在構造函數中使用原型對象

原型對象的共享機制使得它們成爲一次性爲全部對象定義全部方法的理想手段,由於一個方法對全部的對象實例作相同的事,沒理由每一個實例都要有一份本身的方法。

將方法放在原型對象中並使用this方法當前實例是更有效的作法。

function Person(name) {this.name = name}
Person.prototype.sayName = function() {console.log(this.name)};
var person1 = new Person("Nicholas")
console.log(person1.name)                        // Nicholas
person1.sayName()                                // Nicholas
複製代碼

也能夠在原型對象上存儲其餘類型的數據,可是在存儲引用值時要注意,由於這些引用值會被多個實例共享,可能你們不但願一個實例可以改變另外一個實例的值。

function Person(name) {this.name = name}
Person.prototype.favorites = []
var person1 = new Person("Nicholas")
var person2 = new Person("Greg")
person1.favorites.push("pizza")
person2.favorites.push("quinoa")

console.log(person1.favorites)                // ["pizza", "quinoa"]
console.log(person2.favorites)                // ["pizza", "quinoa"]
複製代碼

favorites屬性被定義到原型對象上,意味着person1.favorites和person2.favorites指向同一個數組,你對任意Person對象的favorites插入的值都將成爲原型對象上數組的元素。也可使用字面量的形式替換原型對象:

function Person(name) {this.name=name}
Person.prototype= {
    sayName: function() {console.log(this.name)},
    toString: function(){return `[Person ${this.name} ]`}
}
複製代碼

雖然用這種字面量的形式定義原型很是簡潔,可是有個反作用須要注意。

var person1 = new Person('Nicholas')
console.log(person1 instanceof Person)                // true
console.log(person1.constructor === Person)                // false
console.log(person1.constructor === Object)                // true
複製代碼

使用字面量形式改寫原型對象改寫了構造函數的屬性,所以如今指向Object而不是Person,這是由於原型對象具備個constructor屬性,這是其餘對象實例所沒有的。當一個函數被建立時,其prototype屬性也被建立,且該原型對象的constructor屬性指向該函數本身,當使用字面量形式改寫原型對象Person.prototype時,其constructor屬性將被複寫爲泛用對象Object。爲了不這一點,須要在改寫原型對象時手動重置其constructor屬性:

function Person(name) {this.name = name}
Person.prototype = {
    constructor: Person,             // 爲了避免忘記賦值,最好在第一個屬性就把constructor重置爲本身
    sayName() {console.log(this.name)},
    toString() {return `[Person ${this.name} ]`}
}

var person1 = new Person('Nicholas')
console.log(person1 instanceof Person)                    // true
console.log(person1.constructor === Person)                // true
console.log(person1.constructor === Object)                // false
複製代碼

構造函數、原型對象、對象實例之間:對象實例和構造函數之間沒有直接聯繫。不過對象實例和原型對象之間以及原型對象和構造函數之間都有直接聯繫。

這樣的鏈接關係也意味着,若是打斷對象實例和原型對象之間的聯繫,那麼也將打斷對象實例及其構造函數之間的關係。

4.2.3 改變原型對象

由於每一個對象的 [[Prototype]] 只是一個指向原型對象的指針,因此原型對象的改動會馬上反映到全部引用它的對象。

當對一個對象使用封印 Object.seal() 或凍結 Object.freeze() 時,徹底是在操做對象的自有屬性,但任然能夠經過在原型對象上添加屬性來擴展這些對象實例。

4.2.4 內建對象(如Array、String)的原型對象

全部內建對象都有構造函數,所以也都有原型對象能夠去改變,例如要在數組上添加一個新的方法只須要改變Array.prototype便可

Array.prototype.sum = function() {
    return this.reduce((privious, current) => privious + current)
}
var numbers = [1, 2, 3, 4, 5, 6]
var result = numbers.sum()
console.log(result)                    // 21
複製代碼

sum()函數內部,在調用時this指向數組的對象實例numbers,所以this也能夠調用該數組的其餘方法,好比reduce()。 改變原始封裝類型的原型對象,就能夠給這些原始值添加更多功能,好比:

String.prototype.capitalize = function() {
    return this.charAt(0).toUpperCase() + this.substring(1)
}
var message = 'hello world!'
console.log(message.capitalize())            // Hello world!
複製代碼

總結

  • 構造函數就是用 new 操做符調用的普通函數。可用過 instanceof 操做符或直接訪問 constructor(其實是原型對象的屬性) 來鑑別對象是被哪一個構造函數所建立的。
  • 每一個函數都有一個 prototype 對象,它定義了該構造函數建立的全部對象共享的屬性。而 constructor 屬性其實是定義在原型對象裏,供全部對象實例共享。
  • 每一個對象實例都有 [[Prototype]] 屬性,它是指向原型對象的指針。當訪問對象的某個屬性時,先從對象自身查找,找不到的話就到原型對象上找。
  • 內建對象的原型對象也可被修改
相關文章
相關標籤/搜索