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 繼承 Person var 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 new constructor: 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 object var 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 function my.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; // 保證後面不覆蓋constructor delete body.constructor; } // 處理superClass部分 if (SuperClass) { // 中介軟體實現例項屬性的繼承 SuperClassEmpty = function() {}; SuperClassEmpty.prototype = SuperClass.prototype; Class.prototype = new SuperClassEmpty(); // 原型繼承,解除引用 Class.prototype.constructor = Class; // 保證constructor Class.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=true var 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 { // 這裡其實不覆蓋父物件的方法,包括toString for (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 = x return 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 jets this.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.super var 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.super this.supr = tmp; // 返回返回值,保證wrap後的返回值跟原來一致 return ret; }; } // 如果o和super有同名方法,且o顯式宣告借用super的同名方法,就wrap成一個待執行函式供使用 // 如果沒有顯式的宣告借用super的同名方法,或者是o獨有的方法,或者不是方法就直接用 function process(what, o, supr) { for (var k in o) { // 如果是非繼承方法,按方法註釋規則執行,最終都放進what if (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要麼是o console.log(_constructor==noop); _constructor.apply(this, arguments); } }; // 構造原型方法的介面 fn.methods = function (o) { process(prototype, o, supr); fn[proto] = prototype; return this; }; // 執行實現新類原型,保證新類的constructor fn.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; } // 後臺用,nodejs if (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 class this.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'.
五、悟透javascript:語法甘露
先看用法例項
a、建立類
// 建立類Person var Person = Class(object, { Create : function(name, age) { this.name = name; this.age = age; }, SayHello : function() { alert("Hello, I'm " + this.name + ", " + this.age + " years old."); } }); var BillGates = New(Person, ["Bill Gates", 53]); BillGates.SayHello();
b、繼承類
// Employee繼承Person var Employee = Class(Person, { Create : function(name, age, salary) { Person.Create.call(this, name, age); //呼叫基類的建構函式 this.salary = salary; }, ShowMeTheMoney : function() { alert(this.name + " $" + this.salary); } }); var SteveJobs = New(Employee, ["Steve Jobs", 53, 1234]); SteveJobs.SayHello(); SteveJobs.ShowMeTheMoney();
下面是原始碼分析:顯然,多了一個New方法,建立類和新建類的例項都被巧妙的封裝了。形成了一個有意義的整體!還有一點不同的地方,所有的類都基於字面量,而不是基於函式。程式碼很簡短,但其中原理卻很豐富也很巧妙,可以細細品味一番!
//建立類的函式,用於宣告類及繼承關係 function Class(aBaseClass, aClassDefine) { //建立類的臨時函式殼 function class_() { this.Type = aBaseClass; //我們給每一個類約定一個Type屬性,引用其繼承的類 for (var member in aClassDefine) this[member] = aClassDefine[member]; //複製類的全部定義到當前建立的類 }; class_.prototype = aBaseClass; return new class_(); }; //建立物件的函式,用於任意類的物件建立 function New(aClass, aParams) { //建立物件的臨時函式殼 function new_() { this.Type = aClass; //我們也給每一個物件約定一個Type屬性,據此可以訪問到物件所屬的類 if (aClass.Create) aClass.Create.apply(this, aParams); //我們約定所有類的建構函式都叫Create,這和DELPHI比較相似 }; new_.prototype = aClass; return new new_(); };
由於寫的比較籠統,可能有很多地方沒有解析到,也可能有不準確的地方,還望指正。
看完上面幾種解析,相資訊自己也可以寫出一個自己的封裝類庫出來,至於,怎麼實現看個人喜好了。但基本的思都是一樣的基於原型的繼承方式和迴圈拷貝新方法。