日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
從一個組件的實現(xiàn)來深刻理解JS中的繼承

其實,無論是寫什么語言的程序員,最終的目的,都是把產(chǎn)品或代碼封裝到一起,提供接口,讓使用者很舒適的實現(xiàn)功能。所以對于我來說,往往頭疼的不是寫代碼,而是寫注釋和文檔!如果接口很亂,肯定會頭疼一整天。

成都創(chuàng)新互聯(lián)公司是一家專業(yè)提供宿州企業(yè)網(wǎng)站建設(shè),專注與成都網(wǎng)站設(shè)計、成都網(wǎng)站制作、H5網(wǎng)站設(shè)計、小程序制作等業(yè)務(wù)。10年已為宿州眾多企業(yè)、政府機構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站制作公司優(yōu)惠進行中。

JavaScript 最初是以 Web 腳本語言面向大眾的,盡管現(xiàn)在出了服務(wù)器端的 nodejs,但是單線程的性質(zhì)還沒有變。對于一個 Web 開發(fā)人員來說,能寫一手漂亮的組件極為重要。GitHub 上那些開源且 stars 過百的 Web 項目或組件,可讀性肯定非常好。

從一個例子來學(xué)習(xí)寫組件

組件教程的參考來自于 GitHub 上,通俗易懂,鏈接。

要實現(xiàn)下面這個功能,對一個 input 輸入框的內(nèi)容進行驗證,只有純數(shù)字和字母的組合才是被接受的,其他都返回 failed:

全局變量寫法

這種寫法完全沒有約束,基本所有人都會,完全沒啥技巧:

 
 
  1. // html
  2. // javascript
  3. var input = document.getElementById("input");
  4. function getValue(){
  5.   return input.value;
  6. }
  7. function render(){
  8.   var value = getValue();
  9.   if(!document.getElementById("show")){
  10.     var append = document.createElement('span');
  11.     append.setAttribute("id", "show");
  12.     input.parentNode.appendChild(append);
  13.   }
  14.   var show = document.getElementById("show");
  15.   if(/^[0-9a-zA-Z]+$/.exec(value)){
  16.     show.innerHTML = 'Pass!';
  17.   }else{
  18.     show.innerHTML = 'Failed!';
  19.   }
  20. }
  21. input.addEventListener('keyup', function(){
  22.   render();
  23. }); 

缺點自然不用多說,變量沒有任何隔離,嚴重污染全局變量,雖然可以達到目的,但極不推薦這種寫法。

對象隔離作用域

鑒于以上寫法的弊端,我們用對象來隔離變量和函數(shù):

 
 
  1. var obj = {
  2.   input: null,
  3.   // 初始化并提供入口調(diào)用方法
  4.   init: function(config){
  5.     this.input = document.getElementById(config.id);
  6.     this.bind();
  7.     //鏈式調(diào)用
  8.     return this;
  9.   },
  10.   // 綁定
  11.   bind: function(){
  12.     var self = this;
  13.     this.input.addEventListener('keyup', function(){
  14.       self.render();
  15.     });
  16.   },
  17.   getValue: function(){
  18.     return this.input.value;
  19.   },
  20.   render: function(){
  21.     var value = this.getValue();
  22.     if(!document.getElementById("show")){
  23.       var append = document.createElement('span');
  24.       append.setAttribute("id", "show");
  25.       input.parentNode.appendChild(append);
  26.     }
  27.     var show = document.getElementById("show");
  28.     if(/^[0-9a-zA-Z]+$/.exec(value)){
  29.       show.innerHTML = 'Pass!';
  30.     }else{
  31.       show.innerHTML = 'Failed!';
  32.     }
  33.   }
  34. }
  35. window.onload = function(){
  36.   obj.init({id: "input"});

相對于開放式的寫法,上面的這個方法就比較清晰了。有初始化,有內(nèi)部函數(shù)和變量,還提供入口調(diào)用方法。

新手能實現(xiàn)上面的方法已經(jīng)很不錯了,還記得當(dāng)初做百度前端學(xué)院題目的時候,基本就是用對象了。

不過這種方法仍然有弊端。obj 對象中的方法都是公開的,并不是私有的,其他人寫的代碼可以隨意更改這些內(nèi)容。當(dāng)多人協(xié)作或代碼量很多時,又會產(chǎn)生一系列問題。

函數(shù)閉包的寫法

 
 
  1. var fun = (function(){
  2.   var _bind = function(obj){
  3.     obj.input.addEventListener('keyup', function(){
  4.       obj.render();
  5.     });
  6.   }
  7.   var _getValue = function(obj){
  8.     return obj.input.value;
  9.   }
  10.   var InputFun = function(config){};
  11.   InputFun.prototype.init = function(config){
  12.     this.input = document.getElementById(config.id);
  13.     _bind(this);
  14.     return this;
  15.   }
  16.   InputFun.prototype.render = function(){
  17.     var value = _getValue(this);
  18.     if(!document.getElementById("show")){
  19.       var append = document.createElement('span');
  20.       append.setAttribute("id", "show");
  21.       input.parentNode.appendChild(append);
  22.     }
  23.     var show = document.getElementById("show");
  24.     if(/^[0-9a-zA-Z]+$/.exec(value)){
  25.       show.innerHTML = 'Pass!';
  26.     }else{
  27.       show.innerHTML = 'Failed!';
  28.     }
  29.   }
  30.   return InputFun;
  31. })();
  32. window.onload = function(){
  33.   new fun().init({id: 'input'});

函數(shù)閉包寫法的好處都在自執(zhí)行的閉包里,不會受到外面的影響,而且提供給外面的方法包括 init 和 render。比如我們可以像 JQuery 那樣,稍微對其改造一下:

 
 
  1. var $ = function(id){
  2.   // 這樣子就不用每次都 new 了
  3.   return new fun().init({'id': id});
  4. }
  5. window.onload = function(){
  6.   $('input');

還沒有涉及到原型,只是簡單的閉包。

基本上,這已經(jīng)是一個合格的寫法了。

面向?qū)ο?/strong>

雖然上面的方法以及夠好了,但是我們的目的,是為了使用面向?qū)ο?。面向?qū)ο笠恢币詠矶际潜徽J為***的編程方式,如果每個人的代碼風(fēng)格都相似,維護、查看起來就非常的方便。

但是,我想在介紹面向?qū)ο笾?,先來回憶一?JS 中的繼承(實現(xiàn)我們放到***再說)。

入門級的面向?qū)ο?/strong>

提到繼承,我首先想到的就是用 new 來實現(xiàn)。還是以例子為主吧,人->學(xué)生->小學(xué)生,在 JS 中有原型鏈這么一說,__proto__ 和 prototype ,對于原型鏈就不過多闡述,如果不懂的可以自己去查閱一些資料。

在這里,我還是要說明一下 JS 中的 new 構(gòu)造,比如 var student = new Person(name),實際上有三步操作:

 
 
  1. var student = {};
  2. student.__proto__ = Person.prototype;
  3. Person.call(student, name) 

得到的 student 是一個對象,__proto__執(zhí)行 Person 的 prototype,Person.call 相當(dāng)于 constructor。

 
 
  1. function Person(name){
  2.   this.name = name;
  3. }
  4. Person.prototype.Say = function(){
  5.   console.log(this.name + ' can say!');
  6. }
  7. var ming = new Person("xiaoming");
  8. console.log(ming.__proto__ == Person.prototype) //true new的第二步結(jié)果
  9. console.log(ming.name) // 'xiaoming' new 的第三步結(jié)果
  10. ming.Say() // 'xiaoming can say!' proto 向上追溯的結(jié)果 

利用 __proto__ 屬性的向上追溯,可以實現(xiàn)一個基于原型鏈的繼承。

 
 
  1. function Person(name){
  2.   this.name = name;
  3. }
  4. Person.prototype.Say = function(){
  5.   console.log(this.name + ' can say!');
  6. }
  7. function Student(name){
  8.   Person.call(this, name); //Person 的屬性賦值給 Student
  9. }
  10. Student.prototype = new Person(); //順序不能反,要在最前面
  11. Student.prototype.DoHomeWork = function(){
  12.   console.log(this.name + ' can do homework!');
  13. }
  14. var ming = new Student("xiaoming");
  15. ming.DoHomeWork(); //'xiaoming can do homework!'
  16. ming.Say(); //'xiaoming can say!' 

大概剛認識原型鏈的時候,我也就只能寫出這樣的水平了,我之前的文章。

打開調(diào)試工具,看一下 ming 都有哪些東西:

 
 
  1. ming
  2.   name: "xiaoming"
  3.   __proto__: Person
  4.     DoHomeWork: ()
  5.     name: undefined //注意這里多了一個 name 屬性
  6.     __proto__: Object
  7.       Say: ()
  8.       constructor: Person(name)
  9.       __proto__: Object 

當(dāng)調(diào)用 ming.Say() 的時候,剛好 ming.__proto__.__proto__ 有這個屬性,這就是鏈式調(diào)用的原理,一層一層向下尋找。

這就是最簡單的繼承了。

面向?qū)ο蟮倪M階

來看一看剛才那種做法的弊端。

  1. 沒有實現(xiàn)傳統(tǒng)面向?qū)ο笤撚械?super 方法來調(diào)用父類方法,鏈式和 super 方法相比還是有一定缺陷的;
  2. 造成過多的原型屬性(name),constructor 丟失(constructor 是一個非常重要的屬性,MDN)。

因為鏈式是一層層向上尋找,知道找到為止,很明顯 super 直接調(diào)用父類更具有優(yōu)勢。

 
 
  1. // 多了原型屬性
  2. console.log(ming.__proto__) // {name: undefined} 

為什么會多一個 name,原因是因為我們執(zhí)行了 Student.prototype = new Person();,而 new 的第三步會執(zhí)行一個 call 的函數(shù),會使得 Student.prototype.name = undefined,恰好 ming.__proto__ 指向 Student 的 prototype,用了 new 是無法避免的。

 
 
  1. // 少了 constructor
  2. console.log(ming.constructor == Person) //true
  3. console.log(ming.constructor == Student) // false 

這也很奇怪,明明 ming 是繼承與 Student,卻返回 false,究其原因,Student.prototype 的 constructor 方法丟失,向上找到了Student.prototype.__proto__ 的 constructor 方法。

再找原因,這句話導(dǎo)致了 Student.prototype 的 constructor 方法丟失:

 
 
  1. Student.prototype = new Person();

在這句話之前打一個斷點,曾經(jīng)是有的,只是被替換掉了:

 找到了問題所在,現(xiàn)在來改進:

 
 
  1. // fn 用來排除多余的屬性(name)
  2. var fn = function(){};
  3. fn.prototype = Person.prototype;
  4. Student.prototype = new fn();
  5. // 重新添上 constructor 屬性
  6. Student.prototype.constructor = Student; 

用上面的繼承代碼替換掉之前的 Student.prototype = new Person();

面向?qū)ο蟮姆庋b

我們不能每一次寫代碼的時候都這樣寫這么多行來繼承吧,所以,于情于理,還是來進行簡單的包裝:

 
 
  1. function classInherit(subClass, parentClass){
  2.   var fn = function(){};
  3.   fn.prototype = parentClass.prototype;
  4.   subClass.prototype = new fn();
  5.   subClass.prototype.constructor = subClass;
  6. }
  7. classInherit(Student, Person); 


哈哈,所謂的包裝,就是重抄一下代碼。

進一步完善面向?qū)ο?/strong>

上面的問題只是簡單的解決了多余屬性和 constructor 丟失的問題,而 supper 問題仍然沒有改進。

舉個栗子,來看看 supper 的重要,每個人都會睡覺,sleep 函數(shù)是人的一個屬性,學(xué)生分為小學(xué)生和大學(xué)生,小學(xué)生晚上 9 點睡覺,大學(xué)生 12 點睡覺,于是:

 
 
  1. Person.prototype.Sleep = function(){
  2.   console.log('Sleep!');
  3. }
  4. function E_Student(){}; //小學(xué)生
  5. function C_Student(){}; //大學(xué)生
  6. classInherit(E_Student, Person);
  7. classInherit(C_Student, Person);
  8. //重寫 Sleep 方法
  9. E_Student.prototype.Sleep = function(){
  10.   console.log('Sleep!');
  11.   console.log('Sleep at 9 clock');
  12. }
  13. C_Student.prototype.Sleep = function(){
  14.   console.log('Sleep!');
  15.   console.log('Sleep at 12 clock');

對于 Sleep 方法,顯得比較混亂,而我們想要通過 supper,直接調(diào)用父類的函數(shù):

 
 
  1. E_Student.prototype.Sleep = function(){
  2.   this._supper(); //supper 方法
  3.   console.log('Sleep at 9 clock');
  4. }
  5. C_Student.prototype.Sleep = function(){
  6.   this._supper(); //supper 方法
  7.   console.log('Sleep at 12 clock');

不知道對 supper 的理解正不正確,總感覺怪怪的,歡迎指正!

來看下 JQuery 之父是如何 class 的面向?qū)ο?,原文在這,源碼如下。

 
 
  1. /* Simple JavaScript Inheritance
  2.  * By John Resig http://ejohn.org/
  3.  * MIT Licensed.
  4.  */
  5. // Inspired by base2 and Prototype
  6. (function(){
  7.   // initializing 開關(guān)很巧妙的來實現(xiàn)調(diào)用原型而不構(gòu)造,還有回掉
  8.   var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
  9.   // The base Class implementation (does nothing)
  10.   // 全局,this 指向 window,***的父類
  11.   this.Class = function(){};
  12.  
  13.   // Create a new Class that inherits from this class
  14.   // 繼承的入口
  15.   Class.extend = function(prop) {
  16.     //保留當(dāng)前類,一般是父類的原型
  17.     var _super = this.prototype;
  18.    
  19.     // Instantiate a base class (but only create the instance,
  20.     // don't run the init constructor)
  21.     //開關(guān) 用來使原型賦值時不調(diào)用真正的構(gòu)成流程
  22.     initializing = true;
  23.     var prototype = new this();
  24.     initializing = false;
  25.    
  26.     // Copy the properties over onto the new prototype
  27.     for (var name in prop) {
  28.       // Check if we're overwriting an existing function
  29.       //對函數(shù)判斷,將屬性套到子類上
  30.       prototype[name] = typeof prop[name] == "function" &&
  31.         typeof _super[name] == "function" && fnTest.test(prop[name]) ?
  32.         (function(name, fn){
  33.           //用閉包來存儲
  34.           return function() {
  35.             var tmp = this._super;
  36.            
  37.             // Add a new ._super() method that is the same method
  38.             // but on the super-class
  39.             this._super = _super[name];
  40.            
  41.             // The method only need to be bound temporarily, so we
  42.             // remove it when we're done executing
  43.             //實現(xiàn)同名調(diào)用
  44.             var ret = fn.apply(this, arguments);  
  45.             this._super = tmp;
  46.             return ret;
  47.           };
  48.         })(name, prop[name]) :
  49.         prop[name];
  50.     }
  51.    
  52.     // 要返回的子類
  53.     function Class() {
  54.       // All construction is actually done in the init method
  55.       if ( !initializing && this.init )
  56.         this.init.apply(this, arguments);
  57.     }
  58.     //前面介紹過的,繼承
  59.     Class.prototype = prototype;
  60.    
  61.     Class.prototype.constructor = Class;
  62.  
  63.     Class.extend = arguments.callee;
  64.    
  65.     return Class;
  66.   };
  67. })(); 

這個時候就可以很輕松的實現(xiàn)面向?qū)ο?,使用如下?/p>

 
 
  1. var Person = Class.extend({
  2.   init: function(name){
  3.     this.name = name;
  4.   },
  5.   Say: function(name){
  6.     console.log(this.name + ' can Say!');
  7.   },
  8.   Sleep: function(){
  9.     console.log(this.name + ' can Sleep!');
  10.   }
  11. });
  12. var Student = Person.extend({
  13.   init: function(name){
  14.     this._super('Student-' + name);
  15.   },
  16.   Sleep: function(){
  17.     this._super();
  18.     console.log('And sleep early!');
  19.   },
  20.   DoHomeWork: function(){
  21.     console.log(this.name + ' can do homework!');
  22.   }
  23. });
  24. var p = new Person('Li');
  25. p.Say(); //'Li can Say!'
  26. p.Sleep(); //'Li can Sleep!'
  27. var ming = new Student('xiaoming');
  28. ming.Say(); //'Student-xiaoming can Say!'
  29. ming.Sleep();//'Student-xiaoming can Sleep!'
  30.             // 'And sleep early!'
  31. ming.DoHomeWork(); //'Student-xiaoming can do homework!' 

除了 John Resig 的 supper 方法,很多人都做了嘗試,不過我覺得 John Resig 的實現(xiàn)方式非常的妙,也比較貼近 supper 方法,我本人也用源碼調(diào)試了好幾個小時,才勉強能理解。John Resig 的頭腦真是令人佩服。

ES6 中的 class

在 JS 中,class 從一開始就屬于關(guān)鍵字,在 ES6 終于可以使用 class 來定義類。比如:

 
 
  1. class Point {
  2.   constructor(x, y){
  3.     this.x = x;
  4.     this.y = y;
  5.   }
  6.   toString(){
  7.     return '(' + this.x + ',' + this.y + ')';
  8.   }
  9. }
  10. var p = new Point(3, 4);
  11. console.log(p.toString()); //'(3,4)' 

更多有關(guān)于 ES6 中類的使用請參考阮一峰老師的 Class基本語法。

其實 ES6 中的 class 只是寫對象原型的時候更方便,更像面向?qū)ο?,class 的功能 ES5 完全可以做到,比如就上面的例子:

 
 
  1. typeof Point; //'function'
  2. Point.prototype;
  3. /*
  4. |Object
  5. |--> constructor: function (x, y)
  6. |--> toString: function()
  7. |--> __proto__: Object
  8. */ 

和用 ES5 實現(xiàn)的真的沒有什么差別,反而現(xiàn)在流行的一些庫比 ES6 的 class 能帶來更好的效益。

回到最開始的組件問題

那么,說了這么多面向?qū)ο?,現(xiàn)在回到最開始的那個組件的實現(xiàn)——如何用面向?qū)ο髞韺崿F(xiàn)。

還是利用 John Resig 構(gòu)造 class 的方法:

 
 
  1. var JudgeInput = Class.extend({
  2.   init: function(config){
  3.     this.input = document.getElementById(config.id);
  4.     this._bind();
  5.   },
  6.   _getValue: function(){
  7.     return this.input.value;
  8.   },
  9.   _render: function(){
  10.     var value = this._getValue();
  11.     if(!document.getElementById("show")){
  12.       var append = document.createElement('span');
  13.       append.setAttribute("id", "show");
  14.       input.parentNode.appendChild(append);
  15.     }
  16.     var show = document.getElementById("show");
  17.     if(/^[0-9a-zA-Z]+$/.exec(value)){
  18.       show.innerHTML = 'Pass!';
  19.     }else{
  20.       show.innerHTML = 'Failed!';
  21.     }
  22.   },
  23.   _bind: function(){
  24.     var self = this;
  25.     self.input.addEventListener('keyup', function(){
  26.       self._render();
  27.     });
  28.   }
  29. });
  30. window.onload = function(){
  31.   new JudgeInput({id: "input"});

但是,這樣子,基本功能算是實現(xiàn)了,關(guān)鍵是不好擴展,沒有面向?qū)ο蟮木琛K?,針對目前的情況,我們準備建立一個 Base 基類,init 表示初始化,render 函數(shù)表示渲染,bind 函數(shù)表示綁定,destory 用來銷毀,同時 get、set 方法提供獲得和更改屬性:

 
 
  1. var Base = Class.extend({
  2.   init: function(config){
  3.     this._config = config;
  4.     this.bind();
  5.   },
  6.   get: function(key){
  7.     return this._config[key];
  8.   },
  9.   set: function(key, value){
  10.     this._config[key] = value;
  11.   },
  12.   bind: function(){
  13.     //以后構(gòu)造
  14.   },
  15.   render: function(){
  16.     //以后構(gòu)造
  17.   },
  18.   destory: function(){
  19.     //定義銷毀方法
  20.   }
  21. }); 

基于這個 Base,我們修改 JudgeInput 如下:

 
 
  1. var JudgeInput = Base.extend({
  2.   _getValue: function(){
  3.     return this.get('input').value;
  4.   },
  5.   bind: function(){
  6.     var self = this;
  7.     self.get('input').addEventListener('keyup', function(){
  8.       self.render();
  9.     });
  10.   },
  11.   render: function(){
  12.     var value = this._getValue();
  13.     if(!document.getElementById("show")){
  14.       var append = document.createElement('span');
  15.       append.setAttribute("id", "show");
  16.       input.parentNode.appendChild(append);
  17.     }
  18.     var show = document.getElementById("show");
  19.     if(/^[0-9a-zA-Z]+$/.exec(value)){
  20.       show.innerHTML = 'Pass!';
  21.     }else{
  22.       show.innerHTML = 'Failed!';
  23.     }
  24.   }
  25. });
  26. window.onload = function(){
  27.   new JudgeInput({input: document.getElementById("input")});

比如,我們后期修改了判斷條件,只有當(dāng)長度為 5-10 的時候才會返回 success,這個時候能很快定位到 JudgeInput 的 render 函數(shù):

 
 
  1. render: function(){
  2.   var value = this._getValue();
  3.   if(!document.getElementById("show")){
  4.     var append = document.createElement('span');
  5.     append.setAttribute("id", "show");
  6.     input.parentNode.appendChild(append);
  7.   }
  8.   var show = document.getElementById("show");
  9.   //修改正則即可
  10.   if(/^[0-9a-zA-Z]{5,10}$/.exec(value)){
  11.     show.innerHTML = 'Pass!';
  12.   }else{
  13.     show.innerHTML = 'Failed!';
  14.   }

以我目前的能力,只能理解到這里了。

總結(jié)

從一個組件出發(fā),一步一步爬坑,又跑去介紹 JS 中的面向?qū)ο螅绻隳芸吹?**,那么你就可動手一步一步實現(xiàn)一個 JQuery 了,純調(diào)侃。

關(guān)于一個組件的寫法,從入門級到最終版本,一波三折,不僅要考慮代碼的實用性,還要兼顧后期維護。JS 中實現(xiàn)面向?qū)ο?,剛接觸 JS 的時候,我能用簡單的原型鏈來實現(xiàn),后來看了一些文章,發(fā)現(xiàn)了不少問題,在看 John Resig 的 Class,感觸頗深。還好,現(xiàn)在目的是實現(xiàn)了,共勉!


本文題目:從一個組件的實現(xiàn)來深刻理解JS中的繼承
標題網(wǎng)址:http://www.dlmjj.cn/article/cojgdjo.html