<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">js是一门面象对象的语言,虽然Object 构造函数(直接new Object())或对象字面量(直接用{})都可以用来创建单个对象,但是会产生大量的重复代码,下面总结了几种创建对象的方法。</span>
1 工厂模式
例:
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("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
考虑到在 ECMAScript 中无法创建类,就用函数来封装以特定接口创建对象的细节,解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型) 。随着 JavaScript的发展,又一个新模式出现了。
2.构造函数模式(没有显式地创建对象;直接将属性和方法赋给了 this 对象;没有 return 语句。)
构造函数可用来创建特定类型的对象,像 Object 和 Array 这样的原生构造函数,在运行时会自动出现在执行环境中。
例:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。
person1 和 person2 分别保存着 Person 的一个不同的实例。这两个对象都有一个 constructor (构造函数)属性,该属性指向 Person
alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true
创建自定义的构造函数意味着将来可以将它的实例作为一种特定的类型(Person类型),优于工厂模式。
缺点:每一个实例对象都会将构造函数里的所有方法和属性都分配内存,如下所示,相同的属性实际上是在不同的内存,相同的函数,每次都需要重新创建 Function 实例,况且有 this 对象在,根本不用在执行代码前就把函数绑定到特定对象上面
alert(person1.sayName == person2.sayName); //false
3.原型模式
Javascript规定,每创建了一个新函数都有一个prototype属性,指向另一个对象(即原型对象)。这个对象的所有属性和方法,都会被构造函数的实例继承。在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。
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();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
上图 展示了 Person 构造函数、 Person 的原型属性以及 Person 现有的两个实例之间的关系
Prototype模式的验证方法
1.isPrototypeOf()判断,某个proptotype对象和某个实例之间的关系
alert(Person.prototype.isPrototypeOf(person1)); //true
2. hasOwnProperty()判断某一个属性到底是本地属性,还是继承自prototype对象的属性。只在给定属性存在于对象实例中时,才会返回 true
alert(person1 .hasOwnProperty("name")); //false (hasPrototypeProperty(person1, "name")//true与前面的方法返回值相反)
person1.name = "Greg";
alert(person1.name); //"Greg"——来自实例,不等同于prototype里面的name值
alert(person1.hasOwnProperty("name")); //true
3.ECMAScript 5 增加了一个新方法,叫 Object.getPrototypeOf() ,在所有支持的实现中,这个方法返回一个对象的原型。例如:
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
4. in运算符可以用来判断,某个实例是否含有某个属性,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性
alert("name" in person1); // true
5. Object.keys() 和 Object.getOwnProperty-Names() 方法都可以用来替代 for-in 循环。 支持这两个方法的浏览器有 IE9+、 Firefox 4+、 Safari 5+、 Opera12+和 Chrome。
要取得对象上所有可枚举的实例属性,可以使用 ECMAScript 5 的 Object.keys() 方法。这个方法
接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组
var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName"
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys); //"name,age"
ECMAScript 5 也将 constructor 和 prototype 属性的 [[Enumerable]] 特性设置为 false,如果你想要得到所有实例属性, 无论它是否可枚举, 都可以使用 Object.getOwnPropertyNames()方法。
var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys); //"constructor,name,age,job,sayName"
变量搜索原理:每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这
个属性,则返回该属性的值。
更简单的原型语法:
function Person(){
}
Person.prototype = {
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
Person.prototype 设置为等于一个以对象字面量形式创建的新对象,每创建一个函数,就会同时创建它的 prototype 对象,这个对象也会自动获得 constructor 属性。而我们在
这里使用的语法,本质上完全重写了默认的 prototype 对象,因此 constructor 属性也就变成了新对象的 constructor 属性 (指向 Object 构造函数) , 不再指向 Person 函数。
var friend = new Person();
alert(friend instanceof Object); //true
alert(friend instanceof Person); //true
alert(friend.constructor == Person); //false
alert(friend.constructor == Object); //true
用 instanceof 操作符测试 Object 和 Person 仍然返回 true ,但 constructor 属性则等于 Object 而不等于 Person 了,
注意,以这种方式重设 constructor 属性会导致它的 [[Enumerable]] 特性被设置为 true。默认情况下,原生的 constructor 属性是不可枚举的,因此如果你使用兼容 ECMAScript 5 的 JavaScript 引擎,可以试一试 Object.defineProperty() 。
function Person(){
}
Person.prototype = {
<strong>constructor : Person,</strong>
name : "Nicholas",
age : 29,
job: "Software Engineer",
sayName : function () {
alert(this.name);
}
};
// 重设构造函数,只适用于 ECMAScript 5 兼容的浏览器
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
原型的动态性
尽管可以随时为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来,但如果是重写整个原型对象,调用构造函数时会为实例添加一个指向最初原型的
[[Prototype]] 指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。请记住:实例中的指针仅指向原型,而不指向构造函数
function Person(){
}
var friend = new Person();
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
sayName : function () {
alert(this.name);
}
};
friend.sayName(); //error
原型对象的问题
对于包含引用类型值的属性来说,通过一个实例来修改属性时,其它实例也会受到影响,他们共享原型里面的值。
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(){
alert(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
是目前在 ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法。可以说,这是用来定义引用类型的一种默认模式