JavaScript中创建对象的方法

1.最简单的方法就是创建一个Object实例,然后为其添加属性和方法:(缺点:使用同一个接口创建很多对象,会产生大量的代码)

1
2
3
4
5
6
7
var person = new Object();
person.name = "cooling";
person.age = 20;
person.job = "Web Engineer";
person.sayName() = function(){
alert(this.name);
};//注意这里有分号

2.对象字面量语法:(缺点:使用同一个接口创建很多对象,会产生大量的代码)

注意和上面的区别:用“,”号分隔,用的是“:”号赋值。

1
2
3
4
5
6
7
8
var person = {
name : "cooling",//注意这里是逗号
age : 20,
job : "Web Engineer",
sayName : function(){
alert(this.name);
}
};

3.工厂模式:

1
2
3
4
5
6
7
8
9
10
11
12
function createPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson('cooling1',20,'Web Engineer');
var person2 = createPerson('cooling2',23,'Web Engineer');

4.构造函数模式:

注意:这里可是构造函数法的详细用法!像Object和Array这样的原生构造函数,在运行时,会自动出现在执行的环境中。此外也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。按照惯例,构造函数的首字母都应该大写,非构造函数用小写。

1
2
3
4
5
6
7
8
9
10
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person('cooling1',20,'Web Engineer');
var person2 = new Person('cooling2',23,'Web Engineer');

上述person1和person2分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person。

1
2
alert(person1.constructor == Person);//true
alert(person2.constructor == Person);//true

对象的constructor属性最初是用来标识对象的类型的。但是,提到检测对象类型,还是使用instanceof操作符更靠谱些。

1
2
alert(person1 instanceof Person);//true
alert(person1 instanceof Object);//true

①构造函数解析:

构造函数与普通函数的唯一区别就在于调用他们的方式不同。任何函数,只要是通过new操作符来调用,那么他就是构造函数;而任何函数,如果不通过new来调用,它就和普通函数没太大区别,最多也就是首字母大写了而已。一旦函数被作为构造函数执行,它内部的this属性将引用函数本身。

当成构造函数使用时:

1
2
var person1 = new Person('cooling1',20,'Web Engineer');
person1.sayName();

当成普通函数使用时:

1
2
Person('cooling1',20,'Web Engineer');
windown.sayName();

②构造函数的缺点:

不同实例上(person1和person2就分别为一个实例)的同名函数是不相等的:

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

其允许你给对象配置同样的属性,但是构造函数并没有消除代码的冗余。在上面的例子中,每一个对象(person1和person2就分别为一个对象)都有自己的sayName()方法。这就意外着,如果你有100个实例对象,你就有100个sayName()函数做相同的事情,只是数据不同。

解决方案:(把函数的定义转移到构造函数的外部)

1
2
3
4
5
6
7
8
9
10
11
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
var person1 = new Person('cooling1',20,'Web Engineer');
var person2 = new Person('cooling2',23,'Web Engineer');

这里依旧存在问题:
a.在全局作用域中定义的函数,实际上只能够被某个对象调用。
b.如果存在很多 的方法,那么就需要定义很多个全局函数,这个时候自定义的引用类型就毫无封装性了。

5.原型模式:

①代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(){

}
Person.prototype.name = "cooling";
Person.prototype.age= 20;
Person.prototype.job = "Web Engineer";

Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName();//cooling
var person2 = new Person();
person2.sayName();//cooling

alert(person1.sayName == person2.sayName);//true

②解释:

a.我们创建的任何一个函数,都有一个prototype属性(也就是原型属性)。该属性是一个指针,指向一个对象(也就是原型对象),这个对象包含了很多公用的属性和方法。

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

c.调用构造函数创建对象(新实例)的时候,该实例内部将包含一个指针(内部属性,跟原型属性类似),该指针指向原型对象。这个指针为[[Prototype]],在脚本中,没有标准的的方式访问该指针。但是,在Firefox Safari Chrome中,每一个对象都支持一个__proto__属性。

注意:虽然在任何实现中都无法访问到[[Prototype]],但是可以通过isPrototypeOf()方法来确定原型对象和实例化的对象之间是否有这种关系(如果[[Prototype]]指向调用isPrototypeOf()方法的对象Person.prototype,那么返回true),如下

1
2
alert(Person.prototype.isPrototypeOf(person1));//true
alert(Person.prototype.isPrototypeOf(person2));//true

严重注意:
ECMAScript 5中新增了个方法:Object.getPrototypeOf(),在所有支持的实现中,这个方法可以返回[[Prototype]]的值。例如:

1
2
alert(Object.getPrototypeOf(person1) == Person.prototype);//true
alert(Object.getPrototypeOf(person1).name);//cooling1

③调用属性和方法的顺序:

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(){

}
Person.prototype.name = "cooling";
Person.prototype.age= 20;
Person.prototype.job = "Web Engineer";

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

var person1 = new Person();
var person2 = new Person();
person1.name = "zs1"
person1.sayName();//zs1
person2.sayName();//cooling

通过上面的代码,可以了解,在调用属性或者方法的时候,优先使用实例化的对象上现有的属性和方法。如果实例化的对象上没有,再向函数的原型对象上去找。注意:实例化的对象,可以访问原型对象的属性和方法,但是不能修改它。

不过,可以通过delete将其删除:

1
2
delete person1.name;
person1.sayName();//cooling

④检测:
使用hasOwnProperty()方法,可以检测一个属性是存在于实例对象中的,还是存在于原型对象中的。只在给定属性存在于实例对象中,才返回true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(){

}
Person.prototype.name = "cooling";
Person.prototype.age= 20;
Person.prototype.job = "Web Engineer";

Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "zs1"
alert(person1.hasOwnProperty("name");//true
alert(person2.hasOwnProperty("name");//false

⑤原型与in操作符:

a.通过对象访问给定的属性时,使用in操作符,始终返回true。也就是说,无论属性是直接在对象上访问到的,还是在原型对象上访问到的,使用in操作符都会返回true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(){

}
Person.prototype.name = "cooling";
Person.prototype.age= 20;
Person.prototype.job = "Web Engineer";

Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person2.name = "zs2";

alert(person1.hasOwnProperty("name"));//false
alert("name" in person1);//true
alert("name" in person2);//true

b.通过结合hasOwnProperty()方法,可以判断属性或者方法是位于实例对象上,还是位于原型对象上。

1
2
3
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name) && (name in object);
}

c.for…in:返回的是所有能够通过对象访问的,可以枚举的属性,其中既包括实例对象中的,也包括原型对象中的。对于原型对象中,不可枚举的,进行了屏蔽,也就是说尽管你不可枚举,但是在使用我for…in的时候,我依然是把你当成可枚举的。(在IE8及其以下,没有起到屏蔽作用,也就是说,不可枚举就是不可枚举)。
注:不可枚举指的是[[Enumerable]]标记为false

1
2
3
4
5
6
7
8
9
10
var o = {
toString: function(){
return "My Object";
}
};
for (var prop in o){
if (prop == "toString") {
alert("Found toString");
}
}

此处看不到弹出框,原因是原型的toString()方法的[[Enumerable]]标记为false。具体细节比较繁琐,详见《高级程序设计》153页。

取得对象上所有可以枚举的实例属性,还可以使用Object.keys()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(){

}
Person.prototype.name = "cooling";
Person.prototype.age= 20;
Person.prototype.job = "Web Engineer";

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

var keys = Object.keys(Person.prototype);
alert(keys);//name age job sayName

如果你想得到所有的实例属性,不管是否可以枚举。可以使用Object.getOwnPropertyNames()方法:

1
2
var keys = Object.getPropertyNames(Person.prototype);
alert(keys);//constructor name age job sayName

⑥更简单的原型语法:
为了方便,可以这么使用原型模式创建对象:

1
2
3
4
5
6
7
8
9
10
11
function Person(){

}
Person.prototype = {
name : "cooling",
job : "web engineer",
age : 20,
sayName : functon(){
alert(this.name);
}
};

但是,这个时候相当于是用对象字面量形式创建了一个新的对象,因此,创建的实例对象的指向的原型对象的constructor属性就不再回指原函数Person。如:

1
2
var friend = new Person();
alert(friend.constructor == Person);//false

不过,可以这么干:

1
2
3
4
5
6
7
8
9
10
11
12
function Person(){

}
Person.prototype = {
constructor : Person,//手动让它指向Person!
name : "cooling",
job : "web engineer",
age : 20,
sayName : functon(){
alert(this.name);
}
};

可是,这样的话,会导致constructor的[[Enumerable]]特性被设置成true,原生的应该是false(不可枚举的)。所以,还可以这么干:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(){

}
Person.prototype = {
name : "cooling",
job : "web engineer",
age : 20,
sayName : functon(){
alert(this.name);
}
};
//重设构造函数
Object.defineProperty(Person.prototype,"constructor",{
enumerable : false;
value : Person;
});

⑦原型的动态性:

所谓原型的动态性,就是指随时可以为函数的原型对象添加属性和方法:

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

值得注意的是,随时为函数的原型对象添加属性和方法没有问题。但是如果用如同对象字面量方式创建新的对象,从而添加属性和方法是严重有问题的。因为,这个时候,新建的对象的原型和之前的构造函数的原型不是一个!(这个时候需要注意创建对象的顺序了,如果是先new了,接着再对象字面量创建的话,那么原型对象就重写了。)如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(){

}
var friend = new Person();
Person.prototype = {
constructor : Person,
name : "cooling",
age : 22,
sayName : function(){
alert(this.name);
}
};
var girlFriend = new Person();
girlFriend.sayName();//cooling
friend.sayName();//error(浏览器无任何响应)

⑧原生对象原型&&原型对象的问题:
a.原生对象原型可以像自定义对象原型一样,可以随时添加属性和方法。
b.问题:对于包含引用类型值的属性,存在较大问题。
6.组合使用构造函数模式和原型模式(!最常用最推荐的方式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Person(age,name,job){
this.age = age;
this.name = name;
this.job = job;
this.friend = ['cooling1','cooling2'];
}
Person.prototype = {
constructor :Person,
sayName : function(){
alert(this.name);
}
};
var person1 = new Person(20,'cooling','Web Engineer');
var person2 = new Person(22,'cooling','Web Engineer');

person1.friend.push("cooling3");
alert(person1.friend);//1,2,3
alert(person2.friend);//1,2
alert(person1.friend == person2.friend);//false
alert(person1.sayName == person2.sayName);//true

7.动态原型模式
所谓动态原型模式,就是通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。它是在构造函数模式的基础上,改进了构造函数模式会多次创建方法函数,产生大量内存的问题。

1
2
3
4
5
6
7
8
9
10
11
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
//这里比较关键
if (typeof this.sayName != "function") {
Person.prototype.sayName = function(){
alert(this.name);
};
}
}

!注意:这里对原型做的修改,可以立即在所有实例中得到反应!

8.寄生构造函数模式
和工厂模式的区别就在于使用了new

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var people = new Person('123',23,'213');
people.sayName();//123

9.稳妥构造函数模式
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。其不使用new操作符调用构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(name);//注意这里
};
return o;
}
var people = Person('123',23,'213');
people.sayName();//123