ECMA-262把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”严格来讲,这就相当于说对象是一组没有特定顺序的值。对象的每个属性或者方法都有一个名字,而每个名字都映射到一个值。正因为这样,我们可以把ECMAScript的对象想像成散列表:无非就是一组名值对,其中值可以是数据或函数。
创建对象的方式多种多样,理解和掌握每种创建对象的方法对于学会JavaScript这门语言是十分重要的。
一、使用Object构造函数
var person = new Object();//注意此处Object的首字母一定要大写 person.name = "Zhangsan"; person.age = 21; person.sex = "male"; person.sayName = function(){ alert(this.name); }
上面的例子创建了一个名为person的对象,并给它添加了三个属性(name,age,sex)和一个方法(sayName())。
二、使用字面量创建对象
var person = { name: "Zhangsan", age: 21, sex: "male", sayName: function(){ alert(this.name); } }
使用字面量创建对象的方式跟上面使用Object构造函数创建对象的方式差不多,就像开学的时候报名一样,每来一个同学都要写一遍姓名、年龄、性别这几个字,很明显效率什么低,因为这种方法会产生大量的重复代码。为了解决这个问题,人们开始使用工厂模式的方式来创建对象。
三、工厂模式
工厂模式,顾名思义就是像工厂一样,从流水线上出来的对象都有一样的属性和方法。考虑到ECMAScript中无法创建类,开发人员就发明一种函数,用函数来封装以特定接口创建对象的细节,如下面的例子:
function createPerson(name,age,sex){ var obj = new Object(); obj.name = name; obj.age = age; obj.sex = sex; obj.sayName = function(){ alert(this.name); } return obj; } var person1 = createPerson("Zhangsan",19,"male"); var person2 = createPerson("Lisi",18,"female");
工厂模式看起来解决了创建多个相似对象的问题,但却没有解决对象识别的问题,即怎样知道一个对象的类型。随着JavaScript的发展,又有一个新的模式出现了。
四、构造函数模式
我们知道,像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中,此外,我们也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。举个栗子如下:
function Person(name,age,sex){ this.name = name; this.age = age; this.sex = sex; this.sayName = function(){ alert(this.name); } } var person1 = new Person("Zhangsan",19,"male"); var person2 = new Person("Lisi",18,"female");
在这个例子中,Person函数取代了上面的createPerson()函数。我们注意到,Person()中的代码除了与createPerson()中相同的部分外,还存在以下不同之处:
1、没有显示地创建对象
2、直接将属性和方法赋给了this对象
3、没有return语句
此外,还应该注意到函数名为Person使用的是首字母大写P。按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。
要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际会经历以下四个步骤:
1、创建一个新对象
2、将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
3、执行构造函数中的代码(为这个新对象添加属性)
4、返回新对象
五、原型模式
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以有特定类型的所有实例共享的属性和方法。
function Person(){} Person.prototype.name = "zhangsan"; Person.prototype.age = 19; Person.prototype.sex = "male"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = "lisi"; alert(person1.name);//"lisi"--来自示例 alert(person2.name);//"zhangsan"--来自原型 delete person1.name; alert(person1.name);//"zhangsan "--来自原型
原型模式也不是没有缺点。首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。虽然这会在某种程度上带来了一些不便,但还不是原型的最大问题,原型模式的最大问题是由其共享的本性所导致的。这里举个例子说明:
function Person(name,age,sex){ } Person.prototype = { constructor : Person, name : "zhangsan", age: 19, sex: "male", friends: ["Bob","Jams"], sayName: function(){ alert(this.name); } } var person1 = new Person(); var person2 = new Person(); person1.friends.push("Obama"); alert(person1.friends);//Bob,Jams,Obama alert(person2.friends);//Bob,Jams,Obama alert(person1.friends === person2.friends);//true
六、组合使用构造函数模式和原型模式
创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。另外,这种混合模式还支持向构造函数中传递参数;可谓集两种模式之长。
function Person(name,age,sex){ this.name = name; this.age = age; this.sex = sex; this.friends = ["Bob","Jams"]; } Person.prototype = { constructor : Person, sayName: function(){ alert(this.name); } } var person1 = new Person("zhangsan",19,"male"); var person2 = new Person("lisi",18,"female"); person1.friends.push("Obama"); alert(person1.friends);//Bob,Jams,Obama alert(person2.friends);//Bob,Jams alert(person1.friends === person2.friends);//false alert(person1.sayName === person2.sayName);//true
在这个例子中,实例属性都是在构造函数中定义的,而所有实例共享的属性constructor和方法sayName()则是在原型中定义的。而修改了person1.friends,并不会像上个例子中那样影响到person2.friends。
七、动态原型模式
这个模式的好处在于看起来更像传统的面向对象编程,具有更好的封装性,因为在构造函数里完成了对原型创建。
function Person(name,age,sex){ this.name = name; this.age = age; this.sex = sex; if(typeof this.sayName != "function"){ Person.prototype.sayName = function(){ alert(this.name); } } } var person = new Person("zhangsan",19,"male"); person.sayName();
除了以上其中方式之外,还有寄生构造函数模式和稳妥构造函数模式。实际上这么多种方法中,第六种和第七种是最常用的,我们需根据实际情况使用不同的方法,做到随机应变才能更加高效率的工作。
本文出自 “Fcheng” 博客,请务必保留此出处http://fcheng.blog.51cto.com/10644114/1839597
原文:http://fcheng.blog.51cto.com/10644114/1839597