日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

JavaScript面向对象的理解

發布時間:2023/12/10 javascript 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JavaScript面向对象的理解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言


1. 本文默認閱讀者已有面向對象的開發思想,最好是使用過c++、java,本人Java不太熟悉,所以例子都是用C++來寫的。
2. 本人不是專業網站開發人員,接觸javascript一年多,自己也編寫調試了一些代碼,本文完全根據自己經驗所寫,只希望和朋友們分享。文章難免出錯,希望大家指出,以便及時改正。

3. 代碼測試環境:google chrome


正文:

所謂對象

為了內容完整,我先說一些面向對象的東西。話說為什么要有面向對象的思想?也就是好好的面向過程的程序設計不用,干嘛搞出個面向對象(OO)?我的理解是為了滿足工程開發的需要,增加代碼的重復利用率(通過繼承等),可以提高開發速度。另外也符合人的思考邏輯,面向過程的代碼相對比較難看,很難一眼看出來其中的邏輯,面向對象的代碼較好維護。
好了,進入正題。JavaScript的開發方式我認為也只有兩種,一是面向過程,二是面向對象。用面向過程就是來一個問題,寫一個函數來解決,這就產生了很多的代碼,而且你以前也得代碼比較難重復利用(也可以重復利用,但是當你代碼寫多了,你記得清嗎),當然對于相似的操作也可以寫一個公共函數庫(貌似JQuery就是的吧?不對請指出),這比較好容易理解。而面向對象呢?開發的過程中我們會發現,網頁元素有很多操作都是相似的,而且網頁中有很多模塊都是差不多的,這就讓人很容易聯想到了用面向對象開發。
再來說說提高代碼重復利用率的好處。

一、很顯然可以加快開發速度。
? ?二、減少代碼量,提高網頁加載速度。首先JavaScript是腳本語言。什么是腳本語言呢,腳本語言就是需要有一個翻譯器才能執行的。這個翻譯器也叫執行器、執行引擎、執行宿主等等。它不是直接生成代碼讓cpu執行的,而是給執行器看的,執行器看懂了,然后執行器去通過cpu做那些事情。于是腳本和exe相比,速度較慢,因為exe是直接和cpu對話的。批處理bat,vb腳本vbs等都是腳本語言。vbs也可以用于網頁喲,它和js有什么不同呢,這里就不說了,不是本文重點。腳本語言的代碼的多少直接影響著網頁加載速度,所以提高代碼重復利用率,可以提高加載速度,有利于改善用戶體驗。


所謂this

好,都說得差不多了,那么怎么面向對象呢?剛學JS的時候,一個函數就是一個函數。比如

function a() {alert("呵呵噠"); } a(); 定義完直接調用即可,很簡單。但是,繼續學,又看到了這樣的代碼:
function man( name ) {this.name = name;this.sayName = function(){alert(this.Name);} } 于是乎暈了,哪來的this?又沒有定義對象哪來的this?于是乎在網上搜索答案,最后知道了原來JS也可以面向對象。然后網上說用function聲明的可以是函數,也可以是對象。這就讓人有點亂了。本來腳本語言一般都是弱類型的語言,這個已經讓人有點不習慣了,又來了一個既可以是函數又可以是對象的,頭都大了。時隔半年后的今天,我在寫程序C++的時候,順便將JS的面向對象又想了一遍。貌似是這樣的:
JS的面向對象和C++的面向對象是一樣的。在C++里,我們要先聲明一個類,然后再在.cpp文件中實現它。而在JavaScript里呢?如果還要聲明一個類,然后再定義,那得有多少代碼?于是乎JS省去了類的聲明這個環節,直接將一個函數看成一個構造函數,在定義的同時也就聲明了它即聲明和定義是一起的,這和變量的使用也是一樣的,如this.name = "呵呵",你只要直接給它賦值它就自動產生了。在JS里,所有的函數可以看成是構造函數 。這樣做是為了減少代碼量,從而提高網頁加載速度。
但是,JS里的函數又和C++里的構造函數有不同的地方:C++構造函數是沒有任何返回值的,而JS里可以有。為什么呢,因為為了簡化代碼,統一使用function來聲明變量不是一樣嗎?何必多一個關鍵詞呢?function本來就是聲明函數的,不過要是想產生對象的話,它是作為構造函數來用的。
這樣的話,我們再來看看this。我記得當我學this的時候學得是滿頭霧水。有的說是指向調用者,恩,對的,可是我還是沒真正理解調用者是誰,為什么是調用者?網頁中那么多DOM對象,有時候使用this的時候生怕使用錯了,感覺總是不確定這個this到底指向誰,比如一個按鈕的事件響應函數,它被調用時this就指向按鈕,那么為什么呢,有什么用呢?有沒有一目了然的判斷方法?或者說它和C++里面的this是不是一樣(編程也要追求融匯貫通,否則學語言就是死記了)?答案是肯定的:
首先你得了解C++里面的this是怎么回事。它是一個編譯器給類的非靜態成員函數加上去的一個默認的參數,這個類可能產生很多實例,但是所有實例共享這些函數,實例的成員變量一般是不同的,那么這些函數怎么判斷誰是誰呢?答案就是this,每個對象調用類的成員函數時,它會帶著一個指向自己的一個指針,把它交給類的成員變量,我們知道地址是唯一的,那么類的成員函數就根據這個地址就找到了這個實例所在的地方,從而就能對這個地方的內存進行操作。OK!那么JS也是一樣的,它也有一個默認參數this,誰調用它,this就是這個調用者的對象指針(JS里說指針呢不太好,感覺應該說是句柄,更直接點說,這個this就是那個調用者)。這樣如果你直接調用一個函數

<pre name="code" class="javascript">function a() {alert( this ); } a();

結果是很簡單啦,因為Window是最高層的對象,那么所謂的全局函數就是Window對象的成員函數,所謂的全局變量就是Window的成員變量啦!說到這,可以看出來Js中的對象是層層嵌套的,也就是C++中的內部類,外部類的關系啦!

那么何時作為對象,何時作為函數?

答案是:可以作為純對象,又可以作為純函數,又可以同時作為對象和函數,具體返回值看你的調用方法。推薦一篇文章的參考鏈接:http://www.cnblogs.com/andyliu007/archive/2012/07/27/2795415.html。但是推薦歸推薦,我對于這篇文章中的觀點并不是完全贊同。下面是一段文章的截圖:

根據作者的意思,我們可以認為:如果一個函數有返回值,那么以new的方式使用該函數時,得到的返回值與函數的返回值的類型有關,且當函數返回值是基本類型時,得到的返回值為一個object的對象;當函數返回值為一個引用類型的對象時,那么這個對象就是由這個對象的原型決定,至于是什么,并不清楚。那么請看以下代碼:

function Test1() {this.id = 1;return 1000; } var myTest = new Test1(); alert( myTest.id ); // 可訪問! alert( typeof myTest );結果是



第一張圖片的結果說明了返回的對象并不是函數返回值的prototype,而是一個Test1的對象。

如果返回一個對象呢?

function Test1() {this.id = 1;return new String("我是返回值"); } var myTest = new Test1(); alert( myTest.id ); // 無法訪問 alert( myTest.legth ); alert( typeof myTest );




說明返回的是一個實質上是String類型而typeof是object的對象,你也可以顯式地將new的返回值強制類型轉換成String,也一切正常。

那么有沒有可能是因為String是內置的類型才可以訪問?返回普通的對象也是那樣嗎?請看下例

function Test1() {this.id = 1;return new Test2(); }function Test2() {this.id = 2; } var myTest = new Test1(); alert( myTest.id ); alert( typeof myTest );結果:



說明:

如果函數返回值是原始類型時,沒用,new返回的還是這個函數的對象。

如果函數的返回值是對象時,那么new返回的就是這個對象。

不過你如果沒有強制類型轉換的話,那么typedef出來的類型將是object。

還有需要注意,原始類型的string和引用類型的String( 首字母大寫 )是不一樣的,一個是值,一個是類,類可以有很多屬性和方法,原始類型沒有。


this.name和name的區別

那么既然談到了this.聲明的變量,那么它和不用this.聲明的變量有什么區別呢?先看一段C++示例代碼:

A.h文件 class A() {public:A();~A();public:int name ; } A.cpp文件 A::A() {this.name = 0; // 成員變量,和對象同生命周期int name1 = 1; // 函數的局部變量,函數執行完,內存就會被其他內容覆蓋 } A::~A() { }JS代碼

function A() {this.name = 0; // 成員變量,會隨對象一直存在name1 = 1; // 局部變量,會隨對象一直存在(為什么這么說?測試出來的) }

可以看出:
1、JS里的對象沒有過多的訪問修飾符,只有默認的public,即都可以通過"對象.變量名"的形式在外部訪問。
2、JS里的name1有兩種解釋方法

1) ?看成它對應C++構造函數內的局部變量(很多文章都稱之為局部變量,如?http://www.jb51.net/article/24101.htm)。這么想的話,那么就有:JS局部變量和C++中的局部變量不同,它和成員變量的生命周期一樣。

2) ?這里我們它想成它對應C++中用private修飾的變量(私有變量):外部不能通過對象訪問,它的生命周期也和對象一樣,正好。

我比較偏向 2),因為看成是構造函數的局部變量的話,那么一個類的構造函數是訪問不了的,因為JS里根據變量的函數作用域可知,里面的函數可以訪問外面函數的變量的,這樣才能實現閉包,二者矛盾。所以1)的類比沒有2)確切。

總結一下

函數里帶this的變量相當于C++中public修飾的變量。

? ? ? ? 函數里不帶this的變量相當于C++中private修飾的變量。


所謂閉包

那么問題來了,有時候我們要訪問變量name1啊,怎么辦呢?不能通過"對象.變量名"的形式,因為它不是對象的成員變量。怎么辦呢?

方法一:C++中是通過成員函數來讀寫私有變量的:

A.h文件 class A() {public:A();~A();public:int name ;private:int name1;public:getName(); } A.cpp文件 A::A() {this.name = 0; // 成員變量,和對象同生命周期this.name1 = 1; } A::~A() { } A::getName() {return this.name1; }
那么同樣,JS中你寫一個成員函數來讀取或者寫入
function A() {this.name = 0;name1 = 1;this.getName1 = function(){ return name1; } } gN = new A(); alert( gN.getName1() );
結果:

方法二:

將函數返回出來

function A() {this.name = 0;name1 = 1;getName1 = function(){ return name1; } return getName1; } gN = new A(); alert( gN() );

結果


這種方法涉及到兩次返回,不容易理解,但是這種方法在JS里比較有名,叫做閉包。不過我個人推薦用成員函數來返回局部變量(也可以叫做私有成員變量)。因為將一個函數返回出來保存在了"全局變量中,這導致對象始終在內存中"( 引用自?http://www.jb51.net/article/24101.htm?)。通過成員函數方法返回的也一樣,也是始終存在于內存。

new 和 this

何為new呢?看代碼 function A() {this.name = 1; } a = new A();這個過程發生了什么呢?(還是按C++的過程來模擬、類比,如有錯誤請指教哈) 1、new一塊內存區域。 2、將這塊區域的內存的地址傳遞給構造函數A() ; 3、運行A(),對這塊區域進行變量拷貝,再加一個__proto__屬性指向基類。
那么這樣,this的作用也就一目了然了,就是實例對象的內存地址。故call,apply這兩個JS重要而且難懂的函數的第一個參數this就很容易理解了吧。 這里僅僅說一個方面,至于原型鏈的指向啊都不說了,具體可以看這篇文章:http://blog.csdn.net/zacklin/article/details/7896859。

關于繼承

上面說到了call,和apply,下面說一下call方式實現的繼承(類式繼承)。 建議先看這篇文章http://segmentfault.com/a/1190000002440502,網站頁面也很漂亮( 話說國內很多大網站那頁面真有點難看 )。 下面是代碼 function parent( name ) { this.name = name; this.sayName = function() { alert(this.name); } } function child( name ) { parent.call(this,name); alert(this.sayName); // 相當于執行了一次this.name = "parent";this.sayName = function(){alert(this.name);} } c = new child("child"); c.sayName();結果:

為什么說"借用構造函數雖然解決了剛才兩種問題,但沒有原型,則復用無從談起"呢?第一個alert提示說明了child對象中也有一個函數,所以嘍,它沒有使用parent的函數代碼,即沒有復用。所以這種繼承會浪費內存。但是也不排除比較智能的執行器能夠看出來兩個函數一樣就只保留一份函數也說不定,呵呵,應該不會這么智能吧?

再說說組合式繼承

還是參考的這篇文章:http://segmentfault.com/a/1190000002440502。 組合式繼承就是 原型繼承+類式繼承(就是call繼承)。上面我們說了,call不僅會拷貝變量,而且會拷貝代碼。那么為什么還用call呢?這么用不就行了: function Parent(age) {this.name = ['mike','jack','smith']; // 這里面只添加屬性this.age = age; } Parent.prototype.run = function () // 方法(成員函數)在這里(原型上)添加 {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來繼承屬性,用原型來繼承方法。也就是假如現在有一個父類A,如果想讓它被繼承,那么最好將它的屬性定義在構造函數里,將它的方法定義在它的prototype里,然后用call實現屬性繼承,用prototype直接繼承A,從而繼承方法。

還有寄生式繼承:

名字不知道哪來的,反正看不出來什么意思。因為組合式繼承有個小問題,就是多一次調用問題。怎了解決呢?只是將組合式繼承稍微改動一下,即不直接繼承自A,而是直接繼承自A.prototype,因為prototype是已經new好的對象,沒看出來?這也解釋了為什么設置prototype時需要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();

結果:
注意:prototype的重定向會導致constructor的變化。所以需要重定向constructor。 感覺寫得比那篇文章中的要簡單呀,\*_*/。


再推薦一個鏈接:http://www.w3school.com.cn/js/pro_js_referencetypes.asp
最后
說了那么多,都是語法而已,假如某天語法變了,這些都沒用了。但是思路是要有的。 在我看來,JS和C++/Java沒太大區別,這也說明了編程語言都是相通的。 第一次寫這么長的博文,寫得亂,以后會修改,各位且湊合看哈!

總結

以上是生活随笔為你收集整理的JavaScript面向对象的理解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。