1

和其他语言相比,javascript中的对于undefined的理解还是有点让人困惑的。特别是试着理解ReferenceErrors错误("x is not defined")以及在编码过程中如何去避免这些错误总让人感到比较困惑。

这篇文章是我整理的关于这个知识点的内容。如果你对于javascript中的变量以及属性还不是很熟悉的话,你可以看看我之前写的[文章]()

undefined是什么?

Javascript中,存在着Undefined(基本类型),undefined(值),以及defedined(变量)

Undefined(基本类型)是js内置的基本类型之一(String, Number, Boolean, Null, Object)

undefined(值)是一个原始值,是未定义类型的基础值。任何未被赋值的属性值都可以假设为undefined。无返回值或者返回值为空的函数最后执行得到的值都未undefined。未设定的函数参数值也为undefined.

    var a;
    typeof a; // undefined
    
    window.b;
    typeof window.b; // undefined
    
    var c = (function() {})();
    typeof c; // undefined
    
    var d = (function(e) {return e})();
    typeof d; // undefined

undefined(变量)是初始值为undefined的全局属性,既然它是个全局属性,那么我们也可以通过变量来获取它。例如我在这篇文章里面像这样将它作为一个变量来获取它。

    typeof undefined; // undefined
    
    var f = 2;
    f = undefined;  //重新将变量f赋值为变量undefined
    typeof f; // undefined

在ECMA3当中,它的值可以重新被赋值:

    undefined = 'washing machine';
    typeof undefined;  // string
    
    f = undefined;
    typeof f; // string
    f; // 'washing machine'

不用说,将undefined重新赋值是一个比较糟糕的用法。事实上再ECMA5当中这种做法也是不被允许的。

And then there's null?

大致上我们了解这两者之间的区别,但是还是需要重新复述一遍:undefinednull相比,undefined是一个原始值,它表示一个缺省值。undefinednull之间唯一相似的地方就是它们都能被通过类型转换成false

So what's a RefernceError?

一个ReferenceError表示编译器检测到一个无效的引用值。

在实际情况中,ReferenceError往往是在Javascript获取一个未被赋值的引用时被抛出。
注意下在不同浏览器中,ReferenceError现在的不同的语法错误提示信息。正如我们看到的,在不同浏览器中,错误信息并非特别的清楚明了。

    alert(foo);
    
    //FF/Chrome: foo is not defined
    //IE: foo is undefined
    //Safari: can't find variable foo

Still not clear ..."unresolvable reference"?

在ECMA标准中,一个引用值包含一个引用名称及一个基本值。

如果这个引用是一个属性,那么基础值及这个引用分别位于点号的两侧:

    window.foo; // base value = window, reference name = foo;
    a.b; //base value = a, reference name = b;
    myObj['create']; //base value = myObj, reference name = create;
    //Safari, Chrome, IE8+ only
    Object.defineProperty(window, 'foo', {value: 'hello'});
    //base value = window. reference name = foo;

对于引用变量来说,基础值是当前执行上下文的变量对象。全局上线文的变量对象就是全局对象自己(浏览器当中是window)。任何一个函数上下文都有一个被称为活动对象的变量对象。(这个活动对象取决于调用这个函数的执行context)

    var foo;    //base value = window, reference name = foo;
    function a() {
        var b;  // base value = <code>ActivationObject</code>, reference name = b;
    }

如果一个引用的基础值是undefined的话,那么这个引用就被认为unresolvable

因此,如果点号前面的值为undefined,那么这个属性引用就是unresolved。下面的这个例子就会抛出一个ReferenceError的错误,但是这并非是因为TypeError在此之前就被抛出。这是因为一个属性的基础值是属于CheckObjectCoercible,当试着将一个Undefined类型的转化为一个Object,那么这种情况下会抛出TypeError;

    var foo;    
    foo.bar;    //TypeError (base value, foo, is undefined)
    bar.baz;    //ReferenceError (bar is unresolvable)
    undefined.foo   //TypeError (base value is undefined)

如果使用var关键字,那么将会确保变量对象始终有基础值,那么就不会出现引用变量unresolvable的情况。

当引用被定义的时候既不是属性值也不是变量的时候将会抛出一个ReferenceError:

    foo;   //ReferenceError

Javascript检测到引用名foo没有明确的基础值,因此就会去寻找属性名为foo的变量对象。没有找到的话,就会认为引用名foo没有基础值并抛出ReferenceError的错误。

But isn't foo just a undeclared variable?

技术上来说不是。尽管有时候我们觉得undeclared variable是有利用我们去排查bug。但是事实上如果一个变量未被声明的话也就不是一个变量。

What about implicit globals?

未通过var关键字声明的标识符将会默认当做全局变量而被创建,但这只会在这些标识符被赋值的情况下才会生效。

    function a() {
        alert(foo);     // ReferenceError
        bar = [1, 2, 3]; // no error, bar is global
    }
    a();
    bar;    // [1, 2, 3]

这确实让人有点困惑。如果Javascript检测到unresolvable的引用就直接抛出ReferenceErrors就更好了。(事实上在严格模式下javascript确实是这样做的)

When do I need to code against ReferenceErrors?

如果你的代码写的很合理。我们发现在典型的用法中只有一种方式会遇到unresolvable reference: 当使用属性或者变量的句法不正确的时候。在大多数情况下,如果你能确保声明变量的时候使用var关键字时即可避免这种情况。在代码运行过程中唯一可能会遇到的情况就是引用的变量仅仅存在于部分浏览器或者第三方的代码当中。

一个比较好的例子就是console.在webkit浏览器中,console是内置的,console这个属性可以在任何地方获取到。Firefoxconsole属性取决于Firebug是否被安装以及被打开使用。IE7下没有console,IE8下的console属性仅存在于IE Developer Tools被启动的情况下。Opera明显是有console属性的,但是我从来没用使用过。

最后的结果就是,下面的代码在浏览器中运行时还是有可能会抛出ReferenceError的错误。

    console.log(new Date());

How do I code against variables that may not exist?

一种方式就是去通过使用typeof关键字去嗅探一个unresolvable reference,避免抛出ReferenceError错误:

    if (typeof console != 'undefined') {
        console.log(new Date());
    }

然而这种方法对我来说太啰嗦了,更不用说合理了。我是比较反对使用typeof去进行类型检测的。

幸运的是还有另外一种方式:我们已经知道基础值被定义了,但是属性未被定义是不会抛出ReferenceErrorconsole是全局对象的属性值,因此我们可以这样做:

    window.console && console.log(new Date());

事实上在全局环境下仅仅只需要检测变量是否存在(函数中也存在着执行上下文,你可以决定哪些变量可以存在于你的函数当中)。因此理论上至少你可以避免使用typeof来消除ReferenceError的情况。

原文链接


苹果小萝卜
5.1k 声望356 粉丝

Github: [链接]