JavaScript中的原型、原型鏈、原型模式

打遊戲也要有夢想發表於2021-01-17

今天,我們來聊聊JavaScript中的原型跟原型鏈

原型跟原型模式

這一塊的知識,主要是設計模式方面的。
首先,我們知道JavaScript是物件導向的。既然是物件導向,那它自然也有相應的類跟物件等概念。
在JavaScript中,function這個東西還是比較特殊的,它既能用來宣告方法,還能用來宣告一個類似C#/.NET中的類,然後new一下得到一個物件。
舉例

//js中的function使用方式一:
function testFunc() {
    cosnole.log(123456);
}
testFunc();//123456;

//js中的function使用方式二:在這裡用到了建構函式模式
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        alert(this.name);
    }
}
var person1 = new Person('qwe', 26);//function還可以new一個出來,得到一個物件。
person1.sayName();

console.log(person1.constructor === Person);//true

var person2 = new Person('fgh', 26);
person2.sayName();
console.log(person1.sayName === person2.sayName);//false,這裡是個重點,person1跟person2的sayName方法是不一樣的,是各自的方法

//方式二還可以這樣用
Person('qwe', 26);
window.sayName();

思考:
既然person1.sayName===person2.sayName返回false,兩個物件person1和person2各自的sayName方法雖然本身不同,實現的效果是一樣的,那可不可以讓每個物件呼叫的是同一個公共的方法sayName呢?答案是可以的。
這裡有個優化方案

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = sayName;
}
function sayName() {
    alert(this.name);
}
var person1 = new Person('qwe', 26);
var person2 = new Person('jkl', 26);
console.log(person1.sayName === person2.sayName);//true    這裡是呼叫的公共的方法,不是各自自己的方法

以上的方案的確實現了想要的結果,但我們知道JavaScript是物件導向的,物件導向的三要素有個封裝。但以上的方案並沒有體現出封裝的思想。更好的方式是通過原型模式來解決。

function Person() {
}
Person.prototype.name = "uip";//這裡的Person是屬性,不是建構函式,這個Person還有個屬性prototype,原型模式便是通過這個來實現的
Person.prototype.age = 26;
Person.prototype.sayName = function () {
    alert(this.name);
}
//另外一種寫法
Person.prototype = {
    name: 'uio',
    age = 26,
    sayName = function () {
        alert(this.name);
    }
}

var person1 = new Person();
person1.sayName();

以上便是原型模式,也並非沒有缺點,雖然不用在建構函式裡面傳遞引數來初始化,但得到的物件屬性都是一樣的。這便是最大的問題。
怎麼辦呢?
建構函式模式可以通過建構函式來傳遞引數進行初始化,原型模式可以共享某些屬性跟方法。那可不可以合二為一,將兩者結合起來,豈不更好?
二者結合,程式碼如下:

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype = {
    sayName: function () {
        alert(this.name);
    }
}
var person1 = new Person('sdaf', 26);
person1.sayName();
var person2 = new Person('ssdf', 26);
person2.sayName();
console.log(person1.sayName === person2.sayName);//true,看到這裡,相信便會體會到這個模式精妙之處了吧。sayName是共有的方法,大家共享。

原型鏈的問題

以下三句話特別重要,需要深刻理解。
1、每個函式都有個prototype屬性,prototype是函式獨有的屬性。
2、每個物件都有個__proto__屬性。__proto__是物件獨有的屬性。
2、每個函式的portotype的是Object的例項
在JavaScript中,繼承便是通過原型鏈實現的。

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype = {
    sayName: function () {
        alert(this.name);
    }
}
var person1 = new Person('sdaf', 26);

console.log(person1.__proto__ === Person.prototype);//true
console.log(Person.prototype.__proto__ == Object.prototype);//true   
//在這裡說明一下,Person的基類是Object,Person的prototype是Object的例項,所以是個物件,它有__proto__這個屬性,而這個屬性等價於Object的prototype屬性。
//這樣一環扣一環,構成了一道鏈,便是所謂的原型鏈
console.log(Ojbect.prototype.__proto__ === null);//true

實戰

既然明白以上的知識,怎麼優雅地運用到實際工作中呢?
在搞Vue專案時,我們幾乎不可避免地會通過EventBus進行元件通訊。
每次都需要var bus=new Vue();
在這裡便可以通過原型模式來優化。

Vue.prototype.$bus = new Vue(); // EventBus用於無關係元件間的通訊。

在其他元件,便可以直接通過this.$bus釋出訂閱事件了。

相關文章