JavaScript中创建对象

本文介绍了JavaScript中创建对象的几种方法,以及对象原型的问题。

创建对象的方法大致有五种,使用构造函数、使用对象字面量、工厂模式、构造函数模式以及原型模式。

使用构造函数

1
2
var person = new Object();
person.abc = "abc"; //修改对象内容

这种方式是先用new Object()的方式产生一个空对象,之后为对象添加属性和方法。

使用对象字面量

1
2
3
var person = {
...对象内容...
};

使用对象字面量直接书写一个对象。

以上两种方式在批量产生很多对象时会有大量的重复代码。因此有以下方式:

工厂模式

用函数来封装以特定接口创建对象的细节。

1
2
3
4
5
6
7
8
9
10
11
function createPerson(name, age){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson("zhangsan", 19);
var person2 = createPerson("lisi", 20);

然而这种方法没有解决对象识别的问题。

构造函数模式

1
2
3
4
5
6
7
8
9
function Person(name, age){  //按照惯例,构造函数始终都应以一个大写字母开头
this.name = name;
this.age = age;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("zhangsan", 19);
var person2 = new Person("lisi", 20);

使用new方式调用构造函数实际会经历以下4个步骤:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋给新对象;
  3. 执行构造函数中的代码;
  4. 返回新对象;

构造函数模式解决了对象识别问题,它可以将实例标识为一种特定的类型:

1
alert(person1 instanceof Person);  //true

构造函数的缺点是每个方法都要在每个实例上重新创建一遍。

1
alert(person1.sayName == person2.sayName);  //false

然而函数没有必要创建多个实例,因此可以使用原型模式。

原型模式

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例_共享_的属性和方法。

使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(){
}
Person.prototype.name = "zhangsan";
Person.prototype.age = 19;
Person.prototype.sayName = function(){
alert(this.name);
};

var person1 = new Person();
var person2 = new Person();
person1.sayName(); //"zhangsan"
person2.sayName(); //"zhangsan"
alert(person1.sayName == person2.sayName); //true

原型中的属性和方法是由所有实例共享的

在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。例如 Person.prototype.constructor 指向 Person。

javascriptCreatObject1.jpg

当调用构造函数创建一个新实例后,该实例的内部将包含一个指针[[Prototype]],指向构造函数的_原型对象_(不是构造函数)。该指针无法访问,但是有两个相关的方法:isPrototypeOf()和Object.getPrototypeOf()。

1
2
alert(Person.prototype.isPrototypeOf(person1));  //true
alert(Object.getPrototypeOf(person1) == Person.protype); //true

在读取某个实例的属性时,搜索先从实例本身开始,实例没有再去原型中找。因此实例中的同名属性会屏蔽原型中的同名属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(){
}
Person.prototype.name = "zhangsan";
Person.prototype.age = 19;
Person.prototype.sayName = function(){
alert(this.name);
};

var person1 = new Person();
var person2 = new Person();
person1.name = "lisi";
person1.sayName(); //"lisi" --来自实例
person2.sayName(); //"zhangsan" --来自原型

可以使用delete操作符完全删除实例属性。

1
2
delete person1.name;
person1.sayName(); //"zhangsan"

hasOwnProperty()方法可以检测一个属性是存在于实例中还是存在于原型中。存在于实例中时返回true

1
alert(person1.hasOwnProperty("name"));

而in操作符会在通过对象能够访问给定属性时返回true。

两个组合可以判断属性是否存在于原型中。

在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性,_实例和原型中的属性都会包括_。

Object.keys()方法可以取得对象上所有可枚举的_实例属性
Object.getOwnPropertyNames()方法取得所有实例属性,
无论是否可枚举_。

对原型对象所做的任何修改都可以立即从实例上反映出来。

1
2
3
4
5
var friend  = new Person();
Person.prototype,sayHi = function(){
alert("Hi!");
};
friend.sayHi();

但不能够_重写_整个原型对象。这是因为:

添加属性和方法:

1
2
3
4
5
6
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function(){
alert(this.name);
};

与对象字面量:

1
2
3
4
5
6
7
8
person = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function(){
alert(this.name);
}
};

两种有很大的区别,第一种是修改对象;第二种是直接新建对象。再给原型添加方法时,不能使用第二种,会造成原型链的断裂,因为产生了一个新的原型;

javascriptCreatObject2.jpg

最后,综合使用构造函数和原型,构造函数用于定义实例属性,原型模式用于定义方法和共享的属性。可以取得最优的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["zhouwu", "zhengwang"];
}

Person.prototype.sayName : function(){
alert(this.name);
}

var person1 = new Person("zhangsan", 29, "SE");
var person2 = new Person("lisi", 25, "PM");

person1.friends.push("abc");
alert(person1.friends); //"zhouwu, zhengwang, abc"
alert(person2.friends); //"zhouwu, zhengwang"
alert(person1.friends === person2.friends); //false
alert(person2.sayName === perosn2.sayName); //true