JS 的 Object、new、class、prototype
JS 的物件導向
Object.create(null)
設定一個創建英雄模組
今天想創造一個英雄模組(之後可以快速生成一個英雄),因此我們用Object.create來處理(不過目前是還沒加上參數的)
> // 創造英雄的fc
> function heroCreator(profess, skill) {
> // 創造新的英雄,是否要參考某個物件(ex. 我要讓英雄都有共通技能)
> // 如果沒有要參考,就給一個空物件就好
> const hero = Object.create(null)
>
> // 新增英雄的屬性(可以隨便加)
> // 這邊選擇的加法是,直接把傳進來的參數變成英雄的屬性
> hero.profess = profess
> hero.skill = skill
>
> // 為英雄物件加一個技能
> hero.bye = function() {
> console.log("byeeeeeee");
> }
>
> // 把使用fc的結果傳出來
> return hero
> }
如果直接使用fc、帶引數進去的話,會得到下面這一串
> console.log(heroCreator(123, 46456));
> [Object: null prototype] {
> profess: 123,
> skill: 46456,
> bye: [Function (anonymous)]
> }
> // 來用上面那個打造一個新英雄
> const warrior = heroCreator("warrior", "bigSword")
>
> // 這樣我們就有一個戰士英雄了,並且之後要打造新英雄,直接用heroCreator就好
> console.log(warrior);
>
> [Object: null prototype] {
> profess: 'warrior',
> skill: 'bigSword',
> bye: [Function (anonymous)]
> }
讓英雄模組可以繼承共通技能
不過剛剛那樣我們新創的英雄沒有繼承共有技能
所以我們稍微改一下Object.create、創造一個共通技能(新物件)
> 先創造一個所有英雄都有的技能(一個物件,裡面有兩個fc)
> const skills = {
> attack: function() {
> console.log("attack!!!!");
> },
> shield: function() {
> console.log("defense!!!!!!");
> }
> }
> function heroCreator(profess, skill) {
> const hero = Object.create(skills) -> 創造新物件時,會繼承skills能力
> hero.profess = profess
> hero.skill = skill
>
> hero.bye = function() {
> console.log("byeeeeeee");
> }
>
> return hero
> }
我們來看看新英雄有哪些技能
warrior.bye() # 自己本身有的技能
warrior.attack() # attack、shield -> 繼承自skills的技能
warrior.shield()
那我們來看看warrior怎麼繼承技能的
> warrior.__proto__ # {attack: f, shield: f}
查看warrior的原型鏈,可以發現共用技能放在上一層,所以warrior會自動可以使用共用技能
new Object
上面用Object.create(null)流程創造新物件,有沒有覺得超級麻煩?
因此我們用另一個更快、更彈性的方法 - new 新物件
> 前面一樣要先建立function,不過中間流程不太一樣
> function heroCreator(profess, skill) {
> this.profess = profess,
> this.skill = skill
> }
> Ps. 原本超級長的英雄模組,現在縮短成這樣有沒有覺得超讚的
建好模組後,我們要用這個模組來創造新的英雄了
> 創建一個鷹眼,在模組fc前,多加一個new,就可以做到跟Object.create(null)一樣的功能
> const hawkEye = new heroCreator("hawkEye", "arrow")
console.log(hawkEye) # heroCreator { profess: 'hawkEye', skill: 'arrow' }
new 物件的時候發生了啥事?
這個new會幫我們做幾件事情
- 產生一個空的物件
- {}.proto = function.prototype
- 會有一個 this 指向空物件
- 自動 return 物件
以剛剛英雄模組為例
> function heroCreator(profess, skill) {
> // this -> {} -> new的時候會有一個空物件,並且this會指向空物件
> // {}.__proto__ = heroCreator.prototype -> 再來物件的proto會 = fc的prototype
> this.profess = profess,
> this.skill = skill
> }
> const hawkEye = new heroCreator("hawkEye", "arrow") -> new 物件
–
new寫法除了比較簡短外,還多了 this 功能,this 功能之後會再開一篇解釋
–
prototype、proto是什麼
不過剛剛那個new物件,有沒有發現好像少了什麼?
沒錯,就是少了繼承的技能,也就是共通技能attack、shield沒有繼承到
那我們要怎麼用new物件來做呢?
這邊我們要先提到另外一個東西,就是prototype,還記得剛剛有提到,new物件的時候
有做幾件事,其中一件事是這個嗎?
> {}.__proto__ = heroCreator.prototype
這一句話的意思就是,把一個空物件指向一個空物件
> {}.__proto__ = {} 空物件
> heroCreator.prototype = {} 空物件
>
> ex. 當有**物件**去找__proto__的時候,請去找由heroCreator這個fc,做此物件的prototype
> 如果今天用typeof把兩個狀態印出來,會印出Object喔!!
> console.log(typeof hawkEye.__proto__) # Object
> console.log(typeof heroCreator.prototype) # Object
****** problem 為啥 heroCreator.prototype 是一個物件 heroCreator本來不是一個fc嗎?
Ans. function 自己本身就是一個物件 & prototype也是一個物件
所以物件.物件還是一個物件
******
上面這樣說可能會有點模糊,實際把兩個印出來看看
> hawkEye.__proto__ # {constructor: ƒ} -> hawkEye 的原型鏈下個節點是fc
> heroCreator.prototype # {constructor: ƒ} -> fc的prototype印出來跟hawkEye是一模一樣的東西
> hawkEye.__proto__ === heroCreator.prototype # true
–
如果今天是用瀏覽器看,把 {constructor: ƒ} 點開,可以發現裡面還有一堆東西
那些東西還可以繼續用原型鏈往下看還有哪些共同的東西寫在裡面
–
如果上面還是看不懂,我們實際來寫個範例試試
> 剛剛上面有提到,heroCreator.prototype是一個物件,那我們來為這個物件加功能
> heroCreator.prototype.good = "g123123ood"
> console.log(hawkEye.good)
> Ps. 物件狀態可以隨意加屬性,我們現在加一個.good的屬性,再加上
> 前面用new有讓兩個空物件相等的動作,所以 **鷹眼可以直接取用good屬性**
額外提醒1 - 當Object1 = Object2時,會發生的事情
> 我現在直接做兩個物件,並讓物件1 = 物件2
> let test1 = {
> bye1: "1111",
> hello1: "2222"
> }
> let test2 = {
> bye2: "33333",
> hello2: "44444"
> }
>
> test1 = test2
> console.log(test1.bye2); # "33333" -> 物件1可以直接取用物件2的屬性
額外提醒2 - prototype、proto注意事項
–
所有物件都有 proto 屬性
所有 fn 都有 prototype 屬性、__proto__屬性 => 所有 fc 的 prototype 都是空物件
(只有fn同時都有兩個屬性)
–
function.prototype = Object
了解完prototype後,我們就可以來為剛剛的鷹眼(新英雄)增加繼承技能了
先寫共同的技能(物件)
> const skills = {
> attack: function() {
> console.log("attack!!!!");
> },
> shield: function() {
> console.log("defense!!!!!!");
> }
> }
一樣以剛剛英雄模組為例
> function heroCreator(profess, skill) {
> // this -> {}
> // {}.__proto__ = heroCreator.prototype
> this.profess = profess,
> this.skill = skill
> }
> 在new物件之前,我們先把heroCreator = skills,新英雄就可以使用共有技能了!!!!
> heroCreator.prototype = skills
> const hawkEye = new heroCreator("hawkEye", "arrow")
Class
剛剛提到的Object.create、new,都是JS創造物件的方式,不過在其他語言,class的寫法反而是大宗,因此JS也有class寫法的語法糖(白話文就是,JS也可以用class格式寫法來創造物件,只是它底層還是用Object.create、new寫出來的,只是我們肉眼看不出來)
首先創造一個最上層的英雄模組(創建新英雄傳的引數,都會在這被接住)
> class Hero {
> constructor(profess, skill) {
> this.profess = profess,
> this.skill = skill;
> }
> }
再來創建英雄技能,此模組有繼承Hero的功能,所以所以新創建的英雄都可以有以下技能
> class heroCreator extends Hero { -> extends Hero 可以繼承
> attack() {
> console.log("attack!!!!");
> };
> shield() {
> console.log("defense!!!!")
> }
> }
最後創建新英雄 -> 蜘蛛人!!!!!!!!!
> const spider = new heroCreator("spider", "silk")
這時候我來把spider的原型鏈下個節點有哪些東西,印出來看看
> spider.__proto__
> 印出以下四個項目,可以發現constructor就是Hero
> attack: f attack()
> constructor: class heroCreator
> shield: f shield()
> [[Prototype]]: Object