JavaScript 建立对象

建立对象

了解如何建立对象有助于咱们理解如何继承,和对象的 prototype 属性
建立对象的方法有许多种,后来的方法解决了前者的缺点,了解这些递进的关系有助于帮助咱们更好地了解对象的工做模式git

工厂模式

最开始的模式,即经过一个函数,用户能够选择传入一些参数,而后函数返回一个新的对象完成对象的建立github

function createPerson (name, age, job) {
  let o = new Object()
  o.name = name
  o.age = age
  o.job = job
  o.sayName = function () {
    console.log('`Hello my name is ${this.name}`')
  }
  return o
}
let person1 = createPerson('Nicholas', 29, 'Software Engineer')
let person2 = createPerson('Greg', 27, 'Doctor')
复制代码

显而易见的是能够屡次调用该函数获得多个具备类似的对象,可是其存在一些缺点:
数组

  1. 咱们没法知道上述的 person1 究竟是不是 createPerson 的实例,由于 person1prototypeObject.prototype
  2. 咱们每建立一个对象都要新建一个 sayName 方法属性,即 person1.sayName === person2.sayName // false

构造函数模式

构造函数模式解决了工厂模式的第一个问题,即 能够确认一个实例是否为一个 的对象bash

function Person (name, age, job) {
  this.name = name
  this.age = age
  this.job = job
  this.sayName = function () {
    console.log('`Hello my name is ${this.name}`')
  }
}
let person1 = new Person('Nicholas', 29, 'Software Engineer')
let person2 = new Person('Greg', 27, 'Doctor')
复制代码

这里咱们使用 new 操做符来实例化一个对象时发生了如下几件事:
函数

  1. 新建一个对象
  2. 将构造函数的 this 指向新对象
  3. 执行构造函数中的代码
  4. 返回新对象

同时 person1prototype 被赋值为 Person.prototype 所以使用 person1 instanceof Person 时其实在判断 Person.prototype 是否出如今了 person1 的原型链之中,便可判断 person1Person 的实例。
可是构造函数模式依然没有解决工厂模式的第二个问题。ui

原型模式

原型模式的存在即解决了上述所说的 第二个问题 它使得一个类的全部实例共享一些属性。而在那以前咱们须要了解什么是原型对象,即上文提到的 prototypethis

prototype

这里有几个点本身以为比较重要的,对于我本身理解何为原型比较有帮助
spa

  1. prototype是一个对象
  2. 实例将包含一个属性,这个属性是一个指向了构造函数的原型对象的指针
  3. 实例中能够获取到的属性分两种,一种 实例属性 一种是 原型属性

接下来再讨论原型对象:
prototype

  1. 只要建立了一个函数,就会为该函数建立一个 prototype 属性,这个属性指向一个对象即该函数的原型对象
  2. 默认状况下全部的原型对象都会自动得到一个 constructor 属性,这个属性是一个 指向 prototype 所在函数的指针。即 Person.prototype.constructor 指向了 Person
  3. 当调用构造函数建立一个实例后,该实例的内部将包含一个指针,指向了构造函数的原型对象。

代码示例以下:指针

function Person () {}

Person.prototype.name = 'Nichalos'
Person.prototype.age = 29
Person.prototype.job = 'Software Engineer'
Person.prototype.sayName = function () {
  console.log('Hello my name is ' + this.name)
}

let person = new Person()
复制代码

以上代码表现成原型状态以下,没有上图只有文字描述,多读几遍其实文字也来得精妙啦:

  1. Person.prototype 指向了一个对象(如下简称原型对象,这里时刻牢记是一个对象),即 Person 的原型对象
  2. Person 的原型对象包含一系列属性:第一个即默认状况下被赋予的 constructor 属性,该属性是一个指向构造函数 Person 的指针,因此 Person.prototype.constructor 值为 Person;剩下的属性即代码中定义的各个属性(nameagejobsayName),因此这个原型对象的值为:
{
 constructor: Person,
 name: 'Nichalos',
 age: 29,
 job: 'Software Engineer',
 sayName: Function
}
复制代码
  1. 实例出 person 实例后,person 被赋予了一个内部属性,这个属性指向了第2步中的原型对象,一般这个对象是 __proto__。值得注意的是,虽然咱们只是实例化 person,并无未其添加属性和方法,可是却能够经过搜索原型链 访问 __proto__ 中的属性和方法。我本身将其称做 原型属性

以上描述了原型对象的由来、理清楚了 实例构造函数原型对象 的关系:实例和构造函数没什么关系,可是实例有一个属性指向了原型对象,而原型对象是属于构造函数的,因此三者存在了联系。而接下来咱们要讨论一些关于原型对象的方法:

hasOwnProperty(propertyName: String) // 返回实例是否具备该实例属性(即实例自己的属性)
in // 操做符,'name' in person,这个操做符只要对象可以访问到该属性就返回true,不管是实例属性仍是原型属性
Object.keys(obj: Object) // 该方法会返回一个包含全部可枚举属性的字符串数组
Object.getOwnPropertyNames(obj: Object) // 该方法会的到全部实例的属性不管是实例的属性仍是原型属性,也不论是否可枚举
复制代码

接着是原型的特性:

  1. 为实例添加属性会屏蔽原型中的同名属性而不是改写
  2. 原型具备动态性,当咱们对原型对象作出修改时,可以马上从实例上反应出来,就算是先建立了实例,再修改了原型,以前建立的实例的 [[prototype]] 依然会反应出来
  3. 若是先建立了实例,就不能切断原本原型和构造函数之间的联系,即不能重写构造函数的 prototype 属性例以下:
function Person () {}

Person.prototype.name = 'Nicholas'
Person.prototype.age = 22
Person.prototype.sayName = function () {
  console.log(`Hello, my name is ${this.name}`)
}

let person = new Person()
person.sayName() // Hello, my name is Nicholas

person.name = 'Bob'

person.sayName() // Hello, my name is Bob

Person.prototype = {
  constructor: Person,
  name: 'Peter',
  job: 'Software Engineer',
  tellJob: function () {
    console.log(`Hello, my job is ${this.job}`)
  }
}

let anotherPerson = new Person()

anotherPerson.tellJob() // Hello, my job is Software Engineer
anotherPerson.sayName() // type error, no method called sayName

person.sayName() // Hello, my name is Bob
person.tellJob() // type error, no method called tellJob
复制代码

以上代码解释了实例和构造函数的原型对象之间的关系,同时阐明,当咱们访问一个对象的实例的时候,会先从属于实例自身的实例的属性搜索,找到了直接返回,若找不到则取原型对象中去找。同时,因为全部的实例都共享一个原型,咱们能够将公共的属性和方法都写在原型对象里,让实例共享。解决了文章一开始说的第二个问题。可是这种方法引入了新的一个问题:

function Person () {}

Person.prototype = {
  constructor: Person,
  name: 'Nicholas',
  age: 22,
  job: 'Software Engineer',
  friends: ['Shelby', 'Court'],
  sayName: function () {
    console.log(`Hello, my name is ${this.name}`)
  }
}

let person1 = new Person()
let person2 = new Person()

person1.friends // ['Shelby', 'Court']
person2.friends // ['Shelby', 'Court']

person1.friends.push('Bob')

person1.friends // ['Shelby', 'Court', 'Bob']
person2.friends // ['Shelby', 'Court', 'Bob']

person1.friends === person2.friends // true

复制代码

上述代码说明,原型对象会让实例共享属性,而实际上,咱们但愿实例拥有本身的属性。

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

这个模式时为了解决上述提出的三个问题的,最重要时解决让实例拥有本身的属性,同时全部实例又共享原型上的方法。简而言之为将属性和方法分开设置。

function Person (name, age, job) {
  this.name = name
  this.age = age
  this.job = job
  this.friends = ['Shelby', 'Court']
}

Person.prototype = {
  constructor: Person,
  sayName: function () {
    console.log(`Hello, my name is ${this.name}`)
  }
}

let person1 = new Person('Nicholas', 22, 'Software Engineer')
let person2 = new Person('Greg', 27, 'Doctor')

person1.friends.push('Bob')

person1.friends // ['Shelby', 'Court', 'Bob']
person2.friends // ['Shelby', 'Court']
person1.friends === person2.friends // false
person1.sayName === person2.sayName true
复制代码

其余

其实还剩下几种建立对象的方法:

  1. 动态原型模型:将对构造函数 prototype 属性书写的位置换到构造函数中来
  2. 寄生构造函数模式和稳妥构造函数模式:这两种方法建立的对象和在构造函数外部直接建立对象没有区别,因此没法断定实例和构造函数之间的关系,这里就不作记载了,有兴趣能够翻看书本进行了解。

持续更新在github

相关文章
相关标签/搜索