blog

JSクラス、インスタンス、プロトタイプ 継承 call apply bind

オブジェクト指向の問題では、多くの物事は、そのようなクラスとして、または問題を分析するオブジェクトとしての関数は、プライベートプロパティやパブリックプロパティなどの同じ結果ではない、また、相対的です。...

Aug 25, 2020 · 8 min. read
シェア

レビュー概要

オブジェクト指向の問題では、相対的なものが多く、例えば、関数をクラスとして見るか、オブジェクトとして見るかで問題の分析が異なります。

というのも、関数はインスタンスを構築するためのクラスであることもあれば、Functionクラスによって構築されたインスタンスであるオブジェクトであることもあるからです。

また、独自の普通の関数をクラスとして定義することで、構築されたインスタンスは単なるオブジェクトとなり、それ以外の役割はありません。

だから、問題の分析では、まず明確に考えて、それは関数またはオブジェクトです。プロトタイプ属性の関数であるか、____プロト____属性のオブジェクトであるか、あなたはプロトタイプチェーンメカニズムに行くことができます、プロトタイプチェーンメカニズムは、クラスのプロトタイプを見つけるために所属し、1つ上のレベルを見つけるために、オブジェクトクラスのプロトタイプまで。

練習問題

  • 1
 function Fn() {
 let a = 1;
 this.a = a;
 }
 Fn.prototype.say = function () {
 this.a = 2;
 }
 Fn.prototype = new Fn;
 let f1 = new Fn;
 
 Fn.prototype.b = function () {
 this.a = 3;
 };
 console.log(f1.a); // 1
 console.log(f1.prototype); // undefined 
 console.log(f1.b); 
 //=> function () {
 // this.a = 3;
 // };
 console.log(f1.hasOwnProperty('b'));// false
 console.log('b' in f1); // true
 console.log(f1.constructor == Fn); // true
  • 2
 function fun(){
 this.a=0;
 this.b=function(){
 alert(this.a);
 }
 }
 fun.prototype={
 b:function(){
 this.a=20;
 alert(this.a);
 },
 c:function(){
 this.a=30;
 alert(this.a)
 }
 }
 var my_fun=new fun();
 my_fun.b();// "0"
 my_fun.c();// "30"
  • 3
 function C1(name) {
 if (name) {
 this.name = name;
 }
 }
 function C2(name) {
 this.name = name;
 }
 function C3(name) {
 this.name = name || 'join';
 }
 C1.prototype.name = 'Tom';
 C2.prototype.name = 'Tom';
 C3.prototype.name = 'Tom';
 alert((new C1().name) + (new C2().name) + (new C3().name));
// "Tomundefinedjoin"
  • 4
function Fn(num) { this.x = this.y = num; } Fn.prototype = { x: 20, sum: function () { console.log(this.x + this.y); } }; let f = new Fn(10); console.log(f.sum === Fn.prototype.sum);// true f.sum();// 20 Fn.prototype.sum(); // NaN console.log(f.constructor); //Object

ヒント

これは、現在のインスタンスがクラスのインスタンスであるかどうかを検出するために使用できます。

function Fn(){
}
let f = new Fn;
console.log(f.constructor === Fn); // true

注意: 制限があります。コンストラクタと呼ばれるプライベート属性を現在のインスタンスに追加したり、クラスのプロトタイプをリダイレクトしたりすると、結果が不正確になる可能性があります。

クラスの継承

プロトタイプの継承

function A(){ this.getX = function(){} } A.prototype.getY = function(){} function B(){} B.prototype = new A; let f = new B; f.getX() //クラスAのインスタンスからプライベート・メソッドを継承することができる。 f.getY() //クラスAのインスタンスからパブリック・メソッドを継承することができる。

回答:クラスBのプロトタイプがクラスAのインスタンスを指す場合、後のクラスBのインスタンスはクラスAのインスタンスのプライベートメソッドだけでなく、クラスAのインスタンスのパブリックメソッドも継承することができます。このような継承をプロトタイプ継承と呼びます。これは、クラス B のプロトタイプをリダイレクトして、クラス A のインスタンスの private メソッドと public メソッドをクラス B のインスタンスの public メソッドにすることと同じです。

しかし、このタイプの継承では、元のクラスのプロトタイプは放棄され、元のプロトタイプのパブリック・メソッドはなくなります。また、独自のカスタム・クラスのプロトタイプしか変更できず、組み込みクラスのプロトタイプをリダイレクトすることはできません。

中間クラスの継承

function fn(){ arguments.__proto__ = Array.prototype; arguments.push(500); console.log(arguments); } fn(100,200,300,400) //=> [100,200,300,400,500]

分析

この例では、argumentsはArrayのインスタンスではありませんが、手動でargumentsの__proto__をArrayのプロトタイプに指定することで、argumentsはArrayのプロトタイプ上のメソッドを使用することができます。

注目してください:

このメソッドは、基本的なデータ型の形式で作成されたリテラルを気軽に使用することはできません、参照データ型を指す___プロト___を変更することはできません気軽に変更することはできません、または結果が台無しになります取得します。引数のクラスの配列、および同様の形式の配列ので、このケースでは、配列クラスのプロトタイプへのポインタを変更すると、パブリックメソッドの通常の配列を呼び出すことができます。結局のところ、各クラスのプロトタイプのパブリックメソッドは、自分のクラスのフォームに準拠したメソッドであり、内部コードは自分のフォームに関連しているので、単に他のクラスのメソッドを呼び出すために、このような継承を使用しないでください。

toString.call()

は、データ型を検出するためのObjectプロトタイプのメソッドです。

console.log(Object.prototype.toString.call(1)) //=> "[object Number]" console.log(Object.prototype.toString.call("1")) //=> "[object String]" console.log(Object.prototype.toString.call(true)) //=> "[object Boolean]" console.log(Object.prototype.toString.call(null)) //=> "[object Null]" console.log(Object.prototype.toString.call(undefined)) //=> "[object Undefined]" console.log(Object.prototype.toString.call([])) //=> "[object Array]" console.log(Object.prototype.toString.call({})) //=> "[object Object]" console.log(Object.prototype.toString.call(Array)) //=> "[object Function]"

注: toString.call() はカスタムクラスのインスタンスを検出できません。カスタムクラスのインスタンスを検出した場合、返されるのは "[object Object]" だけです。

function Fn(){} console.log(Object.prototype.toString.call(Fn)) //=> "[object Function]" let f1 = new Fn; console.log(Object.prototype.toString.call(f1)) //=>"[object Object]"

関数内で thisを指す方法の変更

 /* 
 メソッドを指す関数 thisを変更する。
 */
 let obj = {};
 function fn(){
 console.log(this); // window obj
 }
 // fn();
 obj.fn = fn;
 obj.fn();
 delete obj.fn;
 
 // fn関数は、thisが現在のobjオブジェクトを指している状態で実行されるようにしたい。

これは thisを変更する効果がありますが、あまりにも面倒で、実行するたびにコードを見直さなければなりません。

関数内の thisを直接変更することはできません。

let obj = {name:1};
function fn(){
this = obj;
console.log(this);
}
fn();
//=> Uncaught ReferenceError: Invalid left-hand side in assignment

注: 等号の左側に thisを単独で置くことはできません!

call、apply、bindの3つは、関数のthisポイントを変更するための組み込みメソッドです。

call、apply、bindメソッドはFunctionクラスのプロトタイプに存在し、すべての関数から呼び出すことができます。

call

はFunctionクラスのプロトタイプのメソッドです。

呼び出しは、実行される関数の thisを呼び出しメソッドで渡されたパラメータに変更するために使用され、関数は変更直後に実行されます。

例えば、toString.call()は、実際にはcallメソッドを実行しますが、callメソッドの thisはtoSringになり、toStringの thisは渡されたパラメータになります。で、toStringの thisが渡されたパラメータになり、thisが渡された人になります。結局、toStringメソッドを実行するのと同じですが、toStringの thisが渡されたパラメータに変更され、即座に実行されます。

let obj = {name:1}; function fn(){ console.log(this) } fn.call(obj) //=> {name:1} //fnをオブジェクトとして扱うと、それが現在属しているクラスのプロトタイプの呼び出しメソッドが見つかる。

実施プロセス

1. fnは、___proto_____を介して、現在属しているクラスのプロトタイプ上の呼び出しメソッドを見つけます。

2.呼び出しの内部で、呼び出し関数内部の thisをfnに向けることで、呼び出しメソッドが実行され、つまり呼び出し内部の thisが実行され、つまりfnが実行され、呼び出しに実際のパラメータを渡します。

3.呼び出しの内部で、fnを実行させ、fnの thisポイントを最初に渡した実パラメーターに変更します。

4.callメソッドが実行されているように見えますが、実際にはfnメソッドが実行され、fnのthisが変更されています。

要約: fn.call(引数 1, 引数 2, ...)

1.ストリクト・モードでは、callがパラメータを渡さないか、未定義を渡すと、fnの thisは未定義を指し、nullを渡すと thisはnullを指します。

"use strict" let obj = {name:1}; function fn(){ console.log(this) } fn.call() //=> undefined
"use strict" let obj = {name:1}; function fn(){ console.log(this) } fn.call(null) //=> null

2.非厳密モードでは、呼び出しがパラメータを渡さなかったり、未定義を渡したり、NULLを渡すと、thisはウィンドウを指します。

let obj = {name:1}; function fn(){ console.log(this) } fn.call(null) //=> Window

3.呼び出しの第一引数はfnの this、第二引数以降はfnの通常の引数です。

apply

これは、呼び出しメソッドと同じですが、thisポイントを変更しているが、違いは、パラメータを渡す方法は同じではありませんが、2番目のパラメータは、1つずつパラメータとして各項目の配列またはクラス配列に相当する配列またはクラス配列を適用する関数の前に渡された正式なパラメータに対応しています。

let obj = {name:1}; function fn(n,m){ console.log(this,n,m) } fn.apply(obj,[1,2]) //=> これは、配列の最初の項目1を引数nに渡し、2番目の項目2を引数mに渡すことと同じである。

bind

このメソッドも thisを変更しますが、関数の thisをあらかじめ変更するように thisを前処理し、fn関数に thisを変更させません。bindメソッドの戻り値は、thisが変更された後の関数です。()を付けて実行しないと、変更された thisを持つ新しい関数が生成されてしまいます。

let obj = {name:1};
function fn(n,m){
console.log(this,n,m)
}
let res = fn.bind(obj);
res();
//=>{name:1}
console.log(res === fn);
//=>false
res();
//=>{name:1}

つまり、thisを変更した後にbindが返す関数は、複数回実行することができるということです。元の関数は変更されず、返される関数は新しいヒープメモリに格納された新しい関数です。

パラメータを渡すには2つの方法があります。

//最初の
let res = fn.bind(obj,1,2);
//番目だ:
let res = fn.bind(obj)
res(1,2)

ひとつはbindが実行されている間に関数への参照を渡す方法、もうひとつはbindが返す関数が実行されている間に関数への参照を渡す方法です。

小さなケース

let ary = [100,200]; console.log(ary.slice(0));//配列をクローンする //=> [100,200] //sliceこのメソッドにおける thisとは誰なのか? //誰がsliceを呼び出そうとも、sliceの中の thisはsliceを呼び出した人になる。
function fn(){
//argumentsの使い方.slice
console.log([].slice.call(arguments,0))
// []sliceメソッドを見つけ、callメソッドでスライスを実行させ、スライス thisで引数を指し示すことで、スライス内部の操作 thisが引数
//slice内部の thisはargumentsである。
}
fn(100,200,300,400);
//=> [100,200,300,400]

そして、この方法で返される配列は、引数のクラス配列を配列に変換することで実現できます。引数自体は変更されないままなので、配列をクローンアウトすることと同じです。

function fn(){
[].push.call(arguments,500);
console.log(arguments);
}
fn(100,200,300,400);

これはpushメソッドを呼び出す引数と同じで、引数はクラスの配列のままで、最後にアイテムが追加されます。

Read next

LeetCode 今日の質問 - 167. 2つの数の和 II - 入力順序配列 (Java)

167.2つの数の和II - 順序付き配列の入力 昇順にソートされた順序付き配列が与えられたとき、その和が目的の数に等しい2つの数を求めなさい。 この関数は添え字 index1 と index2 を返す必要があります。 最初の数は最も小さい数です。

Aug 25, 2020 · 2 min read