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

宿主对象
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程序以阻塞方式在浏览器中运行的最长时间(秒);
  • 变量名的最大长度。
好想每天都没有什么事情做,但又害怕每天没有什么事情做
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容