對象&原型鏈
面向對象編程(Object Oriented Programming,OOP),將現實世界的復雜關系抽象為一個個對象,由對象之間的分工合作完成對真實世界的模擬。
對象
對象是一個容器,有屬性(property)和方法(method)。
JavaScript中,每一個對象都繼承自另一個對象。對象的構建由構造函數實現,構造函數可理解為對象的模板。由于實例對象由其構造函數創建,每一個對象都繼承于另一個對象,在這里被繼承的對象稱之為“原型(prototype)”對象。其中只有null除外,它沒有自己的原型對象。
在JavaScript,通過構造函數生成實例對象時,會為實例對象分配原型對象。同時每個構造函數中都有一個prototype屬性,該屬性就是實例對象的原型對象,定義在prototype對象上的屬性和方法,都會被所有實例對象共享。因此,每一個實例對象都會有其原型對象,表示實例對象繼承于原型對象,同時實例對象可以調用其屬性或方法:
function animal (name){
this.name = name
}
animal.prototype.color = "white" // 給animal的prototype屬性定義一個屬性
var dog = new animal('阿毛')
dog.color // 'white' // 繼承其構造函數的屬性
對象最大的作用就是節省了公有屬性的內存占用,并且提供了復用。
原型鏈
對象的屬性或方法,可能定義在其自身或者定義在它的原型對象。由于原型對象本身也是對象,有自己的原型,這就形成了原型鏈。所有對象的原型最終追溯到Object.prototype
,Object的原型指向null,原型鏈到null終止,null沒有任何屬性。
原型鏈的作用是,讀取對象某個屬性,JavaScript引擎優先尋找自身的屬性,如果找不到再到原型找,如果還是找不到則到原型的原型找,如果到Object.prototype
還是找不到就返回undefined。如果自身和原型都有同名屬性,優先讀取自身屬性,這個稱之為“覆蓋”(overriding)。
constructor
prototype對象有一個constructor
屬性,prototype所在的構造函數。需要注意對象的繼承不一定要有構造函數
function Constr(){}
var x = new Constr()
var y = new x.constructor()
y instanceof Constr // true
// 可以通過實例對象間接生成新的實例對象
Constr.prototype.createCopy = function(){
return new this.constructor()
}
// 提供了實例方法中調用自身構造函數
this 指向
構造函數中的this指向實例對象,其原理為:
var fn = function (){
this.foo = 'bar'
}
var f = new fn()
// 等同于
var f = Object.setPrototypeOf({}, fn.prototype)
fn.call(f) // this指向實例對象
// new相當于語法糖? call的第一個參數為實例對象,所以this指向實例對象
instanceof
運算符
返回一個布爾值,表示指定對象是否為某個構造函數的實例,其實質為檢查右邊的構造函數是否在左邊的原型鏈上。
var v new Vehicle()
v instanceof Vehicle // true
// 等同于
Vehicle.prototype.isPrototypeOf(v)
常用方法
Object.getPrototypeOf()
:
// 獲取原型對象的標準方法
Object.getPrototypeOf()
Object.getPrototypeOf([]) === Array.prototype // true
Object.setPrototypeOf()
:
// 設置原型對象,返回新對象,接受兩個參數(現有對象,原型對象)
Object.setPrototypeOf()
var a = {x:1}
var b = Object.setPrototypeOf({},a)
b.x // 1
// b繼承于a 所以可以使用a對象的所有屬性和方法,但是b不是由名為a的構造函數生成,有區別
// 等同于var b = Object.create(a)
var fn = function (){
this.foo = 'bar'
}
var f = new fn()
// 等同于
var f = Object.setPrototypeOf({}, fn.prototype)
fn.call(f) // 因此this指向實例對象
Object.create()
:
var A = {
print: function(){
console.log('hello')
}
}
var B = Object.create(a)
B.print() //hello
B.print === A.print // true
// 等同于
var A = function(){}
A.prototype = {
print:function(){
console.log('hello')
}
}
var B = new A()
B.print === A.prototype.print
個人認為還是有區別的,一個是在原型上的屬性,一個是A自身的屬性,雖然都是可調用
new
new
命令執行構造函數,返回一個實例對象:
var a = function (){
this.bala = 123;
}
var b = new a()
b.bala // 123
其原理:
- 創建一個空對象,作為將要返回的對象實例
- 將這個空對象的原型指向prototype屬性
- 將這個空對象的值賦給函數內部的this關鍵字
- 開始執行構造函數內部代碼
參考前面的方法,總結為
var a = function(){
this.balaba = 123;
}
var b
b = {}
Object.setPrototypeOf(b, a)
a.call(b)
忘記new的處理方式
// 使用嚴格模式
function foo (a,b){
'use strict';
this.a = 'a';
this.b = 'b';
}
foo() // 嚴格模式下,this不能指向全局對象,默認指向undefined,JavaScript不允許對undefined添加屬性,于是報錯
// instanceof
function bar (){
if(!(this instanceof bar)){
return new bar()
}
this.a = 1
this.b = 2
}
bar()
命名
構造函數大寫開頭,實例也是,表示一種對象