了解如何建立对象有助于咱们理解如何继承,和对象的
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')
复制代码
显而易见的是能够屡次调用该函数获得多个具备类似的对象,可是其存在一些缺点:
数组
- 咱们没法知道上述的
person1
究竟是不是createPerson
的实例,由于person1
的prototype
是Object.prototype
- 咱们每建立一个对象都要新建一个
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
操做符来实例化一个对象时发生了如下几件事:
函数
- 新建一个对象
- 将构造函数的
this
指向新对象- 执行构造函数中的代码
- 返回新对象
同时
person1
的prototype
被赋值为Person.prototype
所以使用person1 instanceof Person
时其实在判断Person.prototype
是否出如今了person1
的原型链之中,便可判断person1
为Person
的实例。
可是构造函数模式依然没有解决工厂模式的第二个问题。ui
原型模式的存在即解决了上述所说的 第二个问题 它使得一个类的全部实例共享一些属性。而在那以前咱们须要了解什么是原型对象,即上文提到的
prototype
。this
这里有几个点本身以为比较重要的,对于我本身理解何为原型比较有帮助
spa
- prototype是一个对象
- 实例将包含一个属性,这个属性是一个指向了构造函数的原型对象的指针
- 实例中能够获取到的属性分两种,一种 实例属性 一种是 原型属性
接下来再讨论原型对象:
prototype
- 只要建立了一个函数,就会为该函数建立一个
prototype
属性,这个属性指向一个对象即该函数的原型对象- 默认状况下全部的原型对象都会自动得到一个
constructor
属性,这个属性是一个 指向prototype
所在函数的指针。即Person.prototype.constructor
指向了Person
- 当调用构造函数建立一个实例后,该实例的内部将包含一个指针,指向了构造函数的原型对象。
代码示例以下:指针
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()
复制代码
以上代码表现成原型状态以下,没有上图只有文字描述,多读几遍其实文字也来得精妙啦:
Person.prototype
指向了一个对象(如下简称原型对象,这里时刻牢记是一个对象),即Person
的原型对象Person
的原型对象包含一系列属性:第一个即默认状况下被赋予的constructor
属性,该属性是一个指向构造函数Person
的指针,因此Person.prototype.constructor
值为Person
;剩下的属性即代码中定义的各个属性(name
、age
、job
、sayName
),因此这个原型对象的值为:{ constructor: Person, name: 'Nichalos', age: 29, job: 'Software Engineer', sayName: Function } 复制代码
- 实例出
person
实例后,person
被赋予了一个内部属性,这个属性指向了第2步中的原型对象,一般这个对象是__proto__
。值得注意的是,虽然咱们只是实例化person
,并无未其添加属性和方法,可是却能够经过搜索原型链 访问__proto__
中的属性和方法。我本身将其称做 原型属性
以上描述了原型对象的由来、理清楚了 实例 和 构造函数 和 原型对象 的关系:实例和构造函数没什么关系,可是实例有一个属性指向了原型对象,而原型对象是属于构造函数的,因此三者存在了联系。而接下来咱们要讨论一些关于原型对象的方法:
hasOwnProperty(propertyName: String) // 返回实例是否具备该实例属性(即实例自己的属性)
in // 操做符,'name' in person,这个操做符只要对象可以访问到该属性就返回true,不管是实例属性仍是原型属性
Object.keys(obj: Object) // 该方法会返回一个包含全部可枚举属性的字符串数组
Object.getOwnPropertyNames(obj: Object) // 该方法会的到全部实例的属性不管是实例的属性仍是原型属性,也不论是否可枚举
复制代码
接着是原型的特性:
- 为实例添加属性会屏蔽原型中的同名属性而不是改写
- 原型具备动态性,当咱们对原型对象作出修改时,可以马上从实例上反应出来,就算是先建立了实例,再修改了原型,以前建立的实例的
[[prototype]]
依然会反应出来- 若是先建立了实例,就不能切断原本原型和构造函数之间的联系,即不能重写构造函数的
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
复制代码
其实还剩下几种建立对象的方法:
- 动态原型模型:将对构造函数
prototype
属性书写的位置换到构造函数中来- 寄生构造函数模式和稳妥构造函数模式:这两种方法建立的对象和在构造函数外部直接建立对象没有区别,因此没法断定实例和构造函数之间的关系,这里就不作记载了,有兴趣能够翻看书本进行了解。
持续更新在github