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
对象,所以就提升了运行效率。this
构造函数、实例、原型三者之间的关系
任何函数都具备一个 prototype
属性,该属性是一个对象。spa
function F () {} console.log(F.prototype) // => object F.prototype.sayHi = function () { console.log('hi!') }
构造函数的 prototype
对象默认都有一个 constructor
属性,指向 prototype
对象所在函数。prototype
console.log(F.prototype.constructor === F) // => true
经过构造函数获得的实例对象内部会包含一个指向构造函数的 prototype
对象的指针 __proto__
。指针
var instance = new F() console.log(instance.__proto__ === F.prototype) // => true
<p class="tip"> __proto__
是非标准属性。</p>code
实例对象能够直接访问原型对象成员。对象
instance.sayHi() // => hi!
总结:blog
-
任何函数都具备一个
prototype
属性,该属性是一个对象继承 -
构造函数的
prototype
对象默认都有一个constructor
属性,指向prototype
对象所在函数 -
经过构造函数获得的实例对象内部会包含一个指向构造函数的
prototype
对象的指针__proto__
-
全部实例都直接或间接继承了原型对象的成员
属性成员的搜索原则:原型链
了解了 构造函数-实例-原型对象 三者之间的关系后,接下来咱们来解释一下为何实例对象能够访问原型对象中的成员。
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具备给定名字的属性
-
搜索首先从对象实例自己开始
-
若是在实例中找到了具备给定名字的属性,则返回该属性的值
-
若是没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具备给定名字的属性
-
若是在原型对象中找到了这个属性,则返回该属性的值
也就是说,在咱们调用 person1.sayName()
的时候,会前后执行两次搜索:
-
首先,解析器会问:“实例 person1 有 sayName 属性吗?”答:“没有。
-
”而后,它继续搜索,再问:“ person1 的原型有 sayName 属性吗?”答:“有。
-
”因而,它就读取那个保存在原型对象中的函数。
-
当咱们调用 person2.sayName() 时,将会重现相同的搜索过程,获得相同的结果。
而这正是多个对象实例共享原型所保存的属性和方法的基本原理。
总结:
-
先在本身身上找,找到即返回
-
本身身上找不到,则沿着原型链向上查找,找到即返回
-
若是一直到原型链的末端尚未找到,则返回
undefined
实例对象读写原型对象成员
读取:
-
先在本身身上找,找到即返回
-
本身身上找不到,则沿着原型链向上查找,找到即返回
-
若是一直到原型链的末端尚未找到,则返回
undefined
值类型成员写入(实例对象.值类型成员 = xx
):
-
当实例指望重写原型对象中的某个普通数据成员时实际上会把该成员添加到本身身上
-
也就是说该行为实际上会屏蔽掉对原型对象成员的访问
引用类型成员写入(实例对象.引用类型成员 = xx
):
-
同上
复杂类型修改(实例对象.成员.xx = xx
):
-
一样会先在本身身上找该成员,若是本身身上找到则直接修改
-
若是本身身上找不到,则沿着原型链继续查找,若是找到则修改
-
若是一直到原型链的末端尚未找到该成员,则报错(
实例对象.undefined.xx = xx
)
图例:
更简单的原型语法
咱们注意到,前面例子中每添加一个属性和方法就要敲一遍 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 + '岁了') } }
原生对象的原型
<p class="tip"> 全部函数都有 prototype 属性对象。</p>
-
Object.prototype
-
Function.prototype
-
Array.prototype
-
String.prototype
-
Number.prototype
-
Date.prototype
-
...
练习:为数组对象和字符串对象扩展原型方法。
原型对象使用建议
-
私有成员(通常就是非函数成员)放到构造函数中
-
共享成员(通常就是函数)放到原型对象中
-
若是重置了
prototype
记得修正constructor
的指向