全面理解物件導向的 JavaScript(來自ibm)_javascript技巧
当今 JavaScript 大行其道,各种应用对其依赖日深。web 程序员已逐渐习惯使用各种优秀的 JavaScript 框架快速开发 Web 应用,从而忽略了对原生 JavaScript 的学习和深入理解。所以,经常出现的情况是,很多做了多年 JS 开发的程序员对闭包、函数式编程、原型总是说不清道不明,即使使用了框架,其代码组织也非常糟糕。这都是对原生 JavaScript 语言特性理解不够的表现。要掌握好 JavaScript,首先一点是必须摒弃一些其他高级语言如 Java、C# 等类式面向对象思维的干扰,全面地从函数式语言的角度理解 JavaScript 原型式面向对象的特点。把握好这一点之后,才有可能进一步使用好这门语言。本文适合群体:使用过 JS 框架但对 JS 语言本质缺乏理解的程序员,具有 Java、C++ 等语言开发经验,准备学习并使用 JavaScript 的程序员,以及一直对 JavaScript 是否面向对象模棱两可,但希望知道真相的 JS 爱好者。
重新认识面向对象
为了说明 JavaScript 是一门彻底的面向对象的语言,首先有必要从面向对象的概念着手 , 探讨一下面向对象中的几个概念:
一切事物皆对象
对象具有封装和继承特性
对象与对象之间使用消息通信,各自存在信息隐藏
以这三点做为依据,C++ 是半面向对象半面向过程语言,因为,虽然他实现了类的封装、继承和多态,但存在非对象性质的全局函数和变量。Java、C# 是完全的面向对象语言,它们通过类的形式组织函数和变量,使之不能脱离对象存在。但这里函数本身是一个过程,只是依附在某个类上。
然而,面向对象仅仅是一个概念或者编程思想而已,它不应该依赖于某个语言存在。比如 Java 采用面向对象思想构造其语言,它实现了类、继承、派生、多态、接口等机制。但是这些机制,只是实现面向对象编程的一种手段,而非必须。换言之,一门语言可以根据其自身特性选择合适的方式来实现面向对象。所以,由于大多数程序员首先学习或者使用的是类似 Java、C++ 等高级编译型语言(Java 虽然是半编译半解释,但一般做为编译型来讲解),因而先入为主地接受了“类”这个面向对象实现方式,从而在学习脚本语言的时候,习惯性地用类式面向对象语言中的概念来判断该语言是否是面向对象语言,或者是否具备面向对象特性。这也是阻碍程序员深入学习并掌握 JavaScript 的重要原因之一。
实际上,JavaScript 语言是通过一种叫做 原型(prototype)的方式来实现面向对象编程的。下面就来讨论 基于类的(class-based)面向对象和 基于原型的 (prototype-based) 面向对象这两种方式在构造客观世界的方式上的差别。
基于类的面向对象和基于原型的面向对象方式比较
在基于类的面向对象方式中,对象(object)依靠 类(class)来产生。而在基于原型的面向对象方式中,对象(object)则是依靠 构造器(constructor)利用 原型(prototype)构造出来的。举个客观世界的例子来说明二种方式认知的差异。例如工厂造一辆车,一方面,工人必须参照一张工程图纸,设计规定这辆车应该如何制造。这里的工程图纸就好比是语言中的 类 (class),而车就是按照这个 类(class)制造出来的;另一方面,工人和机器 ( 相当于 constructor) 利用各种零部件如发动机,轮胎,方向盘 ( 相当于 prototype 的各个属性 ) 将汽车构造出来。
事实上关于这两种方式谁更为彻底地表达了面向对象的思想,目前尚有争论。但笔者认为原型式面向对象是一种更为彻底的面向对象方式,理由如下:
首先,客观世界中的对象的产生都是其它实物对象构造的结果,而抽象的“图纸”是不能产生“汽车”的,也就是说,类是一个抽象概念而并非实体,而对象的产生是一个实体的产生;
其次,按照一切事物皆对象这个最基本的面向对象的法则来看,类 (class) 本身并不是一个对象,然而原型方式中的构造器 (constructor) 和原型 (prototype) 本身也是其他对象通过原型方式构造出来的对象。
再次,在類別式物件導向語言中,物件的狀態(state) 由物件實例(instance) 所持有,物件的行為方法(method) 則由宣告該物件的類別所持有,並且只有物件的結構和方法能夠被繼承;而在原型式物件導向語言中,物件的行為、狀態都屬於物件本身,並且能夠一起被繼承(參考資源),這也更貼近客觀實際。
最後,類別物件導向語言例如 Java,為了彌補無法使用過程導向語言中全域函數和變數的不便,允許在類別中聲明靜態 (static) 屬性和靜態方法。而實際上,客觀世界不存在所謂靜態概念,因為一切事物皆物件!而在原型式物件導向語言中,除內建物件 (build-in object) 外,不允許全域物件、方法或屬性的存在,也沒有靜態概念。所有語言元素 (primitive) 必須依賴物件存在。但由於函數式語言的特點,語言元素所依賴的物件是隨著執行時間 (runtime) 上下文 (context) 變化而變化的,具體體現在 this 指標的變化。正是這種特徵更貼近 「萬物皆有所屬,宇宙乃萬物生存之根本」的自然觀點。在 程序清單 1中 window 便類似與宇宙的概念。
清單 1. 物件的上下文依賴
<script> <BR> var str = "我是一個String 物件, 我聲明在這裡, 但我不是獨立存在的! > var fun = function() { <BR> console.log( "我是一個Function 物件!誰叫我,我屬於誰:", this ); <BR> }; <BR> <BR> obj.fun = fun; <P> console.log( this === window ); // 印出true console.log( window.str === str ); // 列印true <P> console.log( window.obj == // 列印true <BR> console.log( window.obj ==window = obj ); // 列印true <BR> console.log( window.fun === fun ); // 列印true <BR> fun(); Function 物件!誰呼喚我,我屬於誰:window <BR> obj.fun(); // 印刷 我是一個 Function 物件!誰呼喚我,我屬於誰:obj <BR> fun.apply(str); // 印出 我是一個 Function 物件!誰叫我,我屬於誰:str <BR> </script>
在接受了物件導向存在一種稱為基於原型實現的方式的事實之後,下面我們就可以來深入探討 ECMAScript 是如何依據這一方式構造自己的語言的。
最基本的物件導向
ECMAScript 是一門徹底的物件導向的程式語言(參考資源),JavaScript 是其中的一個變種 (variant)。它提供了 6 種基本資料類型,即 Boolean、Number、String、Null、Undefined、Object。為了實現面向對象,ECMAScript設計出了一種非常成功的數據結構- JSON(JavaScript Object Notation), 這一經典結構已經可以脫離語言而成為一種廣泛應用的數據交互格式(參考資源)。
應該說,具有基本資料型別和 JSON 建構語法的 ECMAScript 已經基本上可以實現物件導向的程式設計了。開發者可以隨意地用字面式聲明(literal notation)方式來建構一個對象,並對其不存在的屬性直接賦值,或用delete將屬性刪除( 註:JS 中的delete 關鍵字用於刪除物件屬性,經常被誤作為C 中的delete,而後者是用於釋放不再使用的物件),如 程式清單2 。
清單 2. 字面式 (literal notation) 物件宣告
var person = {
name: 「張三」,
age: 26,
gender: 「男性」, ( “我在吃” stuff );
}
};
person.height = 176;
delete person[ “age” ];
字面式聲明(literal notation)方式之外,ECMAScript 允許透過 構造器(constructor)建立物件。每個建構器其實是一個 函數(function) 物件, 此函數物件含有一個「prototype」屬性用於實作 基於原型的繼承(prototype-based inheritance)和 共享屬性(shared properties。 物件可以由「new 關鍵字 建構器呼叫」的方式來創建,如 程式清單 3: 清單 3. 使用構造器 (constructor) 建立物件
// 建構器Person 本身就是一個函數物件
function Person() {
// 這裡可以做一些初始化工作
}
// 它有一個名叫prototype 的屬性
Person.prototype = {
三」,
age: 26,
gender: 「男」,
eat: function( stuff ) {
}
// 使用new 關鍵字建構物件
var p = new Person();
徹底理解原型鏈 (prototype chain) 在ECMAScript 中,每個由構造器創建的物件擁有一個指向構造器prototype 屬性值的
隱式引用(implicit reference),這篇引用稱為 原型(prototype)。進一步,每個原型可以擁有指向自己原型的 隱式引用(即該原型的原型),如此下去,這就是所謂的原型鏈(prototype chain) (參考資源)。在特定的語言實作中,每個物件都有一個 __proto__ 屬性來實作原型的 隱式引用。 程序清單 4說明了這一點。 清單 4. 物件的 __proto__ 屬性與隱式參考
function Person( name ) {
this.name = name;
}
var p = new Person();
// 物件的隱式引用指向了建構器的prototype 屬性,所以此處列印true
console.log( p.__proto__ === Person.prototype );
// 原型本身是一個Object 對象,所以他的隱式引用指向了
// Object 構造器的prototype 屬性, 故而打印true
console.log( Person.prototype.__proto__ === Object.prototype );
// 建構器 Person 本身是函數對象,所以此處印出 true
console.log( Person.__proto__ === Function.prototype );
有了 原型鏈,便可以定義一種所謂的 屬性隱藏機制,並透過這個機制實現繼承。 ECMAScript 規定,當要給某個物件的屬性賦值時,解釋器會尋找該物件原型鏈中第一個含有該屬性的物件(註:原型本身就是一個對象,那麼原型鏈即為一組物件的鏈。反之,如果要取得某個物件屬性的值,解釋器自然是傳回該物件原型鏈中首先具有該屬性的物件屬性值。 圖 1說名了這中隱藏機制:
在圖1 中,object1->prototype1->prototype2 構成了物件object1 的原型鏈,根據上述屬性隱藏機制,可以清楚地看到prototype1 物件中的property4 屬性和prototype2 物件中的property3 屬性皆被隱藏。了解原型鏈,那麼將非常容易理解 JS 中基於原型的繼承實作原理,程式清單 5 是利用原型鏈實現繼承的簡單範例。
清單 5. 利用原型鏈 Horse->Mammal->Animal 實作繼承
// () {
}
// 將Animal 的prototype 屬性指向一個對象,
// 也可直接理解為指定Animal 對象的原型
Animal.prototype = {
",
weight: 0,
eat: function() {
alert( "Animal is eating!" ); function Mammal() {
this.name = "mammal";
}
// 指定Mammal 物件的原型為Animal 物件。 Mammal 物件與Animal 物件之間的原型鏈
Mammal.prototype = new Animal();
// 宣告Horse 物件建構子
function Horse( height, weight ) {
this.name = "horse";
this.height = height;
} // 將Horse 物件的原型指定為一個Mamal 對象,繼續建構Horse 與Mammal 之間的原型鏈
Horse.prototype = new Mammal();
// 重新指定eat 方法, 此方法將覆寫從Animal 原型繼承過來的eat 方法
Horse.prototype.eat = function() {
alert( "Horse is eating grass!" )
}
// 驗證並瞭解原型鏈
var horse = new Horse( 100, 300 );
理解清單 5 中物件原型繼承邏輯實作的關鍵在於 Horse.prototype = new Mammal() 和 Mammal.prototype = new Animal() 這兩句程式碼。首先,等式右邊的結果是建構出一個臨時對象,然後將這個對象賦值給等式左邊對象的 prototype 屬性。也就是說將右邊新建的物件作為左邊物件的原型。讀者可以將這兩個等式替換到對應的程式清單 5 程式碼最後兩行的等式中自行領悟。
從程式碼清單 5 可以看出,基於原型的繼承方式,雖然實現了程式碼復用,但其行文鬆散且不夠流暢,可閱讀性差,不利於實現擴展和對原始程式碼進行有效地組織管理。不得不承認,類別繼承方式在語言實作上更具健壯性,且在建構可重複使用程式碼和組織架構程式方面具有明顯的優勢。這使得程式設計師希望尋找到一種能夠在 JavaScript 中以類別繼承風格進行編碼的方法途徑。從抽象的角度來講,既然類別繼承和原型繼承都是為實現物件導向而設計的,並且他們各自實現的載體語言在計算能力上是等價的( 因為圖靈機的計算能力與Lambda 演算的運算能力是等價的),那麼能不能找到一種變換,使得原型式繼承語言透過該變換實現具有類別繼承編碼的風格呢?
目前一些主流的 JS 框架都提供了這個轉換機制,也也就是類別式宣告方法,例如 Dojo.declare()、Ext.entend() 等等。使用者使用這些框架,可以輕易且友善地組織自己的 JS 程式碼。其實,在眾多框架出現之前,JavaScript 大師 Douglas Crockford 最早利用三個函數對Function 物件進行擴展,實現了這種變換,關於它的實現細節可以(參考資源 )。另外還有由 Dean Edwards實現的著名的 Base.js(參考資源)。值得一提的是,jQuery 之父 John Resig 在搏眾家之長之後,用不到 30 行程式碼便實現了自己的 Simple Inheritance。使用其提供的 extend 方法聲明類別非常簡單。 程式清單 6是使用了 Simple Inheritance函式庫實現類別的聲明的例子。其中最後一句列印輸出語句是對 Simple Inheritance實作類別繼承的最佳說明。
清單 6. 使用 Simple Inheritance 實作類別繼承
// 聲明 類型
_issleeping: true,
init: function( name ) {
this._name = name return this. _issleeping;
}
} );
// 宣告Programmer 類別,並繼承Person
var Programmer = Person.extend( { this._super( name );
//設定自己的狀態
var person = new Person( "張三" );
var diors = new Programmer( "張江男", false );
// 印出true
console.log( person.isSleeping() );
console.log( person.isSleeping() );
console.log( person.isSleeping() );
console.log( person.isSleeping() );
// 列印false
console.log( diors.isSleeping() );
// 此處全為true,故印true
console.log( person instanceof Person && person 🎜> console.log( person instanceof Person && person 🎜> console.log( person instanceof Person && person 🎜> && diors instanceof Programmer &&
diors instanceof Person && diors instanceof Class );
プロトタイプ、関数コンストラクター、クロージャー、およびコンテキストベースの this をすでによく理解している場合は、単純な継承がどのように機能するかを理解することはそれほど難しくありません。基本的に、ステートメント var Person = Class.extend(...) では、左側の Person は、実際には extend メソッドを呼び出す Class によって返されるコンストラクター、つまり関数オブジェクトへの参照を取得します。このアイデアに従って、単純な継承がどのようにこれを実行し、プロトタイプ継承からクラス継承への変換を実現するかを引き続き紹介します。図 2 は、Simple Inheritance のソース コードとそれに付随するコメントです。理解を容易にするために、コードを中国語で一行ずつ説明しています。
図 2. 単純な継承ソース コード分析
コードの 2 番目の部分を脇に置いて、最初と 3 番目の部分を全体として一貫して調べます。 extend 関数の基本的な目的は、新しいプロトタイプ プロパティを使用して新しいコンストラクターを構築することです。私たちは John Resig の見事な筆跡と、JS 言語の本質に対する彼の繊細な把握に感嘆せずにはいられません。 John Resig がこのような絶妙な実装方法をどのように思いついたのかについては、興味のある読者はこの記事 (参考資料) を読むことができます。この記事では、Simple Inheritance の初期設計の思考プロセスが詳しく説明されています。
JavaScript プライベート メンバーの実装 ここまでの説明で、オブジェクト指向 JavaScript にまだ懐疑的な人は、JavaScript がオブジェクト指向における情報の隠蔽、つまりプライベートとパブリックを実装していないのではないかという疑いがあるはずです。プライベート メンバーとパブリック メンバーを明示的に宣言する他のクラスベースのオブジェクト指向メソッドとは異なり、JavaScript の情報隠蔽はクロージャーによって実現されます。
リスト 7: を参照してください。 リスト 7. クロージャーを使用して情報の非表示を実装する
// プライベート プロパティを定義します
varpassword = pwd;
// プライベート メソッドを定義します
function getPassword() {
// クロージャでパスワードを返します
returnpassword;
}
// オブジェクトの他のパブリック メソッドがこの特権メソッドを通じてプライベート メンバーにアクセスするために使用される特権関数宣言
this.passwordService = function() {
return getPassword( ) ;
}
}
// パブリックメンバー宣言
User.prototype.checkPassword = function( pwd ) {
return this.passwordService() === pwd; ;
// 非表示を確認します
var u = new User( "123456" ) // true を出力します
console.log( u.checkPassword( "123456" ) ); / 未定義の
console.log( u.password );
// true
console.log( typeof u.gePassword === "unknown" );
JavaScript は、関数型言語の特性によって決定される情報の隠蔽を実現するためにクロージャーに依存する必要があります。上記は JavaScript のコンテキストベースの理解を前提としているため、この記事では関数型言語とクロージャのトピックについては説明しません。 JavaScript に隠蔽された情報については、
Douglas Crockford
が記事「JavaScript のプライベート メンバー」 (

熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

Go語言支援物件導向編程,透過型別定義和方法關聯實作。它不支援傳統繼承,而是透過組合實現。介面提供了類型間的一致性,允許定義抽象方法。實戰案例展示如何使用OOP管理客戶訊息,包括建立、取得、更新和刪除客戶操作。

PHP中OOP最佳實務包括命名約定、介面與抽象類別、繼承與多型、依賴注入。實戰案例包括:使用倉庫模式管理數據,使用策略模式實現排序。

在Golang(Go語言)中並沒有傳統意義上的類別的概念,但它提供了一種稱為結構體的資料類型,透過結構體可以實現類似類別的物件導向特性。在本文中,我們將介紹如何使用結構體實現物件導向的特性,並提供具體的程式碼範例。結構體的定義和使用首先,讓我們來看看結構體的定義和使用方式。在Golang中,結構體可以透過type關鍵字定義,然後在需要的地方使用。結構體中可以包含屬

JavaScript中的HTTP狀態碼取得方法簡介:在進行前端開發中,我們常常需要處理與後端介面的交互,而HTTP狀態碼就是其中非常重要的一部分。了解並取得HTTP狀態碼有助於我們更好地處理介面傳回的資料。本文將介紹使用JavaScript取得HTTP狀態碼的方法,並提供具體程式碼範例。一、什麼是HTTP狀態碼HTTP狀態碼是指當瀏覽器向伺服器發起請求時,服務

Go語言支援物件導向編程,透過struct定義對象,使用指標接收器定義方法,並透過介面實現多態。物件導向特性在Go語言中提供了程式碼重用、可維護性和封裝,但也存在缺乏傳統類別和繼承的概念以及方法簽章強制型別轉換的限制。

透過掌握追蹤物件狀態、設定斷點、追蹤異常和利用xdebug擴展,可以有效調試PHP物件導向程式碼。 1.追蹤物件狀態:使用var_dump()和print_r()檢視物件屬性和方法值。 2.設定斷點:在開發環境中設定斷點,偵錯器會在執行到達斷點時暫停,以便檢查物件狀態。 3.追蹤異常:使用try-catch區塊和getTraceAsString()取得異常發生時的堆疊追蹤和訊息。 4.利用偵錯器:xdebug_var_dump()函數可在程式碼執行過程中檢查變數的內容。

JavaScript和WebSocket:打造高效率的即時搜尋引擎引言:隨著網路的發展,使用者對即時搜尋引擎的要求也越來越高。傳統的搜尋引擎在進行搜尋時,使用者需要點擊搜尋按鈕後才能得到結果,這種方式無法滿足使用者對於即時搜尋結果的需求。因此,採用JavaScript和WebSocket技術來實現即時搜尋引擎成為了一個熱門的話題。本文將詳細介紹使用JavaScri
