一、原型
上回講到量没,生成一個對象我們可以通過new構(gòu)造函數(shù)來實現(xiàn)玉转,如下:
function Person(name, age) {
this.name = name;
this.age = age;
this.getAge = function() {
return this.age;
}
}
var p = new Person('peter', 18);
但是,上面這樣也有個缺陷殴蹄,比如每個person對象實際上都有g(shù)etAge方法而且都一樣究抓,但每次new的時候都要重新生成未免太浪費。那有沒有辦法把公共的方法找個地方存起來大家都去讀袭灯,然后每次只要生成自己私有的屬性和方法而不再生成共有的就可以了刺下?
因為這點,原型就產(chǎn)生了稽荧,原型就是這個存公共方法的地方橘茉。一般我們把公共函數(shù)放在原型里,私有屬性和方法放在構(gòu)造函數(shù)里姨丈,通過構(gòu)造函數(shù)的prototype可以訪問到其對應的原型畅卓。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getAge = function() {
return this.age;
}
var p = new Person('peter', 18);
上面的代碼將公共的getAge挪到了Person的原型上,這樣每次new生成實例的時候跑Person函數(shù)里的代碼的時候就不會重復生成getAge方法了蟋恬,而每個實例對象又能訪問到getAge翁潘。
其對應的原型圖如下,構(gòu)造函數(shù)有個prototype指向其原型筋现,每個原型又有個constructor指向其構(gòu)造函數(shù)唐础,實例中的__proto__指向原型,這樣就能一一對應關(guān)系和互相訪問了矾飞。
二一膨、原型鏈
上面的例子我們講的只是簡單畫了原型圖作為示例,實際上他的原型圖不止這三個洒沦。其實豹绪,所有的構(gòu)造函數(shù)都有prototype指向原型,而所有的原型都有constructor指向其構(gòu)造函數(shù)申眼,而每個實例對象都有一個__proto__指向生成它構(gòu)造函數(shù)的原型瞒津。而原型其實也是個對象實例,通過new Object生成括尸;構(gòu)造函數(shù)也是個函數(shù)實例巷蚪,通過new Function生成。所以上面的圖可以進一步完善成下面的樣子濒翻。
實際上屁柏,根據(jù)上面說的啦膜,原型其實也是個對象實例,通過new Object生成淌喻;構(gòu)造函數(shù)也是個函數(shù)實例僧家,通過new Function生成。那么裸删,F(xiàn)unction.prototype也是個對象實例八拱,而Object和Function也是函數(shù)實例,所以有
Function.prototype.__proto__ === Object.prototype
Object.__proto__ === Function.prototype
Function.__proto__ === Function.prototype
這其中要特別注意涯塔,Object.prototype實際上也是個對象實例肌稻,但js規(guī)定它的__proto__是指向null。所以有Object.prototype.__proto__ === null
伤塌。于是上面的原型最后變成如下圖所示:
所以所謂的原型鏈灯萍,指的是每個實例對象都有自己的原型,而原型實際上也是個對象實例每聪,他也有自己的原型旦棉,層層往上,從而形成一條原型鏈药薯。所有的原型即prototype最終都會追溯到Object.prototype绑洛,而Object.prototype的原型是null,所以原型鏈的末端就是null童本。
// 從上圖的鏈真屯,按著箭頭指向我們可以得到
p.__proto__.constructor.__proto__.__proto__.__proto__ === null // true
而當我們在一個實例里尋找某個屬性或方法時,js引擎會先在當前的實例上去找穷娱,如果找不到就往上一個原型里找绑蔫,找到則返回,找不到再沿著原型鏈往上上個原型找泵额,一直到末端的null配深,還沒有的話則返回undefined。
還是上面的例子:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getAge = function() {
return this.age;
}
var p = new Person('peter', 18);
p.getAge(); // 18
p.toString(); // [object Object]
- (1) 調(diào)用實例p.getAge方法嫁盲,但是實例p上只有name和age屬性篓叶,沒有g(shù)etAge;
(2) 于是往實例p的原型Person.prototype上去找羞秤,找到getAge方法返回缸托,停止尋找。 - (1) 當調(diào)用p.toString方法時瘾蛋,實例p上只有name和age屬性找不到toString方法俐镐;
(2) 于是往p的原型Person.prototype上去找,但Person.prototype的原型只有g(shù)etAge方法沒有toString哺哼;
(3) 于是繼續(xù)往Person.prototype的原型Obect.prototype里找佩抹,而Object.prototype默認定義了toString方法奇唤,所以返回該方法,停止尋找匹摇。
這就是為啥我們生成實例對象時,雖然沒有定義toString或valueOf方法甲葬,但仍能使用的原因廊勃,因為所有的原型鏈最終都能追溯到Object.prototype上,而Object.prototype默認定義了toString和valueOf方法经窖。同理坡垫,其他原型也定義了一些公共方法給實例使用,比如生成數(shù)組實例時画侣,雖然沒有定義pop冰悠、push等數(shù)組方法,卻能使用的原因配乱,是因為在Array.prototype上已經(jīng)默認定義了這些方法溉卓。
三、原型相關(guān)方法
1. 獲得原型的三種方法
(1) 通過實例的__proto__來獲取搬泥,如obj.__proto__
桑寨。但一般__proto__是瀏覽器為了實現(xiàn)原型鏈而增加的內(nèi)在屬性,不是官方規(guī)定的方法不推薦忿檩。
(2) 通過實例的constructor.prototype來獲取尉尾,如obj.constructor.prototype
。因為實例上找constructor屬性時會找到原型的constructor燥透,可以獲得產(chǎn)生該實例的構(gòu)造函數(shù)沙咏,構(gòu)造函數(shù)的prototype又會指向原型,所以間接獲得了原型班套。
(3) 通過Object.getPrototypeOf(obj)
來獲取肢藐。此法是官方定義的方法,推薦用此法孽尽。
function Person(name, age) {
this.name = name;
this.age = age;
}
var p = new Person('peter', 18);
// 下面三種方法都可以獲得實例p的原型
p.__proto__;
p.constructor.prototype;
Object.getPrototypeOf(p);
2. 修改設(shè)置原型的三種方法
(1) 通過實例的__proto__來修改窖壕。該屬性可讀可寫,通過此法設(shè)置效果跟第三點一樣杉女,不過不推薦用此法瞻讽。
(2) 通過給構(gòu)造函數(shù)的prototype直接賦值實現(xiàn)修改。注意用此法修改的時候要相應的修改prototype的constructor熏挎,否則會出現(xiàn)引用錯誤速勇,詳見下面的例子。
(3) 通過Object.setPrototypeOf(obj, proObj)
設(shè)置坎拐。官方定義的方法烦磁,推薦此法养匈,不需要考慮constructor。
function Person(name) {
this.name = name;
}
Person.prototype.constructor === Person // true
// 法一
Person.prototype = {
//constructor: Person, // 應該加上這句都伪,否則constructor不再指向?qū)臉?gòu)造函數(shù)
method: function () {}
};
console.log(Person.prototype.constructor === Person) // false
console.log(Person.prototype.constructor === Object) // true
// 法二
Object.setPrototypeOf(Person, {
method: function () {}
});
console.log(Person.prototype.constructor === Person) // true
console.log(Person.prototype.constructor === Object) // false
3. 判斷原型
通過a.isPrototypeOf(b)
可以判斷a是否是b的原型呕乎。
- 注意前面的getPrototypeOf()和setPrototypeOf()都是定義在Obejct上的,而此法是定義在Object.prototype上的陨晶,所以通過實例可以直接調(diào)用猬仁。
- 只要a在參數(shù)對象b的原型鏈上,該函數(shù)都會返回true先誉。由于Object.prototype處于原型鏈的最頂端湿刽,所以對各種實例都返回true,只有直接繼承自null的對象除外褐耳。
function Person(name, age) {
this.name = name;
this.age = age;
}
var p = new Person('peter', 18);
Person.prototype.isPrototypeOf(p); // true
Object.prototype.isPrototypeOf(p); // true
Object.prototype.isPrototypeOf(Object.create(null)); // false诈闺,括號里面是以null為原型生成的實例