javaScript创建对象
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
javaScript创建对象
⼀、对象
ECMA-262把对象定义为:⽆序属性的集合,其属性可以包含基本值,对象或者函数。
所以js中对象就是⼀组键值对。
⾯向对象的语⾔中,都是通过类的来创建任意多个具有相同属性和⽅法的对象实例的。
但是js中没有类的概念,接下来我先通过⼀个例⼦来阐述js中没有“类”的概念蕴含的哲学。
这点会让初学者很困惑,但是也正因为放下了“类”的概念,js对象才有了其他编程语⾔没有的活⼒。
事实上js中对象的“类”是从⽆到有,⼜不断演化,最终消失于⽆形之中。
举例:⼩蝌蚪找妈妈的故事,⼩蝌蚪在其⾃⾝类型不断演化的过程中,逐渐变成了和妈妈⼀样的“类”。
代码:
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<head>
<title>⼩蝌蚪找妈妈</title>
</head>
<body>
<script>
var life={};//光溜溜的⽣命
for(life.age=1;life.age<=3;life.age++)
{
switch (life.age)
{
case 1:
life.body="卵细胞";//增加body属性
life.say=function(){
console.log(this.age+this.body);
};//新建say⽅法
break;
case 2:
life.tail="尾巴";//增加tail属性
life.gill="腮";//增加gail属性
life.body="蝌蚪";
life.say=function(){
console.log(this.age+this.body+'-'+this.tail+","+this.gill);
};
break;
case 3:
delete life.tail;//删除tail属性
delete life.gill;//删除gill属性
life.legs="四条腿";//增加legs属性
life.lung="肺";//增加lung属性
life.body="青蛙";
life.say=function(){
console.log(this.age+this.body+"-"+this.legs+","+this.lung);
};
break;
}
life.say();//调⽤say⽅法,每次逻辑都会发⽣动态改变
}
</script>
</body>
</html>
效果:
(1)js程序⼀开始产⽣了⼀个⽣命对象life,life诞⽣时只是个光溜溜的⽣命对象,没有任何属性和⽅法。
(2)第⼀次⽣命进化,life对象有了⾝体属性body,并有了⼀个say⽅法,看起来是⼀个“卵细胞”。
(3)第⼆次⽣命进化,它⼜长出了“尾巴”和“腮”,有了tail和gill属性,显⽰它是⼀个“蝌蚪”。
(4)第三次⽣命进化,它的tail和gill消失了,但⼜长出了“四条腿”和“肺”,有了legs和lung属性,从⽽最终变成了“青蛙”。
所以说,对象的“类”是从⽆到有,⼜不断演化,最终消失于⽆形中。
“类”确实可以帮助我们对世界分类,但是我们思想不能被“类”束缚,如果⽣命开始就被规定了固定的“类”,就⽆法演化,蝌蚪就变不成青蛙。
所以js中没有“类”,类已化为⽆形与对象融为⼀体。
这样也更加贴近现实世界,不是吗?
每个对象都是基于⼀个引⽤类型创建的,这个引⽤类型可以是原⽣类型也可以是开发⼈员定义的类型。
js没有类,js对象就是⼀组键值对,接下来看看js中9种创建对象的⽅式。
js创建对象的⽅法的产⽣是⼀个迭代的过程,因为已有⽅法的缺陷催⽣出新的⽅法。
⾸先是早期js程序员经常使⽤也是最简单的⽅法——通
过Objec构造函数创建对象。
⼆、通过Object构造函数创建对象
代码:
<script>
var person=new Object();
person.nam="lxy";
person.age="22";
person.job="Software Engineer";
person.sayName= function () {
alert(this.nam);
}
person.sayName();
</script>
优点:简单
三、通过字⾯量创建对象
早期JS开发⼈员经常使⽤new Object()创建对象,⼏年后对象字⾯量称为创建对象的⾸选模式。
代码:
<script>
var person={
name:"lxy",
age:22,
job:"Software Engineer",
sayName:function(){
alert();
}
};
person.sayName();
</script>
要注意⼀点就是每声明⼀个键值对后⾯标点是“,”。
这些属性在创建时都带有⼀些特征值(characteristic),JavaScript通过这些特征值来定义它们的⾏为。
对象字⾯量相对于Object构造函数代码量少了⼀点点。
但是这2种⽅法通过⼀个接⼝创建很多对象,会产⽣⼤量重复代码。
Don't Repeat Yourself!我们需要对重复的代码进⾏抽象。
⼯⼚模式就是在这种情况下出现的。
四、⼯⼚模式
⼯⼚模式是软件⼯程领域⼀种⼴为⼈知的设计模式,这种模式抽象了创建具体对象的过程。
通过类来创建多个实例必然可以减少代码重复,但是ECMAScript中⽆法创建类,所以就⽤函数来封装以特定接⼝创建对象的细节。
代码:
<script>
function createPerson(name ,age,job){
var o=new Object();
=name;
o.age=age;
o.job=job;
o.sayName=function(){
alert();
}
return o;
}
var lxy=createPerson("lxy",22,"Software Engineer");
var strangerA=createPerson("strangerA",24,"Doctor");
lxy.sayName();
strangerA.sayName();
</script>
⼯⼚模式减少了重复代码,但是不能够识别对象,所有实例都是object类型的。
这时构造函数模式就出现了。
五、构造函数模式
像Object和Array这样的原⽣构造函数,在运⾏时会⾃动出现在执⾏环境中。
我们可以创建⾃定义构造函数,从⽽创建特定类型的对象。
代码:
<script>
function Person(name ,age,job){
=name;
this.age=age;
this.job=job;
this.sayName=function(){
alert();
}
}
var lxy=new Person("lxy",22,"Software Engineer");
var strangerA=new Person("strangerA",24,"Doctor");
lxy.sayName();
strangerA.sayName();
</script>
构造函数中⾸字母⼤写,⽽⾮构造函数⾸字母⼩写作为区别。
通过new操作符来创建Person实例,这样创建的实例都有⼀个constractor(构造函数)属性,该属性指向Person。
alert(lxy.constructor==Person);//true
alert(strangerA.constructor==Person);//true
lxy和strangeA是Person的实例,同时也是Object的实例。
因为所有的对象都继承⾃Object。
创建⾃定义构造函数意味着将来可以将它的实例标识为⼀种特定的类型;⽽这正是构造函数胜过⼯⼚模式的地⽅。
构造函数也是函数,所以语法上可以像普通函数⼀样去⽤,但是可以⽤并不代表应该⽤,还是以构造函数的⽅式⽤更合理。
构造函数的问题是,同⼀构造函数的不同实例的相同⽅法是不⼀样的。
alert(lxy.sayName==strangerA.sayName());//false
这个问题很好理解,因为js中函数就是对象,每定义⼀个函数,也就是实例化了⼀个对象。
从代码的⾓度可能理解的更深刻:
this.sayName=function(){alert()};与
this.sayName=new Function(alert());是等价的。
所以使⽤构造函数创建对象,每个⽅法在每个实例上都要重新实现⼀遍,⼀是耗资源,⼆是创建两个或者多个完成同样任务的Function没有必要,三是有this在,没必要在代码执⾏前就把函数绑定到特定对象上。
所以,有⼀种⽅法是说把函数定义转移到构造函数外部,代码如下:
<script>
function Person(name ,age,job){
=name;
this.age=age;
this.job=job;
this.sayName=sayName;
}
function sayName(){
alert();
}
var lxy=new Person("lxy",22,"Software Engineer");
var strangerA=new Person("strangerA",24,"Doctor");
lxy.sayName();
strangerA.sayName();
</script>
把sayName()函数的定义转移到构造函数外部,成为全局的函数,构造函数内部把sayName赋为为全局的sayName。
这样sayName是⼀个指向外部函数的指针,因此lxy和strangeA就共享了全局的sayName函数。
alert(lxy.sayName==strangerA.sayName);//true
但是这会有更糟糕的问题:全局作⽤域的函数只能被某个对象调⽤,这名不副实啊,会造成对全局环境的污染;更糟糕的是构造函数有多少个⽅法,就要定义多少个全局函数,那构造函数就丝毫没有封装性可⾔了。
但是这样的想法是可贵的,为原型模式做了铺垫,构造函数创建对象问题的解决办法是原型模式。
六、原型模式
原型模式就是把构造函数中⽅法拿出来的基础上,为了避免对全局环境的污染,再做了⼀层封装,但是毕竟是⼀种新的模式,它封装的更彻底,⽽且也不是把所有的函数都封装,⽽是恰到好处的把构造函数中公共的⽅法和属性进⾏了封装。
代码:
<script type="text/javascript">
function Person(){
}
="lxy";
Person.prototype.age=22;
Person.prototype.job="Software Engineer";
Person.prototype.sayName=function(){
alert();
}
var lxy=new Person();
lxy.sayName();
var personA=new Person();
personA.sayName();
alert(lxy.sayName()==personA.sayName());//true
</script>
使⽤原型的好处是可以让所有的实例共享它所包含的属性和⽅法。
完美的解决了构造函数的问题。
因为原型是js中的⼀个核⼼内容,其信息量很⼤,所以另作介绍,有兴趣可看。
原型也有它本⾝的问题,共享的属性值如果是引⽤类型,⼀个实例对该属性的修改会影响到其他实例。
这正是原型模式很少单独被使⽤的原因。
<script type="text/javascript">
function Person(){
}
="lxy";
Person.prototype.age=22;
Person.prototype.job="Software Engineer";
Person.prototype.friends=["firend1","friend2"];
Person.prototype.sayName=function(){
alert();
}
var lxy=new Person();
var personA=new Person();
alert(lxy.friends);//friend1,friend2
alert(personA.friends);//friend1,friend2
alert(lxy.friends==personA.friends);//true
lxy.friends.push("friend3");
alert(lxy.friends);//friend1,friend2,friend3
alert(personA.friends);//friend1,friend2,friend3
alert(lxy.friends==personA.friends);//true
</script>
七、构造函数和原型混合模式
创建⾃定义类型的最常见⽅式,就是组合使⽤构造函数模式和原型模式。
构造函数模式⽤于定义实例属性,⽽原型模式⽤于定义共享的⽅法和属性。
结果,每个实例都有⼀份实例属性的副本,同时⼜共享着对⽅法的引⽤,最⼤限度的节省了内存。
另外,这种混合模式还⽀持向构造函数传递参数,可谓是集两种模式之长。
代码:
<script type="text/javascript">
function Person(name,age,job){
=name;
this.age=age;
this.job=job;
this.friends=["firend1","friend2"];
}
Person.prototype={
constructor:Person,
sayName:function(){
alert();
}
}
var lxy=new Person("lxy",22,"Software Engineer");
var personA=new Person("personA",25,"Doctor");
alert(lxy.friends);//friend1,friend2
alert(personA.friends);//friend1,friend2
alert(lxy.friends==personA.friends);//false
lxy.friends.push("friend3");
alert(lxy.friends);//friend1,friend2,friend3
alert(personA.friends);//friend1,friend2
</script>
实例属性在构造函数中定义,⽽共享属性constructor和共享⽅法sayName()在原型中定义。
⽽修改⼀个实例的friends不会影响其他实例的
friends,因为它们引⽤不同数组,根本没关系。
这种构造函数与原型混合模式,是⽬前使⽤最⼴泛、认同度最⾼的⼀种创建⾃定义类型的⽅法。
可以说,这是⽤来定义引⽤类型的⼀种默认模式。
其实原型就是为构造函数服务的,配合它来创建对象,想要只通过原型⼀劳永逸的创建对象是不可取的,因为它只管创建共享的属性和⽅法,剩下的就交给构造函数来完成。
⼋、动态原型模式
个⼈觉得构造函数和原型混合模式已经可以完美的完成任务了。
但是动态原型模式的提出是因为混合模式中⽤了构造函数对象居然还没创建成功,还需要再操作原型,这在其他OO语⾔开发⼈员看来很别扭。
所以把所有信息都封装到构造函数中,即通过构造函数必要时初始化原型,在构造函数中同时使⽤了构造函数和原型,这就成了动态原型模式。
真正⽤的时候要通过检查某个应该存在的⽅法是否有效,来决定是否需要初始化原型。
<script type="text/javascript">
function Person(name,age,job){
=name;
this.age=age;
this.job=job;
this.friends=["firend1","friend2"];
if(typeof this.sayName!="function"){
alert("初始化原型");//只执⾏⼀次
Person.prototype.sayName=function(){
alert();
}
}
}
var lxy=new Person("lxy",22,"Software Engineer");
lxy.sayName();
var personA=new Person("personA",25,"Doctor");
personA.sayName();
</script>
使⽤动态原型时,不能使⽤对象字⾯量重写原型。
如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。
九、寄⽣的构造函数模式
在前⾯⼏种模式都不适⽤的情况下,适⽤寄⽣(parasitic)构造函数模式。
寄⽣模式其实就是把⼯⼚模式封装在构造函数模式⾥,即创建⼀个函数,该函数的作⽤仅仅是封装创建对象的代码,然后再返回新创建的对象,从表⾯看,这个函数⼜很像典型的构造函数。
代码:
<script type="text/javascript">
function Person(name,age,job){
var o=new Object();
=name;
o.age=age;
o.sayName=function(){
alert();
}
return o;
}
var lxy=new Person("lxy",22,"Software Engineer");
lxy.sayName();
</script>
除了适⽤new操作符使得该函数成为构造函数外,这个模式和⼯⼚模式⼀模⼀样。
返回的对象和构造函数或者构造的原型属性之间没有任何关系,所以不能⽤instanceof,这种⽅式创建的对象和在构造函数外⾯创建的对象没什么两样。
⼗、稳妥的构造函数模式
稳妥的构造函数模式⽤显式声明的⽅法来访问属性。
和这种模式相关的有⼀个概念:稳妥对象。
稳妥对象,指没有公共对象,⽽且其⽅法也不引⽤this的对象。
稳妥对象最适合在⼀些安全的环境中(这些环境中会禁⽤this和new),或者防⽌数据被其他应⽤程序(如Mashup)改动时使⽤。
稳妥构造函数遵循与寄⽣构造函数类似的模式,但有两点不同:⼀是新创建对象的实例⽅法不引⽤this,⽽是不使⽤new操作符调⽤构造函数。
代码:
<script type="text/javascript">
function Person(name,age,job){
//创建要返回的对象
var o=new Object();
//可以在这⾥定义私有变量和函数
//添加⽅法
o.sayName=function(){
alert(name);
}
return o;
}
var lxy=new Person("lxy",22,"Software Engineer");
lxy.sayName();
alert();//undefined
alert(lxy.age);//undefined
</script>
以这种模式创建的对象中,lxy是⼀个稳妥对象,除了使⽤sayName()⽅法外,没有其他⽅法访问name的值,age,job类似。
即使有其他代码会给这个对象添加⽅法或数据成员,但也不可能有别的⽅法访问传⼊构造函数中的原始数据。
与寄⽣构造函数模式类似,使⽤稳妥构造函数模式创建的对象与构造函数之间也没有什么关系,因此不能⽤instanceof操作符。
各种创建⽅法问题总结:
⼯⼚模式:没法知道⼀对象的类型。
构造函数:多个实例之间共享⽅法。
原型:属性值是引⽤类型时,⼀个实例对该属性的修改会影响到其他实例。
组合使⽤构造函数模式和原型模式:【推荐】构造函数模式⽤于定义实例属性,每个实例都有⼀份实例属性的副本;⽽原型模式⽤于定义共享的⽅法和属性,每个实例同时⼜共享着对⽅法的引⽤。