面向对象的程序设计之创建对象

一、理解原型对象


理解原型对象.jpg

  • 实例上的属性和原型上的属性的关系
    • 读取对象属性时,会先看实例上是否有这个属性,如果没有再看原型上是否有这个属性
    • 实例只能访问原型对象上的值,而不能通过对象实例重写原型中的值
    • 在对象实例上定义属性,会屏蔽原型对象中保存的同名属性
    • 只有使用delete删除对象实例上的属性,才能访问到原型对象上的属性
  • 操作属性的一些方法

    • isPrototypeOf():判断实例的[[prototype]]是否指向某个函数的原型对象

      1
      Person.prototype.isPrototypeOf(person1) // true
    • hasOwnProperty():判断一个属性是否存在于实例中

      1
      2
      person1.hasOwnProperty('name') //false
      person2.hasOwnProperty('name') //true
    • in操作符:判断实例对象是否有某个属性(属性可以在实例上或原型对象上)

    • for-in:会返回实例对象和原型对象上所有可枚举的对象
    • Object.key():只会返回实例对象上的所有可枚举的对象
    • Object.getOwnPropertyNames():返回实例对象上所有对象(不管是否可枚举)
  • 给prototype赋值的影响

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function Person(){}
    // 以下写法,prototype的指向变化
    Person.prototype = {
    name: 'TT',
    age: 12
    }
    // 声明实例后,实例的类型还是Person,但是constructor的已经不再指向构造函数
    var person = new Person()
    person instanceof Person // true
    person.constructor == Person // false
    person.constructor == Object // true
    // 修正:可以定义constructor属性,让其指向Person
    // 但是这样会将constructor的`[[Enumerable]]`属性设置为`true`。
    // 默认的constructor是不可枚举的。可以用`defineProperty()`定义constructor属性
    Object.defineProperty(Person.prototype, 'constructor', {
    enumerable: false,
    value: Person
    })

二、创建对象

方法 优缺点
Object构造函数或对象字面量 如果创建多个相似的对象,会产生大量的重复代码
工厂模式 封装了创建对象的细节,但是没有解决对象类型识别的问题(创建的所有对象类型都只是object
构造函数模式 每个方法都要在每个实例上重新创建一遍
原型模式 属性和方法由所有实例共享,但是如果共享属性是引用类型,这个属性的值会被所有实例修改
组合使用构造函数模式和原型模式 构造函数模式定义实例属性,原型模式定义方法和共享的属性
动态原型模式 动态给原型对象添加方法
寄生构造函数模式 用于拷贝一个对象的副本,创建的对象与构造函数的原型对象没有任何关系,不能确定实例对象的类型
稳妥构造函数模式 不适用this和new关键字,用于一些安全环境中

1.工厂模式

1
2
3
4
5
6
7
8
9
10
11
function createPerson (name, age) {
var o = new Object()
o.name = name
o.age = age
o.sayName = function () {
console.log(o.name)
}
return o
}
var person1 = createPerson('TT', 12) // {name: 'TT', age: 12, sayName: [Function]}
var person2 = createPerson('JJ', 13) // {name: 'JJ', age: 12, sayName: [Function]}

2.构造函数模式

  • 使用构造函数模式创建对象,必须使用new操作符,其创建的过程为
    • 1)创建一个新对象
    • 2)将构造函数的作用域赋给新对象(因此this指向这个新对象)
    • 3)执行构造函数中的代码(为这个新对象添加属性)
    • 4)返回新对象
  • constructor属性:使用new操作符创建的对象都有这个属性,这个属性指向创建这个对象所用的构造函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function Person (name, age) {
    this.name = name
    this.age = age
    this.sayName = function () {
    console.log(this.name)
    }
    /*
    在创建实例时,上面sayName的创建逻辑上是如下创建的,所以每个实例上sayName方法不相等
    this.sayName = new Function('console.log(this.name)')
    */
    }
    var person3 = new Person('CC', 12) //{ name: 'CC', age: 12, sayName: [Function] }
    var person4 = new Person('MM', 12) //{ name: 'MM', age: 12, sayName: [Function] }
    person3.constructor == Person // true
    person4 instanceof Object // true
    person4 instanceof Person // true

3.原型模式

  • 每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象(这个对象被称为原型对象,它的所有属性和方法被所有实例共享)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function Person () {
    }
    Person.prototype.name = 'FF'
    Person.prototype.age = 12
    Person.prototype.sayName = function () {
    console.log(this.name)
    }
    var person1 = new Person2()
    var person2 = new Person2()
    person2.name = 'CC'

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

  • 构造函数模式定义实例属性,原型模式定义方法和共享的属性
  • 创建实例的过程
    • 1)创建一个新对象
    • 2)将构造函数的作用域赋给新对象(因此this指向这个新对象)
    • 3)执行构造函数中的代码(为这个新对象添加属性)
    • 4)返回新对象(这个新对象的[[prototype]]指向构造函数的原型对象)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      function Person3 (name, age) {
      this.name = name
      this.age = age
      }
      Person3.prototype.sayName = function () {
      console.log(this.name)
      }
      const person8 = new Person3('KK', 12)
      console.log(person8)

5.动态原型模式

  • 通过检查某个方法是否存在,来决定是否要在原型上添加该方法
    1
    2
    3
    4
    5
    6
    7
    function Person4 (name, age) {
    this.name = name
    this.age = age
    if (typeof this.sayName !== 'function') {
    Person.prototype.sayName = function () {}
    }
    }

6.寄生构造函数模式

  • 使用new 操作符,如果函数没有返回值,使用新对象作为其返回值。该函数有返回值,就使用返回值。
  • 由于该返回值与构造函数的原型对象没有任何关系,所以无法确定对象的类型
    1
    2
    3
    4
    5
    6
    7
    8
    function Person5 (name, age) {
    var o = new Object()
    o.name = name
    o.age = age
    o.sayName = function () {}
    return o
    }
    var person10 = new Person5('MM', 12)

7.稳妥构造函数模式

  • 稳妥对象:没有公共属性,而且其方法不引用this的对象
  • 与寄生构造函数模式不同之处:
    • 实例方法不引用this
    • 不适用new操作符调用构造函数
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      function Person6 (name, age) {
      var o = new Object()
      // 定义私有变量或函数
      var money = 100000
      // 暴露出去的方法不引用this
      o.sayName = function () {
      console.log(name)
      }
      return o
      }
      // 变量person12中保存着一个委托对象,除了调用sayName外,没有别的方式可以访问其他数据成员
      const person12 = Person6('XX', 12)
      person12.sayName()