开发者

Another Javascript Undefined Null Question

So I have been through most of the question开发者_开发知识库s here. Also quite a few articles good and bad.

One thing I am looking for some additional clarification on is how undefined and un declared variables are treated.

Take the code below.

var a;

if(a == null) // True - Due to Type Coercion

if(a == 'null') // False

if(a === null) // False

if(a === 'null') // False

if(a == undefined) // True

if(a === undefined) // True

if(a == 'undefined') // False

if(a === 'undefined') // False

if(a) // False - A is undefined

alert(typeof(a)) // undefined

All of the above I understand. But things get weird when you look at an undeclared variable. Note I am specifically omitting a "var b;".

 alert(typeof(b)) // undefined

 if(typeof(b) == 'undefined') // True

 if(typeof(b) === 'undefined') // True - This tells me the function typeof is returning a string value

 if(typeof(b) == 'null') // False

 if(typeof(b) === 'null') // False

 if(typeof(b) == null) // False

 if(typeof(b) === null) // False

 if(b) // Runtime Error - B is undefined

Any other operation then typeof(b) results in a runtime error. Still I can understand the logic behind the way the laguage is evaluating expressions.

So now I look at a non existent property of a and am really confused.

if(a.c) // Runtime Error - c is null or not an object
alert(typeof(a.c)) // Runtime Error - undefined is null or not an object

I would think that c in this case would be treated like b in the previous example but its not. You have to actually initialize a to something then you can get it to behave as b does. And stop it from throwing runtime errors.

Why is this the case? Is there some special handling of the undefined type or is the typeof function doing something recursively to evaluate the sub property that is throwing the runtime error?

  1. I guess the practical question here is if I am checking a nested object c in a.c I can immediately assume c is undefined if a is undefined?

  2. And what is the best way then if I wanted to check some extremely nested object to see if it was set like x in MyObject.Something.Something.Something.x ? I have to navigate through the structure element by element making sure each one exists before going to the next one down in the chain?


I can immediately assume c is undefined if a is undefined?

Yes.

I have to navigate through the structure element by element making sure each one exists before going to the next one down in the chanin?

Yes.


The reason why

alert(typeof(a.c))

results in a runtime error and

alert(typeof(b))

does not is that in the first example you are trying access a property on an undefined object, which causes a runtime error before the result can be fed into typeof()


Don't forget that undefined is global variable (!), and you (or someone else) can assign it a value, so your example can be wrong here:

if(a == undefined) // True

if(a === undefined) // True

If you really need undefined, then you can get your own "copy" of it

var local_undefined;


Normally you won't come across the need test an extremely (more than three levels) nested object where any of the parents could be undefined. So when you do need to test, I would write it something like this:

if( typeof(a) != 'undefined' && a.c ) {
   // Do something won't run because a is undefined
}

var a = {};

if( typeof(a) != 'undefined' && a.c ) {
   // Do something won't run because though a is defined,
   // a.c is undefined. This expression tests to see if a.c evaluates
   // to true, but won't throw an error even if it is 
   // undefined.
}

If a.c could at any time contain 0 or false but you still needed the test to pass, then use the full typeof test:

var a = {};
a.c = false;

if( typeof(a) != 'undefined' && typeof(a.c) != 'undefined' ) {
   // Do something will run because though both a and a.c are defined.
}


JavaScript is odd in that the value undefined (also typeof a === "undefined") is what variables have until they are given a value. null is a distinct value which is different from undefined. Since the type system in JavaScript is loose there is an implicit type coercion that happens when comparing and testing values of variables.

If a variable is undeclared then you cannot reference it without errors but you can test it with the typeof operator (and its result will be the string "undefined"). A variable which has been declared but not assigned is able to be referenced but still contains the value undefined. You can always reference undefined properties of objects and if they have not been assigned then they will have the value undefined.

See this answer too as I went into more detail about JavaScript type coercion and different values which are often useful to consider empty:

Does VBScript's IsEmpty have an equivalent in JavaScript?

  1. When testing nested objects, if the parent is undefined (or null) then it has no children so no further testing is needed.

  2. To safely test a heavily nested object you will need to test the topmost parent using typeof but you can test any children for actual values (see the testing for empty answer). This is because the top level may not have been declared, but you can always reference undefined properties of objects.


For deeply nested children

try{ if(a.b.c.d.e) {
    // do your stuff
}}catch(e){}

try-catch route is a more elegant and much less type-coding solution

And here's an example:

grand=""
a={ b:{ c:{ d:{ e:"Hello Ancestor" } } } }

try{ if(a.b.c.d.e) {
    grand = a.b.c.d.e
}}catch(e){}

alert( grand )

just take a look at the boring typeof method:

if(typeof a === undefined) {
    if(typeof a.b === undefined) {
        if(typeof a.b.c === undefined) {
            if(typeof a.b.c.d === undefined) {
                if(typeof a.b.c.d.e === undefined) {
                    // your stuff
                }
            }
        }
    }
}

It could be even more elegant and ideal solution after wrapping the try-catch block into a function, however there's no known way to substitute a referenced variable name, which could be passed as a 'string' to a function, with variable contents. e.g. following is not possible:

function isDefined(v) {
    if (typeof valueOfVar(v) !== undefined)
        return true
    else
        return false
}
alert( isDefined('a.b.c.d.e') ) // must be passed as a string to avoid runtime error

there's no valueOfVar() exist in JavaScript, that's just an example


but guess what, I got an enlightment, an evil solution :)

// a={ b:{ c:{ d:{ e:0 } } } }

function exist(v) {
    var local_undefined
    try{ if( eval(v) !== local_undefined ) {
        return true
    }}catch(e){}
    return false
}
alert( exist('a.b.c.d.e') )


An important update to the exists() function — two additional functions that utilize exists()

These cover everything that is ever needed to test whether a variable/property/object at any nesting level is defined/empty/undeclared without ever causing a runtime error in JavaScript

function exists (v) {
    var local_undefined;
    try{ if( eval(v) !== local_undefined ) {
        return true
    }}catch(e){}
    return false
}

function empty (v) {
    if (exists(v)) {
        v = eval(v);
        if (typeof v == 'object') {
            return Object.keys(v).length === 0
        } else if (v)
            return false
    }
    return true
}

function value (v) {
    var local_undefined;
    if (exists(v))
        return eval(v)
    return local_undefined
}


/////////////////////////////////////////
// TEST

ref = 'a.b.c.d.e';

alert( ref +' : '+ value(ref) + '\n'
        + '\nexists\t' + exists(ref)
        + '\nempty\t' + empty(ref)
        + '\nvalue\t' + value(ref)
    );

a = { b:{ c:{ d:{ e:"Hello Ancestor" } } } };
alert( ref +' : '+ value(ref) + '\n'
        + '\nexists\t' + exists(ref)
        + '\nempty\t' + empty(ref)
        + '\nvalue\t' + value(ref)
    )

a = { b:{ c:{ d:{ e:0 } } } };
alert( ref +' : '+ value(ref) + '\n'
        + '\nexists\t' + exists(ref)
        + '\nempty\t' + empty(ref)
        + '\nvalue\t' + value(ref)
    );

b='a'; obj={a:5}; ref='obj[b]';
alert( ref +' : '+ value(ref) + '\n'
        + '\nexists\t' + exists(ref)
        + '\nempty\t' + empty(ref)
        + '\nvalue\t' + value(ref)
    );

However, these methods are working only when exists() empty() value() functions have access to those variables, i.e. both the function and the variables are defined in the same scope.

That's necessary to be able to test local function variables too, otherwise the local function variables that are declared with var are going to be undefined in the called exists() empty() value() function

To test a function's local variable without including exists() empty() value(), the try/catch block should be used within that function


Here's an alternative evil solution to test the local function variables These code snippets can be defined in global scope and then called with eval()

is_ = "v_='"
var_ = "v_='"
get_ = "v_='"
set_ = "v_='"

_exists = "';\nvar local_undefined;\n"
        + "try{ if( eval(v_) === local_undefined ) false; else true }\n"
        + "catch(e){false}\n"

_empty = "';\nif ( eval(\"'\"+_exists) ) {\n"
        + " v_ = eval(v_);\n"
        + " if (typeof v_ == 'object') {\n"
        + "     Object.keys(v_).length === 0;\n"
        + " }\n\telse if (v_)\n"
        + "     false;\n"
        + " else true\n"
        + "} else true"

_value = "';\nif ( eval(\"'\"+_exists) )\n"
    + " eval(v_);\n"
    + "else local_undefined"

_valOrEmpty = "';\n( eval(\"'\"+_exists) )\n"
    + " ? eval(\"'\"+_value) : ''"

_valOrDefault_ = "';\n( eval(\"'\"+_exists) )\n"
    + " ? eval(\"'\"+_value) : "

function f() {
    var a = { b:{ c:{ d:{ e:"Hello Ancestor" } } } };
    ref = 'a.b.c.d.e'
    alert( ref+'\n'   
        +'\nexists\t\t'     + eval(is_  +ref+ _exists)
        +'\nempty\t\t'      + eval(is_  +ref+ _empty)
        +'\nvalue\t\t'      + eval(get_ +ref+ _value)
        +'\n'
        +'\nvalOrEmpty\t'   + eval(get_ +ref+ _valOrEmpty)
        +'\nvalOrDefault\t' + eval(get_ +ref+ _valOrDefault_ +'"Default Value"')
        )
}

d=""; while (d.length < 20) d="—"+d; d="\n\n// "+d+"\n// "
jsCode  ='// ( is_  +var+ _exists )\n\n'                + is_ +'a.b.c.d.e'+_exists
        +d+' ( is_  +var+ _empty )\n\n'                 + is_ +'a.b.c.d.e'+_empty
        +d+' ( get_ +var+ _value )\n\n'                 + get_+'a.b.c.d.e'+_value
        +d+' ( get_ +var+ _valOrEmpty )\n\n'            + var_+'a.b.c.d.e'+_valOrEmpty
        +d+' ( get_ +var+ _valOrDefault_ default )\n\n' + var_+'a.b.c.d.e'+_valOrDefault_+"'Default Value'"

alert(jsCode)

f()
// even though that looks ugly, this is the tidiest solution
// to the awkward 17-year old JavaScript error-handling

Use this wisely

if ( eval(is_  +'any.var.or.property.from.local.or.global.scope'+ _exists) ) {
    // do your stuff
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜