原型对象

186 阅读5分钟

理解原型对象

       在JS中,每一个对象都有一个prototype属性,属性值为一个对象,初始值为空,每一个prototype属性指向的对象都包含唯一一个不可枚举属性constructor,该属性的值是一个对象,指向它所在的构造函数


       无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。

       创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性,其他方法都是从object继承而来的。当调用构造函数创建一个新的实例后,该实例的内部将包含一个指针(prototype)指向构造函数的原型对象。在脚本中没有标准的方式访问[[ prototype ]],但在浏览器控制台中可以console出来可以发现,每一个对象上都支持一个属性_proto_,而在其它实现中,这个属性对脚本则是完全不可见的,不过要明确的真正重要的一个电就是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间

创建对象

1、工厂模式

如下:

function createPerson( name, age, job ){
  var p = new Object();
  p.name = name;
  p.age = age;
  p.job = job;
  p.sayName = function (){
    alert(this.name);
  };
  return p;
}
var person1 = createPerson("Bob", 19, "Teacher");
var person1 = createPerson("Greg", 26, "Doctor");

函数createPerson()能够根据接受的参数来构建一个包含所有必要信息的Person对象,可以无数次的调用这个函数,而每次它都会返回一个包含三个属性和一个方法的对象。工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)

2、构造函数模式

用构造函数的模式可以把上面的例子进行如下重写:

function Person( name, age, job ){
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = function (){
    alert(this.name);
  }
}
var person3 = new Person("Jen", 19, "Teacher");
var person4 = new Person("Gen", 26, "Doctor");

工厂模式和构造函数的区别:

a、没有显式的创建对象

b、直接将属性和方法赋给了this对象

c、没有return语句

d、构造函数始终都应该以一个大写字母开头

要创建Person的新实例必须使用new操作符,以这种方式调用构造函数实际上会经历一下4个步骤:

(1)创建一个新对象

(2)将构造函数的作用域赋给新对象(因此this就指向了这个新对象)

(3)执行构造函数中的代码(为给这个新对象添加属性)

(4)返回新对象

在上述的例子中person3和person4分别都保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person

console.log(person3.constructor == Person);    //true
console.log(person4.constructor == Person);    //true

对象的constructor属性最初是用来标识对象类型的,但是检测对象类型我们一般用instanceof操作符,上述的例子中创建的对象person3和person4既是Object的实例,同时也是Person的实例

console.log(person3 instanceof Person);   //true
console.log(person3 instanceof Object);   //true

优点:创建自定义的构造函数意味着可以将它的实例标识为一种特定的类型,这就是构造函数模式胜过工厂模式的地方

缺点:使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。在上面的例子中,person3和person4都有一个名为sayName()的方法,但那两个方法却不是同一个Function的实例,函数都是对象,因此每定义一个函数,也就是实例化了一个对象。然而创建两个完成同样任务的Function实例的确没有必要,况且有this对象在,根本不用在执行代码前就把函数绑定到特定对象上面。因此我们可以进行如下改造,通过把函数定义转移到构造函数外部来解决这个问题:

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

function sayName() {
  alert(this.name);
}

我们把sayName()函数的定义转移到了构造函数外部,而在构造函数内部,我们将sayName属性设置成等于全局的sayName函数,这样一来,由于sayName包含的是一个指向函数的指针,因此person3和person4对象就共享了在全局作用域中定义的一个sayName()函数,这样做虽然能解决两个函数做同一件事的问题,但是,在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有些名不副实,更麻烦的是,如果对象需要定义多个方法,那么就要定义很多个这种全局函数,所以引入了原型模式来解决这些问题

3、原型模式

我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。简单讲,prototype就是指向通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法,也就是不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中,如下:

function Person( name, age, job ){

}
Person.prototype.name = "Bob";
Person.prototype.age = 32;
Person.prototype.job = "Doctor";
Person.prototype.sayName = function () {
  alert(this.name);
};

var person5 = new Person();
person5.sayName();    //Bob

我们把所有的属性都添加到了Person的prototype属性中,构造函数变成了空函数,但仍然可以通过调用构造函数来创建新对象,而且新对象还会具有相同的属性和方法,与构造函数模式不同的是,新对象的这些属性和方法是由所有实例共享的