本篇解决的问题
- prototype是什么
- 获取对象的原型对象
- 检测值存在于原型对象还是实例
instanceof
for-in
Object.keys()
使用的局限
原型对象定义
任何时候创建一个函数(箭头函数除外),都会为该函数创建一个 prototype
属性,这个属性指向函数的原型对象,函数对象默认有一个属性 constructor
,constructor
指向该函数
function Person() {};
Person.prototype.name = 'jack';
console.log(Person.prototype.constructor === Person); // true
关系如下图:
所以当我们平时在将函数的
prototype
直接指向某个对象后,应该在该对象上定义 constructor
指向该函数
实例
当通过new调用函数创建实例后,该实例内部包含一个指针 [[Prototype]]
指向构造函数的原型对象,该属性为内部属性,无法直接访问,但在Firfox、Chrome和Safari中有一个 __proto__
属性可以访问到构造函数的原型。
在读取对象的某个属性是,js会执行一次搜索,首先检查对象实例上有无该属性,如果有则返回,若无,继续搜索该对象的原型对象,层层向上,直到一个对象的原型对象为 null
,理论上所有的对象最后都会找到Object.prototype,Object的原型对象为null,这就是js的原型链查找
还是接着上面的例子
var jack = new Person();
console.log(jack.name) // 'jack'
console.log(jack.__proto__ === Person.prototype) // true
关系如下图
属性屏蔽
当在实例上添加了一个属性,该属性和原型中的一个属性同名,该属性将会屏蔽掉原型中的那个属性,当调用 delete
后会重新恢复访问
jack.name = 'rose';
console.log(jack.name) // rose
delete jack.name;
console.log(jack.name) // jack
Object.getPrototypeOf()、Object.setPrototypeOf()
上面提到访问对象原型对象的属性__proto__
该属性为浏览器定义,并没有在es规范中,若要访问和修改原型对象则应该用 getPrototypeOf
和 setPrototypeOf
var otherJack = {};
Object.setPrototypeOf(otherJack, Person.prototype);
console.log(otherJack.name); // jack
console.log(Object.getPrototypeOf(otherJack) === jack.__proto__) // true
in
和 hasOwnProperty
in
判断属性是否存在于对象上(包含原型对象)hasOwnProperty
判断对象实例是否具有某个属性(不包含原型对象)
接上一个例子
function Person() {};
Person.prototype.name = 'jack';
var jack = new Person();
console.log('name' in jack) // true
console.log(jack.hasOwnProperty('name')) // false
// 判断属性是否存在原型对象中
function hasPrototypeProperty(obj, key) {
return !obj.hasOwnProperty(key) && key in obj;
}
console.log(hasPrototypeProperty(jack, 'name')) // true
for-in
Object.keys
Object.getPwnPropertyNames
如果想要获得对象的所有属性常用的有三个方法
for-in
遍历对象所有可遍历的属性,包括原型链Object.keys
返回对象实例上所有可遍历属性,不包括原型链Object.getOwnPropertyNames
返回对象实例中所有属性,无论是否可遍历,
function Person() {};
Person.prototype.name = 'jack';
var jack = new Person();
jack.age = 25;
Object.defineProperty(jack, 'from' ,{
value: 'china',
enumerable: false,
});
console.log(Object.keys(jack)) // ['age']
console.log(Object.getOwnPropertyNames(jack)) // ['age', 'from']
for(var key in jack) {
console.log(key);
}
//age
//name
上文中提到在直接设置构造函数的 prototype
为一个新对象后应该在该对象中添加一个属性 construcotr
将该属性指向构造函数自身,直接设置其实是有瑕疵的,这样 constructor
成为可枚举属性了,通过 for-in
会显示,我们应该用 Object.defineProperty
方法,将该属性设置为不可枚举属性
function Peroson() {}
Person.prototype = {
name: 'jack'
}
Object.defineProperty(Person.prototype, 'construcotr', {
value: Person,
enumerable: false,
});
面试题
- 题目1 判断输出结果
function Person() {}
Person.prototype = {
name: 'jack'
};
var jack = new Person();
Person.prototype = {
name: 'otherJack'
};
var otherJack = new Person();
console.log(jack.name);
console.log(otherJack.name);
-题目2 判断输出结果
function Test() {}
Object.prototype.printName = function() {
console.log('Object');
}
Function.prototype.printName = function() {
console.log('Function');
}
Test.printName();
var obj = new Test();
obj.printName();
解题
- 题目1
答案:
jack
otherJack
创建第一个对象后,对象内部的[[Prototype]]指向了当时的Person.prototype
这个对象,后面改变Person.prototype
将其赋予一个新值,并没有改变原来的对象,所有jack
访问到的值还是旧之,将原型对象定义为一个变量就更好理解了
var oldProperty = {
name: 'jack',
}
var newProperty = {
name: 'otherJack',
}
function Person() {}
Person.prototype = oldProperty;
var jack = new Person();
// jack.__proto__ === oldPerperty
Person.prototype = newProperty;
var otherJack = new Person();
// otherJack.__proto__ === newProperty
console.log(jack.name); // jack
console.log(otherJack.name); // otherJack
jack
对象内部指针指向的对象并没有变化
- 题目2
答案:
Function
Object
有几个点:
- 函数也是对象
- 函数的构造函数是
Function
,所以函数的原型对象就是Function.prototype
- 几乎所有的对象原型链末端,也就是最后都会指向
Object.prototype
,然后Object.prototype
的原型对象是null
当我们调用 Test.printName()
js会先找到 Function.prototype
上的方法,而我们通过new创建的实例,函数对象指向的是 Test.prototype
,这里并没有 printName
方法,Test.prototype
的原型对象是默认的具有constructor的一个对象,他的原型对象是 Object.prototype
所以最后调用的是 Object.prototype.printName
,整个指向的图可能更好理解
当我们把 Function.prototype.printName
这句去掉之后两个输出就会都是 Object
参考资料
《JavaScript 高级程序设计(第3版)》