总结这几天啃高程(Javascript 高级程序设计)中看到的几个创建对象的方式,及其优缺点。

面向对象

ES 中有两种属性,分别为数据属性和访问器属性。

  • 数据属性,包含数据值位置,可读取写入,含以下四个特性:
    • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,默认值:true
    • [[Enumerable]]:表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定义的属性,默认值:true
    • [[Writable]]:表示能否修改属性的值。可直接在对象上定义属性,默认值:true
    • [[Value]]:包含这个属性的数据值。读取属性值时,从这个位置读;写入属性值时,把新值保存在此,默认值:undefined。例:var person = { name: "Nick" }; [[Value]] = "Nick"
  • 访问器属性,不包含数据值,具有gettersetter 函数。分别作读取和写入时用,分别负责返回有效值和决定如何处理数据,具有四个属性:
    • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值:true
    • [[Enumerable]]:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值:true
    • [[Get]]:在读取属性时调用的函数。默认值:undefined
    • [[Set]]:在写入属性时调用的函数。默认值:undefined
  • 访问器属性,必须用Object.defineProperty() 定义;数据属性的默认值,也需要使用前者的函数来定义。定义多属性,在 ES5 中可以使用Object.defineProperties() 方法。读取属性,可以使用Object.getOwnPropertyDescriptor() 方法。

工厂模式

  • 函数封装对象所有细节,但每次调用此类函数,均返回函数内所有属性和方法
  • 解决创建多个相似对象的问题
  • 没有解决对象识别问题(如何知道对象的类型?)
function createPerson(name, age, job) {
  var o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.sayName = function() {
    alert(this.name);
  };
  return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

构造函数模式

  • 函数内不用 new 显式创建对象;属性方法直接赋值于 this;无 return 语句
  • this 巨坑,建议与原型模式组合使用
  • 调用时四步骤
    • 创建新对象
    • 作用域赋值于新对象
    • 执行函数代码(新对象添加属性)
    • 返回新对象
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
  this.sayName = function() {
    alert(this.name);
  };
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

原型模式

  • 利用函数中特有的prototype 属性,它是一个指针,指向一个对象。对象包含特定类型的所有实例共享属性及方法
  • 需要区别单一赋值和属性赋值的区别。(有无 constructor
  • 关于原型链,翻阅此链接了解详情
function Person() { }
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function() {
  alert(this.name);
};

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

  • 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性
  • 每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存
  • 支持向构造函数传递参数;是用来定义引用类型的一种默认模式
function Person(name, age, job) {
  this.name = name;
  this.age = age;
  this.job = job;
}
Person.prototype = {
  constructor : Person,
  sayName : function() {
    alert(this.name);
  }
}

动态原型模式

  • 将所有信息封装至构造函数中,在其中初始化原型
  • 保持了同时使用构造函数和原型的优点
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);
    };
  }
}

寄生构造函数模式

  • 函数写法同工厂模式相同
  • 使用 new ,把使用的包装函数叫做构造函数
  • 构造函数在不返回值的情况下,默认返回新对象实例
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;
}
var friend = new Person("Nicholas", 29, "Software Engineer");

稳妥构造函数模式

  • 稳妥对象:没有公共属性,方法不应用 this 的对象
  • 适合在一些安全环境中,或者在防止数据被其他应用程序改动时使用
  • 遵循与寄生构造函数类似的模式,但有两点不同
    • 新创建对象的实例方法不引用 this
    • 不使用 new 操作符调用构造函数
function Person(name, age, job) {
  var o = new Object();
  //可以在这里定义私有变量和函数
  o.sayName = function() {
    alert(name);
  };
  return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");

上例中,除了调用 sayName() 方法外,没有其他方式能够访问其数据成员。

Class (ES6)

  • ES6 引入此概念,作为对象的模板。可以看作只是一个语法糖。
  • class 写法只是让对象原型写法更加清晰,更像面向对象编程的语法。
class Person{
  constructor(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
  }
  sayName() {
    alert(name);
  }
}
var friend = new Person("Nicholas", 29, "Software Engineer");
  • t​​ypeof 某个 class 的时候,结果为 function,可以看作构造函数的另一种写法。
typeof Person // "function"
  • prototype 对象的 constructor 属性,直接指向 class 本身,与 ES5 行为一致。
Person.prototype.constructor === Person // true
  • class 内部所有定义的方法,均不可枚举[[Enumerable]],下例中的 sayName 方法为内部定义方法,不可枚举,与 ES5 的行为不一致。
Object.keys(Person.prototype) // []
Object.getOwnPropertyNames(Person.prototype) // ["constructor","sayName"]