1. 程式人生 > >JavaScript 面向物件程式設計實現

JavaScript 面向物件程式設計實現

JavaScript 面向物件程式設計實現

本文主要講述js面向物件的實現方式(繼承)

面向物件本來就是把某些問題(邏輯),用物件的方式描述,使得業務邏輯能更加清晰,提高維護性,降低實現複雜度。

面向物件的三大特徵:封裝,多型,繼承。(JavaScript 是沒有多型的特性的)。JavaScript的面向物件使用prototype實現的,與其他面嚮物件語言非常不一樣。
實現JavaScript的繼承,

屬性繼承

call() 方法

function subClass(){
    this.food = 'apple'
    superClass.call(this);
    console.log(this.action+ ' a ' +this.food );
}

function superClass(){
    this.action = 'eat'
}

var  shili = new subClass();// eat a apple

apply() 方法

function subClass(){
    this.food = 'apple'
    superClass.apply(this);
    console.log(this.action+ ' a ' +this.food );
}

function superClass(){
    this.action = 'eat'
}

var  shili = new subClass();// eat a apple

這種方法相當於呼叫superClass 的構造器,並且使superClass this 指向 subClass 的this,這樣subClass內部屬性就會有 superClass的屬性。

這種方式繼承來的方法,是靜態的,只要subClass 例項化後,父類的改動是不會影響子類的。

原型繼承

ES5原型物件關係圖

這裡寫圖片描述

原型鏈

prototype 是JavaScript 獨特的繼承方法,用另一種方式詮釋面向物件。
那麼他有什麼特別之處呢?

  1. java語言特性沒有多型的概念。
  2. 繼承的原型是動態的,共享的。
  3. 沒有私有屬性

下面是程式例子

function superClass(){
    this.say = function(){
        console.log('i m subclass');
    }
}

function subClass(){
    this.say = function(){
        console.log('i m subclass');
    }
}

subClass.prototype = new superClass();
subClass.prototype.constructor = subClass;

這是非常常用的方法,對於javascript 來說,沒有繼承的標準,所以繼承的方式可以說是百花齊放,我個人是覺得 prototype的寫都不太優雅,所以對於我來說,es5的繼承方式都是大同小異的,實際上也是這樣的。

混合方式

在某些時候,我們需要傳入引數到父類 ,這種需求在 call /apply 上很輕鬆實現到,但用原型鏈就沒法實現了,怎麼辦呢? 一起用就好了!
(W3school 中的例子)

function ClassA(sColor) {
this.color = sColor;
}

ClassA.prototype.sayColor = function () {
    alert(this.color);
};

function ClassB(sColor, sName) {
    ClassA.call(this, sColor);
    this.name = sName;
}

ClassB.prototype = new ClassA();

ClassB.prototype.sayName = function () {
    alert(this.name);
}; 

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();    //輸出 "blue"
objB.sayColor();    //輸出 "red"
objB.sayName(); //輸出 "John"

ES6 繼承

雖然說ES6只是語法糖,但是ES6確實擁有一些ES5不一樣的東西,雖然ES5同樣能實現。

ES6的繼承本質上是沒有離開原型繼承的。但是在extends關鍵字 在處理 子父類的時候會跟ES6會有不一樣,下圖是關於ES6 父子類之間的關係圖,讀者可以與ES5的原型鏈繼承關係圖對比一下。

這裡寫圖片描述

我們在關係圖中能發現,2圖只有 父類與子類之間關係發生了變化,子類的__proto__ 指向了父類,這樣給我們帶了一個小特性:子類可以繼承父類的靜態方法/屬性,這種小特性在封裝元件的過程是非常有用的,類的概念會更加清晰(感覺就很打臉,當初為何要搞原型呢?)

這裡有一段 Babel 翻譯ES6的程式碼:

ES6 (語法也就是這個樣子)

class Father{
    constructor(name, age){
        this.name = name;
        this.age = age;
    }

    show(){
        console.log(`我叫:${this.name}, 今年${this.age}歲`);
    }
};
class Son extends Father{};

let son = new Son('金角大王', 200);
son.show();//return 我叫:金角大王, 今年200歲

翻譯過程:

"use strict";

var _createClass = function () {
    function defineProperties(target, props) {
        for (var i = 0; i < props.length; i++) {
            var descriptor = props[i];
            descriptor.enumerable = descriptor.enumerable || false;
            descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true;
            Object.defineProperty(target, descriptor.key, descriptor);
        }
    }
    return function (Constructor, protoProps, staticProps) {
        if (protoProps) defineProperties(Constructor.prototype, protoProps);
        if (staticProps) defineProperties(Constructor, staticProps); return Constructor;
    };
}();

function _possibleConstructorReturn(self, call) {
    if (!self) {
        throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }
    return call && (typeof call === "object" || typeof call === "function") ? call : self;
}

function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
        throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
    }
    subClass.prototype = Object.create(
        superClass && superClass.prototype,
        {
            constructor: { value: subClass, enumerable: false, writable: true, configurable: true }
        }
        );
    if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }


var Father = function () {
    function Father(name, age) {
        _classCallCheck(this, Father);

        this.name = name;
        this.age = age;
    }

    _createClass(Father, [{
        key: "show",
        value: function show() {
            console.log("我叫:" + this.name + ", 今年" + this.age + "歲");
        }
    }]);

    return Father;
}();

;

var Son = function (_Father) {
    _inherits(Son, _Father);

    function Son() {
        _classCallCheck(this, Son);

        return _possibleConstructorReturn(this, Object.getPrototypeOf(Son).apply(this, arguments));
    }

    return Son;
}(Father);

參考資料:
W3school
阮一峰ECMAScript入門;
《JavaScript權威指南》 作者:Flanagan
某文章(記得看評論區,評論區有指出錯誤)