回到文章列表

[JS201 筆記] 物件導向基礎與 prototype

TWGD / 2019-01-07

這是 Lidemy 課程:[JS201] 進階 JavaScript:那些你一直搞不懂的地方:物件導向基礎與 prototype 課程筆記。

如果有人不小心看到,請不要太嚴格,這只是我上課的隨筆筆記XD 若有錯誤,請不要吝嗇糾正我,謝謝~


ES6 的物件導向範例:

ES6 才有 class 這個關鍵字。

class Dog { // 名稱開頭通常大寫

    // 建構子,作為初始化
    constructor(name) {
        this.name = name;
    }

    // setter 用來設置值
    setName(name) {
        this.name = name;
    }

    // getter 用來存取值
    getName() {
        return this.name;
    }

    sayHello() {
        console.log('hello' + this.name)
    }
}


let cat = new Dog('cat'); // cat 是 Dog 的 instance
cat.getName();
cat.sayHello();

ES5 的物件導向:

function Dog(name) {
    var myName = name;

    return {
        getName: function() {
            return myName;
        },
        sayHello: function() {
            console.log('hello' + myName);
        }
    }
}

var cat = Dog('cat');
cat.getName();
cat.sayHello();

var cat2 = Dog('cat2');
cat2.getName();
cat2.sayHello();


但是以上會有一個問題:cat.getName() !== cat2.getName()。 每創建一個 instance,就要耗費一個記憶體空間。 因此改用以下的寫法:


// 等同於建構子的作用
function Dog(name) {
    this.name = name;
}

// 設定 method
Dog.prototype.getName = function() {
    return this.name;
}

Dog.prototype,sayHello = function() {
    console.log('hello' + this.name)
}

// new 創建 Dog 的 instance
var cat = new Dog('cat');
console.log(cat);  // Dog {name: 'cat'}
cat.sayHello(); // hello cat

var cat2 = new Dog('cat2');
console.log(cat2);  // Dog {name: 'cat'}
cat2.sayHello(); // hello cat2


console.log(cat.sayHello === cat2.sayHello) // true

從 prototype 來看「原型鍊」

與 javascript 提供的屬性 __proto__ 有關:instance 透過 __proto__ 屬性得以連結 prototype

從以下例子理解 prototype chain 原型鍊:

function Dog(name) {
    this.name = name;
}

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

Dog.prototype.sayHello = function() {
    console.log('hello ' + this.name)
}

var cat = new Dog('cat');
var cat2 = new Dog('cat2');

console.log(cat.__proto__ === Dog.prototype) // true
console.log(cat.__proto__.__proto__ === Object.prototype) // true
console.log(cat.__proto__.__proto__.__proto__ === null) // true

cat.sayHello()
/*
prototype chain 原型鍊
呼叫 cat.sayHello() 的時候,找 sayHello() 的順序
1. cat 身上有沒有 sayHello
2. cat.__proto__ 有沒有 sayHello
3. cat.__proto__.__proto__ 有沒有 sayHello
4. cat.__proto__.__proto__.__proto__ 有沒有 sayHello
5. null (找到頂了)
*/

// 順便看一下 Dog 的 __proto__ 是什麼
console.log(Dog.__proto__ === Function.prototype) // true

prototype 的其他應用:

// 自訂 method 讓其他 string 使用
String.prototype.first = function() {
    return this[0]
}

var a = '123';
console.log(a.first()); // 1

new 底層做了什麼

先有 call() 的基本觀念:

function test() {
    console.log(this);
}

// call 裡面傳什麼東西,this 就會是什麼
test.call('123'); // [String: '123']

以下實作 new 的原理:

function Dog(name) {
    this.name = name;
}

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

Dog.prototype.sayHello = function() {
    console.log('hello ' + this.name)
}

var cat = new Dog('cat');

/*
實作一個 function newDog(){...},來執行以下程式碼:
var cat2 = newDog('cat2');
cat2.sayHello();
*/

function newDog(name) {
    var obj = {}; // 產生一個 obj
    Dog.call(obj, name); // call constructor 完成初始化物件
    obj.__proto__ = Dog.prototype; // 把 obj 關聯到 Dog 的 prototype
    return obj;
}

物件導向的繼承 Inheritance

繼承用在哪裡:若有共同的屬性可以直接繼承上層的,不用重複寫。

  • 注意 super() 的用法。
  • class Dog {
    
        constructor(name) {
            this.name = name;
        }
    
        sayHello() {
            console.log('hello ' + this.name)
        }
    }
    
    class BlackDog extends Dog {
        constructor(name) {
            // super() 呼叫上層得已初始化。強制使用 super()。傳參數進去給上層的 constructor 使用。
            super(name); // Dog 的 constructor。
            this.sayHello(); // 初始化的時候就 say Hello
        }
    
        test() {
            console.log('test:', this.name)
        }
    }
    
    const black = new BlackDog('black'); // hello black
    black.test();  // test: black