JavaScript OOP

Date: 2019/10/20

Lecturer: 土豆

大綱

  • OOP介紹
  • Class實作
  • Prototype
  • JavaScript如何實現Class
  • Labs

OOP介紹

簡介

OOP = Object Oriented Programming

物件導向

Classes & Objects

Class

Object

兩者的關係

設計圖

實際的東西

實例化

Classes & Objects

Attributes

Methods

Class的組成

Classes & Objects

Class

學生

舉個例子

Object

小明

Attributes

Name

Age

Weight

Height

Methods

Walk

Eat

Sleep

Attributes

Name: 小明

Age: 20

Weight: 77

Height: 188

Methods

Walk

Eat

Sleep

Encapsulation

分工合作,各司其職

Object A

Object B

Public

Attritube 1

Attritube 2

Method 1

Method 2

Private

Attritube 1

Method 1

Public

Attritube 1

Attritube 2

Method 1

Method 2

Private

Attritube 1

Method 1

Interface

Getter, Setter, Statics

Getter

讓別的Object取得自己的Property的Interface

Setter

讓別的Object可以設定自己的Property的Interface

Static

不需要實例化Class即可直接使用

Inheritance

站在巨人的肩膀上

Class

學生

Attributes

Name

Age

Weight

Height

Methods

Walk

Eat

Sleep

Class

好學生

Class

壞學生

Methods

Study

Methods

Fight

Overriding

騎在你爸爸頭上

Class

學生

Methods

Walk (正常走)

Eat (一般吃法)

Sleep (一般睡法)

Class

懶學生

Methods

Walk (一搖一擺)

Eat (狼吞虎嚥)

Sleep (呼呼大睡)

一樣名稱

不同動作

Class實作

十分地簡單

class Rabbit {
  constructor(type) {
    this.type = type;
  }
  speak(line) {
    console.log(`The ${this.type} rabbit says '${line}'`);
  }
}

let killerRabbit = new Rabbit("killer");
let blackRabbit = new Rabbit("black");

拆成兩部分來看

constructor()

methods

先來看看this是在this什麼

this.type指的是"實例出"的object裡面叫做type的property

//let type = "what??";

class Rabbit {
  constructor(type) {
    this.type = type;
  }
  speak(line) {
    console.log(`The ${type} rabbit says '${line}'`);
  }
}

let killerRabbit = new Rabbit("killer");
console.log(killerRabbit.type)
killerRabbit.speak();

刪掉this看看,並試著在class外定義一個type

constructor

constructor裡面的東西會在新建object時跑一次

class Apple {
  constructor() {
    console.log("An apple a day keeps doctor away.");
  }
}

let a = new Apple();

methods

你可以這樣在class中定義method

class Apple {
  constructor() {
    console.log("An apple a day keeps doctor away.");
  }
  run() {
    console.log("Your apple keeps you away.");
  }
}

let a = new Apple();
a.run();

咦? 那attribute去哪了?

現在辦不到,以後可能辦的到

你現在只能在constructor或是methods裡面定義

Inheritance

class Apple {
    constructor() {
        console.log("You create an apple!");
    }
    run() {
        console.log("Your apple run away.");
    }
}

class PoisionApple extends Apple {
    constructor() {
        super();
        this.type = "Poision";
        console.log("This is a poision apple!");
    }
}

let a = new PoisionApple();

透過extends來繼承

super()代表呼叫父類別的constructor

overriding

// class Apple ...

class PoisionApple extends Apple {
    constructor() {
        super();
        this.type = "Poision";
        console.log("This is a poision apple!");
    }
    run() {
        console.log("Your apple runs away, and kill a potato with poision.");
    }

}

let a = new PoisionApple();
a.run();

在子類別中定義一樣名稱的method來覆蓋父類別的method

setter, getter

class Apple {
    constructor() {
        this.first_name = "";
        this.last_name = "";
        console.log("You create an apple!");
    }
    run() {
        console.log("Your apple run away.");
    }

    get name() {
        return this.first_name + " " + this.last_name;
    }

    set name(str) {
        let temp = str.split(" ");
        this.first_name = temp[0];
        this.last_name = temp[1];
    }
}

let a = new Apple();
a.name = "John Doe";
console.log(a.first_name);
console.log(a.last_name);
console.log(a.name);

在method前面加上set, get

statics

class Apple {
    // ...
    static listAllSize() {
        return ["small", "medium", "big"];
    }   
}

console.log(Apple.listAllSize());

在method前面加上static

注意這邊沒有任何實例化,但是卻可以呼叫class裡面的method

相對於dynamic被實例化時才放到記憶體中

static代表一開始就在記憶體中

instanceof

class Apple {
	// ...
}

class PoisionApple extends Apple {
	// ...
}

let a = new PoisionApple();
console.log(a instanceof PoisionApple);
console.log(a instanceof Apple);

let b = new Apple();
console.log(b instanceof PoisionApple);
console.log(b instanceof Apple);

instanceof是一個operator

可以查看object是否是某一個class的實例,包含父類別

So easy, right?

Prototype

你剛剛寫的那個不是Class

JavaScript沒有Class
JavaScript沒有Class

JavaScript沒有Class

啊我剛剛寫的是甚麼?

是東拼西湊出來的假貨

害我要解釋老半天...

只是個語法糖(Syntactic sugar)

為什麼沒有Class

因為一開始的應用單純

但是這老兄,不想設計Class卻設計了繼承......

所以才有了Prototype這東西

Brendan Eich

所以什麼是Prototype

let empty = {};
console.log(empty.toString);
console.log(empty.toString());

先來跑跑看這段code

不覺得哪裡怪怪的嗎?

明明就沒有定義過toString這個Property阿

Prototype Chain

Object.prototype

Your Object

let empty = {};
console.log(Object.getPrototypeOf(empty) == Object.prototype);

為了證明我沒有唬爛你,94如此神奇

Your Another Object

console.log(Object.getOwnPropertyNames(Object.prototype));

來看看Object.prototype提供了什麼

Prototype Chain

Object.prototype

Your Object

Your Another Object

Calling toString()

Not Found

Not Found

Found!!

Call it

Prototype Chain

Object.prototype

Function.prototype

Your Function

來看看更多的chain

console.log(Object.getOwnPropertyNames(Function.prototype));

看看Function.protype提供了啥

Prototype Chain

Object.prototype

Array.prototype

Your Array

來看看更多的chain

console.log(Object.getOwnPropertyNames(Array.prototype));

看看Array.protype提供了啥

用Object當作Prototype創建另一個Object

let protoRabbit = {
	type: "none",
	speak: function(line) {
		console.log(`The ${this.type} rabbit says '${line}'`);
	}
};

let killerRabbit = Object.create(protoRabbit);
killerRabbit.type = "killer";
killerRabbit.speak("SKREEEE!");

let sleepyRabbit = Object.create(protoRabbit);
sleepyRabbit.type = "sleepy";
sleepyRabbit.speak("zzzzzzz");

Object.create()

JavaScript如何實現Class

JavaScript class = constructor function + prototype property

// constructor function
function Rabbit(type) {
  console.log("You create a new Rabbit!");
  this.type = type;
}

// prototype property
Rabbit.prototype.speak = function(line) {
  console.log(`The ${this.type} rabbit says '${line}'`);
};

// create a new Rabbit object
let weirdRabbit = new Rabbit("weird");

然後使用new去創建新的object

直接來看看怎麼做

他老兄看到Java有new,所以他也想要

阿就沒有設計Class是要new什麼?

設計概念

那就只好把function拿來new了

然後我:

來看看怎麼用function創建出object

constructor function

function Dog(name) {
    this.name = name;
    this.type = "哈士奇";
    this.speak = function() {
        console.log(`我是${this.name},我要吃罐頭`);
    }
}

let dogA = new Dog("Jack");
let dogB = new Dog("Dogy");

dogA.speak();
dogB.speak();

object的property不共用

constructor function

function Dog(name) {
    this.name = name;
    this.type = "哈士奇";
    this.speak = function() {
        console.log(`我是${this.name},我要吃罐頭`);
    }
}

let dogA = new Dog("Jack");
let dogB = new Dog("Dogy");

dogB.type = "柴犬";

console.log(dogA.type);
console.log(dogB.type);

每個object都有這個,功能一模一樣

問題來了

this.speak = function() {
  console.log(`我是${this.name},我要吃罐頭`);
}
// ...

console.log(dogA.speak === dogB.speak);

但是卻要各自佔掉一個記憶體空間

一個優秀的工程師不能忽略這件事情!!

function被建立時,會自動建立一個叫prototype的property

Prototype就可以派上用場了

let foo = function() {
  return "nothing";
}

console.log(Object.getOwnPropertyNames(foo));

這個prototype就是一個空的object

console.log(typeof(foo.prototype));
console.log(foo.prototype);

需要共用的東西就全部丟到這個prototype裡面

使用new創建新的object時,會自動繼承這個prototype

function Dog(name) {
    this.name = name;
    this.type = "哈士奇";
}

Dog.prototype.speak = function() {
    console.log(`我是${this.name},我要吃罐頭`);
}

let dogA = new Dog("Jack");
let dogB = new Dog("Dogy");

console.log(Object.getPrototypeOf(dogA) === Dog.prototype);
console.log(Object.getPrototypeOf(dogB) === Dog.prototype);
console.log(dogA.speak === dogB.speak);

再來複習一次

// constructor function
function Rabbit(type) {
  console.log("You create a new Rabbit!");
  this.type = type;
}

// prototype property
Rabbit.prototype.speak = function(line) {
  console.log(`The ${this.type} rabbit says '${line}'`);
};

// create a new Rabbit object
let weirdRabbit = new Rabbit("weird");

跟我念一遍~

真正的實現方式是 constructor function + prototype property

JavaScript的class是語法糖

Labs

Lab1: A Vector Type

利用class實作一個二維向量的type,要求如下

Properties

  • x
  • y

Methods

  • plus: 兩個向量相加
  • minus: 兩個向量相減
  • length: 取得向量的長度,這個要做成getter

Lab1: A Vector Type

// Your code here.

console.log(new Vec(1, 2).plus(new Vec(2, 3)));
// → Vec{x: 3, y: 5}
console.log(new Vec(1, 2).minus(new Vec(2, 3)));
// → Vec{x: -1, y: -1}
console.log(new Vec(3, 4).length);
// → 5

使用範例

Lab1: A Vector Type

class Vec {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    plus(vec) {
        return new Vec(this.x + vec.x, this.y + vec.y);
    }

    minus(vec) {
        return new Vec(this.x - vec.x, this.y - vec.y);
    }

    get length() {
        return Math.sqrt(this.x * this.x + this.y * this.y);
    }
}

// Your code here.

console.log(new Vec(1, 2).plus(new Vec(2, 3)));
// → Vec{x: 3, y: 5}
console.log(new Vec(1, 2).minus(new Vec(2, 3)));
// → Vec{x: -1, y: -1}
console.log(new Vec(3, 4).length);
// → 5

ANS

Lab2: Borrowing A Method

let data = {one: true, two: true, hasOwnProperty: true};

// Fix this call
console.log(data.hasOwnProperty("one"));
console.log(data.hasOwnProperty("three"));

之前我們使用過hasOwnProperty來檢查一個object當中有無某個property

現在我們把hasOwnProperty override掉了,請把它修好

Lab2: Borrowing A Method

let data = {
    one: true,
    two: true,
    hasOwnProperty: function(str) {
        return Object.prototype.hasOwnProperty.call(this, str);
    }
}

console.log(data.hasOwnProperty("one"));
console.log(data.hasOwnProperty("three"));

ANS

Lab3: Stack

請利用class實作一個stack的class,要求如下

Properties

  • items: 一個array代表stack中的值

Methods

  • push
  • pop
  • peek: 印出最頂端的值
  • isEmpty: 這個stack是否為空
  • size: getter

Lab3: Stack

// Your code...

let s = new Stack();
console.log(s.items);
// []
s.push(1);
s.push(2);
console.log(s.pop());
// 2
console.log(s.isEmpty());
// false
s.push("a");
s.push(true);
console.log(s.items);
// [1, 'a', true]
console.log(s.size);
// 3

使用範例

Lab3: Stack

class Stack {
    constructor() {
        this.items = [];
    }

    push(item) {
        this.items.push(item);
    } 

    pop() {
        return this.items.pop();
    }

    peek() {
        return this.items[this.items.length - 1];
    }

    get isEmpty() {
        if(this.items.length == 0) {
            return true;
        } else {
            return false;
        }
    }

    get size() {
        return this.items.length;
    }
}

let s = new Stack();
console.log(s.items);
// []
s.push(1);
s.push(2);
console.log(s.pop());
// 2
console.log(s.isEmpty);
// false
s.push("a");
s.push(true);
console.log(s.items);
// [1, 'a', true]
console.log(s.size);
// 3

ANS

感謝聆聽

JavaScript OOP

By Sam Yang

JavaScript OOP

  • 756