面向對象編程的幾個常見名詞的解釋

這里說的類,在ES6中討論的話,只有ES6的class關鍵字定義的一段封裝好的代碼才可以叫類,在ES6之前討論的話,是由構造函數和構造函數的原型語句組成的一套代碼。

構造函數

通過new操作符調用的函數就是構造函數。構造函數的特征一般是:

1、名稱首字母大寫。不是必須,只是為了開發者更快知道它是構造函數而俗成的約定。
2、即將被new。必須。
3、內部通常有this定義。不是必須。
4、外部通常有原型的定義。不是必須。

最簡單的構造函數是:

function Foo() {
}

prototype和__proto__

prototype叫函數原型,是函數(構造函數及其他函數)自帶的一個內部對象。它的主要作用是用于繼承別的對象(包括構造函數的原型對象)的屬性和方法,我們常說的“構造函數B繼承了構造函數A”,其實就是B的原型繼承了A的原型。比如:

var json = { // 隨便定義了一個對象json,有個方法a,值為11
    a: 11
};

function Person(name,age) // 一個最簡的構造函數,簡單到沒有內容
{

}

Person.prototype = json; // 讓這個構造函數繼承json對象的屬性

console.log(new Person().a); // new一個實例對象,這個實例對象就有了屬性a,所以打印11
function Animal(name,age) // 一個空的父構造函數
{

}

Animal.prototype.a = 11; // 給它原型加了一個屬性

function Person(name,age) // 一個子構造函數
{
    this.b = 22; // 給調用Person的對象定義一個屬性
}

Person.prototype = Animal.prototype; // 繼承Animal的原型

console.log(new Animal().a); // 11
console.log(new Animal().b); // undefined
console.log(new Person().a); // 11 // Person繼承了Animal
console.log(new Person().b); // 22

__proto__叫內部原型,是任何對象當然也包括函數自帶的一個對象,對比一下prototype的定義,prototype只是函數自帶的一個對象。那么我們看看:

function Person(name,age)
{
    this.b = 22;
}

var a = {};

console.log(Person.prototype);
console.log(Person.__proto__);
console.log('------------------');
console.log(new Person().prototype);
console.log(new Person().__proto__);
console.log('------------------');
console.log(a.prototype);
console.log(a.__proto__);

結果是這樣的:

Paste_Image.png

可見prototype和__proto__的區別是:

prototype只有函數(構造函數及其他函數)自帶,實例對象跟其他常規對象都不帶。
__proto__是函數(構造函數及其他函數)、實例對象、其他常規對象都自帶。

__proto__跟prototype的關系是:

Foo.prototype === new Foo().__proto__

function Person(name, age)
{
    this.b = 22;
}

console.log(Person.prototype === new Person().__proto__);

注意到console.log(Person.__proto__);的輸出了沒?是一個空函數。其實,所有構造函數/函數的__proto__都指向Function.prototype,它是一個空函數(Empty function)。也就是說,所有構造函數都繼承了Function.prototype的屬性及方法,如length、call、apply、bind(ES5新增)。再說白了,當你定義一個構造函數的那一刻,你就已經在用構造函數的繼承了。

上面說Function.prototype是空函數,空函數也是函數,它也有__proto__,會是什么呢?

console.log(Function.prototype.__proto__); // 空對象
console.log(Function.prototype.__proto__ === Object.prototype) // true

這說明所有的構造函數也都是普通對象,可以給構造函數添加/刪除屬性。同時它也繼承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等。

那么Object.prototype的__proto__是誰?

console.log(Object.prototype.__proto__ === null); // true

已經到頂了,為null。因為null沒有原型也沒有內部原型。

這就是所謂“空生萬物”,即,空生對象,對象生函數。

上面研究的是函數的內部原型,下面研究一下實例對象的內部原型。

var obj = {name: 'jack'}
var arr = [1,2,3]
var reg = /hello/g
var date = new Date
var err = new Error('exception')
 
console.log(obj.__proto__ === Object.prototype) // true
console.log(arr.__proto__ === Array.prototype)  // true
console.log(reg.__proto__ === RegExp.prototype) // true
console.log(date.__proto__ === Date.prototype)  // true
console.log(err.__proto__ === Error.prototype)  // true

結論是:函數實例的內部原型就是Function.prototype,數組實例的內部原型就是Array.prototype,其他都是這種道理。

constructor屬性

constructor屬性是任何對象都有的一個屬性。回憶一下,__proto__也是任何對象都有的一個對象。對象的constructor屬性返回創建該對象的構造函數的引用。證明如下:

var a = {};
var b = [];
var c = '';
var d = new Error();

console.log(a.constructor === Object); // true
console.log(b.constructor === Array); // true
console.log(c.constructor === String); // true
console.log(d.constructor === Error); // true

function e() {}

console.log(new e().constructor === e);

現在討論點好玩的。既然prototype是對象,所以prototype也自帶constructor屬性,下面我們就研究一下構造函數的prototype的constructor屬性。

一個構造函數只要存在,就肯定有prototype對象,它的prototype對象又肯定有constructor屬性,constructor屬性又指向構造函數,等于是個閉環:Foo.prototype.constructor === Foo

function Person(name,age)
{
    this.b = 22;
}

console.log(Person.prototype.constructor === Person); // true

然后繼續推導,上面我總結過一個公式,Foo.prototype === new Foo().__proto__,所以得到:new Foo().__proto__.construator === Foo

function Person(name,age)
{
    this.b = 22;
}

console.log(new Person().__proto__.constructor === Person); // true

再繼續推導,因為對象的constructor屬性返回創建該對象的構造函數的引用,所以,new Person('jack').constructor === Person

function Person(name) {
    this.name = name
}

console.log(new Person('jack').constructor === Person);

由于Person.prototype === new Person('jack').__proto__,所以,new Person('jack').constructor.prototype === new Person('jack').__proto__,也就是說,對象的構造器屬性的原型等于內部原型。

function Person(name) {
    this.name = name;
}

console.log(new Person('jack').constructor.prototype === new Person('jack').__proto__) // true

所以,new Person('jack').constructor.prototype === new Person('jack').__proto__ === Person.prototype就是最后的公式。

是不是很亂?所以有人用思維導圖的方式把這些串聯了起來。如果你到現在還沒有看懵,相信你就可以看懂那些思維導圖了。

Paste_Image.png

如果原型方法被改寫,或者原型被整體重寫,會怎樣?

function Person(name) {
    this.name = name;
}

var p1 = new Person('jack');
console.log(p1.__proto__ === Person.prototype); // true
console.log(p1.__proto__ === p1.constructor.prototype); // true

// 改寫原型方法
Person.prototype.getName = function() {return this.name + 1};
var p2 = new Person('jack');
console.log(p2.getName()); // jack1
console.log(p2.__proto__ === Person.prototype); // true
console.log(p2.__proto__ === p2.constructor.prototype); // true

// 重寫原型
Person.prototype = {
    getName: function() {return this.name + 2}
};
var p3 = new Person('jack');
console.log(p3.getName()); // jack2
console.log(p3.__proto__ === Person.prototype); // true
console.log(p3.__proto__ === p3.constructor.prototype); // false

最后兩行輸出結果可以看出,p3.__proto__仍然指向的是Person.prototype,但不再指向p3.constructor.prototype。為什么?

給Person.prototype賦值的是一個對象直接量{getName: function(){}},使用對象直接量方式定義的對象其構造器(constructor)指向的是根構造器Object,Object.prototype是一個空對象{},{}自然與{getName: function(){}}不等。

給prototype添加的屬性和方法,跟給this添加的屬性和方法,有什么不同?

給prototype添加的屬性方法,不是構造函數自己的,而是外面來的。我說過,當一個構造函數聲明時,就算它是個空函數,你作為開發者其實已經使用了原型繼承,因為你的構造函數通過__proto__指向Function.prototype而繼承了Function.prototype的屬性和方法。這時候如果給構造函數的prototype另外添加屬性和方法,屬性和方法依然來自外部,也就是說,實例對象的屬性和方法來自于構造函數的prototype。

給this添加屬性和方法,實例對象創建的時候就已經獲得了這些屬性和方法,沒有中間步驟。也因此,構造函數里定義的this的屬性和方法,默認只能給自己的實例對象使用,如果想給構造函數的子構造函數使用,也得通過繼承。

私有屬性、私有方法

利用函數作用域的原理,構造函數內部定義的變量和函數,構造函數外部不可直接訪問,這就是私有屬性和私有方法。

function Foo() {
    var a = 1;
    function x() {
        
    }
}

公有屬性、公有方法、特權屬性、特權方法

通過this創建的屬性和方法,所有實例都可以用,所以叫公有屬性、公有方法。由于公有屬性和方法能訪問私有屬性和方法,所以別名“特權屬性”、“特權方法”。

function Foo(name) {
    this.a = name;
    this.x = function () {
        return this.name;
    }
}

構造函數的屬性、方法,跟構造函數原型的屬性、方法,有什么不同?

構造函數的屬性、方法,跟實例沒關系,也不能被繼承,所以稱為靜態屬性、靜態方法。給構造函數自身加屬性和方法的意義,在于封裝。

function Foo() {}

Foo.a = 1;
var a = 1;

觀察上述代碼,Foo.a = 1;表明a屬性跟Foo是相關的,使用的時候就可以直接用Foo.a;。如果不這樣,而是只寫var a = 1;,確實也能照樣使用這個變量a,但是從語義上講,我們根本看不懂a跟Foo有啥關系。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容