ホームページ  >  記事  >  ウェブフロントエンド  >  jsオブジェクト指向_html/css_WEB-ITnoseの包括的な理解

jsオブジェクト指向_html/css_WEB-ITnoseの包括的な理解

WBOY
WBOYオリジナル
2016-06-24 11:23:24902ブラウズ

はじめに

JavaScript は今日非常に人気があり、さまざまなアプリケーションがますますそれに依存するようになりました。 Web プログラマーは、さまざまな優れた JavaScript フレームワークを使用して Web アプリケーションを迅速に開発することに徐々に慣れてきており、ネイティブ JavaScript の学習と深い理解が無視されています。したがって、長年 JS を開発してきた多くのプログラマは、たとえフレームワークを使用していても、クロージャ、関数型プログラミング、およびプロトタイプについて常に不明確であることがよくあります。これらはすべて、ネイティブ JavaScript 言語の機能に対する理解が不十分であることの現れです。 JavaScript を上手に使いこなすためには、まず Java や C# などの他の高級言語のクラスベースのオブジェクト指向思考の干渉を放棄し、JavaScript のプロトタイプベースのオブジェクト指向の特徴を基礎から包括的に理解する必要があります。関数型言語の視点。この点を理解すれば、さらにこの言語を使いこなすことが可能になります。この記事は次のグループに適しています: JS フレームワークを使用したことはあるが、JS 言語の性質を理解していないプログラマー; Java、C++、およびその他の言語での開発経験があり、JavaScript を学習して使用する準備ができているプログラマー。 JavaScript がオブジェクト指向であるかどうかについて常に曖昧だが、JS 愛好家にとっては真実を知りたいと考えているプログラマー。

オブジェクト指向を再理解する

JavaScript が完全なオブジェクト指向言語であることを説明するには、まずオブジェクト指向の概念から始めて、オブジェクト指向のいくつかの概念について説明する必要があります。

  • すべてのものはオブジェクトである
  • オブジェクト カプセル化と継承の特徴を持っています
  • オブジェクト間でメッセージ通信が行われ、それぞれに情報の隠蔽機能があります
  • これら3点を踏まえると、C++は半オブジェクト指向かつ半手続き型言語です。クラスのカプセル化、継承、ポリモーフィズムを実装していますが、オブジェクト以外のグローバル関数と変数もあります。 Java と C# は完全なオブジェクト指向言語であり、オブジェクトなしでは存在できないように関数と変数をクラスの形式で編成します。しかし、ここでは関数自体がプロセスであり、特定のクラスにアタッチされているだけです。

    ただし、オブジェクト指向は単なる概念またはプログラミングのアイデアであり、その存在を特定の言語に依存すべきではありません。たとえば、Java はオブジェクト指向の考え方を使用して言語を構築し、クラス、継承、派生、ポリモーフィズム、インターフェイスなどのメカニズムを実装します。ただし、これらのメカニズムはオブジェクト指向プログラミングを実装するための手段にすぎず、必須ではありません。言い換えれば、言語は、独自の特性に基づいてオブジェクト指向を実装するための適切な方法を選択できます。したがって、ほとんどのプログラマは、最初に Java や C++ などの高レベルのコンパイル言語を学習または使用するため (Java はセミコンパイルおよびセミインタープリタであるため、一般にコンパイル言語として説明されます)、「クラス」という用語を先入観で受け入れます。オブジェクト指向の実装方​​法であるため、スクリプト言語を学習する際には、その言語がオブジェクト指向言語であるかどうか、またはオブジェクト指向性を備えているかどうかを判断するために、クラスベースのオブジェクト指向言語の概念を使用するのが通例です特徴。これは、プログラマーが JavaScript を深く学習して習得することを妨げる重要な理由の 1 つでもあります。

    実際、JavaScript 言語は、プロトタイプ (プロトタイプ) と呼ばれるメソッドを通じてオブジェクト指向プログラミングを実装します。 クラスベース)とプロトタイプベース(プロトタイプベース)のオブジェクト指向の違いについて、客観的な世界を構築する方法について説明しましょう。

    クラスベースのオブジェクト指向アプローチとプロトタイプベースのオブジェクト指向アプローチの比較

    クラスベースのオブジェクト指向アプローチでは、

    オブジェクト (

    オブジェクト)クラス (クラス) に依存します。 )プロデュース。プロトタイプベースのオブジェクト指向アプローチでは、オブジェクト (object)は、コンストラクター (constructor)に依存し、プロトタイプ (prototype)を使用して構築されます。 。 2 つの認識方法の違いを説明するために、客観的な世界の例を挙げてください。たとえば、工場で自動車を製造する場合、作業員は設計図を参照する必要があり、その設計によって自動車の製造方法が規定されます。ここでの設計図は言語で言えばクラス(クラス)のようなもので、車はこのクラス(クラス)に従って製造されます。一方、労働者や機械は(同等です)コンストラクターへ) は、​​エンジン、タイヤ、ステアリング ホイールなどのさまざまなコンポーネント (プロトタイプのさまざまな属性に相当) を使用して車を構築します。 実際、2 つの方法のうちどちらがオブジェクト指向のアイデアをより完全に表現しているかについては、まだ議論があります。しかし、著者は、プロトタイプ オブジェクト指向は、以下の理由から、より徹底したオブジェクト指向のアプローチであると信じています:

    まず第一に、客観的な世界におけるオブジェクトの作成は、他の物理的オブジェクトの構築の結果であり、抽象的なものです。 「車」、つまりクラスはエンティティではなく抽象概念であり、オブジェクトの生成はエンティティの生成です。

    第二に、すべてがオブジェクトであるというオブジェクト指向の最も基本的なルールによれば、クラス自体はオブジェクトではありません。ただし、プロトタイプ メソッドのコンストラクターとプロトタイプは、そのメソッドを使用して構築された別のオブジェクトです。

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

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

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

     <script>  var str = "我是一个 String 对象 , 我声明在这里 , 但我不是独立存在的!" var obj = { des: "我是一个 Object 对象 , 我声明在这里,我也不是独立存在的。" };  var fun = function() {     console.log( "我是一个 Function 对象!谁调用我,我属于谁:", this );  };  obj.fun = fun;  console.log( this === window );     // 打印 true  console.log( window.str === str );  // 打印 true  console.log( window.obj === obj );  // 打印 true  console.log( window.fun === fun );  // 打印 true  fun();                              // 打印 我是一个 Function 对象!谁调用我,我属于谁:window  obj.fun();                          // 打印 我是一个 Function 对象!谁调用我,我属于谁:obj  fun.apply(str);                   // 打印 我是一个 Function 对象!谁调用我,我属于谁:str  </script>

    オブジェクト指向のプロトタイプベースの実装と呼ばれる方法があるという事実を受け入れた後、ECMAScript がこの方法に基づいて独自の言語を構築する方法を深く掘り下げることができます。

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

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

    基本的なデータ型と JSON 構築構文を備えた ECMAScript は、基本的にオブジェクト指向プログラミングを実装できると言うべきです。開発者は自由にリ​​テラル表記を使用してオブジェクトを構築し、その存在しないプロパティに値を直接割り当てることができます。また、deleteを使用してプロパティを削除することもできます(注: JSのdeleteキーワードはオブジェクトの属性を削除するために使用され、よくdeleteと間違えられます) C++ (使用されなくなったオブジェクトを解放するために使用されます) (プログラム リスト 2 など)。 リスト 2. リテラル表記のオブジェクト宣言

     var person = {     name: “张三”,     age: 26,     gender: “男”,     eat: function( stuff ) {         alert( “我在吃” + stuff );     }  };  person.height = 176;  delete person[ “age” ];

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

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

    リテラル宣言 (

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

     // 构造器 Person 本身是一个函数对象 function Person() { 	 // 此处可做一些初始化工作 }  // 它有一个名叫 prototype 的属性 Person.prototype = {     name: “张三”,     age: 26,     gender: “男”,     eat: function( stuff ) {         alert( “我在吃” + stuff );     }  }  // 使用 new 关键字构造对象 var p = new Person();

    由于早期 JavaScript 的发明者为了使这门语言与大名鼎鼎的 Java 拉上关系 ( 虽然现在大家知道二者是雷锋和雷锋塔的关系 ),使用了 new 关键字来限定构造器调用并创建对象,以使其在语法上跟 Java 创建对象的方式看上去类似。但需要指出的是,这两门语言的 new含义毫无关系,因为其对象构造的机理完全不同。也正是因为这里语法上的类似,众多习惯了类式面向对象语言中对象创建方式的程序员,难以透彻理解 JS 对象原型构造的方式,因为他们总是不明白在 JS 语言中,为什么“函数名可以作为类名”的现象。而实质上,JS 这里仅仅是借用了关键字 new,仅此而已;换句话说,ECMAScript 完全可以用其它 new 表达式来用调用构造器创建对象。

     

    彻底理解原型链 (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. 原型链中的属性隐藏机制

    在图 1 中,object1->prototype1->prototype2 构成了 对象 object1 的原型链,根据上述属性隐藏机制,可以清楚地看到 prototype1 对象中的 property4 属性和 prototype2 对象中的 property3 属性皆被隐藏。理解了原型链,那么将非常容易理解 JS 中基于原型的继承实现原理,程序清单 5 是利用原型链实现继承的简单例子。

    清单 5. 利用原型链 Horse->Mammal->Animal 实现继承

     // 声明 Animal 对象构造器 function Animal() {  }  // 将 Animal 的 prototype 属性指向一个对象, // 亦可直接理解为指定 Animal 对象的原型 Animal.prototype = {     name: animal",     weight: 0,     eat: function() {         alert( "Animal is eating!" );     }  }  // 声明 Mammal 对象构造器 function Mammal() {     this.name = "mammal";  }  // 指定 Mammal 对象的原型为一个 Animal 对象。 // 实际上此处便是在创建 Mammal 对象和 Animal 对象之间的原型链 Mammal.prototype = new Animal();  // 声明 Horse 对象构造器 function Horse( height, weight ) {     this.name = "horse";     this.height = height;     this.weight = weight;  }  // 将 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 );  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() 这两句代码。首先,等式右边的结果是构造出一个临时对象,然后将这个对象赋值给等式左边对象的 prototype 属性。也就是说将右边新建的对象作为左边对象的原型。读者可以将这两个等式替换到相应的程序清单 5 代码最后两行的等式中自行领悟。

     

    JavaScript 类式继承的实现方法

    从代码清单 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 实现类式继承

     // 声明 Person 类 var Person = Class.extend( {     _issleeping: true,     init: function( name ) {         this._name = name;     },     isSleeping: function() {         return this._issleeping;     }  } );  // 声明 Programmer 类,并继承 Person  var Programmer = Person.extend( {     init: function( name, issleeping ) {         // 调用父类构造函数        this._super( name );         // 设置自己的状态        this._issleeping = issleeping;     }  } );  var person = new Person( "张三" );  var diors = new Programmer( "张江男", false );  // 打印 true  console.log( person.isSleeping() );  // 打印 false  console.log( diors.isSleeping() );  // 此处全为 true,故打印 true  console.log( person instanceof Person && person instanceof Class     && diors instanceof Programmer &&     diors instanceof Person && diors instanceof Class );

    如果您已对原型、函数构造器、闭包和基于上下文的 this 有了充分的理解,那么理解 Simple Inheritance 的实现原理也并非相当困难。从本质上讲,var Person = Class.extend(...)该语句中,左边的 Person 实际上是获得了由 Class 调用 extend 方法返回的一个构造器,也即一个 function 对象的引用。顺着这个思路,我们继续介绍 Simple Inheritance 是如何做到这一点,进而实现了由原型继承方式到类式继承方式的转换的。图 2 是 Simple Inheritance 的源码及其附带注释。为了方便理解,用中文对代码逐行补充说明。

    图 2.Simple Inheritance 源码解析

    抛开代码第二部分,整体连贯地考察第一和第三部分会发现,extend 函数的根本目的就是要构造一个具有新原型属性的新构造器。我们不禁感叹 John Resig的大师手笔及其对 JS 语言本质把握的细腻程度。至于 John Resig是如何想到这样精妙的实现方法,感兴趣的读者可以阅读本文 (参考资源),其中有详细介绍关于最初设计 Simple Inheritance 的思维过程。

     

    JavaScript 私有成员实现

    到此为止,如果您任然对 JavaScript 面向对象持怀疑态度,那么这个怀疑一定是,JavaScript 没有实现面向对象中的信息隐藏,即私有和公有。与其他类式面向对象那样显式地声明私有公有成员的方式不同,JavaScript 的信息隐藏就是靠闭包实现的。见 程序清单 7:

    清单 7. 使用闭包实现信息隐藏

     // 声明 User 构造器 function User( pwd ) {     // 定义私有属性    var password = pwd;     // 定义私有方法     function getPassword() {         // 返回了闭包中的 password         return password;     }     // 特权函数声明,用于该对象其他公有方法能通过该特权方法访问到私有成员    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" ) );  // 打印 undefined  console.log( u.password );  // 打印 true  console.log( typeof u.gePassword === "undefined" );

    JavaScript 必须依赖闭包实现信息隐藏,是由其函数式语言特性所决定的。本文不会对函数式语言和闭包这两个话题展开讨论,正如上文默认您理解 JavaScript 中基于上下文的 this 一样。关于 JavaScript 中实现信息隐藏,Douglas Crockford在《 Private members in JavaScript 》(参考资源)一文中有更权威和详细的介绍。

     

    结束语

    JavaScript 被认为是世界上最受误解的编程语言,因为它身披 c 语言家族的外衣,表现的却是 LISP 风格的函数式语言特性;没有类,却实也彻底实现了面向对象。要对这门语言有透彻的理解,就必须扒开其 c 语言的外衣,从新回到函数式编程的角度,同时摒弃原有类的面向对象概念去学习领悟它。随着近些年来 Web 应用的普及和 JS 语言自身的长足发展,特别是后台 JS 引擎的出现 ( 如基于 V8 的 NodeJS 等 ),可以预见,原来只是作为玩具编写页面效果的 JS 将获得更广阔发展天地。这样的发展趋势,也对 JS 程序员提出了更高要求。只有彻底领悟了这门语言,才有可能在大型的 JS 项目中发挥她的威力。

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