搞明白原型对象-附面试题

1,277 阅读4分钟

本篇解决的问题

  • prototype是什么
  • 获取对象的原型对象
  • 检测值存在于原型对象还是实例
  • instanceof for-in Object.keys()使用的局限

原型对象定义

任何时候创建一个函数(箭头函数除外),都会为该函数创建一个 prototype 属性,这个属性指向函数的原型对象,函数对象默认有一个属性 constructorconstructor 指向该函数

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规范中,若要访问和修改原型对象则应该用 getPrototypeOfsetPrototypeOf

var otherJack = {};
Object.setPrototypeOf(otherJack, Person.prototype);
console.log(otherJack.name); // jack
console.log(Object.getPrototypeOf(otherJack) === jack.__proto__) // true

inhasOwnProperty

  • 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版)》