JavaScript 学習の概要 [8]、オブジェクト指向プログラミング

黄舟
リリース: 2017-02-10 09:37:04
オリジナル
1208 人が閲覧しました

1. オブジェクト指向プログラミングとは何ですか? ガールフレンド オブジェクト、オブジェクト指向とは、コードでは、あるコードが別のコードと恋に落ちることを意味します。 、私たちは愛情を込めてこのコードに直面しています。これはオブジェクト指向と呼ばれます。このように人々に説明したい人は誰でも大笑いするでしょうしかし、ボーイフレンドまたはガールフレンドはオブジェクトと見なすことができます。オブジェクトには、性別、身長、体重、出身地などが含まれます。オブジェクトには、歩く、走る、ジャンプするなどの属性と方法があります。 。すると、オブジェクトを 2 つの側面から理解できます:

(1) オブジェクト自体から見ると、オブジェクトは単一の物理的オブジェクトの抽象化です。 本、車、テレビもオブジェクトとみなすことができます。Web ページ、データベース、サーバー リクエストもオブジェクトとみなすことができます。 物理的なオブジェクトがオブジェクトに抽象化されると、次のようになります。物理的なオブジェクト間の関係がオブジェクト間の関係になるため、実際の状況をシミュレートし、「オブジェクト」をプログラムすることができます。

(2) オブジェクトの性質から、オブジェクトはプロパティやメソッドを含むコンテナです。

いわゆる属性はオブジェクトの状態であり、いわゆるメソッドはオブジェクトの動作(特定のタスクを完了するための)です。たとえば、動物をオブジェクトに抽象化できます。属性は特定の動物を記録し、メソッドは狩猟、走る、攻撃、飛行、這う、休憩などの動物の行動を表します。

一般的に言えば、オブジェクトはいくつかの外部操作を提供する全体です

例えば、テレビの内部構造と動作原理は理解できませんが、私たちは皆それを使用しています。テレビ、ボタンをうまく使い、その操作方法を知っている限り、この回路やそのコンポーネントがどのように機能するかは私たちには関係ありません。テレビが正常に動作する限り、これらの機能を使用できます。これはオブジェクト指向です。もう 1 つの例は、日付を取得することです。年、月、週などのさまざまな属性を使用してさまざまな時間を取得できます。具体的にどのように実装されるかはわかりませんが、必要なものを取得するためにどの属性を使用すればよいかはわかります。 . これはオブジェクト用です。 では、オブジェクト指向とは一体何でしょうか?簡単に言えば、内部原理を理解せずに機能を使用していることになります。つまり、オブジェクトを使用するときは、オブジェクトが提供する機能のみに注目し、その内部の詳細には注意を払いません。代表的なアプリケーション例は jQuery です。

オブジェクト指向はプログラミングだけでなく、あらゆるものに使用できる普遍的な概念ですが、私たちはそれをオブジェクト指向とは呼びません。直接的にはオブジェクト指向と呼びます。 例えば、食事をしに行ったら、シェフに豚の角煮を持ってくるように言い、その後座って食べるのを待つことができます。シェフに肉を四角形または円形に切るように指示することはできません。 、最初に塩を加えてから、醤油、黒砂糖、または氷砂糖を加えます。本当にこれをやりたい場合は、シェフがシェフであるかどうかを心配する必要があります。あなたが食べたいものを彼に言えば、彼がそれをどのように調理するかを心配する必要はありません、彼はそれを自然にあなたに提供します。これは人生における典型的なオブジェクト指向の考え方です。

従来のオブジェクト指向プログラミング言語とは異なりますが、JS には強力なオブジェクト指向プログラミング機能もあります 以下は、JS オブジェクト指向プログラミングとは何かについて詳しく説明します。

オブジェクト指向プログラミング(略称OOP)は、現在主流のプログラミングパラダイムであり、いわゆるパラダイムは、あるレベルに準拠した関係パターンの集合です。彼の基本的なアイデアは、現実世界のさまざまな複雑な関係をオブジェクトに抽象化し、オブジェクト間の分業と協力によって現実世界のシミュレーションを完成させるというものです。 オブジェクト指向プログラミング プログラムは、一定のレベルの関係パターンに準拠した集合であり、各オブジェクトは明確な役割分担を持つ機能中心です。情報、データの処理、メッセージの送信、その他のタスク。 したがって、オブジェクト指向プログラミングは、柔軟性、コードの再利用性、モジュール性などの特徴を備えており、保守や開発が容易ですが、複数の人が協力する大規模なプロジェクトには非常に適していますが、一般的には適していません。日常のプロジェクトでよく使用されます。

オブジェクト指向プログラミング (OOP) の特徴:

(1)、抽象化: 核心的な問題を把握する

だから-抽象化と呼ばれます。まず、Baidu の抽象化の説明を見てみましょう。抽象化とは、多くのものか​​ら共通かつ必須の特徴を抽出し、その一方で、本質的でない特徴を破棄することです。たとえば、リンゴ、バナナ、梨、ブドウ、桃など、それらに共通する特徴は果物であるということです。果物という概念に到達するプロセスは抽象的なプロセスです。抽象化するには比較をしなければなりませんが、比較しなければ本質的に共通する部分は見つかりません。共通特性とは、ある種類のものを他の種類のものから区別できる特性を指します。これらの区別できる特性は、本質的な特性とも呼ばれます。したがって、物事の共通の特徴を抽出するということは、物事の本質的な特徴を抽出し、本質的でない特徴を破棄することを意味します。したがって、抽象化のプロセスは調整のプロセスでもあります。抽象化する場合、類似点と相違点は抽象化する角度によって異なります。抽象化の角度は、問題を分析する目的によって異なります。

JSでは抽象化の核心は、共通の特徴や核となる問題を把握することです。 例えば、人には名前、性別、出身地、生年月日、身長、体重、血液型、自宅の住所、両親が誰なのか、子供の名前が何なのかなど、多くの特徴があります。従業員ファイルを作成したいと考えていますが、各人の特徴を含めることは不可能であり、名前、性別、部署、役職などの主要な特徴を取得するか、単に入社日を追加する必要があります。出会い系サイトに登録する必要がある場合、現時点では従業員プロフィールに指定されている特徴は必要ありませんが、性別、年齢、身長、体型、星座、車の有無、家、仕事、収入、家族状況など。ある種の物事の主な特徴と、問題に関連する特徴を抽出することです。これはオブジェクト指向プログラミングの抽象化です。

(2)、カプセル化:内部実装は考慮せず、機能的な使用のみを考慮してください

カプセル化とはテレビのようなものです。待ってください。ただし、内部構造は見えません。製品が壊れていない限り、内部にあるものはパッケージであるため、内部に何が入っているかを知る必要はありません。

JS は内部実装を考慮せず、jQuery を使用するのと同じように、関数の使用のみを考慮します。jQuery の関数を使用すると、JS と同じ効果が得られます。 JS を使用するよりも便利です。

(3) 継承: 既存のオブジェクトから新しいオブジェクトを継承する

いわゆる継承は遺伝とも呼ばれます一般的な理解は、親ができることは子供もできるということです食事や睡眠などの行動ができる。 たとえば、JS にはオブジェクト A があります。A にはいくつかの機能があります。今度はオブジェクト B が A から継承されます。このオブジェクト B はオブジェクト A のすべての機能を持ちます。

別の状況は 多重継承です。 子が複数の父親を持つことができるのと同じように、これは明らかに不可能ですが、プログラムでは実現可能です。 たとえば、The box がある場合です。クラスには物を保持するために使用できる特性があり、車のクラスもあります。車の特性は、車輪が付いていることです。このとき、多重継承を使用して別の種類のコンテナを継承できます。トラックの特性を利用して物を載せることができ、走行することができ、車輪が付いています。

(4)、ポリモーフィズム

ポリモーフィズムは、名前が示すように、さまざまな状態のことであり、オブジェクト指向言語では、インターフェイスの複数の異なる実装をポリモーフィズムと呼びます。ポリモーフィズムは JS ではそれほど明白ではありませんが、Java や C++ などの強力な言語ではより便利です。 JS のような弱い言語の場合、それはあまり意味がありません。

2. オブジェクトの構成

オブジェクトは、ホストオブジェクト、ローカルオブジェクト、組み込みオブジェクトに分けることができます。

ホストオブジェクトとはDOMやBOMのことで、ブラウザが提供するオブジェクトです。

ローカルオブジェクトは非静的オブジェクトです。いわゆるローカルオブジェクトは、最初に新しく作成してから使用する必要があります。よく使用されるオブジェクト: Object、Function、Array、String、Boolean、Number、Date、RegExp、Error。

組み込みオブジェクトは静的オブジェクトであり、newなしで直接使用できるクラスです。 Math は最も一般的であり、直接使用できる唯一の組み込みオブジェクトです。

オブジェクト指向の最初のステップは、オブジェクトを作成することです。 典型的なオブジェクト指向プログラミング言語には「クラス」という概念があり、いわゆるクラスとは、果物などの特定の種類の物に共通の特性を表すオブジェクトです。 object はクラスの特定のインスタンスです。たとえば、リンゴは果物の一種です。 クラスは抽象クラスであり、メモリを占有しません。一方、オブジェクトは具象クラスであり、ストレージ領域を占有します。ただし、JS には「クラス」という概念はありませんが、コンストラクターを使用して実装できます。

前にも言いましたが、いわゆる「コンストラクター」は、新しいオブジェクトを作成するために使用される関数です。オブジェクトの基本構造として、コンストラクターは複数のオブジェクトを作成でき、これらのオブジェクトは同じものを持ちます。構造。コンストラクタは通常の関数ですが、その性質や使い方は通常の関数とは異なります。 コンストラクターの最大の特徴は、

がオブジェクトを作成するときにnewキーワードを使用する必要があり、thisキーワードは作成されるオブジェクトインスタンスを表す関数本体内で使用できることです、これは、関数の実行時に現在のオブジェクトを指すために使用されます。 具体的な状況は後で分析します。次に、オブジェクトの構成を調べてみましょう。 実際、オブジェクトの概念はすでに理解しているので、オブジェクトが何で構成されているかを理解するのは難しくありません。 JS では、すべてがオブジェクトです。 JS、属性メソッドをどう理解するか? 属性は変数であり、メソッドは関数です。属性は、動物がどの動物であるかを記録する属性と同様に、ステータスを表します。変数名は、メソッドの説明であるメソッドの名前とも言えます。この方法は行動であり、特定のタスクを完了するプロセスであり、動的です。

3. オブジェクト指向プログラミング

オブジェクトの構成とオブジェクト指向性を理解するために、例を通してオブジェクトにプロパティとメソッドを追加します。

(1), インスタンス: オブジェクトに属性を追加します


<script>
var a = 2;
alert(a);    //返回:2

var arr = [5,6,7,8,9];
//给数组定义一个属性a,等于2。
arr.a = 2;
alert(arr.a);    //返回:2
arr.a++;
alert(arr.a);    //返回:3
</script>
ログイン後にコピー

上記の例を通して、次のことがわかります。 変数とプロパティは同様に、属性は変数で実行できることを実行でき、変数は属性で実行できることを実行できます。それらの違いは、変数は自由でどのオブジェクトにも属さないことですが、例のオブジェクト a と同様に、変数は配列 arr に属します。ああ。たとえば、インデックスの p に属性を定義できます: op[i].index = i。 (2),

例: オブジェクトにメソッドを追加

<script>
function a(){
    alert('abc');    //返回:abc
}

var arr = [5,6,7,8,9];
//给函数添加一个a函数的方法
arr.a = function (){
    alert('abc');    //返回:abc
};
a();
arr.a();
</script>
ログイン後にコピー

上記の例を通して、

機能も無料

、および この 関数がオブジェクトに属している場合、これがメソッド、 は配列 arr のメソッド、 はこのオブジェクトのメソッドです。 つまり、関数とメソッドも同等です。関数ができることとメソッドができることの違いは、関数がオブジェクトに属することです。

システムオブジェクトにプロパティやメソッドを自由に追加することはできません。追加しないと、既存のプロパティやメソッドが上書きされます。

たとえば、配列オブジェクトに属性とメソッドを追加しました。配列には独自の属性とメソッドが追加され、配列自体の属性とメソッドが上書きされます。注意が必要です。
(3),

インスタンス:

オブジェクトの作成

<script>
var obj = new Object();
var d = new Date();
var arr = new Array();
alert(obj);    //返回:[object Object]
alert(d);    //返回当前时间
alert(arr);    //返回为空,空数组
</script>
ログイン後にコピー


新しいオブジェクトを作成新しいオブジェクトを作成できます。オブジェクトは、システムに付属するいくつかのものだけを備えた空のオブジェクトであるため、オブジェクト指向を実装する場合は、オブジェクトにメソッドと属性を追加できます。これにより、他者との衝突を最小限に抑えることができます。

(4),

例:

オブジェクト指向プログラム

<script>
//创建一个对象
var obj = new Object();
//可写为:var obj={};

//给对象添加属性
obj.name = '小白';
obj.qq = '89898989';

//给对象添加方法
obj.showName = function (){
    alert('我的名字叫:'+this.name);
};
obj.showQQ = function (){
    alert('我的QQ是:'+this.qq);
};
obj.showName();    //返回:我的名字叫:小白
obj.showQQ();    //返回:我的QQ是:89898989

//再创建一个对象
var obj2 = new Object();

obj2.name = '小明';
obj2.qq = '12345678';

obj2.showName = function (){
    alert('我的名字叫:'+this.name);
};
obj2.showQQ = function (){
    alert('我的QQ是:'+this.qq);
};
obj2.showName();    //返回:我的名字叫:小白
obj2.showQQ();        //返回:我的QQ是:12345678
</script>
ログイン後にコピー


これは最も単純なオブジェクト指向プログラミングであり、オブジェクトを作成し、オブジェクトに属性を追加しますメソッドとメソッドは、現実の状況とプログラム オブジェクトをシミュレートします。

この小さなプログラムを実行するのに問題はありませんが、Web サイトにユーザー オブジェクトが 1 つだけ存在することはできず、何千ものユーザー オブジェクトが存在する可能性があり、各ユーザーに新しいオブジェクトを与えることは不可能です。実際、これを関数にカプセル化し、ユーザーの数だけ呼び出すことができます。このような関数はコンストラクターと呼ばれます。

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('我的名字叫:' + this.name);
    };
    obj.showQQ = function (){
        alert('我的QQ是:' + this.qq);
    };
    //3.出厂 - 返回
    return obj;
}

var obj1 = userInfo('小白', '89898989');
obj1.showName();
obj1.showQQ();

var obj2 = userInfo('小明', '12345678');
obj2.showName();
obj2.showQQ();
</script>
ログイン後にコピー

 

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

 

5、new 和 this

  (1)new

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


<script>var user = function (){  this.name = '小明';
};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('小明');
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('小明');
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 : '小明',
    qq : 12345678,
    info : function (){
        return this.name + 'QQ是:' + this.qq;
    }
}
alert(user.info());
</script>
ログイン後にコピー

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


<script>
var user = {
    name : '小明',
    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('小明', 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 就是用于指向函数执行时的当前对象,下面再看一个实例:


<body>
<div id="div1"></div>
<script>
var oDiv = document.getElementById('div1');
//给一个对象添加事件,本质上是给这个对象添加方法。
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>
</body>
ログイン後にコピー

   上面的代码,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('我的名字叫:'+this.name);
    };
    obj.showQQ = function (){
        alert('我的QQ是:'+this.qq);
    };
    //3.出厂 - 返回
    return obj;
}

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

//调用的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('我的名字叫:'+this.name);
    };
    obj.showQQ = function (){
        alert('我的QQ是:'+this.qq);
    };
    */
    this.name = name;
    this.qq = qq;

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

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

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

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


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

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('我的名字叫:' + this.name);
};
UserInfo.prototype.showQQ = function (){
    alert('我的QQ是:' + this.qq);
};

var obj1 = new UserInfo('小白', '89898989');
var obj2 = new UserInfo('小明', '1234567');

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、面向对象实例

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

  实例:面向过程的选项卡

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <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('div1');
    var oBtn = oDiv.getElementsByTagName('input');
    var aDiv = oDiv.getElementsByTagName('div');
    
    //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 = '';
                aDiv[i].style.display = 'none';
            }
            //6、最后给当前按钮class属性,再设置当前展示内容的样式为显示
            this.className = 'active';
            aDiv[this.index].style.display = 'block';
       };
    }
};
</script>
</head>
<body>
<div id="div1">
    <input class="active" type="button" value="新闻">
    <input type="button" value="热点">
    <input type="button" value="推荐">
    <div style="display:block;">天气预报</div>
    <div>历史实事</div>
    <div>人文地理</div>
</div>
</body>
</html>
ログイン後にコピー

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

  实例:面向对象的选项卡

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <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('div1');
};

function TabShow(id){
    var _this = this;
    var oDiv = document.getElementById(id);
    this.oBtn = oDiv.getElementsByTagName('input');
    this.aDiv = oDiv.getElementsByTagName('div');
    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 = '';
        this.aDiv[i].style.display = 'none';
    }
    oBtn.className = 'active';
    this.aDiv[oBtn.index].style.display = 'block';
};
</script>
</head>
<body>
<div id="div1">
    <input class="active" type="button" value="新闻">
    <input type="button" value="热点">
    <input type="button" value="推荐">
    <div style="display:block;">天气预报</div>
    <div>历史实事</div>
    <div>人文地理</div>
</div>
</body>
</html>
ログイン後にコピー

   将面向过程的程序,改写成面向对象的形式,原则就是不能有函数套函数,但可以有全局变量,其过程是先将 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('abc');
 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('小明'); 
 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('小明'); 
 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('小明'); 
 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('小明'); 
 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('a');
 15 };
 16 json.b.getUser = function (){
 17     alert('b');
 18 };
 19 json.c.getUser = function (){
 20     alert('c');
 21 };
 22 json.a.getUser();    //返回:a
 23 json.b.getUser();    //返回:b
 24 json.c.getUser();    //返回:c
 25 </script>
ログイン後にコピー

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

 

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!