精读《你不知道的JavaScript》中卷-I-第3章 原生函数

156 阅读4分钟

I-第3章 原生函数

学习原生函数,封装对象包装,拆分。

常用的原生函数

• String()
• Number()
• Boolean()
• Array()
• Object()
• Function()
• RegExp()
• Date()
• Error()
• Symbol()

原生函数可以被当作构造函数来使用。通过构造函数(如new String("abc"))创建出来的是封装了基本类型值(如"abc")的封装对象。

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

内部属性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]"
Object.prototype.toString.call( null ); // "[object Null]"
Object.prototype.toString.call( undefined ); // "[object Undefined]"
Object.prototype.toString.call( "abc" ); // "[object String]"
Object.prototype.toString.call( 42 ); // "[object Number]"
Object.prototype.toString.call( true ); // "[object Boolean]"

封装对象包装

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

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

一般情况下,不需要直接使用封装对象。最好的办法是让 JavaScript 引擎自己决定什么时候应该使用封装对象。

如果要自行封装基本类型值,可以用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]"

拆封

要拆封封装对象中的基本类型值,可以用valuesOf()函数。

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"

原生函数作为构造函数

  1. 数组

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

    var a = new Array( 3 );
    var b = [ undefined, undefined, undefined ];
    var c = [];
    c.length = 3;
    
  2. Object(..)、Function(..) 和 RegExp(..)

    不建议使用Object和Function

    强烈建议使用常量形式(如 /^a*b+/g)来定义正则表达式,这样不仅语法简单,执行效率也更高,因为 JavaScript 引擎在代码执行前会对它们进行预编译和缓存。 与前面的构造函数不同,RegExp(..) 有时还是很有用的,比如动态定义正则表达式时:

    var name = "Kyle";
    var namePattern = new RegExp( "\\b(?:" + name + ")+\\b", "ig" );
    var matches = someText.match( namePattern );
    

    上述情况在JavaScript编程中时有发生,这时new RegExp("pattern","flags")就能派上用场。

  3. Date(..) 和 Error(..)

    创建日期必须使用 new Date()。 Date(..) 主要用来获得当前的 Unix 时间戳(从 1970 年 1 月 1 日开始计算,以秒为单位)。 该值可以通过日期对象中的 getTime() 来获得。

    Date.now()即可获取

    创建错误对象(error object)主要是为了获得当前运行栈的上下文(大部分 JavaScript 引擎通过只读属性 .stack 来访问)。 栈上下文信息包括函数调用栈信息和产生错误的代码行号, 以便于调试(debug)。

    function foo(x) {
        if (!x) {
            throw new Error( "x wasn’t provided" );
        }
        // ..
    }
    

    通常错误对象至少包含一个 message 属性,有时也不乏其他属性(必须作为只读属性访问),如 type。 除了访问 stack 属性以外,最好的办法是调用toString() 来获得经过格式化的便于阅读的错误信息。

  4. Symbol

    可以使用 Symbol(..) 原生构造函数来自定义符号。但它比较特殊,不能带 new 关键 字,否则会出错:

    var mysym = Symbol( "my own symbol" );
    mysym;    // Symbol(my own symbol)
    mysym.toString(); // "Symbol(my own symbol)"
    typeof mysym;   // "symbol"
    
    var a = { };
    a[mysym] = "foobar";
    
    Object.getOwnPropertySymbols( a );
    // [ Symbol(my own symbol) ]
    
  5. 原生原型

    原生构造函数有自己的 .prototype 对象,如 Array.prototype、String.prototype 等。

    这些对象包含其对应子类型所特有的行为特征。

    Function.prototype 是一个空函数,RegExp.prototype 是一个“空”的正则表达式(无任何匹配),而 Array.prototype 是一个空数组。对未赋值的变量来说,它们是很好的默认值。

    function isThisCool(vals = Array.prototype,fn = Function.prototype,rx = RegExp.prototype) {
        return rx.test(
            vals.map( fn ).join( "" )
        );
    }
    
    isThisCool();  // true
    
    isThisCool(
        ["a","b","c"],
        function(v){ return v.toUpperCase(); },
        /D/
    );
    

    这种方法的一个好处是 .prototype 已被创建并且仅创建一次。相反,如果将 []、function(){} 和 /(?:)/ 作为默认值, 则每次调用 isThisCool(..) 时它们都会被创建一次 (具体创建与否取决于 JavaScript 引擎,稍后它们可能会被垃圾回收),这样无疑会造成内存和 CPU 资源的浪费。