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:
- an object to perform check against
- 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)