javascript框架設(shè)計(jì)之類工廠
類與繼承在javascript的出現(xiàn),說明javascript已經(jīng)達(dá)到大規(guī)模開發(fā)的門檻了,在之前是ECMAScript4,就試圖引入類,模塊等東西,但由于過分引入太多的特性,搞得javascript烏煙瘴氣,導(dǎo)致被否決。不過只是把類延時(shí)到ES6.到目前為止,javascript還沒有正真意義上的類。不過我們可以模擬類,曾近一段時(shí)間,類工廠是框架的標(biāo)配,本章會(huì)介紹各種類實(shí)現(xiàn),方便大家在自己的框架中或選擇時(shí)自己喜歡的那一類風(fēng)格。
1.javascript對(duì)類的支持
在其它語言中 ,類的實(shí)例都要通過構(gòu)造函數(shù)new出來。作為一個(gè)刻意模仿java的語言。javascript存在new操作符,并且所有函數(shù)都可以作為構(gòu)造器。構(gòu)造函數(shù)與普通的方法沒有什么區(qū)別。瀏覽器為了構(gòu)建它繁花似錦的生態(tài)圈,比如Node,Element,HTMLElement,HTMLParagraphElement,顯然使用繼承關(guān)系方便一些方法或?qū)傩缘墓蚕?,于是javascript從其它語言借鑒了原型這種機(jī)制。Prototype作為一個(gè)特殊的對(duì)象屬性存在于每一個(gè)函數(shù)上。當(dāng)一個(gè)函數(shù)通過new操作符new出其“孩子”——“實(shí)例”,這個(gè)名為實(shí)例的對(duì)象就擁有這個(gè)函數(shù)的Prototype對(duì)象所有的一切成員,從而實(shí)現(xiàn)實(shí)現(xiàn)所有實(shí)例對(duì)象都共享一組方法或?qū)傩浴6鴍avascript所謂的“類”就是通過修改這個(gè)Prototype對(duì)象,以區(qū)別原生對(duì)象及其其它定義的“類”。在瀏覽器中,node這個(gè)類基于Object修改而來的,而Element則是基于Node,而HTMLElement又基于Element....相對(duì)我們的工作業(yè)務(wù),我們可以創(chuàng)建自己的類來實(shí)現(xiàn)重用與共享。
function A(){ } A.prototype = { aa:"aa", method:function(){ } }; var a = new A; var b = new A; console.log(a.aa === b.aa); console.log(a.method === b.method)
一般地,我們把定義在原型上的方法叫原型方法,它為所有的實(shí)例所共享,這有好也有不好,為了實(shí)現(xiàn)差異化,javascript允許我們直接在構(gòu)造器內(nèi)指定其方法,這叫特權(quán)方法。如果是屬性,就叫特權(quán)屬性。它們每一個(gè)實(shí)例一個(gè)副本,各不影響。因此,我們通常把共享用于操作數(shù)據(jù)的方法放在原型,把私有的屬性放在特權(quán)屬性中。但放于this上,還是讓人任意訪問到,那就放在函數(shù)體內(nèi)的作用域內(nèi)吧。這時(shí)它就成為名副其實(shí)的私有屬性。
function A() { var count = 0; this.aa = "aa"; this.method = function() { return count; } this.obj = {} } A.prototype = { aa:"aa", method:function(){ } }; var a = new A; var b = new A; console.log(a.aa === b.aa);//true 由于aa的值為基本類型,比較值 console.log(a.obj === b.obj) //false 引用類型,每次進(jìn)入函數(shù)體都要重新創(chuàng)建,因此都不一樣。 console.log(a.method === b.method); //false
特權(quán)方法或?qū)傩灾皇侵皇钦谧≡偷姆椒ɑ驅(qū)傩?,因此只要?jiǎng)h掉特權(quán)方法,就能方法到同名的原型方法或?qū)傩浴?/p>
delete a.method; delete b.method; console.log(a.method === A.prototype.method);//true console.log(a.method === b.method); //true
用java的語言來說,原型方法與特權(quán)方法都屬性實(shí)例方法,在java中還有一種叫類方法與類屬性的東西。它們用javascript來模擬也非常簡單,直接定義在函數(shù)上就行了。
A.method2 = function(){} //類方法 var c = new A; console.log(c.method2); //undefined
接下來,我們看下繼承的實(shí)現(xiàn),上面說過,Prototype上有什么東西,它的實(shí)例就有什么東西,不論這個(gè)屬性是后來添加的,還是整個(gè)Prototype都置換上去的。如果我們將這個(gè)prototype對(duì)象置換為另一個(gè)類的原型,那么它就輕而易舉的獲得那個(gè)類的所有原型成員。
function A() {}; A.prototype = { aaa : 1 } function B() {}; B.prototype = A.prototype; var b = new B; console.log(b.aaa); //=> 1; A.prototype.bb = 2; console.log(b.bb) //=> 2;
由于是引用著同一個(gè)對(duì)象,這意味這,我們修改A類的原型,也等同于修該了B類的原型。因此,我們不能把一個(gè)對(duì)象賦值給兩個(gè)類。這有兩種辦法,
方法1:通過for in 把父類的原型成員逐一賦給子類的原型
方法2是:子類的原型不是直接由父類獲得,先將父類的原型賦值給一個(gè)函數(shù),然后將這個(gè)函數(shù)的實(shí)例作為子類的原型。
方法一,我們通常要實(shí)現(xiàn)mixin這樣的方法,有的書稱之為拷貝繼承,好處就是簡單直接,壞處就是無法通過instanceof驗(yàn)證。Prototype.js的extend方法就用來干這事。
function extend (des, source) { //des = destination for (var property in source) des[property] = source[property]; return des; }
方法二,就在原型上動(dòng)腦筋,因此稱之為原型繼承。下面是個(gè)范本
function A() {}; A.prototype = { aa:function(){ alert(1) } } function bridge() { }; bridge.prototype = A.prototype; function B() {} B.prototype = new bridge(); var a = new A; var b = new B; console.log(a == b) //false 證明成功分開原型 console.log(A.prototype == B.prototype) //true 子類共享父類的原型方法 console.log(a.aa === b.aa); //為父類動(dòng)態(tài)添加新的方法 A.prototype.bb = function () { alert(2) } //true,繼承父類的方法 B.prototype.cc = function (){ alert(3) } //false 父類未必有子類的new實(shí)例 console.log(a.cc === b.cc) //并且它能夠正常通過javascript自帶的驗(yàn)證機(jī)制instanceof console.log(b instanceof A) ;//true console.log(b instanceof B) ; //true
方法二能通過instanceof驗(yàn)證,es5就內(nèi)置了這種方法來實(shí)現(xiàn)原型繼承,它就是Object.create,如果不考慮第二個(gè)參數(shù),它約等于下面的代碼。
Object.create = function (o) { function F() {} F.prototype = o; return new F(); }
上面的方法,要求傳入一個(gè)父類的原型作為參數(shù),然后返回子類的原型
不過,我們這樣還是遺漏了一點(diǎn)東西——子類不只是繼承父類的遺產(chǎn),還應(yīng)該有自己的東西,此外,原型繼承并沒有讓子類繼承父類的成員與特權(quán)成員。這些我們都得手動(dòng)添加,如類成員,我們可以通過上面的extend方法,特權(quán)成員我們可以在子類構(gòu)造器中,通過apply實(shí)現(xiàn)。
function inherit(init, Parent, proto){ function Son(){ Parent.apply(this, argument); //先繼承父類的特權(quán)成員 init.apply(this, argument); //在執(zhí)行自己的構(gòu)造器 } } //由于Object.create是我們偽造的,因此避免使用第二個(gè)參數(shù) Son.prototype = Object.create(Parent.prototype,{}); Son.prototype.toString = Parent.prototype.toString; //處理IEbug Son.prototype.valueOf = Parent.prototype.valueOf; //處理IEbug Son.prototype.constructor = Son; //確保構(gòu)造器正常指向,而不是Object extend(Son, proto) ;//添加子類的特有的原型成員 return Son;
下面,做一組實(shí)驗(yàn),測(cè)試下實(shí)例的回溯機(jī)制。當(dāng)我們?cè)L問對(duì)象的一個(gè)屬性,那么他先尋找其特權(quán)成員,如果有同名就返回,沒有就找原型,再?zèng)]有,就找父類的原型...我們嘗試將它的原型臨時(shí)修改下,看它的屬性會(huì)變成那個(gè)。
function A(){ } A.prototype = { aa:1 } var a = new A; console.log(a.aa) ; //=>1 //將它的所有原型都替換掉 A.prototype = { aa:2 } console.log(a.aa); //=>1 //于是我們想到每個(gè)實(shí)例都有一個(gè)constructor方法,指向其構(gòu)造器 //而構(gòu)造器上面正好有我們的原型,javascript引擎是不是通過該路線回溯屬性呢 function B(){ } B.prototype = { aa:3 } a.constructor = B; console.log(a.aa) //1 表示不受影響
因此類的實(shí)例肯定通過另一條通道進(jìn)行回溯,翻看ecma規(guī)范可知每一個(gè)對(duì)象都有一個(gè)內(nèi)部屬性[[prototype]],它保存這我們new它時(shí)的構(gòu)造器所引用的Prototype對(duì)象。在標(biāo)準(zhǔn)瀏覽器與IE11里,它暴露了一個(gè)叫__proto__屬性來訪問它。因此,只要不動(dòng)__proto__上面的代碼怎么動(dòng),a.aa始終堅(jiān)定不毅的返回1.
再看一下,new時(shí)操作發(fā)生了什么。
1.創(chuàng)建了一個(gè)空對(duì)象 instance
2.instance.__proto__ = intanceClass.prototype
3.將構(gòu)造函數(shù)里面的this = instance
4.執(zhí)行構(gòu)造函數(shù)里的代碼
5.判定有沒有返回值,沒有返回值就返回默認(rèn)值為undefined,如果返回值為復(fù)合數(shù)據(jù)類型,則直接返回,否則返回this
于是有了下面的結(jié)果。
function A(){ console.log(this.__proto__.aa); //1 this.aa = 2 } A.prototype = {aa:1} var a = new A; console.log(a.aa) a.__proto__ = { aa:3 } console.log(a.aa) //=>2 delete a. aa; //刪除特權(quán)屬性,暴露原型鏈上的同名屬性 console.log(a.aa) //=>3
有了__proto__,我們可以將原型設(shè)計(jì)繼承設(shè)計(jì)得更簡單,我們還是拿上面的例子改一改,進(jìn)行試驗(yàn)
function A() {} A.prototype = { aa:1 } function bridge() {} bridge.prototype = A.prototype; function B(){} B.prototype = new bridge(); B.prototype.constructor = B; var b = new B; B.prototype.cc = function(){ alert(3) } //String.prototype === new String().__proto__ => true console.log(B.prototype.__proto__ === A.prototype) //true console.log(b.__proto__ == B.prototype); //true console.log(b.__proto__.__proto__ === A.prototype); //true 得到父類的原型對(duì)象
因?yàn)閎.__proto__.constructor為B,而B的原型是從bridge中得來的,而bride.prototype = A.prototype,反過來,我們?cè)诙x時(shí),B.prototype.__proto__ = A.prototype,就能輕松實(shí)現(xiàn)兩個(gè)類的繼承.
__proto__屬性已經(jīng)加入es6,因此可以通過防止大膽的使用
2.各種類工廠的實(shí)現(xiàn)。
上節(jié)我們演示了各種繼承方式的實(shí)現(xiàn),但都很凌亂。我們希望提供一個(gè)專門的方法,只要用戶傳入相應(yīng)的參數(shù),或按照一定簡單格式就能創(chuàng)建一個(gè)類。特別是子類。
由于主流框架的類工廠太依賴他們龐雜的工具函數(shù),而一個(gè)精巧的類工廠也不過百行左右
相當(dāng)精巧的庫,P.js
使用版:https://github.com/jiayi2/factoryjs
這是一個(gè)相當(dāng)精巧的庫,尤其調(diào)用父類的同名方法時(shí),它直接將父類的原型拋在你面前,連_super也省了。
var P = (function(prototype, ownProperty, undefined) { return function P(_superclass /* = Object */, definition) { // handle the case where no superclass is given if (definition === undefined) { definition = _superclass; _superclass = Object; } // C is the class to be returned. // // When called, creates and initializes an instance of C, unless // `this` is already an instance of C, then just initializes `this`; // either way, returns the instance of C that was initialized. // // TODO: the Chrome inspector shows all created objects as `C` // rather than `Object`. Setting the .name property seems to // have no effect. Is there a way to override this behavior? function C() { var self = this instanceof C ? this : new Bare; self.init.apply(self, arguments); return self; } // C.Bare is a class with a noop constructor. Its prototype will be // the same as C, so that instances of C.Bare are instances of C. // `new MyClass.Bare` then creates new instances of C without // calling .init(). function Bare() {} C.Bare = Bare; // Extend the prototype chain: first use Bare to create an // uninitialized instance of the superclass, then set up Bare // to create instances of this class. var _super = Bare[prototype] = _superclass[prototype]; var proto = Bare[prototype] = C[prototype] = C.p = new Bare; // pre-declaring the iteration variable for the loop below to save // a `var` keyword after minification var key; // set the constructor property on the prototype, for convenience proto.constructor = C; C.extend = function(def) { return P(C, def); } return (C.open = function(def) { if (typeof def === 'function') { // call the defining function with all the arguments you need // extensions captures the return value. def = def.call(C, proto, _super, C, _superclass); } // ...and extend it if (typeof def === 'object') { for (key in def) { if (ownProperty.call(def, key)) { proto[key] = def[key]; } } } // if no init, assume we're inheriting from a non-Pjs class, so // default to using the superclass constructor. if (!('init' in proto)) proto.init = _superclass; return C; })(definition); } // as a minifier optimization, we've closured in a few helper functions // and the string 'prototype' (C[p] is much shorter than C.prototype) })('prototype', ({}).hasOwnProperty);
我們嘗試創(chuàng)建一個(gè)類:
var Dog = P (function(proto, superProto){ proto.init = function(name) { //構(gòu)造函數(shù) this.name = name; } proto.move = function(meters){ //原型方法 console.log(this.name + " moved " + meters + " m.") } }); var a = new Dog("aaa") var b = new Dog("bbb"); //無實(shí)例變化 a.move(1); b.move(2);
我們?cè)诂F(xiàn)在的情況下,可以嘗試創(chuàng)建更簡潔的定義方式
var Animal = P (function(proto, superProto){ proto.init = function(name) { //構(gòu)造函數(shù) this.name = name; } proto.move = function(meters){ //原型方法 console.log(this.name + " moved " + meters + " m.") } }); var a = new Animal("aaa") var b = new Animal("bbb"); //無實(shí)例變化 a.move(1); b.move(2); //............... var Snake = P (Animal, function(snake, animal){ snake.init = function(name, eyes){ animal.init.call(this, arguments); //調(diào)運(yùn)父類構(gòu)造器 this.eyes = 2; } snake.move = function() { console.log('slithering...'); animal.move.call(this, 5); //調(diào)運(yùn)父類同名方法 } }); var s = new Snake("snake", 1); s.move(); console.log(s.name); console.log(s.eyes);
私有屬性演示,由于放在函數(shù)體內(nèi)集中定義,因此安全可靠!
var Cobra = P (Snake, function(cobra){ var age = 1;//私有屬性 //這里還可以編寫私有方法 cobra.glow = function(){ //長大 return age++; } }); var c = new Cobra("cobra"); console.log(c.glow()); //1 console.log(c.glow()); //2 console.log(c.glow()); //3
以上所述就是本文的全部內(nèi)容了,希望大家能夠喜歡。
- 十大熱門的JavaScript框架和庫
- 深入解析JavaScript框架Backbone.js中的事件機(jī)制
- JavaScript框架是什么?怎樣才能叫做框架?
- 超贊的動(dòng)手創(chuàng)建JavaScript框架的詳細(xì)教程
- javascript框架設(shè)計(jì)之瀏覽器的嗅探和特征偵測(cè)
- javascript框架設(shè)計(jì)之種子模塊
- javascript框架設(shè)計(jì)之框架分類及主要功能
- 2014 年最熱門的21款JavaScript框架推薦
- javascript框架設(shè)計(jì)讀書筆記之?dāng)?shù)組的擴(kuò)展與修復(fù)
- javascript框架設(shè)計(jì)讀書筆記之字符串的擴(kuò)展和修復(fù)
- javascript框架設(shè)計(jì)讀書筆記之模塊加載系統(tǒng)
- javascript框架設(shè)計(jì)讀書筆記之種子模塊
- JavaScript框架(iframe)操作總結(jié)
- 怎么選擇Javascript框架(Javascript Framework)
- 詳細(xì)介紹8款超實(shí)用JavaScript框架
- brook javascript框架介紹
- 16個(gè)最流行的JavaScript框架[推薦]
- 如何選擇適合你的JavaScript框架
相關(guān)文章
js與jQuery實(shí)現(xiàn)的用戶注冊(cè)協(xié)議倒計(jì)時(shí)功能實(shí)例【三種方法】
這篇文章主要介紹了js與jQuery實(shí)現(xiàn)的用戶注冊(cè)協(xié)議倒計(jì)時(shí)功能,結(jié)合實(shí)例形式分析了三種倒計(jì)時(shí)功能的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-11-11JS中判斷某個(gè)字符串是否包含另一個(gè)字符串的五種方法
本文給大家?guī)鞪S中判斷某個(gè)字符串是否包含另一個(gè)字符串的五種方法,有string對(duì)象的方法,match() 方法,RegExp對(duì)象的方法,test() 方法,exec() 方法,具體內(nèi)容詳情大家參考下本文2018-05-05js實(shí)現(xiàn)帶有動(dòng)畫的返回頂部
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)帶有動(dòng)畫的返回頂部,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08js動(dòng)態(tài)添加帶圓圈序號(hào)列表的實(shí)例代碼
這篇文章主要介紹了js動(dòng)態(tài)添加帶圓圈序號(hào)列表的實(shí)例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02小程序開發(fā)實(shí)現(xiàn)access_token統(tǒng)一管理
本文主要介紹了小程序開發(fā)實(shí)現(xiàn)access_token統(tǒng)一管理,文中通過示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07js中bool值的轉(zhuǎn)換及“&&”、“||”、 “!!”詳解
這篇文章主要給大家介紹了關(guān)于js中bool值的轉(zhuǎn)換方法以及"&&" 、"||"、 "!!"的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友下面來一起看看吧。2017-12-12javascript字符串循環(huán)匹配實(shí)例分析
這篇文章主要介紹了javascript字符串循環(huán)匹配,實(shí)例分析三種常用的字符串循環(huán)匹配的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07