javascript
javascript面向对象包装类Class的类库解析
javascript是個入門門檻很低的語言,甚至一個從來沒有接觸過javascript的技術人員,幾小時內就可以寫出一個簡單有用的程序代碼。
但是如果因此你就下結論:javascript是門簡單的語言。那你就大錯特錯了。想寫出高性能的代碼,同樣需要具備一個高級程序員的基本素養。
一個java或者c++程序員,不一定能寫出高性能的javascript代碼,但更容易寫出高性能的javascript代碼。
javascript的簡單是基于它“胸襟廣闊”的包容性。它聲明時,不需要指定類型,甚至可以任意的轉換類型。它面向對象,卻沒有類(Class)的限制。它是一門崇尚自由又非常嚴謹的語言,如果你是一個自由主義者,那么,擁抱javascript吧!
面向對象編程 (OOP) 是一種流行的編程方法。但javascript的OOP,較之JAVA、c++有很大的同,主要體現它的繼承方式不同。javascript是基于原型PROTOTYPE繼承的。所有對象都是基于原型鏈,最終追述到Object對象。
這里不想討論過多的關于javascript的繼承方式和其它語言的繼承方式的不同之處。主要討論如何封裝javascript的Class,以便更好的管理和維護基礎代碼,減少重復代碼,以及更好的模塊化編程。
?
下面是幾個github上找到的比較好的Class封裝類庫:
? 一、MY-CLASS?
? 項目地址:https://github.com/jiem/my-class
先看基本用法:
a、新建一個類
(function() {// 新建類var Person = my.Class({// 添加靜態方法 STATIC: {AGE_OF_MAJORITY: 18},// 構造函數constructor: function(name, age) {this.name = name;this.age = age;},// 實例方法sayHello: function() {console.log('Hello from ' + this.name + '!');},// 實例方法drinkAlcohol: function() {this.age < Person.AGE_OF_MAJORITY ?console.log('Too young! Drink milk instead!') :console.log('Whiskey or beer?');}});// 暴露給命名空間myLib.Person = Person;})();var john = new myLib.Person('John', 16); john.sayHello(); //log "Hello from John!" john.drinkAlcohol(); //log "Too young! Drink milk instead!"b、繼承一個類
(function() {//Dreamer 繼承 Personvar Dreamer = my.Class(Person, {// 構造方法constructor: function(name, age, dream) {Dreamer.Super.call(this, name, age);this.dream = dream;},// 實例方法sayHello: function() {superSayHello.call(this);console.log('I dream of ' + this.dream + '!');},// 實例方法wakeUp: function() {console.log('Wake up!');}});// Super訪問父類var superSayHello = Dreamer.Super.prototype.sayHello;// 暴露給全局命名空間myLib.Dreamer = Dreamer;})();var sylvester = new myLib.Dreamer('Sylvester', 30, 'eating Tweety'); sylvester.sayHello(); //log "Hello from Sylvester! I dream of eating Tweety!" sylvester.wakeUp(); //log "Wake up!"c、給類添加新方法
// 給myLib.Dreamer添加新方法 my.extendClass(myLib.Dreamer, {// 添加靜態方法 STATIC : {s_dongSomeThing : function(){console.log("do some thing!"); }},// 添加實例方法touchTheSky: function() {console.log('Touching the sky');},// 添加實例方法reachTheStars: function() {console.log('She is so pretty!');}});?d、實現一個類的方法
// 聲明一個新類 myLib.ImaginaryTraveler = my.Class({travel: function() { console.log('Traveling on a carpet!'); },crossOceans: function() { console.log('Saying hi to Moby Dick!'); } });(function() {//Dreamer 繼承 Person 實現 ImaginaryTraveler的方法var Dreamer = my.Class(Person, ImaginaryTraveler, {// 構造方法constructor: function(name, age, dream) {Dreamer.Super.call(this, name, age);this.dream = dream;}// ... });// 暴露給全局命名空間myLib.Dreamer = Dreamer;})();var aladdin = new Dreamer('Aladdin'); aladdin instanceof Person; //true aladdin instanceof ImaginaryTraveler; //false aladdin.travel(); aladdin.wakeUp(); aladdin.sayHello();如果怕忘記new操作符
var Person = my.Class({//you can now call the constructor with or without newconstructor: function(name, city) {if (!(this instanceof Person))return new Person(name, city);this.name = name;this.city = citye;}});下面看一下my.class的源代碼解析:
my.Class實現思路基本是這樣的,如果只有一個參數,那么聲明的是一個基礎類,這個參數是用來聲明新類的方法和屬以及構造函數。它不是繼承而來,但它可以被繼承。
繼承的思路,就是如果有兩個參數,第一個參數做為父類被繼承,第二參數用來聲明新類的方法和屬性以及構造函數,它同樣可以被繼承。
如果有三個以上參數那么,除出第一個參數做為繼承的父類,最后一個參數用聲明新類的方法和屬性以及構造函數。中間的參數是用類來擴展新類的方法。當然也可以通過my.extendClass擴展新方法。
同時,類庫為commonJS和瀏覽環境都提供了支持!
/*globals define:true, window:true, module:true*/ (function () {// Namespace objectvar my = {};// 保證AMD分模塊可用if (typeof define !== 'undefined')define([], function () {return my;});else if (typeof window !== 'undefined')// 保證客戶端可用window.my = my;else// 保證后臺可用module.exports = my;//============================================================================// @method my.Class// @params body:Object// @params SuperClass:function, ImplementClasses:function..., body:Object// @return functionmy.Class = function () {var len = arguments.length;var body = arguments[len - 1]; // 最后一個參數是指定本身的方法var SuperClass = len > 1 ? arguments[0] : null; // 第一個參數是指繼承的方法,實例和靜態部分均繼承var hasImplementClasses = len > 2; // 如果有第三個參數,那么第二個就是implementClass,這里其實只繼承實例對象var Class, SuperClassEmpty;// 保證構造方法if (body.constructor === Object) {Class = function() {};} else {Class = body.constructor;// 保證后面不覆蓋constructordelete body.constructor;}// 處理superClass部分if (SuperClass) {// 中間件實現實例屬性的繼承SuperClassEmpty = function() {};SuperClassEmpty.prototype = SuperClass.prototype;Class.prototype = new SuperClassEmpty(); // 原型繼承,解除引用Class.prototype.constructor = Class; // 保證constructorClass.Super = SuperClass; // 父對象訪問接口// 靜態方法繼承,重載superClass方法extend(Class, SuperClass, false);}// 處理ImplementClass部分,其實只繼承實例屬性部分,除SuperClass #arguments[0]# 和 body #arguments[length-1]#if (hasImplementClasses)for (var i = 1; i < len - 1; i++)// implement是繼承的實例屬性部分, 重載父對象implementClass方法extend(Class.prototype, arguments[i].prototype, false); // 處理本身聲明body部分,靜態要STATIC指定,實例部分要刪除STATIC部分 extendClass(Class, body);return Class;};//============================================================================// @method my.extendClass// @params Class:function, extension:Object, ?override:boolean=truevar extendClass = my.extendClass = function (Class, extension, override) {// 靜態部分繼承靜態部分if (extension.STATIC) {extend(Class, extension.STATIC, override);// 保證實例部分不繼承靜態方法delete extension.STATIC;}// 實例屬性繼繼承實例部 extend(Class.prototype, extension, override);};//============================================================================var extend = function (obj, extension, override) {var prop;// 其實這里的flase是表明,覆蓋父對象的方法if (override === false) {for (prop in extension)if (!(prop in obj))obj[prop] = extension[prop];} else {// 這里其實不覆蓋父對象的方法,包括toStringfor (prop in extension)obj[prop] = extension[prop];if (extension.toString !== Object.prototype.toString)obj.toString = extension.toString;}};})();?
?二、KLASS
?項目地址:https://github.com/ded/klass
先看使用方法:
a、新建一個類
// 聲明一個類 var Person = klass(function (name) {this.name = name }).statics({//靜態方法head: ':)',feet: '_|_'}).methods({//實例方法walk: function () {}})b、繼承一個類
// SuperHuman 繼承 Person var SuperHuman = Person.extend(function (name) {// 自動調用父類的構造方法 }).methods({walk: function() {// 顯式聲明調用父類的walk方法this.supr()this.fly()},fly: function() {}})new SuperHuman('Zelda').walk()c、字面量方式聲明一個類
var Foo = klass({foo: 0,initialize: function() {this.foo = 1},getFoo: function () {return this.foo},setFoo: function (x) {this.foo = xreturn this.getFoo()} })d、實現一個類的方法
因為有時候你可能希望覆寫或者混合一個實例方法,可以這樣:
// 可以傳遞一個字面量去繼承 var Alien = SuperHuman.extend({beam: function() {this.supr()// beam into space } })var Spazoid = new Alien('Zoopo')if (beamIsDown) {// 覆寫beam方法 Spazoid.implement({beam: function() {this.supr()// fallback to jetsthis.jets()}}) }下面看一下klass源代碼解析:
klass的基本設計思路很明確,極力的模仿其它語言的繼承方式。比如:子類構造方法調用父類的構造方法,還可以顯式的聲明調用父類的方法。
這種判斷都是基于正則匹配:fnTest = /xyz/.test(function () {xyz;}) ? /\bsupr\b/ : /.*/;關鍵字"super"
如果顯示的聲明了要調用父類的方法,那么聲明方法的時候,就包裝成一個內部調用父類方法且返回相同值的函數,給當前類的方法。
另一方面,構造方法,也是比較靈活的。如果顯示的聲明了initialize,那么這就是構造方法。否則如果參數是個function那么它就做為構造方法,否則就用父類的構造方法。
通過statics方式添加靜態方法,通過實例的implements和靜態方法methods添加實例方法。
通過父類的extend實現繼承。
同時,類庫為commonJS和瀏覽環境都提供了支持!
/*** Klass.js - copyright @dedfat* version 1.0* https://github.com/ded/klass* Follow our software http://twitter.com/dedfat :)* MIT License*/ !function (context, f) {// fnTest用來驗證是否可能通過正則找出調用super父類方法的方法var fnTest = /xyz/.test(function () {xyz;}) ? /\bsupr\b/ : /.*/,noop = function (){},proto = 'prototype',isFn = function (o) {return typeof o === f;};// 基礎類function klass(o) {return extend.call(typeof o == f ? o : noop, o, 1);}// 包裝成一個借用super同名方法的函數function wrap(k, fn, supr) {return function () {// 緩存原this.supervar tmp = this.supr;// 暫把this.super改造成借用super的同名方法above// 供o里顯式的聲明(fnTest.text(fn)==true)要借用super的同名方法使用this.supr = supr[proto][k];// 借用執行并保存返回值var ret = fn.apply(this, arguments);// 恢復原this.superthis.supr = tmp;// 返回返回值,保證wrap后的返回值跟原來一致return ret;};}// 如果o和super有同名方法,且o顯式聲明借用super的同名方法,就wrap成一個待執行函數供使用// 如果沒有顯式的聲明借用super的同名方法,或者是o獨有的方法,或者不是方法就直接用function process(what, o, supr) {for (var k in o) {// 如果是非繼承方法,按方法注釋規則執行,最終都放進whatif (o.hasOwnProperty(k)) {what[k] = typeof o[k] == f&& typeof supr[proto][k] == f&& fnTest.test(o[k])? wrap(k, o[k], supr) : o[k];}}}// 繼承方法的實現,fromSub是用來控制是否繼承而來,上面的klass里面fromSub是1,表明非繼承而來,構造函數不借用super執行function extend(o, fromSub) {// noop做為媒介類實現原型繼承的解除引用noop[proto] = this[proto];var supr = this,prototype = new noop(), // 創建實例對象供原型繼承使用,解除引用isFunction = typeof o == f,_constructor = isFunction ? o : this,// 如果o是一個構造方法就用,否則由this來決定構造函數_methods = isFunction ? {} : o, // 如果o是一個{...}應該用methods放到fn原型里,如果里面有initialize就是構造函數,如果o是函數就由上面_constructor決定o是構造函數fn = function () { // 因為kclass借助了kclass,所以最終實際上返回的就是fn,fn其實就新類的構造函數//1 如果o是{...}就會被methods直接過濾并添加到fn的原型里,如果o里面有initialize,那么fn的原型里就有initialize,那么它就是構造方法//2 如果o是function,methods什么也添加不到fn的原型里,但是_constructor會接受o當構造函數//3 如果o是{....},同時里面也沒有initialize,那么就是this當構造函數,如果在klass里由call決定,顯然構造函數是noop,如果在非基礎類里,構造函數就是父類的構造函數// 由于o不是函數不會自動調用父類的構造函數,只是把父類的構造函數當做當前類的構造函數----這都是由于this的指向決定的console.log(this);if (this.initialize) {this.initialize.apply(this, arguments);} else {// 調用父類構造方法// 如上面3,o不是函數,不會調用父類的構造方法// 基礎類無父類,不會調用父類構造方法fromSub || isFn(o) && supr.apply(this, arguments);// 調用本類構造方法// 參考上面2,3要么是noop要么是oconsole.log(_constructor==noop);_constructor.apply(this, arguments);}};// 構造原型方法的接口fn.methods = function (o) {process(prototype, o, supr);fn[proto] = prototype;return this;};// 執行實現新類原型,保證新類的constructorfn.methods.call(fn, _methods).prototype.constructor = fn;// 保證新類可以被繼承fn.extend = arguments.callee;// 添加實例方法或者靜態方法,statics:靜態方法,implement實例方法fn[proto].implement = fn.statics = function (o, optFn) {// 保證o是一個object對象,如果o是一個字符串,那么就是添一個方法的情況,如果o是一個object對象說明是批量添加的// 因為要從o里面拷貝o = typeof o == 'string' ? (function () {var obj = {};obj[o] = optFn;return obj;}()) : o;// 添加實例方法或者靜態方法,statics:靜態方法,implement實例方法process(this, o, supr);return this;};return fn;}// 后臺用,nodejsif (typeof module !== 'undefined' && module.exports) {module.exports = klass;} else {var old = context.klass;// 防沖突klass.noConflict = function () {context.klass = old;return this;};// 前臺瀏覽器用//window.kclass = kclass;context.klass = klass;}}(this, 'function');?三、還有一種簡單實現
實現思路很簡單,就是利用ECMAScript 5 原型式繼承Object.create方法,封裝成一個方法,如果不支持ECMAScript5的環境,就平移退化到
function F() {}; F.prototype = superCtor.prototype; ctor.prototype = new F(); ctor.prototype.constructor = ctor;同樣的,除最后一個參數是當前類的方法聲明,其它參數均做為繼承父類,需要循環繼承,但當這里處理的相對比較簡單,沒涉及到覆蓋。你可以自己動手添加。
var Class = (function() { /** * Inherits function.(node.js) * * @param ctor subclass's constructor. * @param superctor superclass's constructor. */ var inherits = function(ctor, superCtor) { // 顯式的指定父類ctor.super_ = superCtor; // ECMAScript 5 原型式繼承并解除引用if (Object.create) { ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } }); } else { // 無Object.create方法的平穩退化function F() {}; F.prototype = superCtor.prototype; ctor.prototype = new F(); ctor.prototype.constructor = ctor; } }; /** * Class function. */ return function() { // 最后一個參數是新類方法、屬性和構造函數聲明var subClazz = arguments[arguments.length - 1] || function() {}; // initialize是構造函數,否構造函數就是一個空函數var fn = subClazz.initialize == null ? function() {} : subClazz.initialize; // 繼承除最一個參數以的類,多繼承,也可以用作擴展方法 for (var index = 0; index < arguments.length - 1; index++) { inherits(fn, arguments[index]); } // 實現新類的方法for (var prop in subClazz) { if (prop == "initialize") { continue; } fn.prototype[prop] = subClazz[prop]; } return fn; } })();看下面實例:
/** * The definition of Cat Class. */ var Cat = Class({ /** * Constructor. * * @param name Cat's name */ initialize: function(name) {this.name = name; }, /** * Eat function. */ eat: function() { alert(this.name + " is eating fish."); } }); /** * The definition of Black Cat Class. */ var BlackCat = Class(Cat, { /** * Constructor. * * @param name Cat's name. * @param age Cat's age. */ initialize: function(name, age) { // call the constructor of super class. BlackCat.super_.call(this, name); this.age = age; }, /** * Eat function. */ eat: function() { alert(this.name + "(" + this.age + ") is eating dog."); } }); /** * The definition of Black Fat Cat Class. */ var BlackFatCat = Class(BlackCat, { /** * Constructor. * * @param name Cat's name. * @param age Cat's age. * @param weight Cat's weight. */ initialize: function(name, age, weight) { // call the constructor of super class. BlackFatCat.super_.call(this, name, age); this.weight = weight; }, /** * Eat function. */ eat: function() { alert(this.name + "(" + this.age + ") is eating dog. My weight: " + this.weight); } }); /** * The definition of Dog Class. */ var Dog = Class({}); var cat = new BlackFatCat("John", 24, "100kg"); cat.eat(); // true alert(cat instanceof Cat); // true alert(cat instanceof BlackCat); // true alert(cat instanceof BlackFatCat); // true alert(cat.constructor === BlackFatCat); // false alert(cat instanceof Dog);?四、mootools類庫的Class
源碼解析可以看這里:http://www.cnblogs.com/hmking/archive/2011/09/30/2196504.html
看具體用法:
a、新建一個類
var Cat = new Class({initialize: function(name){this.name = name;} }); var myCat = new Cat('Micia'); alert(myCat.name); // alerts 'Micia'var Cow = new Class({initialize: function(){alert('moooo');} });b、繼承的實現
var Animal = new Class({initialize: function(age){this.age = age;} }); var Cat = new Class({Extends: Animal,initialize: function(name, age){this.parent(age); // calls initalize method of Animal classthis.name = name;} }); var myCat = new Cat('Micia', 20); alert(myCat.name); // alerts 'Micia'. alert(myCat.age); // alerts 20.c、擴充類的實現
var Animal = new Class({initialize: function(age){this.age = age;} }); var Cat = new Class({Implements: Animal,setName: function(name){this.name = name} }); var myAnimal = new Cat(20); myAnimal.setName('Micia'); alert(myAnimal.name); // alerts 'Micia'.?由于寫的比較籠統,可能有很多地方沒有解析到,也可能有不準確的地方,還望指正。
?看完上面幾種解析,相信息自己也可以寫出一個自己的封裝類庫出來,至于,怎么實現看個人喜好了。但基本的思都是一樣的基于原型的繼承方式和循環拷貝新方法。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的javascript面向对象包装类Class的类库解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VS2005 解决应用程序配置不正确,程
- 下一篇: MATLAB描绘极坐标图像——polar