3

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.

Bardock
  • 65
  • 7
  • 1
    IMHO the code must be wrong or seriously obfuscated - in this context a single bitwise `&` makes no sense at all. I can't make `1 & X` return anything other than zero if X is either an object or undefined. – Alnitak Nov 13 '15 at 11:49
  • @JamesThorpe: What *would* make sense in your eyes? – Bergi Nov 13 '15 at 11:58
  • @Bergi I meant the code makes no sense (unless I guess that function might get called with different arguments that aren't presented here) - what's the in spec is fine – James Thorpe Nov 13 '15 at 11:59
  • 1
    This code works fine if the goal is to set j to 1 if and only when the first argument is something that, when converted to an integer, corresponds to an odd integer, and to 0 otherwise. This includes also strings like "3.1234" and single-element arrays like ["5.98"] which will yield 1. – trincot Nov 13 '15 at 12:13
  • I updated the question and included the full function and how it is called in the actual code. – Bardock Nov 13 '15 at 12:16
  • Now it becomes more clear. This function takes an optional first argument, which when true will indicate the function should recurse into objects in doing its thing. However, it will have undesired effects when the caller does not intend to use this first argument, but happens to have something yield 1 anyway, like with ["3"]. – trincot Nov 13 '15 at 12:19

3 Answers3

3

Bitwise operators do work on 32bit (un)signed integers. If you pass in anything that is not a number, it is implicitly coerced to a number. This is done using the valueOf/toString methods of the object as usual, and for your object {keyA: 1, keyB:2} will yield NaN. Then, this number is casted to an (U)int32, which gives 0 for NaN.

Check the spec for toUint32, follow it to ToNumber and on from there.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
3

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.

trincot
  • 317,000
  • 35
  • 244
  • 286
0

I have built an example using a couple of js classes for declaring Flag Enum and Flag Selections.

The source code is here in my github repo.

Basically to answer the question, and as mentioned by @Bergi, your class should implement valueOf() to return the value of the 'selections'.

// Example:
_w.Feature = new FlagEnum("Feature", {
      CUSTOMER      : "customer info"             // 2
    , ORDER         : "order details"             // 4
    , DELIVERY      : "delivery details"          // 8 
    , DELIVERY_LIST : "listing of all deliveries" // 16
    , DRIVER        : "driver details"            // 32
})
let [CUSTOMER, ORDER, DELIVERY, DELIVERY_LIST, DRIVER] = Feature.get_values()

features = new FlagSel(Feature, CUSTOMER | ORDER | DELIVERY)
// -or-
features = new FlagSel(Feature, [CUSTOMER, ORDER, DELIVERY])

// Using in code

// Check feature using bitwise mask:
if (features & ORDER){
    pc("features includes ORDER")
}
// -or-
// Check feature using has method:
if (features.has(ORDER)){
    pc("features includes ORDER")
}

// Managing values:
features.add(CUSTOMER)
features.rem(ORDER)

// Print available options
console.log(Feature)
// output: <FlagEnum - Feature> options: CUSTOMER, ORDER, DELIVERY, DELIVERY_LIST, DRIVER

// Print selected options
console.log(features)
// output: <FlagSel - Feature> CUSTOMER, ORDER, DELIVERY

Timothy C. Quinn
  • 3,739
  • 1
  • 35
  • 47