prototype 對象

面向對象編程很重要的一個方面冠骄,就是對象的繼承。A 對象通過繼承 B 對象加袋,就能直接擁有 B 對象的所有屬性和方法凛辣。這對于代碼的復用是非常有用的。

大部分面向對象的編程語言职烧,都是通過“類”(class)來實現(xiàn)對象的繼承扁誓。JavaScript 語言的繼承則是通過“原型對象”(prototype)

1.原型對象概述

1.1 構造函數(shù)的缺點

JavaScript 通過構造函數(shù)生成新對象,因此構造函數(shù)可以視為對象的模板蚀之。實例對象的屬性和方法蝗敢,可以定義在構造函數(shù)內部。

function Cat (name, color) {
  this.name = name;
  this.color = color;
}

var cat1 = new Cat('大毛', '白色');

cat1.name // '大毛'
cat1.color // '白色'

上面代碼中足删,Cat函數(shù)是一個構造函數(shù)寿谴,函數(shù)內部定義了name屬性和color屬性,所有實例對象(上例是cat1)都會生成這兩個屬性失受,即這兩個屬性會定義在實例對象上面讶泰。

通過構造函數(shù)為實例對象定義屬性,雖然很方便拂到,但是有一個缺點痪署。同一個構造函數(shù)的多個實例之間,無法共享屬性兄旬,從而造成對系統(tǒng)資源的浪費狼犯。

function Cat(name, color) {
  this.name = name;
  this.color = color;
  this.meow = function () {
    console.log('喵喵');
  };
}

var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');

cat1.meow === cat2.meow
// false

上面代碼中,cat1cat2是同一個構造函數(shù)的兩個實例,它們都具有meow方法辜王。由于meow方法是生成在每個實例對象上面劈狐,所以兩個實例就生成了兩次。也就是說呐馆,每新建一個實例肥缔,就會新建一個meow方法。這既沒有必要汹来,又浪費系統(tǒng)資源续膳,因為所有meow方法都是同樣的行為,完全應該共享收班。

這個問題的解決方法坟岔,就是 JavaScript 的原型對象(prototype)。

1.2 prototype 屬性的作用

JavaScript 繼承機制的設計思想就是摔桦,原型對象的所有屬性和方法社付,都能被實例對象共享。也就是說邻耕,如果屬性和方法定義在原型上鸥咖,那么所有實例對象就能共享,不僅節(jié)省了內存兄世,還體現(xiàn)了實例對象之間的聯(lián)系啼辣。

下面,先看怎么為對象指定原型御滩。JavaScript 規(guī)定鸥拧,每個函數(shù)都有一個prototype屬性,指向一個對象削解。

function f() {}
typeof f.prototype // "object"

上面代碼中富弦,函數(shù)f默認具有prototype屬性,指向一個對象钠绍。

對于普通函數(shù)來說舆声,該屬性基本無用。但是柳爽,對于構造函數(shù)來說,生成實例的時候碱屁,該屬性會自動成為實例對象的原型磷脯。

function Animal(name) {
  this.name = name;
}
Animal.prototype.color = 'white';

var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');

cat1.color // 'white'
cat2.color // 'white'

上面代碼中,構造函數(shù)Animalprototype屬性娩脾,就是實例對象cat1cat2的原型對象赵誓。原型對象上添加一個color屬性,結果,實例對象都共享了該屬性俩功。

原型對象的屬性不是實例對象自身的屬性幻枉。只要修改原型對象,變動就立刻會體現(xiàn)在所有實例對象上诡蜓。

Animal.prototype.color = 'yellow';

cat1.color // "yellow"
cat2.color // "yellow"

上面代碼中熬甫,原型對象的color屬性的值變?yōu)?code>yellow,兩個實例對象的color屬性立刻跟著變了蔓罚。這是因為實例對象其實沒有color屬性椿肩,都是讀取原型對象的color屬性。也就是說豺谈,當實例對象本身沒有某個屬性或方法的時候郑象,它會到原型對象去尋找該屬性或方法。這就是原型對象的特殊之處茬末。

如果實例對象自身就有某個屬性或方法厂榛,它就不會再去原型對象尋找這個屬性或方法

cat1.color = 'black';

cat1.color // 'black'
cat2.color // 'yellow'
Animal.prototype.color // 'yellow';

上面代碼中丽惭,實例對象cat1color屬性改為black击奶,就使得它不再去原型對象讀取color屬性,后者的值依然為yellow吐根。

總結一下正歼,原型對象的作用,就是定義所有實例對象共享的屬性和方法拷橘。這也是它被稱為原型對象的原因局义,而實例對象可以視作從原型對象衍生出來的子對象

Animal.prototype.walk = function () {
  console.log(this.name + ' is walking');
};

上面代碼中冗疮,Animal.prototype對象上面定義了一個walk方法萄唇,這個方法將可以在所有Animal實例對象上面調用。

(4)綁定回調函數(shù)的對象

前面的按鈕點擊事件的例子术幔,可以改寫如下另萤。

var o = new Object();

o.f = function () {
  console.log(this === o);
}

var f = function (){
  o.f.apply(o);
  // 或者 o.f.call(o);
};

// jQuery 的寫法
$('#button').on('click', f);

上面代碼中,點擊按鈕以后诅挑,控制臺將會顯示true四敞。由于apply方法(或者call方法)不僅綁定函數(shù)執(zhí)行時所在的對象,還會立即執(zhí)行函數(shù)拔妥,因此不得不把綁定語句寫在一個函數(shù)體內忿危。更簡潔的寫法是采用下面介紹的bind方法。

4.3 Function.prototype.bind()

bind方法用于將函數(shù)體內的this綁定到某個對象没龙,然后返回一個新函數(shù)铺厨。

var d = new Date();
d.getTime() // 1481869925657

var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.

面代碼中缎玫,我們將d.getTime方法賦給變量print,然后調用print就報錯了解滓。這是因為getTime方法內部的this赃磨,綁定Date對象的實例,賦給變量print以后洼裤,內部的this已經不指向Date對象的實例了邻辉。

bind方法可以解決這個問題。

var print = d.getTime.bind(d);
print() // 1481869925657

bind方法的參數(shù)就是所要綁定this的對象逸邦,下面是一個更清晰的例子恩沛。

var counter = {
  count: 0,
  inc: function () {
    this.count++;
  }
};

var func = counter.inc.bind(counter);
func();
counter.count // 1

上面代碼中,counter.inc方法被賦值給變量func缕减。這時必須用bind方法將inc內部的this雷客,綁定到counter,否則就會出錯桥狡。

this綁定到其他對象也是可以的搅裙。

var counter = {
  count: 0,
  inc: function () {
    this.count++;
  }
};

var obj = {
  count: 100
};
var func = counter.inc.bind(obj);
func();
obj.count // 101

上面代碼中,bind方法將inc方法內部的this裹芝,綁定到obj對象部逮。結果調用func函數(shù)以后,遞增的就是obj內部的count屬性嫂易。

bind還可以接受更多的參數(shù)兄朋,將這些參數(shù)綁定原函數(shù)的參數(shù)。

var add = function (x, y) {
  return x * this.m + y * this.n;
}

var obj = {
  m: 2,
  n: 2
};

var newAdd = add.bind(obj, 5);
newAdd(5) // 20

上面代碼中怜械,bind方法除了綁定this對象颅和,還將add函數(shù)的第一個參數(shù)x綁定成5,然后返回一個新函數(shù)newAdd缕允,這個函數(shù)只要再接受一個參數(shù)y就能運行了峡扩。

如果bind方法的第一個參數(shù)是nullundefined,等于將this綁定到全局對象障本,函數(shù)運行時this指向頂層對象(瀏覽器為window)教届。

function add(x, y) {
  return x + y;
}

var plus5 = add.bind(null, 5);
plus5(10) // 15

注意:

上面代碼中,函數(shù)add內部并沒有this驾霜,使用bind方法的主要目的是綁定參數(shù)x案训,以后每次運行新函數(shù)plus5,就只需要提供另一個參數(shù)y就夠了粪糙。而且因為add內部沒有this萤衰,所以bind的第一個參數(shù)是null,不過這里如果是其他對象猜旬,也沒有影響脆栋。

bind方法有一些使用注意點。

(1)每一次返回一個新函數(shù)

bind方法每運行一次洒擦,就返回一個新函數(shù)椿争,這會產生一些問題。比如熟嫩,監(jiān)聽事件的時候秦踪,不能寫成下面這樣。

element.addEventListener('click', o.m.bind(o));

上面代碼中掸茅,click事件綁定bind方法生成的一個匿名函數(shù)椅邓。這樣會導致無法取消綁定,所以昧狮,下面的代碼是無效的景馁。

element.removeEventListener('click', o.m.bind(o));

正確的方法是寫成下面這樣:

var listener = o.m.bind(o);
element.addEventListener('click', listener);
//  ...
element.removeEventListener('click', listener);

(2)結合回調函數(shù)使用

回調函數(shù)是 JavaScript 最常用的模式之一,但是一個常見的錯誤是逗鸣,將包含this的方法直接當作回調函數(shù)合住。解決方法就是使用bind方法,將counter.inc綁定counter撒璧。

var counter = {
  count: 0,
  inc: function () {
    'use strict';
    this.count++;
  }
};

function callIt(callback) {
  callback();
}

callIt(counter.inc.bind(counter));
counter.count // 1

上面代碼中透葛,callIt方法會調用回調函數(shù)。這時如果直接把counter.inc傳入卿樱,調用時counter.inc內部的this就會指向全局對象僚害。使用bind方法將counter.inc綁定counter以后,就不會有這個問題繁调,this總是指向counter萨蚕。

還有一種情況比較隱蔽,就是某些數(shù)組方法可以接受一個函數(shù)當作參數(shù)涉馁。這些函數(shù)內部的this指向门岔,很可能也會出錯

var obj = {
  name: '張三',
  times: [1, 2, 3],
  print: function () {
    this.times.forEach(function (n) {
      console.log(this.name);
    });
  }
};

obj.print()
// 沒有任何輸出

上面代碼中烤送,obj.print內部this.timesthis是指向obj的寒随,這個沒有問題。但是帮坚,forEach方法的回調函數(shù)內部的this.name卻是指向全局對象妻往,導致沒有辦法取到值。稍微改動一下,就可以看得更清楚冰评。

obj.print = function () {
  this.times.forEach(function (n) {
    console.log(this === window);
  });
};

obj.print()
// true
// true
// true

解決這個問題嗦枢,也是通過bind方法綁定this

obj.print = function () {
  this.times.forEach(function (n) {
    console.log(this.name);
  }.bind(this));
};

obj.print()
// 張三
// 張三
// 張三

(3)結合call方法使用

利用bind方法好渠,可以改寫一些 JavaScript 原生方法的使用形式昨稼,以數(shù)組的slice方法為例。

[1, 2, 3].slice(0, 1) // [1]
// 等同于
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]

上面的代碼中拳锚,數(shù)組的slice方法從[1, 2, 3]里面假栓,按照指定位置和長度切分出另一個數(shù)組。這樣做的本質是在[1, 2, 3]上面調用Array.prototype.slice方法霍掺,因此可以用call方法表達這個過程匾荆,得到同樣的結果。

call方法實質上是調用Function.prototype.call方法杆烁,因此上面的表達式可以用bind方法改寫牙丽。

var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1) // [1]

上面代碼的含義就是,將Array.prototype.slice變成Function.prototype.call方法所在的對象兔魂,調用時就變成了Array.prototype.slice.call烤芦。類似的寫法還可以用于其他數(shù)組方法。

var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop);

var a = [1 ,2 ,3];
push(a, 4)
a // [1, 2, 3, 4]

pop(a)
a // [1, 2, 3]

如果再進一步入热,將Function.prototype.call方法綁定到Function.prototype.bind對象拍棕,就意味著bind的調用形式也可以被改寫。

function f() {
  console.log(this.v);
}

var o = { v: 123 };
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, o)() // 123

上面代碼的含義就是勺良,將Function.prototype.bind方法綁定在Function.prototype.call上面绰播,所以bind方法就可以直接使用,不需要在函數(shù)實例上使用尚困。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末蠢箩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子事甜,更是在濱河造成了極大的恐慌谬泌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逻谦,死亡現(xiàn)場離奇詭異掌实,居然都是意外死亡,警方通過查閱死者的電腦和手機邦马,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門贱鼻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人滋将,你說我怎么就攤上這事邻悬。” “怎么了随闽?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵父丰,是天一觀的道長。 經常有香客問我掘宪,道長蛾扇,這世上最難降的妖魔是什么攘烛? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮屁桑,結果婚禮上医寿,老公的妹妹穿的比我還像新娘。我一直安慰自己蘑斧,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布须眷。 她就那樣靜靜地躺著竖瘾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪花颗。 梳的紋絲不亂的頭發(fā)上捕传,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機與錄音扩劝,去河邊找鬼庸论。 笑死,一個胖子當著我的面吹牛棒呛,可吹牛的內容都是我干的聂示。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼簇秒,長吁一口氣:“原來是場噩夢啊……” “哼鱼喉!你這毒婦竟也來了?” 一聲冷哼從身側響起趋观,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤扛禽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后皱坛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體编曼,經...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年剩辟,在試婚紗的時候發(fā)現(xiàn)自己被綠了掐场。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡抹沪,死狀恐怖刻肄,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情融欧,我是刑警寧澤敏弃,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站噪馏,受9級特大地震影響麦到,放射性物質發(fā)生泄漏绿饵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一瓶颠、第九天 我趴在偏房一處隱蔽的房頂上張望拟赊。 院中可真熱鬧,春花似錦粹淋、人聲如沸吸祟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽屋匕。三九已至,卻和暖如春借杰,著一層夾襖步出監(jiān)牢的瞬間过吻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工蔗衡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纤虽,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓绞惦,卻偏偏與公主長得像逼纸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子翩隧,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354