阅读目录
前言
创建对象
工厂模式
构造函数模式
原型模式
组合使用构造函数模式和原型模式
结束语
前言
严格来讲,JavaScript 并不是一门纯面向对象的编程语言,他并没有提供类,接口和抽象,以及访问权限操作符的概念。没办法像C#、Java、C++那样依托原有的特性容易的实现面向对象的特点。不过有劣势也有会优势,毕竟老天是公平的,ECMA小组在制定Javascript时,没有提供这些特性,不过也提供了Javascript动态增减属性的功能,我们可以利用这些功能模拟出真正面向对象语言中的部分面向对象功能。ECMA-262对对象的定义:“无序属性的集合,其属性可以包含基本值、对象或者函数。”这就等于说对象是一组没有特定顺序的值,对象的每个属性或方法都有一个名字,而每个名字都会映射到一个值。其实可以想象成一个散列表,名值对之类的东西。Javascript的对象跟C#中的对象是一样的,也是引用类型,(至于值类型和引用类型在内存中分配的情况可以参考我的另一篇文章:运行时的相互关系),这个类型可以是javascript定义的原型类型,也可以是开发人员自定义的类型。
创建对象
在javascript中创建对象最简单的方式如:
var obj =new Object();
obj.author = "jwy";
obj.age = 23;
obj.display = function(){
document.write("author is " + this.author +",age is" + this.age);
};
这里创建了一个名为obj的对象,并添加了两个属性:author和age, 和一个display方法。虽然这种方式简单,但是如果使用这种方式创建对象的话,会产生大量的重复代码,每次创建一个对象,都要重新写过这么多代码。
工厂模式
工厂模式在java、C# 或其他语言中都是一种广泛应用的设计模式,抽象了创建具体对象的过程,但是在javascript中没有类的概念,所以我们可以这么模拟实现:
function createObj(author,age){
var o = new Object();
o.author = author;
o.age = age;
o.display = function(){
};
return o;
}
var o1 =createObj("Tom",22);
var o2 =createObj("Jack",23);
可以任意调用这个函数,每次都会返回一个包含制定属性和方法的对象。这个方式解决了上面创建多个类似对象的问题,但是没有解决识别一个对象类型的问题(识别对象类型在大部分地方都很有用)。
构造函数模式
在javascript中,函数只是一个变量名而已,是一个引用类型,通过new操作符可以用来创建特定类型的对象。
function Customer(name,age,tele){
this.name = name;
this.age = age;
this.tele = tele;
this.display = function(){
document.write(...);
}
}
var c1 = new Customer("Evada",2,87517522);
var c2 = new Customer("35",15,87517522);
在上面代码中,看起来跟工厂模式的方法有点雷同,但还是有些许不同之处:
1、没有显式创建对象;
2、直接将方法和属性赋给了this对象;
3、没有return语句;
说明:this引用的是函数据以执行操作的对象,或者也可以说,this是函数在执行时所处的作用域。
如果是用来创建对象的,函数名一般遵循Pascal命名方式,如果是普通函数,或者说是对象的方法,则一般遵循Camel命名方式
要创建Customer的实例,就必须要使用new操作符,执行步骤如下:
1、 创建一个新对象;
2、 将构造函数的作用域赋给新对象(此时this就是当前的新对象了);
3、 执行构造函数中的代码(添加属性和方法)
4、 然后返回新对象,内存中表现就是在堆中给对象分配的地址,返回给在栈中定义的变量。此时该变量就引用了在内存中的对象了。
构造函数也是普通函数,任何函数只要通过new操作符调用,那它就可以作为构造函数,而任何函数,如果不通过new操作符调用,那它跟普通函数也不会有什么两样。
不足之处
每个方法都要在每个实例上重新创建一遍,例如,每个实例都有一个display的方法,但是这两个方法不是同一个Function的实例,函数也是一个对象,这样会在内存中重复创建很多相同的方法,所以可以像下面这样解决这个问题:
function Customer(name,age,tele){
this.name = name;
this.age = age;
this.tele = tele;
this.display = display;
}
function display(){
document.write(...);
}
var c1 = new Customer("Evada",2,87517522);
var c2 = new Customer("35",15,87517522);
在上面代码中,把display函数的定义移到了构造函数外面,然后在构造函数内部将display属性设置成全局的display函数。这样就只在内存中创建了一个display函数,但是问题又来了,这样定义如果需要很多方法,那势必要在全局定义很多函数,但是却只能为某个构造函数所产生的各个实例所调用,这样不仅代码难看,而且毫无封装性可言。
原型模式
在javascript中,每个函数都有一个prototype(原型)属性,这个属性是一个对象,它是包含可以有特定类型的所有实例共享的属性和方法。prototype就是通过调用构造函数而创建的那个对象的原型对象。好处:可以让所有对象实例共享它所包含的属性和方法。所以大可不必在构造函数中定义对象的属性和方法,可以直接添加到原型中。
function Customer(){
}
Customer.prototype.name =’jwy’;
Customer.prototype.age = 23;
Customer.prototype.tele = ‘87517522’;
Customer.prototype.display = function(){
alert(this.name);
}
var c1 = new Customer();
c1.display(); //’jwy’
var c2 = new Customer();
c2.display();
alert(c1.display ==c2.display )//true
这里将方法和属性都添加到了prototype对象中,构造函数中没有代码。但是新创建的对象还会具有相同的属性和方法。而且这些属性和方法是所有实例共享的,不像构造函数模式那样,函数是一个实例一个实例特有的,虽然实现功能一样。
原型不只在构造函数和创建对象中有重要作用,在javascript继承方面也有举足轻重的地位。
什么是原型
每个函数都有一个prototype属性(也是一个对象),这个对象会自动获得一个constructor属性,这个constructor是一个指针,指向prototype属性所属函数。如:Customer.prototype.constructor à Customer ,用firebug 看下就清楚了。
默认情况下,原型属性只会取得constructor属性,其他实例方法是从Object继承而来的,(点击了解Object的方法),当实例化一个对象后,该对象内部将包含一个指针,指向构造函数的原型属性,大部分实现中,这个指针的名字是__proto__,火狐开放了这个属性,所以可以访问,同样可以通过firebug查看(用Chrome查看会更直观),
这个__proto__是连接实例和构造函数的原型属性,而不是实例和构造函数。下面用图片展示一下实例和构造函数以及原型之间的关系,如图:
Customer.prototype指向了原型对象,Customer.prototype.constructor指向了Customer。原型对象中不止包含constructor还包括后来添加的属性。Customer每个实例都包含一个内部(__proto__)属性,该属性指向了Customer.prototype。如果调用方法和属性,在构造函数中是没有的,但是可以通过查找对象属性的过程来实现。
在IE等无法访问到内部__proto__属性的浏览器中,可以通过isPrototypeOf方法来确定对象是否存在这种关系,也就是检测实例是否指向构造函数的prototype属性。如果是就返回true。
alert(Customer.prototype.isPrototypeOf(c1));//true
对象属性搜索过程如下:
1、 给定属性名称,先从对象实例本身开始,如果在实例中找到了该属性,则返回。
2、 没有找到,则继续搜索指针指向的原型对象,在原型中查找,如果找到则返回
如果实例属性和原型属性同名,则实例属性会屏蔽原型属性。
从Object中继承过来的hasOwnProperty方法可以检测一个属性是否在实例中。只有在实例中才会返回true。
alert(c1.hasOwnProperty(‘name’));//false,name属性在原型中
c1.name=”Scott”;//添加了实例属性
alert(c1.hasOwnProperty(‘name’));//true,name属性在实例中
我们还可以自己实现一个方法,用来检测是属性是在原型中的。
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name) && (name in object);
}
前半部分,确定属性不在实例中,后半部分,确定属性是实例所拥有的,则可推断出属性实在原型中的。
简化原型语法
我们在给原型添加属性和方法时每次都要写Customer.prototype,多余代码,很麻烦,更简单的一个做法是,通过字面量重写原型对象。
function Customer(){
}
Customer.prototype={
aame:’jwy’,
age:23,
tele:’87517522’,
display:function(){
document.write(…);
}
};
这里将原型设置为等于一个以对象字面量形式创建的新对象,结果是相同的,但是这里constructor不再指向Customer了,因为这里完全重写了prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object函数),不再指向Person函数。不过可以显式设置constructor的值,使它重新指向Customer。
如果先声明一个实例,再重写prototype对象,结果就会跟所想的都不一样,如下:
function Customer(){}
var c1=new Customer();
Customer.prototype={
Constructor:Customer,
name:’jwy’,
age:23,
…
};
c1.display();//error,undefine
这里先声明一个实例,然后直接重写了整个prototype对象,实例化实例时,实例有一个指向最初原型的指针,__proto__,但是后面重写了整个原型,现在实例的__proto__还是依然指向最初的原型,后面重写的原型所添加的属性和方法,在当前实例中找不到,所以导致方法调用失败。如图所示:
原型对象存在的问题
原型模式少了传参,所以每个实例都是相同的属性值,但是如果属性值是引用类型的,则每个实例不仅共享相同的属性值,而且任意一个对象更改了,则其他对象也会收到更改后的值,因为引用类型的值是在堆中,属性变量只是一个指针指向它而已。
function Customer(){}
var c1=new Customer();
Customer.prototype={
Constructor:Customer,
name:’jwy’,
age:23,
bank:[1,2,4],
…
};
var c1=new Customer();
var c2=new Customer();
c1.bank.push(33);
alert(c2.bank);//1,2,4,33
结合使用构造函数和原型模式
结合两种模式,可以实现这样的效果:构造函数定义实例属性,原型模式定义实例共享的属性和方法。
fnction Customer(name,age,tele){
this.name=name;
this.age=age;
this.tele=tele;
}
Customer.prototype={
constructor:Customer,
dsplay:function(){
document.write(…);
}
};
var c1=new Customer(‘jwy’,23,’87517522’);
var c2=new Customer(‘Scott,23,’1825081’);
这种模式,是目前应用得最广泛的。
结束语
这里学习了javascript中对象的创建,通过分析各种不同的构建方法,权衡利弊。
如果觉得不错的话,请点击下推荐,(*^__^*) !!
转载请注明出处:http://www.cnblogs.com/enshjiang/archive/2012/03/22/2411006.html