Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Object.defineProperties

39 views
Skip to first unread message

Cezary Tomczyk

unread,
May 3, 2017, 2:36:53 AM5/3/17
to
"The Object.defineProperties() method defines new or modifies existing
properties directly on an object, returning the object."

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties

However, in the polyfill
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties#Polyfill

I see:

if (typeof obj !== 'object' || obj === null)
throw new TypeError('bad obj');

I think here it should be check for null and all primitive types
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures

Native built-in Object.defineProperties method allows me to add
properties to a function as the function is an object.

Am I thinking correctly that mentioned polyfill has a bug?

--
Cezary Tomczyk
http://www.ctomczyk.pl/
https://www.aslint.org/ - Accessibility validation tool

Cezary Tomczyk

unread,
May 3, 2017, 4:14:30 AM5/3/17
to
On 03/05/2017 09:24, Stefan Ram wrote:
> Cezary Tomczyk <cezary....@gmail.com> writes:
>> Native built-in Object.defineProperties method allows me to add
>> properties to a function as the function is an object.
>
> The Web gives:
>
> function isObject(val) {
> if (val === null) { return false;}
> return ( (typeof val === 'function') || (typeof val === 'object') );
> }
>
> . The above might not work with host objects, which might
> have even other types. So another suggestion from the Web is:
>
> function isObject(obj) {
> return obj === Object(obj);
> }
>
> The directly above might be used by underscore.js.
>
> Other observations:
>
> Object.getPrototypeOf does /not/ throw when called with an object.

I use:

function isTypeOf(obj, typeToCheck) {
if (arguments.length < 2) {
return false;
}
return Object.prototype.toString.call(obj).slice(8,
-1).toLowerCase() === typeToCheck.toLowerCase();
}

which gives me more precisely a type of object.

The Object.defineProperties says about object, but function, array are
also objects. That's why:

if (typeof obj !== 'object' || obj === null)
throw new TypeError('bad obj');

looks like a bug to me.

Cezary Tomczyk

unread,
May 3, 2017, 4:59:57 AM5/3/17
to
On 03/05/2017 10:44, Stefan Ram wrote:
> Cezary Tomczyk <cezary....@gmail.com> writes:
>> function isTypeOf(obj, typeToCheck) {
>> if (arguments.length < 2) {
>> return false;
>> }
>> return Object.prototype.toString.call(obj).slice(8,
>> -1).toLowerCase() === typeToCheck.toLowerCase();
>> }
>> which gives me more precisely a type of object.
>
> This was historically called the "[[Class]] internal slot".
>
> Your function gives »true« for
>
> isTypeOf( 3, "Number" )
>
> even though »3« /is not an object/ (so it's not an object
> with the [[Class]] »Number« either).

> So, this might wrongly indicate that »3« is an object.

But isTypeOf is not checking if specified obj is an object type. It
checks expected type of obj defined in typeToCheck. This means that for
3 it gives "number" type which is correct.


> After,
>
> class A {}
>
> »true« is returned from
>
> isTypeOf( new A(), "Object" )
>
> , but beware, people also can
>
> class A { get [Symbol.toStringTag]() { return "B"; }}
>
> and then
>
> isTypeOf( new A(), "B" )
>
> will return »true«.

Here are my unit tests for isTypeOf:

describe('#isTypeOf', function () {

it('should return false if passed object is undefined',
function () {
expect(object.isTypeOf()).toBeFalsy();
});

it('should return true if object is a number type and
it\'s type is passed in capital letters', function () {
expect(object.isTypeOf(1, 'NUMBER')).toBeTruthy();
});

it('should return true if object is a number type',
function () {
expect(object.isTypeOf(1, 'number')).toBeTruthy();
});

it('should return true if object is an object type',
function () {
expect(object.isTypeOf({}, 'object')).toBeTruthy();
});

it('should return true if object is an array type',
function () {
expect(object.isTypeOf([], 'array')).toBeTruthy();
});

it('should return true if object is a string type',
function () {
expect(object.isTypeOf('test', 'string')).toBeTruthy();
expect(object.isTypeOf('', 'string')).toBeTruthy();
});

it('should return true if object is a boolean type',
function () {
expect(object.isTypeOf(true, 'boolean')).toBeTruthy();
expect(object.isTypeOf(false, 'boolean')).toBeTruthy();
});

// ES6 feature (need this verification especially for
PhantomJS)
if (object.isHostMethod(global, 'Symbol')) {

it('should return true if object is a symbol type',
function () {
expect(object.isTypeOf(global.Symbol('foo'),
'symbol')).toBeTruthy();
});

}

});

And they are all passing.

Cezary Tomczyk

unread,
May 3, 2017, 6:36:18 AM5/3/17
to
On 03/05/2017 11:24, Stefan Ram wrote:
> Cezary Tomczyk <cezary....@gmail.com> writes:
>> And they are all passing.
>
> Yes, but the OP question was about detecting
> whether something is an /object/.

To be precise I was asking rather about why polyfill of
Object.defineProperties

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties#Polyfill

doesn't allow

if (typeof obj !== 'object' || obj === null)
throw new TypeError('bad obj');

to add properties to the other objects like function or array. :-)

The native built-in Object.defineProperties does allow it.

> And, my previous example was not to the point!
> This is more like it:
>
> let a = new String( "abc" );
>
> console.log( Object.prototype.toString.call( a ) );
>
> Object.defineProperty
> ( a, Symbol.toStringTag, { get: function() { return "Number"; }});
>
> console.log( Object.prototype.toString.call( a ) );
>
> This will print
>
> [object String]
> [object Number]
>
> even though »a« is still a string. Console:
>
>> a
> < String { "abc", 4 more... }

Interesting. I didn't know about Symbol.toStringTag
(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toStringTag).


Seems typeof operator returns "object" before changing and after
changing string description of an object.

So, I can't trust what's returned by
Object.prototype.toString.call(obj). However, if I want to check what
type is an object then I can use typeof operator, but it's not quite
thing I want as the typeof for object type array give me "object".

From one side it is fine as the array is an object in fact, but what I
wanted is to check what's the real type of an object.
Object.prototype.toString.call() gives me what I want, but only under
condition that the default string description of an object has not changed.

--
Cezary Tomczyk
http://www.ctomczyk.pl/

Cezary Tomczyk

unread,
May 3, 2017, 12:47:25 PM5/3/17
to
On 03/05/2017 14:07, Stefan Ram wrote:
> Cezary Tomczyk <cezary....@gmail.com> writes:
>> So, I can't trust what's returned by
>> Object.prototype.toString.call(obj). However, if I want to check what
>> type is an object then I can use typeof operator, but it's not quite
>> thing I want as the typeof for object type array give me "object".
>
> I just had this idea: You can tag each prototype with your
> own tag symbol: For example, to tag the String prototype:
>
> |> let myTag = Symbol( "myTag" );
> |> Object.getPrototypeOf( "" )[ myTag ] = "String"
>
> Now, when we have any object, we can check its prototype for
> our tag:
>
> |> Object.getPrototypeOf( "abc" )[ myTag ]
>
> . No one else can mess with our tag, because they don't have
> the symbol!
>
> At least it seems to work for numbers and strings here:
>
> |> Object.getPrototypeOf( 1 )[ myTag ]= "Number"
>
> |> Object.getPrototypeOf( 2 )[ myTag ]
> |< Number
>
> |> Object.getPrototypeOf( "abc" )[ myTag ]
> |< String

Interesting approach. It seems it's working. Thanks.

Let's see if anyone else would have a different idea.

Cezary Tomczyk

unread,
May 5, 2017, 2:33:01 AM5/5/17
to
On 05/05/2017 04:39, Stefan Ram wrote:
> r...@zedat.fu-berlin.de (Stefan Ram) writes:
>> Cezary Tomczyk <cezary....@gmail.com> writes:
>>> Native built-in Object.defineProperties method allows me to add
>>> properties to a function as the function is an object.
>> The Web gives:
>> function isObject(val) {
>> if (val === null) { return false;}
>> return ( (typeof val === 'function') || (typeof val === 'object') );
>> }
>> . The above might not work with host objects, which might
>> have even other types.
>
> However, according to Flanagan, a host object may not
> return »undefined«, »boolean«, »number«, or »string« as
> its type. So, another way to check whether something is
> an object might be to check that its type is not one
> of those.

Yes, but typeof for [] will still returns "object".

One more thing. Regarding:

let a = new String( "abc" );

console.log( Object.prototype.toString.call( a ) );

Object.defineProperty
( a, Symbol.toStringTag, { get: function() { return "Number"; }});

console.log( Object.prototype.toString.call( a ) );

if typing "a instanceof String" then there is "true". So, perhaps
instanceof is the way of checking the type of object instead of
Object.prototype.toString.call or typeof or Object.defineProperty[...]

Thomas 'PointedEars' Lahn

unread,
May 5, 2017, 12:15:13 PM5/5/17
to
Cezary Tomczyk wrote:

> On 03/05/2017 09:24, Stefan Ram wrote:
>> Cezary Tomczyk <cezary....@gmail.com> writes:
>>> Native built-in Object.defineProperties method allows me to add
>>> properties to a function as the function is an object.
>>
>> The Web gives:
>>
>> function isObject(val) {
>> if (val === null) { return false;}
>> return ( (typeof val === 'function') || (typeof val === 'object') );
>> }
>>
>> . The above might not work with host objects, which might
>> have even other types.

Correct. But it SHOULD work with newer implementations, see

<http://www.ecma-international.org/ecma-262/7.0/#sec-typeof-operator>

[This *huge* specification has a content filter in the sidebar (now?),
which is very handy. It would be better, though, if it were not *only*
one huge document.]

>> So another suggestion from the Web is:
>>
>> function isObject(obj) {
>> return obj === Object(obj);
>> }

Nice. But note that this would also designate convertible primitive values
“not an object”: isObject(42) === false.

>> Other observations:
>>
>> Object.getPrototypeOf does /not/ throw when called with an object.

I beg your pardon?

> I use:
>
> function isTypeOf(obj, typeToCheck) {
> if (arguments.length < 2) {
> return false;
> }
> return Object.prototype.toString.call(obj).slice(8,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> -1).toLowerCase() === typeToCheck.toLowerCase();
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> }
>
> which gives me more precisely a type of object.

I hope you have optimized that somewhat.

> The Object.defineProperties says about object, but function, array are
> also objects. That's why:
>
> if (typeof obj !== 'object' || obj === null)
> throw new TypeError('bad obj');
>
> looks like a bug to me.

typeof [] === "object", so that is OK. (common misconception)

typeof function () {} === "function", so that is not OK in theory; however,
one might decide that it is not OK to modify function objects.

--
PointedEars
FAQ: <http://PointedEars.de/faq> | <http://PointedEars.de/es-matrix>
<https://github.com/PointedEars> | <http://PointedEars.de/wsvn/>
Twitter: @PointedEars2 | Please do not cc me./Bitte keine Kopien per E-Mail.

Thomas 'PointedEars' Lahn

unread,
May 5, 2017, 12:18:58 PM5/5/17
to
Cezary Tomczyk wrote:

> On 03/05/2017 14:07, Stefan Ram wrote:
>> Cezary Tomczyk <cezary....@gmail.com> writes:
>>> So, I can't trust what's returned by
>>> Object.prototype.toString.call(obj). However, if I want to check what
>>> type is an object then I can use typeof operator, but it's not quite
>>> thing I want as the typeof for object type array give me "object".
>>
>> I just had this idea: You can tag each prototype with your
>> own tag symbol: For example, to tag the String prototype:
>>
>> |> let myTag = Symbol( "myTag" );
>> |> Object.getPrototypeOf( "" )[ myTag ] = "String"
>> […]
> Interesting approach. It seems it's working. Thanks.
>
> Let's see if anyone else would have a different idea.

Quack ;-)

Thomas 'PointedEars' Lahn

unread,
May 5, 2017, 12:24:50 PM5/5/17
to
Stefan Ram wrote:

> r...@zedat.fu-berlin.de (Stefan Ram) writes:
>>Cezary Tomczyk <cezary....@gmail.com> writes:
>>>Native built-in Object.defineProperties method allows me to add
>>>properties to a function as the function is an object.
>>The Web gives:
>>function isObject(val) {
>> if (val === null) { return false;}
>> return ( (typeof val === 'function') || (typeof val === 'object') );
>>}
>>. The above might not work with host objects, which might
>>have even other types.
>
> However, according to Flanagan, a host object may not
> return »undefined«, »boolean«, »number«, or »string« as
> its type. So, another way to check whether something is
> an object might be to check that its type is not one
> of those.

The same as you continue to neglect to cite your sources properly,
"Flanagan" neglects to mention that he is referring to more recent editions
of the ECMAScript Language Specification there, and that some implementors
did not care.

Cezary Tomczyk

unread,
May 5, 2017, 1:37:54 PM5/5/17
to
On 05/05/2017 18:14, Thomas 'PointedEars' Lahn wrote:
> Cezary Tomczyk wrote:
[...]
>> I use:
>>
>> function isTypeOf(obj, typeToCheck) {
>> if (arguments.length < 2) {
>> return false;
>> }
>> return Object.prototype.toString.call(obj).slice(8,
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>> -1).toLowerCase() === typeToCheck.toLowerCase();
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>> }
>>
>> which gives me more precisely a type of object.
>
> I hope you have optimized that somewhat.

I can't find a reliable way at the moment. Perhaps I should combine few
methods, but that might be an overengineering.

>> The Object.defineProperties says about object, but function, array are
>> also objects. That's why:
>>
>> if (typeof obj !== 'object' || obj === null)
>> throw new TypeError('bad obj');
>>
>> looks like a bug to me.
>
> typeof [] === "object", so that is OK. (common misconception)

In a sense of that [] is an object - yes, but I'd prefer to know that
tested object is in fact an Array type. Operator instanceof looks
promising, but I need to check it anyway.

> typeof function () {} === "function", so that is not OK in theory; however,
> one might decide that it is not OK to modify function objects.

Functions are objects so they can be extended like any other objects.

Thomas 'PointedEars' Lahn

unread,
May 5, 2017, 2:12:00 PM5/5/17
to
Cezary Tomczyk wrote:

> On 05/05/2017 18:14, Thomas 'PointedEars' Lahn wrote:
>> Cezary Tomczyk wrote:
> [...]
>>> I use:
>>>
>>> function isTypeOf(obj, typeToCheck) {
>>> if (arguments.length < 2) {
>>> return false;
>>> }
>>> return Object.prototype.toString.call(obj).slice(8,
>> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>> -1).toLowerCase() === typeToCheck.toLowerCase();
>> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>> }
>>>
>>> which gives me more precisely a type of object.
>> I hope you have optimized that somewhat.
>
> I can't find a reliable way at the moment. Perhaps I should combine few
> methods, but that might be an overengineering.

To begin with, you should have

var _toString = {}.toString;

function isTypeOf(obj, typeToCheck) {
if (arguments.length < 2) {
return false;
}
return _toString.call(obj).slice(8, -1).toLowerCase() ===
typeToCheck.toLowerCase();
}

>>> The Object.defineProperties says about object, but function, array are
>>> also objects. That's why:
>>>
>>> if (typeof obj !== 'object' || obj === null)
>>> throw new TypeError('bad obj');
>>>
>>> looks like a bug to me.
>> typeof [] === "object", so that is OK. (common misconception)
>
> In a sense of that [] is an object - yes, but I'd prefer to know that
> tested object is in fact an Array type. Operator instanceof looks
> promising, but I need to check it anyway.

Array.isArray() exists (since ECMAScript Ed. 5). For general and own
(array) types, I am using “instanceof” or “constructor”. Most of the time,
though, duck typing instead (“quack” ;-)).

>> typeof function () {} === "function", so that is not OK in theory;
>> however, one might decide that it is not OK to modify function objects.
>
> Functions are objects so they can be extended like any other objects.

They also have built-in, non-inherited, read-only properties. I augment
function objects only as a last resort (currently, to tag them as user-
defined).

Thomas 'PointedEars' Lahn

unread,
May 5, 2017, 3:00:04 PM5/5/17
to
I am not sure that is the reason. ISTM that most importantly, them being
objects allows for references to them to be assigned and passed. The latter
would be useful for an event-driven application.

John G Harris

unread,
May 6, 2017, 5:35:02 AM5/6/17
to
On Fri, 05 May 2017 20:59:56 +0200, Thomas 'PointedEars' Lahn
<Point...@web.de> wrote:

>Cezary Tomczyk wrote:
>
>> On 05/05/2017 18:14, Thomas 'PointedEars' Lahn wrote:
>>> typeof function () {} === "function", so that is not OK in theory;
>>> however, one might decide that it is not OK to modify function objects.
>>
>> Functions are objects so they can be extended like any other objects.
>
>I am not sure that is the reason. ISTM that most importantly, them being
>objects allows for references to them to be assigned and passed. The latter
>would be useful for an event-driven application.

It's values that are assigned and passed, not references. That's why
typeof returns "function", not "reference to function"

John

Cezary Tomczyk

unread,
May 6, 2017, 9:26:09 AM5/6/17
to
On 05/05/2017 20:11, Thomas 'PointedEars' Lahn wrote:
> Cezary Tomczyk wrote:
>
>> On 05/05/2017 18:14, Thomas 'PointedEars' Lahn wrote:
>>> Cezary Tomczyk wrote:
>> [...]
>>>> I use:
>>>>
>>>> function isTypeOf(obj, typeToCheck) {
>>>> if (arguments.length < 2) {
>>>> return false;
>>>> }
>>>> return Object.prototype.toString.call(obj).slice(8,
>>> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>>> -1).toLowerCase() === typeToCheck.toLowerCase();
>>> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>>> }
>>>>
>>>> which gives me more precisely a type of object.
>>> I hope you have optimized that somewhat.
>>
>> I can't find a reliable way at the moment. Perhaps I should combine few
>> methods, but that might be an overengineering.
>
> To begin with, you should have
>
> var _toString = {}.toString;
>
> function isTypeOf(obj, typeToCheck) {
> if (arguments.length < 2) {
> return false;
> }
> return _toString.call(obj).slice(8, -1).toLowerCase() ===
> typeToCheck.toLowerCase();
> }

Tiny optimisation, but that's fine. :-)

>>>> The Object.defineProperties says about object, but function, array are
>>>> also objects. That's why:
>>>>
>>>> if (typeof obj !== 'object' || obj === null)
>>>> throw new TypeError('bad obj');
>>>>
>>>> looks like a bug to me.
>>> typeof [] === "object", so that is OK. (common misconception)
>>
>> In a sense of that [] is an object - yes, but I'd prefer to know that
>> tested object is in fact an Array type. Operator instanceof looks
>> promising, but I need to check it anyway.
>
> Array.isArray() exists (since ECMAScript Ed. 5). For general and own
> (array) types, I am using “instanceof” or “constructor”. Most of the time,
> though, duck typing instead (“quack” ;-)).

Ah, right, I forgot about Array.isArray() :)

Finally I rewrote it a bit:

var _toString = {}.toString;

function isTypeOf(obj, typeToCheck) {
var objectType;

if (arguments.length !== 2) {
return false;
}

objectType = typeof obj;

if (typeToCheck === 'array' && Array.isArray(obj)
|| typeToCheck === 'null' && objectType === 'object'
|| typeToCheck === 'object' && objectType === 'object'
|| typeToCheck === 'number' && objectType === 'number'
|| typeToCheck === 'string' && objectType === 'string'
|| typeToCheck === 'boolean' && objectType === 'boolean'
|| typeToCheck === 'undefined' && objectType === 'undefined'
|| typeToCheck === 'symbol' && objectType === 'symbol'
) {
return true;
}

return _toString.call(obj).slice(8, -1).toLowerCase() ===
typeToCheck.toLowerCase();
}

Thomas 'PointedEars' Lahn

unread,
May 6, 2017, 10:43:29 AM5/6/17
to
Tiny optimization with big impact, I think. It saves

- 1 lookup along the scope chain up to the global object for “Object”
*every time the function is called*

- 2 lookups along the prototype chain each for “prototype” and “toString”
*every time the function is called*

and removes the dependency on a potentially non-initial value of
“Object.prototype” and “Object.prototype.toString”.

>>>>> The Object.defineProperties says about object, but function, array are
>>>>> also objects. That's why:
>>>>>
>>>>> if (typeof obj !== 'object' || obj === null)
>>>>> throw new TypeError('bad obj');
>>>>>
>>>>> looks like a bug to me.
>>>> typeof [] === "object", so that is OK. (common misconception)
>>>
>>> In a sense of that [] is an object - yes, but I'd prefer to know that
>>> tested object is in fact an Array type. Operator instanceof looks
>>> promising, but I need to check it anyway.
>>
>> Array.isArray() exists (since ECMAScript Ed. 5). For general and own
>> (array) types, I am using “instanceof” or “constructor”. Most of the
>> time, though, duck typing instead (“quack” ;-)).
>
> Ah, right, I forgot about Array.isArray() :)

Be sure to emulate it if it is not there.

> Finally I rewrote it a bit:
>
> var _toString = {}.toString;
>
> function isTypeOf(obj, typeToCheck) {
> var objectType;
>
> if (arguments.length !== 2) {

This change does not make sense to me. “< 2” was clearer.

> return false;
> }
>
> objectType = typeof obj;

IMO, DRY demands declaration and initialization in one statement, if
possible.

> if (typeToCheck === 'array' && Array.isArray(obj)
> || typeToCheck === 'null' && objectType === 'object'
> || typeToCheck === 'object' && objectType === 'object'
> || typeToCheck === 'number' && objectType === 'number'
> || typeToCheck === 'string' && objectType === 'string'
> || typeToCheck === 'boolean' && objectType === 'boolean'
> || typeToCheck === 'undefined' && objectType === 'undefined'
> || typeToCheck === 'symbol' && objectType === 'symbol'

I would use parentheses to make clear what conditions belong together.

> ) {
> return true;
> }
>
> return _toString.call(obj).slice(8, -1).toLowerCase() ===
> typeToCheck.toLowerCase();
> }

The question remains: What is being *gained* by this comparably inefficient
precision instead of duck typing? _Not_ all things that *can* be done also
*should* be done.

Cezary Tomczyk

unread,
May 6, 2017, 2:23:10 PM5/6/17
to
On 06/05/2017 16:43, Thomas 'PointedEars' Lahn wrote:
> Cezary Tomczyk wrote:
>
>> On 05/05/2017 20:11, Thomas 'PointedEars' Lahn wrote:
>>> Cezary Tomczyk wrote:
>>>> On 05/05/2017 18:14, Thomas 'PointedEars' Lahn wrote:
>>>>> Cezary Tomczyk wrote:
>>>> [...]
[...]
>>> To begin with, you should have
>>>
>>> var _toString = {}.toString;
>>>
>>> function isTypeOf(obj, typeToCheck) {
>>> if (arguments.length < 2) {
>>> return false;
>>> }
>>> return _toString.call(obj).slice(8, -1).toLowerCase() ===
>>> typeToCheck.toLowerCase();
>>> }
>>
>> Tiny optimisation, but that's fine. :-)
>
> Tiny optimization with big impact, I think. It saves
>
> - 1 lookup along the scope chain up to the global object for “Object”
> *every time the function is called*
>
> - 2 lookups along the prototype chain each for “prototype” and “toString”
> *every time the function is called*
>
> and removes the dependency on a potentially non-initial value of
> “Object.prototype” and “Object.prototype.toString”.

That's true, but it is also important to mention that the impact is
significant in case of hundred operations at the same time.

>>>>>> The Object.defineProperties says about object, but function, array are
>>>>>> also objects. That's why:
>>>>>>
>>>>>> if (typeof obj !== 'object' || obj === null)
>>>>>> throw new TypeError('bad obj');
>>>>>>
>>>>>> looks like a bug to me.
>>>>> typeof [] === "object", so that is OK. (common misconception)
>>>>
>>>> In a sense of that [] is an object - yes, but I'd prefer to know that
>>>> tested object is in fact an Array type. Operator instanceof looks
>>>> promising, but I need to check it anyway.
>>>
>>> Array.isArray() exists (since ECMAScript Ed. 5). For general and own
>>> (array) types, I am using “instanceof” or “constructor”. Most of the
>>> time, though, duck typing instead (“quack” ;-)).
>>
>> Ah, right, I forgot about Array.isArray() :)
>
> Be sure to emulate it if it is not there.

Yes, thanks.

>> Finally I rewrote it a bit:
>>
>> var _toString = {}.toString;
>>
>> function isTypeOf(obj, typeToCheck) {
>> var objectType;
>>
>> if (arguments.length !== 2) {
>
> This change does not make sense to me. “< 2” was clearer.

Passing 1 argument doesn't make sense and that's why I've used !== 2.

>> return false;
>> }
>>
>> objectType = typeof obj;
>
> IMO, DRY demands declaration and initialization in one statement, if
> possible.

I did it on purpose. If argument will be !== 2 then it is not efficient
to initialize anything before.

>> if (typeToCheck === 'array' && Array.isArray(obj)
>> || typeToCheck === 'null' && objectType === 'object'
>> || typeToCheck === 'object' && objectType === 'object'
>> || typeToCheck === 'number' && objectType === 'number'
>> || typeToCheck === 'string' && objectType === 'string'
>> || typeToCheck === 'boolean' && objectType === 'boolean'
>> || typeToCheck === 'undefined' && objectType === 'undefined'
>> || typeToCheck === 'symbol' && objectType === 'symbol'
>
> I would use parentheses to make clear what conditions belong together.

That can be done :-)

>> ) {
>> return true;
>> }
>>
>> return _toString.call(obj).slice(8, -1).toLowerCase() ===
>> typeToCheck.toLowerCase();
>> }
>
> The question remains: What is being *gained* by this comparably inefficient
> precision instead of duck typing? _Not_ all things that *can* be done also
> *should* be done.

The gain is to get the real type (let me call it like that) of object.

Thomas 'PointedEars' Lahn

unread,
May 6, 2017, 4:22:06 PM5/6/17
to
Cezary Tomczyk wrote:

> On 06/05/2017 16:43, Thomas 'PointedEars' Lahn wrote:
>> Cezary Tomczyk wrote:
>>> On 05/05/2017 20:11, Thomas 'PointedEars' Lahn wrote:
>>>> To begin with, you should have
>>>>
>>>> var _toString = {}.toString;
>>>>
>>>> function isTypeOf(obj, typeToCheck) {
>>>> if (arguments.length < 2) {
>>>> return false;
>>>> }
>>>> return _toString.call(obj).slice(8, -1).toLowerCase() ===
>>>> typeToCheck.toLowerCase();
>>>> }
>>>
>>> Tiny optimisation, but that's fine. :-)
>>
>> Tiny optimization with big impact, I think. It saves
>>
>> - 1 lookup along the scope chain up to the global object for “Object”
>> *every time the function is called*
>>
>> - 2 lookups along the prototype chain each for “prototype” and
>> “toString”
>> *every time the function is called*
>>
>> and removes the dependency on a potentially non-initial value of
>> “Object.prototype” and “Object.prototype.toString”.
>
> That's true, but it is also important to mention that the impact is
> significant in case of hundred operations at the same time.

It is more significant if this function is called several times in a single-
threaded environment: the slower it is, the slower does the program run.

>>> Finally I rewrote it a bit:
>>> […]
>>> function isTypeOf(obj, typeToCheck) {
>>> […]
>>> if (arguments.length !== 2) {
>> This change does not make sense to me. “< 2” was clearer.
>
> Passing 1 argument doesn't make sense and that's why I've used !== 2.

Yes, but …

>>> return false;
>>> }
>>>
>>> objectType = typeof obj;
>> IMO, DRY demands declaration and initialization in one statement, if
>> possible.
>
> I did it on purpose. If argument will be !== 2 then it is not efficient
> to initialize anything before.

[Your reply here does not have anything to do with what you are replying
to.]

… why should the function return “false” if I pass 3 or more arguments to
it?

Besides, an insufficient number of arguments should throw an exception
instead.

>> The question remains: What is being *gained* by this comparably
>> inefficient precision instead of duck typing? _Not_ all things
>> that *can* be done also *should* be done.
>
> The gain is to get the real type (let me call it like that) of object.

Why is that important?

Stefan Weiss

unread,
May 6, 2017, 4:37:21 PM5/6/17
to
Cezary Tomczyk wrote:
> if (typeToCheck === 'array' && Array.isArray(obj)
> || typeToCheck === 'null' && objectType === 'object'
> || typeToCheck === 'object' && objectType === 'object'

So any object except an Array tests positive for "null" - is that intentional?

isTypeOf({}, "null") → true

Also, if you're going to such lengths, you might as well include the
"function" type in the conditional.

On the whole, I'm not convinced that this function is very useful in
practice. I can't think of many situations where I would consider `false`
and `new Boolean(false)` as having the same "type". They're certainly not
interchangable. The `typeof` operator and `Array.isArray()` are usually all
that's needed.

- stefan

Cezary Tomczyk

unread,
May 6, 2017, 5:06:02 PM5/6/17
to
You're right. I've simplified it. Aparat of 'typeof' operator,
'Array.isArray()' I need to cover 'null' case.

function isTypeOf(obj, typeToCheck) {
var type;

if (arguments.length < 2) {
throw new Error('[isTypeOf] requires two arguments');
}

type = typeToCheck.toLowerCase();

if (type === 'null' && obj === null) {
return 'null';
}

if (type === 'array' && Array.isArray(obj)) {
return 'array';
}

return typeof obj === type;

Cezary Tomczyk

unread,
May 6, 2017, 5:19:54 PM5/6/17
to
True and if optimization can be done without complexity I am fine with that.

>>>> Finally I rewrote it a bit:
>>>> […]
>>>> function isTypeOf(obj, typeToCheck) {
>>>> […]
>>>> if (arguments.length !== 2) {
>>> This change does not make sense to me. “< 2” was clearer.
>>
>> Passing 1 argument doesn't make sense and that's why I've used !== 2.
>
> Yes, but …
>
>>>> return false;
>>>> }
>>>>
>>>> objectType = typeof obj;
>>> IMO, DRY demands declaration and initialization in one statement, if
>>> possible.
>>
>> I did it on purpose. If argument will be !== 2 then it is not efficient
>> to initialize anything before.
>
> [Your reply here does not have anything to do with what you are replying
> to.]
>
> … why should the function return “false” if I pass 3 or more arguments to
> it?
>
> Besides, an insufficient number of arguments should throw an exception
> instead.

Agreed. In my other post I do throw new Error.

>>> The question remains: What is being *gained* by this comparably
>>> inefficient precision instead of duck typing? _Not_ all things
>>> that *can* be done also *should* be done.
>>
>> The gain is to get the real type (let me call it like that) of object.
>
> Why is that important?

One of the reason is because I use it in other function that extends
deeply one object with another. I can't use Object.assign as the changes
on extended object must not mutate original object.

Stefan Weiss

unread,
May 6, 2017, 6:30:04 PM5/6/17
to
Cezary Tomczyk wrote:
> function isTypeOf(obj, typeToCheck) {
> var type;
>
> if (arguments.length < 2) {
> throw new Error('[isTypeOf] requires two arguments');
> }
>
> type = typeToCheck.toLowerCase();
>
> if (type === 'null' && obj === null) {
> return 'null';
> }
>
> if (type === 'array' && Array.isArray(obj)) {
> return 'array';
> }
>
> return typeof obj === type;
> }

It's shorter, but I still don't see the advantage of this:

isTypeOf(value, "null")
isTypeOf(value, "string")
isTypeOf(value, "array")

over this:

value === null
typeof value == "string"
Array.isArray(value)

There may be a use for a function that can examine values using a
dynamically provided type - for example in a debugger, something like "list
all object properties, sorted by type, and separating nulls and arrays".

But I'm wondering if you actually need it. In another post, you wrote:

| One of the reason is because I use it in other function that extends
| deeply one object with another. I can't use Object.assign as the
| changes on extended object must not mutate original object.

Could you elaborate on that? Object.assign() only changes the first of its
arguments (the target); the rest stays intact. There are other caveats with
Object.assign(), but the source object should not be changed (unless its
getters are doing something strange).


- stefan

Thomas 'PointedEars' Lahn

unread,
May 6, 2017, 8:38:11 PM5/6/17
to
Cezary Tomczyk wrote:

> function isTypeOf(obj, typeToCheck) {
> var type;
>
> if (arguments.length < 2) {
> throw new Error('[isTypeOf] requires two arguments');
> }

You should throw a TypeError or a custom-typed exception (I throw
object.js:jsx.InvalidArgumentError for such).

The message is confusing (“Is ‘isTypeOf’ the element of a one-element
array?”)

Thomas 'PointedEars' Lahn

unread,
May 6, 2017, 8:40:41 PM5/6/17
to
Cezary Tomczyk wrote:

> On 06/05/2017 22:21, Thomas 'PointedEars' Lahn wrote:
>> Besides, an insufficient number of arguments should throw an exception
>> instead.
>
> Agreed. In my other post I do throw new Error.

See my follow-up there.

>>>> The question remains: What is being *gained* by this comparably
>>>> inefficient precision instead of duck typing? _Not_ all things
>>>> that *can* be done also *should* be done.
>>> The gain is to get the real type (let me call it like that) of object.
>> Why is that important?
>
> One of the reason is because I use it in other function that extends
> deeply one object with another. I can't use Object.assign as the changes
> on extended object must not mutate original object.

See Stefan’s follow-up.

Cezary Tomczyk

unread,
May 7, 2017, 3:42:59 AM5/7/17
to
On 07/05/2017 02:37, Thomas 'PointedEars' Lahn wrote:
> Cezary Tomczyk wrote:
>
>> function isTypeOf(obj, typeToCheck) {
>> var type;
>>
>> if (arguments.length < 2) {
>> throw new Error('[isTypeOf] requires two arguments');
>> }
>
> You should throw a TypeError or a custom-typed exception (I throw
> object.js:jsx.InvalidArgumentError for such).

Nice tip. I will use:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError

However, optional argument 'lineNumber' there is a bit weird to me as I
don't know where the specified fragment of code will be in a production
version (after minification) and therefore I can't pass there a line number.

> The message is confusing (“Is ‘isTypeOf’ the element of a one-element
> array?”)

The purpose of that message is to tell you where the message happened,
here the '[isTypeOf]' refers to function name, and what happened, here
is a message that 'requires two arguments'.

Cezary Tomczyk

unread,
May 7, 2017, 4:06:49 AM5/7/17
to
Here is the example case:

var o = { a: [1] },
c = {};

Object.assign(c, o);

console.log(o.a[0]);

1

console.log(c.a[0]);

1

c.a[0] = 2;

console.log(o.a[0]);

2

but should be 1.

So, Object.assign does shallow copy, not deep copy. My goal , by having
"extend" function in that case, is to do deep copy.

If I do extend({}, o); then new object will have his own data and
changes made on new object must not reflect on the source object.

So far I've done:

function extend(target, source) {
var output = target;

function processProperty(key) {
if (isTypeOf(source[key], 'object')) {
output[key] = {};
extend(output[key], source[key]);
} else if (isTypeOf(source[key], 'array')) {
output[key] = source[key].slice(0);
} else {
output[key] = source[key];
}
}

Object.keys(source).forEach(processProperty);

return output;
}

Unit test passes when I run them in the browser.

it('should extend new object with specified object
(deep copy)', function () {
var o = {
id: 1,
arr: [
{
example: 'example'
}
],
nodes: [
document.createElement('span')
],
str: 'str'
},
obj = object.extend({}, o);

obj.id = 2;
obj.arr[0].example = 'example2';
obj.nodes[0] = document.createElement('div');
obj.str = 'str2';

expect(o.id).toBe(1);
expect(o.arr[0].example).toBe('example');
expect(o.nodes[0].nodeName.toLowerCase()).toBe('span');
expect(o.str).toBe('str');
});


However, when I run it in PhantomJS 2.1.1 (Mac OS X) then it throws
exception:

"TypeError: Requested keys of a value that is not an object. in
object.js (line 9)"

While I understand that "source" is passed as a non-object then I can't
find out what's causing it. At least debugging PhantomJS is a bit hard.

Thomas 'PointedEars' Lahn

unread,
May 7, 2017, 11:11:10 AM5/7/17
to
Cezary Tomczyk wrote:

> On 07/05/2017 02:37, Thomas 'PointedEars' Lahn wrote:
>> Cezary Tomczyk wrote:
>>> function isTypeOf(obj, typeToCheck) {
>>> var type;
>>>
>>> if (arguments.length < 2) {
>>> throw new Error('[isTypeOf] requires two arguments');
>>> }
>>
>> You should throw a TypeError or a custom-typed exception (I throw
>> object.js:jsx.InvalidArgumentError for such).
>
> Nice tip. I will use:
> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
>
> However, optional argument 'lineNumber' there is a bit weird to me as I
> don't know where the specified fragment of code will be in a production
> version (after minification) and therefore I can't pass there a line
> number.

It is optional and proprietary anyway.

But, if available, you can obtain the URI and line number from the “stack”
property and set them later. (This is where custom-typed exceptions come
in.)

>> The message is confusing (“Is ‘isTypeOf’ the element of a one-element
>> array?”)
>
> The purpose of that message is to tell you where the message happened,
> here the '[isTypeOf]' refers to function name, and what happened, here
> is a message that 'requires two arguments'.

I *know* what it is *supposed* to tell. It is nevertheless confusing to the
person reading it. The message should not require such an explanation.

It should also say more than “requires two arguments” because the next
question of the developer would be “What arguments does it require, then?”.

See also jsx.InvalidArgumentError; currently

<https://github.com/PointedEars/JSX/blob/master/object.js#L3924-L3947>

Usage example:

<https://github.com/PointedEars/JSX/blob/master/object.js#L2669-L2671>

Cezary Tomczyk

unread,
May 7, 2017, 2:08:28 PM5/7/17
to
On 07/05/2017 17:11, Thomas 'PointedEars' Lahn wrote:
> Cezary Tomczyk wrote:
>
>> On 07/05/2017 02:37, Thomas 'PointedEars' Lahn wrote:
>>> Cezary Tomczyk wrote:
>>>> function isTypeOf(obj, typeToCheck) {
>>>> var type;
>>>>
>>>> if (arguments.length < 2) {
>>>> throw new Error('[isTypeOf] requires two arguments');
>>>> }
>>>
>>> You should throw a TypeError or a custom-typed exception (I throw
>>> object.js:jsx.InvalidArgumentError for such).
>>
>> Nice tip. I will use:
>> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError
>>
>> However, optional argument 'lineNumber' there is a bit weird to me as I
>> don't know where the specified fragment of code will be in a production
>> version (after minification) and therefore I can't pass there a line
>> number.
>
> It is optional and proprietary anyway.
>
> But, if available, you can obtain the URI and line number from the “stack”
> property and set them later. (This is where custom-typed exceptions come
> in.)

Thank you. I am going to use it in that way.

>>> The message is confusing (“Is ‘isTypeOf’ the element of a one-element
>>> array?”)
>>
>> The purpose of that message is to tell you where the message happened,
>> here the '[isTypeOf]' refers to function name, and what happened, here
>> is a message that 'requires two arguments'.
>
> I *know* what it is *supposed* to tell. It is nevertheless confusing to the
> person reading it. The message should not require such an explanation.
>
> It should also say more than “requires two arguments” because the next
> question of the developer would be “What arguments does it require, then?”.

And then, if available, developer may look at the documentation. ;-)
But I agree that the error message should be more precise. One of the
most annoying thing is to get unclear error message.

Stefan Weiss

unread,
May 7, 2017, 6:50:55 PM5/7/17
to
Cezary Tomczyk wrote:
> So, Object.assign does shallow copy, not deep copy. My goal , by having
> "extend" function in that case, is to do deep copy.
>
> If I do extend({}, o); then new object will have his own data and changes
> made on new object must not reflect on the source object.

Okay, that makes sense. I thought you were saying that the action of
Object.assign() itself caused the source object to be modified.

> function extend(target, source) {
> var output = target;
>
> function processProperty(key) {
> if (isTypeOf(source[key], 'object')) {
> output[key] = {};
> extend(output[key], source[key]);
> } else if (isTypeOf(source[key], 'array')) {
> output[key] = source[key].slice(0);
> } else {
> output[key] = source[key];
> }
> }
>
> Object.keys(source).forEach(processProperty);
>
> return output;
> }

Without testing this myself, I can see a potential problem: an array in the
source object could contain objects as elements. Your extend() function does
a shallow copy on arrays, so modifying objects in the target array will
affect both the source and the target.

Apart from that, I still don't quite see the use for the isTypeOf()
function... what's wrong with this:

if (Array.isArray(source[key])) {
// clone array (and recurse)
} else if (typeof source[key] == "object" && source[key] !== null) {
// clone object (and recurse)
} else {
// copy scalar values (and null)
}

For the sake of completeness, I'd like to amend my earlier statement:
The `typeof` operator, `Array.isArray()`, *and strict comparisons* are
usually all that's needed.

Also for the sake of completeness, not all objects can be cloned 1:1 so that
the they are 100% independent and disconnected. Simple data container
objects should be fine, though.

> However, when I run it in PhantomJS 2.1.1 (Mac OS X) then it throws exception:
>
> "TypeError: Requested keys of a value that is not an object. in object.js
> (line 9)"
>
> While I understand that "source" is passed as a non-object then I can't find
> out what's causing it. At least debugging PhantomJS is a bit hard.

Sorry, I don't have enough experience with PhantomJS to help with that. I
suggested it for my current project, but was rejected. Their mailing list
may be worth a try: https://groups.google.com/forum/#!forum/phantomjs


- stefan

Cezary Tomczyk

unread,
May 8, 2017, 7:17:57 AM5/8/17
to
On 08/05/2017 00:50, Stefan Weiss wrote:
> Cezary Tomczyk wrote:
>> So, Object.assign does shallow copy, not deep copy. My goal , by having
>> "extend" function in that case, is to do deep copy.
>>
>> If I do extend({}, o); then new object will have his own data and changes
>> made on new object must not reflect on the source object.
>
> Okay, that makes sense. I thought you were saying that the action of
> Object.assign() itself caused the source object to be modified.
>
>> function extend(target, source) {
>> var output = target;
>>
>> function processProperty(key) {
>> if (isTypeOf(source[key], 'object')) {
>> output[key] = {};
>> extend(output[key], source[key]);
>> } else if (isTypeOf(source[key], 'array')) {
>> output[key] = source[key].slice(0);
>> } else {
>> output[key] = source[key];
>> }
>> }
>>
>> Object.keys(source).forEach(processProperty);
>>
>> return output;
>> }
>
> Without testing this myself, I can see a potential problem: an array in the
> source object could contain objects as elements. Your extend() function does
> a shallow copy on arrays, so modifying objects in the target array will
> affect both the source and the target.

Since my last post I rewrote it to:

function extend(destination, source) {
var property,
src;

function cloneArray(a) {
return Object.assign({}, a);
}

for (property in source) {

if (Object.prototype.hasOwnProperty.call(source, property)) {
src = source[property];

if (isTypeOf(src, 'object')) {
destination[property] = destination[property] || {};
extend(destination[property], src);
} else if (Array.isArray(src)) {
destination[property] = src.map(cloneArray);
} else if (getTypeOf(src) === 'date') {
destination[property] = new Date(src.valueOf());
} else if (getTypeOf(src) === 'regexp') {
destination[property] = new RegExp(src);
} else {
destination[property] = source[property];
}
}

}
return destination;
}

Of course, this doesn't cover all scenarios, but at least some major
cases that I need.

> Apart from that, I still don't quite see the use for the isTypeOf()
> function... what's wrong with this:
>
> if (Array.isArray(source[key])) {
> // clone array (and recurse)
> } else if (typeof source[key] == "object" && source[key] !== null) {
> // clone object (and recurse)
> } else {
> // copy scalar values (and null)
> }

There are even more cases that I've discovered while writing unit tests.
I gave up and simplified to:

var _toString = {}.toString;

function getTypeOf(obj) {
if (arguments.length !== 1) {
return false;
}

return _toString.call(obj).slice(8, -1).toLowerCase();
}

function isTypeOf(obj, typeToCheck) {
var type;

if (arguments.length < 2) {
throw new TypeError('[isTypeOf] requires two arguments');
// The error message will have more precise data as Thomas suggested
}

type = typeToCheck.toLowerCase();

return getTypeOf(obj) === type;
}

> For the sake of completeness, I'd like to amend my earlier statement:
> The `typeof` operator, `Array.isArray()`, *and strict comparisons* are
> usually all that's needed.

This would not be enough. For example, if you do typeof Date or RegExp
object you will get "object" in result. And that is not what I want.
Because of that I think using "toString" here is fine. Even, if there
are edge cases, at the moment I think this is an acceptable solution.
Unless there will be no better solution ;-)

> Also for the sake of completeness, not all objects can be cloned 1:1 so that
> the they are 100% independent and disconnected. Simple data container
> objects should be fine, though.

>> However, when I run it in PhantomJS 2.1.1 (Mac OS X) then it throws exception:
>>
>> "TypeError: Requested keys of a value that is not an object. in object.js
>> (line 9)"
>>
>> While I understand that "source" is passed as a non-object then I can't find
>> out what's causing it. At least debugging PhantomJS is a bit hard.
>
> Sorry, I don't have enough experience with PhantomJS to help with that. I
> suggested it for my current project, but was rejected. Their mailing list

Interesting, but I do not see more reliable way of executing tests in
browser environment. I mean, you can use browser itself and execute
manually, but that's not the point. I know also that there are some fake
DOM solution for NodeJS, but I have no experience with that. Anyway,
it's an off-topic subject :-)
Thanks. I've handled the problem.

--
Cezary Tomczyk
http://www.ctomczyk.pl/
0 new messages