[你不知道的 JavaScript 范例和语法] 第一章:范例

译者的媒介

一向都想好好研讨这个在 GitHub 上很有名望的系列,而翻译恰是最好的浏览门路之一。可以让我浏览的时刻,不那末囫囵吞枣。

图灵社区出书了该系列两部份的中文版——《作用域和闭包》以及《this和对象原型》,我就盘算从《范例和语法》这本最先做起。

同时,我对本书的翻译进度会在 GitHub 上同步,愿望能有更多的偕行介入进来,将更多的干货孝敬社区。

PS:近来关于翻译英文原版系列很有兴致,假如有好的干货英文文章(且人人信得过我的程度),可以放在批评区,有时间我肯定会翻译!

第一章:范例

大多数开辟人员以为,动态言语(如 JavaScript)并没有范例。让我们来看看 ES5.1 的 范例 关于这部份内容是怎样说的:

本范例中一切算法所操纵的值都有一个范例与之对应。这些值的范例均在本范例中对应。固然,这些范例也多是 ECMAScript 言语中划定的范例的子范例。

在 ECMAScript 言语中,每一个 ECMAScript 范例所对应的值都被 ECMAScript 顺序开辟人员直接操纵。ECMAScript 言语中划定的范例为 Undefined, Null, Boolean, String, Number 以及Object。

假如你是强范例言语(静态言语)的粉丝,你或许会对如许运用“范例”感到很恶感。在那些言语里,“范例”所具有的寄义可比在 JS 里的多得多。

有人说 JS 不应当宣称它有“范例,应当把这类东西称为“标签”,或是“子范例”。

好吧。我们将运用这一大略的定义(类似于范例中所形貌的):一个范例是一个固有的,内建的特征集,无论是编译引擎照样开辟人员,都可以用它来肯定一个值的行动,并把这个值和其他值加以辨别。

简朴来讲,假如在编译引擎和开辟人员眼里,值 42(数字)和值 "42"(字符串)处置惩罚的要领差异,那末我们就说他们有差异的范例—— numberstring。当你处置惩罚 42 时,你将运用一些处置惩罚数字的要领,比方数学运算。而当你处置惩罚 "42" 时,你则会运用一些字符串处置惩罚要领,比方输出到页面,等等。这两个值有差异的范例。

虽然这并非什么严谨的定义,但关于我们接下来的议论,已绰绰有余了。而且如许的定义,和JS怎样描述本身是一致的。

范例——或是别的什么

不斟酌学术上的争辩,我们来想想为什么 JavaScript 会须要范例

关于每种范例及其基础行动都有所相识,有助于更高效的将值举行范例转换(详见第四章,范例转换)。险些一切的JS顺序,都存在着如许那样的范例转换,所以相识这些,对你来讲很主要。

假如你有一个值为 42number,但想对它举行 string 范例的操纵,如移除 1 位置的字符 "2",你最好先将这个值的范例从 number 转换为 string

这看似很简朴。

然则举行如许的范例转换,有很多体式格局。有些体式格局很明白,很简朴就能说出前因后果,而且也值得信任.但假如你不够仔细,范例转换可能以一种匪夷所思的体式格局展示在你眼前。

范例转换多是 JavaScript 最大的迷惑之一了。这点经常被视为这一言语的缺点,是应当防止运用的。

由于有了对 JavaScript 范例的周全相识,我们愿望可以申明为什么范例转换的坏名声夸大其词,以至是不适当的——我们会转变你的传统看法,让你看到范例转换的壮大气力和实用性。不过起首,我们先来相识一下值和范例。

内建范例

JavaScript 定义了七种内建范例:

  • null

  • undefined

  • boolean

  • number

  • string

  • object

  • symbol —— ES6 中新增

提醒:以上范例,除 object 的被称为基础范例。

typeof 运算符会检测所给值得范例,并返回以下个中字符串范例的值——但是奇怪的是,返回的效果和我们方才列出的的内建范例并不一一对应。

typeof undefined     === "undefined"; // true
typeof true          === "boolean";   // true
typeof 42            === "number";    // true
typeof "42"          === "string";    // true
typeof { life: 42 }  === "object";    // true

// ES6新增!
typeof Symbol()      === "symbol";    // true

列出的六种范例的值都邑返回一个对应范例称号的字符串。Symbol 是 ES6 中新增的数据范例,我们会在第三章细致引见。

你或许注重到了,我将 null 从列表中除去了。由于他很特别——当运用 typeof 运算符时,它表现的就像 bug 一样:

typeof null === "object"; // true

假如它返回的是 "null" 的话,那可真是件功德,惋惜的是,这个 bug 已存在了 20 年,而且由于有太多的 web 顺序依靠这一 bug 运转,修复这一 bug 的话,将会制造更多的 bug,而且使很多 web 运用没法运转,所以预计未来也不会修复。

假如你想要肯定一个 null 范例的值是这一范例,你须要运用复合剖断:

var a = null;

(!a && typeof a === "object"); // true

null 是基础范例中唯一值表现的像 false 一样的范例(详见第四章),但假如运转 typeof 举行搜检,返回的照样 "object"

那末,typeof 返回的第七种字符串范例的值是什么?

typeof function a(){ /* .. */ } === "function"; // true

单拍脑壳想的话,很轻易明白 function(函数)会是 JS 中顶级的内建范例,尤其是它针对 typeof 运算符的表现。但是,假如你浏览相干的规范,会发明它实际上是对象范例(object)的子范例。更确切的说,函数是一种“可以被挪用的对象”——一类具有名为 [[Call]] 的内建属性且可以被挪用的对象。

函数实际上是对象这点实在很有效。最主要的一点就是,它可以有属性。比方:

function a(b,c) {
    /* .. */
}

该函数具有一个 length 属性,值为函数形式参数的个数。

a.length; // 2

本例中,函数声明中包含两个形参(bc),所以“函数的长度”是 2

那末数组呢?他们也是 JS 内置的范例,会不会有什么特别的表现?

typeof [1,2,3] === "object"; // true

但是并没有,只是平常的对象罢了。平常将它们也视为对象的“子范例”(详见第三章),与平常对象差异的是,它们可以经由过程数字来序列化(就像平常对象那样可以经由过程字符串范例的 key(键)来序列化一样),而且操纵有可以自动更新的 length 属性。

值和范例

在 JavaScript 中,变量不具有范例——值有范例。变量可以在任何时刻保留任何值。

换句话说,JS 并非强范例的言语,编译引擎不会让一个变量一直保留和这个变量最最先所保留的值具有雷同的范例。变量可以保留一个 string 范例的值,并在接下来的赋值操纵中保留一个number范例,以此类推。

一个42number 范例的,而且这个范例是不能转变的。另一个值,如 "42"string 范例,可以经由过程对 number 范例的 42 举行范例转换(详见第四章)来获得。

假如你用 typeof 运算符去操纵一个变量,看上去就像是在求“变量是什么范例?”,但是 JS 中的变量并不具有范例。所以,实际上是在求“变量中保留的值是什么范例?”。

var a = 42;
typeof a; // "number"

a = true;
typeof a; // "boolean"

typeof 运算符返回的必定是字符串范例:

typeof typeof 42; // "string"

个中typeof 42会返回"number",然后typeof "number"就会返回"string"

undefined vs “undeclared”(未定义和未声明)

当变量没有被赋值的时刻,其值为 undefined。挪用 typeof 运算符对它举行操纵会返回 "undefined"

var a;

typeof a; // "undefined"

var b = 42;
var c;

// 然后另
b = c;

typeof b; // "undefined"
typeof c; // "undefined"

关于很多开辟者都以为“未定义(undefined)”相称因而“未声明”的代名词,但是在 JS 中,这两个观点判然差异。

一个“未定义(undefined)”的变量是已在当前作用域中声清楚明了的,只不过是如今它并没有保留其他的值罢了。而“未声明(undeclared)”则是指在当前作用域中没有声明的变量。

斟酌以下的示例:

var a;

a; // undefined
b; // ReferenceError: b is not defined(毛病的中文粗心是:援用毛病:b 还没有定义)

浏览器关于这一毛病的形貌可以说相称让人疑心。“b 还没有定义”很轻易让人明白成“b 是未定义”。然后,“未定义”和“还没有定义”间的差异实在是太大了。假如浏览器如果能报个像“未找到变量 b”或是“b 还没有声明”之类的毛病,就不会这么让人含糊了。

一样的,typeof 运算符的特别行动加重了这一疑心,请看例子:

var a;

typeof a; // "undefined"

typeof b; // "undefined"

关于“未声明”或着说“还没有定义”的变量,typeof 会返回 "undefined"。你会发明,虽然 b 是一个没有声明的变量,然则当我们实行 typeof b 的时刻却没有报错。会涌现这类状况,源于 typeof 运算符特别的平安机制。

和前面的例子一样,假如关于没有声明的变量,typeof 会返回一个“未声明”之类的东西,而不是将其和“undefined”等量齐观的话,就不会有这么多麻烦了。

typeof 对处置惩罚未声明的处置惩罚

但是,在浏览器端这类,多个剧本文件均可以在全局定名空间下加载变量的 JavaScript 环境中,这类平安机制反而很有效。

提醒:很多开辟者深信,在全局定名空间下不应当有任何变量,一切的东西都应当在模块或者是私有/星散的定名空间中。理论上,这很棒,而且确切是我们寻求的一个目的,但是在实践中,这险些是不可能的。不过 ES6 中加入了对模块的支撑,这使得我们可以更靠近这一目的。

比方,在你的顺序中,你经由过程一个全局变量 DEBUG 完成了一个调试形式。你愿望在最先举行 debug,如在控制台输出一条调试信息之前,搜检这个变量是不是已声明。你可以将全局的 var DEBUG = true 声明写在一个名为”debug.js”的文件夹下,当你在举行开辟/测试下才在浏览器中引入,而不是在临盆环境。

而你须要注重的,就是怎样去在你的其他代码中搜检这个全局的 DEBUG 变量,毕竟你可不愿望报一个 ReferenceError。在这类场景下,typeof 运算符就成了我们的好帮手。

// 注重,这类要领会报错!
if (DEBUG) {
    console.log( "Debugging is starting" );
}

// 更加平安的搜检体式格局
if (typeof DEBUG !== "undefined") {
    console.log( "Debugging is starting" );
}

这类搜检不仅关于用户定义的变量很有效,当你在见此一个内建的 API 的时刻,这类不会抛出毛病的搜检也异常棒:

if (typeof atob === "undefined") {
    atob = function() { /*..*/ };
}

提醒:当你在对一个如今不存在的特征写“polyfill(腻子剧本)”的时刻,你须要防止用 var 来声明变量 atob。假如你在 if 语句内里运用 var atob 来声明,纵然 if 语句的前提不满足,变量的声明也会被提拔到作用域的最顶级(详见本系列中的《作用域和闭包》)。在部份浏览器中,对一些特别的全局的内建对象范例(常称为“宿主对象”,如浏览器中的 DOM 对象),这类反复的声明会报错。所以最好防止运用 var 来阻挠变量提拔。

另一种不运用 typeof 平安机制,举行搜检的要领,就是应用一切的全局变量都是(global)全局对象(在浏览器中就是 window 对象)这一点。所以,上面的搜检另有以下等价的写法(一样很平安):

if (window.DEBUG) {
    // ..
}

if (!window.atob) {
    // ..
}

和援用一个未声明的变量差异,当你尝试猎取一个对象(即使是 window 对象)不存在的属性的时刻,并不会抛出什么 ReferenceError

而另一方面,一些开辟者尽力防止运用 window 对象来援用全局变量,尤其是当你的代码运转在多种 JS 环境(不光是浏览器,比方服务端的 node.js)时,全局(global)对象可不肯定叫 window

即使当你不运用全局变量的时刻,typeof 的平安机制也有它的用武之地,虽然这类状况很少见,也有一些开辟人员以为这类设想并不值得。比方你预备写一个可供别人复制粘贴的通用函数,想要晓得顺序中是不是定义了某一特定的变量(将会影响你函数的实行),你可以如许:

function doSomethingCool() {
    var helper =
        (typeof FeatureXYZ !== "undefined") ?
        FeatureXYZ :
        function() { /*.. 默认值 ..*/ };

    var val = helper();
    // ..
}

doSomethingCool() 会搜检是不是存在一个名为 FeatureXYZ 的变量,有的话就运用,没有的话,就运用默认值。如今,假如有人在他的顺序/模块中运用了这一大众函数,搜检它们是不是定义了 FeatureXYZ 就显得尤为主要:

// IIFE (详见本系列《作用域和闭包》一书中的马上实行函数表达式)
(function(){
    function FeatureXYZ() { /*.. my XYZ feature ..*/ }

    // include `doSomethingCool(..)`
    function doSomethingCool() {
        var helper =
            (typeof FeatureXYZ !== "undefined") ?
            FeatureXYZ :
            function() { /*.. default feature ..*/ };

        var val = helper();
        // ..
    }

    doSomethingCool();
})();

在这里,FeatureXYZ 并非一个全局变量,但我们依然运用 typeof 运算符的平安机制来搜检。注重到,在这类状况下,我们可没有全局对象用于这一搜检(像运用 window.___ 那样),所以 typeof 真的很有协助。

有些开辟者可能会喜好一种叫做“依靠注入”的设想形式,让 doSomethingCool() 不去搜检 FeatureXYZ 是不是在它外部/四周被定义,而是经由过程显现的推断来肯定,如:

function doSomethingCool(FeatureXYZ) {
    var helper = FeatureXYZ ||
        function() { /*.. 默认值 ..*/ };

    var val = helper();
    // ..
}

要完成这一功用,实在有很多解决方案。没有一种形式是“对的”或“错的”——要对种种要领举行衡量。不过总的来讲,typeof 的平安机制确切给了我们更多的挑选。

总结

JavaScript 具有七种内建范例:nullundefinedbooleannumberstringobjectsymbol。可以经由过程运用 typeof 运算符来对它们举行辨别。

变量不具有范例,但值有。这些范例定义了值的行动。

很多开辟者会将“未定义(undefined)”和“未声明”等量齐观,然则在 JavaScript 它们完整差异。undefined是一个可供已声明的变量保留的值。“未声明”意味着一个未经声明的变量。

不幸的是,JavaScript 中很多处所都将二者等量齐观,比方毛病信息(”ReferenceError: a is not defined”),以及用 typeof 操纵,二者都返回 "undefined"

不过,typeof 这类平安机制(阻挠报错)在某些场景中,如须要搜检一个变量是不是存在的时刻照样很有效的。

原书 《You Don’t Know JS: Types & Grammar》
本章原文 Chapter 1: Types

    原文作者:LeuisKen
    原文地址: https://segmentfault.com/a/1190000003888178
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞