天天看點

javascript模拟類的最佳實踐

1:如何模拟一個類

在sencha touch2 系列裡面定義一個類和new出這個類的對象

Ext.define(
	"Animal", {
		config: {
			name: null
		},
		constructor: function(config) {
			this.initConfig(config);
		},
		speak: function() {
			console.log('說點什麼');
		}
	}
)

var my=Ext.create("Animal",{name:"bb"})
my.speak();
           

上面代碼裡面constructor在create的時候會自動調用,然後初始化config對象配置的屬性。constructor完全就像面對象裡面的構造函數……

下面我模拟一下

// 在sencha中new一個對象傳了兩個參數Ext.create("Animal",{name:"bb"})
// 這裡就不模拟sencha的命名空間了,是以生成該類的對象的時候傳一個配置對象即可
// 把命名空間(mss)和命名單獨提取出來,new mss.define({});
var mss = {} //建立一個命名空間
mss.define = function(config) {
	if(typeof config !== 'object') {
		console.log('參數錯誤');
		return;
	}
	var interface = function() { //當new 該define傳回的函數,會自動執行atrr和init
		this.attr && this.attr();
		this.init && this.init.apply(this, arguments);
	}
	for(var i in config) {
		config.hasOwnProperty(i) &&(interface.prototype[i] = config[i]);
	}
	return interface;
}

var Car = mss.define({
	attr: function() {
		this.type = '汽車';
	},
	init: function() {
		console.log(this.type);
	},
	speank: function() {
		console.log('我是' + this.type);
	}
});

var car1 = new Car();
car1.speank();


           

輸出

汽車
我是汽車
[Finished in 0.1s]
           

這樣就模拟成了:define一個類,然後new出來調用其方法;

2:如何在此基礎上繼承一個類

首先看看sencha touch2系列的繼承

Ext.define(
	"Person", {
		extend: "Animal",
		speak: function() {
			console.log('我是人');
		}
	}
)
           

加多一個屬性extend搞定。

下面在mss.define模拟一下

// 在sencha中new一個對象傳了兩個參數Ext.create("Animal",{name:"bb"})
// 這裡就不模拟sencha的命名空間了,是以生成該類的對象的時候傳一個配置對象即可
// 把命名空間(mss)和命名單獨提取出來,new mss.define({});
var _mss = {} //建立一個命名空間
_mss.Define = function(parClass, curConfig) {


	// 若sup 是個object,表示這是一個新類
	// 若sup 是個function,表示這是一個繼承
	if(typeof parClass === 'object') {
		curConfig = parClass;
		parClass = function() {};
	}
	// 定義傳回類
	// 當new 該define傳回的函數,會自動執行atrr和init
	var interface = function() { 
		this.attr && this.attr();
		this.init && this.init.apply(this, arguments);
	}
	// 傳回類繼承 parClass
	interface.prototype = new parClass();

	// 為傳回類包含的兩個初始化函數定義基礎方法
	// 獲得繼承的init方法 和attr方法
	// 如果parClass存在init方法,那麼nterface.prototype.init
	// 和new parClass().init相等
	var parInit = interface.prototype.init || function() {};
	var curInit = curConfig.init || function() {};
	var parAttr = interface.prototype.attr || function() {};
	var curAttr = curConfig.attr || function() {};

	// 為傳回類原型初始化目前屬性,這裡注意可能被後面的方法重寫
	for(var i in curConfig) {
		curConfig.hasOwnProperty(i) && (interface.prototype[i] = curConfig[i]);
	}

	// 如果目前傳回類已經繼承了init,重寫該方法
	if(arguments.length && arguments[0].prototype && arguments[0].prototype.init === parInit) {
		interface.prototype.init = function() {
			var scope = this;
			var args = [function() {
				parInit.apply(scope, arguments);
			}];
			var slice = [].slice;
			curInit.apply(scope, args.concat(slice.call(arguments)));

		}
	}


	// 如果目前傳回類已經繼承了attr,重寫attr 或者是首次構造改方法(新類)
	interface.prototype.attr = function() {
		parAttr.call(this);
		curAttr.call(this);
	}

	// 繼承父類的成員屬性
	for(var i in parClass) {
		parClass.hasOwnProperty(i) && (interface[i] = parClass[i]);
	}

	return interface;
}

var Car = _mss.Define({
	attr: function() {
		this.type = '汽車';
	},
	init: function() {
		console.log(this.type);
	},
	speank: function() {
		console.log('我是' + this.type);
	}
});

var car1 = _mss.Define(Car, {
})
new car1().speank();



           

輸出

汽車
我是汽車
[Finished in 0.1s]
           

對于call實作繼承

interface.prototype.attr = function() {
		parAttr.call(this);
		curAttr.call(this);
	}
           

在Chorome控制台列印這段代碼就可以解釋一下這段代碼了

var _Attr = function() {
  this.a = 1;
}
var B = function() {
  this.attr();
};
B.prototype.attr = function(){_Attr.call(this);}
console.log(new B());
	

VM665:9 B {a: 1}a: 1__proto__: Battr: (){_Attr.call(this);}constructor: () {__proto__: Object
           

繼續閱讀