ES5模擬實現面向對象的三大特徵:封裝、繼承、多態

第一次嘗試寫技術文章,文筆比較渣。內容不是不少,但仍是花費了很多時間。javascript

先前去某互聯網公司面試,面試官問能不能說一下怎樣用ES5模擬實現面向對象的三大特徵:封裝、繼承、多態,記得當時只是balabala的說了一下js繼承的6種方式。java

js繼承的6種方式:

  1. 原型鏈繼承
  2. 構造函數繼承
  3. 組合繼承
  4. 原型式繼承
  5. 寄生式繼承
  6. 寄生組合式繼承

最近有時間,因而就抽空大概封裝整理了一下...面試

關於js的繼承,網上有不少大佬寫的文章,這裏就再也不贅述了。數據結構

下面會用到寄生組合式繼承的方式來封裝咱們的Class方法,首先我認爲有兩點是須要考慮的:app

一、命名空間namespace的問題函數

就是我聲明的這個類須要掛載到哪一個父級下面,就像java裏的輸入流類InputStream、輸出流類OutputStream 都是位於java.io.*包(package)下的,日期類Date、數據結構類HashMap/HashSet 都是位於java.util.*包(package)下的ui

二、實現相似ES6中在子類構造函數裏面調用父類的方法(super)this

二話很少說,直接上代碼spa

1、實現Namespace函數:
/** * 掛載Class到指定的命名空間下 * @param { String } namespace 以點鏈接的字符串 * @param { Function } Ctor 被掛載的Class * @param { Object } rootNamespace 頂級命名空間 */
    let Namespace = function(namespace, Ctor, rootNamespace) {
        let nsName, nsNames = namespace.split("."),
            max = nsNames.length - 1,
            index = 0;
        if (!rootNamespace) try {
            if (!new RegExp("^[a-zA-Z_$][a-zA-Z0-9_$]*$").test(nsNames[0])) 
            	throw "";
            rootNamespace = new Function("return " + nsNames[0])(), index = 1
        } catch (i) {
            rootNamespace = window
        }
        for (; max > index; index++) 
        	nsName = nsNames[index], rootNamespace[nsName] || (rootNamespace[nsName] = {}), rootNamespace = rootNamespace[nsName];
        rootNamespace[nsNames[max]] || (rootNamespace[nsNames[max]] = Ctor)
    };
複製代碼
2、實現相似call super,須要添加一個默認的頂級父類SuperClass
let SuperClass = function() {},
        _proto_ = new Object;
    _proto_.superclass = Object, _proto_.__NAME__ = "Object", _proto_.superinstance = new Object;
    _proto_.callsuper = function(a) {
        let self = this;
        // 傳入a爲父類的方法名
        if (this._realsuper = this._realsuper ? this._realsuper.prototype.superclass : this.superclass, "string" == typeof a) {
            let args = Array.prototype.slice.call(arguments, 1);
            self._realsuper.prototype[a].apply(self, args)
        } else {
            let args = Array.prototype.slice.call(arguments, 0);
            self._realsuper.apply(self, args)
        }
        this._realsuper = null // 調用後置空,便於下次再次調用 
    }, SuperClass.prototype = _proto_;
複製代碼
3、Class方法封裝
let Class = function(className, oArgs) {
        let ns = oArgs.ns && oArgs.ns + "." + className;
        if (ns) try {
            let clazz = new Function("return " + ns)();
            if (clazz) return clazz // 存在則直接返回
        } catch (g) {}

        let superClass = oArgs.extend || SuperClass,
            fn = function() {},
            plugins = oArgs.plugins || [];
        fn.prototype = superClass.prototype;
        let construct = oArgs.construct || function() {},
            properties = oArgs.properties || {},
            methods = oArgs.methods || {},
            statics = oArgs.statics || {},
            _proto_ = new fn;
        for (let property in _proto_) _proto_.hasOwnProperty(property) && delete _proto_[property];
        for (let prop in properties) _proto_[prop] = properties[prop];
        for (let methodName in methods) _proto_[methodName] = methods[methodName];
        for (let index = 0; index < plugins.length; index++) {
            let plugin = plugins[index];
            for (let p in plugin) _proto_[p] = plugin[p]
        }
        _proto_.constructor = construct, _proto_.superclass = superClass, _proto_.superinstance = new fn, _proto_.__NAME__ = className, construct.prototype = _proto_;
        for (let p in statics) construct[p] = statics[p];
        return ns && Namespace(ns, construct), construct
    };
複製代碼

至此,封裝完成prototype

調用方式以下:
let myClass = Class("Test", {
    ns: "", // 命名空間
    extend: SuperClass, // 父類、可選
    plugins: [], // 插件類
    construct: function(e) { // 構造函數,能夠在這裏執行callsuper
    	...
        this.callsuper(e)
    },
    statics: {}, // 靜態屬性和方法
    methods: {}  // 實例屬性和方法
});
複製代碼
相關文章
相關標籤/搜索