JS面向对象篇二、什么是原型?原型对象与实例对象、构造函数的关系及相关方法
本文内容:
1、构造函数、原型对象与实例对象之间的关系;
2、isPrototypeOf()和Object.getPrototypeOf();
3、实例对象上与原型对象上同名的属性处理;
4、hasOwnProperty()方法和in操作符判断属性来自实例对象本身还是它的原型对象;
5、for-in、Object.keys()和Object.getOwnPropertyNames()方法获取实例对象或者原型对象上的属性;
6、需注意的特殊问题
构造函数、原型对象与实例对象
function Person () {}
Person.prototype.name = 'zhangsan'
Person.prototype.age = 23
Person.prototype.sayName = function () {
console.log(thi.name)
}
var p1 = new Person()
p1.sayName() // 'zhangsan'
var p2 = new Person()
p2.sayName() // 'zhangsan'
首先每一个函数都有一个prototype属性,这个属性指向一个对象,这个对象就是原型对象;
默认情况下,所有原型对象都会自动获得一个constructor属性,这个属性指向prototype属性所在的函数,也就是构造函数
当调用构造函数创建一个实例对象后,该实例对象有一个内部属性:[[prototype]](在部分浏览器中可通过__proto__访问到),这个属性指向构造函数的原型对象
如在上面的例子中:构造函数Person有一个prototype属性,它指向原型对象,而原型对象默认有且只有一个constructor属性,该属性指向构造函数Person,即Person.prototype.constructor指向Person,实例对象p1和p2也都包含一个内部属性([[prototype]]),该属性指向Person.prototype。
它们之间的关系如图:
另外还需要注意的一个问题:
虽然p1和p2两个实例中并没有sayName(),但是在例子中我们也能访问到,成功打印出‘zhangsan’,这是通过查找对象属性来实现的,其过程如下:
每当代码读取到某个属性时,都会执行一次搜索,首先从实例对象本身开始,如果实例对象上找到了该属性,则返回该属性值,若没有找到,则继续到实例的[[prototype]]指向的原型对象上去找,也就是去创建此实例的构造函数的prototype指向的原型对象上找,找到了就返回该属性值。
调用p1.sayName(),首先p1上没有定义该属性,那么要到Person.prototype中去找,Person.prototype中有sayName()的定义,则取该函数。
isPrototypeOf()和Object.getPrototypeOf()
因为[[prototype]]为内部属性,我们无法直接访问到它,但是我们可以通过isPrptotypeOf()方法来确定对象与它的构造函数的原型对象之间的关联;
console.log(Person.prototype.isPrototypeOf(p1)) // true
console.log(Person.prototype.isPrototypeOf(p2)) // true
ECMAScript5中新增了Object.getPrototypeOf()方法用来获取一个对象的原型:
console.log(Object.getPrototypeOf(p1) == Person.prototype) // true
console.log(Object.getPrototypeOf(p1).name) // 'zhangsan'
实例对象上定义的与原型对象同名的属性
在实例对象上定义了与原型对象上同名的属性后,会屏蔽掉原型对象的该属性,访问的是实例对象上的属性。
hasOwnProperty()
使用hasOwnProperty()方法能够判断某个属性是否是实例对象本身的属性。
function Person () {}
Person.prototype.name = 'zhangsan'
var p1 = new Person()
console.log(p1.hasOwnProperty('name')) // false
p1.job = 'coder'
console.log(p1.hasOwnProperty('job')) // true
in操作符
in操作符,可以判断属性是否可以通过对象访问到,无论是原型上的属性还是实例上的。
console.log('name' in p1) // true
console.log('job' in p1) // true
结合in操作符和hasOwnProperty()方法就能够准确的判断出,一个属性是实例上对象本身的,还是原型对象上的。
function isPrototypeProperty (obj, name) {
return !obj.hasOwnProperty(name) && name in obj
}
遍历对象属性
for-in
for-in循环返回的事所有能够通过对象访问的、可枚举的(enumerable)属性,既包含存在于实例中的属性,又包含原型中的属性。
Object.keys()
ECMAScript5中新增的方法,返回的是所有可枚举的实例属性的字符串数组
function Person () {}
Person.prototype.name = 'zhangsan'
Person.prototype.sayName = function () {
console.log(this.name)
}
console.log(Object.keys(Person.prototype)) // ['name', 'sayName']
var p1 = new Person()
p1.name = 'lisi'
p1.age = 23
console.log(Object.keys(p1)) // ["name", "age"]
Object.getOwnPropertyNames()
返回所有的实例属性,无论它是否可枚举。
function Person () {}
Person.prototype.name = 'zhangsan'
Person.prototype.sayName = function () {
console.log(this.name)
}
console.log(Object.keys(Person.prototype)) // ['constructor','name', 'sayName']
更简单的原型语法
之前的代码中,每添加一个属性就要写一遍Person.prototype,为了简化代码,可以做优化如下:
function Person() {}
Person.prototype = {
name : 'zhangsan',
age : 23,
sayName : function () {
console.log(this.name)
}
}
var p1 = new Person()
要注意这样做会对对原型对象的constructor有影响,之前提到,每一个函数都有一个prototype属性,这个属性自动获取了一个constructor属性,它指向构造函数,但是上面这种写法相当于给Person的prototype属性重新赋予了一个新的对象,而这个对象不存在constructor属性了。
另外一个问题:重写构造函数的原型和调用构造函数创建实例的顺序很重要!
当我们像下面这样做时会导致报错:
function Person () {}
var p1 = new Person()
Person.prototype = {
name : 'zhangsan',
sayName : function () {
console.log(this.name)
}
}
console.log(p1.name) // 报错
如上代码,由于先创建了实例对象,这时p1的[[prototype]]属性指向了Person.prototype,即构造函数Person的原型对象,接着,重写了Person.prototype,再访问p1.name的时候,由于p1实例上找不到name属性,于是要到它指向的原型对象上面找,而它的原型对象依然是最初构造函数默认指向的那个原型对象,并不是后面赋值的字面两对象,所以是找不到的,因此报错。