背景

在 C++ 中,当我们想用下标访问对象中的第 i 个元素时,可以重载下标运算符(subscript operator)。但是 JavaScript(ECMAScript 5.1)中并没有类似的机制,如果我们想访问第 i 个元素,必须添加一个名为 ToString(i) 的属性;如果想访问元素 1 .. n ,就必须添加 n 个属性。显然这种做法存在额外的内存开销,在嵌入式硬件上是不可接受的。

思路

为了解决上述问题,一个很自然的想法就是对 ECMA 的 [[Get]]1[[Put]]2 进行扩展:

  1. 当属性 P 未找到时,令 I = ToNumber(P)
  2. I 为整数,则查找 __get_by_index__ / __put_by_index__ 方法。
  3. 若找到,则返回 __get_by_index__(I) / __put_by_index__(I, V)

实现

JerryScript 引擎的 [[Get]][[Put]] 实现位于 “ecma-objects.c”,本文限于篇幅不贴具体代码,大家只要在注释处按照上节的讨论编写即可,留作练习。

[[GET]]:

ecma_value_t
ecma_op_object_get (ecma_object_t *object_p, /**< the object */
                    ecma_string_t *property_name_p) /**< property name */
{
    ......
    do {
        ......
    }
    while (object_p != NULL);

    {
        // Implement our [[GET]] extension right here.
    }

    ......
}

[[PUT]]:

ecma_value_t
ecma_op_object_put (ecma_object_t *object_p, /**< the object */
                    ecma_string_t *property_name_p, /**< property name */
                    ecma_value_t value, /**< ecma value */
                    bool is_throw) /**< flag that controls failure handling */
{
    ......
    ecma_property_t *property_p = ecma_find_named_property (object_p, property_name_p);
    ......
    if (property_p == NULL)))
    {
        {
           // Implement our [[PUT]] extension right here.
        }

        if (type == ECMA_OBJECT_TYPE_STRING)
        {
            ......
        }
        ......
    }
    ......
}

测试

将 JerryScript 重新编译后,我们可以使用以下代码进行验证:

function Box() {
}

Box.prototype.__get_by_index__ = function (i) {
    print('get:', i);
    return i;
};

Box.prototype.__put_by_index__ = function (i, value) {
    print('put:', i, value);
    return value;
};

var box = new Box();

for (var i = 0; i < 10; ++i) {
    assert(box[i] === i);
}

for (var i = 0; i < 10; ++i) {
    assert((box[i] = i) === i);
}

大功告成~