blog

JavaScriptにおける継承

# 継承 > オブジェクト指向言語の3大機能の1つ、**継承**。 > 継承はJSにおいて非常に重要な概念であり、プログラマのレベルを見る上で最も重要なベンチマークの一つです。...

Nov 29, 2020 · 10 min. read
シェア

継承

オブジェクト指向言語の3大特徴の1つである「継承」。継承はJSにおいて非常に重要な概念であり、プログラマーのレベルを見る上で最も重要な指標の一つです。

I. プロトタイプ・オブジェクト

すべてのコンストラクタはprototypeと呼ばれるプロパティを持っています。新しいプロパティやメソッドは prototype を使って追加することができ、追加されたプロパティはコンストラクタのすべてのオブジェクトに共通です。

function Student(name,age){ this.name = name; this.age = age; } Student.prototype.study = function(){ console.log(`${this.name},勉強になる!`); }; let stu1 = new Student('tom',20); let stu2 = new Student('jack',22); let stu3 = new Student('chris',25); stu1.study();// tom,勉強中 stu2.study();// jack,勉強中 stu3.study();// chris,勉強中 stu1.study === stu2.study === stu3.study;// true

上記のコードを通して、このコンストラクタによって生成されたオブジェクトはデフォルトでこのプロパティにリンクされることがわかります。また、このメソッドを追加することで、prototypeはオブジェクトのプロパティであり、そのプロパティ値はprototypeオブジェクトと呼ばれるオブジェクトであることがわかります。

プロトタイプの役割

  • オブジェクト間のデータ共有
  • クラスに新しいプロパティとメソッドを追加し、新しい内容は現在のページで既に作成されているオブジェクトに対しても有効です。

システム・クラスへのプロパティとメソッドの追加

プロトタイプを使用すると、システム・クラスにプロパティやメソッドを追加して拡張することができます。

// Array配列クラスにプロパティを追加し、型を出力する Array.prototype.type = 'Array'; // 自身の長さを出力するメソッドをArrayクラスに追加する。 Array.prototype.size = function(){ return this.length; } let arr = [1,2,3,4,5]; arr.type;// "Array" arr.size();// 5

もちろん、上記のコードは単にプロトタイプ関数を示すためのものであり、以下の形式は、現在の配列内のすべての数値の最大値を取得するために使用される**max()**メソッドを追加するためのArray配列クラスのためのものです。

Array.prototype.max = function(){ if(!this.length) return NaN; let max = this[0]; for( let i=1;i<this.length;i++ ){ if(typeof this[i] !== 'number') return NaN; if(max<this[i]){ max = this[i]; } } return max; }

オブジェクトのプロトタイプは、実際には隠された属性である、つまり、オブジェクトが自分のプロトタイプのビューを見つけたい非常に不便ですが、この問題を解決するために、各オブジェクトのブラウザは、プロパティを提供します:プロト

function Animal(){} let cat = new Animal(); cat.__proto__;// オブジェクトのプロトタイプに直接アクセスする

protoは、オブジェクトのプロトタイプ・オブジェクトをオブジェクトの視点から論じたものです。prototypeは 、コンストラクタから見たプロトタイプ属性、つまりコンストラクタによって生成されたオブジェクトのプロトタイプ・オブジェクトを表します。注:実際には、コンストラクタのプロトタイプとオブジェクトのプロトタイプは同じものです。

cat.__proto__ === Animal.prototype;// true

注:protoプロパティはブラウザが提供するものであり、標準のプロパティではないため、Objectクラスのstaticメソッド:Object.getPrototypeOf();を使用するのが開発上の正しい方法です

Object.getPrototypeOf(cat) === Animal.prototype;// true

静的メソッドとインスタンス・メソッド

すべてのクラスで両方のメソッドを持つことができます:

  • 静的メソッド:クラスに直接バインドされ、クラスから直接呼び出されるメソッド。
  • Instance Methods(インスタンスメソッド):クラスのプロトタイプにバインドされたメソッドで、そのクラス配下のオブジェクトから呼び出されます。

静的メソッド

のような静的メソッドをすでにいくつか学びました:

// パラメータが配列かどうかを判定する Array.isArray(); // オブジェクトのすべてのキーを取得する Object.keys(); // オブジェクトのプロトタイプを取得する Object.getPrototypeOf(); ...

お気づきかもしれませんが、いわゆるスタティック・メソッドは、実際にはオブジェクトではなく「クラス」から呼び出されます。あなた自身のクラスと静的メソッドを定義してください。

// function Str(){ } // strクラスの静的メソッド Str.getSize = function(args){ return args.length; } Str.getSize('hello');// 5

インスタンスメソッド

インスタンスメソッドは、開発で最も使用されます。

// 文字列中の指定された文字の出現回数を取得する String.prototype.getCharCount(char){ let reg = new RegExp(char,'g'); let count = 0; while(Array.isArray(reg.exec(this))==true){ count++; } return count; } 'hello'.getCharCount('l');// 2

第三に、コンストラクタ constructor

すべてのオブジェクトには、コンストラクタを記述するコンストラクタ属性があります。

function Student(){} let stu = new Student(); stu.prototype.constructor; // function Student(){}

コンストラクタ属性はオブジェクトのプロトタイプ・オブジェクトによって提供されるため、オブジェクトはこの属性に直接アクセスできます。これにより、関数の "name" 属性から現在のオブジェクトの "class" 名を取得することができます。

stu.constructor.name;// Student

第四に、プロトタイプ・チェイン

JSのすべてのオブジェクトは、それ自身のプロトタイプ・オブジェクトを持ち、プロトタイプ・オブジェクトは、それ自身のプロトタイプ・オブジェクトを持ちます。このように1つレベルを上げると、すべてのオブジェクトのプロトタイプは、Objectコンストラクタのプロトタイプ・プロパティであるObject.prototypeまで遡ることができます。つまり、すべてのオブジェクトはObject.prototypeのプロパティを継承しています。これは、すべてのオブジェクトがvalueOfメソッドとtoStringメソッドを持っている理由も説明します。

このロジックに従うと、Object.prototypeオブジェクトのプロトタイプまで進みますが、そのプロトタイプはnullで終わり、nullにはプロパティがないので、プロトタイプの連鎖はそこで終わります。

function Student(){} let stu = new Student(); console.log(Student.prototype); console.log(Object.getPrototypeOf(Student.prototype)); console.log(Object.getPrototypeOf(Student.prototype).constructor);// Object console.log(Object.getPrototypeOf(Object.getPrototypeOf(Student.prototype)));// null

プロトタイプ・チェインの継承を使用すると、子クラスが親クラスの能力を獲得するような操作を行うことができます。

V. オブジェクトのパッケージ化

ラップされたオブジェクトについて説明する前に、まず疑問について考えてみましょう。

// 配列オブジェクトを作成する let arr = []; // 配列オブジェクトは arr.push('hello'); arr.push('JS'); arr;// ['hello','JS']

しかし、基本データ型の値はオブジェクトではありませんが、例えばメソッドを呼び出すことができるということは興味深いことです:

let str = 'hello JS'; str.charAt(4);// 'o' let num = 10; num.toString();// '10'

そこで登場するのが、オブジェクトのラッピングです。

いわゆる「ラップ・オブジェクト」とは、Number、Boolean、Stringの3つのネイティブ・オブジェクトに対応する3種類の値のことです。これらの3つのネイティブオブジェクトは、元の型の値をオブジェクトに変換することができます。

直接文字列呼び出しメソッドを使用して実際には、JSの内部でいくつかのことを行うには、静かに文字列オブジェクトに文字列を変換します。文字列メソッドの呼び出しの終了時に、JSは静かに元の値型Stringに文字列オブジェクトを変換します。 具体的な手順は次のとおりです:

'hello world'.indexOf('o'); // JSは内部的に以下の処理を行う。 // 1,文字列値を文字列オブジェクトに変換する let str = new String('hello world'); str;// String{"hello world"} // 2,オブジェクトは str.indexOf('o'); //3,文字列オブジェクトを基本データ型の値に変換する。 let str = new String('hello world').valueOf(); str;// 'hello world'

上記のコードの後、JSは非常に勤勉であることを感じませんか?はい、JS言語はしばしば骨の折れる仕事のいくつかを完了するのに役立ちますが、このメカニズムは諸刃の剣であり、心に留めておく必要があります。このメカニズムを理解することは努力を節約しますが、そうでなければ、それは "ピット "になります。

VI. 一般的な後継者のタイプ

  • プロトタイプの継承
  • コンストラクタによる継承
  • コピー継承
  • hasOwnProperty():プロパティの検出

プロトタイプ継承

プロトタイプ継承とは、親クラスのインスタンスを子クラスのプロトタイプとして使用することです。

// function Person(name){ this.name = name||'tt'; this.sayHello = function(){ alert('hello'); } } Person.prototype.sayGoodbye = function(){ alert('goodbye'); } // function Student(){} // を指すようにサブクラスのプロトタイプを変更する。 Student.prototype = new Person(); let stu = new Student(); stu.name;// tt Student.prototype.name = 'jack'; stu.name;// 'jack' stu.sayHello(); stu.sayGoodbye();

コンストラクタ継承

コンストラクタ型継承では、主に **call() と apply()** メソッドを使用します。コンストラクタ型継承を理解するためには、まずこの2つのメソッドがどのようなものかを理解する必要があります。

JavaScriptのすべてのFunctionオブジェクトには、apply()メソッドとcall()メソッドがあります。

  • apply():オブジェクトのメソッドを呼び出し、現在のオブジェクトを別のオブジェクトに置き換えます。例:B.apply(A, arguments); つまり、AオブジェクトはBオブジェクトのメソッドを適用します。
  • call():メソッドのオブジェクトを呼び出し、現在のオブジェクトを別のオブジェクトで置き換えます。つまり、AオブジェクトはBオブジェクトのメソッドを呼び出します。
/*apply() */ function.apply(thisObj[, argArray]) /*call() */ function.call(thisObj[, arg1[, arg2[, [,...argN]]]]);

もちろん、上記の基本的な説明とコードだけでは、まだ何をするのか理解できないでしょう。ここではその基本的な使い方を見てみましょう。

// function getSum(num1,num2){ return num1+num2; } // function getDiff(num1,num2){ return num1-num2; } // getDiffgetSumを呼び出す。 getSum.apply(getDiff,[10,5]);// 15 // getSumgetDiffを呼び出す getDiff.apply(getSum,[10,5]);// 5 // getDiffgetSumを呼び出す。 getSum.call(getDiff,10,5);// 15 // getSumgetDiffを呼び出す getDiff.call(getSum,10,5);// 5

上記のコードから見つけることができる、これらの2つの関数の特性は、呼び出し元の呼び出し()または適用()メソッドの実際の実装の外側の内部です。図これらの2つの関数を使用する方法をコンストラクタの継承の必要性の後に達成することができます。

// function Person(name){ this.name = name||'tt'; this.sayHello = function(){ alert('hello'); } } Person.prototype.sayGoodbye = function(){ alert('goodbye'); } // // 親クラスのコンストラクタでサブクラスを拡張することは、親クラスのインスタンス・プロパティをサブクラスに代入することと同じである。 function Student(name){ // call()メソッドを使用して、thisがプロパティを指すように変更する。 Person.call(this); this.name = name; } let stu = new Student('jack'); stu.name;// 'jack' stu.sayHello(); stu.sayGoodbye();

applyメソッドについては、さらにいくつかのトリックがあります。

// mathsオブジェクトのmaxメソッドを使用して、配列内の最大数を素早く取得する let arr = [,8,,21,14]; // 最初のパラメーターnullは、どのオブジェクトの thisも変更する必要がないことを意味する。 console.log(Math.max.apply(null,arr));

call()メソッドとapply()メソッドの違い

どちらのメソッドも、現在のオブジェクトを別のオブジェクトに置き換えます。この2つのメソッドの違いは、主にパラメータにあります。

  • call(): 引数は無制限
    • パラメータ1:新しい thisオブジェクト。
    • ,3,4...その他のパラメータ
  • apply():引数は 2 つだけです。
    • パラメータ1:新しい thisオブジェクト。
    • 引数2: 配列または配列に似たオブジェクト

コピー継承

コピー継承もよく使われますが、コピーには区別があります:

  • 浅いコピー:直接割り当てられたコピー。
  • ディープ・コピー:オブジェクトAのすべての属性をオブジェクトBにコピーします。

シャローコピー

let obj1 = { name:'tom', age:20 }; // 浅いコピー、直接代入コピー let obj2 = obj1; obj2.height = '180cm'; obj1;// {name:'tom',age:20,height:'180cm'} obj2;// {name:'tom',age:20,height:'180cm'}

その通り、いわゆるシャロー・コピーは、あなたが学んだ配列の直接代入と同じことで、実際にはobj1のアドレス値をobj2に渡しているのです。

ディープコピー

let obj1 = { name:'tom', age:20 }; // Deep copy,オブジェクトAのすべてのプロパティをオブジェクトBにコピーする。 let obj2 = {}; for( let key in obj1 ){ obj2[key] = obj1[key] } obj2.height = '180cm' console.log(obj1);// {name: "tom", age: 20} console.log(obj2);// {name: "tom", age: 20, height: "180cm"}

ディープコピーの利点は、obj2に対する操作がobj1オブジェクトに影響しないことですが、上のコードは実はバグだらけです。

let obj1 = { name:'tom', age:20, hobby:['game','eat'] }; // Deep copy,オブジェクトAのすべてのプロパティをオブジェクトBにコピーする。 let obj2 = {}; for( let key in obj1 ){ obj2[key] = obj1[key] } obj2.hobby.push('basketball'); console.log(obj1.hobby);// ['game','eat','basketball'] console.log(obj1.hobby);// ['game','eat','basketball']

オブジェクトobj1の属性の値が基本データ型でなくなった場合、単純なコピーではやはり他人のアドレスの値を直接運ぶことになるので、直接単純にコピーすることはできません。そのため、もう少し厳密な処理が必要となり、再帰を使用することができます。

let obj1 = { name:'tom', age:20, hobby:['game','eat'] }; // 再帰関数を定義する function deepClone(obj){ let objClone = Array.isArray(obj)?[]:{}; if( obj && typeof obj==='object' ){ for( let key in obj ){ // 参照データ型の場合は再帰を使う。 if(obj[key] && typeof obj[key]==='object'){ objClone[key] = deepClone(obj[key]); }else{ // 基本データ型なら直接コピーする objClone[key] = obj[key] } } } return objClone; } let obj2 = deepClone(obj1); obj2.hobby.push('haha'); console.log(obj1); console.log(obj2);

hasOwnProperty(): プロパティを検出します。

Read next

Javaのルックオーバーとは何かわからない - JAVAの基礎 (1)

1. Java言語とは何ですか? Javaはオブジェクト指向プログラミング言語の一つで、登場以来空前の注目を集め、コンピュータ携帯電話、家電製品などの分野で最も人気のある開発言語の一つとなっています。 2.Java言語の特徴とは? 3.オブジェクト指向とプロセス指向の違い プロセス指向とは、ある問題を解決するために必要な手順を分析し、それを...

Nov 28, 2020 · 4 min read