写之前先说点废话,本来这篇文章上周五就该写完了,但是这周末过的的确有点浑浑噩噩了,对学习的态度也有些消极了,这两天心思都放在如何利用hexo 搭建自己的博客上了,还好这次辛苦没有白费,终于捣鼓出来了,地址为
younglight.top
欢迎参观!
好了,现在进入正题,在上篇文章中我整理了些比较基础简单的关于对象的介绍,包括属性方法以及创建对象实例。但是我们当时使用var Person={}
的方法创建的实例也仅仅是一个实例而已,但是想想我们生活中如此多的事物,假如都用如此方法一个个创建,多少了女娲也不够使啊,想当年女娲造人也只是造了个模子,然后ctrl+c ctrl+v
,所以,我们也在想,能不能跟女娲一样造个模子,然后通过模子在创建更过差不多的对象实例。首先想到的是一种称为工厂模式的方法。
工厂模式
所谓工厂模式,就是先造一个模型,然后大规模生产,他的做法类似这样:
var createPerson=function(name,age,job){
var o={}
o.name=name;
o.age=age;
o.job=job;
o.sayname=function(){
alert(this.name)
}
return o;
}
var xm=createPerson('小明','18','student')
上面我们创建了一个函数,在函数内部首先创建了一对象,接着根据参数给这个对象添加属性,最后返回这个对象。如此一来,我们只要知道了一个对象的基本信息,就可以大量创建相似的对象,具有相同的功能。但是此方法的缺陷也很明显,即无法识别不同类型的对象,什么意思呢?
假如此时你又创建了一个createDog函数,经过这个函数可以创建许多dog,但是等到你使用的时候,早就不知道这个对象是什么了。于是,出现了解决此问题的方法——构造函数模式。
构造函数模式
所谓构造函数,就是用来产生特定类型对象的一个函数,注意,它是一个函数,只是它的功能特别点儿,其他方面和普通函数没什么区别。我们先看下它的面目:
var Person=function(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.secondname='二明'
this.sayname=function(){
alert(this.name);
}
}
上面就是一个构造函数,我们给他定义了如姓名,年龄等属性;
js中的this,指向调用它的对象,假如我们此时在全局调用,那它就指向window,我现在调用函数
Person()
,那么此时函数内的属性都赋值给了window,通过alert(secondname) //二明
就可以看出来。我们为了区分普通函数和构造函数,通常对构造函数的名称首字母大写,这是行业习惯,而非硬性规定。
而通过构造函数创建对象的正确用法是这样的:
var xm=new Person('小明','18','student');
通过new方法,创建一个对象实例,在此过程中,依次进行了如下四个动作:
- 创建一个空对象并赋值给了xm;
- 将此函数的作用域链赋给xm,即将this指向xm;
- 对xm执行函数内容,即进行属性方法的赋值;
- 返回xm对象。
此时,我们就能根据不同的构造函数来区分不同的对象,区分方法就是instanceof
方法:
alert(xm instanceof Person); //true
alert(xm instanceof Dog); //false
这样,对象识别问题被解决了,可是,又有了新的问题,从构造函数实例化对象的过程中我们可以看到,每次创建一个对象,都要执行一遍里面的代码,包括属性和方法,属性没什么大问题,但是方法不一样,每次执行都会创建一个全新的方法,虽然它们功能相同,但却不是同一个:可以通过检测alert(xm.sayname==xh.sayname) //false
得到验证,这违背了我们的想法,我们当然是想同一种方法只创建一次就能被所有对象调用。
当然,也还是有解决办法的。
构造函数升级版
既然在构造函数内声明的方法无法被共享,那就将方法写在外面,就像这样:
var Person=function(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.secondname='二明'
this.sayname=sayname1;
}
var sayname1=function(){
alert(this.name)
}
这样,我们每个对象调用方法时调用的是一个全局作用域中的函数引用,这样,sayname方法只需要创建一次,就可以为所有Person对象的实例引用了。对,还是有可是,假如每一类对象的所有方法都放在全局作用域中,后果时不可想象的,包括命名冲突,格式混乱,不易维护。这样的话对于对象的封装性也无从谈起了。
嗯嗯,为了解决这个问题(人类的进步就是在不断地发现问题和解决问题),接下来,就到了令无数人闻风丧胆的原型模式了。
原型模式
其实js的原型并不是多么难,只是有点复杂,我将会用有趣的类比来尽可能的让你理解其中的关系。
什么是原型 (本文不再详细介绍原型)
每当我们创建一个函数时,这个函数都会有一个prototype 属性,这个属性是一个指针,指向一个对象。
原型模式的使用
现在,我们将一个实例对象类比为小明,而构造函数就是小明的干爹,为什么是干爹,因为小明是对象,而构造函数是函数,不严格一样,所以,是干爹,每new 一次构造函数,就相当于它干爹捡了一个干儿子,小明就是这样来的。
用代码来叙述就是这样:
var Dad=function(){}; //构造函数
var xm=new Dad(); //创建对象
在构造函数模式下,小明的名字,年龄,技能都是由它干爹来完成的,但是在原型模式下,他干爹觉得小明骨骼精奇,怕自己耽误了小明的天赋,于是找来了他的一个熟人,也就是构造函数的prototype属性所指向的对象,来当小明的师父,此时,小明的一切东西,都由他师父来决定:
Dad.prototype.name='小明';
Dad.prototype.age='18';
Dad.prototype.sayname=function(){
alert(this.name)
}
alert(xm.name) //小明
现在,你想找小明的相关信息都要去他师父那里去找,但是,小明也是可以自己更改名字的,记住,这个更改是小明给自己重新起了一个名字来覆盖了来自他师父的名字,而不是更改了他师父的名字,看例子:
xm.name='大明';
alert(xm.name); //大明
alert(Dad.prototype.name); //小明
如果想把自己改的名字恢复为一开始,则必须要使用delete删除自己添加的属性。
delete xm.name;
alert(xm.name) //小明
通过原型,我们可以把对象的属性都写到原型中去,这样,每一个对象的属性都可以从他的原型里获取。
var xh=new Dad();
var xw=new Dad();
xh.name='小华';
xw.name='小王';
alert(xh.name); //小华
alert(xw.name); //小王
xh.sayname(); //小华
xw.sayname(); //小王
现在我们看到,原型模式既解决了方法共享问题,又能良好的封装对象,实在是不可多得的良方妙药。等等,我们似乎高兴的有点早了,现在假设原型属性中有一个引用类型值时;
Dad.prototype.familiy=['father','mother'];
alert(xm.familiy); //fanther mother
alert(xh.familiy); //father mother
xm.familiy.push('sister');
alert(xm.familiy); //father mother sisiter
alert(xh.familiy); //father mother sisiter**
大家很容易看到,当小明通过添加自的家庭成员妹妹时,小华也莫名奇妙的多了一妹妹,当然,你要你觉得他俩关系好,小明的妹妹就是小华的妹妹,那的确没啥问题,但是,要是小明多的是个老婆呢?这个关系好也不行吧!因此,原型方式还是存在一些不完美,因为并不是所有的属性都想要被所有对象共享的。那怎么办?很好办!既然原型可以存放方法和公有属性,构造函数可以放私有属性,把他们结合起来岂不美哉!接下来,介绍组合使用构造函数模式和原型模式。
组合使用构造函数模式和原型模式
作为最常用的一种创建对象的模式,这个模式结合了前两者的优点,具体用法如下:
var Dad=function(name,age){ //创建构造函数
this.name=name; //定义私有属性
this.age=age;
this.familiy=['father','mother'];
}
Dad.prototype={ //定义公有属性及方法
job:'IT',
sayname:function(){
alert(this.name);
},
sayfamiliy:function(){
alert(this.familiy);
}
}
xm=new Dad('xm','18'); //创建对象实例
xh=new Dad('xh','20');
xm.familiy.push('wife'); //添加私有属性
xh.familiy.push('sisiter');
xm.sayname(); //xm
xh.sayname(); //xh
xm.sayfamiliy(); //father mother wife
xh.sayfamiliy(); //father mother sisiter
又上面的案例,很容易的看出此模式的大体构造以及优点,我们将私有属性都放在了构造函数内,这样,每次创建对象都会生成一个拥有独立私有属性的对象,各个对象之间互补干扰,又能同时共享它们的公共属性及方法。
至此,常用的对象创建方法都已经完成,还有其他几个特殊情况下使用的方法,包括动态原型模式、寄生构造函数模式、稳妥构造函数模式。大家可以去红皮书上了解一下。
本文章对于原型的讲述并没有很深入,觉得关于原型有必要另写一篇文章。