W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
大部分面向?qū)ο蟮木幊陶Z言,都是以“類”(class)作為對象體系的語法基礎(chǔ)。JavaScript 語言不是如此,它的面向?qū)ο缶幊袒凇霸蛯ο蟆薄?/p>
JavaScript通過構(gòu)造函數(shù)生成新對象,因此構(gòu)造函數(shù)可以視為對象的模板。實例對象的屬性和方法,可以定義在構(gòu)造函數(shù)內(nèi)部。
function Cat (name, color) {
this.name = name;
this.color = color;
}
var cat1 = new Cat('大毛', '白色');
cat1.name // '大毛'
cat1.color // '白色'
上面代碼的Cat
函數(shù)是一個構(gòu)造函數(shù),函數(shù)內(nèi)部定義了name
屬性和color
屬性,所有實例對象都會生成這兩個屬性。但是,這樣做是對系統(tǒng)資源的浪費,因為同一個構(gòu)造函數(shù)的對象實例之間,無法共享屬性。
function Cat(name, color) {
this.name = name;
this.color = color;
this.meow = function () {
console.log('mew, mew, mew...');
};
}
var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');
cat1.meow === cat2.meow
// false
上面代碼中,cat1
和cat2
是同一個構(gòu)造函數(shù)的實例。但是,它們的meow
方法是不一樣的,就是說每新建一個實例,就會新建一個meow
方法。這既沒有必要,又浪費系統(tǒng)資源,因為所有meow
方法都是同樣的行為,完全應(yīng)該共享。
JavaScript的每個對象都繼承另一個對象,后者稱為“原型”(prototype)對象。只有null
除外,它沒有自己的原型對象。
原型對象上的所有屬性和方法,都能被派生對象共享。這就是JavaScript繼承機制的基本設(shè)計。
通過構(gòu)造函數(shù)生成實例對象時,會自動為實例對象分配原型對象。每一個構(gòu)造函數(shù)都有一個prototype
屬性,這個屬性就是實例對象的原型對象。
function Animal (name) {
this.name = name;
}
Animal.prototype.color = 'white';
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');
cat1.color // 'white'
cat2.color // 'white'
上面代碼中,構(gòu)造函數(shù)Animal
的prototype
對象,就是實例對象cat1
和cat2
的原型對象。在原型對象上添加一個color
屬性。結(jié)果,實例對象都能讀取該屬性。
原型對象的屬性不是實例對象自身的屬性。只要修改原型對象,變動就立刻會體現(xiàn)在所有實例對象上。
Animal.prototype.color = 'yellow';
cat1.color // "yellow"
cat2.color // "yellow"
上面代碼中,原型對象的color
屬性的值變?yōu)?code class="highlighter-rouge">yellow,兩個實例對象的color
屬性立刻跟著變了。這是因為實例對象其實沒有color
屬性,都是讀取原型對象的color
屬性。也就是說,當實例對象本身沒有某個屬性或方法的時候,它會到構(gòu)造函數(shù)的prototype
屬性指向的對象,去尋找該屬性或方法。這就是原型對象的特殊之處。
如果實例對象自身就有某個屬性或方法,它就不會再去原型對象尋找這個屬性或方法。
cat1.color = 'black';
cat2.color // 'yellow'
Animal.prototype.color // "yellow";
上面代碼中,實例對象cat1
的color
屬性改為black
,就使得它不再去原型對象讀取color
屬性,后者的值依然為yellow
。
總結(jié)一下,原型對象的作用,就是定義所有實例對象共享的屬性和方法。這也是它被稱為原型對象的含義,而實例對象可以視作從原型對象衍生出來的子對象。
Animal.prototype.walk = function () {
console.log(this.name + ' is walking');
};
上面代碼中,Animal.prototype
對象上面定義了一個walk
方法,這個方法將可以在所有Animal
實例對象上面調(diào)用。
由于JavaScript的所有對象都有構(gòu)造函數(shù),而所有構(gòu)造函數(shù)都有prototype
屬性(其實是所有函數(shù)都有prototype
屬性),所以所有對象都有自己的原型對象。
對象的屬性和方法,有可能是定義在自身,也有可能是定義在它的原型對象。由于原型本身也是對象,又有自己的原型,所以形成了一條原型鏈(prototype chain)。比如,a
對象是b
對象的原型,b
對象是c
對象的原型,以此類推。
如果一層層地上溯,所有對象的原型最終都可以上溯到Object.prototype
,即Object
構(gòu)造函數(shù)的prototype
屬性指向的那個對象。那么,Object.prototype
對象有沒有它的原型呢?回答可以是有的,就是沒有任何屬性和方法的null
對象,而null
對象沒有自己的原型。
Object.getPrototypeOf(Object.prototype)
// null
上面代碼表示,Object.prototype
對象的原型是null
,由于null
沒有任何屬性,所以原型鏈到此為止。
“原型鏈”的作用是,讀取對象的某個屬性時,JavaScript引擎先尋找對象本身的屬性,如果找不到,就到它的原型去找,如果還是找不到,就到原型的原型去找。如果直到最頂層的Object.prototype
還是找不到,則返回undefined
。
如果對象自身和它的原型,都定義了一個同名屬性,那么優(yōu)先讀取對象自身的屬性,這叫做“覆蓋”(overriding)。
需要注意的是,一級級向上,在原型鏈尋找某個屬性,對性能是有影響的。所尋找的屬性在越上層的原型對象,對性能的影響越大。如果尋找某個不存在的屬性,將會遍歷整個原型鏈。
舉例來說,如果讓某個函數(shù)的prototype
屬性指向一個數(shù)組,就意味著該函數(shù)可以當作數(shù)組的構(gòu)造函數(shù),因為它生成的實例對象都可以通過prototype
屬性調(diào)用數(shù)組方法。
var MyArray = function () {};
MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;
var mine = new MyArray();
mine.push(1, 2, 3);
mine.length // 3
mine instanceof Array // true
上面代碼中,mine
是構(gòu)造函數(shù)MyArray
的實例對象,由于MyArray
的prototype
屬性指向一個數(shù)組實例,使得mine
可以調(diào)用數(shù)組方法(這些方法定義在數(shù)組實例的prototype
對象上面)。至于最后那行instanceof
表達式,我們知道instanceof
運算符用來比較一個對象是否為某個構(gòu)造函數(shù)的實例,最后一行就表示mine
為Array
的實例。
下面的代碼可以找出,某個屬性到底是原型鏈上哪個對象自身的屬性。
function getDefiningObject(obj, propKey) {
while (obj && !{}.hasOwnProperty.call(obj, propKey)) {
obj = Object.getPrototypeOf(obj);
}
return obj;
}
prototype
對象有一個constructor
屬性,默認指向prototype
對象所在的構(gòu)造函數(shù)。
function P() {}
P.prototype.constructor === P
// true
由于constructor
屬性定義在prototype
對象上面,意味著可以被所有實例對象繼承。
function P() {}
var p = new P();
p.constructor
// function P() {}
p.constructor === P.prototype.constructor
// true
p.hasOwnProperty('constructor')
// false
上面代碼中,p
是構(gòu)造函數(shù)P
的實例對象,但是p
自身沒有contructor
屬性,該屬性其實是讀取原型鏈上面的P.prototype.constructor
屬性。
constructor
屬性的作用,是分辨原型對象到底屬于哪個構(gòu)造函數(shù)。
function F() {};
var f = new F();
f.constructor === F // true
f.constructor === RegExp // false
上面代碼表示,使用constructor
屬性,確定實例對象f
的構(gòu)造函數(shù)是F
,而不是RegExp
。
有了constructor
屬性,就可以從實例新建另一個實例。
function Constr() {}
var x = new Constr();
var y = new x.constructor();
y instanceof Constr // true
上面代碼中,x
是構(gòu)造函數(shù)Constr
的實例,可以從x.constructor
間接調(diào)用構(gòu)造函數(shù)。
這使得在實例方法中,調(diào)用自身的構(gòu)造函數(shù)成為可能。
Constr.prototype.createCopy = function () {
return new this.constructor();
};
這也提供了繼承模式的一種實現(xiàn)。
function Super() {}
function Sub() {
Sub.superclass.constructor.call(this);
}
Sub.superclass = new Super();
上面代碼中,Super
和Sub
都是構(gòu)造函數(shù),在Sub
內(nèi)部的this
上調(diào)用Super
,就會形成Sub
繼承Super
的效果。
由于constructor
屬性是一種原型對象與構(gòu)造函數(shù)的關(guān)聯(lián)關(guān)系,所以修改原型對象的時候,務(wù)必要小心。
function A() {}
var a = new A();
a instanceof A // true
function B() {}
A.prototype = B.prototype;
a instanceof A // false
上面代碼中,a
是A
的實例。修改了A.prototype
以后,constructor
屬性的指向就變了,導(dǎo)致instanceof
運算符失真。
所以,修改原型對象時,一般要同時校正constructor
屬性的指向。
// 避免這種寫法
C.prototype = {
method1: function (...) { ... },
// ...
};
// 較好的寫法
C.prototype = {
constructor: C,
method1: function (...) { ... },
// ...
};
// 好的寫法
C.prototype.method1 = function (...) { ... };
上面代碼中,避免完全覆蓋掉原來的prototype
屬性,要么將constructor
屬性重新指向原來的構(gòu)造函數(shù),要么只在原型對象上添加方法,這樣可以保證instanceof
運算符不會失真。
此外,通過name
屬性,可以從實例得到構(gòu)造函數(shù)的名稱。
function Foo() {}
var f = new Foo();
f.constructor.name // "Foo"
instanceof
運算符返回一個布爾值,表示指定對象是否為某個構(gòu)造函數(shù)的實例。
var v = new Vehicle();
v instanceof Vehicle // true
上面代碼中,對象v
是構(gòu)造函數(shù)Vehicle
的實例,所以返回true
。
instanceof
運算符的左邊是實例對象,右邊是構(gòu)造函數(shù)。它的運算實質(zhì)是檢查右邊構(gòu)建函數(shù)的原型對象,是否在左邊對象的原型鏈上。因此,下面兩種寫法是等價的。
v instanceof Vehicle
// 等同于
Vehicle.prototype.isPrototypeOf(v)
由于instanceof
對整個原型鏈上的對象都有效,因此同一個實例對象,可能會對多個構(gòu)造函數(shù)都返回true
。
var d = new Date();
d instanceof Date // true
d instanceof Object // true
上面代碼中,d
同時是Date
和Object
的實例,因此對這兩個構(gòu)造函數(shù)都返回true
。
instanceof
的原理是檢查原型鏈,對于那些不存在原型鏈的對象,就無法判斷。
Object.create(null) instanceof Object // false
上面代碼中,Object.create(null)
返回的新對象的原型是null
,即不存在原型,因此instanceof
就認為該對象不是Object
的實例。
除了上面這種繼承null
的特殊情況,JavaScript之中,只要是對象,就有對應(yīng)的構(gòu)造函數(shù)。因此,instanceof
運算符的一個用處,是判斷值的類型。
var x = [1, 2, 3];
var y = {};
x instanceof Array // true
y instanceof Object // true
上面代碼中,instanceof
運算符判斷,變量x
是數(shù)組,變量y
是對象。
注意,instanceof
運算符只能用于對象,不適用原始類型的值。
var s = 'hello';
s instanceof String // false
上面代碼中,字符串不是String
對象的實例(因為字符串不是對象),所以返回false
。
此外,undefined
和null
不是對象,所以instanceOf
運算符總是返回false
。
undefined instanceof Object // false
null instanceof Object // false
利用instanceof
運算符,還可以巧妙地解決,調(diào)用構(gòu)造函數(shù)時,忘了加new
命令的問題。
function Fubar (foo, bar) {
if (this instanceof Fubar) {
this._foo = foo;
this._bar = bar;
}
else {
return new Fubar(foo, bar);
}
}
上面代碼使用instanceof
運算符,在函數(shù)體內(nèi)部判斷this
關(guān)鍵字是否為構(gòu)造函數(shù)Fubar
的實例。如果不是,就表明忘了加new
命令。
Object.getPrototypeOf
方法返回一個對象的原型。這是獲取原型對象的標準方法。
// 空對象的原型是Object.prototype
Object.getPrototypeOf({}) === Object.prototype
// true
// 函數(shù)的原型是Function.prototype
function f() {}
Object.getPrototypeOf(f) === Function.prototype
// true
// f 為 F 的實例對象,則 f 的原型是 F.prototype
var f = new F();
Object.getPrototypeOf(f) === F.prototype
// true
Object.setPrototypeOf
方法可以為現(xiàn)有對象設(shè)置原型,返回一個新對象。
Object.setPrototypeOf
方法接受兩個參數(shù),第一個是現(xiàn)有對象,第二個是原型對象。
var a = {x: 1};
var b = Object.setPrototypeOf({}, a);
// 等同于
// var b = {__proto__: a};
b.x // 1
上面代碼中,b
對象是Object.setPrototypeOf
方法返回的一個新對象。該對象本身為空、原型為a
對象,所以b
對象可以拿到a
對象的所有屬性和方法。b
對象本身并沒有x
屬性,但是JavaScript引擎找到它的原型對象a
,然后讀取a
的x
屬性。
new
命令通過構(gòu)造函數(shù)新建實例對象,實質(zhì)就是將實例對象的原型,指向構(gòu)造函數(shù)的prototype
屬性,然后在實例對象上執(zhí)行構(gòu)造函數(shù)。
var F = function () {
this.foo = 'bar';
};
var f = new F();
// 等同于
var f = Object.setPrototypeOf({}, F.prototype);
F.call(f);
Object.create
方法用于從原型對象生成新的實例對象,可以替代new
命令。
它接受一個對象作為參數(shù),返回一個新對象,后者完全繼承前者的屬性,即原有對象成為新對象的原型。
var A = {
print: function () {
console.log('hello');
}
};
var B = Object.create(A);
B.print() // hello
B.print === A.print // true
上面代碼中,Object.create
方法在A
的基礎(chǔ)上生成了B
。此時,A
就成了B
的原型,B
就繼承了A
的所有屬性和方法。這段代碼等同于下面的代碼。
var A = function () {};
A.prototype = {
print: function () {
console.log('hello');
}
};
var B = new A();
B.print === A.prototype.print // true
實際上,Object.create
方法可以用下面的代碼代替。如果老式瀏覽器不支持Object.create
方法,可以就用這段代碼自己部署。
if (typeof Object.create !== 'function') {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
上面代碼表示,Object.create
方法實質(zhì)是新建一個構(gòu)造函數(shù)F
,然后讓F
的prototype
屬性指向作為原型的對象o
,最后返回一個F
的實例,從而實現(xiàn)讓實例繼承o
的屬性。
下面三種方式生成的新對象是等價的。
var o1 = Object.create({});
var o2 = Object.create(Object.prototype);
var o3 = new Object();
如果想要生成一個不繼承任何屬性(比如沒有toString
和valueOf
方法)的對象,可以將Object.create
的參數(shù)設(shè)為null
。
var o = Object.create(null);
o.valueOf()
// TypeError: Object [object Object] has no method 'valueOf'
上面代碼表示,如果對象o
的原型是null
,它就不具備一些定義在Object.prototype
對象上面的屬性,比如valueOf
方法。
使用Object.create
方法的時候,必須提供對象原型,否則會報錯。
Object.create()
// TypeError: Object prototype may only be an Object or null
object.create
方法生成的新對象,動態(tài)繼承了原型。在原型上添加或修改任何方法,會立刻反映在新對象之上。
var o1 = { p: 1 };
var o2 = Object.create(o1);
o1.p = 2;
o2.p
// 2
上面代碼表示,修改對象原型會影響到新生成的對象。
除了對象的原型,Object.create
方法還可以接受第二個參數(shù)。該參數(shù)是一個屬性描述對象,它所描述的對象屬性,會添加到新對象。
var o = Object.create({}, {
p1: {
value: 123,
enumerable: true,
configurable: true,
writable: true,
},
p2: {
value: 'abc',
enumerable: true,
configurable: true,
writable: true,
}
});
// 等同于
var o = Object.create({});
o.p1 = 123;
o.p2 = 'abc';
Object.create
方法生成的對象,繼承了它的原型對象的構(gòu)造函數(shù)。
function A() {}
var a = new A();
var b = Object.create(a);
b.constructor === A // true
b instanceof A // true
上面代碼中,b
對象的原型是a
對象,因此繼承了a
對象的構(gòu)造函數(shù)A
。
對象實例的isPrototypeOf
方法,用來判斷一個對象是否是另一個對象的原型。
var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);
o2.isPrototypeOf(o3) // true
o1.isPrototypeOf(o3) // true
上面代碼表明,只要某個對象處在原型鏈上,isPrototypeOf
都返回true
。
Object.prototype.isPrototypeOf({}) // true
Object.prototype.isPrototypeOf([]) // true
Object.prototype.isPrototypeOf(/xyz/) // true
Object.prototype.isPrototypeOf(Object.create(null)) // false
上面代碼中,由于Object.prototype
處于原型鏈的最頂端,所以對各種實例都返回true
,只有繼承null
的對象除外。
__proto__
屬性(前后各兩個下劃線)可以改寫某個對象的原型對象。
var obj = {};
var p = {};
obj.__proto__ = p;
Object.getPrototypeOf(obj) === p // true
上面代碼通過__proto__
屬性,將p
對象設(shè)為obj
對象的原型。
根據(jù)語言標準,__proto__
屬性只有瀏覽器才需要部署,其他環(huán)境可以沒有這個屬性,而且前后的兩根下劃線,表示它本質(zhì)是一個內(nèi)部屬性,不應(yīng)該對使用者暴露。因此,應(yīng)該盡量少用這個屬性,而是用Object.getPrototypeof()
(讀?。┖?code class="highlighter-rouge">Object.setPrototypeOf()(設(shè)置),進行原型對象的讀寫操作。
原型鏈可以用__proto__
很直觀地表示。
var A = {
name: '張三'
};
var B = {
name: '李四'
};
var proto = {
print: function () {
console.log(this.name);
}
};
A.__proto__ = proto;
B.__proto__ = proto;
A.print() // 張三
B.print() // 李四
上面代碼中,A
對象和B
對象的原型都是proto
對象,它們都共享proto
對象的print
方法。也就是說,A
和B
的print
方法,都是在調(diào)用proto
對象的print
方法。
A.print === B.print // true
A.print === proto.print // true
B.print === proto.print // true
可以使用Object.getPrototypeOf
方法,檢查瀏覽器是否支持__proto__
屬性,老式瀏覽器不支持這個屬性。
Object.getPrototypeOf({ __proto__: null }) === null
上面代碼將一個對象的__proto__
屬性設(shè)為null
,然后使用Object.getPrototypeOf
方法獲取這個對象的原型,判斷是否等于null
。如果當前環(huán)境支持__proto__
屬性,兩者的比較結(jié)果應(yīng)該是true
。
如前所述,__proto__
屬性指向當前對象的原型對象,即構(gòu)造函數(shù)的prototype
屬性。
var obj = new Object();
obj.__proto__ === Object.prototype
// true
obj.__proto__ === obj.constructor.prototype
// true
上面代碼首先新建了一個對象obj
,它的__proto__
屬性,指向構(gòu)造函數(shù)(Object
或obj.constructor
)的prototype
屬性。所以,兩者比較以后,返回true
。
因此,獲取實例對象obj
的原型對象,有三種方法。
obj.__proto__
obj.constructor.prototype
Object.getPrototypeOf(obj)
上面三種方法之中,前兩種都不是很可靠。最新的ES6標準規(guī)定,__proto__
屬性只有瀏覽器才需要部署,其他環(huán)境可以不部署。而obj.constructor.prototype
在手動改變原型對象時,可能會失效。
var P = function () {};
var p = new P();
var C = function () {};
C.prototype = p;
var c = new C();
c.constructor.prototype === p // false
上面代碼中,C
構(gòu)造函數(shù)的原型對象被改成了p
,結(jié)果c.constructor.prototype
就失真了。所以,在改變原型對象時,一般要同時設(shè)置constructor
屬性。
C.prototype = p;
C.prototype.constructor = C;
c.constructor.prototype === p // true
所以,推薦使用第三種Object.getPrototypeOf
方法,獲取原型對象。
var o = new Object();
Object.getPrototypeOf(o) === Object.prototype
// true
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: