<font id="zqva1"></font>
<rt id="zqva1"></rt>
  • <tt id="zqva1"></tt>
    <cite id="zqva1"></cite>

    <cite id="zqva1"><noscript id="zqva1"></noscript></cite>
      <rp id="zqva1"><meter id="zqva1"></meter></rp>

        <cite id="zqva1"></cite>
          <b id="zqva1"></b>
          <rp id="zqva1"></rp>
          <cite id="zqva1"></cite>

          <rt id="zqva1"></rt>

        1. <rp id="zqva1"></rp>

          JavaScript面向對象思想及繼承(轉載)

          時間:?2017-10-30閱讀:?894標簽:?面向對象作者:?abzerolee

          前言

          為什么說是再談呢,網上講解這個的博客的很多,我開始學習也是看過,敲過就沒了,自以為理解了就結束了,書到用時方恨少啊。實際開發中一用就打磕巴,于是在重新學習了之后分享出來。開了這么一個宏觀的題目,需要做一下簡單說明,這篇文章將會講解以下幾個問題:

          1. 什么是面向對象編程思想,為什么要用面向對象思想。
          2. js中的面向對象思想和其他靜態語言相比有什么不同。
          3. js中prototype,constructor,__proto__這些都是什么鬼?怎么用他們實現面向對象中的繼承。
          4. 一些小感悟小建議之類的吧啦吧啦。

          下面我們直接開始干貨。。。

          面向對象 what's it? why ues it?

          什么是面向對象編程?當作者剛開始工作時,懷著對面向對象編程的無限敬仰和好奇,問了同事Java大牛這個問題,他的回答引我深思:不要面向對象編程,要面向工資編程。言歸正傳,面向對象中的對象,當然不是男女朋友的對象,ECMAScript中,對象是一個無序屬性集,這里的“屬性”可以是基本值、另一個對象或者函數。實際應用可以理解為一本書,一個人,一個班級,所以萬物都是對象。對象的屬性怎么理解,以人為例,指人的名字、身高、體重等等,對象的屬性還可以是函數稱之為方法,指代對象的一些操作,動作。如人的說話,走路等等。提到面向對象,那就需要提到面向過程,我們不用官方的方式來解釋,從實際問題中思考。

          假設現在項目需求為畫一個三角形,一個矩形。直接編寫代碼時,我們肯定考慮的是第一步 畫三角形 第二步 畫矩形。我們會編寫一個三角形函數triangle() 一個矩形函數rect() 然后一步步調用,這是面向過程的思想。

          function triangle() {...}
          function rect() {...}
          
          triangle();
          rect();

          面向對象中我們首先會抽象問題,矩形三角形都是對象,他們的類型都是形狀。他們有各自的邊長頂點,那么我們會先創建一個基本對象 形狀 Shape屬性有頂點、邊長,三角形和Triangle和矩形Rect都是基本對象擴展出的新對象,有各自的畫圖方法draw(),然后用對象得到具體的指向對象(即實例,后文解釋)triangle調用draw方法

          function Shape() {...}
          function Triangle() {...}
          function Rect() {...}
          
          let triangle = new Triang();
          triangle.draw();
          let rect = new Rect();
          rect.draw();

          面對一個問題,面向過程的思路是第一步做什么,第二步做什么 面向對象則需要先分析出問題中的對象都有什么,對象的屬性、方法是什么,讓對象要做什么。
          假設現在需要獲得畫出矩形的邊長,面向對象中只需要在Rect中加上一個方法就可以,面向過程則需要拿到畫出的矩形,再得到邊長,相比較而言面向對象易于擴展。

          面向對象中有三大特征,封裝,繼承,多態。封裝指將變化封裝起來,外面調用時不需要知道內部的實現,繼承指的是一個對象可以共享父級對象的一些屬性,比如上文的問題中,形狀Shape有頂點這個屬性,三角形和矩形都可以繼承該屬性而不需要再重新定義。多態指的是封裝之后的變化如何處理,比如上文中將draw函數放在形狀Shape中,內部實現就是連接點,三角形和矩形調用父級對象的draw,三角形與矩形的頂點不同。

          為什么要使用面向對象?面向對象因為封裝,繼承,多態的特征使程序更易于擴展,維護,重用。比如在另外一個環境中我們需要畫三角形,我們只需要將三角形這個對象及形狀父級對象引入,剩下關于三角形的操作都是三角形這個對象的內部實現。維護起來去該對象的該方法找錯,比在整個環境中找三角形函數要好很多。

          js中的面向對象

          面向對象中類指的是同一類型對象的抽象,首字母大寫,比如上文中的形狀Shape 類,三角形是通過Shape擴展而來,則也是一個類,Shape稱之為它的父類,它是Shape的子類,同理Rect也是Shape的一個子類。類的具體抽象稱之為實例,通常為小寫,創建實例的過程稱之為實例化。上文中triangle就是一個Triangle三角形的實例,指具體畫出的那個三角形。關于父類,子類,實例我們再用一個一個示例來展示

          父類 Animal 
          子類 Cat 實例 cat1_tom
          子類 Dog 實例 dog1

          Animal 指所有動物,Cat 指所有貓 繼承Animal 是動物的一個子類,cat1_tom 指的具體一個叫 tom 的貓。有了類我們就需要給類加一些標識,以區分類之間的區別、即屬性和方法。

          1.走出‘類’,走進原型

          當我們弄清楚了類是什么,JavaScript沒有類的概念,是通過原型來實現面向對象。在以類為中心的面向對象編程語言中,類和對象的關系可以想象成鑄模和鑄件的關系,對象總是從類中創建而來。而在原型編程的思想中,類并不是必需的,對象未必需要從類中創建而來,一個對象是通過克隆另外一個對象所得到的。

          從設計模式的角度講,原型模式是用于創建對象的一種模式,如果我們想要創建一個對象,一種方法是先指定它的類型,然后通過類來創建這個對象。原型模式選擇了另外一種方式,我們不再關心對象的具體類型,而是找到一個對象,然后通過克隆來創建一個一模一樣的對象。而克隆出來的這個對象會記住他的原型,由誰克隆而來,同時也會共享原型的屬性和方法。這樣一個一個對象克隆而來,則形成了一條原型鏈。對上文中的例子而言,三角形的原型是形狀,貓和狗的原型是動物。

          2.構造函數

          在java中new Class()new 之后跟的是一個類名,而在js中類之后跟的是一個構造函數。

          function Shape(name) {
            this.val = 1;
            this.name = name;
            this.all = '圖形';
            return this.name
          }
          let a = Shape('a'); // 'a'
          
          let shape1 = new Shape('triangle'); 
          let shape2 = new Shape('rect');

          構造函數的定義與一般函數的定義相同,注意首字母大寫。構造函數本質上還是一個函數,可以傳參可以有返回值,只是內部使用了this變量,函數存在調用問題:

          1. 直接調用:在瀏覽器環境中相當于在window上掛在了val這個屬性,值為1。請注意這個特點,如果Shape.call(obj) 即相當于設定obj對象的val為1。
          2. new 調用:生成一個實例,即生成一個新對象,這個this指向當前新生成的對象。

          constructor和prototype

          這里的概念還希望大家閱讀緩慢 最好能在瀏覽器或者node環境下敲一下理解更深。請首先一定理解何為實例何為構造函數(構造器)。他們的關系是 
          __A為B的構造函數 則 B為A的一個實例__。

          在山的那邊,海的那邊,有一個prototype ,還有一個__proto__

          首先創建一個Cat的構造函數,希望say是Cat的實例共享屬性,

          function Cat(name) {
            this.name = name;
            this.say = function() {console.log(this.name)};
          }
          
          let cat1 = new Cat('tom'); 
          let cat2 = new Cat('bob');
          cat1.say === cat2.say // false

          但是發現cat1 cat2的共有方法all并沒有共享,每一個實例對象,都有自己的屬性和方法的副本。這不僅無法做到數據共享,也是極大的資源浪費, 那么引入prototype對象:

          function Cat(name) {
            this.name = name;
          }
          Cat.prototype.say = function() {
            console.log(this.name);
          }
          let cat1 = new Cat('tom'); 
          let cat2 = new Cat('bob');
          cat1.say === cat2.say 
          cat1.say === Cat.prototype.say; // true
          cat1.prototype; // undefined
          cat1.hasOwnProperty('say');// false

          __實例對象的constructor屬性指向其構造函數(1)__,這樣看起來實例對象好像“繼承”了prototype對象一樣。__實例沒有prototype__,上文最后一行代碼通過hasOwnPropertyk可以判斷say這個方法并不是cat1自己的方法,__如果一個方法沒有在實例對象自身找到,則向其構造函數prototype中開始尋找(2)__。

          既然實例是繼承自構造器的prototype,那么有沒有一個屬性可以直接表示對象的繼承關系呢?答案是有的__proto__,很多瀏覽器都實現了這個屬性,如下所示。

          cat1.__proto__ === Cat.prototype // true
          Cat.__proto__ === Function.prototype; // true
          Function.prototype.__proto__ === Object.prototype; // true

          從上我們可以發現 Cat 構造器的原型為Function.prototype ,Cat.prototype的原型為Object.prototype,所以當cat1調toString時 Cat.prototype上沒有找到 就去Function.prototype上尋找,這就構成了原型鏈。但是對象的原型鏈查找和構造函數的原型查找又有一點小區別(不查Function),構造器生成的實例對象原型鏈的查找過程可以如下表示:

          cat1 
           => cat1.__proto__(Cat.prototype) 
           => cat1.__proto__.__proto__(Function.prototype) 
           => cat1.__proto__.__proto__.__proto__ (Object.prototype)

          還有通過對象字面量創建的對象的原型鏈查找方式

          let obj = {};
          obj => obj.__proto__(Object.prototype) ;

          這里根據上文__加粗(2)__的語言可以得到__Function.prototype 的構造函數是Object(3)__。關于兩者的關系,我們后續繼續討論。

          大家都有constructor

          上文的兩個實例對象cat1 cat2,他們都具有一個屬性constructor,指向實例的構建函數Cat,意思是他們由Cat創建而來。__實例有一個constructor屬性,指向其構造函數(4)__

          cat1.constructor === Cat; // true
          cat1.constructor === Cat; // true
          Cat.constructor === Function; // true
          Cat.prototype.constructor === Cat; // true
          
          Object.constructor === Function;// true

          構造函數同樣具有construtor,指向Function,Cat.prototype同樣具有construtor,指向他自身,__構造函數的prototype對象的constructor指向該構造函數(5)__。

          根據上文最后一行代碼 可以判斷Object 的構造函數 是Function。則我們可以得到Object是Function的一個實例。如下Object 與 Function的關系是

          1. Object是Function的一個實例。
          2. Function.prototype 是 Object 的 一個實例。

          根據上文總結如下:

          1. 實例對象的constructor指向其構造器。
          2. 實例對象沒有prototype。
          3. 實例對象可以通過構造函數的prototype對象實現屬性方法共享。’
          4. 實例對象的__proto__ 原型指向其構造函數的prototype對象
          5. 構造器的constructor指向 Function。
          6. 構造函數的prototype可以掛在公共屬性方法,prototype的constructor屬性指向該構造函數。
          7. 構造函數 的__proto__ 原型指向 Function.prototype。
          8. 構造函數prototype對象的 __proto__ 原型指向Object.prototype。
          9. 對象原型指的是對象的 __proto__ 屬性。

          繼承方式的漸進式

          通過上面的知識我們已經了解了原型的概念,接下來我們來一步一步實現基于原型的繼承。
          在繼承之前,我們有必要統一一下概念及名詞,

          實例的歸實例 構造器的歸構造器
          function Animal(name) {
            let name = name; // 私有屬性
            this.getName = function() { // 特權方法 也是實例方法
                this.log(name);
              return name;
            }
            this.color = 'none'; // 實例屬性
            this.say = function() { // 實例方法
              console.log(this.color);
            }
          }
          Animal.prototype.a = 1; // 公共屬性
          Animal.prototype.log = function(sth) { // 公共方法
            consoel.log(sth)
          }

          js沒有嚴格意義的私有成員,所以對象屬性都算做公開,所以我們在私有 公有上不做贅述,只是判斷改屬性是在實例上 還是在構造函數的prototype上。

          1. 私有屬性:指的是構造器內部的屬性,構造器外部不可以獲得,只能通過特權方法來訪問。
          2. 特權方法:一般稱有權訪問私有變量和私有函數的公有方法為特權方法,但是js沒有共有方法的概念,這個方法是掛載在實例上的。
          3. 實例屬性(方法):實例屬性指的是掛載在實例自身的屬性。
          4. 公共屬性(方法):公共屬性指的是掛在在構造器的prototype對象上的屬性。
          1. 直接修改prototype

          我們已經知道實例對象可以通過構造函數的prototype對象實現屬性方法共享。即實例對象繼承了構造器的.prototype對象,那么構造器和構造器之間的繼承是不是也可以用這樣的方式。

          function Animal() {
            this.special = '貓';
          };
          function Cat() {}
          let cat1 = new Cat();

          如上,cat1要繼承Animal的special屬性,

          1. 首先 cat1 作為構造器Cat 的一個實例可以繼承 Cat.prototype 對象中得屬性。
          2. Cat.prototype 作為一個對象則應該繼承 Animal.protoype.
          3. Cat.prototype 應該作為構造函數Animal的一個實例。
          function Animal() {
            this.special = '貓';
            this.arr = [2,3];
          };
          function Cat() {}
          Cat.prototype = new Animal();
          let cat1 = new Cat();
          cat1.special; // '貓';
          
          let cat2 = new Cat();
          cat1.special = '狗';
          cat2.special; // '貓'
          cat1.special === Cat.prototype.special; // false
          cat1.arr.push(1);
          cat1.arr; // [2,3,1];
          cat1.arr; // [2,3,1];

          雖然我們很簡單就實現了繼承,但是問題一轉變,就出現了bug。比如我現在希望cat1 cat2 的special 都是公共屬性,arr 是實例屬性。可以發現cat1操作了special 這個公共屬性,cat2.special并沒有改變,但是cat1.arr 改變后 cat2.arr 也改變了。其次,構造器之間的繼承不能傳遞參數,那讓我們更正2.0

          2. 構造函數的函數特性
          function Animal(name) {
            this.name = name;
            this.arr = [2,3];
          };
          Animal.prototype.special = '貓';
          
          function Cat(name) { 
            Animal.apply(this, arguments);
          }
          
          Cat.prototype = new Animal();
          
          let cat1 = new Cat('tom');
          let cat2 = new Cat('mary');
          
          cat1.special = '狗'; 
          cat2.special; // 貓;
          cat1.hasOwnProperty('special'); // true
          cat2.hasOwnProperty('special;); // false,
          
          cat1.arr.push(1);
          cat1.arr; // [2,3,1];
          cat2.arr; // [2,3];
          
          cat1.name; // 'tom'
          cat2.name; // 'mary'

          special作為公共的屬性掛載在父級構造器prototype上,雖然我們修改了cat1.special cat2.special沒有改變,這主要是因為cat1.special 的改變是作用在實例而不是原型上,大家可以把這個公共屬性改成數組或對象 作為一個引用存儲,就可以發現special是公共屬性。cat1.arr的操作不影響cat2.arr的操作。而且可以實現構造器直接傳參,這里實在子級構造器的內部直接調用父級構造器,構造器調用方式的區別前文也介紹過了。

          看到這里,好像我們已經實現繼承了,但是依然存在問題啊。代碼的構建從來都是改大于寫。

          cat1.constructor; // [Function: Animal]

          前文提到實例對象的constructor屬性應該指向其構造函數,這里直接指向了父級構造器;在Cat構造器內部有一份Animal的實例屬性,在Cat.prototype上同樣有一份Animal的實例屬性,屬性重復。

          3. 利用空構造器過濾實例屬性
          function Animal(name) {
            this.name = name;
            this.arr = [2,3];
          };
          Animal.prototype.special = '貓';
          
          function Cat(name) { 
            Animal.apply(this, arguments);
          }
          
          let F = function() {};
          F.prototype = Animal.prototype;
          Cat.prototype = new F();
          Cat.prototype.constructor = Cat;
          Cat.__proto__ = Animal.prototype;
          
          let cat1 = new Cat('tom');
          let cat2 = new Cat('mary');
          
          cat1.constructor;

          這里新建了一個空構造器 F() 讓F.prototype = Animal.prototype,子級構造器
          Cat.prototype = new F(); 這樣在Cat.prototype中就沒有那一份Animal實例化之后的數據。再將Cat.prototype.constructor 重新指會 構造器本身,則cat1.constructor ye的指向也沒有問題了。同時修正了Cat的原型指向。

          最后

          首先感謝閱讀完全文,到這里,相信基本對于原型繼承實現面向對象編程沒有什么問題了。之后的主要矛盾在于問題的抽象上,如何抽象合適的對象,哪些屬性和方法作為公共的,哪些作為實例的,這只有日積月累的經驗才能給自己最好的答案。關鍵還是在于理解了基礎概念,多用,多練,就會發先問題。我就是自以為理解了,但是在construtor指向上老犯糊涂,還有關于Object 與 Function,多用是加深理解的最好方式了,不妨以后再解決問題是,多考慮一下面向對象。

          其次,不能限定自己必須使用什么,不管是黑貓還是白貓,抓住老鼠就是好貓,代碼的最終目的是為解決問題而生,同時代碼是用來讀的,不論是什么樣的編程思路,邏輯清晰,可擴展,可復用,健壯性完好那就是好代碼。

          最后的最后,文中若有錯誤,還請及時指正。最后一個學習方法的分享,當接觸一個新的知識點或者工具,1.先會用 知道這個東西是什么(what?) 怎么用(how?), 2. 會用之后不妨了解一下原理看看內部實現(why?),3. 等研究的比較深刻了,自然而然對在何種情況使用(where, when)。編程學習還是要帶著問題去學習,有問題,才會記得更深刻,沒問題的兩種人,要么真的會了,要么一點都不會,再次感謝閱讀~~~~來源:再談JavaScript面向對象思想及繼承

          站長推薦

          1.阿里云: 本站目前使用的是阿里云主機,安全/可靠/穩定。點擊領取2000元代金券、了解最新阿里云產品的各種優惠活動點擊進入

          2.騰訊云: 提供云服務器、云數據庫、云存儲、視頻與CDN、域名等服務。騰訊云各類產品的最新活動,優惠券領取點擊進入

          3.廣告聯盟: 整理了目前主流的廣告聯盟平臺,如果你有流量,可以作為參考選擇適合你的平臺點擊進入

          鏈接: http://www.modern-decoration.com.cn/article/detial/28

          面向對象的反思

          在面向對象的設計中,系統是由對象和讓對象狀態發生改變的方法,讓對象到達另一種狀態來達到目的的。當系統漸漸變大,對象漸漸變多,每個對象間的糾纏越來越多的時候一個對象的狀態受到多個控制信號的機會就越來越多

          面向對象三大特征(封裝,繼承,多態)

          代碼封裝可以避免代碼冗余,避免去哪聚變量污染,本質就是把公共的代碼抽離出來在需要的地方隨時調用在繼承中也有體現

          淺析面向過程與面向對象

          在面試時經常會被問到面向過程和面向對象有什么區別,雖然都是編程的一種思想,但是他們的側重點不同,我們從以下幾個方面進行簡單總結。

          ES6 class與面向對象編程

          在ES5中,我們經常使用方法或者對象去模擬類的使用,并基于原型實現繼承,雖然可以實現功能,但是代碼并不優雅,很多人還是傾向于用 class 來組織代碼,很多類庫、框架創造了自己的 API 來實現 class 的功能。

          ES6 類繼承 和 super的使用

          ES6中繼承的子類中,如果使用構造函數constructor()那么就必須使用 super()方法初始化,這樣下面才可以調用this關鍵字。super()只能用在子類的構造函數之中,用在其他地方就會報錯。

          面向對象設計與分析

          面向對象方法是一種運用對象,類,繼承,封裝,聚合,關聯,消息,多態等概念和原則來構造軟件系統的開發思想(方法)。

          面向對象設計的6個設計原則

          面向對象設計的6個設計原則:1.單一職責原則,2.里氏替換原則,3.依賴倒置原則,4.接口隔離原則,5.迪米特法則,6.開閉原則

          內容以共享、參考、研究為目的,不存在任何商業目的。其版權屬原作者所有,如有侵權或違規,請與小編聯系!情況屬實本人將予以刪除!

          文章投稿關于web前端網站點搜索站長推薦網站地圖站長QQ:522607023

          小程序專欄: 土味情話心理測試腦筋急轉彎幽默笑話段子句子語錄成語大全運營推廣

          国产精品高清视频免费 - 视频 - 在线观看 - 影视资讯 - 唯爱网