Search code examples
javascriptobjectbitwise-and

How does bitwise-and operator work on objects in javascript?


I'm working on an open source project and stumbled over bitwise operators. I do understand the principles and also that javascript evaluates non-zero integer values to true (correct me here if I'm wrong; found this statement in an accepted answer in another post).

The code in question is as follows:

function example(){
  var args = arguments;
  var j = 1 & args[0];
  .
  .
  .
}

var test = {keyA: 1, keyB: 2};
example(test);

My Question is: What's the value of j?

What is the binary equivalent of an object?

As far as i understand it, j = 0 if the last bit in test is 0 and j = 1if the last bit in testis 1.

Please help me out here guys. I spend the last hour searching any nearly related post here and most topics are about numbers, one or two were about booleans and that's that.

Edit: As the code example given above doesn't seem to be as clear as i thought, here the full function as i found it (and working):

function Extend() {
    var args = arguments;
    var target;
    var options;
    var propName;
    var propValue;
    var deep = 1 & args[0];
    var i = 1 + deep;
    target = args[i - 1] || {};

    for (; i < args.length; i++) {
        // Only deal with non-null/undefined values
        if (options = args[i]) {
            // Extend the base object
            for (propName in options) {
                propValue = options[propName];

                if (propValue !== undefined) {
                    propValue = options[propName];
                    target[propName] = (deep && IsPlainObject(target[propName])) ? Extend(deep, {}, propValue) : propValue;
                }
            }
        }
    }

    // Return the modified object
    return target;
}

var _someVariable = Extend({keyA: 1, keyB: 2, keyC: 10}, _someOtherVariable);

deephas to have some meaning as it determines whether to enter the FOR-loop ... or not.


Solution

  • The intent of the deep variable is to detect the difference between these two calls:

    Extend({keyA: {a: 1}, keyB: 2}, {keyC: 3, keyA: {b: 2}});
    

    and

    Extend(true, {keyA: {a: 1}, keyB: 2}, {keyC: 3, keyA: {b: 2}});
    

    The first variant will return this:

    {keyA: {b: 2}, keyB: 2, keyC: 3}
    

    The second is intended to return this:

    {keyA: {a: 1, b: 2}, keyB: 2, keyC: 3}
    

    So the function in fact allows for a first, optional argument, that will make the function apply the extension recursively so you get a deep instead of a shallow extended object. You can also see this intent by analysing the recursive call, where the first argument is deep, the second is the object to extend, and the third the object to extend with. The following line also shows this:

    var i = 1 + deep;
    

    As i is point where the loop will start from, you can see that if deep is set to 1 instead of 0, the loop will start processing from argument #2 onwards, which makes sense, as argument #0 was recognised as being the optional argument, and the next one is the target object. Note that the function accepts a variable number of additional arguments which it will use to extend the target object with. It is over these arguments that the i variable loops.

    As a side note: because of a bug, the second version returns the same as the first. To fix the bug, replace

    target[propName] = (deep && IsPlainObject(target[propName]))
        ? Extend(deep, {}, propValue) : propValue;
    

    with:

    target[propName] = (deep && IsPlainObject(target[propName]))
        ? Extend(deep, target[propName], propValue) : propValue;
    

    Now, coming to the essence:

    var deep = 1 & args[0];
    

    The use of the bitwise operator must have been an idea to have efficiency rule over clarity. The intent was to set deep to 1 if the first argument represented the optional argument, which should be a boolean indicating whether the extending should happen shallow or deep. As objects will make this expression evaluate to 0, it seemed like a nice trick. But there is an issue with this. If one would like to do this:

    Extend(["3"], {myattr: 2});
    

    One would expect to get back an array-like object with an additional custom property myattr:

    {0: "3", length: 1, myattr: 2}
    

    However, the current Extend function will misinterpret the first argument as an instruction to perform a deep extension. This is because 1 & ["3"] will evaluate to 1 & 3, which evaluates to 1. And so the result will be the second argument without any extension happening:

    {myattr: 2}
    

    Conclusion: it is better to avoid such cryptic use of bitwise operators, and do something like this:

    var deep = args.length > 0 && typeof args[0] === 'boolean' && args[0];
    

    In common language: let deep be true (1) when there is at least one argument and that argument is a boolean and its value is true. In all other cases let it be false (0). Note that one cannot pass false as the first argument, thinking that it will make the function perform a shallow extension. In this case, that argument will be taken as the object to extend, which will fail. So the optional first argument, if provided, must be a boolean with value true. This is true both for the original Extend function and the corrected one.

    Finally, it would be good to add comments to this function to clarify the use of the first optional argument.