ecma-262把对象定义为:“ 无序属性的集合,其属性可以包含基本值、对象、或者函数。”
解释:对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。可以把对象想象成散列表,无非就是一组名值对,其中值可以是数据或函数。
创建一个对象可以有如下方法:
- 根据引用类型Object
- 使用对象字面量
- 自定义引用类型
根据引用类型——Object 创建一个对象,代码如下
//创建一个实例对象
var person = new Object();
//为该实例添加属性和方法
person.name = "xiaoming";
person.age = 18;
person.job = "student";
person.sayName = function () {
alert(this.name)
}
早期的JavaScript开发人员经常使用跟这个模式创建新对象。后来对象字面量以其简洁直观的优势成为创建这种对象的首选模式。
使用对象字面量创建对象,代码如下
var person = {
name:"xiaoming",
age:18,
job:"student",
sayName(){
alert(this.name)
}
};
虽然以上方法都可以用来创建单个对象,但这些方式有个明显的确定:使用同一个接口创建很多对象,会产生大量的重复代码。为解决这个问题,人们开始使用工厂模式的一种变体,来抽象创建具体对象的过程,用函数来封装以特定接口创建对象的细节。
使用工厂模式创建对象,代码如下
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 person1 = Person("xiaoming", 18, "student");
var person2 = Person("xiaohong", 25, "teacher");
上述代码能够根据接受的参数来构建一个包含必要信息的Person对象,可以无数次的调用这个函数,它都会返回一个包含三个属性一个方法的对象。但是工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。即在使用instanceof 关键字时返回的是 Object,而不是Person或者其他。示例代码如下
console.log(person1 instanceof Object);//true
console.log(person1 instanceof Person);//false
于是一种新的模式出现了。
使用构造函数模式创建对象
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
alert(this.name)
}
}
var person1 = new Person("xiaoming", 18, "student");
var person2 = new Person("xiaohong", 25, "teacher");
创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下四个步骤:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
- 执行都在函数中的代码(为这个新对象添加属性)
- 返回新对象
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型;这正是构造函数模式优于工厂模式的地方。
alert(person1 instanceof Object);//true
alert(person1 instanceof Person);//true
alert(person2 instanceof Object);//true
alert(person2 instanceof Person);//true
但是构造函数模式虽然好用,但是也有缺点,例如,每一个方法都要在每个实例上重新创建一遍。在上个例子中,person1和person2都有一个名为sayName的方法,但那两个方法不是同一个Function的实例。于是可以在这个方法上进行修改,示例如下:
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName=sayName;
}
function sayName () {
alert(this.name)
}
我们把sayName函数的定义转移到构造函数外部,这样person1和person2对象就共享了在全局作用域中定义的同一个sayName函数。但是这种方法不符合全局作用域函数的使用场景,而且当需要多个实例函数的时候就要定义多个全局函数,自定义的引用类型就丝毫没有封装性可言。于是我们可以通过原型模式来解决。
使用原型模式创建函数对象,示例如下
function Person() {
}
Person.prototype.name = "xiaoming";
Person.prototype.age = 18;
Person.prototype.job = "student";
Person.prototype.sayName = function () {
alert(this.name);
};
var person1 = new Person();
person1.sayName();//"xiaoming"
var person2 = new Person();
person2.sayName();//"xiaoming"
alert(person1.sayName == person2.sayName);//true
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途就是包含可以有特定类型的所有实例共享的属性和方法。原型模式虽然解决了上述问题但是它省略了为构造函数初始化传参这一环节,结果所有实例在默认情况下都将取得相同的属性值。示例代码如下
Person.prototype = {
constructor: Person,
name: "xiaoming",
age: 18,
job: "student",
friends: ["xiaohong", "xiaolan"],
sayName: function () {
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("joker");
alert(person1.friends);//"xiaohong,xiaolan,joker"
alert(person2.friends);//"xiaohong,xiaolan,joker"
alert(person1.friends === person2.friends);//true
实例一般都是要有属于自己的全部属性的,上述案例中对person1对象friend属性进行改变person2也会发生改变,所以我们很少单独使用原型模式。而是组合使用构造函数模式和原型模式。
组合使用构造函数模式和原型模式创建对象,示例代码如下
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ["xiaohong", "xiaolan"]
}
Person.prototype = {
constructor: Person,
sayName: function () {
alert(this.name)
}
}
var person1 = new Person("xiaoming", 18, "student");
var person2 = new Person("joker", 25, "basketball player");
person1.friends.push("james");
alert(person1.friends);//"xiaohong,xiaolan,joker"
alert(person2.friends);//"xiaohong,xiaolan"
alert(person1.friends === person2.friends);//false
alert(person1.sayName === person2.sayName);//true
这种构造函数与原型混成的模式是目前在ecmasript中使用最广泛,认同度最高的一种创建自定义类型的方法。
使用动态原型模式自定义引用类型
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);
};
}
}
var person = new Person("xiaoming", 18, "student");
person.sayName();
这个案例中,只在sayName方法不存在的情况下,才会将它添加到原型中。这个方法只会初次调用构造函数时才会执行。此后,原型已经完成初始化,不需要再做什么修改了。这里对原型所作的修改,能够立即在所有实例中得到反映。