JS创建对象的七种模式:
一:工厂模式
这种模式抽象了创建具体对象的过程。可以用函数来封装以特定接口创建对象的细节。
function createPerson(name,age,job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
}
return o;
}
var person11 = createPerson('Andy',27,'IT');
var person12 = createPerson('Jack',26,'Teacher');
缺点:工厂模式虽然解决了创建多个相似对象的问题,但没有解决对象识别的问题(即怎样知道一个对象的模型)。
二:构造函数模式
<span style="font-size:18px;"> function Person(name,age,job){
this.name = name;
this.age =age;
this.job= job;
this.sayName = function(){
console.log(this.name);
}
}
var person21 = new Person('Andy',27,'IT');
var person22= new Person('Jack',26,'Teacher');</span>
Person()中的代码处了与createPerson()中有相同的部分外,还存在以下不同之处:
1,没有显示地创建对象;
2,直接将属性和方法赋给了this对象;
3,没有return语句;
要创建Person的新实例,必须使用new操作符。以这种方式调用的构造函数实际上会经历以下4个步骤:
(1)创建一个新对象;
(2)将构造函数的作用域赋给新对象(this执行了新对象)
(3)执行构造函数中的代码(为这个新对象添加属性)
(4)返回新对象;
前面的例子的最后,person21和person22分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性执行Person。
console.log(person21.constructor == Person); // true;
console.log(person22.constructor == Person); // true;
对象的constructor属性最初是用来标识对象类型的。但是,提到检测对象类型,还是instanceof操作符更可靠一些 console.log(person21 instanceof Person);// true;
console.log(person21 instanceof Object);// true;
1,将构造函数当做函数
构造函数与其他函数的唯一区别,就在于调用它们的方式不同 2,构造函数的问题
使用构造函数的主要问题是,每个方法都要子啊每个实例上重新创建一遍。在前面的实例中,person21和person22都有一个名为sayName()的方法,但是这两个方法不是同一个Function的实例。 ECMAScript中的函数就是对象,因此每定义一个函数,也就是实例化了一个对象。 function Person2(name,age,job){
this.name = name;
this.age = age;
this.job=job;
this.sayName = new Function("console.log(this.name)");// 与声明函数在逻辑上是等价的
}
console.log(person21.sayName == person22.sayName) // false;
所以,创建两个完成同样任务的Function实例的确没有必要;况且有this对象在,根本不用在执行代码前就把函数绑定到特定对象上。 function Person21(name,age,job){
this.name= name;
this.age = age;
this.job=job;
this.sayName = sayName;
}
function sayName(){
console.log(this.name);
}
可是,新问题又来了:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实,如果对象需要定义很多方法,就要定义很多全局函数,就毫无封装性可言了; 三:原型模式:
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,执行一个对象,而这个对象的用于是包含可以由特定类型的所有实例共享的属性和方法。 function Person3(){
}
Person3.prototype.name='Andy';
Person3.prototype.age = 27;
Person3.prototype.sayName=function(){
console.log(this.name);
}
var person31= new Person3();
person31.sayName(); // "Andy";
var person32= new Person3();
person32.sayName();// "Andy"
与构造函数不同的是,新对象的这些属性和方法有所有实例共享的。 1,理解原型对象
创建自定义的构造函数后,其原型对象默认只会取得constructor属性;其他方法,则都是从Object继承而来的。 当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性)。执行构造函数的原型对象。 ECMAScript-262第5版叫这个指针[[Prototype]]。虽然在脚本上没有标准的方式访问[[Prototype]],但火狐,Safari和Chrome在每个对象上都支持一个属性 _proto_; 而在其他实现中,这个属性对脚本则是完全不可见的。 不过,要明确的真正重要的一点就是,这个链接存在于实例与构造函数的原型对象之间,不是存在于实例与构造函数之间。 虽然在所有实例都无法访问到[[Prototype]]。但可以通过 isPrototypeOf() 方法来确定对象之间是否存在这种关系。 console.log(Person3.prototype.isPrototypeOf(person31));// true;
这里,我们用原型对象的isPrototypeOf()方法测试了person31。因为它内部有一个指向Person3.prototype的指针。因此返回true; ECMAScript5新增了一个新方法。Object.getPrototypeOf()。在所有支持的实现中,都返回[[Prototye]]的值;浏览器支持情况:IE9+...... console.log(Object.getPrototypeOf(person31) == Person3.prototype);// true;
console.log(Object.getPrototypeOf(person31).name);// "Andy"
使用hasOwnPrototype()方法可以检测一个属性的是存在于实例中,还是存在于原型中。这个方法只在给定属性存在于实例中,才会返回true; function Person31(){
}
Person31.prototype.name = 'Andy';
Person31.prototype.age= 27;
Person31.prototype.sayName=function(){
console.log(this.name);
}
var person33 = new Person31();
var person34 = new Person31();
console.log(person33.hasOwnProperty('name'));// false
console.log('name' in person33); // true;( in 操作符)
console.log(hasPrototypeProperty(person33,'name'));// true
person33.name= 'Jack';// 来自实例
console.log(person33.hasOwnProperty('name'));// true;
console.log('name' in person33);// true;
console.log(hasPrototypeProperty(person33,'name'));// false
2,原型与in操作符
有两种方式使用in操作符:单独使用和在for-in循环中使用。 在单独使用中,in操作符会在通过对象能够访问到给定属性时返回true。无论该属性存在于实例中还是原型中。如上 <span style="font-size:18px;"> function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name) && (name in object);
}</span>
上面的函数可以确定属性是原型中的属性;如上 在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性,其中即包括存在于实例的属性,也包括存于原型中的属性。 屏蔽了原型中不可枚举属性(即将[[Enumerable]])标记为false的属性)的实例属性也会在for-in循环中返回; 因此根据规定,所有开发人员定义的属性都是可枚举的---只有在IE8以及跟早版本中例外。 要取得对象上所有课枚举的对象的实例属性,可以使用ECMAScript5 的 Object.keys() 方法。 浏览器支持情况:IE9+........... 这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。 function Person32(){
}
Person32.prototype.name = 'Andy';
Person32.prototype.age= 27;
Person32.prototype.sayName=function(){
console.log(this.name);
}
var keys=Object.keys(Person32.prototype);
console.log(keys);// ["name", "age", "sayName"]
var person35 = new Person32();
person35.name='Jack';
person35.age = 60;
var pKeys =Object.keys(person35);
console.log(pKeys);//["name", "age"]
这里keys中将保存一个数组,数组中是字符重“name”,“age”,“sayName”。这个顺序也是在他们在for-in循环中出现的顺序。 如果通过Person32的实例调用,则Object.keys()。返回的数组只包含'name','age'这两个实例属性 如果想得到所有实例属性,无论是否可枚举。都可以用Object.getOwnPropertyNames(); 浏览器支持情况:IE9+.......... var nKeys=Object.getOwnPropertyNames(Person32.prototype);
console.log(nKeys);// ["constructor", "name", "age", "sayName"]
3,更简单的原型语法
更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。 <span style="font-size:18px;"> function People(){
}
People.prototype={
name:'Andy',
age:27,
sayName:function(){
console.log(this.name);
}
}</span>
上面的代码,我将People.prototype设置为等于一个以对象字面量形式创建的新对象。最终结果相同,但是:constructor属性就不在指向People了。 我们使用的这种方法,本质上重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向People函数。 此时,尽管使用instanceof操作符还能返回正确的结果,但通过constructor已经无法确定对象的类型了 var student = new People();
console.log(student instanceof Object);// true
console.log(student instanceof People);// true;
console.log(student.constructor == People);//false
// 如果constructor的值很重要,可以这样写
function People1(){
}
People1.prototype={
constructor:People1,
name:'Andy',
age:27,
sayName:function(){
console.log(this.name);
}
}
4,原型的动态性
尽管可以随时为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来,但如果是重写了整个原型对象,情况就不一样了。 调用构造函数时会为实例添加一个指向最初原型的[[Prototye]]指针,而把原型修改为另一对象就等于切断了构造函数与最初原型之间的联系。 function People2(){
}
var student2 = new People2();
People2.prototype={
constructor:People1,
name:'Andy',
age:27,
sayName:function(){
console.log(this.name);
}
}
student2.sayName();// error
5,原生对象的原型
6,原型对象的问题
最大问题是有其共享的本性所导致的; function People3(){
}
People3.prototype={
constructor:People3,
name:'Andy',
age:27,
friends:["lucy",'lily'],
sayName:function(){
console.log(this.name);
}
}
var studen5 = new People3();
var studen6 = new People3();
studen5.friends.push('Lau');
console.log(studen5.friends); //["lucy", "lily", "Lau"]
console.log(studen6.friends);//["lucy", "lily", "Lau"]
console.log(studen5.friends == studen6.friends);// true
四:组合使用构造函数模式和原型模式
组合模式也是最常见方式; function Publc(name,age,job){
this.name=name;
this.age = age;
this.job=job;
this.friends=['lucy','lily'];
}
Publc.prototype={
constructor:Publc,
sayName:function(){
console.log(this.name);
}
}
var p1 = new Publc('Andy',27,'IT');
var p2 = new Publc('Jack',26,'Teacher');
p1.friends.push('Lau');
console.log(p1.friends);//["lucy", "lily", "Lau"]
console.log(p2.friends);//["lucy", "lily"]
五:动态原型模式
它把所有信息封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),有保持了同时使用构造函数和原型的有点 <span style="font-size:18px;"> function Work(name,age,job){
//属性
this.name=name;
this.age=age;
this.job=job;
//方法
if(typeof this.sayName !='function'){ // 这里只有在sayName()方法不存在的情况下,才会将它添加到原型中。
Work.prototype.sayName = function(){
console.log(this.name);
}
}
}
var n1 =new Work('Andy',27,'IT');
n1.sayName();</span>
六:寄生构造函数模式
这种模式基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象; function Factory(name,age,job){
var o= new Object();
o.name=name;
o.age=age;
o.job=job;
o.sayName=function(){
console.log(this.name);
}
return o;
}
var a1 = new Factory('Andy',27,'IT');
a1.sayName(); // "Andy";
除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的。 构造函数在不返回值的情况下,默认会返回新对象实例。而通过在构造函数的末尾添加return语句,可以重写调用构造函数时返回的值。 有一点需要说明:返回的对象与构造函数或者与构造函数原型属性之间没有关系;也即使说,构造函数返回的对象与在构造函数之外创建的对象没有什么不同; 因此,不能依赖instanceof操作符来确定对象类型。所有建议不要使用这种模式; 七:稳妥的构造函数模式
稳妥对象:指的是没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全的环境(这些环境会禁止使用this和new), 或者在防止数据被其他应用程序改动时使用。 稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一:新创建的对象实例方法不引用this;二:不使用new操作符调用构造函数; function Factory1(name,age,job){
var o= new Object();// 要返回的对象
//可以在这里定义私有变量和函数
o.sayName=function(){
console.log(name);
}
return o;// 返回对象;
}
var b1 =Factory('Andy',27,'IT');
b1.sayName(); // "Andy";
注意:与寄生构造函数模式类似,使用稳妥构造函数模式创建的对象与构造函数之间也没有什么关系,因此instanceof操作符对这种对象也没有什么意义;所以,尽量避开使用此模式;