gracekrcx / weekly-notes

4 stars 0 forks source link

[2019-10-12] 進階 JavaScript - 物件導向基礎與 prototype #9

Open gracekrcx opened 5 years ago

gracekrcx commented 5 years ago

物件導向的基礎

(這是一個箱子裡,有多個你已定義好的功能(你想要的功能),有需要你可以一直複製相同箱子的故事) (在物件導向的世界裡,不太會有直接去 call 一個 function 的情形,例如 add(),通常會是對一個物件做操作,例如:obj.add())

ECMAScript 6 中引入了類別 (class) 作為 JavaScript 現有原型程式(prototype-based)繼承的語法糖。

類別語法並不是要引入新的物件導向繼承模型到 JavaScript 中,而是提供一個更簡潔的語法來建立物件和處理繼承。 (JavaScript classes, introduced in ECMAScript 2015, are primarily syntactical sugar over JavaScript's existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.)

ES6 class

// class 像是一個設計圖
class Dog {
 setName(name){
  this.name = name // 這裡的 this 就是呼叫他的人,操控這個 instance 的人,在這裡就是 d
  console.log('this-->',this) // Dog {name: "PooBoo"}
 }
 sayHello(){
  console.log(this.name)
 }
}

var d = new Dog() // instance : 用 new 這個關鍵字去實體化
d.setName('PooBoo')
d.sayHello()
var d2 = new Dog()
console.log(d.sayHello() === d2.sayHello()) // true

ES6 class - Constructor

class Dog {
 // 在 new 的時候,其實就是在 call 這個 constructor 
 // 在被 new 時就會 call 這個 function 所以很適合做一些初始化的事情
 constructor(name) {
  this.name = name
 }
}

var d = new Dog('aaa') // new 時帶入參數

ES5 的 class 做了什麼

  1. 用一個 function 去實作 constructor
  2. 用 prototype 去實作 method

♥♥♥ ES5 裡可以把一個 function 當作一個 constructor 用,new 一個 function 產生一個 instance,背後的機制 javascript 會做好

---> 屬性,method 都放在 constructor

// 這個 function 就是 constructor
function Dog(name) {
 this.name = name
 this.getName = function() {
  return this.name
 }
}
var b = new Dog('FooPan')
var d = new Dog('BooPoo')
console.log( b.getName == d.getName)
// false,如果這樣寫 new 幾個 Dog 就會有幾個 getName function
// 因為 function 記憶體位置是不一樣的
// 比較好的寫法是 Dog.prototype.getName 

// 使用 prototype 寫法
function Dog(name) {
 this.name = name
 }
}
// 在 Dog 的 prototype 上加一個 function
Dog.prototype.getName = function(){
 return this.name
}
Dog.prototype.sayHello = function(){
 console.log(this.name)
}

var b = new Dog('FooPan')
var d = new Dog('BooPoo')
console.log(d) // d 就是 Dog 的 instance

console.log(b.getName === d.getName) // true

// d.__proto__ 是什麼
{getName: ƒ, sayHello: ƒ, constructor: ƒ}

// Dog.prototype(d.__proto__) 有 method and constructor
console.log(d.__proto__ === Dog.prototype) // true
console.log(Dog.__proto__ === Function.prototype) // true

instance 和 prototype 之間是如何串連的

  1. 透過屬性 __proto__
  2. 在 new 一個 instance 時,javascript 就幫你加了一個屬性 __proto__
    soo.__proto__ === Dog.prototype  // true

從 prototype 來看「原型鍊」__proto__

instance 身上有 getName (但這樣就會有一直重複複製相同的 function 的問題)

結論:

  1. 會先找 instance 裡有沒有 getName()
  2. 如果沒有才會去 proto

function Dog(name) {
 this.name = name
 this.getName = function() {
  return 'MyName'
 }
}

Dog.prototype.getName = function(){
 return this.name
}

var soo = new Dog('Soo')
console.log(soo.getName())
// 'MyName'

d 的 instance 身上沒有 sayHello,所以往上找 proto

prototype chain 原型練

d.sayHello() 查找順序

  1. d 身上有沒有 sayHello
  2. d.__proto__ 有沒有 sayHello
  3. d.__proto__.__proto__ 有沒有 sayHello
  4. d.__proto__.__proto__.__proto__ 有沒有 sayHello // null
  5. null 出現代表已經是最上層了
d.sayHello()

d.__proto__ === Dog.prototype
d.__proto__.__proto__ === Object.prototype  // 更上層
Dog.prototype.__proto__ === Object.prototype  // true

// 所以也可以在 Object 上設定 sayHello

Object.prototype.sayHello = function(){
 console.log('Object', this.name)
}

prototype prototype

string.toUpperCase

透過 __proto__這個屬性,可以知道 s.toUpperCase 就是 String.prototype.toUpperCase() 這個 function

var s = 'abcd'
var newS = s.toUpperCase() 

s.toUpperCase === String.prototype.toUpperCase //true
s.__proto__ === String.prototype // true
s.__proto__.toUpperCase() === String.prototype.toUpperCase() // true

new 做了什麼事?

  1. 產生一個新的 object
  2. 執行 constructor,完成物件初始化,有了物件
  3. __proto__ 產生關聯
// 產生一個新的 Object

function Dog(name) {
 this.name = name
 this.getName = function() {
  return name
 }
}

var soo = new Dog('Soo')
{
 name : 'Soo'
 getName : function(){ return name }
}
function test() {
 console.log(this)
}
test.call(true)  // 第一個參數就會是 this
function Dog(name){
 this.myName = name
}

Dog.prototype.sayHello = function(){
 console.log(this.myName)
}

var booPoo = newDog('booPoo') // newDog 要做 new 做的事情
booPoo.sayHello() // booPoo

// new 在做什麼
function newDog(name){
 var obj = {} // 產生一個新的 object

 Dog.call(obj, name) // 執行 constructor
 console.log(obj) // {myName: "booPoo"}

 obj.__proto__ = Dog.prototype // 用 __proto__ 產生關聯
 return obj
}

物件導向的繼承:Inheritance

繼承就像人會遺傳父母的DNA

  1. 有共同的屬性時,就不用重複做一樣的東西
  2. 父母有的東西,你都可以用
  3. javascript 裡強制在用 this 前要先 call super()
class Dog {
 constructor(obj){
  console.log('1-->')
  this.name = obj.name
  this.age = obj.age
 }

 sayHello(){
  console.log(this.name)
 }
}
// BlackDog 繼承 Dog,Dog有的屬性 BlackDog 都可以用
class BlackDog extends Dog {
 // 沒有 constructor 所以他會去找他父層的 constructor 
 test(){
  console.log('test!', this) // BlackDog {name: "Black", age: undefined}
 }
}

const pooBoo = new BlackDog({name : 'Black'})
pooBoo.test()

BlackDog 自己加上 constructor

class Dog {
 constructor(obj){
  this.name = obj.name
  this.age = obj.age
 }

 sayHello(){
  console.log(this.name)
 }
}

class BlackDog extends Dog {
 constructor(obj){
  super(obj) 
  // 這裏 call super() 就是在 call 父層的 constructor
  // call Dog.constructor,強制這裡一定要 call super() 
  // 去避免沒有初始化就直接用 this call function
  this.sayHello()
 }
 test(){
  console.log('test!', this) // BlackDog {name: "Black", age: undefined}
 }
}

const pooBoo = new BlackDog({name : 'Black'})
pooBoo.test()

參考文章

Classes

gracekrcx commented 4 years ago

我覺得的物件導向開發

是一些功能的模組,變得比較有規範

例如:一個錢包

需要的功能有

  1. 存錢
  2. 領錢
  3. 查詢戶頭裡的餘額

例如:會員個人資料模組

需要的功能有

  1. 上傳照片
  2. 新增個人資料
  3. 修改個人資料
  4. 查詢會員點數

例如:一隻狗

狗應該會有的行為,應該每隻狗都會有

  1. 會跑
  2. 會叫
  3. 會翻滾
gracekrcx commented 4 years ago

在 ES6

可以使用 class 語法去寫物件導向

在 ES5

使用 function 當作 constructor 搭配 prototype 語法去寫物件導向