看到 一个论坛帖子 问到:(类似下面的代码)
var arr = [1, 2, 3]; arr.a = 'a'; arr.b = 'b';
为什么可以这样写?arr 自身到底是什么?
原因其实很简单,因为 js 里一切都是对象,array 也是 object,所以它就能做所有 object 能做的事情,比如,给它加个属性 a。
如果 Array 能那么搞,Boolean,Date, Function, Number,RegExp,String 也可以就不足为奇了。
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 的形式作为数据使用。它是否还有什么未知领域等待人们去发掘呢?