创建对象

388 阅读7分钟

1.工厂模式

用函数来封装,以特定接口创建对象

  • 无法知道对象类型
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; 
} 
var person1 = createPerson("Nicholas", 29, "Software Engineer"); 
var person2 = createPerson("Greg", 27, "Doctor");
person1.sayName() // Nicholas
person2.sayName() // Greg

2.构造函数模式

  • 按照惯例,构造函数的首字母要大写
function Person(name, age, job){ 
 this.name = name; 
 this.age = age; 
 this.job = job; 
 this.sayName = function(){ 
    console.log(this.name); 
 }; 
} 
var person1 = new Person("Nicholas", 29, "Software Engineer"); 
var person2 = new Person("Greg", 27, "Doctor");
person1.sayName() // Nicholas
person2.sayName() // Greg

构造函数模式,和 工厂模式相比

  • 1.没有显示的创建对象
  • 2.直接将属性和方法,赋给了this对象
  • 3.没有return语句

使用构造函数创建对象,会经历四个步骤

  • 1.创建对象
  • 2.将构造函数的作用域赋给新对象(this指向新对象)
  • 3.执行构造函数中的代码(为新对象增加属性)
  • 4.返回新对象

使用构造函数创建的对象,都有一个constructor属性,指向构造函数。

创建自定义的构造函数,意味着,可以将它的实例,识别为一种特定的类型

constructor 属性,用来标识对象类型。可以使用instanceof 检测

person1 instanceof Object // true

构造函数的问题:

  • 每个方法,都相当于在实例上创建了一遍
function Person(name, age, job){ 
 this.name = name; 
 this.age = age; 
 this.job = job; 
 //this.sayName = function(){ 
 //   console.log(this.name); 
 //}; 
 // 相当于下边的写法
 this.sayName = new Function("alert(this.name)"); // 每定义一个函数,都相当于实例化了一个对象
}
var person1 = new Person(); 
var person2 = new Person();
person1.sayName == person2.sayName; //false

因此,我们可以把函数定义,放到全局作用域

function Person(name, age, job){ 
    // some code
    this.sayName= sayName
}

function sayName(){}

如此,sayName指向同一个函数,实现了函数共享。但是如果方法过多,就需要在全局作用域创建多个函数,那么我们自定义的引用类型,就没有什么意义了。 这个问题,可以通过原型模式解决

3.原型模式

每个函数都有一个prototype(原型)属性,指向一个对象(原型对象)。

这个对象的用途是:包含可以有特定类型的所有实例共享的属性和方法

即:所有实例都可以共享prototype的属性和方法

function Person(){ 
}
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function(){ 
 console.log(this.name); 
}; 
var person1 = new Person(); 
person1.sayName(); //"Nicholas" 
var person2 = new Person();
person2.sayName(); //"Nicholas" 
console.log(person1.sayName == person2.sayName); //true

所有实例(person1 和 person2)访问的都是同一组属性和一个sayName方法

理解原型对象

函数都有一个prototype属性,指向函数的原型对象。在默认情况下,原型对象都有一个constructor(构造函数)属性,这个属性,包含指向prototype所在函数的指针

Person.prototype.constructor === Person // true

创建了自定义函数之后,构造函数的原型对象默认只包含constructor属性,至于其他方法,都是从Object对象继承而来。

当构造函数创建一个实例之后,该实例包含一个内部属性,指向 构造函数的原型对象

该属性存在于实例原型对象之间,和构造函数没有直接关系

person1.__proto__ === Person.prototype // true

这个内部属性,ECMA5叫做 [[prototype]],FireFox、safari、chrome 叫 __proto__,

avatar

可以使用 isPrototypeOf 检测 [[prototype]] 属性的指向

Person.prtotype.isPrototypeOf(person1) // true

也可以使用getPrototypeOf获取原型对象

(Object.getPrototypeOf(person1) == Person.prototype // true

读取属性的过程

每当代码读取某个属性时,会先去对象实例本身开始查找,查找到的话,就会返回;查不到的话,就会继续搜索[[prototype]](__proto__)属性指向的原型对象,直至查到Object.prototype。(这种查找机制,叫做原型链)

当为对象添加一个属性时,会自动屏蔽掉原型的同名属性,但不会修改。

即使将该属性赋值为null,也不会恢复指向原型的链接

但是可以通过delete操作符,删除实例上的属性,恢复链接

function Person(){ 
} 
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function(){ 
 alert(this.name); 
}; 
var person1 = new Person(); 
var person2 = new Person(); 
person1.name = "Greg"; 
console.log(person1.name); //"Greg" --实例
console.log(person2.name); //"Nicholas" -- 原型
delete person1.name; 
console.log(person1.name); //"Nicholas" -- 原型

使用 hasOwnProperty 检测一个属性,是否存在于一个实例中

alert(person1.hasOwnProperty("name")); //false 
person1.name = "Greg"; 
alert(person1.name); //"Greg" -- 实例
alert(person1.hasOwnProperty("name")); //true 
alert(person2.name); //"Nicholas" -- 原型
alert(person2.hasOwnProperty("name")); //false 
delete person1.name; 
alert(person1.name); //"Nicholas" -- 原型
alert(person1.hasOwnProperty("name")); //false

使用in操作符,判断属性是否在原型链上

person1.hasOwnProperty("name"); //false 
"name" in person1; //true
person1.name = "Greg"; 
person1.name; //"Greg" -- 实例
person1.hasOwnProperty("name"); //true 
"name" in person1; //true
function Person(){ 
} 
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function(){ 
 alert(this.name); 
}; 
var person = new Person(); 
alert(hasPrototypeProperty(person, "name")); //true name存在于原型中 
person.name = "Greg"; 
alert(hasPrototypeProperty(person, "name")); //false 实例中重写name属性,那么name就存在于实例中了

Object.keys() 返回实例中所有可枚举属性

for-in循环,返回是 能过通过对象访问的、可枚举的属性,包括实例 以及 原型中的

Object.getOwnPropertyNames()获取所有实例属性,包括不可枚举

var keys = Object.getOwnPropertyNames(Person.prototype); 
console.log(keys); //"constructor,name,age,job,sayName"

注意:

当我们使用字面量方式创建新对象,要注意constructor的指向

function Person(){ 
} 
Person.prototype = { 
  name : "Nicholas", 
  age : 29, 
  job: "Software Engineer", 
  sayName : function () { 
  alert(this.name); 
  } 
}
Person.prototype.constructor === Person; // false 
Person.prototype.constructor=== Object ; // true 

当prototype被重写的时候,constructor不再指向Person,而是指向新对象的构造函数(Object) 需要手动指定constructor

Person.prototype.constructor = Person

但是此时,constructor属性,将会变成可枚举的

Object.keys(Person.prototype) // ["name", "age", "job", "sayName", "constructor"]

使用

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

原型的动态性

在原型中查找值的过程是一次搜索,因此,我们对原型对象所做的任何修改都能立即在实例上显现出来。即使先创建实例,也是如此。

function Person(){ 
} 
var p1 = new Person() // 先创建的实例

//Person.prototype.sayHi = function(){ 
// alert("hi"); 
//}; 
//p1.sayHi(); // hi

// 重写圆形原型
Person.prototype = { 
  name : "Nicholas", 
  age : 29, 
  job: "Software Engineer", 
  sayName : function () { 
    alert(this.name); 
  } 
}

p1.sayName() //p1.sayName is not a function
console.log(Person.prototype.constructor === Person); // false

原型对象被重写,将会不同。

调用构造函数时,会为实例添加一个[[prototype]](或者说是__proto__)指针,指向原型,而重写原型,将会斩断构造函数和原型之间的联系。

所以,导致 p1.sayName() 调用报错

avatar

对象实例所对应的原型对象,引用的仍是重写之前的原型。

原型对象的问题

引用值共享

function Person(){ 
   this.hobby = ['basket ball','tennis']
} 
Person.prototype = { 
   constructor: Person, 
   name : "Nicholas", 
   age : 29, 
   job : "Software Engineer", 
   friends : ["Shelby", "Court"], 
   sayName : function () { 
       alert(this.name); 
   }
}; 
var person1 = new Person(); 
var person2 = new Person(); 
person1.friends.push("Van"); 
console.log(person1.friends); //"Shelby,Court,Van" 
console.log(person2.friends); //"Shelby,Court,Van"
console.log(person1.friends === person2.friends); //true

person1.hobby.push('ping-pong')
console.log(person1.hobby) // [ 'basket ball', 'tennis', 'ping-pong' ] 构造函数中的属性,是实例私有的,不会共享
console.log(person2.hobby) // [ 'basket ball', 'tennis' ]

原型中所有实例的属性和方法是共享的,可以节省大量内存,但是也同时导致了引用值共享的问题(person1.friends=['Ann'] 并不会有这种问题-- 在实例添加同名属性,会屏蔽调原型上的属性)

那么如何解决原型对象的应用值共享问题?-- 组合使用原型和构造函数

4.组合使用原型模式和构造函数模式

组合使用原型模式和构造函数模式,是创建自定义类型最常见的方式。原型模式定义共享的属性和方法,构造模式定义实例属性

每个实例都会拥有自己的一份实例属性的副本,同时又共享着对方法的引用,最大限度的节省了内存。

另外,还支持像构造函数传递参数

function Person(name, age, job){
 this.name = name; 
 this.age = age; 
 this.job = job; 
 this.friends = ["Shelby", "Court"]; 
} 
Person.prototype = { 
  constructor : Person, 
  sayName : function(){ 
    console.log(this.name); 
 } 
} 
var person1 = new Person("Nicholas", 29, "Software Engineer"); 
var person2 = new Person("Greg", 27, "Doctor"); 
person1.friends.push("Van"); 
alert(person1.friends); //"Shelby,Count,Van" 
alert(person2.friends); //"Shelby,Count" 
alert(person1.friends === person2.friends); //false 
alert(person1.sayName === person2.sayName); //true

5.动态原型模式

即:通过检查某个存在的方法是否有效,来决定否需要初始化原型

 //方法
 if (typeof this.sayName != "function"){// sayName不存在的时候才会执行,即在初次调用构造函数时
    Person.prototype.sayName = function(){ 
        alert(this.name); 
    }; 
 }

注意:不能使用字面量的方式对原型赋值,会导致构造函数与原型之间的关系被切断,那么if判断自然就无效了

6.寄生构造函数模式

原理:创建一个函数,该函数仅仅是封装创建对象的方法,然后再返回新创建的对象

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; 
} 
var friend = new Person("Nicholas", 29, "Software Engineer"); 
friend.sayName(); //"Nicholas"

除了使用new 操作符之外,和工厂模式一毛一样。

该模式下创建的对象,和构造函数之间没有任何关系,也不可以使用instanceOf检测类型

7.稳妥构造函数模式

所谓稳妥对象,指 没有公共属性,其方法也不引用this对象。

function Person(name, age, job){ 
  var o = new Object(); // 创建要返回的对象
  // 可以在这里定义私有变量和函数
  o.sayName = function(){  // 添加方法
    alert(name); 
  }; 
  //返回对象
  return o; 
}

在这种模式下,只有调用sayName()才可以访问其数据成员。

同样,该模式下创建的对象,和构造函数之间没有任何关系,也不可以使用instanceOf检测类型。