ホームページ  >  記事  >  ウェブフロントエンド  >  オブジェクト指向 JavaScript (IBM より) の包括的な理解_JavaScript スキル

オブジェクト指向 JavaScript (IBM より) の包括的な理解_JavaScript スキル

WBOY
WBOYオリジナル
2016-05-16 17:16:22948ブラウズ

当今 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) 本身也是其他对象通过原型方式构造出来的对象。

もう一度言いますが、クラスベースのオブジェクト指向言語では、オブジェクトの状態はオブジェクト インスタンスによって保持され、オブジェクトの動作メソッドはオブジェクトを宣言するクラスによって保持され、オブジェクトの構造のみが保持されます。プロトタイプのオブジェクト指向言語では、オブジェクトの動作とステータスはオブジェクト自体に属し、一緒に継承できます (参照リソース)。これも客観的な現実に近いものです。

最後に、Java などのクラスベースのオブジェクト指向言語では、手続き型言語でグローバル関数や変数を使用できない不便さを補うために、静的プロパティや静的メソッドを宣言できるようにしています。クラス。実際、すべてがオブジェクトであるため、客観的な世界にはいわゆる静的な概念は存在しません。プロトタイプのオブジェクト指向言語では、組み込みオブジェクト、グローバル オブジェクト、メソッド、またはプロパティを除いて、存在することは許可されず、静的な概念は存在しません。すべての言語要素 (プリミティブ) は、その存在のためにオブジェクトに依存する必要があります。ただし、関数型言語の特性により、言語要素が依存するオブジェクトは実行時コンテキストの変化に応じて変化し、特に this ポインターの変化に反映されます。この性質は、「すべてのものは何かに属しており、宇宙はすべてのものの生存の基盤である」という自然観に近いものです。 リスト 1ウィンドウ は宇宙の概念に似ています。

リスト 1. オブジェクトのコンテキスト依存関係

コードをコピーします コードは次のとおりです。

<script> <br> var str; = "私は String オブジェクトです。ここで宣言しますが、独立して存在しません! "<br> var obj = { des: "私は Object オブジェクトです。ここで宣言しますが、独立して存在しません。 " }; <br> var fun = function() { <br> console.log( "私は Function オブジェクトです! 誰が私を呼んでも、私は誰に属しますか:", this ); <br> }; <p> obj.fun = 楽しい </p> <p> console.log( this === window ); // true を出力します <br> console.log( window.str === str ) // true を出力します <br> console.log( window.obj == = obj ); // Print true <br> console.log( window.fun === fun ); // Print 私は Function オブジェクトです!誰が私に電話をかけても、私は誰に属しますか: window <br> obj.fun();誰が私を呼んでいるのか、私は誰に属しているのか: obj <br> fun.apply(str); // Print 私は Function オブジェクトです。誰が私を呼んでいますか、私は誰に属していますか: str <br> </script>


プロトタイプベースの実装と呼ばれるオブジェクト指向の実装方​​法があるという事実を受け入れた後、ECMAScript がこの方法に基づいて独自の言語をどのように構築するかを詳しく見てみましょう。



最も基本的なオブジェクト指向

ECMAScript は完全なオブジェクト指向プログラミング言語 (参考リソース) であり、JavaScript はそのバリアントです。これは 6 つの基本データ型、つまり Boolean、Number、String、Null、Unknown、Object を提供します。オブジェクト指向を実現するために、ECMAScript は非常に成功したデータ構造である JSON (JavaScript Object Notation) を設計しました。この古典的な構造は言語から分離でき、広く使用されるデータ対話形式 () になります。参照リソース)。

基本的なデータ型と JSON 構築構文を備えた ECMAScript は、基本的にオブジェクト指向プログラミングを実装できると言うべきです。開発者は、

リテラル表記 (リテラル表記) を自由に使用してオブジェクトを構築し、その存在しないプロパティに値を直接割り当てたり、削除属性を使用したりできます。 (注: JS の delete キーワードはオブジェクト属性を削除するために使用され、C で使用されなくなったオブジェクトを解放するために使用される delete とよく間違われます) ( Program List 2 など)。

リスト 2. リテラル表記のオブジェクト宣言

コードをコピー コードは次のとおりです:

var person = {
名前: "张三"、
年齢: 26、
性別: "男性"、
食べる: function(stuff) {
アラート( "私は" ものを食べています );
}
};
person.height = 176;

実際の開発プロセスでは、ほとんどの初心者や、JS アプリケーションに対する高度な要件を持たない開発者は、基本的に、基本的な開発ニーズを満たすために ECMAScript 定義のこの部分のみを使用します。ただし、このようなコードの再利用性は、継承、派生、ポリモーフィズムなどを実装した他のクラスベースのオブジェクト指向の強く型付けされた言語と比較すると非常に弱く、複雑な JS アプリケーション開発のニーズを満たすことができません。そこで ECMAScript では、オブジェクト継承の問題を解決するためにプロトタイプを導入しました。


関数コンストラクターを使用してオブジェクトを構築します

リテラル表記 (リテラル表記) メソッドに加えて、ECMAScript では コンストラクター (コンストラクター) 。各コンストラクターは実際には 関数 (function) オブジェクト であり、 プロトタイプベース プロトタイプベースの継承 を実装するための「プロトタイプ」属性が含まれています。 >共有プロパティ オブジェクトは、プログラム リスト 3: などの「新しいキーワード コンストラクター呼び出し」によって作成できます。 リスト 3. コンストラクターを使用してオブジェクトを作成する コードをコピー

コードは次のとおりです:

// コンストラクター person 自体は関数ですobject function Person() { // 一部の初期化作業はここで実行できます } // プロトタイプと呼ばれる属性があります Person.prototype = { name: "張「三」、
年齢: 26、
性別: "男性"、
食べる: function( スタッフ ) {
アラート( "私は " スタッフ );
}
}
// new キーワードを使用してオブジェクトを構築します
var p = new Person();



初期の JavaScript の発明者は、言語を有名な Java に接続するために、コンストラクターの呼び出しを制限し、オブジェクトを作成するために new キーワードを使用したためです (ただし、この 2 つが雷峰と雷峰塔に関連していることは今では誰もが知っています)。構文は Java がオブジェクトを作成する方法と似ていることがわかります。ただし、オブジェクト構築メカニズムが完全に異なるため、これら 2 つの言語における new の意味はそれとは何の関係もないことを指摘しておく必要があります。クラスベースのオブジェクト指向言語でのオブジェクト作成方法に慣れている多くのプログラマが、JS オブジェクトのプロトタイプ構築方法を完全に理解するのが難しいのは、まさにここでの構文の類似性のためです。彼らは常に理解していないためです。なぜJS言語では「関数」がクラス名として使えるのかという現象。本質的に、JS はここで new キーワードを借用するだけであり、それ以上は何も借用しません。つまり、ECMAScript はコンストラクターを呼び出すことによって他の new 以外の式を使用してオブジェクトを作成できます。




プロトタイプチェーンを完全に理解する
ECMAScript では、コンストラクターによって作成された各オブジェクトには、コンストラクターのプロトタイプ属性の値を指す

暗黙的参照 (

暗黙的参照) があります。参照は プロトタイプ (

プロトタイプ) と呼ばれます。さらに、各プロトタイプは、それ自体のプロトタイプ (つまり、プロトタイプのプロトタイプ) を指す 暗黙の参照 を持つことができます。これが続くと、これがいわゆる プロトタイプ チェーン (プロトタイプ チェーン) (参考リソース)。特定の言語実装では、各オブジェクトには、プロトタイプへの 暗黙的参照 を実装するための __proto__ 属性 があります。 リスト 4 はこの点を示しています。 リスト 4. オブジェクトの __proto__ 属性と暗黙的参照 コードをコピー

コードは次のとおりです:


function Person( name ) {
this.name = name;
}
var p = new Person(); // オブジェクトの暗黙的な参照は、コンストラクターのプロトタイプ属性なので、ここに true が出力されます
console.log( p.__proto__ === Person.prototype );
// プロトタイプ自体は Object オブジェクトであるため、その暗黙の参照は // Object コンストラクターのプロトタイプ属性であるため、true が出力されます

console.log( Person.prototype.__proto__ == = オブジェクト.プロトタイプ );
// コンストラクター person 自体は関数オブジェクトであるため、ここに true が出力されます
console.log( Person.__proto__ === Function.prototype );



プロトタイプ チェーンを使用すると、いわゆる

プロパティ隠蔽メカニズムを定義し、このメカニズムを通じて継承を実現できます。 ECMAScript では、オブジェクトの属性に値を割り当てるときに、インタプリタがオブジェクトのプロトタイプ チェーン内でその属性を含む最初のオブジェクトを検索することが規定されています (注: プロトタイプ自体がオブジェクトであり、プロトタイプ チェーンはセットのチェーンになります)オブジェクトのプロトタイプ チェーン内の最初のオブジェクトは、割り当ての対象となるオブジェクトそのものです。逆に、オブジェクト プロパティの値を取得したい場合、インタプリタは、オブジェクトのプロトタイプ チェーン内で最初にそのプロパティを持つオブジェクト プロパティ値を自然に返します。 図 1 は、隠れたメカニズムを説明しています:


図 1. プロトタイプ チェーンのプロパティ隠蔽メカニズム
図 1 では、object1->prototype1->prototype2 がオブジェクト object1 のプロトタイプ チェーンを構成しています。上記の属性隠蔽メカニズムによれば、prototype1 オブジェクトの property4 属性と、prototype1 オブジェクトの property3 属性が明確にわかります。 prototype2 オブジェクトは両方とも非表示になります。プロトタイプ チェーンを理解すると、JS でのプロトタイプベースの継承の実装原理を理解するのが非常に簡単になります。图 1. 原型链中的属性隐藏机制プログラム リスト 5

は、プロトタイプ チェーンを使用して継承を実装する簡単な例です。

リスト 5. プロトタイプ チェーン Horse->Mammal->Animal を使用して継承を実装する

コードをコピー コードは次のとおりです:// Animal オブジェクト コンストラクターを宣言します
function Animal () {
}
// Animal のプロトタイプ属性をオブジェクトにポイントします
// これは、Animal オブジェクトのプロトタイプを指定すると直接理解することもできます
Animal.prototype = {
名前: 動物 ",
体重: 0,
食べる: function() {
alter( "動物が食べています!" );
}
}
/ / 哺乳類オブジェクトのコンストラクターを宣言します
function Mammal() {
this.name = "mammal";
}
// 哺乳類オブジェクトのプロトタイプを動物オブジェクトとして指定します
// これ実際に作成しているのは、哺乳類オブジェクトと動物オブジェクト間のプロトタイプ チェーン
Mammal.prototype = new Animal();
// Horse オブジェクト コンストラクターを宣言します
function Horse( height,weight) {
this.name = "horse"; this.weight = height; 🎜> }

// Horse オブジェクトのプロトタイプを Mammal オブジェクトとして指定し、Horse と Mammal の間のプロトタイプ チェーンの構築を続行します
Horse.prototype = new Mammal();
// Eat メソッドを再指定します。このメソッドは、Animal プロトタイプから継承された Eat メソッドをオーバーライドします。
Horse.prototype.eat = function() {
alter( "Horse is eatgrass!" ) ;
}
// プロトタイプ チェーンを確認して理解する
var horse = new Horse( 100, 300 ); console.log( horse.__proto__ === Horse.prototype ); > console.log( Horse.prototype.__proto__ === Mammal.prototype );

console.log( Mammal.prototype.__proto__ === Animal.prototype );

リスト 5 のオブジェクト プロトタイプ継承ロジックの実装を理解する鍵は、Horse.prototype = new Mammal() と Mammal.prototype = new Animal() の 2 行のコードにあります。まず、方程式の右側の結果として一時オブジェクトが構築され、次にこのオブジェクトが方程式の左側のオブジェクトのプロトタイプ プロパティに割り当てられます。つまり、右側の新しく作成されたオブジェクトは、左側のオブジェクトのプロトタイプとして使用されます。読者は、これら 2 つの式をリスト 5 のコードの最後の 2 行にある対応する式に置き換えて理解することができます。

JavaScript クラス継承の実装方法

コードリスト 5 からわかるように、プロトタイプベースの継承方法はコードの再利用を実現しますが、その記述は緩くて流暢ではなく、可読性も低いため、拡張や効果的な組織と管理には役立ちません。ソースコード。クラス継承は言語実装においてより堅牢であり、再利用可能なコードの構築やプログラムの編成において明らかな利点があることを認めなければなりません。このため、プログラマは JavaScript でクラスベースの継承スタイルでコーディングする方法を模索するようになりました。抽象的な観点から見ると、クラス継承とプロトタイプ継承はどちらもオブジェクト指向を実装するように設計されており、それらによって実装されるキャリア言語は計算能力の点で同等であるため(チューリングマシンとラムダ計算の計算能力は同等であるため)累乗は等価です)、この変換を通じてプロトタイプ継承言語がクラスベースの継承コーディング スタイルを実装できるようにする変換を見つけることができるでしょうか?

現在、一部の主流の JS フレームワークは、この変換メカニズム、つまり、Dojo.declare()、Ext.entend() などのクラス宣言メソッドを提供しています。これらのフレームワークを使用すると、ユーザーは JS コードを簡単かつフレンドリーに整理できます。実際、多くのフレームワークが登場する前に、JavaScript マスター Douglas Crockford が最初に 3 つの関数を使用して Function オブジェクトを拡張し、この変換を実現しました。リソース )。 Dean Edwards によって実装された有名な Base.js もあります (参考リソース)。 jQuery の父である John Resig が、他の人の強みを活用した後、30 行未満のコードで独自の 単純な継承 を実装したことは言及する価値があります。クラスが提供する extend メソッドを使用してクラスを宣言するのは非常に簡単です。 プログラム リスト 6 は、Simple Inheritance ライブラリ実装クラス宣言の使用例です。最後の print 出力ステートメントは、クラス継承を実装するための 単純な継承 を最もよく説明しています。 リスト 6. 単純継承を使用したクラス継承の実装

// Person クラスを宣言します
var Person = Class .extend( {
_issleeping: true,
init: function( name ) {
this._name = name;
},
isSleeping: function() {
return this.
}
} );
// Programmer クラスを宣言し、 Person
var Programmer = Person.extend( {
init: function( name, issleeping ) {
// 親クラスのコンストラクターを呼び出します
this._super( name );
// 独自の状態を設定します
this._issleeping = issleeping;
}
> var person = new Person( "Zhang San" );
var diors = new Programmer( "Zhang Jiangnan", false );
// print true
console.log( person.isSleeping() ) ;
//Print false
console.log( diors.isSleeping() );
// ここではすべて true なので、true を出力します
console.log( personinstanceof person && personinstanceof Class
&& diors インスタンスオブ プログラマー &&
ディオール インスタンスオブ パーソン && ディオール インスタンスオブ クラス );

プロトタイプ、関数コンストラクター、クロージャー、およびコンテキストベースの this をすでによく理解している場合は、単純な継承がどのように機能するかを理解することはそれほど難しくありません。基本的に、ステートメント var Person = Class.extend(...) では、左側の Person は、実際には extend メソッドを呼び出す Class によって返されるコンストラクター、つまり関数オブジェクトへの参照を取得します。このアイデアに従って、単純な継承がどのようにこれを実行し、プロトタイプ継承からクラス継承への変換を実現するかを引き続き紹介します。図 2 は、Simple Inheritance のソース コードとそれに付随するコメントです。理解を容易にするために、コードを中国語で一行ずつ説明しています。

図 2. 単純な継承ソース コード分析

图 2.Simple Inheritance 源码解析

コードの 2 番目の部分を脇に置いて、最初と 3 番目の部分を全体として一貫して調べます。 extend 関数の基本的な目的は、新しいプロトタイプ プロパティを使用して新しいコンストラクターを構築することです。私たちは John Resig の見事な筆跡と、JS 言語の本質に対する彼の繊細な把握に感嘆せずにはいられません。 John Resig がこのような絶妙な実装方法をどのように思いついたのかについては、興味のある読者はこの記事 (参考資料) を読むことができます。この記事では、Simple Inheritance の初期設計の思考プロセスが詳しく説明されています。

JavaScript プライベート メンバーの実装 ここまでの説明で、オブジェクト指向 JavaScript にまだ懐疑的な人は、JavaScript がオブジェクト指向における情報の隠蔽、つまりプライベートとパブリックを実装していないのではないかという疑いがあるはずです。プライベート メンバーとパブリック メンバーを明示的に宣言する他のクラスベースのオブジェクト指向メソッドとは異なり、JavaScript の情報隠蔽はクロージャーによって実現されます。

リスト 7

: を参照してください。 リスト 7. クロージャーを使用して情報の非表示を実装する

// ユーザー コンストラクターを宣言
関数User( pwd ) {
// プライベート プロパティを定義します
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 のプライベート メンバー」 (
参考資料) でより信頼できる詳細な紹介をしています。

結論

JavaScript は、C 言語ファミリーの仮面をかぶっているため、世界で最も誤解されているプログラミング言語であると考えられていますが、クラスを持たず、LISP スタイルの関数型言語の機能を表現していますが、オブジェクトも完全に実装しています。指向性のある。この言語を完全に理解するには、C 言語のコートを脱ぎ、関数型プログラミングの観点に立ち戻り、同時に元のクラスのオブジェクト指向の概念を捨てて学習し、理解する必要があります。近年の Web アプリケーションの人気と JS 言語自体の急速な発展、特にバックエンド JS エンジン (V8 ベースの NodeJS など) の登場により、本来は単なる JS であったものが、ページエフェクトを書くためのおもちゃとして、より広い世界が広がります。この開発傾向により、JS プログラマーに対する要求も高まります。この言語を徹底的に理解することによってのみ、大規模な JS プロジェクトでその力を活用することができます。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。