一、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
对象,因此就提高了运行效率。
二、构造函数、实例、原型三者之间关系
- 任何函数都具有一个
prototype
属性,该属性是一个对象。function F () {} console.log(F.prototype) // => object F.prototype.sayHi = function () { console.log('hi!') } //构造函数的 `prototype` 对象默认都有一个 `constructor` 属性, //指向 `prototype` 对象所在函数。 console.log(F.prototype.constructor === F) // => true
- 通过构造函数得到的实例对象内部会包含一个指向构造函数的
prototype
对象的指针__proto__
。var instance = new F() console.log(instance.__proto__ === F.prototype) // => true //`__proto__` 是非标准属性。 //实例对象可以直接访问原型对象成员。 instance.sayHi() // => hi!
- 总结:
- 任何函数都具有一个
prototype
属性,该属性是一个对象 - 构造函数的
prototype
对象默认都有一个constructor
属性,指向prototype
对象所在函数 - 通过构造函数得到的实例对象内部会包含一个指向构造函数的
prototype
对象的指针__proto__
- 所有实例都直接或间接继承了原型对象的成员
- 任何函数都具有一个
原型链
实例对象读写原型对象的成员
- 读取:
- 先在自己身上找,找到即返回
- 自己身上找不到,则沿着原型链向上查找,找到即返回
- 如果一直到原型链的末端还没有找到,则返回
undefined
- 值类型成员写入(
实例对象.值类型成员 = xx
):- 当实例期望重写原型对象中的某个普通数据成员时实际上会把该成员添加到自己身上
- 也就是说该行为实际上会屏蔽掉对原型对象成员的访问
- 引用类型成员写入(
实例对象.引用类型成员 = xx
):- 同上
- 复杂类型修改(
实例对象.成员.xx = xx
):- 同样会先在自己身上找该成员,如果自己身上找到则直接修改
- 如果自己身上找不到,则沿着原型链继续查找,如果找到则修改
- 如果一直到原型链的末端还没有找到该成员,则报错(
实例对象.undefined.xx = xx
)
原型对象使用建议
- 私有成员(一般就是非函数成员)放到构造函数中
- 共享成员(一般就是函数)放到原型对象中
- 如果重置了
prototype
记得修正constructor
的指向//我们注意到,前面例子中每添加一个属性和方法就要敲一遍 `Person.prototype` 。 //为减少不必要的输入,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象: function Person (name, age) { this.name = name this.age = age } //重置prototype Person.prototype = { constructor: Person, // => 手动将 constructor 指向正确的构造函数 type: 'human', sayHello: function () { console.log('我叫' + this.name + ',我今年' + this.age + '岁了') } }
- 数组或者String 中的prototype是不可以修改的