美文网首页
第3章 原生函数

第3章 原生函数

作者: 不系流年系乾坤 | 来源:发表于2016-10-14 12:31 被阅读22次

    第 3 章原生函数

    常用的原生函数有:

    • String()

    • Number()

    • Boolean()

    • Array()

    • Object()

    • Function()

    • RegExp()

    • Date()

    • Error()

    • Symbol()——ES6 中新加入的!

    JavaScript 中 的 String() 和 Java 中 的 字 符 串 构 造 函 数
    String(..) 非常相似,可以这样来用:

    var s = new String( "Hello World!" );
    console.log( s.toString() ); // "Hello World!"
    

    原生函数可以被当作构造函数来使用,但其构造出来的对象可能会和我们设想的有所
    出入:

    var a = new String( "abc" );
    typeof a; // 是"object",不是"String"
    a instanceof String; // true
    Object.prototype.toString.call( a ); // "[object String]"
    

    通过构造函数(如 new String("abc"))创建出来的是封装了基本类型值(如 "abc")的封
    装对象。

    typeof 在这里返回的是对象类型的子类型。

    可以这样来查看封装对象:

    console.log( a );
    

    在本书写作期间, Chrome 的最新版本是这样显示的: String {0: "a", 1:
    "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"},而老版本这样显示:
    String {0: "a", 1: "b", 2: "c"}。最新版本的 Firefox 这样显示: String
    ["a","b","c"];老版本这样显示: "abc",并且可以点击打开对象查看器。
    这些输出结果随着浏览器的演进不断变化,也带给人们不同的体验。

    再次强调, new String("abc") 创建的是字符串 "abc" 的封装对象,而非基本类型值 "abc"。

    3.1 内部属性 [[Class]]

    所有 typeof 返回值为 "object" 的对象(如数组)都包含一个内部属性 [[Class]](我们可
    以把它看作一个内部的分类,而非传统的面向对象意义上的类)。这个属性无法直接访问,
    一般通过 Object.prototype.toString(..) 来查看。

    Object.prototype.toString.call( [1,2,3] );
    // "[object Array]"
    Object.prototype.toString.call( /regex-literal/i );
    // "[object RegExp]"
    

    对象的内部 [[Class]] 属性和创建该对象的内建原生构造函数相对应,但并非
    总是如此。

    基本类型值

    Object.prototype.toString.call( null );
    // "[object Null]"
    Object.prototype.toString.call( undefined );
    // "[object Undefined]"
    

    虽然 Null() 和 Undefined() 这样的原生构造函数并不存在,但是内部 [[Class]] 属性值仍
    然是 "Null" 和 "Undefined"。

    其他基本类型值(如字符串、数字和布尔)的情况有所不同,通常称为“包装”

    Object.prototype.toString.call( "abc" );
    // "[object String]"
    Object.prototype.toString.call( 42 );
    // "[object Number]"
    Object.prototype.toString.call( true );
    // "[object Boolean]
    

    上例中基本类型值被各自的封装对象自动包装,所以它们的内部 [[Class]] 属性值分别为
    "String"、 "Number" 和 "Boolean"。

    从 ES5 到 ES6, toString() 和 [[Class]] 的行为发生了一些变化

    3.2 封装对象包装

    由 于 基 本 类 型 值 没 有 .length
    和 .toString() 这样的属性和方法,需要通过封装对象才能访问,此时 JavaScript 会自动为
    基本类型值包装( box 或者 wrap)一个封装对象:

    var a = "abc";
    a.length; // 3
    a.toUpperCase(); // "ABC"
    

    如果需要经常用到这些字符串属性和方法,比如在 for 循环中使用 i < a.length,那么从
    一开始就创建一个封装对象也许更为方便,这样 JavaScript 引擎就不用每次都自动创建了。
    但实际证明这并不是一个好办法,因为浏览器已经为 .length 这样的常见情况做了性能优
    化,直接使用封装对象来“提前优化”代码反而会降低执行效率。
    一般情况下,我们不需要直接使用封装对象。最好的办法是让 JavaScript 引擎自己决定什
    么时候应该使用封装对象。换句话说,就是应该优先考虑使用 "abc" 和 42 这样的基本类型
    值,而非 new String("abc") 和 new Number(42)。

    封装对象释疑

    var a = new Boolean( false );
    if (!a) {
    console.log( "Oops" ); // 执行不到这里
    }
    

    我们为 false 创建了一个封装对象,然而该对象是真值(“ truthy”,即总是返回 true,参见
    第 4 章),所以这里使用封装对象得到的结果和使用 false 截然相反。

    如果想要自行封装基本类型值,可以使用 Object(..) 函数(不带 new 关键字):

    var a = "abc";
    var b = new String( a );
    var c = Object( a );
    typeof a; // "string"
    typeof b; // "object"
    typeof c; // "object"
    b instanceof String; // true
    c instanceof String; // true
    Object.prototype.toString.call( b ); // "[object String]"
    Object.prototype.toString.call( c ); // "[object String]"
    

    一般不推荐直接使用封装对象(如上例中的 b 和 c),但它们偶尔也会派上
    用场。

    3.3 拆封

    如果想要得到封装对象中的基本类型值,可以使用 valueOf() 函数:

    var a = new String( "abc" );
    var b = new Number( 42 );
    var c = new Boolean( true );
    a.valueOf(); // "abc"
    b.valueOf(); // 42
    c.valueOf(); // true
    

    在需要用到封装对象中的基本类型值的地方会发生隐式拆封。具体过程(即强制类型转
    换)

    var a = new String( "abc" );
    var b = a + ""; // b的值为"abc"
    typeof a; // "object"
    typeof b; // "string"
    

    3.4 原生函数作为构造函数

    关于数组( array)、对象( object)、函数( function)和正则表达式,我们通常喜欢以常
    量的形式来创建它们。实际上,使用常量和使用构造函数的效果是一样的(创建的值都是
    通过封装对象来包装)。
    如前所述,应该尽量避免使用构造函数,除非十分必要,因为它们经常会产生意想不到的
    结果。

    3.4.1 Array(..)

    var a = new Array( 1, 2, 3 );
    a; // [1, 2, 3]
    var b = [1, 2, 3];
    b; // [1, 2, 3]
    

    构造函数 Array(..) 不要求必须带 new 关键字。不带时,它会被自动补上。
    因此 Array(1,2,3) 和 new Array(1,2,3) 的效果是一样的。

    Array 构造函数只带一个数字参数的时候,该参数会被作为数组的预设长度( length),而
    非只充当数组中的一个元素。
    这实非明智之举:一是容易忘记,二是容易出错。
    更为关键的是,数组并没有预设长度这个概念。这样创建出来的只是一个空数组,只不过
    它的 length 属性被设置成了指定的值。
    如若一个数组没有任何单元,但它的 length 属性中却显示有单元数量,这样奇特的数据结
    构会导致一些怪异的行为。而这一切都归咎于已被废止的旧特性(类似 arguments 这样的
    类数组)。

    我们将包含至少一个“空单元”的数组称为“稀疏数组”。

    var a = new Array( 3 );
    a.length; // 3
    a;
    

    a 在 Chrome 中显示为 [ undefined x 3 ](目前为止),这意味着它有三个值为 undefined
    的单元,但实际上单元并不存在(“空单元” 这个叫法也同样不准确)。

    var a = new Array( 3 );
    var b = [ undefined, undefined, undefined ];
    var c = [];
    c.length = 3;
    a;
    b;
    c;
    

    我们可以创建包含空单元的数组,如上例中的 c。只要将 length 属性设置为
    超过实际单元数的值,就能隐式地制造出空单元。另外还可以通过 delete
    b[1] 在数组 b 中制造出一个空单元。

    b 在当前版本的 Chrome 中显示为 [ undefined, undefined, undefined ],而 a 和 c 则显示
    为 [ undefined x 3 ]。是不是感到很困惑?

    更令人费解的是在当前版本的 Firefox 中 a 和 c 显示为 [ , , , ]。仔细看来,这其中有三
    个逗号,代表四个空单元,而不是三个。

    Firefox 在输出结果后面多添了一个 ,,原因是从 ES5 规范开始就允许在列表(数组值、属性列表等)末尾多加一个逗号(在实际处理中会被忽略不计)。所以如果你在代码或者调
    试控制台中输入 [ , , , ],实际得到的是 [ , , ](包含三个空单元的数组)。这样做虽
    然在控制台中看似令人费解,实则是为了让复制粘贴结果更为准确。

    针对这种情况, Firefox 将 [ , , , ] 改为显示 Array [<3 empty slots>],这
    无疑是个很大的提升。

    更糟糕的是,上例中 a 和 b 的行为有时相同,有时又大相径庭:

    a.join( "-" ); // "--"
    b.join( "-" ); // "--"
    a.map(function(v,i){ return i; }); // [ undefined x 3 ]
    b.map(function(v,i){ return i; }); // [ 0, 1, 2 ]
    

    a.map(..) 之所以执行失败,是因为数组中并不存在任何单元,所以 map(..) 无从遍历。而
    join(..) 却不一样,它的具体实现可参考下面的代码:

    function fakeJoin(arr,connector) {
    var str = "";
    for (var i = 0; i < arr.length; i++) {
    if (i > 0) {
    str += connector;
    }
    if (arr[i] !== undefined) {
    str += arr[i];
    }
    }
    return str;
    }
    var a = new Array( 3 );
    fakeJoin( a, "-" ); // "--"
    

    从中可以看出, join(..) 首先假定数组不为空,然后通过 length 属性值来遍历其中的元
    素。而 map(..) 并不做这样的假定,因此结果也往往在预期之外,并可能导致失败。

    我们可以通过下述方式来创建包含 undefined 单元(而非“空单元”)的数组:

    var a = Array.apply( null, { length: 3 } );
    a; // [ undefined, undefined, undefined ]
    

    apply(..) 是一个工具函数,适用于所有函数对象,它会以一种特殊的方式来调用传递给
    它的函数。

    假设在 apply(..) 内部该数组参数名为 arr, for 循环就会这样来遍历数组: arr[0]、
    arr[1]、 arr[2]。 然 而, 由 于 { length: 3 } 中 并 不 存 在 这 些 属 性, 所 以 返 回 值 为
    undefined

    换句话说,我们执行的实际上是 Array(undefined, undefined, undefined),所以结果是单
    元值为 undefined 的数组,而非空单元数组。

    虽然 Array.apply( null, { length: 3 } ) 在创建 undefined 值的数组时有些奇怪和繁琐,
    但是其结果远比 Array(3) 更准确可靠。

    总之, 永远不要创建和使用空单元数组。

    相关文章

      网友评论

          本文标题:第3章 原生函数

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