【问题标题】:Efficient way to track object keys with JavaScript使用 JavaScript 跟踪对象键的有效方法
【发布时间】:2018-08-13 19:07:01
【问题描述】:

我正在使用带有陷阱的Proxy 对象来跟踪对象键,以便我可以轻松地迭代和/或从对象中选择一个随机键,而性能开销很小。目前,我在添加键时将它们存储在一个数组中。这对于插入和随机选择非常有效,但是当一个属性被删除时,开销是巨大的:

// Benchmark
var testObject = createProxy();

var start = performance.now();

for( var i = 0; i < 1e4; i++ )
  testObject[Math.random() * 1e6 << 0] = true;
for( var i in testObject )
  if( i[0] !== '_' )
    delete testObject[ i ];
    
var end = performance.now();

var total = ( end - start );
console.log( 'Test took ' + total + ' ms' );

// Implementation
function createProxy() {
  function keyTracker() {
    const set = new Set();
    function defineProperty( target, property, descriptor ) {
      target[property] = descriptor.value;
      if( property[0] === '_' ) return true;
      if( set.has( property ) ) return true;
      
      set.add( property );
      target[ '__keys' ].push( property );
      return true;
    }
    function deleteProperty( target, property ) {
      if( property[ 0 ] === '_' ) return true;
      
      delete target[ property ];
      if( !set.delete( property ) ) return true;
      
      target[ '__keys' ] = target[ '__keys' ].filter( 
        key => key !== property
      );
      return true;
    }
    return { defineProperty, deleteProperty };
  }
  
  var proxy = new Proxy(
    Object.defineProperty( {}, '__keys', {
     configurable: true,
     enumerable: false,
     writable: true,
     value: []
  } ), keyTracker() );
  
  return proxy;
}

随着对象中键数量的增长,调用Array.filter() 的开销会成倍增加。我正在寻找一种能够让我避免调用它来删除单个元素的解决方案。

有没有一种方法可以重新构建它以允许 O(1) 插入、随机选择和删除键?

【问题讨论】:

  • 你需要关心这个......为什么?用户不会注意到它是每秒 1000 万次操作还是 100 万次......如果你真的,真的,真的需要你不应该使用的那么多性能Proxy(slow)... 或 Object.defineProperty(slow)... 或 JavaScript。
  • 我了解 Proxy/defineProperty 等的性能开销。开销不是问题。问题是当对象中的键数量(用作字典)增长太大时,性能会呈指数下降,因为它当前依赖于数组,并且从 10k 键增加到 100 万键是不可行的。这将在 NodeJS v10 中运行,而不是在浏览器中运行,而这个选择不在我的掌控之中。

标签: javascript dynamic-proxy


【解决方案1】:

您可以使用splice。它会让你下降 800-1000 毫秒。

       target[ '__keys' ].splice(target[ '__keys' ].indexOf(property), 1);

// Benchmark
var testObject = createProxy();

var start = performance.now();

for( var i = 0; i < 1e4; i++ )
  testObject[Math.random() * 1e6 << 0] = true;
for( var i in testObject )
  if( i[0] !== '_' )
    delete testObject[ i ];
    
var end = performance.now();

var total = ( end - start );
console.log( 'Test took ' + total + ' ms' );

// Implementation
function createProxy() {
  function keyTracker() {
    const set = new Set();
    function defineProperty( target, property, descriptor ) {
      target[property] = descriptor.value;
      if( property[0] === '_' ) return true;
      if( set.has( property ) ) return true;
      
      set.add( property );
      target[ '__keys' ].push( property );
      return true;
    }
    function deleteProperty( target, property ) {
      if( property[ 0 ] === '_' ) return true;
      
      delete target[ property ];
      if( !set.delete( property ) ) return true;
      
      target[ '__keys' ]
      .splice(target[ '__keys' ].indexOf(property), 1);
      return true;
    }
    return { defineProperty, deleteProperty };
  }
  
  var proxy = new Proxy(
    Object.defineProperty( {}, '__keys', {
     configurable: true,
     enumerable: false,
     writable: true,
     value: []
  } ), keyTracker() );
  
  return proxy;
}

【讨论】:

    【解决方案2】:

    你可以使用排序数组,然后使用二分查找来实现O(log n)

    // Benchmark
    Array.prototype.binarySearch = function (target, comparator) {
        var l = 0,
            h = this.length - 1,
            m, comparison;
        comparator = comparator || function (a, b) {
            return (a < b ? -1 : (a > b ? 1 : 0)); /* default comparison method if one was not provided */
        };
        while (l <= h) {
            m = (l + h) >>> 1; /* equivalent to Math.floor((l + h) / 2) but faster */
            comparison = comparator(this[m], target);
            if (comparison < 0) {
                l = m + 1;
            } else if (comparison > 0) {
                h = m - 1;
            } else {
                return m;
            }
        }
        return~l;
    };
    Array.prototype.binaryInsert = function (target, duplicate, comparator) {
        var i = this.binarySearch(target, comparator);
        if (i >= 0) { /* if the binarySearch return value was zero or positive, a matching object was found */
            if (!duplicate) {
                return i;
            }
        } else { /* if the return value was negative, the bitwise complement of the return value is the correct index for this object */
            i = ~i;
        }
        this.splice(i, 0, target);
        return i;
    };
    Array.prototype.binaryDelete = function (target, duplicate, comparator) {
        var i = this.binarySearch(target, comparator);
        if (i >= 0) { /* if the binarySearch return value was zero or positive, a matching object was found */
        this.slice(i,1)
        }
        return i;
    };
    var testObject = createProxy();
    
    var start = performance.now();
    
    for( var i = 0; i < 1e4; i++ )
      testObject[Math.random() * 1e6 << 0] = true;
    for( var i in testObject )
      if( i[0] !== '_' )
        delete testObject[ i ];
        
    var end = performance.now();
    
    var total = ( end - start );
    console.log( 'Test took ' + total + ' ms' );
    
    // Implementation
    function createProxy() {
      function keyTracker() {
        const set = new Set();
        function defineProperty( target, property, descriptor ) {
          target[property] = descriptor.value;
          if( property[0] === '_' ) return true;
          if( set.has( property ) ) return true;
          
          set.add( property );
          target[ '__keys' ].binaryInsert( property );
          return true;
        }
        function deleteProperty( target, property ) {
          if( property[ 0 ] === '_' ) return true;
          
          delete target[ property ];
          if( !set.delete( property ) ) return true;
          
          target[ '__keys' ].binaryDelete(property)
          return true;
        }
        return { defineProperty, deleteProperty };
      }
      
      var proxy = new Proxy(
        Object.defineProperty( {}, '__keys', {
         configurable: true,
         enumerable: false,
         writable: true,
         value: []
      } ), keyTracker() );
      
      return proxy;
    }

    从这里使用了二分搜索植入 javascript-binary-search

    【讨论】:

      猜你喜欢
      • 2012-02-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-28
      • 1970-01-01
      • 2018-09-01
      • 2013-03-13
      相关资源
      最近更新 更多