JavaScript function which acts on itself to extend itself with another object

chrixm :

I am sure there is a name for what I am trying to do and I am sure this has been asked before, but I am struggling to find an existing answer - probably because I am not searching for the correct term. I have a JavaScript function called extend() which is similar to Object.assign() to merge Objects together - I don't have access to Object.assign in my current implementation.

function extend(target) {
  for (var i = 1; i < arguments.length; ++i) {
    if (typeof arguments[i] !== 'object') {
      continue;
    }
    for (var j in arguments[i]) {
      if (arguments[i].hasOwnProperty(j)) {
        if (typeof arguments[i][j] === 'object') {
          target[j] = extend({}, target[j], arguments[i][j]);
        }
        else if (!target.hasOwnProperty(j)) {
          target[j] = arguments[i][j];
        }
      }
    }
  }
  return target;
}

This works fine if I call it using extend(target, defaults) which will merge the contents of defaults into target without overwriting any existing keys in target (unless they are Objects and then they are merged).

I am looking for a way to actually call this using target.extend(defaults), so the first target parameter to extend is actually implied as this. I have tried to rewrite the function by replacing target with this, but it doesn't have the desired result (I think the way I have changed the recursion is wrong).

target.extend = function() {
   ...
   this[j] = this.extend(this[j], arguments[i][j]);
   ...
   return this;
};

Any pointers (or references to existing answers) would be gratefully appreciated.

CertainPerformance :

You can assign the function to Object.prototype to achieve the functionality you're looking for:

Object.defineProperty(
  Object.prototype,
  'extend',
  {
    enumerable: false,
    value: function() {
      const target = this;
      for (var i = 0; i < arguments.length; ++i) {
        if (typeof arguments[i] !== 'object') {
          continue;
        }
        for (var j in arguments[i]) {
          if (arguments[i].hasOwnProperty(j)) {
            if (typeof arguments[i][j] === 'object') {
              target[j] = extend({}, target[j], arguments[i][j]);
            }
            else if (!target.hasOwnProperty(j)) {
              target[j] = arguments[i][j];
            }
          }
        }
      }
      return target;
    }
  }
);


const obj = { foo: 'foo' };
obj.extend({ bar: 'bar' });
console.log(obj);

Or, cleaning up the code a bunch:

Object.defineProperty(
  Object.prototype,
  'extend',
  {
    enumerable: false,
    value: function(...args) {
      const target = this;
      for (const arg of args) {
        for (const [key, val] of Object.entries(arg)) {
          if (typeof val === 'object') extend(target[key], val);
          else target[key] = val;
        }
      }
    }
  }
);


const obj = { foo: 'foo' };
obj.extend({ bar: 'bar' });
console.log(obj);

But this is a bad idea, because it can result in name collisions and confusing code. What if there's an object like

const obj = { extend: true };

What does obj.extend do then? (It'll refer to the own property, but this isn't something a script-writer should have to worry about. Better to have a standalone function.)

This is exactly the same reason why many of the Object helper methods exist on the Object constructor, and not on Object.prototype. For example, you call Object.assign, or Object.defineProperty, not obj.assign or obj.defineProperty.

If you want this functionality only on certain objects, you can avoid the prototype pollution by calling Object.defineProperty on those objects:

const addExtend = obj => {
  Object.defineProperty(
    obj,
    'extend',
    {
      enumerable: false,
      value: function(...args) {
        const target = this;
        for (const arg of args) {
          for (const [key, val] of Object.entries(arg)) {
            if (typeof val === 'object') extend(target[key], val);
            else target[key] = val;
          }
        }
      }
    }
  );
};

const obj = { foo: 'foo' };
addExtend(obj);
obj.extend({ bar: 'bar' });
console.log(obj);

Or, if you have control over where the object is created, you can use Object.create so that extend is a method inherited from the prototype:

const objExtendProto = {};
Object.defineProperty(
  objExtendProto,
  'extend',
  {
    enumerable: false,
    value: function(...args) {
      const target = this;
      for (const arg of args) {
        for (const [key, val] of Object.entries(arg)) {
          if (typeof val === 'object') extend(target[key], val);
          else target[key] = val;
        }
      }
    }
  }
);

const obj = Object.create(objExtendProto);
obj.foo = 'foo';
obj.extend({ bar: 'bar' });
console.log(obj);

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=31882&siteId=1