JavaScript——创建对象的模式

最近了解了几种创建对象的模式,下面总结一下它们的区别。

  • 工厂模式
  • 构造函数模式
  • 原型模式
  • 组合构造函数模式和原型模式
  • 动态原型模式
  • 寄生构造函数模式
  • 稳妥构造函数模式

工厂模式

用函数封装以特定接口创建对象的细节。

function a (name) {
    var obj = new Object();
    obj.name = name;
    obj.hello = function () {
          alert("hello "+this.name);
    };
    return obj;
}

var a1 = a("one");
var a2 = a("two");

工厂模式解决了创建多个相似对象的问题,但是无法知道对象的类型。

构造函数模式

构造函数可以创建指定类型的对象。创建自定义的构造函数可以创建自定义对象的属性和方法。

function A (name) {
    this.name = name;
    this.hello = function () {
          alert("hello "+this.name);
    };
}
var a1 = new A("one");
var a2 = new A("two");

这里遵循构造函数命名规则第一个字母要大写,与工厂模式相比,构造函数模式没有显式创建对象,直接把属性和方法赋给this对象,同时也没有返回对象,在创建A对象时要使用new,这种方式的过程:1.创建一个新对象 2. 将构造函数的作用域赋给新对象(this指向这个对象)3.执行构造函数(新对象添加属性方法)4.返回对象。

a1和a2分别保存着A的一个不同的实例,两个对象里都有一个constructor(构造函数)属性指向A

alert(a1.contructor == A);//true
alert(a2.contructor == A);//true

检测对象类型最后还是用instanceof,a1、a2也是Object的实例。

alert(a1 instanceof A);//true
alert(a1 instanceof Object);//true

以这种方式定义的构造函数是定义在Global对象(在浏览器是window对象)的。

1.将构造函数当作函数
构造函数与其他函数的区别在于调用方式不同,任何函数,只要通过new调用就可以作为构造函数,不通过new调用就跟普通函数一样

//当作构造函数
var a = new A("one");
a.hello();//“hello one”
//普通函数
A("one");//添加到window
window.hello();//"hello one"
//在另一个对象的作用域中调用
var obj = new Object();
A.call(obj,"one");
obj.hello();//"hello one"

2.构造函数的问题
构造函数的问题就是每个方法都要在每个实例上重新创建一遍,a1、a2都有hello方法,但是两个方法不是同一个实例。函数也是对象,实例化函数也是实例化对象。也就是说a1和a2的hello方法并不相等。创建两个相同任务的function实例没有必要,而且有this对象,并不用在执行代码前就把函数绑定在特定对象上。

function A (name) {
    this.name = name;
    this.hello = hello;
}
function hello () {
      alert("hello "+this.name);
}
var a1 = new A("one");
var a2 = new A("two");

这样a1、a2 对象共享了在全局作用域定义的同一个hello()函数,解决了两个函数做同一件事的问题,但是这个全局函数只被某个对象调用,不符合全局的说法。而且如果方法很多,就要定义很多全局函数,那我们自定义的类型就没有封装性了。

原型模式

每个函数都有一个prototype属性,是一个指向对象的指针,使用这个属性我们就不必在构造函数中定义对象的实例信息,可以将这些信息直接添加到原型对象中。

function A(){
}
A.prototype.name = "张三";
A.prototype.hello = function(){    
 alert("hello " + this.name);
}

var a1 = new A();

a1.hello();      //"张三"
var a2= new A();

a2.hello();      //"张三"
alert(a1.hello == a2.hello);//true

属性和方法都添加在原型上,构造函数实为空函数,与纯构造函数创建的对象不同,这些属性和方式都是公用的(单例模式)这个是优点也是缺点,函数和基本值属性共享很好,但是如果是引用属性就有问题了。

function A(){}
A.prototype = {    
   constructor:A,    
   name:"张三",   
   cwu:["cat","dog"],    
   hello:function(){ 
      alert("hello " + this.name);  
   }
 }
var a1= new A();
var a2= new A(); 
a1.cwu.push("fish");
console.log(a1.cwu);   //["cat","dog","fish"]
console.log(a2.cwu);   //["cat","dog","fish"]
console.log(a1.cwu=== a2.cwu);   //true

因为共享,改变其中一个的实例就会改变全部的实例。

组合构造函数模式和原型模式

结合两者优点,这种模式也是比较常用的。既存在公共属性和方法,又可以自带私有参数

function A(name){     
   this.name = name; 
   this.cwu = ["cat","dog"];
}
A.prototype = {    
    constructor:A,     
     hello:function(){ 
      alert("hello " + this.name);  
   }
}

var a1= new A("张三");
var a2= new A("哈哈");
a1.cwu.push("fish");

console.log(a1.cwu);   //["cat","dog","fish"]
console.log(a2.cwu);   //["cat","dog"]

console.log(a1.cwu=== a2.cwu);   //false
console.log(a1.hello === a2.hello);   //true

动态原型模式

组合构造函数模式和原型模式的另一种形式,把初始化原型的操作放入了构造函数中并且仅在特殊条件情况下才会初始化原型

function A(name){     
   this.name = name; 
   this.cwu = ["cat","dog"];
   if(typeof this.hello!="function"){    
      A.prototype.hello= function(){     
        alert("hello " + this.name);
      }
   }
}

 var a1= new A("张三");

a1.cwu.push("fish"); 
a1.hello();
a1

只在第一次调用构造函数并且不存在hello方法时才会调用,hello方法能在所有实例中使用。
需要注意的是,之后如果重写hello方法,会覆盖所有原来创建的实例,所以不要轻易改动。
ps:可以使用instanceof操作符来确定他的类型。

寄生构造函数模式

需要创建一个自定义类型,以上模式都不适用的时候,可以尝试这种模式。这种模式只有创建一个封装创建对象的函数。

function A(name){
  var a = new Object();
  a.name = name; 
  a.hello= function(){
      alert("hello " + this.name);
  }    
  return a; 
}
var a= new A("张三");
a.hello();   //"张三"

//工厂模式
function a (name) {
    var obj = new Object();
    obj.name = name;
    obj.hello = function () {
          alert("hello "+this.name);
    };
    return obj;
}

var a1 = a("one");
var a2 = a("two");

我们可以看到寄生构造函数模式与工厂模式几乎是一样的,主要的区别:
1、寄生模式创建对象时使用了new关键字
2、寄生模式的外部包装函数是一个构造函数

构造函数在不返回值的情况下,默认返回对象的新实例。而通过在构造函数的末尾添加一个return 语句,可以重写调用构造函数是返回的值。

这种模式还可以为对象创建构造函数。假设我们想创建一个具有额外方法的特殊数组。由于不能直接修改Array构造函数,因此可以使用这个模式:

function SpecialArray() {
    //创建数组
    var array=new Array();
    //添加值  arguments获取的是实参,不是形参,所以SpecialArray()并没有形参接收传递过来的参数
    array.push.apply(array,arguments);
    array.toPipedString=function(){
        return this.join("|");
    }
    return array;
}
var colors=new SpecialArray("red","blue","black");
alert(colors.toPipedString());  //输出:red|blue|black

在这个构造函数里我们创建了一个数组对象,push()方法初始化了数组的值,同时添加了toPipedString()方法,最后返回这个数组对象。

ps:不能使用instanceof操作符来确定对象的类型,因为返回的对象与构造函数或者构造函数的原型没有任何关系。

alert(colors instanceof SpecialArray); //输出:false
alert(colors instanceof Array); //输出:true

稳妥构造函数模式

道格拉斯 * 克罗克福德 发明了JavaScript中的稳妥对象这个概念.所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合用在一些安全的环境中(这些环境会禁止使用new和this),或者防止数据被其他的应用改动。
稳妥构造函数与寄生构造函数模式类似,但是也有两点区别:

  • 稳妥模式不使用new操作符调用构造函数
  • 新创建对象的实例方法不引用this
function A(name,age) {
    //创建要返回的对象
    var o=new Object();
    //可以在这里定义私有变量和函数
    //添加方法
    o.hello=function(){
        alert("hello "+this.name);
    }
    //返回对象
    return o;
}
var a=A("张三",18);
a.hello();  //使用稳妥构造函数模式只能通过其构造函数内部的方法来获取里面的属性值

a变量里面保存的是一个稳妥对象,而除了调用他的hello()方法外,没有别的方法可以访问其数据成员。即使有其他的代码会给这个对象添加方法和数据成员,但也不可能有别的方法访问到传入到构造函数中的原始数据。稳妥构造函数模式提供的这种安全性,所以它非常适合在某些安全执行环境中。

ps:不能使用instanceof操作符来确定对象的类型,因为返回的对象与构造函数或者构造函数的原型没有任何关系。

你可能感兴趣的:(JavaScript——创建对象的模式)