您的位置:首页 > Web前端 > JavaScript

创建JavaScript对象的各种模式和各自的特点

2018-03-10 19:55 453 查看
    在js中创建一个对象的方法有很多种,你可以随时new一个新对象出来,但是如果需要创建很多类似的对象,一个一个去new就太麻烦了。为了解决这个问题,前人摸索出了很多创建对象的模式,这些模式各有利弊,现在就来总结下都有哪些模式,每个模式都有什么特点。

    工厂模式:

    每次创建对象都要new Object(),然后给对象赋予属性和方法,把这些工作封装为一个函数怎样?自然而然地人们开始这么做。function createObj(){
var o = new Object(a,b,c);
o.a = a;
o.b = b;
o.c = c;
o.all = function(){
return a+b+c;
}
return o;
}
var o1 = creatObj(1,2,3);
var o2 = creatObj(4,5,6);    把创建对象的过程用函数封装了起来,只需要传入各个属性的值,就能创建出一个对象,多次调用就会创建多个拥有相同属性和方法的对象来。

    但是创建的对象之间是没有任何关系的,它们仅仅只是相似而已。它们相互独立,之间没有任何影响。它们都直接是Object的实例,知道这点也并没有什么卵用,因为所有对象都是Object的实例。

    构造函数模式:

    为了向其他面向对象的语言看齐(主要是java),JavaScript允许用构造函数的形式创建出“Class”。通过同一个构造函数创建出的对象,都是这个构造函数的实例。 
function Person(name,age){
this.name = name;    //this会根据函数运行时的环境指向不同的对象
this.age = age;
this.getName = function(){
alert(this.name);
}
}
Person("王五","15");    //如果直接在全局环境中运行这个函数,则this指向的是window
var person1 = new Person("张三","20");    //new操作会改变this的指向,this指向了新创建的对象
var person2 = new Person("李四","18");
console.log(window.name);    //王五
console.log(person1.name);    //张三
console.log(person2.name);    //李四
    对象的constructor属性指向了其构造函数,即Person。person1.constructor == Person //true
person2.constructor == Person //true
person1 instanceof Object //true
person1 instanceof Person //true    所有对象都继承自Object,是Object的实例。同时person1和person2又是Person的实例。
    构造函数模式有什么弊端呢?所有实例对象中的方法都是一个个独立的函数,方法之间没有关系,每次new出一个对象都会同时new出一个或者多个函数对象。person1.getName == person2.getName //false    我们可以把方法放在构造函
4000
数外边定义来解决这个问题,让所有实例都共享同一套方法。但是这样做不仅会污染全局环境,而且这违背了封装性。function Person(name,age){
this.name = name;
this.age = age;
this.getName = getName;
}
//将函数定义在全局环境,不仅仅对象的实例可以调用,实际上可以在任何地方调用,这根本不像是一个实例对象的方法。
function getName(){
console.log(this.name);
}

    原型模式:

    每个函数都有一个原型属性,这个属性是一个指向一个对象的指针。我们的构造函数也有原型(prototype),经过构造函数创建出的对象实例中,会继承构造函数原型中的属性和方法。直接看例子。function Person(){}; //空的构造函数

/*在构造函数的原型中定义属性和方法*/
Person.prototype.name = "张三";
Person.prototype.age = 18;
Person.prototype.getName = function(){
console.log(this.name);
};

var p1 = new Person();
p1.getName(); //"张三",共享的是原型中的属性值

var p2 = new Person();
p2.getName(); //"张三",共享的是原型中的属性值

p1.getName == p2.getName //true,实例中的方法是共享的一个函数

/*修改原型中的属相值,会在对象实例中同步体现*/
Person.prototype.name = "李四";
p1.getName();// "李四"    如果在某个实例中修改属性的值,则会屏蔽原型中的属性值,访问这个实例中相应的属相时,返回的将是修改后的值,同时别的实例中的属性值依然是继承自原型。如果想恢复其指向原型,只能用delete操作符删除该实例属性,暴露出原型属性。
    原型模式创建的对象实例实现了高度的共享,但这并不是什么好事。如果包含引用类型值的属性,在一个实例上修改这个属性的值,可能在其余属性中也有体现。function Person(){};

Person.prototype.name = "张三";
Person.prototype.age = 18;
Person.prototype.likesColors = ["black","red","blue"];
Person.prototype.getName = function(){
console.log(this.name);
};

p1 = new Person();
p2 = new Person();

console.log(p1.likesColors);//"black","red","blue"
console.log(p2.likesColors);//"black","red","blue"

/*修改一个实例的引用类型属性,会同步刷新至其余的实例属性*/
p1.likesColors.push("green");
console.log(p2.likesColors);//"black","red","blue","green"
    一般情况下,实例化出对象后,应该有属于自己的全部属性的,如果一切都是共享的,实例就失去了独立性。

    组合使用构造函数模式和原型模式:

    为了集两家之长,我们用构造函数模式定义实例属性,用原型模式定义方法和需要共享的属性。function Person(name,age,likesColors){
this.name = name;
this.age = age;
this.likesColors = likesColors;
}

Person.prototype = {
getName : function(){
console.log(this.name);
}
}

var p1 = new Person("张三",18,["red","black"]);
var p2 = new Person("李四",20,["red","black","green"]);

p1.likesColors.push("blue");
console.log(p1.likesColors); //"red","black","blue"
console.log(p2.likesColors); //"red","black","green"
p1.getName == p2.getName; //true
p1.likesColors == p2.likesColors; //false
    这样,每个实例都有属于自己的属性副本,修改实例的属性也不会影响到别的实例,实例的方法又统一从原型获取。该模式是目前使用最广泛的模式。

    动态原型模式:

    为什么要把构造函数与原型分开?能不能更好的封装起来?把所有的信息都放在构造函数里边行不行?为了与其他的面向对象的编程语言看齐,动态原型模式诞生了。看代码:function Person(name,age,sex){
this.name = name;
this.age = age;
Person.prototype.getName = function(){
console.log(this.name);
}
}

var p1 = new Person("张三",18,"男");
var p2 = new Person("李四",20,"女");
p1.getName();
p2.getName();    我们来看看实例化对象的时候会发生什么。
    1、创建一个新的空对象。
    2、将this指向了这个对象。
    3、执行构造函数中的代码(每次实例化都会全部执行)。
    4、返回这个对象。
    问题来了,每次实例化对象的时候,都会运行一次构造函数中的代码,运行属性赋值代码是应该的,可是每次都会运行原型修改代码。原型确定后,以后的实例化就不需要再运行了,可以加一个判断条件:function Person(name,age,sex){
this.name = name;
this.age = age;
//只在第一次实例化对象的时候会运行,之后的实例化时因为已经存在了这个方法,就不会再次运行里边的代码了
if(typeof this.getName != "function"){
Person.prototype.getName = function(){
console.log(this.name);
}
}
}

    寄生构造函数模式:

    把工厂模式的代码拿过来稍加改造就是寄生构造函数模式。function Person(name,age){
var o = new Object();
o.name = name;
o.age = age;
o.getName = function(){
console.log(this.name);
}
return o;
}
var p1 = new Person("张三",18);
p1.getName(); //"张三"
console.log(p1 instanceof Person); //false    new操作符会做些什么?参考动态原型一节。构造函数在不返回值的情况下会默认返回新对象的实例,如果构造函数有返回值,则执行返回值。除了使用了new操作符,这个模式和工厂模式一毛一样,同样不能用instanceof去识别对象的类型。
    如果想创建一个数组,使其有自己定义的方法,可以用这个模式。function ArrayToString(){
var o = new Array();
o.push.apply(o,arguments);
o.toArrString = function(){
return this.join("|");
}
return o;
}

var arr1 = new ArrayToString("a","b","c"); //用new操作符来运行
console.log(arr1.toArrString()); //"a|b|c"
var arr2 = ArrayToString("d","e","f"); //不用new,直接调用函数
console.log(arr2.toArrString()); //"d|e|f"    我们发现用new操作符来创建对象和不用new的效果是一样的。不知道在什么情况下会用这种模式,难道仅仅是为了让创建对象更像是在“创建对象”?

    稳妥构造函数类型:

    稳妥就是安全,安全意味着对象的属性不能随意改变,不能改变意味着不能用"."来访问安全属性和修改安全属性,所以在构造函数中就不能用this关键字(不使用new操作符调用构造函数)。function Person(name,age,sex){
var o = new Object();
o.getName = function(){
return name;
};
o.getAge = function(){
return age;
}
o.getSex = function(){
return sex;
}
return o;
}

var p1 = Person("张三",18,"男");
console.log(p1.getName()); //"张三"
console.log(p1.getAge()); //18
console.log(p1.getSex()); //"男"
console.log(p1.name); //undefined    除了通过对象的获取器函数外,没有别的方法可能访问传入到构造函数中的原始数据。但是可以在对象实例上增加属性,这样的属性可以随意访问。这种模式适合在某些安全环境使用。
    这种模式也不能用instanceof操作符判断对象的类型。

参考《Javascrip高级程序设计(第三版)》[美]Nicholas C.Zakas
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: