你盼世界,我盼望你無bug
。Hello 你們好!我是霖呆呆!前端
(這個號稱全掘金最臭不要臉的男人又成功用標題把你騙了進來,哈哈 😄)面試
"先給你個三連"
segmentfault
滴滴滴~ 又是一星期沒見了markdown
看着右側目錄這麼一大排的題目1、題目2、題目三...
你是不很開心,終於...又有題作了。ide
你會發現霖呆呆的文章每期都是這麼豐滿,字動不動就是上萬,題目動不動就是好幾十題,我也很擔憂大家會不會不想去看。函數
包括我本身其實也不是特別願意去看一些大篇幅的文章。工具
(固然,炒雞棒的文章除外哈,好比掘金上都超好看)oop
所以最近我轉化了一種思路,將一些知識點化爲題目,讓咱們在作題的同時來消化理解它。post
這樣就避免了整篇文章都是概念性的東西,有些枯燥😅。性能
(不過對於一些硬性必須記的東西你們也千萬不能偷懶得記着啊)
並且這幾天我發現了一些很奇怪的事情,有些讀者就是衝着我文章的評論來的。就好比個人那篇Promise面試題,一哥們就挑明瞭和我說:
"我是直接看的評論,很精彩"
我是直接看評論的...
你跳過霖呆呆辛辛苦苦寫的1.1w字
的內容,點到評論區看評論?!😭
最最最主要的是!!!😭
你還不點贊...不點贊...點贊...贊!!!😭
我...設計師...給我配個我在風中凌亂的表情包。
[表情包風中凌亂~]
"你?!開除!"
哈哈哈~
玩歸玩鬧歸鬧,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
就是語法糖。
(三)
原型鏈繼承的思惟導圖
(這個暫時看不懂不要緊,在繼承那一章節中會講到)
把客觀事物封裝成抽象的類,隱藏屬性和方法,僅對外公開接口。
(雖然如下內容是概念部分,可是對你解題頗有幫助哦,請務必仔細閱讀它 😁)
都知道ES6
的class
實際就是一個語法糖,那麼在ES6
以前,是沒有類這個概念的,所以是藉助於原型對象和構造函數來實現。
var
聲明的屬性)this
設置,或者設置在構造函數原型對象上好比Cat.prototype.xxx
)Cat.xxx
),不須要實例就能夠調用(例如Object.assign()
)(理解私有屬性方法和公有屬性方法)
好比我如今想要封裝一個生產出貓,名爲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
承接的就是公有小貓咪: "憑啥每次都是用我舉例子"
霖呆呆: "由於你可愛撒~"
小貓咪: "嘻嘻"
(理解靜態屬性方法和公有屬性方法)
咱們如今往剛剛的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
'我會用唾液清潔身體'
複製代碼
能夠看到,咱們定義的descript
和actingCute
是定義在構造函數Cat
上的,因此能夠直接被Cat
調用,爲靜態屬性和方法。
可是descript
和actingCute
並不能存在於乖乖
這個實例上,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
)
(理解實例自身的屬性和定義在構造函數原型對象中的屬性的區別)
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
卻能訪問和調用它們。
所以咱們得出結論:
定義在構造函數原型對象上的屬性和方法雖然不能直接表如今實例對象上,可是實例對象卻能夠訪問或者調用它們
既然咱們已經知道了實例自身的屬性和定義在構造函數原型對象中的屬性的區別,那麼咱們通常是如何區分它們的呢?
來看看這裏:
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()
方法傳入屬性名來判斷一個屬性是否是實例自身的屬性(上面👆的說法其實並不太嚴謹,由於要創建在可枚舉屬性的前提下(屬性的enumerable
爲true
),不過這邊我不發散下去了...)
下面讓咱們來作道題,看看你到底有沒有掌握上面的知識點呢 😁。
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
纔是給構造函數上增長屬性)。
若是個人構造函數和構造函數原型對象上存在相同名稱的屬性咋辦呢 🤔️ ?
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
)。
也就是大名鼎鼎的原型鏈查找。
咱要是沒理解不要緊哈,一塊兒來看下面一個例子。
如今我在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/)
"等會等會,讓我緩一下"
"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()
方法傳入屬性名來判斷一個屬性是否是實例自身的屬性在ES6
以後,新增了class
這個關鍵字。
它能夠用來代替構造函數,達到建立「一類實例」的效果。
而且類的數據類型就是函數,因此用法上和構造函數很像,直接用new
命令來配合它建立一個實例。
還有一件事你可能不知道吧,那就是,類的全部方法都定義在類的prototype屬性上面。
例如:
class Cat {
constructor() {}
toString () {}
toValue () {}
}
// 等同於
function Cat () {}
Cat.prototype = {
constructor() {}
toString () {}
toValue () {}
}
複製代碼
這個能夠看下面👇的題目2.2
來理解它。
如今咱們將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 () {} }
❤️跳
'我跳起來了~來追我啊'
複製代碼
(弄懂在類中定義屬性或方法的幾種方式)
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()
複製代碼
請仔細看看這道題,在這裏面我用了四種不一樣的方式來定義一些屬性。
constructor
中var
一個變量,它只存在於constructor
這個構造函數中constructor
中使用this
定義的屬性和方法會被定義到實例上class
中使用=
來定義一個屬性和方法,效果與第二點相同,會被定義到實例上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 () {}
}
複製代碼
(在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
中定義的實例屬性和方法,因此並不會有descript
和actingCute
jump
中的this
指向的是實例對象guaiguai
,而且執行了'我跳起來了~來追我啊'
直接定義在class
中的屬性或者方法就至關因而定義在實例對象
上,因此也屬於實例方法,cleanThebody
會執行打印出'我會用唾液清潔身體'
使用了static
定義的屬性和方法爲靜態屬性和方法,並不存在於實例上,因此打印出undefined
和報錯
actingCute
使用了static
修飾符,因此它是靜態方法,存在於Cat
這個類上,所以它裏面的this
指向這個類,而且執行了'一聽到貓我就想到了它會賣萌'
descript
使用了static
修飾符,因此它是靜態屬性,打印出'我這個類是用來生產出一隻貓的'
Cat.staticName = 'staticName'
就至關於定義了一個靜態屬性,因此打印出staticName
咱們再來看看這道題,友情提示,這是個坑 🤮...
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
複製代碼
坑二 🤮...
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
上的type
爲class
type
的時候,發現getType
函數中並無這個變量,因此就向外層查找,找到了window
中存在這個變量,所以打印出window
。(var type = 'constructor'
是函數constructor
中的變量, 你也能夠理解爲是constructor
函數的私有變量)既然作到了函數類型的題目,那怎麼能不想到箭頭函數呢?嘿嘿 。
陰笑~
讓咱們將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
題,裏面介紹了構造函數對象中普通函數和箭頭函數的區別。
若是在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
裏定義的。
那麼,原型對象中相同名稱的屬性和方法呢?
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
題。
好吧 😅,如今能夠加大難度了:
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
的時候,它會默認調用constructor
這個函數,來接收一些參數,並構造出一個新的實例對象(this
)並將它返回。class
沒有定義constructor
,也會隱式生成一個constructor
方法(二) class中幾種定義屬性的區別::
在constructor
中var
一個變量,它只存在於constructor
這個構造函數中
在constructor
中使用this
定義的屬性和方法會被定義到實例上
在class
中使用=
來定義一個屬性和方法,效果與第二點相同,會被定義到實例上
在class
中直接定義一個方法,會被添加到原型對象prototype
上
在class
中使用了static
修飾符定義的屬性和方法被認爲是靜態的,被添加到類自己,不會添加到實例上
(三) other:
class
本質雖然是個函數,可是並不會像函數同樣提高至做用域最頂層class
中箭頭函數等題目請參照構造函數來處理class
生成的實例對象,也會有沿着原型鏈查找的功能知識無價,支持原創。
參考文章:
你盼世界,我盼望你無bug
。這篇文章就介紹到這裏,讓咱們先打好面向對象的基礎,才能挑戰後面的魔鬼
題目 😄。(才能繼承家產,哈哈哈)
系列中的繼承
和多態
霖呆呆也會在近幾天給更出來,請期待一小下吧 😄。
最後,正經的給個三連吧 😂。
喜歡霖呆呆的小夥還但願能夠關注霖呆呆的公衆號 LinDaiDai
或者掃一掃下面的二維碼👇👇👇.
我會不定時的更新一些前端方面的知識內容以及本身的原創文章🎉
你的鼓勵就是我持續創做的主要動力 😊.
相關推薦:
《【建議星星】要就來45道Promise面試題一次爽到底(1.1w字用心整理)》