JS對象原型prototype與繼承,ES6的class與extends

與java武通、c++相同霹崎,JavaScript 也是一門面向?qū)ο蟮某绦蛘Z言。對象類型冶忱,是具有一系列相同的特征的事物的高度抽象尾菇,比如說人,每一個人有名字朗和,會說話,會吃飯等簿晓,人就是一種對象類型眶拉。 如何來定義這種對象類型,描述其屬性特征呢憔儿?

傳統(tǒng)方式:通過function關(guān)鍵字來定義一個對象類型

function People(name) {
     this.name = name
}
People.prototype.toSay= function () {
    alert("我的名字是:" + this.name)
}
People.prototype.toEat= function () {
    alert("我吃飯")
}
var p = new People("小明")
p.toSay(); // 我的名字是小明

上面的代碼里剃袍,我們定義People這種類型骇窍,它的屬性特征有name、toSay、toEat 伴挚。然后我們以People為模板new出來一個p的實例對象。剛接觸js時缩滨,可能會疑惑呐萨,function聲明的不是函數(shù)么,怎么又變成定義對象類型底哥?prototype是什么咙鞍?

其實在js中,函數(shù)本身也是一個對象趾徽。這種對象有點特殊续滋,它的作用是定義了對象類型,可以說是數(shù)據(jù)結(jié)構(gòu)模板孵奶。 而prototype是它的一個屬性疲酌,稱為對象原型,其本質(zhì)也是一個對象了袁,包含constructor和其他屬性成員朗恳。constructor默認指向自身構(gòu)造函數(shù)

所以聲明People的時候载绿,程序自動People對象添加了prototype屬性僻肖,并且讓prototype.constructor指向了People,即函數(shù)本身卢鹦。所以上面的例子等同于下面的寫法:

function People(name) {
   this.name = name
}
var proto = {
   constructor : People,
   toSay: function (name) {
        alert("我的名字是:" + name)   
   },
   toEat: function() {
        alert("我吃飯")
   }
}
People.prototype = proto   // 指定People的Prototype屬性

prototype的作用:當我們new一個實例對象p時臀脏,程序根據(jù)對象類型People的原型prototype劝堪,將原型所定義的屬性(constructor除外)復(fù)制給新的實例對象p,并執(zhí)行了一次prototype.constructor 所指向的構(gòu)造函數(shù)揉稚,對實例對象p進行初始化秒啦。

實例對象p有兩種屬性:實例屬性原型屬性
實例屬性: 構(gòu)造方法里定義的
原型屬性: 在原型prototype里定義
hasOwnProperty方法可以幫我們區(qū)分

p.hasOwnProperty("name"); // true
p.hasOwnProperty("toSay"); // false,因為這個屬性是原型上定義的

問題1:為什么我們不直接都在構(gòu)造函數(shù)里面定義呢搀玖?

function  People(name) {
      this.name = name
      this.toSay = function() {
            alert("我的名字是:" + this.name)
      }
     this.toEat  = function() {
            alert("我吃飯")
    }
}

答: 這個主要考慮內(nèi)存管理余境,因為函數(shù)是內(nèi)存中的一個對象,也就是說灌诅,toSay或toEat都是對象占有一定內(nèi)存芳来。寫在構(gòu)造函數(shù)里面,每new一個實例對象猜拾,都會執(zhí)行一次構(gòu)造函數(shù)即舌,都會重新創(chuàng)建一個函數(shù)對象,賦給新的實例對象的屬性上挎袜。結(jié)果就是每一個實例對象的toSay或toEat屬性都對應(yīng)各自的函數(shù)對象顽聂,而這些函數(shù)功能都是一樣的,我們創(chuàng)建了一大堆重復(fù)的函數(shù)對象盯仪。使用prototype不會紊搪,因為大家共享一個prototype對象。

問題2: 為什么name不是直接定義在原型prototype上呢全景?
答:每個人名字不同耀石,如果定義在prototype上,大家名字就一樣了爸黄,其中一個改變了name值娶牌,都會影響到其他實例對象。

注意:實例對象是沒有prototype屬性馆纳,所以你不可以用實例對象為模板new一個新的實例對象來诗良,只能用函數(shù)對象為模板來創(chuàng)建。

var p1 = new People(''小明")鲁驶; // 正確鉴裹,函數(shù)對象的prototype的constructor指定構(gòu)造方法
var p2 = new p1("小王") ; // error ,實例對象沒有prototype钥弯,找不到構(gòu)造方法

各大瀏覽器廠商給實例對象實現(xiàn)了一個 __proto__ 屬性径荔,指向?qū)ο笤停覀兎Q為實例對象的隱式原型脆霎,即:

var  p1 = new People("小明")
p1.__proto__ ===  People.prototype    // true

但我們要避免使用這個屬性总处, 這個屬性作用我猜測是瀏覽器提供給我們方便調(diào)試的時候用的。


問題1:People.prototype是一個對象睛蛛,這個對象是什么鹦马?
答:Object胧谈, js所有對象默認繼承js內(nèi)置對象Object。
問題2: js中荸频,怎么實現(xiàn)對象的繼承菱肖?
答:js的繼承是通過對象原型prototype來實現(xiàn)的。

// 父類型
function Animal(name) {
    this.name= name
    this.hasFoot = true
    this.color = ["orange", ''black"]
}
Animal.prototype = {
    constructor: Animal,
    voice: function(word) {
         console.info(word)
    } 
}

// 子類型 Cat
function Cat() {}
Cat.prototype = new Animal("cat");  //  Cat.prototype.constructor是Animal
Cat.prototype.constructor = Cat; // 我們將構(gòu)造函數(shù)指定回來旭从,因為我們可以在構(gòu)造擴展其它屬性

上面的代碼稳强,我們就實現(xiàn)了Cat的對象類型是繼承了Animal對象類型,所以我們可以看到:

var cat1 = new Cat()
cat1.hasFoot    // true
cat1.color      // ["orange","black"]
cat1.toString   // function toString() { [native code] }

hasFoot和悦、與footNum都是從父類型annimal繼承過來的退疫,而toString為什么有呢,其實是這樣鸽素,Cat繼承了Animal褒繁,而Animal默認繼承了Object,所以當我們找cat1的toString屬性是付鹿,發(fā)現(xiàn)自身實例屬性沒有澜汤,發(fā)現(xiàn)原型上也沒有定義蚜迅,那程序就會尋所繼承的父對象的實例屬性舵匾,父對象的原型屬性,這樣一步步找下去谁不,這就是JS的原型鏈坐梯。所以就是:

cat1.__proto__   === Cat.prototype   // true
cat1.__proto__.__proto__  === Animal.prototype  //  true ,因為Cat.prototype是一個Animal的實例對象

上面的程序設(shè)計存在一個問題,有的貓只有一種顏色刹帕,有貓身上的顏色有三種吵血,橘、白偷溺、黑蹋辅。顯然從Animal繼承過來的顏色只有不能滿足這種情況

var  cat2 =new Cat()
cat2.color.push("white");
cat1.color  // ["orange", "black", "white"]
//原因是因為,color是來自Cat.prototype挫掏,cat1和cat2共享一個prototype侦另,你改變了cat2,cat1的color原型屬性就會受到影響

面對這種情況尉共,我們的Cat對象類型應(yīng)該這么寫:

function Cat() {
     Animal.call(this)  // 這樣就可以將原型的實例屬性變成自身的實例屬性
}
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat
var cat1 = new Cat()
var cat2 = new Cat()
cat1.color === cat2.color   // false

雖然的方式解決了問題褒傅,但是還是有個缺點,調(diào)用了兩次Animal構(gòu)造函數(shù)袄友。第一次是指定Cat.prototype殿托,第二次是Cat自身構(gòu)造函數(shù)中主動調(diào)用。我們更想要的是剧蚣,指定了prototype支竹,new實例時旋廷,構(gòu)造函數(shù)就不要再調(diào)用Animal()了。此時我們需要一個工具來完成

// 工具extend
function extend(super, suber) {
        var proto = Object.create(super.prototype)
        proto.constructor = suber
        suber.prototype = proto
}

function Cat() {
      Animal.call(this); 
}
extend(Animal, Cat)

上面的這種方式唾戚,將指定Cat.prototype從通過new Animal()換成直接Object.creat(Animal.prototype),這樣就避免了Animal() 構(gòu)造函數(shù)的執(zhí)行柳洋。
實際上,這方式是最高效的方式叹坦。

對比其他面向?qū)ο箝_發(fā)語言(如: java)熊镣,js通過function定義對象類型,容易讓人不理解募书。ES6新規(guī)范推出class和extends關(guān)鍵字來實現(xiàn)面向?qū)ο缶幊獭?br> ES6方式:用class關(guān)鍵字定義對象類型绪囱,用extends關(guān)鍵字實現(xiàn)繼承

const private2 = Symbol('I am symbol value')
class A {
   a1 = '1'  // ES7 實例屬性,需要new實例來訪問, ES6規(guī)定class沒有靜態(tài)屬性莹捡,只有靜態(tài)方法所以只能在constructor中定義屬性
   static a2 = '2'  // ES7的靜態(tài)屬性鬼吵,直接 A.a2 訪問,不需要new實例
   getA1() {
         return  this.a1      // this指向new實例
   }
   static  getA2() {
        return   ‘2’    // 靜態(tài)方法
   }
   constructor(name) {
         //一定要有構(gòu)造方法篮赢,如果沒有默認生成空構(gòu)造方法
        this.a3 = '3'   // 這里定義實例屬性
        this.name = name
    }

   // 私有方法寫法
   publicMethod() {
         private1()    // 私有方法1齿椅,可以寫在class體外
         private2()   // 利用Symbol值來定義
   }
   [private2]() {
       // 這里是私有方法
   }
}
const private1 = function() { // 這里也是私有方法,但別export出去}
// 最后export class
export default A

class關(guān)鍵字會讓我們更清晰設(shè)計一個對象類型启泣,實際上,這只是語法糖:

  1. A 的實質(zhì)還是一個function
  2. 對屬性的定義是實例屬性涣脚,而對方法的定義是定義在原型上
// 通過extends繼承
class B extends A{
    constructor() {
      // 一定要在構(gòu)造函數(shù)的第一句調(diào)用super
      super()    // 這是調(diào)用父類的構(gòu)造方法
      this.b1 = '11'
      this.b2 = super.a1    // super直接調(diào)用時指向父類構(gòu)造方法,范圍屬性時寥茫,指向父類實例遣蚀,或調(diào)用父類靜態(tài)方法
    }
}

我們可以知道,實際上A纱耻、B都是兩個對象類型芭梯,B繼承A。ES6的class作為語法糖也提供了prototype和proto兩個屬性弄喘;

let instanceA = new A()
let instanceB = new B()
A.prototype   // Object
instanceA.__proto__    //即A.prototype 還是Object
B.prototype   //  A的實例對象玖喘,并且constructor指定為ClassB
instanceB.__proto__   //B.prototype
instanceB.__proto__.__proto__    // 即A.prototype ,即Object

// es6提供對class  的__proto__的訪問
A.__proto__    // A本質(zhì)是函數(shù),函數(shù)也是對象蘑志,A是Object的實例累奈,實例對象__proto__是對象類型的原型,這里是 [native code]
B.__proto__    // B繼承A卖漫,B.prototype是A的實例费尽,B是A的實例,所以B.__proto__  === A.prototype

對于class本來就是讓我們能夠避開傳統(tǒng)function的不容易理解的語義羊始,我們實際中盡量不要去使用proto,很多時候把自己給繞暈了旱幼。另外,這里補充一句:
class內(nèi)部定義的變量是不能存在變量提升的突委,也就是說你用了var也是不存在變量提升柏卤。因為他是一個語法糖冬三,我們new一個實例時才會走進構(gòu)造函數(shù)棧,執(zhí)行完后缘缚,當前棧被銷毀勾笆,而里面返回的值賦給了實例的屬性,而里面的變量標記會被清除掉桥滨,因此不存在變量提升窝爪。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市齐媒,隨后出現(xiàn)的幾起案子蒲每,更是在濱河造成了極大的恐慌,老刑警劉巖喻括,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邀杏,死亡現(xiàn)場離奇詭異,居然都是意外死亡唬血,警方通過查閱死者的電腦和手機望蜡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拷恨,“玉大人脖律,你說我怎么就攤上這事√羲妫” “怎么了状您?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵勒叠,是天一觀的道長兜挨。 經(jīng)常有香客問我,道長眯分,這世上最難降的妖魔是什么拌汇? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮弊决,結(jié)果婚禮上噪舀,老公的妹妹穿的比我還像新娘。我一直安慰自己飘诗,他們只是感情好与倡,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著昆稿,像睡著了一般纺座。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上溉潭,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天净响,我揣著相機與錄音少欺,去河邊找鬼。 笑死馋贤,一個胖子當著我的面吹牛赞别,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播配乓,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼仿滔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了犹芹?” 一聲冷哼從身側(cè)響起堤撵,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎羽莺,沒想到半個月后实昨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡盐固,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年荒给,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刁卜。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡志电,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蛔趴,到底是詐尸還是另有隱情挑辆,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布孝情,位于F島的核電站鱼蝉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏箫荡。R本人自食惡果不足惜魁亦,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望羔挡。 院中可真熱鬧洁奈,春花似錦、人聲如沸绞灼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽低矮。三九已至印叁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喉钢。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工姆打, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人肠虽。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓幔戏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親税课。 傳聞我的和親對象是個殘疾皇子闲延,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容