为什么要使用Object.prototype.hasOwnProperty.call()?

发布时间 2023-12-14 15:21:08作者: unuliha

翻译自:What’s the deal with Object.prototype.hasOwnProperty.call()?

你一定在他人的代码或者某个库中见过下面的代码:

Object.prototype.hasOwnProperty.call(objRef, 'propName');

现在我们来看下这段代码到底什么意思。

在弄清楚的过程中,我们会依次理解下面几件事:

  1. Object.prototype是什么?
  2. 一个对象在没有定义该函数,在对象的原型链中也没有定义该函数时,为什么可以借调使用该函数?
  3. 为什么我们在Objectprototype上调用hasOwnProperty,而不是直接在objRef实例本身上调用?

好吧,让我们开始吧。

1. Object.prototype

Prototypal inheritanceJavaScript最主要的特性之一,它允许对象从它的原型中继承方法和属性。你可以把原型当成一个模板。

让我们看个例子:

var obj = {name: 'aman'}
obj.hasOwnProperty(‘name’)  // returns true

正如你所见,我们并没有在obj上定义hasOwnProperty,但是我们可以成功调用它,这是怎么回事?

这是因为原型继承和原型链的机制起作用了,让我们继续深入。

当我们创建字面量对象obj时,它的原型(prototype)被设定为了Object.prototype,我们可以通过下面的代码验证:

Object.getPrototypeof(obj) === Object.prototype // returns true
obj.__proto__ === Object.prototype // returns true

[[Prototype]]表明了对象之间的继承关系,在我们的例子中,即objObject.prototype之间的关系。

原型链长这样:

// Prototype chain
obj —-> Object.prototype ——> null

因此当我们调用hasOwnProperty()方法时,编译器在obj上找不到该方法,就向上找(原型链上找),在Object.prototype上找到了该方法。

此外,我们可以通过Object.setPrototypeOf() 或者Object.create(),自己设定或者重新配置对象的原型。

看下这个例子:

var person = {name: 'peter'};
var PersonPrototype = {getName: function(){ return this.name; }}; 

// Setting person's prototype 
Object.setPrototypeOf(person, PersonPrototype);

// Trying to access getName() method will cause a prototype chain lookup (aka prototype delegation) 
// and finds it on PersonPrototype. 
person.getName(); // 'peter'

2. 函数借用

假如我们有一个函数和一个对象:

function sayHello() { console.log(`Greetings ${this.name}`) }
var a = {name: 'peter'};

我们怎样做才能让a调用函数sayHellow并且传入a的名字peter呢?我们并不想让a去实现函数sayHello,也不想让函数sayHellow出现在a的原型链上。

答案就是通过Function.prototype上的callapply方法。

JavaScript中,我们创建的每个函数都继承了Function.prototype,正如上面我们介绍的原型链一样,我们可以在所有函数对象上调用call方法。

call method 的语法如下:

// 'call' method is available on Function.prototype
func.call(objRef, ...args); 

第一个参数是想要借用这个函数的对象,后面跟着函数的剩下的参数列表。

因此,想要借用函数sayHellow,我们需要做的就是将a作为参数传入sayHellow.call方法:

sayHello.call(a); // Greetings peter 

3. Object.prototype.hasOwnProperty vs instance.hasOwnProperty

在对原型继承和函数借用进行了简单介绍后,是时候弄清楚为什么我们在Object.prototype上调用hasOwnProperty而不是在实例本身上调用了。

正如我们上面介绍的那样,我们可以自己配置对象的原型链,一种方式就是在创建对象实例的时候使用 Object.create()

// Object.create() accepts an argument which becomes
// the prototype for newly created object.
var a = Object.create(null); // Setting `null` as prototype for 'a'. 

// Adding a 'name' property on the instance
a.name = 'peter';

// Using `hasOwnProperty` method would cause an error
a.hasOwnProperty('name'); //? throws a TypeError: a.hasOwnProperty is not a function

当调用hasOwnProperty时,程序抛出了错误,对象及其原型链上没有这个方法,a的原型链长下面这样:

// Prototype chain
a ---> null

你也许会想知道为什么会有人用这种方式创建对象,但是事实上,在JavaScript中,你就是可以随心所欲地疯。

想象一下,你正在创建一个library,并且暴露了一个方法,这个方法接受一个对象作为参数,如果你的函数中直接在外部传入的对象上调用hasOwnProperty,同时别人传进来的对象的原型是null,那你的函数执行将被打断。

因此,我们可以使用函数借用来避免此类问题,具体地,传入的对象能够通过call方法借用Object.prototypehasOwnProperty方法。

// Will not break your code if 'a' has a null prototype. ✅
Object.prototype.hasOwnProperty.call(a, 'name'); // true; 

总结

  • 每个对象字面量继承了Object.prototype,这允许你调用Object.prototype上的方法如hasOwnProperty

  • 我们能够通过Object.setPrototypeOf或者Object.create(prototype)重写/创建原型链。

  • 每个函数都继承了Function.prototype,因此可以调用Function.prototype上的callapplybind等方法。

  • 一个对象可以在不定义函数且原型链上没有定义某个函数时,借用该函数,如Object.prototype.hasOwnProperty.call(a, 'name');

  • 使用Object.prototype.hasOwnProperty.call(objRef, 'propName')避免objRef原型是null时导致的TypeError