黑铁时代
Programing is not only one kind of technology, but also one kind of art.

  上节介绍了工厂模式,这节的构造函数模式就是为了解决工厂模式中无法进行对象识别的缺点。

 

  工厂模式中,我们用new Object()的方式创建了一个Object类型的实例;当我们想创建一个Array类型的实例的时候,我们也可以用new Array()来完成。因此,我们也可以创建自己的构造函数,然后就了创建自己的类型的实例了。下面的例子就是构造函数方式:

  function Person( name, age ) {

    this.name = name;

    this.age = age;

    this.introduce = function () {

      alert ( 'Hello, my name is ' + this.name + ', and my age is ' + this.age + '.' );

    }

  }

  

  var p1 = new Person( 'Leo', 25 );

  p1.introduce(); // Hello, my name is Leo, and my age is 25.

  上面的示例就是构造函数模式了,它看起来就像申明一个函数,但是函数中的代码我们需要注意。用到了this,我们曾经提到this和函数的执行环境密切联系的。如果函数在全局作用域中执行,this就指向window;如果是在某个对象的作用域下执行,那么this就指向这个对象,上面例子中的this就指向了p1这个对象,因为introduce是在p1这个对象的作用域下执行的。

 

new 操作符的原理

  为什么我们使用 new 操作符之后就可以创建某个构造函数的事例了?因为当我们调用 new 操作符的时候,大致会经历如下一个步骤:

  1. 创建一个Person类型的实例(将实例的constructor指向Person,以及实例的原型指向Person的原型);

  2. 将构造函数的作用域添加到新对象中,即构造函数将在新实例的作用域下执行,那么this就指向这个新的实例;

  3. 执行构造函数中的代码,为this添加属性和方法,就等于为新的实例添加属性和方法;

  4. 执行完成后,返回这个新实例;

 

  在创建某个类型的实例的时候,都会为这个实例初始化constructor属性,这个属性会指向实例它的构造函数。于是我们就可以通过判断constructor属性判断对象的类型了。

  alert( p1.constructor === Person ) // true

  但是由于JavaScript属于动态语言,constructor属性可以任意改变,所以就不是百分之百可靠了,于是我们使用另一种更加可靠的方式就是instanceof操作符。instanceof操作符就是用来判断某个对象是不是某个类型的实例。由于所有类型的基类都是Object,所以所有的实例也都是Object类型的实例。

  alert( p1 instanceof Person ) // true

  alert( p1 instanceof Object ) // true

 

  如果我们不用new操作符,直接调用var p1 = Person( 'name', 25 );的话,Person就会像普通函数一样被调用,如果是在全局环境中,那么this就指向window了,就相当于在给window添加属性和方法,而且不会有任何返回值,那么p1就是undefined。使用new操作符就相当于将this指向了一个新的实例化的对象,在构造函数执行完成后将这个对象返回并赋值给p1。

 

  构造函数模式看起来是如此优秀,不仅封装了属性和方法,而且使用new操作符也让它看起来更像传统的面向对象的语言,同时还解决对象识别的问题。尽管如此,这种模式也存在自身的缺点,那就是在为对象创建方法的时候,每个对象都有自己方法的实例。比如我们在创建另一个Person对象p2,那么p1.introduce和p2.introduce是两个不同的Function类型的实例,但是它们的创建过程却是完全一样的。虽然这种情况并不会影响对对象的使用,但是会造成无谓的性能的损耗。

  假设我们要创建1000个Person类型的实例,那我们就要创建1000个Function类型的实例,而这1000个Function类型实例的创建方式却完全一样,这的确是没必要的。那么怎么解决这个问题?我们知道Function类型属于引用类型,可以存放在堆中的,我们就完全可以让所有Person类型的对象共享同一个Function类型的实例,这样就大大减少了内存的消耗,还可以减少对堆的操作(因为对引用类型的操作必定比起操作普通类型要慢),从而提高性能。所以我们需要做的就是在构造函数之外像普通函数一样申明introduce函数,然后在内部引用它即可。

  function Person( name, age ) {

    this.name = name;

    this.age = age;

    this.introduce = introduce; // 共享方法

  }

  function introduce () {

    alert ( 'Hello, my name is ' + this.name + ', and my age is ' + this.age + '.' );

  }

  这样处理之后,虽然解决性能问题,但是又引起了新的问题,JavaScript就是这么难搞!为了能可靠的共享到这些方法,那么最好就将他们定义成全局的函数,那么当需要大量方法的时候,代码就变得非常难维护了;而且将方法在外部,任何对象都可以使用,某个对象的方法却可以被任意使用,这就违背了面向对象封装的原则。不过软件大师巧妙的使用原型来解决了这个问题,下节将继续讨论面向对象中最重要的概念之原型模式。

posted on 2012-07-01 01:38  黑铁时代  阅读(239)  评论(0编辑  收藏  举报