创建对象有很多种方式:Object构造函数 、对象字面量、create、工厂模式、构造函数模式、原型模式、混合模式(构造函数与原型相结合)、动态原型模式、寄生构造模式....
本文主要是是对工厂模式、构造函数模式、原型模式、混合模式进行介绍:
一、工厂模式
提出的背景:使用Object构造函数还是对象字面量方式创建一个对象是可以的,但是当使用同一个接口创建多个对象时,会产生大量重复的代码;
function createPerson(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function(){
alert(this.name);
}
return o;
}
var person1 = createPerson("zhangsan",18);
优点:可以创建多个函数即无数次的调用该函数缺点:无法解决对象识别问题(不知道创建的是什么对象);
二:构造函数模式:
提出背景:面对工厂模式无法解决对象识别问题,而构造函数被提出
function Person(name,age){ this.name = name; this.age = age; this.sayName = function(){ alert(this.name); } } var person = new Person("zhangsan",18);
优点:解决了对象的识别问题
==================================讲重点:============================================缺点:每个方法都要在每个实例上创建一遍。
1、构造函数与工厂模式有什么区别:
(1):没有在构造函数内显示的创建对象
(2):将属性和方法都赋值给了this;
(3):没有return 语句。
(4):江湖规矩:构造函数名的第一个字母大写;(尊重江湖规矩);
2、构造函数创建对象时使用了 new ,new操作符执行了哪些操作:
(1):创建对象;
(2):该变this指向;即this指向刚刚创建出来的对象
(3):为对象添加一些属性或方法
(4):返回对象;
面对每个方法都要在每个实例上创建一边,有人把sayName转到外部,这样解决了每个方法在每个实例上都创建一边的问题,这样会有全局变量的存在,更甚者若对象需要定义很多方法,则就要定义很多函数,这样自定义类就没有封装性可言。
function Person(name,age){ this.name = name; this.age = age; this.sayName = sayName; } function sayName(){ alert(this.name); } var person = new Person("zhangsan",18);
三、原型对象
声明两点:
1、每个函数都有prototype属性,该属性是一个指针,指向一个该函数的原型对象(该原型对象包含特定类型的所有实例共享的属性和方法)。
2、每个对象都有constructor属性,指向该对象的构造函数。
function Person(){} Person.prototype.name = "zhangsan"; Person.prototype.age = 18; Person.prototype.sayName = function(){ alert(this.name); } var person = new Person(); person.sayName();
调用Person时创建实例,在实例内部有一个指针,在现代浏览器中为:_proto_;该_proto_指向构造函数的原型对象,也就是说这个链接是建立
在实例与构造函数原型对象之间,而不是建立在实例与构造函数之间。
上图反应了Person构造函数、Person的原型对象、以及person实例之间的关系。
每当代码读取每个对象的某个属性时,都会执行一次搜索。搜索的顺序是:首先看对象实例上有没有这个属性,若有直接进行后续的操作,若没有,顺着搜索指针
到该实例构造函数的原型对象上去找,若找到执行后续的操作,若没有找到,继续顺着搜索指针到Object构造函数的原型对象上去查找,找到则执行后续的操作,没有
找到则返回null。这就是对象实例共享原型所保存的属性和方法。
**************************************************划重点************************************************************************
注意同名屏蔽问题:所谓的同名屏蔽就是在搜索的过程中是按照上面的顺序搜索查找的,若实例对象上有该属性就直接返回,就不会顺着搜索指针到该实例的构造函数
的原型对上去查找,这就是所谓的同名屏蔽问题。
同名屏蔽只是阻止我们访问原型对象中的属性,但不会修改原型对象中的属性。当采用delete删除实例上的属性时,再次搜索查找时还是会按照上面介绍的顺序进行查找,
就可以找到该实例构造函数的原型对象上的属性。
问题又来了,既然可以在实例中添加属性,在搜索时阻止我们访问原型对象中的属性,但是如何判断访问的属性是原型中的属性还是实例中的属性呢?
hasOwnProperty()就可以检测访问该属性时是来自实例的属性还是原型对象的属性。该方法继承自Object。只有当实例中存在该属性,使用hasOwnProperty()方法才会返回true。
如何确定该属性是在实例中还是在原型对象中?
采用hasOwnProperty()与in 结合;in操作符无论对象是在实例中还是在属性中都是返回true;
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name)&&(name in object);
}
结果为true,则属性在该实例构造函数的原型对象中;为false则属性在该实例中。
**当原型对象采用对象字面量的形式书写时,注意在构造函数的原型对象的第一行写上:constructor:构造函数名, 在这之后写属性和方法。
原型模型创建对象的优点:解决了构造函数中每个属性和方法在每个实例中都创建一次的问题。
缺点:省略了构造函数传参,默认情况下都将取得相同的属性值;由于方法和属性都是加在构造函数的原型对象上,最大的缺点是共享的本性所导致的。
function Person(){} Person.prototype={ constructor:Person, name:'zhangsan', age:18, friends:['lisi','wangwu','zhaoliu'], sayName:function(){ alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push('sunqi'); alert(person2.friends);//'lisi','wangwu','zhaoliu','sunqi'
面对上面的种种问题,开发中最常用的是构造函数模式与原型模式想结合的方式进行。四、构造函数与原型模式相结合
function Person(name,age){
this.name = name;
this.age = age;
this.friends = ['lisi','wangwu'];
}
Person.prototype.sayName = function(){
alert(this.name);
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push('zhaoliu');
alert(person1.friends);//'lisi','wangwu','zhaoliu'
alert(person2.friends);//'lisi','wangwu'
alert(peroson1.sayName === person2.sayName)//true
五、动态原型模式
所谓的动态原型模式就是在有必要的前提下通过构造函数原型对象创建方法;在创建方法之前要判断在该方法是否有效。
function Person(name,age){ this.name = name; this.age = age; }
if(typeof this.sayName !="function"){ Person.prototype.sayName = function(){ alert(this.name); } } var person = new Person('zhangsan',18); person.sayName();
注意加粗字体:只有在sayName()不存在时,才将他添加到构造函数的原型对象中。在使用动态原型模式时 不要使用对象字面量的方式重写原型.
六:寄生构造模式
函数的形式类似于工厂模式,创建对象的形式类似与构造函数模式.
function Person(name,age){ var o = new Object(); o.name = name; o.age = age; o.sayName = function(){ alert(this.name); } return o; } var person = new Person('zhangsan',18); person.sayName();