创建对象

使用new Object()或者对象字面量都可以创建对象,但是这样创建的对象过于简单,不易于对象的属性与方法的扩展与继承。
下面讲的对象可以与JavaEE中的bean做类比。

工厂模式

对,首先可能想到的是使用设计模式中的工厂模式

function createPizza(type) {
  var o = new Object();
  o.type = type;
  o.bake = function() {
    alert('Start~');
    alert(this.type);
    alert('End~');
  };
  return o;
}

var cheesePizza = createPizza('cheese');
var veggiePizza = createPizza('veggie');
cheesePizza.bake();

优点

工厂模式解决了创建多个类似对象的问题

缺点

对象无法识别,即创建出来的对象无法通过instanceof等分析出属于哪种类型

构造函数模式

用构造函数可用来创建特定类型的对象

// 构造函数首字母遵循OO语言惯例进行大写
function Pizza(type) {
  this.type = type;
  this.bake = function() {
    alert('Start~');
    alert(this.type);
    alert('End~');
  };
}

var cheesePizza = new Pizza('cheese');
var veggiePizza = new Pizza('veggie');
cheesePizza.bake();

与工厂模式相比:

  1. 没有在方法中显示创造对象(o);
  2. 直接将属性与方法赋值给this;
  3. 没有return语句

在用new的时候,会经历一下4步:

  1. 创建一个新对象
  2. 将构造函数的作用域赋值给新对象(此时this指向新对象)
  3. 执行构造函数代码(为对象添加属性)
  4. 返回新对象
如果不使用new,将构造函数当做函数使用,则this指向Global对象(在浏览器中为window对象),当然,可以使用call方法来指定作用域,例如
var o = new Object();
Pizza.call(o, 'salty');
o.bake();

使用构造函数方法,每个实例对象都有一个constructor构造函数属性,该属性指向Pizza(使用对象字面量、工厂模式方法创建的对象该属性指向Object)

cheesePizza.constructor == Pizza

检查某个对象属于哪种类型,一般使用instanceof,cheesePizza同时属于PizzaObject(之所以属于Object,是因为所有对象均继承于Object)

cheesePizza instanceof Pizza;
cheesePizza instanceof Object;

优点

与工厂模式相比,构造函数模式能够识别出对象类型
与下面的原型模式相比,能够实现对象属性的互相独立,在引用类型属性上很有用

缺点

每个实例对象的方法都是独立的,导致方法不能够共享

原型模式

每个函数(不是实例对象)都有一个prototype属性,该属性是一个指针,指向一个对象,对象的用途是包含所有实例共享的属性和方法。prototype通过调用构造函数创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有实例对象共享属性与方法。

function Pizza() {

}

Pizza.prototype.type = 'original'
Pizza.prototype.bake = function() {
  alert('Start~');
  alert(this.type);
  alert('End~');
};

var cheesePizza = new Pizza();
cheesePizza.type = 'cheese';
var veggiePizza = new Pizza();
veggiePizza.type = 'veggie';

cheesePizza.bake();
veggiePizza.bake();

各个对象共享属性与方法,同时每个对象都可以建立自己的属性,并屏蔽掉原型对象的同名属性,因为共享属性与方法,所以以下等式成立

cheesePizza.bake == veggiePizza.bake

对象字面量重写原型对象

也可以通过对象字面量来重写整个原型对象:

Pizza.prototype = {
  type: 'original',
  bake: function() {
    alert('Start~');
    alert(this.type);
    alert('End~');
  }
}

这样完全重写,原型对象上的constructor属性不再指向Pizza函数(全新的constructor指向Object),不过不影响通过instanceof来识别对象类型。如果constructor特别重要的话,可以显式将它置为适当的值:

Pizza.prototype = {
  constructor: Pizza,
  type: 'original',
  bake: function() {
    alert('Start~');
    alert(this.type);
    alert('End~');
  }
}

不过这种方式会将constructor的属性特征变为可枚举,而默认情况下它是不可枚举的,如果想不可枚举,可以使用Object.defineProperty()方法。

原型的动态性

对原型对象的修改会体现在实例对象上,即使实例对象先被创建。但是通过对象字面量重写的原型对象则没有该动态性

优点

定义在原型对象上的属性,能够保证在各实例对象上的共享

缺点

对于引用类型的属性,各实例的共享会导致额外的问题。

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

整合构造函数模式与原型模式,构造函数模式用于定义实例属性,原型模式用于定义方法和共享属性。

动态原型模式

寄生构造函数模式

稳妥构造函数模式

各创建模式在Chrome浏览器中的表现

可以通过Chrome浏览器观察使用工厂模式创建的cheesePizza对象属性为:

cheesePizza
{type: "cheese", bake: ƒ}
  bake: ƒ ()
  type: "cheese"
  __proto__:
    constructor: ƒ Object()
    hasOwnProperty: ƒ hasOwnProperty()
    isPrototypeOf: ƒ isPrototypeOf()
    propertyIsEnumerable: ƒ propertyIsEnumerable()
    toLocaleString: ƒ toLocaleString()
    toString: ƒ toString()
    valueOf: ƒ valueOf()
    __defineGetter__: ƒ __defineGetter__()
    __defineSetter__: ƒ __defineSetter__()
    __lookupGetter__: ƒ __lookupGetter__()
    __lookupSetter__: ƒ __lookupSetter__()
    get __proto__: ƒ __proto__()
    set __proto__: ƒ __proto__()

使用构造函数模式创建cheesePizza对象属性为:

cheesePizza
Pizza {type: "cheese", bake: ƒ}
  bake: ƒ ()
  type: "cheese"
  __proto__:
    constructor: ƒ Pizza(type)
    __proto__:
      constructor: ƒ Object()
      hasOwnProperty: ƒ hasOwnProperty()
      isPrototypeOf: ƒ isPrototypeOf()
      propertyIsEnumerable: ƒ propertyIsEnumerable()
      toLocaleString: ƒ toLocaleString()
      toString: ƒ toString()
      valueOf: ƒ valueOf()
      __defineGetter__: ƒ __defineGetter__()
      __defineSetter__: ƒ __defineSetter__()
      __lookupGetter__: ƒ __lookupGetter__()
      __lookupSetter__: ƒ __lookupSetter__()
      get __proto__: ƒ __proto__()
      set __proto__: ƒ __proto__()

使用原型模式创建cheesePizza对象属性为:

cheesePizza
Pizza {type: "cheese"}
  type: "cheese"
  __proto__:
    bake: ƒ ()
    type: "original"
    constructor: ƒ Pizza()
    __proto__:
      constructor: ƒ Object()
      hasOwnProperty: ƒ hasOwnProperty()
      isPrototypeOf: ƒ isPrototypeOf()
      propertyIsEnumerable: ƒ propertyIsEnumerable()
      toLocaleString: ƒ toLocaleString()
      toString: ƒ toString()
      valueOf: ƒ valueOf()
      __defineGetter__: ƒ __defineGetter__()
      __defineSetter__: ƒ __defineSetter__()
      __lookupGetter__: ƒ __lookupGetter__()
      __lookupSetter__: ƒ __lookupSetter__()
      get __proto__: ƒ __proto__()
      set __proto__: ƒ __proto__()

参考文章

ESLint 需要约束 for-in (guard-for-in)

个人不定期更新主页


莫显辉
92 声望2 粉丝

碰到的一点知识总结