创建对象的模式
创建对象有多种模式,如工厂模式、构造函数模式、原型模式、动态原型模式、寄生构造函数模式、稳妥构造函数模式
工厂模式
//工厂模式
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
}
return o;
}
let person1 = createPerson('Simon', 24, 'Programmmer');
构造函数模式
//构造函数模式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
}
}
let person1 = new Person('Simon', 24, 'Programmer');
let person2 = new Person('Simoner',23,'Student');
alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
alert(person1.sayName == person2.sayName); //false
构造函数模式相对于工厂模式的区别,即Person()中的代码与createPerson()中的代码的不同之处在于:
- 没有显式地创建对象
- 直接将属性和方法赋值给了this对象
- 没有return语句
在创建Person实例的过程中,使用了new操作符,在以这种方式创建实例的过程中会经历以下4个步骤:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(因此this就指向了这个对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
在上述代码中,person1和person2分别保存着一个Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person
构造函数很好用,但是缺点是构造函数的每个方法都要在每个实例上重新创建一遍,因此person1中的sayName与person2的sayName是不一样的。
原型模式
理解原型对象
在JS中创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。总得来讲,prototype就是通过调用构造函数而创建的那个对象实例的原型对象。
function Person() {}
Person.prototype.name = 'simon';
Person.prototype.age = 24;
Person.prototype.job = 'programmer';
Person.prototype.sayName = function() {
console.log(this.name);
}
/*
//如果这样写,那么Person.prototype.constructor将不再指向Person了,而是指向Object构造函数,因为Person.prototype赋值的是一个对象了,但是new的实例仍然是属于Personde,若想继续指向Perosn则可以在属性中指定constructor,如注释的代码
Person.prototype = {
//constructor: Person,
name: 'simon',
age: 24,
job: 'programmer',
sayName: function(){
console.log(this.name);
}
}
let person = new Person();
console.log(person instacneof Object); //true
console.log(person instacneof Person); //true
console.log(person.constructor == Person); //false
console.log(person.constructor == Object); //true
*/
let person1 = new Person();
person1.sayName(); //simon
let person2 = new Person();
person2.sayName(); //simon
console.log(person1.sayName); //simon
console.log(person1.sayName == person2.sayName); //true
无论何时,只要创建了一个函数,就会为该函数创建一个prototype属性,这个属性会指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。在上面的代码中,Person.prototype. 和 constructor都指向Person,而通过这个构造函数,还可以继续为原型对象添加其他属性和方法。
Person构造函数、Person的原型属性(原型对象)以及Person现有的两个实例之间的关系如上图所示。Person.prototype指向了原型对象,而Person.prototype.constructor又指回了Person。原型对象中除了constructor属性外(最初只有constructor属性,并且也是共享的),还包括了后来添加的其他属性,person1和person2都包含了一个内部属性[[prototype]],该属性仅仅指向了Person.prototype,也就是说这两个实例与构造函数没有直接的关系。(实例中的指针仅指向原型,而不指向构造函数)
in操作符以及hasOwnProperty方法的使用
in操作符可以单独使用,也可以在for-in循环中使用。
单独使用时,in操作符会通过对象能访问给定属性时返回true,无论该属性是在原型中还是实例中。
function Person() {}
Person.prototype.name = 'simon';
Person.prototype.age = 24;
Person.prototype.job = 'programmer';
Person.prototype.sayName = function() {
console.log(this.name);
}
let person1 = new Person();
//name属性来自原型
console.log('name' in person1); //true
console.log(person1.hasOwnProperty('name')); //false
person1.name = 'Simoner';
//name属性来自实例
console.log('name' in person1); //true
console.log(person1.hasOwnProperty('name')); //true
hasOwnProperty方法用于确定属性存在于实例中还是原型中。因此用hasOwnProperty和in操作符可以判断属性存在于实例中还是原型中
//若存在于原型中就返回true,存在于实例中就返回false
function hasProperty(object, property){
return !object.hasOwnProperty(property) && (property in object);
}
for-in操作符循环返回的是能通过对象访问的、可枚举的(enumerated属性为true)所有原型以及实例中的属性。在ECMAScript5中,constructor和property属性的[[Enumerated]]设置为false。
原型模式的缺点
- 它忽略了为构造函数传递初始化参数的步骤,因此所有实例在创建时都默认赋值相同的属性值
- 原型中的所有属性是被很多实例共享的,对于包含引用类型值的属性来说,将会出现问题,如下面代码所示,一个实例添加的属性将被另外一个实例所使用,这是不合理的。
function Person(){}
Person.prototype = {
constructor: Person,
name: 'simon',
age: 24,
job: 'programmer',
friends: ['bob','jack'],
sayName: function(){
console.log(this.name);
}
}
let person1 = new Person();
let person2 = new Person();
person1.friends.push('jerry');
console.log(person1.friends); //['bob','jack','jerry']
console.log(person2.friends); //['bob','jack','jerry']
console.log(person1.friends === person2.friends); //true
组合使用构造函数模式和原型模式—最常使用的模式
创建自定义类型最常见的方式就是组合使用构造函数模式和原型模式。构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。最终,每个实例都有自己的一份实例属性的副本,同时也共享着对方法的引用(数据不能共享, 但是方法可以)。
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['bob','jack'];
}
Person.prototype = {
constructor: Person,
sayName = function (){
console.log(this.name);
}
}
let person1 = new Person('simon',24,'programmer');
let person2 = new Person('simonre',23,'student');
person1.friends.push('jerry');
console.log(person1.friends); //['bob','jack','jerry']
console.log(person1.friends); //['bob','jack']
console.log(person1.fsayName === person2.sayName); //true
console.log(person1.friends === person2.friends); //false
寄生构造函数模式
若前面几种模式都不适用,可以使用寄生(parasitic)构造函数模式。其基本思想是创建一个函数,该函数的作用仅仅是封装对象的代码,然后再返回新创建的对象。
function Person(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
}
return o;
}
let person1 = new Person('simon',24,'programmer');
person1.sayName();
寄生构造函数模式创建的对象与构造函数或者与构造函数的原型属性之间没有关系,也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。不能用instanceof操作符来确定对象类型。因此,尽量不使用寄生构造函数模式来创建对象