JavaScript原型对象

原型
内容引导:
1使用 prototype 原型对象解决构造函数的问题
2分析 构造函数、prototype 原型对象、实例对象 三者之间的关系
3属性成员搜索原则:原型链
4实例对象读写原型对象中的成员
5原型对象的简写形式
6原生对象的原型
  + Object
  + Array
  + String
  + ...
7原型对象的问题
8构造的函数和原型对象使用建议
1 更好的解决方案: `prototype`
Javascript 规定,每一个构造函数都有一个 `prototype` 属性,指向另一个对象。
这个对象的所有属性和方法,都会被构造函数的实例继承。
这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 `prototype` 对象上。
function Person (name, age) {
  this.name = name
  this.age = age
}
console.log(Person.prototype)
Person.prototype.type = 'human'
Person.prototype.sayName = function () {
  console.log(this.name)
}
var p1 = new Person(...)
var p2 = new Person(...)
console.log(p1.sayName === p2.sayName) // => true
这时所有实例的 `type` 属性和 `sayName()` 方法,
其实都是同一个内存地址,指向 `prototype` 对象,因此就提高了运行效率。
2 构造函数、实例、原型三者之间的关系


任何函数都具有一个 `prototype` 属性,该属性是一个对象。
function F () {}
console.log(F.prototype) // => object
F.prototype.sayHi = function () {
  console.log('hi!')
}
构造函数的 `prototype` 对象默认都有一个 `constructor` 属性,指向 `prototype` 对象所在函数。
console.log(F.constructor === F) // => true
通过构造函数得到的实例对象内部会包含一个指向构造函数的 `prototype` 对象的指针 `__proto__`。
var instance = new F()
console.log(instance.__proto__ === F.prototype) // => true
<p class="tip">
  `__proto__` 是非标准属性。
</p>
实例对象可以直接访问原型对象成员。
instance.sayHi() // => hi!
总结:
1 任何函数都具有一个 `prototype` 属性,该属性是一个对象
2 构造函数的 `prototype` 对象默认都有一个 `constructor` 属性,指向 `prototype` 对象所在函数
3 通过构造函数得到的实例对象内部会包含一个指向构造函数的 `prototype` 对象的指针 `__proto__`
4 所有实例都直接或间接继承了原型对象的成员
3 属性成员的搜索原则:原型链
了解了 **构造函数-实例-原型对象** 三者之间的关系后,接下来我们来解释一下为什么实例对象可以访问原型对象中的成员。
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性
1 搜索首先从对象实例本身开始
2 如果在实例中找到了具有给定名字的属性,则返回该属性的值
3 如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性
4 如果在原型对象中找到了这个属性,则返回该属性的值
也就是说,在我们调用 `person1.sayName()` 的时候,会先后执行两次搜索:
5 首先,解析器会问:“实例 person1 有 sayName 属性吗?”答:“没有。
6 ”然后,它继续搜索,再问:“ person1 的原型有 sayName 属性吗?”答:“有。
7 ”于是,它就读取那个保存在原型对象中的函数。
8 当我们调用 person2.sayName() 时,将会重现相同的搜索过程,得到相同的结果。
而这正是多个对象实例共享原型所保存的属性和方法的基本原理。
总结:
1 先在自己身上找,找到即返回
2 自己身上找不到,则沿着原型链向上查找,找到即返回
3 如果一直到原型链的末端还没有找到,则返回 `undefined`
4 实例对象读写原型对象成员
读取:
1 先在自己身上找,找到即返回
2 自己身上找不到,则沿着原型链向上查找,找到即返回
3 如果一直到原型链的末端还没有找到,则返回 `undefined`
值类型成员写入(`实例对象.值类型成员 = xx`):
4 当实例期望重写原型对象中的某个普通数据成员时实际上会把该成员添加到自己身上
5 也就是说该行为实际上会屏蔽掉对原型对象成员的访问
5引用类型成员写入(`实例对象.引用类型成员 = xx`):
- 同上
6复杂类型修改(`实例对象.成员.xx = xx`):
1 同样会先在自己身上找该成员,如果自己身上找到则直接修改
2 如果自己身上找不到,则沿着原型链继续查找,如果找到则修改
3 如果一直到原型链的末端还没有找到该成员,则报错(`实例对象.undefined.xx = xx`)
7 更简单的原型语法
我们注意到,前面例子中每添加一个属性和方法就要敲一遍 `Person.prototype` 。
为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象:
function Person (name, age) {
  this.name = name
  this.age = age
}
Person.prototype = {
  type: 'human',
  sayHello: function () {
    console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
  }
}
在该示例中,我们将 `Person.prototype` 重置到了一个新的对象。
这样做的好处就是为 `Person.prototype` 添加成员简单了,但是也会带来一个问题,那就是原型对象丢失了 `constructor` 成员。
所以,我们为了保持 `constructor` 的指向正确,建议的写法是:
function Person (name, age) {
  this.name = name
  this.age = age
}
Person.prototype = {
  constructor: Person, // => 手动将 constructor 指向正确的构造函数
  type: 'human',
  sayHello: function () {
    console.log('我叫' + this.name + ',我今年' + this.age + '岁了')
  }
}
8 原生对象的原型
<p class="tip">
  所有函数都有 prototype 属性对象。
</p>
Object.prototype
Function.prototype
Array.prototype
String.prototype
Number.prototype
Date.prototype
- ...
练习:为数组对象和字符串对象扩展原型方法。
9 原型对象的问题
- 共享数组
- 共享对象
如果真的希望可以被实例对象之间共享和修改这些共享数据那就不是问题。但是如果不希望实例之间共享和修改这些共享数据则就是问题。
一个更好的建议是,最好不要让实例之间互相共享这些数组或者对象成员,一旦修改的话会导致数据的走向很不明确而且难以维护。
10 原型对象使用建议
1私有成员(一般就是非函数成员)放到构造函数中
2共享成员(一般就是函数)放到原型对象中
3如果重置了 `prototype` 记得修正 `constructor` 的指向

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值