js面向对象设计---创建对象的方式

一、Object构造函数或对象字面量

const obj = new Object();
const obj1={
  name: 'xxx'
}
  • 缺点: 使用同一个接口创建很多对象,会产生大量的重复代码。

二、工厂模式

function createPerson(name, age, job){    
  const o = new Object();   
  o.name = name;    
  o.age = age;   
  o.job = job;    
  o.sayName = function(){  alert(this.name);    };    
  return o;
}
const person1 = createPerson("Nicholas", 29, "Software Engineer");

函数createPerson()能够根据接受的参数来构建一个包含所有必要信息的Person对象。可以无数次地调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象。

  • 优点:然解决了创建多个相似对象的问题
  • 缺点:没有解决对象识别的问题(即怎样知道一个对象的类型)

三、构造函数模式

function Person(name, age, job){    
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = function(){
    alert(this.name);    
  };
}
const person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
  • 构造函数始终都应该以一个大写字母开头.
  • 构造函数与其他函数的唯一区别,就在于调用它们的方式不同。

要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4个步骤:
(1)创建一个新对象;
(2)将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
(3)执行构造函数中的代码(为这个新对象添加属性);
(4)返回新对象。

person1和person2分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person,如下所示。

alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
alert(person1 instanceof Object); // true
alert(person1 instanceof Person); // true
alert(person2 instanceof Object); // true
alert(person2 instanceof Person); // true

缺点:每个方法都要在每个实例上重新创建一遍。不是同一个Function的实例
创建两个完成同样任务的Function实例的确没有必要;
况且有this对象在,根本不用在执行代码前就把函数绑定到特定对象上面。
因此,大可像下面这样,通过把函数定义转移到构造函数外部来解决这个问题。

function sayName()
    alert(this.name);    
};
function Person(name, age, job){    
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = sayName
}

新问题:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。而更让人无法接受的是:如果对象需要定义很多方法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。好在,这些问题可以通过使用原型模式来解决。

四、原型模式

我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

function Person(){}

Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
  alert(this.name);
};
const person1 = new Person();
//"Nicholas"person1.sayName();var person2 = new Person();
  • 优点: 可以让所有对象实例共享它所包含的属性和方法。
  • 缺点:它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。原型模式的最大问题是由其共享的本性所导致的。原型中所有属性是被很多实例共享的,这种共享对于函数非常合适。对于那些包含基本值的属性倒也说得过去,通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。然而,对于包含引用类型值的属性来说,问题就比较突出了。
function Person(){}

Person.prototype = { 
  constructor: Person,
  name : "Nicholas",
  age : 29,
  job : "Software Engineer",
  friends : ["Shelby", "Court"],
  sayName() { console.log(this.name);}
};
var person1 = new Person();
var person2 = new Person();person1.friends.push("Van"); 
//"Shelby,Court,Van" 
console.log(person1.friends);
//"Shelby,Court,Van" 
console.log(person2.friends);
//true
console.log(person1.friends === person2.friends);

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

目前在ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法。
创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。

  • 1.构造函数模式用于定义实例属性,
  • 2.原型模式用于定义方法和共享的属性。结

每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。另外,这种混成模式还支持向构造函数传递参数

function Person(name, age, job){    this.name = name; 
 this.age = age; 
 this.job = job; 
 this.friends = ["Shelby", "Court"];
}

Person.prototype = { 
  constructor : Person, 
  sayName : function(){ alert(this.name); }
}

const person1 = new Person("Nicholas", 29, "Software Engineer");
const person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends);//"Shelby,Count,Van"
alert(person2.friends);//"Shelby,Count"
alert(person1.friends === person2.friends);//false
alert(person1.sayName === person2.sayName);//true

五、动态原型模式

把所有信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。

function Person(name, age, job){    //属性    
  this.name = name;
  this.age = age;
  this.job = job;
  //方法     
  if (typeof this.sayName != "function"){ 
    Person.prototype.sayName = function(){
      alert(this.name);
   };
  } 
}

const friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

使用动态原型模式时,不能使用对象字面量重写原型。前面已经解释过了,如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。

六 寄生构造函数模式

基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;

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;
}

const friend = new Person("Nicholas", 29, "Software Engineer");
//"Nicholas"
friend.sayName();

返回的对象与构造函数或者与构造函数的原型属性之间没有关系;

七 稳妥构造函数模式

所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者在防止数据被其他应用程序(如Mashup程序)改动时使用。稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。

function Person(name, age, job){    
  //创建要返回的对象    
  var o = new Object();    
  //可以在这里定义私有变量和函数   
   //添加方法    
  o.sayName = function(){       
   alert(name);    
  };    
//返回对象   
 return o;

注意,在以这种模式创建的对象中,除了使用sayName()方法之外,没有其他办法访问name的值。可以像下面使用稳妥的Person构造函数。

var friend = Person("Nicholas", 29, "Software Engineer");
//"Nicholas"
friend.sayName();

这样,变量person中保存的是一个稳妥对象,而除了调用sayName()方法外,没有别的方式可以访问其数据成员。

参考文献:《javascript 高级编程 第三版》

你可能感兴趣的:(js面向对象设计---创建对象的方式)