Javascript Object 杂谈

看到 一个论坛帖子 问到:(类似下面的代码)

var arr = [1, 2, 3];
arr.a = 'a';
arr.b = 'b';

为什么可以这样写?arr 自身到底是什么?

原因其实很简单,因为 js 里一切都是对象,array 也是 object,所以它就能做所有 object 能做的事情,比如,给它加个属性 a

如果 Array 能那么搞,BooleanDate, Function, NumberRegExpString 也可以就不足为奇了。

function toStr(obj) {
  return Object.prototype.toString.call(obj); 
}

function addAttr(arr) {
  for(var i = arr.length-1; i >= 0; i--) {
    arr[i].attr = toStr(arr[i]);
  }
  return arr;
}

function checkAttr(arr) {
  for(var i = 0; i < arr.length; i++) {
    console.log(typeof arr[i], arr[i].attr);
  }
}

var arr = [
    new Boolean(false), 
    new Date(), 
    new Function(), 
    new RegExp('\\w+'), 
    new Number(1.0), 
    new String('str')];
checkAttr(addAttr(arr));

/** output >>
object [object Boolean]
object [object Date]
function [object Function]
object [object RegExp]
object [object Number]
object [object String]
*/

细心的人会发现上面都是使用 new 构造的对象,如果换成是字面方式 (literal) 定义的原生类型 (primitive) 又会是如何呢?

var arr = [false, 1.0, 'str'];
checkAttr(addAttr(arr));
/** output >>
boolean undefined
number undefined
string undefined
*/

在 js 里一切都是对象,但原生类型对象 (boolean, number, string) 与非原生类型对象还是有区别的。前者可能称之为 原生数据 (primitive data) 会更加适合。而在需要的时候(访问属性,调用方法),js 会自动将原生数据封装成对象然后再进行处理 [参考]。而原生数据的存在,是因为性能的原因 [参考]。所以 js 里一切都是对象是对使用者而言。

虽然,我们可以给数组对象添加属性,但并不建议那么做,原因是 Array 并不预期被当作 key/value 形式的对象来使用。情况放到 Boolean, Number, String 等对象上是一样的。

除非,不给定这个预期,或者改变这个预期。

比如,使用 jQuery,在执行 $(selector) 返回的是一个 jQuery 对象,这个对象可以执行 jQuery.fn 上面的所有方法,并且可以像访问一个数组那样访问里面所包含的数据节点。下面是 jQuery 中 /src/core.js 的一些源代码片断:

jQuery.fn = jQuery.prototype = {
     constructor: jQuery,
     init: function( selector, context, rootjQuery ) {
       // ...
       return jQuery.merge( this, selector );
     },
    
     // For internal use only.
     // Behaves like an Array's method, not like a jQuery method.
     push: push,
     sort: [].sort,
     splice: [].splice
};

// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;

通过查询过滤之后,人们会预期得到的是一个节点数组,但 jQuery 并没有简单的返回那个数组(如果真是那样,后面就没戏了),而是将查询的结果合并到一个 jQuery 类型的对象上然后返回。也就是上面代码中 constructor 和 init 的部分。后面添加到 jQuery.fn 的 push, sort, splice 都是 Array prototype 上的方法,目的就是使这个对象可以像个 Array 一样操作,虽然注释里说的是仅供内部使用。

在 Array object 上添加属性数据算不符合预期。但如果反过来,将一个 object 模拟成 Array,这样就没有了原来的“预期”的障碍,通过给出新的预期,来表达程序的设计目的。

我在各种场合,无数次听见人们盛赞 jQuery 是如何好用,里面也是有几分 JavaScript Object 的功劳的,但更重要的是,设计者有扎实的基本功,既尊重传统,又敢于突破。

Object 是如此的简单而灵活。它可以包含属性与方法,当成对象来使用;可以以 maps 或者 key/value pairs 的形式作为数据使用。它是否还有什么未知领域等待人们去发掘呢?

<参考>
JavaScript Coercion Demystified