DEV Community

Michal Ševčík
Michal Ševčík

Posted on

Function.call.bind( Object.prototype.hasOwnProperty ), wait what?

The other day I was going through a source code of prop-types library and stumbled upon this piece of code:

const has = Function.call.bind(Object.prototype.hasOwnProperty)

And I was like WHAT ON EARTH IS GOING ON HERE.

By looking at the code you can probably guess that has will end up being a function that will check if an object has an own property. An own property is property existing directly on the object, so the check is performed without consulting the prototype chain.

You may be asking - can't I just do ({ foo: 1 }).hasOwnProperty(prop)? Yes you can. But there is one specific scenario, where this will fail. Consider:

const myObj = Object.create(null);

The Object.create() method creates a new object, using an existing object as the prototype of the newly created object.

This creates an object, which does not have any prototype. So there's no hasOwnProperty method in the prototype chain.

myObj.hasOwnProperty() // Uncaught TypeError: myObj.hasOwnProperty is not a function

You can fix this by doing:

Object.prototype.hasOwnProperty.call(myObj, prop)

But that is very lengthy and you should always account for an object, that might have no prototype.

So here comes the has function, you use it like so:

const myObj = { foo: true }
has(myObj, 'foo') // true
has(myObj, 'bar') // false

How does it work

Just a quick recap what Function.call and Function.bind do.

Function.call

The call() method calls a function with a given this value and arguments provided individually.

Function.bind

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

Solution

OK, so looking at the has function and how it's used has({ foo: true }, 'foo') we can say, that we need to create a function, that takes two arguments:

  1. an object to perform check against
  2. a property to check

Remember Object.prototype.hasOwnProperty.call(myObj, prop)? Does not .call()'s function signature look like what we're trying to achieve? has function has two arguments - context (object), and property. And we can use call function to provide context and additional arguments.

OK, and can we have .call function that's called with another context, specifically Object.prototype.hasOwnProperty's context? Yes we can, that where const has = Function.call.bind(Object.prototype.hasOwnProperty) comes into play.

console.dir(has)
// output
ƒ bound call()
    name: "bound call"
    __proto__: ƒ ()
    [[TargetFunction]]: ƒ call()
    [[BoundThis]]: ƒ hasOwnProperty()
    [[BoundArgs]]: Array(0)

What has just happened? We took the .call function that exists on the Function.prototype object and changed its context to the ƒ hasOwnProperty().

If you inspect the output of console.dir, you can see that we got back the call function ([[TargetFunction]]), but it's this is bound to ƒ hasOwnProperty() [[BoundThis]].

That means that when you call has({ foo: true, 'foo' }), you're still executing the call function that takes a context as a first argument and then any other arguments that follow when calling the function. But with one big difference - the call's context is bound to of Object.prototype.hasOwnProperty.

Easier example

When I was trying to wrap my head around this, it helped me to test this on a little bit easier to understand code.

function sayHi() {
    console.log('Hi ' + this.name) // NOTICE the usage of this.name
} 

const boundSayHi = Function.call.bind(sayHi)

boundSayHi({ name: 'Jane' }) // "Hi Jane"
boundSayHi({ name: 'Peter' }) // "Hi Peter"

// instead of

sayHi.call({ name: 'Jane' }) // "Hi Jane"

The exact same thing is happening here:

console.dir(boundSayHi)
// output
ƒ bound call()
    name: "bound call"
    __proto__: ƒ ()
    [[TargetFunction]]: ƒ call()
    [[BoundThis]]: ƒ sayHi()
    [[BoundArgs]]: Array(0)

We get back a function call stored in boundSayHi variable, but it's context is bound to ƒ sayHi().

When you then run boundSayHi({ name: 'Jane' }), you're calling the call function bound to sayHi function and you're passing down a context for that sayHi function.

Takeaways

  • Bear in mind that calling hasOwnPrototype on an object might not work, because it may have no prototype.
  • Object.prototype.hasOwnProperty.call(obj, prop) is too long, especially if you're using that multiple times in a project, or a file. Create helpers.
  • If something does not make sense, try to understand it. Dig down. It may take hours, even days. But it will click eventually. It's hard to find bugs in code you copy & paste & don't understand.

Top comments (0)