美文网首页Web前端之路让前端飞Web 前端开发
你不知道的JavaScript(中卷)|附录

你不知道的JavaScript(中卷)|附录

作者: xpwei | 来源:发表于2017-11-08 15:22 被阅读64次

    宿主对象
    JavaScript中有关变量的规则定义得十分清楚,但也不乏一些例外情况,比如自动定义的变量,以及由宿主环境(浏览器等)创建并提供给JavaScript引擎的变量——所谓的“宿主对象”(包括内建对象和函数)。

    var a = document.createElement( "div" );
    typeof a; // "object"--正如所料
    Object.prototype.toString.call( a ); // "[object HTMLDivElement]"
    a.tagName; // "DIV"
    

    上例中,a不仅仅是一个object,还是一个特殊的宿主对象,因为它是一个DOM元素。其内部的[[Class]]值(为“HTMLDivElement”)来自预定义的属性(通常也是不可更改的)。
    其他需要注意的宿主对象的行为差异有:

    • 无法访问正常的object内建方法,如toString();
    • 无法写覆盖;
    • 包含一些预定义的只读属性;
    • 包含无法将this重载为其他对象的方法;
    • 其他......

    在针对运行环境进行编码时,宿主对象扮演着一个十分关键的角色,但要特别注意其行为特性,因为它们常常有别于普通的JavaScript object。
    在我们经常打交道的宿主对象中,console及其各种方法(log(..)、error(..)等)是比较值得一提的。console对象由宿主环境提供,以便从代码中输出各种值。
    console在浏览器中是输出到开发工具控制台,而在Node.js和其他服务器JavaScript环境中,则是指向JavaScript环境系统进程的标准输出(stdout)和标准错误输出(stderr)。

    全局DOM变量
    你可能已经知道,声明一个全局变量(使用var或者不使用)的结果并不仅仅是创建一个全局变量,而且还会在global对象(在浏览器中为window)中创建一个同名属性。
    还有一个不太为人所知的事实是:由于浏览器演进的历史遗留问题,在创建带有id属性的DOM元素时也会创建同名的全局变量:

    <div id="foo"></div>
    if (typeof foo == "undefined") {
        foo = 42; // 永远也不会运行
    }
    console.log(foo); // HTML元素
    

    你可能认为只有JavaScript代码才能创建全局变量,并且习惯使用typeof或..in window来检测全局变量。但是如上例所示,HTML页面中的内容也会产生全局变量,并且稍不注意就很容易让全局变量检查错误百出。
    这也是尽量不要使用全局变量的一个原因。如果确实要用,也要确保变量名的唯一性,从而避免与其他地方的变量产生冲突,包括HTML和其他第三方代码。

    原生原型
    一个广为人知的JavaScript的最佳实践是:不要扩展原生原型。
    如果向Array.prototype中加入新的方法和属性,假设它们确实有用,设计和命名都很得当,那它最后很有可能会被加入到JavaScript规范当中。这样一来你所做的扩展就会与之冲突。
    所以,首先不要扩展原生方法,除非你确信代码在运行环境中不会有冲突。如果对此你并非100%确定,那么进行扩展是非常危险的。这需要你自己仔细权衡利弊。
    其次,在扩展原生方法时需要加入判断条件(因为你可能无意中覆盖了原来的方法)。对于前面的例子,下面的处理方式要更好一些:

    if (!Array.prototype.push) {
        // Netscape 4没有Array.push
        Array.prototype.push = function (item) {
            this[this.length - 1] = item;
        };
    }
    

    shim/polyfill
    polyfill(或者shim)能有效地为不符合最新规范的老版本浏览器填补缺失的功能,让你能够通过可靠的代码来支持所有你想要支持的运行环境。

    ES5-Shim(https://github.com/es-shims/es5-shim) 是一个完整的shim/polyfill
    集合,能够为你的项目提供ES5 基本规范支持。同样,ES6-Shim(https://
    github.com/es-shims/es6-shim)提供了对ES6 基本规范的支持。虽然我们可
    以通过shim/polyfill 来填补新的API,但是无法填补新的语法。可以使用
    Traceur(https://github.com/google/traceur-compiler/wiki/GettingStarted) 这样
    的工具来实现新旧语法之间的转换。

    对于将来可能成为标准的功能,按照大部分人赞同的方式来预先实现能和将来的标准兼容的polyfill,我们成为prollyfill(probably fill)。

    <script>
    绝大部分网站/Web应用程序的代码都存放在多个文件中,通常可以在网页中使用<scriopt src=..></script>来加载这些文件,或者使用<script> .. </script>来包含内敛代码。
    这些文件和内联代码是相互独立的JavaScript程序还是一个整体呢?
    答案是它们的运行方式更像是相互独立的JavaScript程序,但是并非总是如此。
    它们共享global对象(在浏览器中在是window),也就是说这些文件中的代码在共享的命名空间中运行,并相互交互。
    如果某个script中定义了函数foo(),后面的script代码就可以访问并调用foo(),就像foo()在其内部被声明过一样。
    但是全局变量作用域的提升机制在这些便捷中不适用,因此无论是<script> .. </script>还是<script src=..></script>,下面的代码都无法运行(因为foo()还未被声明):

    <script>foo();</script>
    <script>
        function foo() { .. }
    </script>
    

    但是下面的两端代码则没问题:

    <script>
        foo();
        function foo() { .. }
    </script>
    

    和:

    <script>
        function foo() { .. }
    </script>
    <script>foo();</script>
    

    如果script中的代码(无论是内联代码还是外部代码)发生错误,它会像独立的JavaScript程序那样停止,但是后续的script中的代码(仍然共享global)依然会接着运行,不会受影响。
    你可以使用代码来动态创建script,将其加入到页面的DOM中,效果是一样的:

    var greeting = "Hello World";
    var el = document.createElement("script");
    el.text = "function foo(){ alert( greeting );\
    } setTimeout( foo, 1000 );";
    document.body.appendChild(el);
    

    如果将el.src的值设置为一个文件URL,就可以通过<script src=""></script>动态加载外部文件。

    内联代码和外部文件中的代码之间有一个区别,即在内联代码中不可以出现</script>字符串,一旦出现即被视为代码结束。因此对于下面这样的代码需要非常小心:

    <script>
        var code = "<script>alert( ‘Hello World’ )</script>";
    </script>
    

    上述代码看似没什么问题,但是字符串常量中的</script>将会被当作结束标签来处理,因此会导致错误。常用的变通方法是:

    "</sc" + "ript>";
    

    另外需要注意的一点是,我们是根据代码文件的字符集属性(UTF-8、ISO-8859-8等)来解析外部文件中的代码(或者默认字符集),而内联代码则使用其所在页面文件的字符集(或者默认字符集)。

    现实中的限制
    JavaScript规范对于函数中参数的个数,以及字符串常量的长度等并没有限制;但是由于JavaScript引擎实现各异,规范在某些地方有一些限制。

    function addAll() {
        var sum = 0;
        for (var i = 0; i < arguments.length; i++) {
            sum += arguments[i];
        }
        return sum;
    }
    var nums = [];
    for (var i = 1; i < 100000; i++) {
        nums.push(i);
    }
    addAll(2, 4, 6); // 12
    addAll.apply(null, nums); // 应该是: 499950000
    

    在一些JavaScript引擎中你会得到正确答案499950000,而另外一些引擎(如Safari 6.x)中则会产生错误“RangeError: Maximum call stack size exceeded”。
    下面列出一些已知的限制:

    • 字符串常量中允许的最大字符数(并非只是针对字符串值);
    • 可以作为参数传递到函数中的数据大小(也称为栈大小,以字节为单位);
    • 函数声明中的参数个数;
    • 未经优化的调用栈(例如递归)的最大层数,即函数调用链的最大长度;
    • JavaScript程序以阻塞方式在浏览器中运行的最长时间(秒);
    • 变量名的最大长度。
    好想每天都没有什么事情做,但又害怕每天没有什么事情做

    相关文章

      网友评论

        本文标题:你不知道的JavaScript(中卷)|附录

        本文链接:https://www.haomeiwen.com/subject/wjsemxtx.html