理解原型对象
1. prototype
一个函数一旦创建,就会根据特定的规则为其创建一个prototype属性(指针),指向函数的原型对象。该原型对象在创建时自动获得一个constructor属性,该constructor指向prototype所在函数。
即:Person.prototype.constructor === Person
Person.prototype = {
constructor: Person // 自动生成的 prototype样子
}
2. __proto__
当调用构造函数创建一个实例后,该实例内部包含一个指针,指向构造函数的原型对象,ECMA-262称其为[[Prototype]],多数浏览器中叫做__proto__。
也就是说:person1.__proto__ === Person.prototype
可以使用 isPrototypeOf()方法来确定对象与原型之间的关系
Person.prototype.isPrototypeOf(person1) // true
也可以使用Object.getPrototypeOf()
获取实例的prototype
Object.getPrototypeOf(person1) === Person.prototype
Object.getPrototypeOf(person1).name
在访问实例的属性时,与原型同名的实例属性会拼比原型属性
因为对象属性的访问是根据原型链向上搜索的,找到后就停止。
可以使用hasOwnPropery()
方法来检测属性是否是实例自己的
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.gender = 'male'
Person.prototype.job = 'none'
const person1 = new Person('Jack', 21)
person1.hasOwnProperty('gender') // false
person1.job = 'Doctor' // person1.job === 'doctor'
3. in 操作符
function Person(age) {
this.age = age
}
Person.prototype.name = 'gary'
const person1 = new Person(21)
'name' in person1 // true
'age' in person1 // true
person1.hasOwnProperty('name') // false
只要能否访问到,不管是实例自身的还是继承的 都返回true
4. 字面量原型对象
即把字面量对象赋值给原型
Person.prototype = {
name: 'Rajesh',
age: 21,
job: 'Astronomer',
sayHi: function() {
console.log('cant talk to girls')
}
}
此时,constructor属性已经不再指向Person了,转而指向Object
但我们可以手动把constructor指向Person,如下所示:
Person.prototype = {
constructor: Person,
name: 'Rajesh',
age: 21,
job: 'Astronomer',
sayHi: function() {
console.log('cant talk to girls')
}
}
注意:显示地重设constructor属性会导致其 [[Enumerable]] 特性被设置为 true, 而默认生成的constructor是不可枚举的。当然可以通过Object.defineProperty()
手动改亿下!
function Person() {
}
Person.prototype = {
name: 'zed',
age: 21,
job: 'none'
}
Object.defineProperty(Person.prototype, 'constructor', {
enumerable: false,
value: Person
})
5. 原型的动态性
指的是:在初始化实例后更改原型属性,所有示例也会得到相应更改,毕竟是通过指针向上搜索访问的。
const someone = new Person()
Person.prototype.sayHi = function() {
alert('Hi')
}
someone.sayHi() // 'hi' 没有问题的
但如果在实例化后重写了整个原型对象,则实例与原型对象之前的联系就被切断了,此时就无法找到新设的原型属性/函数了。
const someone = new Person()
Person.prototype = {
name: 'what',
age: 21,
sayHi: function() {
alert('hi')
}
}
someone.sayHi() // error 找不到
6. 原生对象的原型
可以像修改自定义对象原型一样来修改原生对象的原型来为其添加新方法
例如,为String添加startsWith函数,示例略。
7. 原型对象的问题
原型对象的问题是由其共享的本质造成的,共享的属性和函数是我们想要的,但引用类型共享的是内存地址值,这就会造成多实例对原型中同一引用类型进行操作,需要注意。
function Person() {
}
Person.prototype = {
constructor: Person,
name: 'zed',
age: 21,
friends: ['a', 'b']
}
const person1 = new Person()
const person2 = new Person()
person1.friends.push('c')
person1.friends === person2.friends // true
该问题可通过构造函数与原型模式组合的方式来避免,即:把引用类型的属性放到实例里。