中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何從一個組件的實現來深刻理解JS中的繼承

發布時間:2021-11-16 17:33:48 來源:億速云 閱讀:92 作者:柒染 欄目:web開發

今天就跟大家聊聊有關如何從一個組件的實現來深刻理解JS中的繼承,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。

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

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

從一個例子來學習寫組件

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

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

如何從一個組件的實現來深刻理解JS中的繼承

全局變量寫法

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

// html <input type="text" id="input"/> // javascript var input = document.getElementById("input"); function getValue(){   return input.value; } function render(){   var value = getValue();   if(!document.getElementById("show")){     var append = document.createElement('span');     append.setAttribute("id", "show");     input.parentNode.appendChild(append);   }   var show = document.getElementById("show");   if(/^[0-9a-zA-Z]+$/.exec(value)){     show.innerHTML = 'Pass!';   }else{     show.innerHTML = 'Failed!';   } } input.addEventListener('keyup', function(){   render(); });

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

對象隔離作用域

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

var obj = {   input: null,   // 初始化并提供入口調用方法   init: function(config){     this.input = document.getElementById(config.id);     this.bind();     //鏈式調用     return this;   },   // 綁定   bind: function(){     var self = this;     this.input.addEventListener('keyup', function(){       self.render();     });   },   getValue: function(){     return this.input.value;   },   render: function(){     var value = this.getValue();     if(!document.getElementById("show")){       var append = document.createElement('span');       append.setAttribute("id", "show");       input.parentNode.appendChild(append);     }     var show = document.getElementById("show");     if(/^[0-9a-zA-Z]+$/.exec(value)){       show.innerHTML = 'Pass!';     }else{       show.innerHTML = 'Failed!';     }   } } window.onload = function(){   obj.init({id: "input"}); }

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

新手能實現上面的方法已經很不錯了,還記得當初做百度前端學院題目的時候,基本就是用對象了。

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

函數閉包的寫法

var fun = (function(){   var _bind = function(obj){     obj.input.addEventListener('keyup', function(){       obj.render();     });   }   var _getValue = function(obj){     return obj.input.value;   }   var InputFun = function(config){};   InputFun.prototype.init = function(config){     this.input = document.getElementById(config.id);     _bind(this);     return this;   }   InputFun.prototype.render = function(){     var value = _getValue(this);     if(!document.getElementById("show")){       var append = document.createElement('span');       append.setAttribute("id", "show");       input.parentNode.appendChild(append);     }     var show = document.getElementById("show");     if(/^[0-9a-zA-Z]+$/.exec(value)){       show.innerHTML = 'Pass!';     }else{       show.innerHTML = 'Failed!';     }   }   return InputFun; })(); window.onload = function(){   new fun().init({id: 'input'}); }

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

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

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

基本上,這已經是一個合格的寫法了。

面向對象

雖然上面的方法以及夠好了,但是我們的目的,是為了使用面向對象。面向對象一直以來都是被認為***的編程方式,如果每個人的代碼風格都相似,維護、查看起來就非常的方便。

但是,我想在介紹面向對象之前,先來回憶一下 JS 中的繼承(實現我們放到***再說)。

入門級的面向對象

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

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

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

得到的 student 是一個對象,__proto__執行 Person 的 prototype,Person.call 相當于  constructor。

function Person(name){   this.name = name; } Person.prototype.Say = function(){   console.log(this.name + ' can say!'); } var ming = new Person("xiaoming"); console.log(ming.__proto__ == Person.prototype) //true new的第二步結果 console.log(ming.name) // 'xiaoming' new 的第三步結果 ming.Say() // 'xiaoming can say!' proto 向上追溯的結果

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

function Person(name){   this.name = name; } Person.prototype.Say = function(){   console.log(this.name + ' can say!'); } function Student(name){   Person.call(this, name); //Person 的屬性賦值給 Student } Student.prototype = new Person(); //順序不能反,要在最前面 Student.prototype.DoHomeWork = function(){   console.log(this.name + ' can do homework!'); } var ming = new Student("xiaoming"); ming.DoHomeWork(); //'xiaoming can do homework!' ming.Say(); //'xiaoming can say!'

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

打開調試工具,看一下 ming 都有哪些東西:

ming   name: "xiaoming"   __proto__: Person     DoHomeWork: ()     name: undefined //注意這里多了一個 name 屬性     __proto__: Object       Say: ()       constructor: Person(name)       __proto__: Object

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

這就是最簡單的繼承了。

面向對象的進階

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

  1. 沒有實現傳統面向對象該有的 super 方法來調用父類方法,鏈式和 super 方法相比還是有一定缺陷的;

  2. 造成過多的原型屬性(name),constructor 丟失(constructor 是一個非常重要的屬性,MDN)。

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

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

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

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

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

如何從一個組件的實現來深刻理解JS中的繼承

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

Student.prototype = new Person();

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

如何從一個組件的實現來深刻理解JS中的繼承

找到了問題所在,現在來改進:

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

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

面向對象的封裝

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

function classInherit(subClass, parentClass){   var fn = function(){};   fn.prototype = parentClass.prototype;   subClass.prototype = new fn();   subClass.prototype.constructor = subClass; } classInherit(Student, Person);

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

進一步完善面向對象

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

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

Person.prototype.Sleep = function(){   console.log('Sleep!'); } function E_Student(){}; //小學生 function C_Student(){}; //大學生 classInherit(E_Student, Person); classInherit(C_Student, Person); //重寫 Sleep 方法 E_Student.prototype.Sleep = function(){   console.log('Sleep!');   console.log('Sleep at 9 clock'); } C_Student.prototype.Sleep = function(){   console.log('Sleep!');   console.log('Sleep at 12 clock'); }

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

E_Student.prototype.Sleep = function(){   this._supper(); //supper 方法   console.log('Sleep at 9 clock'); } C_Student.prototype.Sleep = function(){   this._supper(); //supper 方法   console.log('Sleep at 12 clock'); }

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

來看下 JQuery 之父是如何 class 的面向對象,原文在這,源碼如下。

/* Simple JavaScript Inheritance  * By John Resig http://ejohn.org/  * MIT Licensed.  */ // Inspired by base2 and Prototype (function(){   // initializing 開關很巧妙的來實現調用原型而不構造,還有回掉   var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;   // The base Class implementation (does nothing)   // 全局,this 指向 window,***的父類   this.Class = function(){};     // Create a new Class that inherits from this class   // 繼承的入口   Class.extend = function(prop) {     //保留當前類,一般是父類的原型     var _super = this.prototype;         // Instantiate a base class (but only create the instance,     // don't run the init constructor)     //開關 用來使原型賦值時不調用真正的構成流程     initializing = true;     var prototype = new this();     initializing = false;         // Copy the properties over onto the new prototype     for (var name in prop) {       // Check if we're overwriting an existing function       //對函數判斷,將屬性套到子類上       prototype[name] = typeof prop[name] == "function" &&         typeof _super[name] == "function" && fnTest.test(prop[name]) ?         (function(name, fn){           //用閉包來存儲           return function() {             var tmp = this._super;                         // Add a new ._super() method that is the same method             // but on the super-class             this._super = _super[name];                         // The method only need to be bound temporarily, so we             // remove it when we're done executing             //實現同名調用             var ret = fn.apply(this, arguments);               this._super = tmp;             return ret;           };         })(name, prop[name]) :         prop[name];     }         // 要返回的子類     function Class() {       // All construction is actually done in the init method       if ( !initializing && this.init )         this.init.apply(this, arguments);     }     //前面介紹過的,繼承     Class.prototype = prototype;         Class.prototype.constructor = Class;       Class.extend = arguments.callee;         return Class;   }; })();

這個時候就可以很輕松的實現面向對象,使用如下:

var Person = Class.extend({   init: function(name){     this.name = name;   },   Say: function(name){     console.log(this.name + ' can Say!');   },   Sleep: function(){     console.log(this.name + ' can Sleep!');   } }); var Student = Person.extend({   init: function(name){     this._super('Student-' + name);   },   Sleep: function(){     this._super();     console.log('And sleep early!');   },   DoHomeWork: function(){     console.log(this.name + ' can do homework!');   } }); var p = new Person('Li'); p.Say(); //'Li can Say!' p.Sleep(); //'Li can Sleep!' var ming = new Student('xiaoming'); ming.Say(); //'Student-xiaoming can Say!' ming.Sleep();//'Student-xiaoming can Sleep!'             // 'And sleep early!' ming.DoHomeWork(); //'Student-xiaoming can do homework!'

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

ES6 中的 class

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

class Point {   constructor(x, y){     this.x = x;     this.y = y;   }   toString(){     return '(' + this.x + ',' + this.y + ')';   } } var p = new Point(3, 4); console.log(p.toString()); //'(3,4)'

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

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

typeof Point; //'function' Point.prototype; /* |Object |--> constructor: function (x, y) |--> toString: function() |--> __proto__: Object */

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

回到最開始的組件問題

那么,說了這么多面向對象,現在回到最開始的那個組件的實現&mdash;&mdash;如何用面向對象來實現。

還是利用 John Resig 構造 class 的方法:

var JudgeInput = Class.extend({   init: function(config){     this.input = document.getElementById(config.id);     this._bind();   },   _getValue: function(){     return this.input.value;   },   _render: function(){     var value = this._getValue();     if(!document.getElementById("show")){       var append = document.createElement('span');       append.setAttribute("id", "show");       input.parentNode.appendChild(append);     }     var show = document.getElementById("show");     if(/^[0-9a-zA-Z]+$/.exec(value)){       show.innerHTML = 'Pass!';     }else{       show.innerHTML = 'Failed!';     }   },   _bind: function(){     var self = this;     self.input.addEventListener('keyup', function(){       self._render();     });   } }); window.onload = function(){   new JudgeInput({id: "input"}); }

但是,這樣子,基本功能算是實現了,關鍵是不好擴展,沒有面向對象的精髓。所以,針對目前的情況,我們準備建立一個 Base 基類,init  表示初始化,render 函數表示渲染,bind 函數表示綁定,destory 用來銷毀,同時 get、set 方法提供獲得和更改屬性:

var Base = Class.extend({   init: function(config){     this._config = config;     this.bind();   },   get: function(key){     return this._config[key];   },   set: function(key, value){     this._config[key] = value;   },   bind: function(){     //以后構造   },   render: function(){     //以后構造   },   destory: function(){     //定義銷毀方法   } });

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

var JudgeInput = Base.extend({   _getValue: function(){     return this.get('input').value;   },   bind: function(){     var self = this;     self.get('input').addEventListener('keyup', function(){       self.render();     });   },   render: function(){     var value = this._getValue();     if(!document.getElementById("show")){       var append = document.createElement('span');       append.setAttribute("id", "show");       input.parentNode.appendChild(append);     }     var show = document.getElementById("show");     if(/^[0-9a-zA-Z]+$/.exec(value)){       show.innerHTML = 'Pass!';     }else{       show.innerHTML = 'Failed!';     }   } }); window.onload = function(){   new JudgeInput({input: document.getElementById("input")}); }

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

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

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

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

看完上述內容,你們對如何從一個組件的實現來深刻理解JS中的繼承有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

乳源| 吉安县| 日喀则市| 洮南市| 黄石市| 江津市| 商南县| 兴业县| 襄汾县| 马尔康县| 交城县| 皋兰县| 沂水县| 泾阳县| 泰顺县| 团风县| 临夏市| 科技| 苍山县| 汉川市| 泸溪县| 滕州市| 仁化县| 莱西市| 绩溪县| 呼和浩特市| 密云县| 开远市| 荆州市| 古田县| 满洲里市| 西盟| 孝昌县| 阿克| 海门市| 石首市| 普安县| 汤原县| 凤庆县| 榕江县| 历史|