2

很久以前,我一直愿意完全模糊原生数组和常规对象之间的界限,不仅扩展了与 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吗?

还有一个未解决的问题:如何干扰我自己的未填充实用程序库,并以一致的方式使其也适用于对象?

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

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

4

2 回答 2

1

我想这是你的问题:

当属性被更改、添加或删除时,我们如何更新对象的数组索引及其长度属性?

您创建方法来执行此操作,因此您基本上模仿了Object 内部方法。我不认为你可以用 getter 和 setter 做到这一点,但我可能错了。

其余的更多是评论而不是答案。

我很久以前就愿意完全模糊原生数组和常规对象之间的界限

这条线已经完全模糊了。数组是对象,唯一让它们与众不同的是它们特殊的长度属性。

EcmaScript 5 通过引入泛型方法显然先发制人,

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

由一种类型定义但可由任何类型使用

一点也不,事实上 ES5 的限制更大。在 ed 3 中,call使用applythisArg强制为一个对象Object(*thisArg*),或者如果没有传递任何内容,则替换全局对象。在未修改通过thisArg的 ES5 中并非如此。

将数组用作对象的限制与约定有关,与语言本身无关。大多数开发人员在何时应该使用对象或数组时看到了明显的区别。在少数情况下您确实需要像对象一样使用数组,但毫无疑问它们存在。jQuery 是一个对象利用数组属性的示例,例如,选择器收集的元素作为数字属性添加,并且长度属性是元素的数量。这样,泛型数组方法可以应用于 jQuery 对象(顺便说一句,所有这些都在第 3 版中)。

Object.watch方法在 JavaScrpit™ 中,它不是 ES5 的一部分,因此请谨慎使用。

创建自己的内置对象版本的一个主要问题是,您可能最终将每个内置方法包装在本机方法中(就像 jQuery 包装每个 DOM 方法一样)并开始在每个属性上设置 getter 和 setter ,或以函数调用结束来替换属性访问(例如 jQueryval和方法)。如果性能很重要,则相当乏味且缓慢。attrprop

哦,对不起,我提到了 jQuery... :-(

设计一个库或框架以充分利用该语言的特性似乎更明智,而不是试图强迫它做它做得不好或本机不做的事情。

但尝试满分。:-)

于 2013-03-13T00:07:44.397 回答
0

那里有一个库 watch.js,它也在关注属性更新或新属性添加。 试用!

它与 setInterval 一起使用,因此它对性能不友好。

当 Harmony 出现时,我们可以简单地做一些事情:

Object.observe(obj,Observer);

检查规格: Harmony

但是,当以后的对象扩展不是焦点时,我可以在初始化时冻结整个对象,而不是被属性更改或属性添加所困扰。

代码相应更改:

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;
};

我还发现了上一篇文章中提到的安格斯·克罗尔(Angus Croll)所说的内容。

“是的,我们可以利用像 underscore.js 这样编写良好的库提供的等效功能,但我们仍然被锁定在非标准的倒置签名中,其中方法是静态的,对象只是额外的参数——这是一种笨拙的安排一种仅实例语言。在某些时候,所有支持的浏览器都将兼容 ES5,此时已填充的代码库可以简单地删除它的 shim 库并继续执行,而未填充的代码库必须在主要重构或永久非标准和静态实用程序库。”

于 2013-03-13T02:45:44.867 回答