美文网首页
中卷(1.3)

中卷(1.3)

作者: 风声233 | 来源:发表于2018-08-13 12:04 被阅读5次

    内容大纲:
    js 为基本数据类型值提供了封装对象,称为原生函数(如 String、Number、Boolean 等)。它们为基本数据类型值提供了该子类型所特有的方法和属性(如:String#trim() 和 Array#concat(..))。
    对于简单标量基本类型值,比如 "abc",如果要访问它的 length 属性或 String.prototype 方法,js 引擎会自动对该值进行封装来实现这些属性和方法的访问。

    原生函数

    常用的原生函数(内建函数)有:

    • String( )
    • Numebr( )
    • Boolean( )
    • Array( )
    • Object( )
    • Function( )
    • RegExp( )
    • Date( )
    • Error( )
    • Symbol( ) ——ES6新加入的!
      原生函数可以当作构造函数来用,但其构造出来的对象可能会和我们想象的不一样:
    var a = new String( "abc" );
    typeof a; // "object"
    a instanceof String; // true
    Object.prototype.toString.call(a); // "[object String]"
    

    通过构造函数创建出来的时封装了基本类型值得封装对象。

    内部属性[[Class]]

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

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

    大多数情况下,对象的内部 [[Class]] 属性和创建该对象的内建原生构造函数相对应,但并非总是如此。
    那么基本类型值呢?下面先来看看 null 和 undefined:

    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]"  
    

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

    封装对象包装

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

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

    如果需要经常用这些字符串属性和方法,比如在 for 循环中使用 i < a.length,那么从一开始就创建一个封装对象也许更为方便,这样 js 引擎就不用每次都自动创建了。但实际证明这并不是一个好办法!因为浏览器已经为 .length 这样的常见情况做了性能优化,直接使用封装对象来“提前优化”代码反而会降低执行效率。

    封装对象与 truthy 真值
    var a = new Boolean(false);
    if(!a) {
      console.log( "Oops" ); // 执行不到这里
    }
    

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

    拆封

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

    var a = new String( "abc" );
    a.valueOf(); // "abc"
    

    隐式拆封:

    var a = new String( "abc" );
    var b = a + ""; // b的值为“abc”
    
    typeof a; // "object"
    typeof b; // "string"
    
    Array(..) 的空单元与 undefined 单元
    var a = new Array( 3 );
    
    a.length; // 3
    a; // [undefined x 3] in Chrome
    

    a 在 Chrome 中的显示意味着它有三个值为 undefined 的单元,但实际上单元并不存在(“空单元”这个叫法也不准确)。从下面的代码可以看出他们的差别:

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

    b在当前版本的 Chrome 中显示为 [ undefined, undefined, undefined ],而 a 和 c 则显示为 [ undefined x 3]。是不是感到困惑?
    更糟糕的是,上例中 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(..) 却不一样,它会假定数组不为空,然后通过 length 属性值来遍历其中的元素。而 map(..) 并不做这样的假定,因此结果也往往在预期之外,并可能导致失败。
    我们可以通过下述方式来创建包含 undefined 单元(而非“空单元”)的数组:

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

    apply(..) 是一个工具函数,适用于所有函数对象,它会以一种特殊的方式来调用传递给它的函数。
    第一个参数是 this 对象,这里不用费心,暂将它设为 null。第二个参数则必须是个数组或类数组的值。
    于是 Array.apply(..) 调用 Array(..) 函数,并将 { length: 3 } 作为函数的参数。
    我们可以设想 apply(..) 内部有一个 for 循环,从 0 开始循环到 length(即循环到2,不包括3)。
    假设在 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) 更准确可靠。
    总之,永远不要创建和使用空单元数组。

    Date(..) 和 Error(..)

    相较于其他的原生构造函数,Date(..) 和 Error(..) 的用处要大得多,因为没有对应的常量形式来作为它们的替代。
    创建日期对象必须使用 new Date()。 Date(..) 可以带参数,用来指定日期和时间,而不带参数的话则使用当前的日期和时间。
    Date(..) 主要用来获得当前的 Unix 时间戳。该值可以通过日期对象中的 getTime() 来获得。
    从 ES5 开始引入了一个更简单的方法,即静态函数 Date.now()。对 ES5 之前的版本我们可以使用下面的 polyfill:

    if(!Date.now){
      Date.now = function(){
        return (new Date()).getTime();
      }
    }
    

    如果调用 Date() 时不带 new 关键字,则会得到当前日期的字符串值。其具体格式规范没有规定,浏览器使用“Fri Jul 18 2014 00:31:02 GMT-0500 (CDT)”这样的格式显示。
    错误对象通常与 throw 一起使用:

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

    ES6 中新加入了一个基本数据类型——符号。符号是具有唯一性的特殊值(并非绝对),用它来命名对象属性不容易导致重名。该类型的引入主要源于 ES6 的一些特殊构造,此外符号也可以自行定义。
    符号可以用作属性名,但无论是在代码还是开发控制台中都无法查看和访问它的值,只会显示为诸如 Symbol(Symbol.create) 这样的值。
    ES6 中有一些预定义符号,以 Symbol 的静态属性形式出现,如 Symbol.create、Symbol.iterator 等,我们可以这样来使用:

    obj[Symbol.iterator] = function(){ /* .. */};
    

    我们可以用 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) ]
    

    虽然符号实际上并非私有属性(通过 Object.getOwnPropertySymbols(..) 便可公开获得对象中的所有符号),但它却主要用于私有或特殊属性。很多开发人员喜欢用它来替代有下划线(_)前缀的属性,而下划线前缀通常用于命名私有或特殊属性。
    符号并非对象,而是一种简单标量基本类型。

    原生原型

    原生构造函数有自己的 .prototype 对象,如 Array.prototype、String.prototype 等。
    这些对象包含其对应子类型所特有的行为特征。
    根据文档约定,我们将 String.prototype.XYZ 简写为 String#XYZ,对其他 .prototype 也同样如此。

    • String#indexOf(..)
      在字符串中找到指定子字符串的位置。
    • String#charAt(..)
      获得字符串指定位置上的字符。
    • String#substr(..)、String#substring(..) 和 String#slice(..)
      获得字符串的指定部分。
    • String#toUpperCase() 和 String#toLowerCase()
    • String#trim()
      去掉字符串前后的空格,返回新的字符串。

    以上方法并不改变原字符串的值,而是返回一个新字符串。
    借助原型代理,所有字符串都可以访问这些方法。

    阅读下一篇

    相关文章

      网友评论

          本文标题:中卷(1.3)

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