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

今天,咱來聊聊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發佈訂閱事件了。