0. Object.create 和对象字面量
先说说创建对象的一般方法,最简单就是对象字面量。
var book={
“titile”:”JS”,
“sub-titile”:”DG”,
“author”:{
firstname:”David”,
lastname:”xxx”
}
};
这种方法对于属性有一定的要求,就是如果属性名有连接符,其他符号就必须用字符串表示,一般可以不需要。
对象直接量是一个表达式,这个表达式每次运算都创建并初始化一个新的对象。每次计算对象直接量的时候都会计算它的每个属性值。 也就是说,如果在一个重复调用的函数的循环体中使用了对象直接量,它会创建很多新对象。
所有对象直接量创建的对象具有同一个原型对象,就是Object.prototype
Object.create
这个方法用于创建一个新对象,第一个参数是这个对象的原型。 Object.create() 提供第二个可选参数,用于对对象的属性进一步描述。
var o1=Object.create({x:1,y:2});
var o2=Object.create(null); //o2不继承任何属性和方法
var o3=Object.create(Object.prototype);//和new Object()效果一样。
new 操作符会把构造函数创建的对象的原型设置为构造函数的prototype属性的值~!
虽然上面两个方法都可以创建单个对象,但是明显缺点:使用同一个接口创建很多对象,产生大量重复代码,
而且仅仅是改变一下值,也要改变传递的对象。
·~~
Object.creaet({x:1,y:2,z:3})
Object.creaet({x:1,y:3,z:3})
Object.creaet({x:3,y:3,z:1})
//对象直接量也是如此
1.工厂模式
function createPeople(name,age,job){
var o=new Object(); //Object.create(Object.prototype)
o.name=name;
o.age=age;
o.job=job;
o.sayName=function(){
}
return o;
}
Usage
var person1=createPeople("NIco",12,"banker");
var person2=createPeople("NIco",13,"banker");
优缺点
工厂模式虽然解决了创建多个相似对象的问题,但是却没有解决对象识别的问题,也就是怎么知道一个对象的类型?
2.构造函数模式
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
//...
};
}
var person1=new Person('xxx',29,"S");
不同之处:
- 没有显式创建对象
- 没有return语句
- 直接将属性和方法赋给this对象
- 使用了new关键字
大写字母只是构造函数的一个约定而已。
构造函数的问题
构造函数的每个方法都要在每个实例上重新创建一遍。在前面的例子中,就是那个sayName方法,方法在不同对象上是不同的实例。
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=new Function("alert(this.name)"); //与声明函数在逻辑上等价
}
因为其实创建函数也是创建一个对象,JS中函数也是对象~ 所以不同实例上的同名函数是不相等的。
例如:
person1.sayName==person2.sayName //false
new关键字的实质
- 隐式创建一个新对象
- 将构造函数的作用域赋给新对象(this指向了这个对象)
- 把构造函数的方法和属性赋给这个对象(也就是执行这个构造函数)
- 隐式返回this指向的这个新对象
可以看到我们的这个函数的原型把constructor指向了本身。即 准确说我们每次创建一个函数,都会有一个原型。
var p1=new Person('3a',12,'xx');
对于这个新的对象来说,它的原型就是prototype属性指向的那个对象~
3.原型模式
由前面的介绍,可以知道原型是每个函数都有的一个属性,这个属性是一个指针,指向一个对象。
这个对象的用途:
包含了可以由特定类型的所有实例共享的方法和属性。
prototype就是通过调用构造函数创建的那个对象实例的原型对象
换句话说吗,也就是说所有的实例都会共享原型对象的属性和方法!
function Person(){
}
Person.prototype.name="Nicolas";
Person.prototype.age=29;
Person.prototype.job="software engineer";
Person.prototype.sayName=function(){
};
var p1=new Person();
var p2=new Person();
比较实例的方法发现相等,没有重复创建!
p1.sayName==p2.sayName; //true
更简单的原型模式:
function Person(){}
Person.prototype={
name:"Nicolas",
age:29,
job:"Soft Engineer"
sayName:function(){
}
};
这种写法跟前面的一样, 注意的是前面的函数声明是必须的。 因为创建一个函数,就同时就会创建它的prototype对象,这个对象会自动获得constructor属性。
而不声明这个函数的话,这个原型对象的constructor属性就是指向Object构造函数了~!!
[[Enumberable]]
这种方式设置constructor属性会导致它的这个 [[Enumberable]]属性被设置为true。 默认下原生的constructor属性是不可枚举的。
原型模式的问题
省略了为构造函数传递参数的环节,所有实例默认下都取得相同的属性值。
所有属性是被实例共享的,基本值的属性是可以通过添加一个同名属性来覆盖。
但是包含引用类型的属性值,就不同了~
我们修改了这个属性的值,就会反映到所有实例对象上!
function Person(){}
Person.prototype={
name:"Nicolas",
age:29,
friend:["SH","WW"],
job:"Soft Engineer"
sayName:function(){
}
};
var p1=new Person();
var p2=new Person();
p1.friend.push('Van'); //
无论是p1还是p2的数组都改变了。
4.组合构造模式和原型模式
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
}
Person.prototype={
name:"Nicolas",
age:29,
friend:["SH","WW"],
job:"Soft Engineer"
sayName:function(){
}
};
构造函数模式定义实例属性
原型模式定义方法和共享的属性。
每个实例有自己的一份实例属性的副本,同时共享方法的引用!
应用最广泛,最认同的一种方式
5.动态原型模式
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
if(typeof this.sayName!="function"){
Person.prototype.sayName=function(){
//..
}
}
}
var friend=new Person("ss",29,'xxx');
区别:
仅仅是在于如果sayName方法如果不存在的时候,才会将它添加到原型中。 这段代码只是在初次调用构造函数执行。
此后原型已经完全初始化了。 记住,这里对原型做的修改都会在所有实例中反映!
这种方法,检查方法有没有被定义,也就是说原型可以在后面更新。
比如:
Person.prototype.xxx=function(){
}
你用一个之前创建过的实例访问这个函数是合法的
值得注意的是
不要像这样重写:
Person.prototype={
xxx:
yyy:
}
因为这样会切断现有实例与新原型的联系!
6.寄生构造函数模式
function Person(name,age,job){
var o=new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function(){
alert(this.name);
};
return o;
}
这个模式其实和工厂模式是一模一样,只是用法
var friend=new Person("ss",29,'xxx');
创建了一个对象,并把方法和属性都封装都里面。
用处
默认返回新的对象实例,也可以在需要返回值的时候返回值~
用在特殊情况下创建构造函数。
如下面这个,我们不能直接修改Array()这个内置的构造函数,所以我们使用这个模式新建一个对象(数组)。
function SpecialArray(){
//创建数组
var values=[];
//添加值
values.push.apply(values,arguments);
//添加方法
values.toPipeString=function(){
return this.join("|");
}
return values;
}
返回值的问题
我们new操作符,默认是会返回一个新的实例对象(new创建一个对象,将该函数作用域赋给对象即绑定this,更重要的应该是创建一个连接到该函数的prototype成员)
而我们在构造函数末尾写一个return语句就可以重写构造函数时返回的值。
这也是为什么上面的写法是返回一个数组。
寄生构造函数模式的问题
- 返回的对象与构造函数或者构造函数的原型对象没有什么关系。 在构造函数内部和外部创建都一样
- 不能依赖instanceof 确定对象类型。
所以:
var colors=new SpecialArray("blue",'rer','cos')
var colorsx=new SpecialArray("blue",'rear','cosx');
colors.toPipeString==colorsx.toPipeString
//false
函数对象被重新创建了, 所以不建议使用这种模式
7.稳妥构造函数模式
durable object 稳妥对象:
没有公共属性,其方法不引用this的对象。
用处
一些安全的环境中,禁止使用this和new,或者防止数据被其他应用程序改动时使用。
function Person(name,age,job){
var o=new Object();
//定义私有变量和函数
o.sayName=function(){
alert(name);
};
return o;
}
这种模式,除了sayName方法外没有方法可以访问私有变量name!
var friend=new Person("ss",29,'xxx');
friend.sayName(); //ss
问题
同样,它创建的对象与构造函数之间也没有什么关系,不能用instanceof操作符来区分对象的类型。
总结
定义在原型中的属性和方法是被实例共享的,因为它这些函数或者属性都是通过原型链搜索得到的,搜索!