js创建对象的模式
Object构造函数或对象字面量都可以用来创建对象,但会产生大量重复代码,所以要选择恰当的模式进行快速地创建对象:
1.工厂模式
工厂模式抽象了创建具体对象的过程,简单说就是把重复的代码封装起来:
function creatPerosn(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function () {
alert(this.name);
}
return o;
}
var person1 = creatPerson("名字1",20);
var perosn2 = creatPerson("名字2",30);
缺点:没有解决对象识别的问题(即怎样知道一个对象的类型)
2.构造函数模式
function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = function () {
alert(this.name);
};
}
var person1 = new Person('名字1',20);
var person2 = new Person('名字2',30);
person1.sayName();
person2.sayName();
2.1与工厂模式不同的地方:
- 没有显示创建对象
- 直接将属性和方法赋给了this对象
- 没有return语句
- 大写字母开头(借鉴自其它OO语言)
- 解决了对象类型标识符的问题
2.2要创建Person实例,必须使用new操作符,调用构造函数经历以下步骤:
- 创建一个新对象
- 将构造函数的作用域赋给新对象(this就指向了这个新对象)
- 执行构造函数中的代码(为新对象添加属性)
- 返回新对象
2.3构造函数的问题
每个方法都要在每个实例上重新创建一遍,如person1和person2的sayName方法根本不需要多次创建,浪费内存,因此引出了一个思路:怎样设置类的共享方法和共享属性。
3原型模式
构造函数的prototype属性是一个指针,指向一个对象,这个对象用途是包含可以由特定类型的所有实例共享的属性和方法。示例:
function Person() {
}
Person.prototype.name = 'jaychou';
Person.prototype.age = 20;
Person.prototype.sayName = function () {
alert(this.name);
}
var person1 = new Person();
person1.sayName();//"jaychou"
var person2 = new Person();
person2.sayName();//"jaychou"
在实现中无法访问到[[prototype]],但可以通过isPrototype()方法确定对象之间是否存在这种关系。
接上面
alert(Person.prototype.isPrototypeOf(person1));//true
alert(Person.prototype.isPrototypeOf(person2));//true
3.1原型和属性相关小点总结:
- 读取某个对象的某个属性时,会执行搜索,先从对象实例本身开始,如果有就返回,如果没有就继续搜索指针指向的原型对象,有就返回,没有就继续搜索原型链的下一个。
- 在实例中添加属性,会屏蔽其原型对象中保存的同名属性。修改实例的这个属性,并不会影响到原型的那个同名属性。
- 使用hasOwnProperty()方法可以检测一个属性是存在于实例中,只有在给定属性存在于对象实例中才会返回true。
function Person(age) {
this.age = age;
}
Person.prototype.name = 'jaychou';
var person1 = new Person(20);
alert(person1.hasOwnProperty("name"));//存在于原型中,返回false
alert(person1.hasOwnProperty("age"));//存在于实例中,返回true
- in操作符,有两种方式使用:单独使用和在for-in循环中使用。单独使用时,会在通过对象能够访问给定属性时返回true,无论存在于实例还是原型中。而for-in中只能枚举enumerable数据属性为true(true代表可枚举)的属性(IE8及以前有一个bug,屏蔽不可枚举属性的实例属性不会出现在for-in循环中)。
单独使用in操作符:
function Person(age) {
this.age = age;
}
Person.prototype.name = 'jaychou';
var person1 = new Person(20);
alert(person1.hasOwnProperty("name"));//存在于原型中,返回false
alert(person1.hasOwnProperty("age"));//存在于实例中,返回true
alert("name" in person1);//存在于原型中,返回true
alert("age" in person1);//存在于实例中,返回true
for-in循环的使用:
function Person(age,height) {
this.age = age;
this.height = height;
}
Person.prototype.name = 'jaychou';
var person1 = new Person(20,'180cm');
Object.defineProperties(person1,{
weight:{
value:'75kg',
configurable:true,
enumerable:true,
writable:true
},
work:{
value:'software engineer',
configurable:true,
enumerable:false,
writable:true
}
})
for(var prop in person1){
//依次弹出属性:age,height,weight,name,不会弹出work,因为work的enumerable为false
alert(prop);
}
alert("work" in person1);//为true,因为work属性只是不能被枚举,不妨碍被访问
- 所以结合in操作符和hasOwnProperty()方法可以清晰知道给定属性是否存在某个实例对象中,存在于实例中还是其原型中。
- 要取得对象上所有可枚举的实例属性,可以使用Object.keys()方法,参数为对象,返回可枚举属性的字符串数组。
function Person(age,height) {
this.age = age;
this.height = height;
}
Person.prototype.name = 'jaychou';
Person.prototype.hobby = 'football';
var person1 = new Person(20,'180cm');
Object.defineProperties(person1,{
weight:{
value:'75kg',
configurable:true,
enumerable:true,
writable:true
},
work:{
value:'software engineer',
configurable:true,
enumerable:false,
writable:true
}
})
alert(Object.keys(person1));//返回age,height,weight,并不会返回name和hobby原型属性
alert(Object.keys(Person.prototype));//返回name和hobby
- 如果想得到所有实例属性,无论是否可枚举,使用Object.getOwnPropertyNames()方法:
接上面
//返回age,height,weight,work,即使work不可枚举
alert(Object.getOwnPropertyNames(person1));
//返回name,hobby,constructor,即使constructor不可枚举
alert(Object.getOwnPropertyNames(Person.prototype));
3.2更简单的原型写法
如果为原型添加多个属性和方法,为了更好的视觉效果,可以用一个包含所有属性和方法的对象字面量来重写整个原型对象,并且添加好不可枚举的constructor属性。但是注意的是,如果重写整个原型对象,那么就是切断了构造函数与最初原型之间的联系了。
function Person() {
}
Person.prototype = {
name:'jaychou',
age:20,
job:'software engineer',
sayName:function () {
alert(this.name);
}
}
Object.defineProperty(Person.prototype,"constructor",{
// 让constructor不可枚举
enumerable:false,
value:Person
});
3.3原型的动态性
如下面的例子,先是创建了实例friend,然后才添加了原型方法sayName,但调用的时候是正常的,因为会查找sayName。
function Person() {
}
var friend = new Person();
Person.prototype.sayName = function () {
alert('kk');
}
friend.sayName();//正常调用
重写整个原型对象导致的错误示例:
function Person() {
}
var friend = new Person();
Person.prototype = {
name:'jaychou',
age:20,
job:'software engineer',
sayName:function () {
alert(this.name);
}
}
Object.defineProperty(Person.prototype,"constructor",{
// 让constructor不可枚举
enumerable:false,
value:Person
});
friend.sayName();//调用错误,因为friend指向的是旧的原型对象而不是新的
3.4原生对象的原型
原型模式不仅体现在自定义类型方面,所有原生的引用类型也是采用原型模式,所有原生引用类型都在其构造函数的原型上定义了相关的方法。
- 通过原生对象的原型,可以取得所有默认方法的引用,而且还可以定义新方法,但不建议修改原生对象的原型,因为有可能造成命名冲突或者意外地重写了原生方法。
3.5原型对象的问题
- 省略了为构造函数传递初始化参数的环节,所有实例都取得了相同的属性值
- 原型中所有的属性都是共享的(方法共享本来就是我们大多数情况下希望做到的,也是使用原型模式的重要原因),特别体现在包含引用类型值的属性,如一个数组属性,实例A对其push操作了,就会影响到了其他所有的实例
所以创建自定义类型的最常见方式就是:组合使用构造函数模式与原型模式。
4.组合使用构造函数模式和原型模式
组合使用的好处:构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。结果:每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。还支持向构造函数传递参数。可谓是集两种模式之长。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor:Person,
sayName:function () {
alert(this.name);
}
}
var person1 = new Person('jay',29,'singer');
var person2 = new Person('jack',30,'software engineer');
person1.sayName();//jay
person2.sayName();//jack
alert(person1.sayName === person2.sayName);//true
5.动态原型模式
动态原型模式的用处:把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(如有必要的情况下),又保持了同时使用构造函数和原型的优点。简单说,就是通过检查某个应该存在的方法是否有效来决定是否需要初始化原型。
function Person(name, age, job) {
//属性
this.name = name;
this.age = age;
this.job = job;
//方法
if(typeof this.sayName != "function"){
console.log('设置方法');//这里只会在初次调用构造函数时才会执行
Person.prototype.sayName = function () {
alert(this.name);
};
Person.prototype.sayAge = function () {
alert(this.age);
}
}
}
var friend = new Person("jay",20,"software engineer");
var friend1 = new Person("jaychou",30,"saler");
- 设置原型相关属性和方法的代码只会在初次调用构造函数时才会执行
- if语句只需要检测其中一个属性或方法即可
6.寄生构造函数模式
- 思想:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象
- 外部的样子像构造函数
- 本质上就是工厂模式,区别:①使用了new操作符(其实不使用new操作符也可以),②把它称为构造函数,显式使用return语句返回内部创建好的改造好的对象
- 它返回的对象与在构造函数外部创建的对象没有什么不同,因为它的主要作用就是封装创建对象的代码而已
- 主要应用场景:为原生类型添加额外的方法(在不重写原生方法和添给原生添加新方法不能直接修改原生构造函数前提下),就是简单改造原生类型实例
function SpecialArray() {
//创建数组
var values = new Array();
//添加传进来的值
values.push.apply(values,arguments);
//添加额外方法
values.toPipedString = function () {
return this.join("-");
};
//返回数组
return values;
}
var colors = new SpecialArray("red","blue","black");
alert(colors.toPipedString());
7.稳妥构造函数模式
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。与寄生构造函数模式相似,区别:①新创建对象的实例方法不引用this,②不使用new操作符调用构造函数。
function Person(name,age,job) {
//创建要返回的对象
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function () {
alert(name);
}
//返回对象
return o;
}
var friend = Person('jaychou',30,'saler');
friend.sayName();
- 稳妥对象使外界没有别的方式可以访问其数据成员,非常适合在某些安全执行环境提供的环境下使用。