🔥【何不三連】比繼承家業還要簡單的JS繼承題-封裝篇(牛刀小試)

前言

你盼世界,我盼望你無bug。Hello 你們好!我是霖呆呆!前端

(這個號稱全掘金最臭不要臉的男人又成功用標題把你騙了進來,哈哈 😄)面試

"先給你個三連"segmentfault

滴滴滴~ 又是一星期沒見了markdown

看着右側目錄這麼一大排的題目1、題目2、題目三...你是不很開心,終於...又有題作了。ide

你會發現霖呆呆的文章每期都是這麼豐滿,字動不動就是上萬,題目動不動就是好幾十題,我也很擔憂大家會不會不想去看。函數

包括我本身其實也不是特別願意去看一些大篇幅的文章。工具

(固然,炒雞棒的文章除外哈,好比掘金上都超好看)oop

所以最近我轉化了一種思路,將一些知識點化爲題目,讓咱們在作題的同時來消化理解它。post

這樣就避免了整篇文章都是概念性的東西,有些枯燥😅。性能

(不過對於一些硬性必須記的東西你們也千萬不能偷懶得記着啊)

並且這幾天我發現了一些很奇怪的事情,有些讀者就是衝着我文章的評論來的。就好比個人那篇Promise面試題,一哥們就挑明瞭和我說:

"我是直接看的評論,很精彩"

我是直接看評論的...

你跳過霖呆呆辛辛苦苦寫的1.1w字的內容,點到評論區看評論?!😭

最最最主要的是!!!😭

你還不點贊...不點贊...點贊...贊!!!😭

我...設計師...給我配個我在風中凌亂的表情包。

[表情包風中凌亂~]

"你?!開除!"

哈哈哈~

玩歸玩鬧歸鬧,JS繼承把你教!

其實這是一篇系列型的文章,讓咱們轉到系列介紹去看看吧...

等會見...

JS繼承系列介紹

看過霖呆呆文章的小夥伴應該都感受的出,我比較喜歡針對每一個知識點出一些比較細節的題目,而後將這些細節連串起來最後組合成你們最愛的綜合題😄。

該系列主要爲了讓咱們完全理解JavaScript面向對象的三大特性:封裝繼承多態

"咦~這三大特性我知道啊,大清都完了你還在這談"

啊~ 看到這裏你先彆着溜,開始的我也是和你同樣以爲背背概念,寫點小例子就懂了,直到霖呆呆本身給本身出了幾道魔鬼題,我才發現以前對它們的理解仍是不太全面...所以纔有了本系列。

系列總目錄:

  • 封裝

    • ES6以前的封裝-構造函數
    • ES6以後的封裝-class
  • 繼承

    • 原型鏈繼承
    • 構造繼承
    • 組合繼承
    • 寄生組合繼承
    • 原型式繼承
    • class中的extends繼承
  • 多態

(在開始寫以前本想要一篇文章所有搞定的,可是發現字真太多了,因此才分開來寫,並且我終於又能夠用我最愛的緋紅主題了 😄)

這一章節主要是想向你們介紹一下JS面向對象的第一大特性-封裝,也是爲了給後面最重要的繼承打好基礎。

題目也不太多,總共17道,算是牛刀小試吧。

經過閱讀本章節你能夠學習到:

  • ES6以前的封裝-構造函數
  • ES6以後的封裝-class

前期準備

先來理解一些最最最基本的概念:

(一)

// 1. 構造函數
function Cat (name) {
this.name
}
// 2. 構造函數原型對象
Cat.prototype
// 3. 使用Cat構造函數建立的實例'乖乖'
var guaiguai = new Cat('guaiguai')
// 4. 構造函數的靜態方法,名爲fn
Cat.fn = function () {}
// 5. 原型對象上的方法,名爲fn
Cat.prototype.fn = function () {}
複製代碼
實例原型鏈
實例原型鏈

(二)

語法糖的意思是現有技術本能夠實現,可是採用某種寫法會更加簡潔優雅。

好比class就是語法糖。

(三)

原型鏈繼承的思惟導圖

(這個暫時看不懂不要緊,在繼承那一章節中會講到)

封裝

把客觀事物封裝成抽象的類,隱藏屬性和方法,僅對外公開接口。

1. ES6以前的封裝

(雖然如下內容是概念部分,可是對你解題頗有幫助哦,請務必仔細閱讀它 😁)

都知道ES6class實際就是一個語法糖,那麼在ES6以前,是沒有類這個概念的,所以是藉助於原型對象構造函數來實現。

  • 私有屬性和方法:只能在構造函數內訪問不能被外部所訪問(在構造函數內使用var聲明的屬性)
  • 公有屬性和方法(或實例方法):對象外能夠訪問到對象內的屬性和方法(在構造函數內使用this設置,或者設置在構造函數原型對象上好比Cat.prototype.xxx)
  • 靜態屬性和方法:定義在構造函數上的方法(好比Cat.xxx),不須要實例就能夠調用(例如Object.assign())

1.1 題目一

(理解私有屬性方法公有屬性方法)

好比我如今想要封裝一個生產出貓,名爲Cat的構造函數。

  • 因爲貓的都是咱們肉眼看不見的,因此我把它們設置爲私有屬性(隱藏起來)
  • 而且貓的心跳咱們也是看不到的,因此我把它設置爲私有方法(隱藏起來)
  • 而後貓的毛色是能夠看見的,因此我把它設置爲公有屬性
  • 而且貓跳起來這個動做咱們是看的到的,因此我把它設置爲公有方法
function Cat (name, color) {
var heart = '❤️'
var stomach = '胃'
var heartbeat = function () {
console.log(heart + '跳')
}

this.name = name
this.color = color

this.jump = function () {
heartbeat() // 能跳起來代表這隻貓是活的,心也就能跳
console.log('我跳起來了~來追我啊')
}
}
var guaiguai = new Cat('guaiguai', 'white')
console.log(guaiguai)
guaiguai.jump()
複製代碼

上述代碼打印出來的應該是:

Cat{ name: 'guaiguai', color: 'white', jump: function(){} }
❤️跳
我跳起來了~來追我啊
複製代碼

能夠看到,咱們生產出名字叫作乖乖的小貓咪只有這幾個屬性能訪問到(也就是能被肉眼看到),爲公有屬性:

  • name
  • color
  • jump

而私有屬性,是咱們看不到的:

  • heart
  • somach
  • heartbeat

因此若是你想要直接使用它是不可以的:

// 私有
console.log(guaiguai.heart) // undefined
console.log(guaiguai.stomach) // undefined
guaiguai.heartbeat() // 報錯
複製代碼

小結:

很好區分:

  • 在函數內用var定義的就是私有的
  • 在函數內用this承接的就是公有

小貓咪: "憑啥每次都是用我舉例子"

霖呆呆: "由於你可愛撒~"

小貓咪: "嘻嘻"

1.2 題目二

(理解靜態屬性方法公有屬性方法)

咱們如今往剛剛的Cat構造函數中加些東西。

  • 咱們須要對Cat這個構造函數加一個描述,代表它是用來生產貓的,因此我把descript設置爲它的靜態屬性

  • 因爲一聽到貓這種動物就以爲它會賣萌,因此我把賣萌這個動做設置爲它的靜態方法

  • 因爲貓都會用唾液清潔身體,因此我把清潔身體這個動做設置爲它的公有方法

// 這段是舊代碼
function Cat (name, color) {
var heart = '❤️'
var stomach = '胃'
var heartbeat = function () {
console.log(heart + '跳')
}

this.name = name
this.color = color

this.jump = function () {
heartbeat() // 能跳起來代表這隻貓是活的,心也就能跳
console.log('我跳起來了~來追我啊')
}
}
// 這段是新增的代碼
Cat.descript = '我這個構造函數是用來生產出一隻貓的'
Cat.actingCute = function () {
console.log('一聽到貓我就想到了它會賣萌')
}
Cat.prototype.cleanTheBody = function () {
console.log('我會用唾液清潔身體')
}
var guaiguai = new Cat('guaiguai', 'white')

console.log(Cat.descript)
Cat.actingCute()
console.log(guaiguai.descript)
guaiguai.cleanTheBody()
複製代碼

上述代碼打印出來的應該是:

'我這個構造函數是用來生產出一隻貓的'
'一聽到貓我就想到了它會賣萌'
undefined
'我會用唾液清潔身體'
複製代碼

能夠看到,咱們定義的descriptactingCute是定義在構造函數Cat上的,因此能夠直接被Cat調用,爲靜態屬性和方法。

可是descriptactingCute並不能存在於乖乖這個實例上,descript只是對構造函數Cat的描述,並非對乖乖的描述,因此打印出undefined

不過清潔身體是定義在原型對象prototype中的,屬於公有方法(實例方法),也就是乖乖這個實例能夠用它來調用。

靜態屬性和方法:

  • descript
  • actingCute

實例(公有)屬性和方法:

  • name
  • color
  • jump
  • cleanTheBody

小結:

也很好區分:

  • 在構造函數上也就是使用Cat.xxx定義的是靜態屬性和方法
  • 在構造函數內使用this設置,或者設置在構造函數原型對象上好比Cat.prototype.xxx,就是公有屬性和方法(實例方法)

(也有小夥伴可能會有疑問,這個靜態屬性和方法是有什麼用的啊,感受咱們編碼的時候並無用到過啊。Really? 哈哈 😄,Promise.all()、Promise.race()、Object.assign()、Array.from()這些不就是嗎?)

(至於實例方法,想一想push、shift,它們實際上不是存在於原型對象上的嗎?Array.prototype.push

1.3 題目三

(理解實例自身的屬性定義在構造函數原型對象中的屬性的區別)

OK👌,霖呆呆你剛剛既然已經說了使用this.xxx = xxx的方式和使用Cat.prototype.xxx = xxx都是屬於實例對象guaiguai上的公有屬性,那它們是有什麼區別嗎?

來看這道題咱們就能理解它們的區別了:

function Cat (name) {
this.name = name
}
Cat.prototype.prototypeProp = '我是構造函數原型對象上的屬性'
Cat.prototype.cleanTheBody = function () {
console.log('我會用唾液清潔身體')
}
var guaiguai = new Cat('guaiguai')
console.log(guaiguai)
console.log(guaiguai.name)
console.log(guaiguai.prototypeProp)
guaiguai.cleanTheBody()
複製代碼

這裏輸出的結果 🤔️?

Cat {name: "guaiguai"}
'guaiguai'
'我是構造函數原型對象上的屬性'
'我會用唾液清潔身體'
複製代碼

看到沒,name是使用this.xxx = xxx的形式定義的,它能直接讓實例guaiguai就擁有這個屬性。

prototypeProp 、cleanTheBody畢竟是定義在構造函數原型上的,因此並不能出如今實例guaiguai上,可是guaiguai卻能訪問和調用它們。

所以咱們得出結論:

定義在構造函數原型對象上的屬性和方法雖然不能直接表如今實例對象上,可是實例對象卻能夠訪問或者調用它們

1.4 題目四

既然咱們已經知道了實例自身的屬性定義在構造函數原型對象中的屬性的區別,那麼咱們通常是如何區分它們的呢?

來看看這裏:

function Cat (name) {
this.name = name
}
Cat.prototype.prototypeProp = '我是構造函數原型對象上的屬性'
Cat.prototype.cleanTheBody = function () {
console.log('我會用唾液清潔身體')
}
var guaiguai = new Cat('guaiguai')

for (key in guaiguai) {
if (guaiguai.hasOwnProperty(key)) {
console.log('我是自身屬性', key)
} else {
console.log('我不是自身屬性', key)
}
}
console.log('-分隔符-')
console.log(Object.keys(guaiguai))
console.log(Object.getOwnPropertyNames(guaiguai))
複製代碼

這道題中,我分別用了三種方式來獲取實例對象guaiguai上的屬性名:

  • for...in...
  • Object.keys()
  • Object.getOwnPropertyNames()

輸出的結果爲:

'我是自身屬性 name'
'我不是自身屬性 prototypeProp'
'我不是自身屬性 cleanTheBody'
'-分隔符-'
["name"]
["name"]
複製代碼

由此能夠得出:

  • 使用for...in...能獲取到實例對象自身的屬性和原型鏈上的屬性
  • 使用Object.keys()Object.getOwnPropertyNames()只能獲取實例對象自身的屬性
  • 能夠經過.hasOwnProperty()方法傳入屬性名來判斷一個屬性是否是實例自身的屬性

(上面👆的說法其實並不太嚴謹,由於要創建在可枚舉屬性的前提下(屬性的enumerabletrue),不過這邊我不發散下去了...)

1.5 題目五

下面讓咱們來作道題,看看你到底有沒有掌握上面的知識點呢 😁。

function Person (name, sex) {
this.name = name
this.sex = sex
var evil = '我很邪惡'
var pickNose = function () {
console.log('我會扣鼻子但不讓你看見')
}
this.drawing = function (type) {
console.log('我要畫一幅' + type)
}
}
Person.fight = function () {
console.log('打架')
}
Person.prototype.wc = function () {
console.log('我是我的我會wc')
}
var p1 = new Person('lindaidai', 'boy')
console.log(p1.name)
console.log(p1.evil)
p1.drawing('國畫')
p1.pickNose()
p1.fight()
p1.wc()
Person.fight()
Person.wc()
console.log(Person.sex)
複製代碼

答案:

'lindaidai'
undefined
'我要畫一幅國畫'
Uncaught TypeError: p1.pickNose is not a function
Uncaught TypeError: p1.fight is not a function
'我是我的我會wc'
'打架'
Uncaught TypeError: Person.wc is not a function
undefined
複製代碼

解析:

  • name爲公有屬性,實例訪問它打印出'lindaidai'
  • evil爲私有屬性,實例訪問它打印出'undefined'
  • drawing是共有(實例)方法,實例調用它打印出'我要畫一幅國畫'
  • pickNose是私有方法,實例調用它會報錯,由於它並不存在於實例上
  • fight是靜態方法,實例調用它報錯,由於它並不存在於實例上
  • wc存在於構造函數的原型對象中,使用實例調用它打印出'我是個我會wc'
  • fight存在於構造函數上,使用構造函數調用它打印出'打架'
  • wc存在於構造函數的原型對象中,並不存在於構造函數中,因此報錯
  • sex爲公有(實例)屬性,並不存在於構造函數上,使用構造函數訪問它爲undefined

這裏你們可能會有一個疑惑點了,爲何最後一個Person.sex也會是undefined呢?

我明明已經這樣寫了:

function Person (sex) {
this.sex = sex
}
複製代碼

看起來sex是定義在Person裏的呀。

注意了,this.sex表示的是給使用構造函數建立的實例上增長屬性sex,而不是給構造函數自己增長(只有Person.sex纔是給構造函數上增長屬性)。

1.6 題目六

若是個人構造函數和構造函數原型對象上存在相同名稱的屬性咋辦呢 🤔️ ?

function Cat () {
this.color = 'white'
this.getColor = function () {
console.log(this.color)
}
}
Cat.prototype.color = 'black'
var cat = new Cat()
cat.getColor()
複製代碼

這裏的執行結果爲:

'white'
複製代碼

其實這個很好理解,你原型對象上雖然有一個名叫color的屬性,可是我實例對象本身就也有一個啊,那我爲何要用你的呢?只有我本身沒有,我纔會到你那裏去拿。

因此這也就引出了另外一個常常聽到的概念:

當訪問一個對象的屬性 / 方法時,它不只僅在該對象上查找,還會查找該對象的原型,以及該對象的原型的原型,一層一層向上查找,直到找到一個名字匹配的屬性 / 方法或到達原型鏈的末尾(null)。

也就是大名鼎鼎的原型鏈查找

咱要是沒理解不要緊哈,一塊兒來看下面一個例子。

1.7 題目七

如今我在Cat的原型對象上,還有它原型對象的原型對象上都定義一個叫作color的屬性。

(原型對象本質也是個對象,因此它的__proto__也就是Object.prototype)

function Cat () {
this.color = 'white'
this.getColor = function () {
console.log(this.color)
}
}
Cat.prototype.color = 'black'
Object.prototype.color = 'yellow'
Object.prototype.feature = 'cute'
var cat = new Cat()

cat.getColor()
console.log(cat)
console.log(cat.feature)
複製代碼

而後讓咱們來看看結果:

'white'
Cat {color: "white", getColor: ƒ}
'cute'
複製代碼

看到了不。

color這個屬性仍是以它自身的white爲主,可是feature這個屬性沒在實例cat上吧,因此它就會向上一層一層查找,結果在Object.prototype中找到了,所以打印出cute

整個過程就是這樣:

(偷個懶,盜個圖😂,圖片來源https://muyiy.cn/idea/)

1.8 題目八

"等會等會,讓我緩一下"

"wc,我忽然就想明白了不少事情!"

好比下面這種寫法:

var obj = { name: 'obj' }
console.log(obj.toString())
console.log(obj.hasOwnProperty('name'))
console.log(Object.prototype)
複製代碼

爲何個人obj中明明就沒有toString()、hasOwnProperty()方法,可是我卻能夠調用它。

如今我知道了,原來obj本質是個Object類型。

使用var obj = { name: 'obj' }就至關因而調用了new Object

var obj = new  Object({ 'name': 'obj' })
複製代碼

這樣的話,我固然就可使用Object.prototype上的方法啦!

執行結果爲:

(obj.toString()這裏的結果爲[object Object]應該都知道是什麼緣由吧?)

總結-構造函數

如今在來回頭看看那句話:

把客觀事物封裝成抽象的類,隱藏屬性和方法,僅對外公開接口

是否是好理解多了呢?

而後讓咱們對構造函數配合原型對象封裝來作一個總結吧:

(一) 私有屬性、公有屬性、靜態屬性概念:

  • 私有屬性和方法:只能在構造函數內訪問不能被外部所訪問(在構造函數內使用var聲明的屬性),見題1.1
  • 公有屬性和方法(或實例方法):對象外能夠訪問到對象內的屬性和方法(在構造函數內使用this設置,或者設置在構造函數原型對象上好比Cat.prototype.xxx),見題1.2
  • 靜態屬性和方法:定義在構造函數上的方法(好比Cat.xxx),不須要實例就能夠調用(例如Object.assign())

(二) 實例對象上的屬性和構造函數原型上的屬性:

  • 定義在構造函數原型對象上的屬性和方法雖然不能直接表如今實例對象上,可是實例對象卻能夠訪問或者調用它們。(見題1.3)
  • 當訪問一個對象的屬性 / 方法時,它不只僅在該對象上查找,還會查找該對象的原型,以及該對象的原型的原型,一層一層向上查找,直到找到一個名字匹配的屬性 / 方法或到達原型鏈的末尾(null)。(見題1.7)

(三) 遍歷實例對象屬性的三種方法:

  • 使用for...in...能獲取到實例對象自身的屬性和原型鏈上的屬性
  • 使用Object.keys()Object.getOwnPropertyNames()只能獲取實例對象自身的屬性
  • 能夠經過.hasOwnProperty()方法傳入屬性名來判斷一個屬性是否是實例自身的屬性

2. ES6以後的封裝

ES6以後,新增了class 這個關鍵字。

它能夠用來代替構造函數,達到建立「一類實例」的效果。

而且類的數據類型就是函數,因此用法上和構造函數很像,直接用new命令來配合它建立一個實例。

還有一件事你可能不知道吧,那就是,類的全部方法都定義在類的prototype屬性上面

例如:

class Cat {
constructor() {}
toString () {}
toValue () {}
}
// 等同於
function Cat () {}
Cat.prototype = {
constructor() {}
toString () {}
toValue () {}
}
複製代碼

這個能夠看下面👇的題目2.2來理解它。

2.1 題目一

如今咱們將1.1的題目換成class版本的來看看。

class Cat {
constructor (name, color) {
var heart = '❤️'
var stomach = '胃'
var heartbeat = function () {
console.log(heart + '跳')
}

this.name = name
this.color = color
this.jump = function () {
heartbeat()
console.log('我跳起來了~來追我啊')
}
}
}
var guaiguai = new Cat('guaiguai', 'white')
console.log(guaiguai)
guaiguai.jump()
複製代碼

其實你會發現,當你使用class的時候,它會默認調用constructor這個函數,來接收一些參數,並構造出一個新的實例對象(this)並將它返回,所以它被稱爲constructor構造方法(函數)。

(另外,其實若是你的class沒有定義constructor,也會隱式生成一個constructor方法)

能夠看到,通過用class改造後的Cat

公有(實例)屬性和方法:

  • name
  • color
  • jump

而對於私有屬性,我的感受上述的heart不該該叫作私有屬性,它只不過被侷限於constructor這個構造函數中,是這個做用域下的變量而已。

執行結果:

Cat{ name: 'guaiguai', color: 'white', jump: function () {} }
❤️跳
'我跳起來了~來追我啊'
複製代碼

2.2 題目二

(弄懂在類中定義屬性或方法的幾種方式)

class Cat {
constructor () {
var heart = '❤️'
this.name = 'guaiguai'
this.jump = function () {}
}
color = 'white'
cleanTheBody = function () {
console.log('我會用唾液清潔身體')
}
hideTheShit () {
console.log('我在臭臭完以後會把它藏起來')
}
}
var guaiguai = new Cat()
console.log(guaiguai)
console.log(Object.keys(guaiguai))
guaiguai.cleanTheBody()
guaiguai.hideTheShit()
複製代碼

請仔細看看這道題,在這裏面我用了四種不一樣的方式來定義一些屬性。

  1. constructorvar一個變量,它只存在於constructor這個構造函數中
  2. constructor中使用this定義的屬性和方法會被定義到實例上
  3. class中使用=來定義一個屬性和方法,效果與第二點相同,會被定義到實例上
  4. class中直接定義一個方法,會被添加到原型對象prototype

至此,這道題的答案爲:

Cat {color: "white", name: "guaiguai", cleanTheBody: ƒ, jump: ƒ}
["color", "cleanTheBody", "name", "jump"]
'我會用唾液清潔身體'
'我在臭臭完以後會把它藏起來'
複製代碼

解析:

  • heart只能在constructor函數中使用,所以不會出如今實例上。
  • name、jump、color、cleanTheBody知足於上面👆說到的第二點和第三點
  • hideTheShit是在類裏直接定義的,知足於上面👆說的第四點,所以它不會被Object.keys()獲取到。
  • hideTheShit雖然是在原型對象中,可是也仍是能被實例對象所調用,所以最後一段代碼也會被執行'我在臭臭完以後會把它藏起來'

這四種定義的方式已經介紹完了 😁,相信你們比較迷惑的一點就是如下這兩種方式的定義吧:

class Cat {
cleanTheBody = function () {}
hideTheShit () {}
}
複製代碼

看起來都是定義一個函數呀,爲何第一個就能夠在實例對象中,而第二個是在原型對象中呢 🤔️ ?

其實不須要特地的去記住它,你只須要知道:在類的全部方法都定義在類的prototype屬性上面

這裏的cleanTheBody你能夠理解爲它和color同樣只是一個普通的變量,只不過這個變量是個函數,因此它並不算是定義在類上的函數,所以不會存在於原型對象上。

hideTheShit是實實在在的定義在類上的方法,因此它和constructor方法同樣,都是在類的原型對象上。

轉化爲僞代碼就是:

class Cat {
constructor() {}
hideTheShit () {}
}
// 等同於
function Cat () {}
Cat.prototype = {
constructor() {}
hideTheShit () {}
}
複製代碼

2.3 題目三

(在class定義靜態屬性和方法)

前面咱們給Cat定義靜態屬性和方法是採用這種方式,Cat.xxx

function Cat () {...}
Cat.descript = '我這個構造函數是用來生產出一隻貓的'
Cat.actingCute = function () {
console.log('一聽到貓我就想到了它會賣萌')
}
複製代碼

class中你也可使用Cat.xxx這種方式定義,由於前面說過了,class本質也是個對象。

但除此以外,你還可使用static標識符表示它是一個靜態的屬性或者方法:

class Cat {
static descript = '我這個類是用來生產出一隻貓的'
static actingCute () {
console.log('一聽到貓我就想到了它會賣萌')
}
// static actingCute = function () {} // 這種寫法也是設置靜態的方法
}
複製代碼

OK👌,如今讓咱們來作作下面這道題吧 😊:

class Cat {
constructor (name, color) {
var heart = '❤️'
var stomach = '胃'
var heartbeat = function () {
console.log(heart + '跳')
}
this.name = name
this.color = color
heartbeat()
this.jump = function () {
console.log(this)
console.log('我跳起來了~來追我啊')
}
}
cleanTheBody = function () {
console.log('我會用唾液清潔身體')
}
static descript = '我這個類是用來生產出一隻貓的'
static actingCute () {
console.log(this)
console.log('一聽到貓我就想到了它會賣萌')
}
}
Cat.staticName = 'staticName'
var guaiguai = new Cat('guaiguai', 'white')

console.log(guaiguai)
guaiguai.jump()
guaiguai.cleanTheBody()
console.log(guaiguai.descript)
guaiguai.actingCute()

Cat.actingCute()
console.log(Cat.descript)
console.log(Cat.staticName)
複製代碼

結果:

❤️跳
Cat{ name: 'guaiguai', color: 'white', jump: function(){}, cleanTheBody: function(){} }
Cat{ name: 'guaiguai', color: 'white', jump: function(){}, cleanTheBody: function(){} }
'我跳起來了~來追我啊'
'我會用唾液清潔身體'
undefined
Uncaught TypeError: guaiguai.actingCute is not a function

class Cat{...}
'一聽到貓我就想到了它會賣萌'
'我這個類是用來生產出一隻貓的'
'staticName'
複製代碼

結果分析:

  • 首先在構造guaiguai這個對象的時候會執行heartbeat方法,打印出❤️跳

  • 其次打印出的guaiguai它只會擁有class中定義的實例屬性和方法,因此並不會有descriptactingCute

  • jump中的this指向的是實例對象guaiguai,而且執行了'我跳起來了~來追我啊'

  • 直接定義在class中的屬性或者方法就至關因而定義在實例對象上,因此也屬於實例方法,cleanThebody會執行打印出'我會用唾液清潔身體'

  • 使用了static定義的屬性和方法爲靜態屬性和方法,並不存在於實例上,因此打印出undefined和報錯

  • actingCute使用了static修飾符,因此它是靜態方法,存在於Cat這個類上,所以它裏面的this指向這個類,而且執行了'一聽到貓我就想到了它會賣萌'

  • descript使用了static修飾符,因此它是靜態屬性,打印出'我這個類是用來生產出一隻貓的'

  • Cat.staticName = 'staticName'就至關於定義了一個靜態屬性,因此打印出staticName

2.4 題目四

咱們再來看看這道題,友情提示,這是個坑 🤮...

var a = new A()
function A () {}
console.log(a)

var b = new B()
class B {}
console.log(b)
複製代碼

你開始的預想是否是:

A{}
B{}
複製代碼

😁,結果卻發現報錯了:

A {}
Uncaught ReferenceError: Cannot access 'B' before initialization
複製代碼

那是由於,函數A是會被提高至做用域的最頂層,因此能夠在定義函數A以前使用new A()

可是類卻不存在這種提高機制,因此當你執行new B()的時候它就會告訴你在B沒有初始化以前不能使用它。

儘管咱們知道,class它的本質也是一個函數:

console.log(typeof B) // function
複製代碼

2.5 題目五

坑二 🤮...

class Cat {
constructor () {
this.name = 'guaiguai'
var type = 'constructor'
}
type = 'class'
getType = function () {
console.log(this.type)
console.log(type)
}
}
var type = 'window'
var guaiguai = new Cat()
guaiguai.getType()
複製代碼

這裏的執行結果是什麼呢?

主要是考察了你對做用域以及class的理解。

答案爲:

'class'
'window'
複製代碼

解析:

  • 調用getType函數的是guaiguai,因此裏面的this指向了guaiguai,而guaiguai上的typeclass
  • 當要打印出type的時候,發現getType函數中並無這個變量,因此就向外層查找,找到了window中存在這個變量,所以打印出window。(var type = 'constructor'是函數constructor中的變量, 你也能夠理解爲是constructor函數的私有變量)

2.6 題目六

既然作到了函數類型的題目,那怎麼能不想到箭頭函數呢?嘿嘿 。

陰笑~

讓咱們將2.5中的getType函數換成箭頭函數看看?

class Cat {
constructor () {
this.name = 'guaiguai'
var type = 'constructor'
}
type = 'class'
getType = () => {
console.log(this.type)
console.log(type)
}
}
var type = 'window'
var guaiguai = new Cat()
guaiguai.getType()
console.log(guaiguai)
複製代碼

如今調用guaiguai.getType()你以爲會是啥?

"既然箭頭函數內的this是由外層做用域決定的,那這裏外層做用域是window,固然this.type就是'window'咯"

咦~

還記得我以前說過的,class的本質是個函數嗎?因此你碰到class內有箭頭函數的題目,把它當成構造函數建立對象來處理就能夠了。

在構造函數中若是使用了箭頭函數的話,this指向的就是這個實例對象。

所以將class轉化爲構造函數的話,僞代碼爲:

function Cat () {
this.type = 'class'
this.getType = () => {
console.log(this.type)
console.log(type)
}
}
Cat.prototype.constructor = function () {
this.name = 'guaiguai'
var type = 'constructor'
}
var type = 'window'
var guaiguai = new Cat()
guaiguai.constructor()
guaiguai.getType()
console.log(guaiguai)
複製代碼

別的都好理解,這裏爲啥,constructor要放在原型對象中,而且要在var guaiguai = new Cat()下面再調用它呢?

嘻嘻,還記得在2.2中咱們就說過了嗎,任何放在類上的方法都至關於寫在原型對象上,而且在使用類的時候,會隱式執行constructor函數。這兩段代碼就是爲了模擬這個操做。

這樣的話,上面👆兩個題目的結果都是:

'class'
'window'
Cat {type: "class", name: "guaiguai", getType: ƒ}
複製代碼

哇~

有點意思哈~

class還能這樣玩?😁

霖呆呆你....你臭不要臉

不過上面對於箭頭函數還有不理解的小夥伴能夠查看這篇

《【建議👍】再來40道this面試題酸爽繼續(1.2w字用手整理)》

文章中的7.4題,裏面介紹了構造函數對象中普通函數和箭頭函數的區別

2.7 題目七

若是在class中存在兩個相同的屬性或者方法會怎麼樣呢 🤔️?

class Cat {
constructor () {
this.name = 'cat1'
}
name = 'cat2'
getName = function () {
console.log(this.name)
}
}
var cat = new Cat()
cat.getName()
複製代碼

這道題中,咱們調用getName方法,打印出的會是:

'cat1'
複製代碼

因此能夠看出constructor中定義的相同名稱的屬性和方法會覆蓋在class裏定義的。

2.8 題目八

那麼,原型對象中相同名稱的屬性和方法呢?

class Cat {
constructor () {
this.name = 'cat1'
}
name = 'cat2'
getName = function () {
console.log(this.name)
}
}
Cat.prototype.name = 'cat3'
var cat = new Cat()
cat.getName()
複製代碼

答案:

'cat1'
複製代碼

沒錯,仍是以constructor中的爲準。這裏和構造函數中同名屬性的處理方式是同樣的,能夠看上面👆的1.7題。

2.9 題目九

好吧 😅,如今能夠加大難度了:

class Cat {
constructor () {
this.name = 'guaiguai'
var type = 'constructor'
this.getType = () => {
console.log(this.type)
console.log(type)
}
}
type = 'class'
getType = () => {
console.log(this.type)
console.log(type)
}
}
var type = 'window'
var guaiguai = new Cat()
guaiguai.getType()
console.log(guaiguai)
複製代碼

首先咱們很清楚,若是type打印出的是window那就表示使用的是第二個getType,不然表示用的是第一個getType

那麼根據題2.7,咱們能夠看出,第一個getType是會覆蓋第二個的,因此執行結果爲:

'class'
'constructor'
Cat {type: "class", name: "guaiguai", getType: ƒ}
複製代碼

總結-class

來吧,對class實現封裝也來作個總結唄:

(一) class的基本概念:

  • 當你使用class的時候,它會默認調用constructor這個函數,來接收一些參數,並構造出一個新的實例對象(this)並將它返回。
  • 若是你的class沒有定義constructor,也會隱式生成一個constructor方法

(二) class中幾種定義屬性的區別:

  • constructorvar一個變量,它只存在於constructor這個構造函數中

  • constructor中使用this定義的屬性和方法會被定義到實例上

  • class中使用=來定義一個屬性和方法,效果與第二點相同,會被定義到實例上

  • class中直接定義一個方法,會被添加到原型對象prototype

  • class中使用了static修飾符定義的屬性和方法被認爲是靜態的,被添加到類自己,不會添加到實例上

(三) other:

  • class本質雖然是個函數,可是並不會像函數同樣提高至做用域最頂層
  • 如遇class中箭頭函數等題目請參照構造函數來處理
  • 使用class生成的實例對象,也會有沿着原型鏈查找的功能

後語

知識無價,支持原創。

參考文章:

你盼世界,我盼望你無bug。這篇文章就介紹到這裏,讓咱們先打好面向對象的基礎,才能挑戰後面的魔鬼題目 😄。(才能繼承家產,哈哈哈)

系列中的繼承多態霖呆呆也會在近幾天給更出來,請期待一小下吧 😄。

最後,正經的給個三連吧 😂。

喜歡霖呆呆的小夥還但願能夠關注霖呆呆的公衆號 LinDaiDai 或者掃一掃下面的二維碼👇👇👇.

我會不定時的更新一些前端方面的知識內容以及本身的原創文章🎉

你的鼓勵就是我持續創做的主要動力 😊.

相關推薦:

《全網最詳bpmn.js教材》

《你的掘金文章本能夠這麼炫(博客美化工具一波帶走)》

《【建議改爲】讀完這篇你還不懂Babel我給你寄口罩》

《【建議星星】要就來45道Promise面試題一次爽到底(1.1w字用心整理)》

《【建議👍】再來40道this面試題酸爽繼續(1.2w字用手整理)》

《💦【何不三連】作完這48道題完全弄懂JS繼承(1.7w字含辛整理-返璞歸真)》

相關文章
相關標籤/搜索