目录
JS对象创建的几种方式
1. 使用对象字面量表示法
2. 使用new操作符后跟Object构造函数
3. 工厂模式
4. 构造函数模式(自定义的构造函数)
5. 原型模式
6. 组合使用构造函数模式和原型模式(最常用)
7. 动态原型模式
8. 寄生构造函数模式
9. 稳妥构造函数模式
JS对象创建的几种方式
1. 使用对象字面量表示法
var person = {
name: "Nike",
age: 29
};
对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。
2. 使用new操作符后跟Object构造函数
var person = new Object();
person.name = "Nike";
person.age = 29;
这行代码创建了Object引用类型的一个新实例,然后把实例保存在变量Person中。
虽然object构造函数或者对象字面量都可以创建单个对象,但这些方式有个明显的缺点:使用了同一个接口创建很多对象,会产生大量的重复代码,就是如果你有100个对象,那你要输入100次很多相同的代码。为了解决这个问题,开始使用工厂模式的一种变体。
3. 工厂模式
这种模式抽象了创建具体对象的过程。考虑到ECMAScript中无法创建类,故发明了一种函数,用函数来封装以特定的接口创建对象的细节。
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("Nike", 29, "Software Engineer");
var person1 = createPerson("Cindy", 25, "Doctor");
函数createPerson()能够根据接受的参数来构建一个包含所有必要信息的Person对象。可以无数次地调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象,但我们无法判断返回的对象究竟是一个什么样的类型。
因而工厂模式虽然解决了创建多个相似对象的问题, 但却没有解决对象识别的问题(怎样知道一个对象的类型)
4. 构造函数模式(自定义的构造函数)
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
alert(this.name);
};
}
var person1 = new Person("Nike", 29, "Software Engineer");
var person2 = new Person("Cindy", 25, "Doctor");
对比工厂模式,存在以下不同之处:
- 没有显示地创建对象
- 直接将属性和方法赋给了this对象
- 没有return语句
要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数经历了四个步骤:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
- 执行构造函数中的代码(为这个新对象添加属性)
- 返回新对象
person1和person2分别保存着Person的一个不同的实例。
对于检测对象类型,我们应该使用instanceof操作符,在这个例子中创建的所有对象既是Object的实例,同时也是Person的实例
alert(person1 instanceof Object); //ture
alert(person1 instanceof Person); //ture
alert(person2 instanceof Object); //ture
alert(person2 instanceof Object); //ture
构造函数与其他函数的唯一区别在于:
调用的方式不同:构造函数需要通过new操作符来调用,普通函数不需要通过new操作符来调用。
构造函数的主要问题:
每个方法都要在每个实例上重新创建一遍。
虽然person1和person2都有一个名为sayName()的方法,但两个方法不是同一个Function的实例。等价于<=>
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function() {alert(this.name)};
}
解决:把函数定义转移到构造函数外面
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
this.sayName = function() {
alert(this.name);
};
var person1 = new Person("Nike", 29, "Software Engineer");
var person2 = new Person("Cindy", 25, "Doctor");
5. 原型模式
function Person() {}
Person.prototype.name = "Nike";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
alert(this.name);
}
var person1 = new Person();
person1.sayName(); //Nike
var person2 = new Person();
person2.sayName(); //Nike
alert(person1.sayName == person2.sayName); //true
与构造函数模式不同的是,新对象的这些属性和方法是由所有实例共享的。
简单的写法:前面例子中每添加一个属性和方法就要敲一遍Person.prototype。为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象
function Person() {}
Person.prototype = {
name: "Nike",
age: 29,
job: "Software Engineer",
sayName: function() {
alert(this.name);
}
};
原型对象的问题
1.省略了为构造函数传递初始化参数这一环节,使得所有实例在默认情况下都将取得不同的属性值
解决:为对象实例添加一个同名属性,可以屏蔽原型中的对应属性
function Person() {}
Person.prototype.name = 'Nike';
Person.prototype.age = 20;
Person.prototype.jbo = 'teacher';
Person.prototype.sayName = function() {
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = 'Greg';
alert(person1.name); //'Greg' --来自实例
alert(person2.name); //'Nike' --来自原型
2.若包含引用类型的属性时,就会重写原型
function Person() {}
Person.prototype = {
name: "Nike",
age: 29,
job: "Software Engineer",
friends: ["shelby", "court"],
sayName: function() {
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Greg");
alert(person1.friends); //"shelby","court,Greg"
alert(person2.friends); //"shelby","court,Greg"
alert(person1.friends === person2.friends); //true
解决:这时候我们就可以使用构造函数模式与原型模式结合的方式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
6. 组合使用构造函数模式和原型模式(最常用)
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本. 但同时又共享着对方法的引用,最大限度地节省了内存。
//构造函数模式定义实例属性
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
//原型模式定义方法和共享的属性
Person.prototype = {
constructor: Person,
sayName: function() {
alert(this.name);
}
}
var person1 = new Person("Nike", 29, "Software Engineer");
var person2 = new Person("Cindy", 25, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
在这个例子中,实例属性都是在构造函数中定义的,而由所有实例共享的属性constructor和方法sayName()则是在原型中定义的。而修改了personl.friends(向其中添加一个新字符串),并不会影响到 person2.friends,因为它们分别引用了不同的数组。
7. 动态原型模式
有其他00语言经验的开发人员在看到独立的构造函数和原型时,很可能会感到非常困惑。动态原型模式正是致力于解决这个问题的一个方案 ,它把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点
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 person1 = new Person("Nike", 29, "Software Engineer");
person1.sayName(); //Nike
这里只在 sayName()方法不存在的情况下,才会将它添加到原型中。这段代码只会在初次调用构造函数时才会执行。此后,原型已经完成初始化 ,不需要再做什么修改了。不过要记住,这里对原型所做的修改,能够立即在所有实例中得到反映。因此,这种方法确实可以说非常完美。其中,if语句检查的可以是初始化之后应该存在的任何属性或方法——不必用一大堆if语句检查每个属性和每个方法;
8. 寄生构造函数模式
通常 ,在前述的几种模式都不适用的情况下,可以使用寄生(parasitic)构造函数模式。这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;但从表面上看,这个函数又很像是典型的构造函数。
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 = new Person("Nike", 29, "Software Engineer");
person1.sayName(); //Nike
在这个例子中,Person 函数创建了一个新对象,并以相应的属性和方法初始化该对象,然后又返回了这个对象。除了使用 new 操作符并把使用的包装函数叫做构造两数之外,这个模式跟工厂模式其实是一模一样的。构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加一个 return 语句,可以重写调用构造函数时返回的值。
这个模式可以在特殊的情况下用来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊数组。由于不能直接修改 Array 构造函数,因此可以使用这个模式。
function SpecialArray() {
//创建数组
var array = new Array();
//添加属性
array.push.apply(array, arguments);
//添加方法
array.toPipedString = function() {
return this.join("|");
};
//返回数组
return array;
}
var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString()); //red|blue|green
在这个例子中 ,我们创建了一个名叫 SpecialArray 的构造函数。在这个函数内部,首先创建了 一个数组 ,然后 push()方法(用构造函数接收到的所有参数)初始化了数组的值。随后,又给数组实例添加了一个 toPipedString()方法,该方法返回以竖线分割的数组值。最后,将数组以函数值的形式返回。接着 ,我们调用了 SpecialArray 构造函数 ,向其中传入了用于初始化数组的值 ,此后又调用了 toPipedString()方法。
关于寄生掏造函数模式,有一点需要说明:首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此,不能依赖 instanceof 操作符来确定对象类型。由于存在上述问题,我们建议在可以使用其他模式的情况下,不要使用这种模式。
9. 稳妥构造函数模式
所谓稳妥对象.指的是没有公共属性,而且其方法也不引用 this 的对象。稳妥对象最适合在 一些安全的环境中( 这些环境中会禁止使用 this 和 new ) 或者在防止数据被其他应用程序(如 Mashup 程序)改动时使用。稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的 实例方法不引用 this;二是不使用 new 操作符调用构造函数。
function Person(name, age, job) {
//创建要返回的对象
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function() {
alert(name);
};
//返回对象
return o;
}
注意,在以这种模式创建的对象中,除了使用 sayName()方法之外,没有其他办法访问name 的值。
var person1 = new Person("Nike", 29, "Software Engineer");
person1.sayName(); //Nike
这样.变量 person 中保存的是一个稳妥对象,而除了调用 sayName()方法外,没有别的方式可以访问其数据成员。即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传入到构造函数中的原始数据。稳妥构造函数模式提供的这种安全性,使得它非常适合在某些安全执行环境——例如,ADsafe(www.adsafe.org)和Caja(http://code.google.com/p/google-caja/) 提供的环境——下使用
完结!