扩展常规对象以支持ES5 Array功能的问题

Problems with extending regular Objects to support ES5 Array capabilities

本文关键字:Array 功能 问题 ES5 支持 常规 对象 扩展      更新时间:2023-09-26

我很久以前就愿意让原生数组和常规对象之间的界限完全模糊,不仅扩展具有与ES5中数组相同功能的对象,而且在两边捆绑我的自定义方法包。

一些聪明人想到了这些范式的变化。就像Angus Croll在文章javascript-object-keys-finally:

中提到的那样

"此外,随着数组和常规对象之间的界限变得模糊(辅助)通过自定义getter和setter),我们可能会看到泛型的增长"类数组"对象,它兼有两个世界的优点——非数字标识符和对Array.prototype定义的富API集的访问。EcmaScript 5通过引入泛型方法,由一种类型定义,但可被任何类型使用。"

在此过程中,他在文章中编码了一些东西:extending-objects-with-javascript-getters
function extendAsArray(obj) {
    if (obj.length === undefined || obj.__lookupGetter__('length')) {
        var index = 0;
        for (var prop in obj) {
            if(!obj.__lookupGetter__(prop)) {
                (function(thisIndex, thisProp) {
                    obj.__defineGetter__(thisIndex, function() {return obj[thisProp]});
                })(index, prop)
                index++;
            }
        };
        obj.__defineGetter__("length", function() {return index});
    }
    return obj;
}
var myObj = {
    left:50,
    top:20,
    width:10,
    height:10
}
extendAsArray(myObj);
[].map.call(myObj,function(s){return s+' px'}).join(', '); 
//"50px ,20px ,10px, 10px"

这种方法对我来说非常有趣。然而,它似乎也遇到了一些严重的问题!

  1. 如何扩展原来的myObj模型与几个新的属性?我们是否应该在每个属性更改上运行extendAsArray以更新它与length属性相关的内容?

  2. 当一个属性改变时,不仅仅是length属性是相关的;数组下标也应该更新,因为类数组属性请求肯定是未定义的。所以当

    console.log(myObj.length) -> 4
    myObj.zAxis=0
    
    然后

    console.log(myObj[4]) // -> undefined!
    console.log(myObj.length) // -> 4!
    

我已经修改了Angus的代码,所以它支持自动更新length属性的请求:

function extendAsArray(obj) {
    var index = 0;
    for(var prop in obj){
        if(!obj.__lookupGetter__(prop)){
           (function(thisIndex, thisProp){
              Object.defineProperty(obj, thisIndex, {
                    get: function(){return obj[thisProp]}
                    , enumerable: true
                    , configurable: true
                    , writeable: true
              });
           })(index, prop)
           index++;
        }
    }
    if(!obj.__lookupGetter__('length')){
       Object.defineProperty(obj, 'length', {
          get: function(){
            return extendAsArray(obj);
          }
          , configurable: true
          , writeable: true
       });
       return obj;
    }
    else{
       return index;
    }
}

问题是:我们如何更新对象的数组索引与它的length属性一起,当一个属性被改变,添加或删除?

我应该使用Object.watch吗?

还有一个未解决的问题:如何干扰我自己的unshimmed实用程序库,使它也以一致的方式为对象?

我对这两种类型使用相同的代码库:z.Object({}).mapEveryz.Object([]).mapEvery做同样的事情

请避免提及JQuery,以及下划线。对于这两种类型,我已经有了一个全面的、自定义的方法列表,我愿意使用可能与我的未修改的标准一起完成的标准,我不愿意重构它!

我猜这是你的问题:

我们如何更新对象的数组索引和它的长度属性,当一个属性被改变,增加或删除?

你创建方法来做这件事,所以你本质上模仿Object的内部方法。我不认为getter和setter可以这样做,但我可能错了。

其余部分与其说是回答,不如说是评论。

我早就希望原生数组和普通对象之间的界限完全模糊了

界限已经完全模糊了。数组是对象,唯一将它们分开的是它们特殊的长度属性。

EcmaScript 5通过引入泛型方法

明显抢占了这一趋势。

ES5没有引入泛型方法,至少从第3版开始它们就在语言中了。

由一种类型定义,但可被任何

使用

一点也不,事实上ES5有更多的限制。在第3版中,callapply使用Object(*thisArg*)thisArg强制转换为对象,或者在没有传递任何内容的情况下替换全局对象。在ES5中不是这样的,它传递thisArg不加修改。

使用数组作为对象的限制与约定有关,而与语言本身无关。大多数开发人员都清楚地看到对象和数组应该在什么时候被使用。在少数情况下,您确实需要像使用对象一样使用数组,但毫无疑问它们是存在的。jQuery是对象利用数组属性的一个例子,例如,由选择器收集的元素被添加为数字属性,并且有一个长度属性表示元素的数量。通过这种方式,通用数组方法可以应用于jQuery对象(顺便说一下,所有这些都在第3版中)。

Object.watch方法在javascript™中,它不是ES5的一部分,所以请谨慎使用。

创建自己版本的内置对象的一个主要问题是,你可能最终会将每个内置方法包装在一个本地方法中(就像jQuery包装了每个DOM方法一样),并开始在每个属性上设置getter和setter,或者以函数调用来取代属性访问(例如jQuery的val, attrprop方法)。

哦,对不起,我提到了jQuery…

设计一个库或框架来充分利用语言所拥有的特性,而不是试图强迫它去做它做不好的事情,或者它本身不能做的事情,似乎是更明智的。

但是你的尝试给满分。:-)

有一个库watch.js,它可以监视属性更新或新属性添加。尝试!

它与setInterval一起工作,所以它不是性能友好的。

当Harmony不在时,我们可以做一些简单的事情:

Object.observe(obj,Observer);

检查规格:和谐

但是,当稍后的对象扩展不在焦点中时,可以在初始化时冻结整个对象,既不需要更改属性也不需要添加属性。

代码相应更改:

extendAsArray = function z_extendAsArray(obj){
    var index = 0;
    for(var prop in obj){
        if(!obj.__lookupGetter__(prop)){
           (function(thisIndex, thisProp){
              Object.defineProperty(obj, thisIndex, {
                    get: function(){return obj[thisProp]}
                    , enumerable: true
                    , configurable: true
                    , writeable: true
              });
           })(index, prop)
           index++;
        }
    }
    if(!obj.__lookupGetter__('length')){
       Object.defineProperty(obj, 'length', {
        value:index
       });
       if(!Object.freeze){
          Object.defineProperty(Object, "freeze", {
                enumerable: false
              , configurable: false
              , writable: false
              , value: function (obj) {
                  var props = Object.getOwnPropertyNames(obj);
                  for(var i=0; i<props.length; i++){
                      var desc = Object.getOwnPropertyDescriptor(obj,props[i]);
                      if("value" in desc ){
                         desc.writable = false;
                      }
                      desc.configurable = false;
                      Object.defineProperty( obj, props[i], desc );
                  }
                  return Object.preventExtensions(obj);
              }
          });
       }
       Object.freeze(obj);
    }
    return obj;
};

我也发现了安格斯·克罗尔,谁已经提到了前一篇文章谈论它。

"是的,我们可以利用像underscore.js这样编写良好的库提供的等效功能,但我们仍然被锁定在非标准的反向签名中,其中方法是静态的,对象只是额外的参数——对于仅实例语言来说,这是一种笨拙的安排。在某种程度上,所有受支持的浏览器都将兼容ES5,到那时,被屏蔽的代码库可以简单地删除它的屏蔽库并继续使用,而未被屏蔽的代码库必须在重大重构或永远非标准的静态实用程序库之间做出选择。"