コンストラクター関数の紹介
関数をコンストラクターとして使用してオブジェクトを作成できます。コンストラクター関数の名前が Person の場合、そのコンストラクターで作成されたオブジェクトは Person のインスタンスです。
var Person = function(name){
this.name = name;
};
Person.prototype.walk=function(){
this.step().step().step();
};
var bob = new Person("Bob");
Person はコンストラクター関数です。Person を使用してインスタンスを作成する場合は、 new キーワードを使用する必要があります。
var bob = new Person("Bob");console.log(bob.name);//=Bob
var ben = new Person("Ben");console.log(ben.name);//=Ben
プロパティ/メンバーname
はインスタンス固有であり、ボブとベンでは異なります
メンバーwalk
は Person.prototype の一部であり、すべてのインスタンスで共有されます。 bob と ben は Person のインスタンスであるため、walk メンバー (bob.walk===ben.walk) を共有します。
bob.walk();ben.walk();
walk() は bob で直接見つけることができなかったため、これは bob のコンストラクタであるため、JavaScript は Person.prototype でそれを探します。そこに見つからない場合は、Object.prototype を探します。これをプロトタイプチェーンと呼びます。継承のプロトタイプ部分は、このチェーンを長くすることによって行われます。たとえば、bob => Employee.prototype => Person.prototype => Object.prototype (継承については後で詳しく説明します)。
bob、ben、およびその他のすべての作成された Person インスタンスは walk を共有しますが、関数はインスタンスごとに異なる動作をしますthis
。の値はthis
呼び出し元のオブジェクトになります。今のところ、それが現在のインスタンスであるとしましょう。したがって、bob.walk()
「これ」は bob になります。("this" と呼び出しオブジェクトについては後で詳しく説明します)。
ベンが赤信号を待っていて、ボブが青信号だった場合。次に、ベンとボブの両方で walk() を呼び出すと、明らかにベンとボブに異なることが起こります。
ben.walk=22
bob と benが ben.walk への 22walk
の割り当てを共有していても、 bob.walkには影響しません。これは、そのステートメントがwalk
ben で直接呼び出されるメンバーを作成し、値 22 を割り当てるためです。2 つの異なる walk メンバー: ben.walk と Person.prototype.walk があります。
walk
bob.walk を要求すると、bob で見つからないため、Person.prototype.walk 関数が取得されます。ただし、メンバー walk は ben で作成されており、JavaScript が ben で walk を検出したため、Person.prototype を検索しないため、ben.walk を要求すると値 22 が返されます。
2 つの引数を指定して Object.create を使用する場合、Object.defineProperty または Object.defineProperties シャドウイングの動作は少し異なります。詳細については、こちらをご覧ください。
プロトタイプの詳細
オブジェクトは、プロトタイプを使用して別のオブジェクトから継承できます。を使用して、任意のオブジェクトのプロトタイプを他の任意のオブジェクトに設定できますObject.create
。コンストラクター関数の紹介で、メンバーがオブジェクトで見つからない場合、JavaScript はプロトタイプチェーンでそれを探すことを見てきました。
前のパートで、インスタンスのプロトタイプ (ben.walk) からのメンバーの再割り当てがそのメンバーをシャドウすることを見てきました (Person.prototype.walk を変更するのではなく、ben で walk を作成します)。
メンバーを再割り当てせずに変更するとどうなるでしょうか? 変更とは、(たとえば) オブジェクトのサブ プロパティを変更したり、オブジェクトの値を変更する関数を呼び出したりすることです。例えば:
var o = [];
var a = o;
a.push(11);//mutate a, this will change o
a[1]=22;//mutate a, this will change o
次のコードは、メンバーを変更することによって、プロトタイプ メンバーとインスタンス メンバーの違いを示しています。
var person = {
name:"default",//immutable so can be used as default
sayName:function(){
console.log("Hello, I am "+this.name);
},
food:[]//not immutable, should be instance specific
// not suitable as prototype member
};
var ben = Object.create(person);
ben.name = "Ben";
var bob = Object.create(person);
console.log(bob.name);//=default, setting ben.name shadowed the member
// so bob.name is actually person.name
ben.food.push("Hamburger");
console.log(bob.food);//=["Hamburger"], mutating a shared member on the
// prototype affects all instances as it changes person.food
console.log(person.food);//=["Hamburger"]
上記のコードは、ben と bob が person のメンバーを共有していることを示しています。人物は 1 人だけで、ボブとベンのプロトタイプとして設定されています (人物は、インスタンスに存在しない要求されたメンバーを検索するために、プロトタイプ チェーンの最初のオブジェクトとして使用されます)。上記のコードの問題は、bob と ben が独自のfood
メンバーを持つ必要があることです。ここでコンストラクター関数の出番です。インスタンス固有のメンバーを作成するために使用されます。引数を渡して、これらのインスタンス固有のメンバーの値を設定することもできます。
次のコードは、コンストラクター関数を実装する別の方法を示しています。構文は異なりますが、考え方は同じです。
- 多くのインスタンスで同じメンバーを持つオブジェクトを定義します (人物はボブとベンの設計図であり、ジリー、マリー、クレアなどの場合があります)。
- インスタンス (bob および ben) に対して一意である必要があるインスタンス固有のメンバーを定義します。
- ステップ 2 のコードを実行するインスタンスを作成します。
コンストラクター関数を使用して、次のコードの手順 2 でプロトタイプを設定し、手順 3 でプロトタイプを設定します。
このコードでは、プロトタイプと食べ物から名前を削除しました。これは、インスタンスを作成するときにすぐにこれをシャドウする可能性が高いためです。Name は、コンストラクター関数でデフォルト値が設定されたインスタンス固有のメンバーになりました。food メンバーもプロトタイプからインスタンス固有のメンバーに移動されるため、ben に food を追加しても bob.food には影響しません。
var person = {
sayName:function(){
console.log("Hello, I am "+this.name);
},
//need to run the constructor function when creating
// an instance to make sure the instance has
// instance specific members
constructor:function(name){
this.name = name || "default";
this.food = [];
return this;
}
};
var ben = Object.create(person).constructor("Ben");
var bob = Object.create(person).constructor("Bob");
console.log(bob.name);//="Bob"
ben.food.push("Hamburger");
console.log(bob.food);//=[]
オブジェクトの作成と定義に役立つ、より堅牢な同様のパターンに遭遇する場合があります。
継承
次のコードは、継承する方法を示しています。タスクは基本的に以前のコードと同じですが、少し追加されています
- オブジェクトのインスタンス固有のメンバーを定義します (関数 Hamster および RussionMini)。
- 継承のプロトタイプ部分を設定 (RussionMini.prototype = Object.create(Hamster.prototype))
- インスタンス間で共有できるメンバーを定義します。 (Hamster.prototype と RussionMini.prototype)
- 手順 1 のコードを実行するインスタンスを作成し、継承するオブジェクトに対して、親コードも実行するようにします (Hamster.apply(this,arguments);)。
パターンを使用すると、「古典的な継承」と呼ばれるものがあります。構文に混乱している場合は、喜んで詳細を説明したり、別のパターンを提供したりします。
function Hamster(){
this.food=[];
}
function RussionMini(){
//Hamster.apply(this,arguments) executes every line of code
//in the Hamster body where the value of "this" is
//the to be created RussionMini (once for mini and once for betty)
Hamster.apply(this,arguments);
}
//setting RussionMini's prototype
RussionMini.prototype=Object.create(Hamster.prototype);
//setting the built in member called constructor to point
// to the right function (previous line has it point to Hamster)
RussionMini.prototype.constructor=RussionMini;
mini=new RussionMini();
//this.food (instance specic to mini)
// comes from running the Hamster code
// with Hamster.apply(this,arguments);
mini.food.push("mini's food");
//adding behavior specific to Hamster that will still be
// inherited by RussionMini because RussionMini.prototype's prototype
// is Hamster.prototype
Hamster.prototype.runWheel=function(){console.log("I'm running")};
mini.runWheel();//=I'm running
継承のプロトタイプ部分を設定する Object.create
これはObject.createに関するドキュメントです。基本的には、最初の引数を返されたオブジェクトのプロトタイプとして、2 番目の引数 (ポリフィルではサポートされていません) を返します。
2 番目の引数が指定されていない場合、返されるオブジェクトのプロトタイプ (返されるオブジェクトのプロトタイプ チェーンで使用される最初のオブジェクト) として使用される最初の引数を持つ空のオブジェクトが返されます。
RussionMini のプロトタイプを Hamster のインスタンスに設定する人もいます (RussionMini.prototype = new Hamster())。これは、同じことを達成しても (RussionMini.prototype のプロトタイプは Hamster.prototype です)、Hamster インスタンス メンバーを RussionMini.prototype のメンバーとして設定するため、望ましくありません。したがって、RussionMini.prototype.food は存在しますが、共有メンバーです (「プロトタイプについての詳細」のボブとベンを覚えていますか?)。RussionMini の作成時に food メンバーはシャドーイングされます。Hamster コードはそれを使用Hamster.apply(this,arguments);
して実行されるためthis.food = []
です。ただし、Hamster メンバーは引き続き RussionMini.prototype のメンバーのままです。
もう 1 つの理由として、Hamster を作成するには、まだ使用できない可能性のある渡された引数に対して多くの複雑な計算を実行する必要があることが考えられます。ここでも仮引数を渡すことができますが、コードが不必要に複雑になる可能性があります。
親関数の拡張とオーバーライド
children
機能を拡張する必要がある場合もありparent
ます。
「子」(=RussionMini) に特別なことをさせたい。RussionMini が Hamster コードを呼び出して何かを実行し、その後何か追加の処理を実行できる場合、Hamster コードをコピーして RussionMini に貼り付ける必要はありません。
次の例では、ハムスターは時速 3 km で走ることができますが、Russion mini はその半分の速さしか走れないと仮定しています。RussionMini で 3/2 をハードコーディングすることはできますが、この値を変更する場合、変更が必要なコード内の複数の場所があります。親 (Hamster) の速度を取得するために Hamster.prototype を使用する方法を次に示します。
var Hamster = function(name){
if(name===undefined){
throw new Error("Name cannot be undefined");
}
this.name=name;
}
Hamster.prototype.getSpeed=function(){
return 3;
}
Hamster.prototype.run=function(){
//Russionmini does not need to implement this function as
//it will do exactly the same as it does for Hamster
//But Russionmini does need to implement getSpeed as it
//won't return the same as Hamster (see later in the code)
return "I am running at " +
this.getSpeed() + "km an hour.";
}
var RussionMini=function(name){
Hamster.apply(this,arguments);
}
//call this before setting RussionMini prototypes
RussionMini.prototype = Object.create(Hamster.prototype);
RussionMini.prototype.constructor=RussionMini;
RussionMini.prototype.getSpeed=function(){
return Hamster.prototype
.getSpeed.call(this)/2;
}
var betty=new RussionMini("Betty");
console.log(betty.run());//=I am running at 1.5km an hour.
欠点は、Hamster.prototype をハードコーディングすることです。super
Javaのように有利になるパターンがあるかもしれません。
私が見たほとんどのパターンは、継承レベルが 2 レベル (Child => Parent => GrandParent) を超えると機能しなくなるか、スーパー スルークロージャを実装してより多くのリソースを使用します。
Parent (=Hamster) メソッドをオーバーライドするには、同じことを行いますが、Hamster.prototype.parentMethod.call(this,.... は行いません。
this.constructor
コンストラクター プロパティは JavaScript によってプロトタイプに含まれています。変更することはできますが、コンストラクター関数を指す必要があります。だからHamster.prototype.constructor
ハムスターを指す必要があります。
継承のプロトタイプ部分を設定した後、正しい関数を再度指すようにする必要があります。
var Hamster = function(){};
var RussionMinni=function(){
// re use Parent constructor (I know there is none there)
Hamster.apply(this,arguments);
};
RussionMinni.prototype=Object.create(Hamster.prototype);
console.log(RussionMinni.prototype.constructor===Hamster);//=true
RussionMinni.prototype.haveBaby=function(){
return new this.constructor();
};
var betty=new RussionMinni();
var littleBetty=betty.haveBaby();
console.log(littleBetty instanceof RussionMinni);//false
console.log(littleBetty instanceof Hamster);//true
//fix the constructor
RussionMinni.prototype.constructor=RussionMinni;
//now make a baby again
var littleBetty=betty.haveBaby();
console.log(littleBetty instanceof RussionMinni);//true
console.log(littleBetty instanceof Hamster);//true
ミックスインによる「多重継承」
Cat が移動できる場合、Cat は Movable から継承すべきではないため、継承しないほうがよいものもあります。猫は可動ではなく、動くことができます。クラスベースの言語では、Cat は Movable を実装する必要があります。JavaScript では、Movable を定義し、ここで実装を定義できます。Cat は、それをオーバーライド、拡張するか、デフォルトの実装にすることができます。
Movable の場合、インスタンス固有のメンバー ( などlocation
) があります。また、インスタンス固有ではないメンバーもあります (関数 move() など)。インスタンス固有のメンバーは、インスタンスの作成時に mxIns (mixin ヘルパー関数によって追加) を呼び出すことによって設定されます。mixin ヘルパー関数を使用して、Movable.prototype から Cat.prototype にプロトタイプ メンバーを 1 つずつコピーします。
var Mixin = function Mixin(args){
if(this.mixIns){
i=-1;len=this.mixIns.length;
while(++i<len){
this.mixIns[i].call(this,args);
}
}
};
Mixin.mix = function(constructor, mix){
var thing
,cProto=constructor.prototype
,mProto=mix.prototype;
//no extending, if multiple prototypes
// have members with the same name then use
// the last
for(thing in mProto){
if(Object.hasOwnProperty.call(mProto, thing)){
cProto[thing]=mProto[thing];
}
}
//instance intialisers
cProto.mixIns = cProto.mixIns || [];
cProto.mixIns.push(mix);
};
var Movable = function(args){
args=args || {};
//demo how to set defaults with truthy
// not checking validaty
this.location=args.location;
this.isStuck = (args.isStuck===true);//defaults to false
this.canMove = (args.canMove!==false);//defaults to true
//speed defaults to 4
this.speed = (args.speed===0)?0:(args.speed || 4);
};
Movable.prototype.move=function(){
console.log('I am moving, default implementation.');
};
var Animal = function(args){
args = args || {};
this.name = args.name || "thing";
};
var Cat = function(args){
var i,len;
Animal.call(args);
//if an object can have others mixed in
// then this is needed to initialise
// instance members
Mixin.call(this,args);
};
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
Mixin.mix(Cat,Movable);
var poochie = new Cat({
name:"poochie",
location: {x:0,y:22}
});
poochie.move();
上記は、同じ名前の関数を、最後にミックスインされたものに置き換える単純な実装です。
この変数
すべてのサンプル コードthis
で、現在のインスタンスを参照しています。
this 変数は実際には呼び出し元のオブジェクトを参照し、関数の前にあるオブジェクトを参照します。
明確にするために、次のコードを参照してください。
theInvokingObject.thefunction();
これが間違ったオブジェクトを参照するインスタンスは、通常、イベント リスナー、コールバック、またはタイムアウトと間隔をアタッチする場合です。次の 2 行のコードではpass
、関数を呼び出していません。関数の受け渡しは:someObject.aFunction
であり、呼び出しは:someObject.aFunction()
です。値は、関数が宣言されたオブジェクトを参照するのthis
ではなく、関数が宣言されたオブジェクトを参照しますinvokes
。
setTimeout(someObject.aFuncton,100);//this in aFunction is window
somebutton.onclick = someObject.aFunction;//this in aFunction is somebutton
this
上記のケースで someObject を参照するには、関数の代わりにクロージャーを直接渡すことができます。
setTimeout(function(){someObject.aFuncton();},100);
somebutton.onclick = function(){someObject.aFunction();};
クロージャースコープに含まれる変数を細かく制御できるように、プロトタイプでクロージャーの関数を返す関数を定義するのが好きです。
var Hamster = function(name){
var largeVariable = new Array(100000).join("Hello World");
// if I do
// setInterval(function(){this.checkSleep();},100);
// then largeVariable will be in the closure scope as well
this.name=name
setInterval(this.closures.checkSleep(this),1000);
};
Hamster.prototype.closures={
checkSleep:function(hamsterInstance){
return function(){
console.log(typeof largeVariable);//undefined
console.log(hamsterInstance);//instance of Hamster named Betty
hamsterInstance.checkSleep();
};
}
};
Hamster.prototype.checkSleep=function(){
//do stuff assuming this is the Hamster instance
};
var betty = new Hamster("Betty");
(コンストラクター) 引数の受け渡し
Child が Parent ( ) を呼び出すときHamster.apply(this,arguments);
、Hamster が RussionMini と同じ引数を同じ順序で使用すると仮定します。他の関数を呼び出す関数の場合、通常は別の方法で引数を渡します。
私は通常、1 つのオブジェクトを関数に渡し、その関数に必要なものを変更させます (デフォルトを設定します)。その後、その関数は、同じことを行う別の関数にそれを渡します。次に例を示します。
//helper funciton to throw error
function thowError(message){
throw new Error(message)
};
var Hamster = function(args){
//make sure args is something so you get the errors
// that make sense to you instead of "args is undefined"
args = args || {};
//default value for type:
this.type = args.type || "default type";
//name is not optional, very simple truthy check f
this.name = args.name || thowError("args.name is not optional");
};
var RussionMini = function(args){
//make sure args is something so you get the errors
// that make sense to you instead of "args is undefined"
args = args || {};
args.type = "Russion Mini";
Hamster.call(this,args);
};
var ben = new RussionMini({name:"Ben"});
console.log(ben);// Object { type="Russion Mini", name="Ben"}
var betty = new RussionMini();//Error: args.name is not optional
関数チェーンで引数を渡すこの方法は、多くの場合に役立ちます。何かの合計を計算するコードで作業していて、後でその何かの合計を特定の通貨に再因数分解したい場合、通貨の値を渡すために多くの関数を変更する必要がある場合があります。通貨値の範囲を拡大することもできますが (グローバルのようにwindow.currency='USD'
)、それを解決するには悪い方法です。
オブジェクトを渡すargs
と、関数チェーンで利用できるときはいつでも通貨を追加し、他の関数を変更せずに必要なときにいつでも変更/使用できます (関数呼び出しで明示的に渡す必要があります)。
プライベート変数
JavaScript にはプライベート修飾子がありません。
私は次のことに同意します: http://blog.millermedeiros.com/a-case-against-private-variables-and-functions-in-javascript/個人的には使用していません。
_aPrivate
名前を付けるか、すべてのプライベート変数を というオブジェクト変数に入れることで、メンバがプライベートであることを他のプログラマに示すことができます_
。
クロージャーを介してプライベート メンバーを実装できますが、インスタンス固有のプライベート メンバーには、プロトタイプにない関数からのみアクセスできます。
クロージャーとしてプライベートを実装しないと、実装がリークし、コードを拡張するユーザーがパブリック API の一部ではないメンバーを使用できるようになります。これは良いことも悪いこともあります。
これは、テストのために特定のメンバーを簡単にモックできるため、優れています。これにより、他のユーザーがコードを簡単に改善 (パッチ) する機会が得られますが、コードの次のバージョンが同じ実装やプライベート メンバーを持つという保証がないため、これも良くありません。
クロージャーを使用することで、他の人に選択を与えることはなく、ドキュメントで命名規則を使用することで選択できます。これは JavaScript に固有のものではありません。他の言語では、プライベート メンバーを使用しないことを決定できます。これは、他の人が何をしているかを知っていることを信頼し、(リスクを伴いながら) 好きなように選択できるようにするためです。
それでもプライベートを主張する場合は、次のパターンが役立つ場合があります。private は実装していませんが、protected を実装しています。