JS是一种完全面向对象的程序设计语言,在面向对象处理方面,具有多种多样的实现方式,加之对象成员的动态性使得这门语言更加灵活;而js对象成员动态性也是创建和扩展对象的有力方式。
1 对象成员动态性
属性动态性:对于没有该成员属性的对象,可以直接采用赋值操作增加属性,
方法动态性:与属性动态性一样
比如:var obj=new Object();
属性动态性:obj.name='ffs';
方法动态性:obj.myThoed=function(){console.log('我是动态扩展的方法')};obj.myThoed();
js就是这样灵活;
2 创建对象
由于从语法层面没有类这种概念,与C#,Java这种语言不同,所以js里面采用函数来模拟类创建对象;
2.1 字面量创建对象
var obj={};这也是一个对象,常常临时封装一些成员而构成对象;
2.2 构造函数创建对象
var obj=new Object();这样通过Object构造函数也创建一个对象;与字面量均常常适用于临时封装一堆成员;不具有代码重用,也不具有对象类型。
2.3 工厂模式创建对象
工厂模式:通过一个函数模板创建一个对象的方式;
function Person(name,sex,age){
var o=new Object(){};
o.name=name;
o.sex=sex;
o.age=age;
o.say=function(){console.log('说中国话')};
return o;
};
var p=Person('zhangsan','男',28);通过人的一些属性传入工厂函数创建人这个对象;这个工厂方法就像模板一样,我给你什么东西你给我创建什么;
特点:通过参数初始化要创建的对象,但是该对象没有具体的类型,只能创建一些简单的对象;
实用:常常用于一些简单对象的创建模板,这些对象不具有复杂的逻辑处理,比如坐标点point;
2.4 构造函数模式创建对象
构造函数创建对象在java,c#强类型语言中创建对象唯一方法,而在js中却只是创建对象的方法之一,你说js是不是很灵活;
如:function Person(name,sex,age){
this.name=name;
this.sex=sex;
this.age=age;
this.say=function(){console.log('说中国话')};
};
var p=new Person('zhangsan','男',28);与工厂模式不同的是使用new关键字创建对象,其本质过程其实是跟工厂模式一致的,只不过使用new关键字,这些细节过程被js引擎自动做了,那么其具体过程是怎样呢?
一样的 先构建一个对象var o=new Object();将this指向o,然后在最后加上return o;语句,只是这些被js引擎遮蔽了;
特点:构造函数创建的对象具有构造函数一说,如p的构造函数为Person,即 p.constructor=Person,而Person又是一个函数指针,也具有函数的特性;
实用:构造函数适用于那些需要对象类型的对象创建,且创建的对象复杂性比工厂模式要强,可以创建复杂逻辑的对象,代码重用性高;
对于typeof p来说这里既可以是Object 也可以是与Person相等;
函数特性:我们知道Person也是一个函数,那么也可以像函数一样调用,
如:var o=new Object();Person.call(o,'zhangsan','男',28);这样对象o的成员得到了扩展,而Person本身并不会有返回值,所以这里也仅仅是对象o的成员扩展,且o对象没有构造函数,这里充分体现了对象动态特性;
2.5 原型模式创建对象
在函数学习中我们知道函数包括2个重要成员,argument,length,prototype(原型对象),也就是说原型其本质上也是一个对象{};且原型属于函数,也属于函数创建的对象;
这么一说,Object也具有一个原型对象,我们所有的对象直接或间接继承自Object,其实也直接间接的拥有了Object的原型对象,就好比一棵树,或者说父子关系,一级一级的向下传递,上层不能访问下层的成员;
原型与对象,与函数相互引用;即Person.prototype,pertotype.constructor又指向Person;
创建对象:
function Person(){
Person.prototype.name=name;
Person.prototype.sex=sex;
Person.prototype.age=age;
Person.prototype.say=function(){console.log('说中国话')};
}
在原型对象上扩展的属性和方法又叫做原型属性,原型方法,或者说静态属性,静态方法;一个类型的原型对象只会存在一份,即便是该类型的对象,子对象都共同引用这个原型对象,对于原型属性来说任何一个对象修改,那么其他对象查看也会随机修改;
1)对象成员搜索机制:在访问一个对象是否具有某个成员时,其搜索顺序为,先查找是否存在实例成员,如果有则直接返回,没有则继续在原型上查找,对于原型连继承的继续向原型连上层查找,直到Object顶层
如:var p=new Person();p.name;name属性存在于原型属性中,这里需要2次遍历;
2)对象成员set机制:比如说查找Person的对象p的name属性,如果进行赋值,会将name属性变为实例属性(因为对象成员动态性),而对外暴露来说就遮蔽了原型对象中name原型属性。如果使用delete删除实例属性,自然下次又能继续访问该原型属性了。
如:var p=new Person();p.name='张三';nam就成了实例属性,进而遮蔽了原型属性name,如果delete p.name,下次也能访问p.name只不过现在又回到原型属性了;
这里引入一个属性检测方法:hasOwnerProperty();检测对象是否拥有实例属性,如p.hasOwnerProperty('name'):为false,如果p.name='zhangsan';这时候就为true了
hasPrototypeProperty():用于检测是否具有原型成员,如:p.hasPrototyPeroperty('name'),为true;如果p.name='zhangsan';这时候就为false了
in:用于检测对象是否能够访问到该成员,而不在乎该成员是否是实例还是原型成员,如:'name' in p 始终为true;
for in:用于对象所有可枚举的属性,而不在乎属性是否为实例还是原型属性,比如:for(var o in p){if(o=='name'){//----}}
Object.keys(对象):用于获取原型对象可以枚举的属性,其实就是获取对象所有可枚举的属性,同样的道理也可以使用数组下标运算符访问属性
如:Object,keys(Person.prototype);结果为['name','sex','age'],也可以使用[]访问你属性:p['name']结果为张三;
原型对象的问题:由于原型相当于静态成员被所有实例对象共享,那么就共享的属性修改就会存在同步问题,但是呢,原型优势又在于共享对象,节约了内存空间;
还有就是没有构造删除哪样通过传参来初始化对象。
2.6 组合使用构造原型模式创建对象
组合模式吸纳了构造和原型的有点,使得这种模式可以创建高度负责逻辑的对象,就像c#中的类一样,具体怎么做呢?
一般我们将对象属性放在实例属性中定义,而对象的方法我们放在原型对象中;
如:function Person(name){
this.name=name;
Person.pertotype.say=function(){
console.log('说话');
}
}
特性:属性=》实例属性而不是定义为原型属性;方法=》通通放在原型中
实用:常常用于向c#哪有定义一个类,而真正创建多个对象;用于创建复杂逻辑的对象;而不是像构造或者原型他们那样,因为web页面常常是spa模式,一个类型对象一般只会有一个实例,因而使用简单方式创建对象
2.7 动态原型模式
所谓的动态原型是指在定义原型方法之前先判断一次,是否能够访问到该方法如果不存在再将该方法定义在原型中
如:function Person(name){
this.name=name;
Person.pertotype.say=function(){
console.log('说话');
}
//先判断,再定义===》动态原型模式也
if(typeof this.go!='function'){
Person.prototype.go=function(){console.log('走路')};
}
}
2.8 稳妥构造模式
构造模式一般是这样
function Person(name,sex,age){
this.name=name;
this.sex=sex;
this.age=age;
this.say=function(){console.log('说中国话')};
};
但是如果在this并不安全的环境中使用这种构造,会造成一些问题,那么这样呢
function Person(name,sex,age){
var o=new Object(){}; o.name=name;
o.sex=sex;
o.age=age;
o.say=function(){rerturn name;};
return o;
};
这里除了say方法没有其他方法可以访问name变量,这样name暴露的入口仅有一个而已就会显得很安全,很稳妥,所就叫稳妥构造模式,哎这种概念这蛋疼
2.9 寄生构造模式
function Person(name,sex,age){
var o=new Object(){};
o.name=name;
o.sex=sex;
o.age=age;
o.say=function(){console.log('说中国话')};
return o;
};
var p=Person('zhangsan','man','29');这样使用我们叫工厂模式,工厂模式并不具有构造模式的构造函数特性
var p=new Person('zhangsan','man','29');这样使用我们叫寄生构造模式,这样就具有工厂模式不具有的构造函数特性
总结:
js中由于SPA原因很多对象只会存在一份实例,且对象的逻辑不会很复杂,又加之对象成员动态性使得创建对象有很多方法方式,他们的共同基础都是对象动态特性
字面量,工厂,构造,原型,组合构造原型,寄生,稳妥,动态原型等等方式创建对象都有在某些场景下使用那种方式创建对象,且创建的对象所具有的特性也不一样,当然最重要的还是要数原型模式,原型对象
在js面向对象中既复杂又强大,又很重要,因此原型是必须掌握的,同样的组合构造原型模式也是创建对象最高级形式也是非常重要的。这些细节很多很多需要多实践才能掌握和熟练使用。