JavaScript対象に向けたプログラム設計――「作成対象」の注意点

17105 ワード

オブジェクトを作成
Objectコンストラクタまたはオブジェクトの字面量は、単一のオブジェクトを作成するために使用できます.しかし、この方法の欠点は非常に明白である.同一のインターフェースを作成すると、多くのオブジェクトが重複コードを生成する可能性が高い.この問題を解決するために、人々は工場モデルの変体を使い始めました.
工場モデル(捨てる、推薦しない)
このモードはオブジェクト認識の問題を解決していません.例えば:
具体的に単一のオブジェクトを作成します.
var person = {};
person.name = "Oliver";
person.age = 18;
person.sayName = function(){
    return this.Name;
};
工場モードに変更:
function createPerson(name,age){
    var obj = {};
    obj.name = name;
    obj.age = age;
    obj.sayName = function(){
        return this.name
    };
    return obj; //       obj   ,     obj     createPerson   。
}

var newPerson = createPerson("Oliver",18);
コンストラクタモード
コンストラクタは、特定の種類のオブジェクトを作成できます.したがって、カスタムのコンストラクタを作成して、カスタムオブジェクトの種類の属性と方法を定義できます.例えば:
function Person(name,age){ //     ,               
    this.name = name;
    this.age = age;
    this.sayName = function (){
        return this.name;
    };
}

var newPerson = new Person("Oliver",18);

document.write(newPerson.name); //Oliver
document.write(newPerson.age); //18
document.write(newPerson.sayName()); //Oliver
確かに便利です
ここで必ず覚えてください.コンストラクションはすべて大文字で始まり、他の関数を区別するために使います.
また、
function Person(name,age){ //     ,               
    this.name = name;
    this.age = age;
    this.sayName = function (){
        return this.name;
    };
}

var person1 = new Person("Oliver",18);
var person2 = new Person("Troy",24);

document.write(person1.constructor == Person); //true
document.write(person2.constructor == Person); //true
ここのperson 1とperson 2はそれぞれPersonの異なる例を保存している.両方のオブジェクトにはPersonを指すconstructor属性があります.
上記の例では、person 1とperson 2はObjectの例であり、Personの例でもある.instance ofオペレータによって検証できます.
console.log((person1 instanceof Object) && (person2 instanceof Person)); //true
このように構成関数を作成するのはGlobalに定義されている(windowオブジェクト)です.
コンストラクタを関数として使う
どんな関数でもnewオペレータを通して呼び出しさえすれば、席の構造関数ができます.どの関数もnewオペレータで呼び出さないと、普通の関数と変わりません.この構造関数は以下の通りです.
function Car(name,color,sound){
    this.name = name;
    this.color = color;
    this.sound = function(){
        return sound;
    };
    console.log(this.name + " " + this.color + " " + this.sound());
}
コンストラクタとして使用する場合:
var benz = new Car("C200","White","Boom Boom"); //C200 White Boom Boom
普通の関数として呼び出された場合:
Car("Benz","White","Boom!"); //Benz White Boom!
console.log(window.name + window.color + window.sound()); //BenzWhiteBoom!
他のオブジェクトのスコープで呼び出した場合:
var cars = {};
Car.call(cars,"Benz","White","Boom Boom!");
document.write(cars.sound()); //Boom Boom!
構造関数の問題
問題は、各方法が各インスタンスで再作成されることです.これらの問題は内部の関数を外部に移すことによって解決できます.例えば:
function Car(name,color){
    this.name = name;
    this.color = color;
    this.show = show;
}
function show(){
    console.log(this.name + this.color);
}
var benz = new Car("Benz","white");
benz.show(); //Benzwhite
しかし、この問題はパッケージ性が全くない.プロトタイプモードで解決できます.
プロトタイプ
function Person(){};
Person.prototype.name = "Oliver";
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
};
var person1 = new Person();
person1.sayName(); //Oliver
var person2 = new Person();
person2.sayName(); //Oliver;
console.log(person1.sayName == person2.sayName); //true
構造関数とは異なり、これらの新しいオブジェクトの属性と方法はすべてのインスタンスによって共有される.ここで二つの新しいpersonが訪問したのは同じグループの属性と同じsayName()関数です.
原型の対象を理解する
上記のPersonを例にとって、Personコンストラクタの中にプロトタイプの属性があります.この属性はプロトタイプのPerson Prottypeを指します.このPerson Prottypeの中にはconstrutor属性が含まれています.この属性はコンストラクタPersonを指します.構造関数の例は、Person Prottypeを指す「Prottype」の内部属性を含む.例えば:
function Person(){};
Person.prototype.name = "Oliver";
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
person1.sayName(); //Oliver

var person2 = new Person();
person2.sayName(); //Oliver;

console.log(Person.prototype);
/*
age: 18
constructor: function Person() {}
name: "Oliver"
sayName: function () {
__proto__: Object
*/
console.log(Person.prototype.constructor);
//function Person() {}
console.log(Object.getPrototypeOf(person1));
/*
age: 18
constructor: function Person() {}
name: "Oliver"
sayName: function () {
__proto__: Object
*/
構造関数、プロトタイプの属性と実例の関係については、「jsアドバンストプログラミング」の第6.2.3章を参照してください.
2つの方法:isPrototypeOf()およびObject.getProtytypeOf()(ECMAScript 5).前者は[Prottotype]を確定するためのものです.後者は[Prottype]の値を返します.例えば:
console.log(Person.prototype.isPrototypeOf(person1)); //true
console.log(Object.getPrototypeOf(person1).name); //Oliver

console.log(Object.getPrototypeOf(person1));
/*
age: 18
constructor: function Person() {}
name: "Oliver"
sayName: function () {
__proto__: Object
*/
オブジェクトに属性を追加すると、この属性は原型オブジェクトの同名属性をブロックしますが、その属性は変更されません.deleteを使ってこの属性を削除すると、元の属性に再アクセスできます.例えば:
function Person(){};
Person.prototype.name = "Oliver";
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
person1.sayName(); //Oliver     Name

person1.name = "Troy";
person1.sayName(); //Troy     Name

delete person1.name;
person1.sayName(); //Oliver     Name
あるオブジェクトの属性を読み取るたびに、インスタンス自体から検索を開始します.指定された名前の属性が見つからない場合は、元のオブジェクトから再度検索します.
方法hasOwnProperty()は、属性がオブジェクトのインスタンスにある場合、trueを返すことを検出する.例えば:
console.log(person1.hasOwnProperty("age")); //false age       
console.log(person1.hasOwnProperty("name")); //true name       
原型とin操作符
2つの方法は、in操作子を使用します.単独で使用し、for-iサイクルで使用します.
単独で使用する場合、inはtrueに戻り、この属性がインスタンスまたはプロトタイプに存在することを説明する.例えば:
function Person(){};
Person.prototype.name = "Oliver";
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
person1.name = "Troy";
person1.sayName(); //Troy     Name

console.log("name" in person1); //true name         
console.log(person1.hasOwnProperty("name")); //true name      
//       name        
また、

function Person(){};
Person.prototype.name = "Oliver";
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
person1.name = "Troy";
person1.sayName(); //Troy     Name

var person2 = new Person();

console.log("name" in person1); //true name         
console.log(person1.hasOwnProperty("name")); //true name      
//       name        

console.log("name" in person2); //true
console.log(person2.hasOwnProperty("name")); //false
//      name        
関数hasPrototypeProperty(object,name)をカスタマイズします.すなわち、上記の2つの方法を同時に使用して、属性がインスタンスに存在するかどうかを決定する.例えば:
function Person(){};
Person.prototype.name = "Oliver";
Person.prototype.age = 18;
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
person1.name = "Troy";
person1.sayName(); //Troy     Name

var person2 = new Person();

function hasPrototypeProperty(object,name){
    console.log((name in object) && !(object.hasOwnProperty(name)))
}

hasPrototypeProperty(person2,"name"); //true name       
hasPrototypeProperty(person1,"name"); //false name       
Object.defineProperty()方法で定義された属性:
function Person(){};
Person.prototype.name = "Oliver";
Person.prototype.sayName = function(){
    console.log(this.name);
};

var person1 = new Person();
Object.defineProperty(person1, "age", {
    value: 18
})

console.log(person1.hasOwnProperty("age")); //true age       
for-i、[[enumerable]、defineProperty、ハスOwnPropertyの例について:
var person1 = {
    age: 18
};
Object.defineProperty(person1, "name", {
    value: "Oliver",
    enumerable: true
})
for(var x in person1){
    console.log(x);
}
console.log(person1.hasOwnProperty("name")); //true
また、
function Person(age){
    this.age = age;
}
var person1 = new Person(18);
Object.defineProperty(person1, "name", {
    value: "Oliver",
    enumerable: false
})
for(var x in person1){
    console.log(x); // defineProperty    name        ,       
}
console.log(person1.hasOwnProperty("name")); //true
また、
function Person(){};
Person.prototype.age = 18;
var person1 = new Person();
Object.defineProperty(person1, "name", {
    value: "Oliver",
    enumerable: false
})
for(x in person1){
    console.log(x); //             name     
}
しかし、
function Person(){};
Person.prototype.age = 18;
Person.prototype.name = "Oliver";
var person1 = new Person();
Object.defineProperty(person1, "name", {
    enumerable: false
})
for(x in person1){
    console.log(x); //            name     
}
プロトタイプの[[enumerable]はfalseに設定され、for-innを呼び出しても列挙できます.
さらに、Object.keys()方法は、エニュメレート・属性のすべての文字列配列を返すことができる.
function Person(){};
Person.prototype.age = 18;
Person.prototype.name = "Oliver";

var person1 = new Person();
Object.defineProperty(person1, "sound", {
    value: "miao~",
    enumerable: true //   
});
Object.defineProperty(person1, "sound2", {
    value: "wang~",
    enumerable: false //    
});

console.log(Object.keys(Person.prototype)); //["age", "name"]
console.log(Object.keys(person1)); //["sound"]
Object.getOwnPropertyName()方法は、列挙の可否にかかわらず、すべての例示的属性を返してもよい.
function Person(){};
Person.prototype.age = 18;
Person.prototype.name = "Oliver";

var person1 = new Person();
Object.defineProperty(person1, "sound", {
    value: "miao~",
    enumerable: true //   
});
Object.defineProperty(person1, "sound2", {
    value: "wang~",
    enumerable: false //    
});

console.log(Object.keys(Person.prototype)); //["age", "name"]
console.log(Object.keys(person1)); //["sound"]
console.log(Object.getOwnPropertyNames(Person.prototype)); //["constructor", "age", "name"]
console.log(Object.getOwnPropertyNames(person1)); //["sound","sound2"]
もっと簡単な原型文法
つまり文字の量の方法です.

function Person(){};
Person.prototype = {
    name: "Oliver",
    age: 18,
    sayName: function(){
        console.log(this.name);
    }
};

var person1 = new Person();
console.log(Person.prototype.constructor); //    Person()    

function People(){};
People.prototype.name = "Troy";
People.prototype.age = 26;
People.prototype.sayName = function(){
    console.log(this.name);
};

var people1 = new People();
console.log(People.prototype.constructor); //     People()    
上の一つは文字の量の方法です.しかし、これによってもたらされる問題は、彼の元のオブジェクトのconstructor属性が、前の例のPerson()構造関数を指しなくなることである.(実は私たちは本質的にプロトタイプのオブジェクトを書き換えたのです.)
もしconstructorの値が本当に重要であれば、それを適切な値に設定するだけでいいです.
function Person(){};
Person.prototype = {
    constructor: Person,
    name: "Oliver",
    age: 18,
    sayName: function(){
        console.log(this.name);
    }
};

var person1 = new Person();
console.log(Person.prototype.constructor); //    Person()    

function People(){};
People.prototype.name = "Troy";
People.prototype.age = 26;
People.prototype.sayName = function(){
    console.log(this.name);
};

var people1 = new People();
console.log(People.prototype.constructor); //     People()    
しかし、文字どおりの量の問題はまだ終わっていません.このようにconstructor属性をリセットすると[Enumerable]特性がtrueに設定されます.したがって、ECMAScript 5をサポートするjsエンジンにおいて、Object.defineProperty()方法でfalseに修正することができる.
function Person(){};
Person.prototype = {
    constructor: Person,
    name: "Oliver",
    age: 18,
    sayName: function(){
        console.log(this.name);
    }
};

var person1 = new Person();
console.log(Person.prototype.constructor);
for (var x in person1){
    console.log(x); //     constructor,                  
}

Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false
});

for (var x in person1){
    console.log(x); //       constructor  ,         ECMAScript 5 js   
}

/*
[Log] function Person() {} (repetition.html, line 130)
[Log] constructor (repetition.html, line 132)
[Log] name (repetition.html, line 132)
[Log] age (repetition.html, line 132)
[Log] sayName (repetition.html, line 132)
[Log] name (repetition.html, line 140)
[Log] age (repetition.html, line 140)
[Log] sayName (repetition.html, line 140)
*/
原型の動態
私たちは元のオブジェクトに対して行った変更は直ちに実例に反映されます.実例と原型の間のリンクはコピーではなくポインタにすぎないからです.
function Person(){};
var person = new Person(); //person Person()           
Person.prototype.name = "Oliver";
console.log(person.name); //          
プロトタイプを書き換えると違っています.例の「Prottype」はプロトタイプのオブジェクトに向けられます.元のプロトタイプのオブジェクトを修正すると、構造関数と最初のプロトタイプとの連絡が切れます.
function Person(){};
var person = new Person();

Person.prototype = { //     Person.prototype,    Person.prototype
    constructor: Person,
    name: "Oliver"
}

console.log(person.name); //        ,        Person.prototype
ここから明らかに分かるように、Person.prototype={}実は字面量の方法は原型の対象を書き直したのです.Person.prototype.name=「Oliver」であれば書き換えではなく修正して、「新しい原型オブジェクト」を作成しません.
「js高級プログラム設計」の本の中で6.2.3の図6-3はこの原理を明確に説明しています.
原生対象の原型
すべての原生の引用タイプ(Object、Aray、Stringなど)は、その構造関数の原型に方法を定義しています.また、オリジナルのオブジェクトをカスタマイズする方法もあります.
var array = new Array();
Array.prototype.name = function(){
    console.log("Array")
};

array.push("hello ","there");
console.log(array);

array.name();
変更もできます
var array = new Array();
Array.prototype.toString = function(){
    return("Array")
};

array.push("hello ","there");
console.log(array.toString());
//      toString()  
オリジナルオブジェクトの原型の修正や書き換えは強く推奨されません.
原型対象の問題
参照の種類の値を含む属性であり、問題が深刻です.プロトタイプに具体的に表される属性は、インスタンスによって共有される.
function Person(){};
Person.prototype = {
    constructor: Person,
    name: "Oliver",
    age: 18,
    friends: ["Troy","Alice"]
}

var person1 = new Person();
var person2 = new Person();

person1.friends.push("Ellen");
console.log(person1.friends); //["Troy", "Alice", "Ellen"]
console.log(person2.friends); //["Troy", "Alice", "Ellen"]
//      ,               ,push()       person1.friends,    

person1.friends = ["Troy", "Alice"];
console.log(person1.friends); //["Troy", "Alice"]
console.log(person2.friends); //["Troy", "Alice", "Ellen"]
//                 ,        

console.log(person1.hasOwnProperty("friends")); //true;
console.log(person2.hasOwnProperty("friends")); //false;
//       ,                      "friends"
ここから分かるように、もし私達の初志はこのようにただ共有の行列を作りたいならば、もちろん問題はありません.しかし、実例は自分の属性を持っているので、単独でモデルを使うべきではない.構造関数モードとプロトタイプモードを組み合わせて使用します.
コンストラクタモードとプロトタイプモードを組み合わせて使用します.
これは参照の種類を定義するためのデフォルトのモードです.

function Person(name,age){
    this.name = name;
    this.age = age;
    this.friends = [];
} //     
Person.prototype = {
    constructor: Person,
    sayName: function(){
        return this.name;
    }
} //     
var person1 = new Person("Oliver",18);
var person2 = new Person("Troy",24);
person1.friends.push("Alice","Mark");
person2.friends.push("Mac");
console.log(person1.friends.toString());
console.log(person2.friends.toString());
/*
[Log] Alice,Mark (repetition.html, line 228)
[Log] Mac (repetition.html, line 229)
*/
ダイナミックプロトタイプ
あるべき方法が有効かどうかをチェックすることによって、初期化の原型が必要かどうかを決定することができます.
function Person(name,age){
    this.name = name;
    this.age = age;
    if (typeof this.sayName != "function"){
        Person.prototype.sayName = function(){
            return (this.name);
        };
    }
}
var person = new Person("Oliver",18);
console.log(person.sayName()); //Oliver
実際には次のコードをコンストラクターにカプセル化しました.
function Person(name,age){
    this.name = name;
    this.age = age;
}
Person.prototype.sayName = function(){
    return(this.name);
};
var person = new Person("Troy",24);
console.log(person.sayName()); //Troy
寄生構造関数モード
世紀は悲しいです.工場のモデルと同じです.他のモードが使用できる場合は、このモードは使用しないことを推奨します.
コンストラクタモード
妥当な対象とは、公的な属性がないこと、またその方法でもthisを引用しない対象を指す.
function Person(name,age){
    var obj = new Object();
    obj.sayName = function(){
        console.log(name);
    };
    return obj;
}
var person1 = Person("Oliver",18);
person1.sayName(); //Oliver