搜尋
首頁web前端js教程JavaScript學習總結【8】、物件導向編程

1、什麼是物件導向程式設計

  要理解面向對象,得先搞清楚什麼是對象,女朋友對象,面向對象就是面向對象,換在代碼中,就是一段代碼相中了另一段代碼,自此夜以繼日的含情脈脈的面向著這段代碼,這就叫做面向對象,誰要這麼給人解釋,那笑話可就鬧大了,但是可以把男朋友或者女朋友視為一個對象,之前我們也簡單的介紹過對象,即可以把一個人視為一個對象,對象有他的屬性和方法,屬性如:性別、身高、體重、籍貫等,方法有走、跑、跳等。那我們就可以從兩方面理解對象:

  (1)、從對象本身理解,對象就是單一實物的抽象。

  一本書、一輛車、一台電視可以被視為對象,一張網頁、一個資料庫、一個伺服器請求也可以被視為一個對象,當實物被抽象成對象,那麼實物之間的關係就變成了物件之間的關係,從而可以模擬現實情況,針對"物件"進行程式設計。

  (2)、從物件的性質來理解,物件是一個容器,包含屬性和方法。

  所謂屬性,就是對象的狀態,所謂方法,就是對象的行為(完成某種任務),比如,我們可以把動物抽象為對象,屬性記錄具體是哪一種動物,方法表示動物的行為,例如:捕獵、奔跑、攻擊、飛、爬、休息等。

  總體來講,對像是一個整體,對外提供一些操作,比如電視,我們並不了解其內部原理,但是我們構成工作原理,但是我們都會使用,對於電視來說,只要用好按鈕,會操作,這個電路那個元件怎麼運作,跟我們沒什麼關係,只要電視能正常運作就好了,我們只要知道每個按鈕是乾嘛的,就可以使用這些功能,這就是面向對象。再例如取得時間Date,透過不同的屬性我們可以取得到不同的時間,例如年份月份星期,我們並不知道他具體是怎麼實現的,但是都知道使用哪個屬性可以獲得到所需要的,這就是面向對象。

  那到底什麼是物件導向?簡單說就是在不了解內部原理的情況下,會使用其功能。就是使用物件時,只專注於物件提供的功能,不注意其內部細節。典型的應用實例就是 jQuery。

  面向對像是一種通用的思想,並非只有編程中能用,任何事情都可以使用,生活中充滿了面向對象的思想,只是我們不直接叫面向對象,而是叫面向對象一些別的什麼。 比如你去吃飯,你就告訴廚師來一份紅燒肉,然後就可以坐下來等著吃了,你不可能給廚師說要把肉切成方的或者圓的,要先放鹽,再放醬油,還要加紅糖,加冰糖也可以,誰真要這樣,廚師非得跟你急,他是廚師還是你是廚師,你只要把想吃的告訴他,你不用去管他是怎麼做的,他自然會做好給你端上來,這就是生活中典型的物件導向的想法。

  雖然不同於傳統的物件導向程式語言,但是 JS 也有很強的物件導向程式設計能力,接下來就具體分析以下什麼是 JS 物件導向程式設計。

  物件導向程式設計(Object Oriented Programming,縮寫為 OOP)是目前主流的程式設計範式,所謂範式,就是符合某一種層級的關係模式的集合。他的核心思想是將真實世界中各種複雜的關係,抽象化為一個個對象,然後由對象之間的分工與合作,完成對真實世界的模擬。 物件導向程式設計的程式就是符合某一種層級的關係模式的集合,是一系列物件的組合,每一個物件都是功能中心,具有明確分工,可以完成接受資訊、處理資料、發出訊息等任務。 因此,物件導向程式設計具有靈活性、程式碼的可重用性、模組性等特點,並且容易維護和開發,非常適合多人合作的大型項目,而在平時專案中一般不常使用。

  面向對象編程(OOP)的特點:OOP
㟎、抽象:抓住核心問題

  所謂抽象,先來看看百度對於抽象的解釋:抽像是從眾多的事物中抽取出共同的、本質性的特徵,而捨棄其非本質的特徵。例如蘋果、香蕉、鴨梨、葡萄、桃子等,它們共同的特性就是水果。得出水果概念的過程,就是一個抽象的過程。要抽象,就必須進行比較,沒有比較就無法找到本質上共同的部分。共同特徵是指那些能把一類事物與其他類事物區分開來的特徵,這些具有區分作用的特徵又稱本質特徵。因此抽取事物的共同特徵就是抽取事物的本質特徵,捨棄非本質的特徵。所以抽象的過程也是一個裁剪的過程。在抽象時,同與不同,決定從哪個角度來抽象化。抽象的角度取決於分析問題的目的。

  在 JS 中,抽象的核心就是抽,就是抓住共同特徵,抓住核心的問題。

比如說人,有很多特徵,例如姓名、性別、籍貫、出生日期、身高、體重、血型、家庭住址、父母是誰、孩子叫啥等,如果一家公司要建立員工檔案,不可能將每個特徵都註明,需要抓住一些主要的特徵,例如:姓名、性別、部門、職位,或再加上入職日期就完了。如果需要註冊婚友網站,那這時候就不是需要員工檔案中註明的那些特質了,要一些例如:性別、年齡、身高、體形、星座、有車否、有房否、工作、收入、家庭狀況等。就是把一類事物主要的特徵、跟問題相關的特徵抽取出來。這就是物件導向程式設計的抽象。   
(2)、封裝:不考慮內部實現,只考慮功能使用

  與電視等等,但是看不到內部的構成,我們也不用知道內部是什麼鬼,依然可以正常使用,除非這貨壞了,這內部的東西就是封裝。 JS 就是不考慮內部的實現,只考慮功能的使用,就像使用jQuery 一樣,jQuery 就是對JS 的封裝,我們使用jQuery 的功能,能完成與JS 相同的效果,並且還比使用JS 更方便。

  (3)、繼承:從已有對像上,繼承出新的對象

 〜的能幹,比如吃飯,睡覺。 在 JS 中,例如有一個物件 A,A 中有一些功能,現在從 A 繼承出一個物件 B,這個物件 B 就具有物件 A 的所有功能。

  還有一種情況是多重繼承,好比一個孩子可以有好多個爹,顯然這是不可能的事,但是在程序中有一個可行的,類盒子,盒子有一個特徵可以用來裝東西,還有一類汽車,汽車的特徵就是會跑,有遼輒,這時候就可以多重繼承,繼承出另一類集裝箱貨車,他特徵既可以裝東西又會跑,有遼輒。

  (4)、多態

  多態,顧名思義是一種不同的方式來實現物件。多態在 JS 中不是那麼明顯,但是對於強語言比較有用,例如 Java,C++。對於 JS 這種弱語言,意義並不大。

 

2、對象的組成

 㟎

  宿主物件就是 DOM 和 BOM,也就是由瀏覽器提供的物件。

  本地對象為非靜態對象,所謂本地對象,就是需要先 new,再使用。常用的物件如:Object、Function、Array、String、Boolean、Number、Date、RegExp、Error。

  內建物件為靜態對象,就是不需要 new,直接可以使用的類別。 Math 是最常見,也是可以直接使用的僅有的內建物件。

  面向對象的第一步,就是要創建對象。 典型的物件導向程式設計的語言都存在"類"(class) 這樣一個概念,所謂類,就是物件的抽象,表示某一類事物的共同特徵,例如水果,而物件就是類別的具體實例,例如蘋果就是水果的一種,類是抽象的,不佔用內存,而對像是具體的,佔用存儲空間。但是在 JS 中沒有 "類別" 這個概念,不過可以使用建構子實作。

  之前我們說過,所謂"構造函數",就是用來創建新對象的函數,作為對象的基本結構,一個構造函數,可以創建多個對象,這些對像都有相同的結構。建構函數就是一個普通的函數,但是他的特徵與用法和普通函數不一樣。 建構子的最大特點是,在建立物件時必須使用new 關鍵字,且函數體內部可以使用this 關鍵字,代表了所要建立的物件實例,this 就用於指向函數執行時的目前物件。 具體情況下面我們再做分析,現在先來研究下對象的組成。

  其實我們已經理解了對象的概念,也就不難看出他是由什麼構成的,對象就是由屬性和方法組成的,JS 中一切皆對象,那在JS 中,屬性和方法到底該怎麼理解呢? 屬性就是變量,方法就是函數。屬性代表狀態,就像動物的屬性記錄他具體是哪一種動物一樣,他是靜態的,變數名稱也可以說是方法名稱,是對方法的描述。而方法也就是行為,是完成某種任務的過程,他是動態的。

 

3、物件導向程式設計

  我們透過實例的方式,為物件添加屬性和方法,來理解物件的組成和物件導向。

  (1)、實例:給物件新增屬性

給物件新增屬性


㟜的實例,我們可以看到,

變數和屬性就是一樣的,變數可以做的事,屬性也可以做,屬性可以做的事,變數也可以做。他們的區別就在於,變數是自由的,不屬於任何對象,而屬性不是自由的,他是屬於一個對象的,

就像例子中的對象a,他是屬於數組arr 的,在使用的時候就寫成arr.a。我們可以為任何物件定義屬性,例如為 p 定義一個屬性用於索引:op[i].index = i。   (2)、

實例:㟎,可以看到,a 函數也是自由的,而當這個 a


函數屬於一個對象的時候,這就是方法,

是數組arr 的a 方法,也是數組arr 的a 方法,也就是這個對象的方法。 所以函數和方法也是等同的,函數可以做的事,方法就可以做,他們的不同,也是在於函數是自由的,而方法是屬於一個對象的。   我們不能在系統物件中隨意附加屬性和方法,否則會覆蓋已有的屬性和方法。 例如實例中我們是在數組物件上附加屬性和方法的,數組有他自己的屬性和方法,我們再給其附加屬性和方法,就會覆蓋掉數組本身的屬性和方法,這一點需要注意。

  (3)、實例:
建立物件

新對象,可以 new 一個 Object。 object 是空白對象,只有系統自帶的一些很少量的東西,所以在實作物件導向的時候,就可以給 object 加法,加屬性。這樣可以最大限度的避免跟著其他起衝突。   (4)、實例:物件導向程式


給物件添加屬性和方法,模擬現實情況,針對物件進行程式設計。

這個小程式運行是沒有什麼問題,但是存在很嚴重的缺陷,一個網站中不可能只有一個用戶對象,可能有成千上萬個,不可能給每個用戶都 new 一個 object。其實可以將其封裝為一個函數,然後再調用,有多少個用戶,調用多少次,這樣的函數就被稱為構造函數。  

4、构造函数

  构造函数(英文:constructor)就是一个普通的函数,没什么区别,但是为什么要叫"构造"函数呢?并不是这个函数有什么特别,而是这个函数的功能有一些特别,跟别的函数就不一样,那就是构造函数可以构建一个类。构造函数的方式也可以叫做工厂模式,因为构造函数的工作方式和工厂的工作方式是一样的。工厂模式又是怎样的呢?这个也不难理解,首先需要原料,然后就是对原料进行加工,最后出厂,这就完事了。构造函数也是同样的方式,先创建一个对象,再添加属性和方法,最后返回。既然说构造函数可以构建一个类出来,这个该怎么理解呢?很 easy,可以用工厂方式理解,类就相当于工厂中的模具,也可以叫模板,而对象就是零件、产品或者叫成品,类本身不具备实际的功能,仅仅只是用来生产产品的,而对象才具备实际的功能。比如:var arr = new Array(1,2,3,4,5); Array 就是类,arr 就是对象, 类 Array 没有实际的功能,就是用来存放数据的,而对象 arr 具有实际功能,比如:排序sort()、删除shift()、添加push()等。我们不可能这么写:new arr(); 或 Array.push();,正确的写法:arr.push();。


<script>
function userInfo(name, qq){

    //1.原料 - 创建对象
    var obj = new Object();

    //2.加工 - 添加属性和方法
    obj.name = name;
    obj.qq = qq;
    obj.showName = function (){
        alert(&#39;我的名字叫:&#39; + this.name);
    };
    obj.showQQ = function (){
        alert(&#39;我的QQ是:&#39; + this.qq);
    };
    //3.出厂 - 返回
    return obj;
}

var obj1 = userInfo(&#39;小白&#39;, &#39;89898989&#39;);
obj1.showName();
obj1.showQQ();

var obj2 = userInfo(&#39;小明&#39;, &#39;12345678&#39;);
obj2.showName();
obj2.showQQ();
</script>

 

  这个函数的功能就是构建一个对象,userInfo() 就是构造函数,构造函数作为对象的类,提供一个模具,用来生产用户对象,我们以后在使用时,只调用这个模板,就可以无限创建用户对象。我们都知道,函数如果用于创建新的对象,就称之为对象的构造函数,我们还知道,在创建新对象时必须使用 new 关键字,但是上面的代码,userInfo() 构造函数在使用时并没有使用 new关 键字,这是为什么呢?且看下文分解。

 

5、new 和 this

  (1)new

  new 关键字的作用,就是执行构造函数,返回一个实例对象。看下面例子:


<script>var user = function (){  this.name = &#39;小明&#39;;
};var info = new user();
alert(info.name);    //返回:小明</script>


 

   上面实例通过 new 关键字,让构造函数 user 生产一个实例对象,保存在变量 info 中,这个新创建的实例对象,从构造函数 user 继承了 name 属性。在 new 命令执行时,构造函数内部的 this,就代表了新生产的实例对象,this.name 表示实例有一个 name 属性,他的值是小明。

  使用 new 命令时,根据需要,构造函数也可以接受参数。


<script>
var user = function (n){
  this.name = n;
};

var info = new user(&#39;小明&#39;);
alert(info.name);    //返回:小明
</script>

 

  new 命令本身就可以执行执行构造函数,所以后面的构造函数可以带括号,也可以不带括号,下面两行代码是等价的。


var info = new user;var info = new user();


  那如果没有使用 new 命令,直接调用构造函数会怎样呢?这种情况下,构造函数就变成了普通函数,并不会生产实例对象,this 这时候就代表全局对象。


<script>
var user = function (n){
  this.name = n;
};
alert(this.name);    //返回:小明

var info = user(&#39;小明&#39;);
alert(info.name);    //报错
</script>

  上面实例中,调用 user 构造函数时,没有使用 new 命令,结果 name 变成了全局变量,而变量 info 就变成了 undefined,报错:无法读取未定义的属性 'name'。使用 new 命令时,他后边的函数调用就不是正常的调用,而是被 new 命令控制了,内部的流程是,先创建一个空对象,赋值给函数内部的 this 关键字,this 就指向一个新创建的空对象,所有针对 this 的操作,都会发生在这个空对象上,构造函数之所以叫"构造函数",就是说这个函数的目的,可以操作 this 对象,将其构造为需要的样子。下面我们看一下 new 和函数。


<script>
var user = function (){
//function = user(){
    alert(this);
}
user();    //返回:Window
new user();//返回:Object
</script>

 

  通过上面实例,可以看到,在调用函数时,前边加个 new,构造函数内部的 this 就不是指向 window 了,而是指向一个新创建出来的空白对象。

  说了这么多,那为什么我们第四章的构造函数,在使用的时候没有加 new 关键字呢,因为我们完全是按照工厂模式,也就是构造函数的结构直接编写的,我们的步骤已经完成了 new 关键字的使命,也就是把本来 new 需要做的事,我们已经做了,所以就用不着 new 了。那这样岂不是做了很多无用功,写了不必要的代码,浪费资源,那肯定是了,这也是构造函数的一个小问题,我们在下一章再做具体分析。

  (2)、this

  this 翻译为中文就是这,这个,表示指向。之前我们提到过,this 指向函数执行时的当前对象。那么我们先来看看函数调用,函数有四种调用方式,每种方式的不同方式,就在于 this 的初始化。

  ①、作为一个函数调用


1 <script>2 function show(a, b) {3     return a * b;4 }5 alert(show(2, 3));    //返回:66 </script>


 

  实例中的函数不属于任何对象,但是在 JS 中他始终是默认的全局对象,在 HTML 中默认的全局对象是 HTML 页面本身,所以函数是属于 HTML 页面,在浏览器中的页面对象是浏览器窗口(window 对象),所以该函数会自动变为 window 对象的函数。


<script>
function show(a, b) {
    return a * b;
}
alert(show(2, 3));    //返回:6
alert(window.show(2, 3));//返回:6
</script>

  上面代码中,可以看到,show() 和 window.show() 是等价的。这是调用 JS 函数最常用的方法,但不是良好的编程习惯,因为全局变量,方法或函数容易造成命名冲突的 Bug。

  当函数没有被自身的对象调用时,this 的值就会变成全局对象。


1 <script>2 function show() {3     return this;4 }5 alert(show());    //返回:[object Window]6 </script>


  全局对象就是 window 对象,函数作为全局对象对象调用,this 的值也会成为全局对象,这里需要注意,使用 window 对象作为一个变量容易造成程序崩溃。

  ②、函数作为方法调用


<script>
var user = {
    name : &#39;小明&#39;,
    qq : 12345678,
    info : function (){
        return this.name + &#39;QQ是:&#39; + this.qq;
    }
}
alert(user.info());
</script>

  在 JS 中可以将函数定义为对象的方法,上面实例创建了一个对象 user,对象拥有两个属性(name和qq),及一个方法 info,该方法是一个函数,函数属于对象,user 是函数的所有者,this 对象拥有 JS 代码,实例中 this 的值为 user 对象,看下面示例:


<script>
var user = {
    name : &#39;小明&#39;,
    qq : 12345678,
    info : function (){
        return this;
    }
}
alert(user.info());    //返回:[object Object]
</script>

  函数作为对象方法调用,this 就指向对象本身。

  ③、使用构造函数调用函数

  如果函数调用前使用了 new关键字,就是调用了构造函数。


<script>
function user(n, q){
    this.name = n;
    this.qq  = q;
}

var info = new user(&#39;小明&#39;, 12345678);
alert(info.name);    //返回:小明
alert(info.qq);        //返回:12345678
</script>

  这看起来就像创建了新的函数,但实际上 JS 函数是新创建的对象,构造函数的调用就会创建一个新的对象,新对象会继承构造函数的属性和方法。构造函数中的 this 并没有任何的值,this 的值在函数调用时实例化对象(new object)时创建,也就是指向一个新创建的空白对象。

  ④、作为方法函数调用函数

  在 JS 中,函数是对象,对象有他的属性和方法。call() 和 apply() 是预定义的函数方法,这两个方法可用于调用函数,而且这两个方法的第一个参数都必须为对象本身。


<script>
function show(a, b) {
    return a * b;
}
var x = show.call(show, 2, 3);
alert(x);    //返回:6

function shows(a, b) {
    return a * b;
}
var arr = [2,3];
var y = shows.apply(shows, arr);
var y1 = shows.call(shows, arr);
alert(y);    //返回:6
alert(y1);    //返回:NaN
</script>

  上面代码中的两个方法都使用了对象本身作为作为第一个参数,两者的区别在于:apply()方法传入的是一个参数数组,也就是将多个参数组合称为一个数组传入,而call()方法则作为call的参数传入(从第二个参数开始),不能传入一个参数数组。

  通过 call() 或 apply() 方法可以设置 this 的值, 且作为已存在对象的新方法调用。在下面用到的时候,我们再具体分析。

  this 就是用于指向函数执行时的当前对象,下面再看一个实例:


<div></div>
<script>
var oDiv = document.getElementById(&#39;div1&#39;);
//给一个对象添加事件,本质上是给这个对象添加方法。
oDiv.onclick = function (){
    alert(this);    //this就是oDiv
};

var arr = [1,2,3,4,5];
//给数组添加属性
arr.a = 12;
//给数组添加方法
arr.show = function (){
    alert(this.a);    //this就是arr
};
arr.show();    //返回:12


function shows(){
    alert(this);    //this就是window
}

//全局函数是属于window的。
//所以写一个全局函数shows和给window加一个shows方法是一样的。
window.shows = function (){
    alert(this);
};
shows();    //返回:[object Window]
</script>

   上面的代码,this 就代表着当前的函数(方法)属于谁,如果是一个事件方法,this 就是当前发生事件的对象,如果是一个数组方法,this 就是数组对象,全局的方法是属于 window 的,所以 this 指向 window。

 

6、原型

  前面我们说过构造函数在使用时没有加 new,这只能算是一个小问题,没有加我们可以给加上,无伤大雅,但其实他还存在着一个更严重的问题,那就是函数重复定义。


<script>
function userInfo(name, qq){

    //1.原料 - 创建对象
    var obj = new Object();

    //2.加工 - 添加属性和方法
    obj.name = name;
    obj.qq = qq;
    obj.showName = function (){
        alert(&#39;我的名字叫:&#39;+this.name);
    };
    obj.showQQ = function (){
        alert(&#39;我的QQ是:&#39;+this.qq);
    };
    //3.出厂 - 返回
    return obj;
}

//1.没有new。
var obj1 = userInfo(&#39;小白&#39;, &#39;89898989&#39;);
var obj2 = userInfo(&#39;小明&#39;, &#39;1234567&#39;);

//调用的showName返回的函数都是相同的。
alert(obj1.showName);
alert(obj2.showName);

//2.函数重复。
alert(obj1.showName == obj2.showName);    //返回:false
</script>

  通过上面的代码,我们可以看到,弹出这两个对象的 showName,调用的 showName 返回的函数是相同的,他们新创建对象所使用的方法都是一样的,尽管这两个函数长的是一样的,但其实他们并不是一个东西,我们将 对象1 和 对象2 做相等比较,结果返回 false。这时候就带来了一个相对严重的问题,一个网站中也不可能只有 2 个用户,比如有 1 万个用户对象,那么就会有 1 万 showName 和 showQQ 方法,每一个对象都有自己的函数,但明明这两个函数都是一样的,结果却并非如此。这样就很浪费系统资源,而且性能低,可能还会出现一些意想不到的问题。该怎么解决这个问题呢?方法也很简单,就是使用原型。

  (1)、什么是原型

  JS 对象都有一个之前我们没有讲过的属性,即 prototype 属性,该属性让我们有能力向对象添加属性和方法,包括 String对象、Array对象、Number对象、Date对象、Boolean对象,Math对象 并不像 String 和 Date 那样是对象的类,因此没有构造函数 Math(),该对象只用于执行数学任务。

  所有 JS 的函数都有一个prototype属性,这个属性引用了一个对象,即原型对象,也简称原型。这个函数包括构造函数和普通函数,我们讲的更多是构造函数的原型,但是也不能否定普通函数也是有原型的。

  在看实例之前,我们先来看几个小东西:typeof运算符、constructor属性、instanceof运算符。

  typeof 大家都熟悉,JS 中判断一个变量的数据类型就会用到 typeof 运算符,返回结果为 JS 基本的数据类型,包括 number、string、boolean、object、function、undefined,语法:typeof obj。

  constructor 属性返回所有 JS 变量的构造函数,typeof 无法判断 Array对象 和 Date对象 的类型,因为都返回 object,所以我们可以利用 constructor 属性来查看对象是否为数组或者日期,语法:obj.constructor。


<script>
var arr = [1,2,3,4,5];
function isArray(obj) {
    return arr.constructor.toString().indexOf("Array") > -1;
}
alert(isArray(arr));    //返回:ture

var d = new Date();
function isDate(obj) {
    return d.constructor.toString().indexOf("Date") > -1;
}
alert(isDate(d));    //返回:ture
</script>

   这里需要注意,constructor 只能对已有变量进行判断,对于未声明的变量进行判断会报错,而 typeof 则可对未声明变量进行判断(返回 undefined)。

  instanceof 这东西比较高级,可用于判断一个对象是否是某一种数据类型,查看对象是否是某个类的实例,返回值为 boolean 类型。另外,更重要的一点是 instanceof 还可以在继承关系中用来判断一个实例是否属于他的父类型,语法:a instanceof b。


<script>
// 判断 a 是否是 A 类的实例 , 并且是否是其父类型的实例
function A(){} 
function B(){} 
B.prototype = new A();    //JS原型继承

var a = new B();
alert(a instanceof A);    //返回:true
alert(a instanceof B);    //返回:true 
</script>

   上面的实例中判断了一层继承关系中的父类,在多层继承关系中,instanceof 运算符同样适用。

  下面我们就来看看普通函数的原型:


1 <script>2 function A(){}3 alert(A.prototype instanceof Object);    //返回:true4 </script>


  上面代码中 A 是一个普通的函数,我们判断函数 A 的原型是否是对象,结果返回 true。

  说了这么多,原型到底是个什么东西,说简单点原型就是往类的上面添加方法,类似于class,修改他可以影响一类元素。原型就是在已有对象中加入自己的属性和方法,原型修改已有对象的影响,prototype 属性可返回对象类型原型的引用,如果对象创建在修改原型之前,那么该对象不会拥有修改后的原型方法,就是说原型链的改变,不会影响之前产生的对象。有关原型链的知识,下面我们在讲继承时,再做分析。

  下面我们通过实例的方式,进一步的理解原型。

  实例:给数组添加方法

<script>
var arr1 = new Array(2,8,8);
var arr2 = new Array(5,5,10);

arr1.sum = function (){
    var result = 0;
    for(var i=0; i<this.length; i++){
        result += this[i];
    }
    return result;
};

alert(arr1.sum());  //返回:18
alert(arr2.sum());  //报错:arr2没有sum方法
</script>

  上面的实例只给 数组1 添加了 sum 方法,这就类似于行间样式,只给 arr1 设置了,所以 arr2 肯定会报错,这个并不难理解。

  实例:给原型添加方法

<script>
var arr1 = new Array(2,8,8);
var arr2 = new Array(5,5,10);

Array.prototype.sum = function (){
    var result = 0;
    for(var i=0; i<this.length; i++){
        result += this[i];
    }
    return result;
};

alert(arr1.sum());    //返回:18
alert(arr2.sum());    //返回:20
</script>

  通过上面的实例,我们可以看到,通过原型 prototype 给 Array 这个类添加一个 sum 方法,就类似于 class,一次可以设置一组元素,那么所有的 Array 类都具有这个方法,arr1 返回结果为 18,而 arr2 在加了原型之后,也返回了正确的计算结果 20。

  (2)、解决历史遗留问题

  现在我们就可以使用原型,来解决没有 new 和函数重复定义的问题了。


<script>
function UserInfo(name, qq){

    //1.原料 - 创建对象
    //var obj = new Object();

    //加了new之后,系统(浏览器)会自动替你声明一个变量:
    //var this = new Object();

    //2.加工 - 添加属性和方法
    /*
    obj.name = name;
    obj.qq = qq;
    obj.showName = function (){
        alert(&#39;我的名字叫:&#39;+this.name);
    };
    obj.showQQ = function (){
        alert(&#39;我的QQ是:&#39;+this.qq);
    };
    */
    this.name = name;
    this.qq = qq;

    //3.出厂 - 返回
    //return obj;

    //系统也会自动替你返回:
    //return this;
}

//2.函数重复的解决:userInfo给类加原型。
UserInfo.prototype.showName = function (){
    alert(&#39;我的名字叫:&#39; + this.name);
};

UserInfo.prototype.showQQ = function (){
    alert(&#39;我的QQ是:&#39; + this.qq);
};


//1.加上没有new。
var obj1 = new UserInfo(&#39;小白&#39;, &#39;89898989&#39;);
var obj2 = new UserInfo(&#39;小明&#39;, &#39;1234567&#39;);

obj1.showName();
obj1.showQQ();
obj2.showName();
obj2.showQQ();

//加了原型之后
alert(obj1.showName == obj2.showName);    //返回:true
</script>

 

  上面的代码看着有点复杂,我们把不必要的省略,如下:


<script>
function UserInfo(name, qq){
    this.name = name;
    this.qq = qq;
}

UserInfo.prototype.showName = function (){
    alert(&#39;我的名字叫:&#39; + this.name);
};
UserInfo.prototype.showQQ = function (){
    alert(&#39;我的QQ是:&#39; + this.qq);
};

var obj1 = new UserInfo(&#39;小白&#39;, &#39;89898989&#39;);
var obj2 = new UserInfo(&#39;小明&#39;, &#39;1234567&#39;);

obj1.showName();
obj1.showQQ();
obj2.showName();
obj2.showQQ();

alert(obj1.showName == obj2.showName);    //返回:true
</script>

  现在代码是不是比最初的样子,简洁了很多,new 关键字也使用了,而且每个对象都是相等的。通过上面的实例,我们可以看到,再加上 new 之后,使用就方便了很多,代码明显减少了,因为在加了 new 之后,系统也就是浏览器自动为你做两件事,这就是 new 的使命,第一件事是替你创建了一个空白对象,也就是替你声明了一个变量:var this = new Object();,第二件事就是再提你返回这个对象:return this;,这里需要注意,在之前我们也讲过,在调用函数的时候,前边加个 new,构造函数内部的 this 就不是指向 window 了,而是指向一个新创建出来的空白对象。

  这种方式就是流行的面向对象编写方式,即混合方式构造函数,混合的构造函数/原型方式(Mixed Constructor Function/Prototype Method),他的原则是:用构造函数加属性,用原型加方法,也就是用构造函数定义对象的所有非函数属性,用原型方式定义对象的函数方法。用原型的作用,就是此对象的所有实例共享原型定义的数据和(对象)引用,防止重复创建函数,浪费内存。原型中定义的所有函数和引用的对象都只创建一次,构造函数中的方法则会随着实例的创建重复创建(如果有对象或方法的话)。这里需要注意,不管在原型中还是构造函数中,属性(值)都不共享,构造函数中的属性和方法都不共享,原型中属性不共享,但是对象和方法共享。所以创建类的最好方式就是用构造函数定义属性,用原型定义方法。使用该方式,类名的首字母要大写,这也是一种对象命名的规范。

 

7、面向对象实例

  通常我们在写程序时,都使用的是面向过程,即要呈现出什么效果,基于这样的效果,一步步编写实现效果的代码,接下来我们就把面向过程的程序,改写成面向对象的形式。面向过程的程序写起来相对容易些,代码也比较直观,易读性强,我们先看一个面向过程的实例。

  实例:面向过程的选项卡

nbsp;html>


    <meta>
    <title>JavaScript实例</title>
<style>
#div1 input{background:white;}
#div1 input.active{background:green;color:white;}
#div1 div{
    width:200px;
    height:200px;
    background:#ccc;
    display:none;
}
</style>
<script>
window.onload = function (){
    //1、获取所需元素。
    var oDiv = document.getElementById(&#39;div1&#39;);
    var oBtn = oDiv.getElementsByTagName(&#39;input&#39;);
    var aDiv = oDiv.getElementsByTagName(&#39;div&#39;);
    
    //2、循环遍历所有按钮。
    for(var i=0; i<oBtn.length; i++){
        //5、给按钮定义index属性,当前按钮的索引号为按钮的索引号i
        oBtn[i].index = i;
        //3、给当前按钮添加点击事件。
        oBtn[i].onclick = function (){
           //4、再循环所有按钮,清空当前按钮的class属性,并将当前内容的样式设置为隐藏
           //在执行清空和设置之前,需要给当前按钮定义一个索引
           //这一步的目的:主要就是实现切换效果,点击下一个按钮时,当前按钮失去焦点,内容失去焦点
             for(var i=0; i<oBtn.length; i++){
                oBtn[i].className = &#39;&#39;;
                aDiv[i].style.display = &#39;none&#39;;
            }
            //6、最后给当前按钮class属性,再设置当前展示内容的样式为显示
            this.className = &#39;active&#39;;
            aDiv[this.index].style.display = &#39;block&#39;;
       };
    }
};
</script>


<div>
    <input>
    <input>
    <input>
    <div>天气预报</div>
    <div>历史实事</div>
    <div>人文地理</div>
</div>

  这样一个简单的效果,谁都可以做的出来,那要怎么写成面向对象的形式呢,我们先来看代码,再做分析。

  实例:面向对象的选项卡

nbsp;html>


    <meta>
    <title>JavaScript实例</title>
<style>
#div1 input{background:white;}
#div1 input.active{background:green;color:white;}
#div1 div{
    width:200px;
    height:200px;
    background:#ccc;
    display:none;
}
</style>
<script>
window.onload = function(){
    new TabShow(&#39;div1&#39;);
};

function TabShow(id){
    var _this = this;
    var oDiv = document.getElementById(id);
    this.oBtn = oDiv.getElementsByTagName(&#39;input&#39;);
    this.aDiv = oDiv.getElementsByTagName(&#39;div&#39;);
    for(var i=0; i<this.oBtn.length; i++){
        this.oBtn[i].index = i;
        this.oBtn[i].onclick = function (){
            _this.fnClick(this);
        };
    }
}

TabShow.prototype.fnClick = function (oBtn){
    for(var i=0; i<this.oBtn.length; i++){
        this.oBtn[i].className = &#39;&#39;;
        this.aDiv[i].style.display = &#39;none&#39;;
    }
    oBtn.className = &#39;active&#39;;
    this.aDiv[oBtn.index].style.display = &#39;block&#39;;
};
</script>


<div>
    <input>
    <input>
    <input>
    <div>天气预报</div>
    <div>历史实事</div>
    <div>人文地理</div>
</div>

   将面向过程的程序,改写成面向对象的形式,原则就是不能有函数套函数,但可以有全局变量,其过程是先将 onload 改为构造函数,再将全局变量改为属性,函数改为方法,这就是面向对象的思维,所以第一步就是把嵌套函数单独出来,当函数单独出去之后,onload 中定义的变量在点击函数中就会报错,onload 也相当于一个构造函数,初始化整个程序,所以再对 onload 函数作出一些修改,让他初始化这个对象,然后就是添加属性和方法,我们说变量就是属性,函数就是方法,所以这里也只是改变所属关系。这个过程中最需要注意的是 this 的指向问题,通过闭包传递 this,以及函数传参,把对象作为参数传递。之前的 this 都是指向当前发生事件的对象,将函数改为方法后,我们给这个方法添加的是按钮点击事件,所以这时候 this 就指向这个按钮,本应该这个 this 是指向新创建的对象,这就需要转换 this 的指向 var _this = this;。TabShow 函数就是 onload 函数的改造,fnClick 方法是第一步单独出去的函数,最后被改为了选项卡函数 (TabShow函数) 的方法。

 

8、继承和原型链

  (1)、继承

  前边我们简单的说过继承是从已有对象上,再继承出一个新对象,继承就是在原有类的基础上,略作修改,得到一个新的类,不影响原有类的功能。继承的实现有好几种方法,最常用的就是 call() 方法和原型实现继承。下面看一个继承的实例:


 1 <script> 
 2 function A(){ 
 3     this.abc = 12; 
 4 } 
 5 A.prototype.show = function (){ 
 6     alert(this.abc); 
 7 }; 
 8  
 9 function B(){
 10     A.call(this);
 11 }
 12 
 13 for(var i in A.prototype){
 14     B.prototype[i]=A.prototype[i];
 15 }
 16 
 17 B.prototype.fn=function (){
 18     alert(&#39;abc&#39;);
 19 };
 20 
 21 var objB = new B();
 22 alert(objB.abc);    //返回:12
 23 objB.show();        //返回:12
 24 objB.fn();            //返回:abc
 25 
 26 var objA = new A();
 27 objA.fn();    //报错:A没有该方法
 28 </script>


  上面的代码,B函数 继承了 A函数 的属性,通过 call 方法,该方法有一个功能,可以改变这个函数在执行时里边的 this 的指向,如果 B函数 中不使用 call,this 则指向 new B(),使用 call 后,this 则指向 A。方法继承 B.prototype = A.prototype;,A 的方法写在原型里,赋给 原型B,原型也是引用,将 A的原型 引用给 B的原型,就相当于 原型A 和 原型B 公用引用一个空间,所以 原型B 自己的方法,原型A 也可以用,给 原型B 添加一个方法,也就是给 原型A 添加一个方法。所以可以使用循环遍历 原型A 中的内容,再将这些内容赋给 原型B,这样 原型A 就没有 原型B 的方法了,也就是给 B 再添加方法,A 将不会受到影响(objA.fn() 报错),B 不仅有从父级继承来的方法(objB.show()),还有自己的方法(obj.fn())。

  (2)、原型链

  在 JS 中,每当定义一个对象(函数)时,对象中都会包含一些预定义的属性。其中函数对象的一个属性就是原型对象 prototype。这里需要注意:普通对象没有 prototype,但有__proto__ 属性。原型对象的主要对象就是用于继承。


 1 <script> 
 2 var A = function(name){ 
 3     this.name = name; 
 4 }; 
 5 A.prototype.getName = function(){ 
 6     alert(this.name); 
 7 } 
 8 var obj = new A(&#39;小明&#39;); 
 9 obj.getName();    //返回:小明
 10 
 11 </script>


   上面的代码,通过给 A.prototype 定义了一个函数对象的属性,再 new 出来的对象就继承了这个属性。

  JS 在创建对象(不论是普通对象还是函数对象)时,都有一个叫做 __proto__ 的内置属性,用于指向创建它的函数对象的原型对象 prototype。


 1 <script> 
 2 var A = function(name){ 
 3     this.name = name; 
 4 } 
 5 A.prototype.getName = function(){ 
 6     alert(this.name); 
 7 } 
 8 var obj = new A(&#39;小明&#39;); 
 9 obj.getName();    //返回:小明
 10 
 11 alert(obj.__proto__ === A.prototype);    //返回:true
 12 </script>


  同样,A.prototype 对象也有 __proto__ 属性,它指向创建它的函数对象(Object)的 prototype。


 1 <script> 
 2 var A = function(name){ 
 3     this.name = name; 
 4 } 
 5 A.prototype.getName = function(){ 
 6     alert(this.name); 
 7 } 
 8 var obj = new A(&#39;小明&#39;); 
 9 obj.getName();        //返回:小明
 10 
 11 alert(A.prototype.__proto__ === Object.prototype);    //返回:true
 12 </script>


  Object.prototype 对象也有 __proto__ 属性,但它比较特殊,为 null。


 1 <script> 
 2 var A = function(name){ 
 3     this.name = name; 
 4 } 
 5 A.prototype.getName = function(){ 
 6     alert(this.name); 
 7 } 
 8 var obj = new A(&#39;小明&#39;); 
 9 obj.getName();        //返回:小明
 10 
 11 alert(Object.prototype.__proto__);    //返回:null
 12 </script>


  综上,我们把这个由 __proto__ 串起来的直到 Object.prototype.__proto__ 为 null 的链就叫做原型链。

  在 JS 中,可以简单的将值分为两种类型,即原始值和对象值。每个对象都有一个内部属性 (prototype),通常称之为原型。原型的值可以是一个对象,也可以是 null。如果他的值是一个对象,则这个对象也一定有自己的原型,由于原型对象本身也是对象,而他自己的原型对象又可以有自己的原型,这样就组成了一条链,我们就称之为原型链。JS 引擎在访问对象的属性时,如果在对象本身中没有找到,则会去原型链中查找,如果找到,直接返回值,如果整个链都遍历且没有找到属性,则返回 undefined。原型链一般实现为一个链表,这样就可以按照一定的顺序来查找,如果对象没有显式的声明自己的 ”__proto__”属性,那么这个值默认的设置为 Object.prototype,而当 Object.prototype 的 ”__proto__”属性值为 ”null”时,则标志着原型链的终结。

 

9、JSON 的面向对象

  JSON 的面向对象,就是把方法包含在一个 JSON 中,在仅仅只有一个对象时使用,整个程序只有一个对象,写起来比较简单,但是不适合多个对象。这种方式也被称为命名空间所谓命名空间,就是把很多 JSON 用附加属性的方式创建,然后每个里边都有自己的方法,这种方法主要用来分类,使用方便,避免冲突。就相当于把同一类方法归纳在一起,既可以不冲突,而且找起来方便。


 1 <script> 
 2 //创建一个空的json 
 3 var json = {}; 
 4  
 5 //现在就有了3个空的json 
 6 json.a = {}; 
 7 json.b = {}; 
 8 json.c = {}; 
 9 
 10 //现在3个json里边各有一个getUser函数,而且各不相同。
 11 //在JS中,如果是相同命名的函数就会产生冲突,相互覆盖。
 12 //但是这3个json不会相互冲突,相互覆盖。
 13 json.a.getUser = function (){
 14     alert(&#39;a&#39;);
 15 };
 16 json.b.getUser = function (){
 17     alert(&#39;b&#39;);
 18 };
 19 json.c.getUser = function (){
 20     alert(&#39;c&#39;);
 21 };
 22 json.a.getUser();    //返回:a
 23 json.b.getUser();    //返回:b
 24 json.c.getUser();    //返回:c
 25 </script>

以上就是JavaScript学习总结【8】、面向对象编程 的内容,更多相关内容请关注PHP中文网(www.php.cn)!

 

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
Java面试常见问题与答案Java面试常见问题与答案Jun 16, 2023 am 08:52 AM

Java作为一门常见的编程语言,其在IT行业中有着广泛的应用,成为了许多公司招聘中的重要技能之一。在Java开发岗位的面试中,面试官往往会问及一些常见的Java问题,以此考察应聘者的Java编程水平。本文将列举几个常见的Java面试问题及其答案,供广大应聘者参考。什么是Java虚拟机?答:Java虚拟机(JVM)是一种虚拟的计算机,可以执行Java字节码。它

PHP的面向对象编程范式为项目管理和组织提供优势PHP的面向对象编程范式为项目管理和组织提供优势Sep 08, 2023 am 08:15 AM

PHP的面向对象编程范式为项目管理和组织提供优势随着互联网的飞速发展,各种规模的网站和应用程序如雨后春笋般涌现出来。为了满足日益增长的需求,并提高开发效率和可维护性,采用面向对象编程(Object-OrientedProgramming,简称OOP)的方法成为了现代软件开发的主流。在PHP这样的动态脚本语言中,OOP为项目管理和组织带来了许多优势,本文将介

面向对象编程和MySql:如何实现更优雅的代码面向对象编程和MySql:如何实现更优雅的代码Jun 16, 2023 am 08:03 AM

随着计算机应用的不断普及和发展,程序设计语言也不断演化和更新。面向对象编程语言在当今程序设计中占据了主导地位,而MySQL则是最流行的关系型数据库管理系统之一。如何在面向对象编程中结合使用MySQL实现更优雅的代码呢?一、面向对象编程的基本概念面向对象编程(ObjectOrientedProgramming,简称OOP)是一种编程思想,它强调对象的概念和

Java面向对象编程:封装、继承和多态Java面向对象编程:封装、继承和多态May 11, 2023 pm 07:51 PM

Java是一种面向对象编程语言,具备三个重要的特性:封装、继承和多态。这三个特性使得Java程序具有高度的可重用性、可维护性和可扩展性。在本文中,我们将介绍Java面向对象编程的三个重要特性。一、封装封装是面向对象编程的基础。它是指将数据和行为封装在一起,保护数据不被直接访问,而是通过类的接口进行访问。封装可以使得程序更加安全、可靠和易于维护。在Java中,

PHP面向对象编程中的访问者模式解析PHP面向对象编程中的访问者模式解析Aug 10, 2023 pm 01:33 PM

PHP面向对象编程中的访问者模式解析访问者模式是一种常用的设计模式,它可以分离数据结构和处理逻辑,使得同一个数据结构可以有不同的处理逻辑,而且可以在不修改数据结构的情况下增加新的处理逻辑。在PHP中,访问者模式可以帮助我们更好地组织代码,并提高代码的可维护性和可扩展性。本文将深入探讨PHP面向对象编程中的访问者模式,并通过示例代码进行解析。一、模式概述访问者

面向对象编程是什么意思面向对象编程是什么意思Jul 17, 2023 pm 01:56 PM

面向对象编程是一种编码设计,它使用数据来表示一组指令。它是一种具有对象概念的程序编程典范,同时也是一种程序开发的抽象方针。它由描述状态的属性和用来实现对象行为的方法组成,完成了从数据模型到处理模型的结合与统一。

理解PHP面向对象编程中的工厂模式理解PHP面向对象编程中的工厂模式Aug 10, 2023 am 10:37 AM

理解PHP面向对象编程中的工厂模式工厂模式是一种常用的设计模式,它用于创建对象的过程中将对象的创建和使用解耦。在PHP面向对象编程中,工厂模式可以帮助我们更好地管理对象的创建和生命周期。本文将通过代码示例来详细介绍PHP中的工厂模式。在PHP中,我们可以通过使用工厂模式来实现对象的创建和初始化过程,而不是直接使用new关键字。这样做的好处是,如果将来需要改变

解析PHP面向对象编程中的类属性和方法解析PHP面向对象编程中的类属性和方法Aug 12, 2023 pm 01:06 PM

解析PHP面向对象编程中的类属性和方法PHP是一种被广泛应用于Web开发的脚本语言,它支持面向对象编程(OOP)的特性。在PHP中,类是一种用来创建对象的蓝图或模板,而属性和方法则是类的核心部分。本文将深入解析PHP面向对象编程中的类属性和方法,并通过代码示例来加深理解。一、类属性类属性是指用于描述类的特有数据的变量。它们可以存储对象的状态和特征。在PHP中

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
2 週前By尊渡假赌尊渡假赌尊渡假赌
倉庫:如何復興隊友
4 週前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒險:如何獲得巨型種子
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境