javascript
JavaScript面向对象的理解
前言
1. 本文默認(rèn)閱讀者已有面向?qū)ο蟮拈_發(fā)思想,最好是使用過c++、java,本人Java不太熟悉,所以例子都是用C++來寫的。
2. 本人不是專業(yè)網(wǎng)站開發(fā)人員,接觸javascript一年多,自己也編寫調(diào)試了一些代碼,本文完全根據(jù)自己經(jīng)驗(yàn)所寫,只希望和朋友們分享。文章難免出錯(cuò),希望大家指出,以便及時(shí)改正。
3. 代碼測試環(huán)境:google chrome
正文:
所謂對(duì)象
為了內(nèi)容完整,我先說一些面向?qū)ο蟮臇|西。話說為什么要有面向?qū)ο蟮乃枷?#xff1f;也就是好好的面向過程的程序設(shè)計(jì)不用,干嘛搞出個(gè)面向?qū)ο?OO)?我的理解是為了滿足工程開發(fā)的需要,增加代碼的重復(fù)利用率(通過繼承等),可以提高開發(fā)速度。另外也符合人的思考邏輯,面向過程的代碼相對(duì)比較難看,很難一眼看出來其中的邏輯,面向?qū)ο蟮拇a較好維護(hù)。好了,進(jìn)入正題。JavaScript的開發(fā)方式我認(rèn)為也只有兩種,一是面向過程,二是面向?qū)ο蟆S妹嫦蜻^程就是來一個(gè)問題,寫一個(gè)函數(shù)來解決,這就產(chǎn)生了很多的代碼,而且你以前也得代碼比較難重復(fù)利用(也可以重復(fù)利用,但是當(dāng)你代碼寫多了,你記得清嗎),當(dāng)然對(duì)于相似的操作也可以寫一個(gè)公共函數(shù)庫(貌似JQuery就是的吧?不對(duì)請(qǐng)指出),這比較好容易理解。而面向?qū)ο竽?#xff1f;開發(fā)的過程中我們會(huì)發(fā)現(xiàn),網(wǎng)頁元素有很多操作都是相似的,而且網(wǎng)頁中有很多模塊都是差不多的,這就讓人很容易聯(lián)想到了用面向?qū)ο箝_發(fā)。
再來說說提高代碼重復(fù)利用率的好處。
一、很顯然可以加快開發(fā)速度。
? ?二、減少代碼量,提高網(wǎng)頁加載速度。首先JavaScript是腳本語言。什么是腳本語言呢,腳本語言就是需要有一個(gè)翻譯器才能執(zhí)行的。這個(gè)翻譯器也叫執(zhí)行器、執(zhí)行引擎、執(zhí)行宿主等等。它不是直接生成代碼讓cpu執(zhí)行的,而是給執(zhí)行器看的,執(zhí)行器看懂了,然后執(zhí)行器去通過cpu做那些事情。于是腳本和exe相比,速度較慢,因?yàn)閑xe是直接和cpu對(duì)話的。批處理bat,vb腳本vbs等都是腳本語言。vbs也可以用于網(wǎng)頁喲,它和js有什么不同呢,這里就不說了,不是本文重點(diǎn)。腳本語言的代碼的多少直接影響著網(wǎng)頁加載速度,所以提高代碼重復(fù)利用率,可以提高加載速度,有利于改善用戶體驗(yàn)。
所謂this
好,都說得差不多了,那么怎么面向?qū)ο竽?#xff1f;剛學(xué)JS的時(shí)候,一個(gè)函數(shù)就是一個(gè)函數(shù)。比如
function man( name ) {this.name = name;this.sayName = function(){alert(this.Name);} } 于是乎暈了,哪來的this?又沒有定義對(duì)象哪來的this?于是乎在網(wǎng)上搜索答案,最后知道了原來JS也可以面向?qū)ο蟆H缓缶W(wǎng)上說用function聲明的可以是函數(shù),也可以是對(duì)象。這就讓人有點(diǎn)亂了。本來腳本語言一般都是弱類型的語言,這個(gè)已經(jīng)讓人有點(diǎn)不習(xí)慣了,又來了一個(gè)既可以是函數(shù)又可以是對(duì)象的,頭都大了。時(shí)隔半年后的今天,我在寫程序C++的時(shí)候,順便將JS的面向?qū)ο笥窒肓艘槐椤C菜剖沁@樣的:
JS的面向?qū)ο蠛虲++的面向?qū)ο笫且粯拥摹T贑++里,我們要先聲明一個(gè)類,然后再在.cpp文件中實(shí)現(xiàn)它。而在JavaScript里呢?如果還要聲明一個(gè)類,然后再定義,那得有多少代碼?于是乎JS省去了類的聲明這個(gè)環(huán)節(jié),直接將一個(gè)函數(shù)看成一個(gè)構(gòu)造函數(shù),在定義的同時(shí)也就聲明了它即聲明和定義是一起的,這和變量的使用也是一樣的,如this.name = "呵呵",你只要直接給它賦值它就自動(dòng)產(chǎn)生了。在JS里,所有的函數(shù)可以看成是構(gòu)造函數(shù) 。這樣做是為了減少代碼量,從而提高網(wǎng)頁加載速度。
但是,JS里的函數(shù)又和C++里的構(gòu)造函數(shù)有不同的地方:C++構(gòu)造函數(shù)是沒有任何返回值的,而JS里可以有。為什么呢,因?yàn)闉榱撕喕a,統(tǒng)一使用function來聲明變量不是一樣嗎?何必多一個(gè)關(guān)鍵詞呢?function本來就是聲明函數(shù)的,不過要是想產(chǎn)生對(duì)象的話,它是作為構(gòu)造函數(shù)來用的。
這樣的話,我們?cè)賮砜纯磘his。我記得當(dāng)我學(xué)this的時(shí)候?qū)W得是滿頭霧水。有的說是指向調(diào)用者,恩,對(duì)的,可是我還是沒真正理解調(diào)用者是誰,為什么是調(diào)用者?網(wǎng)頁中那么多DOM對(duì)象,有時(shí)候使用this的時(shí)候生怕使用錯(cuò)了,感覺總是不確定這個(gè)this到底指向誰,比如一個(gè)按鈕的事件響應(yīng)函數(shù),它被調(diào)用時(shí)this就指向按鈕,那么為什么呢,有什么用呢?有沒有一目了然的判斷方法?或者說它和C++里面的this是不是一樣(編程也要追求融匯貫通,否則學(xué)語言就是死記了)?答案是肯定的:
首先你得了解C++里面的this是怎么回事。它是一個(gè)編譯器給類的非靜態(tài)成員函數(shù)加上去的一個(gè)默認(rèn)的參數(shù),這個(gè)類可能產(chǎn)生很多實(shí)例,但是所有實(shí)例共享這些函數(shù),實(shí)例的成員變量一般是不同的,那么這些函數(shù)怎么判斷誰是誰呢?答案就是this,每個(gè)對(duì)象調(diào)用類的成員函數(shù)時(shí),它會(huì)帶著一個(gè)指向自己的一個(gè)指針,把它交給類的成員變量,我們知道地址是唯一的,那么類的成員函數(shù)就根據(jù)這個(gè)地址就找到了這個(gè)實(shí)例所在的地方,從而就能對(duì)這個(gè)地方的內(nèi)存進(jìn)行操作。OK!那么JS也是一樣的,它也有一個(gè)默認(rèn)參數(shù)this,誰調(diào)用它,this就是這個(gè)調(diào)用者的對(duì)象指針(JS里說指針呢不太好,感覺應(yīng)該說是句柄,更直接點(diǎn)說,這個(gè)this就是那個(gè)調(diào)用者)。這樣如果你直接調(diào)用一個(gè)函數(shù)
<pre name="code" class="javascript">function a() {alert( this ); } a(); 結(jié)果是很簡單啦,因?yàn)閃indow是最高層的對(duì)象,那么所謂的全局函數(shù)就是Window對(duì)象的成員函數(shù),所謂的全局變量就是Window的成員變量啦!說到這,可以看出來Js中的對(duì)象是層層嵌套的,也就是C++中的內(nèi)部類,外部類的關(guān)系啦!
那么何時(shí)作為對(duì)象,何時(shí)作為函數(shù)?
答案是:可以作為純對(duì)象,又可以作為純函數(shù),又可以同時(shí)作為對(duì)象和函數(shù),具體返回值看你的調(diào)用方法。推薦一篇文章的參考鏈接:http://www.cnblogs.com/andyliu007/archive/2012/07/27/2795415.html。但是推薦歸推薦,我對(duì)于這篇文章中的觀點(diǎn)并不是完全贊同。下面是一段文章的截圖:
根據(jù)作者的意思,我們可以認(rèn)為:如果一個(gè)函數(shù)有返回值,那么以new的方式使用該函數(shù)時(shí),得到的返回值與函數(shù)的返回值的類型有關(guān),且當(dāng)函數(shù)返回值是基本類型時(shí),得到的返回值為一個(gè)object的對(duì)象;當(dāng)函數(shù)返回值為一個(gè)引用類型的對(duì)象時(shí),那么這個(gè)對(duì)象就是由這個(gè)對(duì)象的原型決定,至于是什么,并不清楚。那么請(qǐng)看以下代碼:
function Test1() {this.id = 1;return 1000; } var myTest = new Test1(); alert( myTest.id ); // 可訪問! alert( typeof myTest );結(jié)果是
第一張圖片的結(jié)果說明了返回的對(duì)象并不是函數(shù)返回值的prototype,而是一個(gè)Test1的對(duì)象。
如果返回一個(gè)對(duì)象呢?
function Test1() {this.id = 1;return new String("我是返回值"); } var myTest = new Test1(); alert( myTest.id ); // 無法訪問 alert( myTest.legth ); alert( typeof myTest );
說明返回的是一個(gè)實(shí)質(zhì)上是String類型而typeof是object的對(duì)象,你也可以顯式地將new的返回值強(qiáng)制類型轉(zhuǎn)換成String,也一切正常。
那么有沒有可能是因?yàn)镾tring是內(nèi)置的類型才可以訪問?返回普通的對(duì)象也是那樣嗎?請(qǐng)看下例
function Test1() {this.id = 1;return new Test2(); }function Test2() {this.id = 2; } var myTest = new Test1(); alert( myTest.id ); alert( typeof myTest );結(jié)果:
說明:
如果函數(shù)返回值是原始類型時(shí),沒用,new返回的還是這個(gè)函數(shù)的對(duì)象。
如果函數(shù)的返回值是對(duì)象時(shí),那么new返回的就是這個(gè)對(duì)象。
不過你如果沒有強(qiáng)制類型轉(zhuǎn)換的話,那么typedef出來的類型將是object。
還有需要注意,原始類型的string和引用類型的String( 首字母大寫 )是不一樣的,一個(gè)是值,一個(gè)是類,類可以有很多屬性和方法,原始類型沒有。
this.name和name的區(qū)別
那么既然談到了this.聲明的變量,那么它和不用this.聲明的變量有什么區(qū)別呢?先看一段C++示例代碼:
A.h文件 class A() {public:A();~A();public:int name ; } A.cpp文件 A::A() {this.name = 0; // 成員變量,和對(duì)象同生命周期int name1 = 1; // 函數(shù)的局部變量,函數(shù)執(zhí)行完,內(nèi)存就會(huì)被其他內(nèi)容覆蓋 } A::~A() { }JS代碼
function A() {this.name = 0; // 成員變量,會(huì)隨對(duì)象一直存在name1 = 1; // 局部變量,會(huì)隨對(duì)象一直存在(為什么這么說?測試出來的) } 可以看出:
1、JS里的對(duì)象沒有過多的訪問修飾符,只有默認(rèn)的public,即都可以通過"對(duì)象.變量名"的形式在外部訪問。
2、JS里的name1有兩種解釋方法
1) ?看成它對(duì)應(yīng)C++構(gòu)造函數(shù)內(nèi)的局部變量(很多文章都稱之為局部變量,如?http://www.jb51.net/article/24101.htm)。這么想的話,那么就有:JS局部變量和C++中的局部變量不同,它和成員變量的生命周期一樣。
2) ?這里我們它想成它對(duì)應(yīng)C++中用private修飾的變量(私有變量):外部不能通過對(duì)象訪問,它的生命周期也和對(duì)象一樣,正好。
我比較偏向 2),因?yàn)榭闯墒菢?gòu)造函數(shù)的局部變量的話,那么一個(gè)類的構(gòu)造函數(shù)是訪問不了的,因?yàn)镴S里根據(jù)變量的函數(shù)作用域可知,里面的函數(shù)可以訪問外面函數(shù)的變量的,這樣才能實(shí)現(xiàn)閉包,二者矛盾。所以1)的類比沒有2)確切。
總結(jié)一下
函數(shù)里帶this的變量相當(dāng)于C++中public修飾的變量。
? ? ? ? 函數(shù)里不帶this的變量相當(dāng)于C++中private修飾的變量。
所謂閉包
那么問題來了,有時(shí)候我們要訪問變量name1啊,怎么辦呢?不能通過"對(duì)象.變量名"的形式,因?yàn)樗皇菍?duì)象的成員變量。怎么辦呢?
方法一:C++中是通過成員函數(shù)來讀寫私有變量的:
那么同樣,JS中你寫一個(gè)成員函數(shù)來讀取或者寫入
function A() {this.name = 0;name1 = 1;this.getName1 = function(){ return name1; } } gN = new A(); alert( gN.getName1() );
結(jié)果:
方法二:
將函數(shù)返回出來
function A() {this.name = 0;name1 = 1;getName1 = function(){ return name1; } return getName1; } gN = new A(); alert( gN() );
結(jié)果
new 和 this
何為new呢?看代碼 function A() {this.name = 1; } a = new A();這個(gè)過程發(fā)生了什么呢?(還是按C++的過程來模擬、類比,如有錯(cuò)誤請(qǐng)指教哈) 1、new一塊內(nèi)存區(qū)域。 2、將這塊區(qū)域的內(nèi)存的地址傳遞給構(gòu)造函數(shù)A() ; 3、運(yùn)行A(),對(duì)這塊區(qū)域進(jìn)行變量拷貝,再加一個(gè)__proto__屬性指向基類。那么這樣,this的作用也就一目了然了,就是實(shí)例對(duì)象的內(nèi)存地址。故call,apply這兩個(gè)JS重要而且難懂的函數(shù)的第一個(gè)參數(shù)this就很容易理解了吧。 這里僅僅說一個(gè)方面,至于原型鏈的指向啊都不說了,具體可以看這篇文章:http://blog.csdn.net/zacklin/article/details/7896859。
關(guān)于繼承
上面說到了call,和apply,下面說一下call方式實(shí)現(xiàn)的繼承(類式繼承)。 建議先看這篇文章:http://segmentfault.com/a/1190000002440502,網(wǎng)站頁面也很漂亮( 話說國內(nèi)很多大網(wǎng)站那頁面真有點(diǎn)難看 )。 下面是代碼 function parent( name ) { this.name = name; this.sayName = function() { alert(this.name); } } function child( name ) { parent.call(this,name); alert(this.sayName); // 相當(dāng)于執(zhí)行了一次this.name = "parent";this.sayName = function(){alert(this.name);} } c = new child("child"); c.sayName();結(jié)果:為什么說"借用構(gòu)造函數(shù)雖然解決了剛才兩種問題,但沒有原型,則復(fù)用無從談起"呢?第一個(gè)alert提示說明了child對(duì)象中也有一個(gè)函數(shù),所以嘍,它沒有使用parent的函數(shù)代碼,即沒有復(fù)用。所以這種繼承會(huì)浪費(fèi)內(nèi)存。但是也不排除比較智能的執(zhí)行器能夠看出來兩個(gè)函數(shù)一樣就只保留一份函數(shù)也說不定,呵呵,應(yīng)該不會(huì)這么智能吧?
再說說組合式繼承
還是參考的這篇文章:http://segmentfault.com/a/1190000002440502。 組合式繼承就是 原型繼承+類式繼承(就是call繼承)。上面我們說了,call不僅會(huì)拷貝變量,而且會(huì)拷貝代碼。那么為什么還用call呢?這么用不就行了: function Parent(age) {this.name = ['mike','jack','smith']; // 這里面只添加屬性this.age = age; } Parent.prototype.run = function () // 方法(成員函數(shù))在這里(原型上)添加 {return this.name + ' are both' + this.age; }; function Child(age) {Parent.call(this,age); // call繼承屬性 } Child.prototype = new Parent(); // 原型鏈繼承方法var test = new Child(21); // 寫new Parent(21)也行alert(test.run()); // mike,jack,smith are both21即只用call來繼承屬性,用原型來繼承方法。也就是假如現(xiàn)在有一個(gè)父類A,如果想讓它被繼承,那么最好將它的屬性定義在構(gòu)造函數(shù)里,將它的方法定義在它的prototype里,然后用call實(shí)現(xiàn)屬性繼承,用prototype直接繼承A,從而繼承方法。還有寄生式繼承:
名字不知道哪來的,反正看不出來什么意思。因?yàn)榻M合式繼承有個(gè)小問題,就是多一次調(diào)用問題。怎了解決呢?只是將組合式繼承稍微改動(dòng)一下,即不直接繼承自A,而是直接繼承自A.prototype,因?yàn)閜rototype是已經(jīng)new好的對(duì)象,沒看出來?這也解釋了為什么設(shè)置prototype時(shí)需要new,這和C++不同,C++只要聲明就可以。看下例Child.prototype = new Parent(); // 原型鏈繼承方法 再上代碼: function A( name,age ) {this.name = name;this.age = age; } A.prototype.sayNameAge = function() {alert(this.name+" "+this.age); }function B( name,age ) {A.call( this,name,age ); } B.prototype = A.prototype; B.constructor = B;<span style="white-space:pre"> </span>//<span style="white-space:pre"> </span>定位回來,都則就指向A,不知道為啥,求大神指教 b = new B( "我是B",2 ); b.sayNameAge();結(jié)果:
注意:prototype的重定向會(huì)導(dǎo)致constructor的變化。所以需要重定向constructor。 感覺寫得比那篇文章中的要簡單呀,\*_*/。
再推薦一個(gè)鏈接:http://www.w3school.com.cn/js/pro_js_referencetypes.asp
最后
說了那么多,都是語法而已,假如某天語法變了,這些都沒用了。但是思路是要有的。 在我看來,JS和C++/Java沒太大區(qū)別,這也說明了編程語言都是相通的。 第一次寫這么長的博文,寫得亂,以后會(huì)修改,各位且湊合看哈!
總結(jié)
以上是生活随笔為你收集整理的JavaScript面向对象的理解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓简单天气预报app源码_七个个小众但
- 下一篇: SpringBoot远程访问redis配