26

I think this is fairly simple,

Is there a simple way to add a secondary name for a property (I think this one is String specific – I'm not sure), i.e.,

c = length // this line pseudo code

'hello world'.length // returns 11
'hello world'.c      // this line is pseudo code, meant to return 11

In the example above, there's an alias created for the property length. Is that possible to do in JavaScript?

TrojanByAccident
  • 227
  • 1
  • 6
  • 20
  • 1
    What are you trying to solve? Because it sounds a bit like an [XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – VLAZ Sep 10 '16 at 22:43
  • 2
    `Object.defineProperty(String.prototype, "c", {get: function() { return this.length }})` https://jsfiddle.net/7krcvhne/ –  Sep 10 '16 at 22:49
  • 2
    This answered my question –  Sep 10 '16 at 22:52
  • Possible duplicate of [Dynamically access object property using variable](http://stackoverflow.com/questions/4244896/dynamically-access-object-property-using-variable) –  Sep 14 '16 at 02:01

2 Answers2

40

1. With Bracket Notation

With bracket notation, you can access the property like so:

'hello world'[c]

This does the same thing as 'hello world'.length if c is 'length' as a string.

var c = 'length';
console.log('hello world'[c]);

The only difference is that the property is a string. Bracket notation is a property accessor.

2. With Object.defineProperty()

Now if you want an alias:

Object.defineProperty(String.prototype, 'c', {
    get: function() {
        return this.length;
    }
});

console.log("hello world".c);

The above uses Object.defineProperty to define a property for the existing object, String's prototype object. That way, all instances of a string will have this new property. Per the documentation:

The Object.defineProperty() method defines a new property directly on an object, or modifies an existing property on an object, and returns the object.

Syntax

Object.defineProperty(obj, prop, descriptor)

Where obj is the object being modified, prop is the new or existing property, and descriptor is the descriptor for the new or existing property.

Thus, the above defines a property for the object String.prototype, with name c. Its descriptor is a get function which returns the length of this. In the example above, this refers to the string so it returns the length of the string. You can read more about getters here.


This can also be defined for more types by changing to the applicable prototype (obj), such as using Object.prototype instead. However, this has potential issues, as trying to return this.length on an object without a length property will return undefined, as seen here. You can also use Object.defineProperties to define multiple properties at a time.

Community
  • 1
  • 1
Andrew Li
  • 55,805
  • 14
  • 125
  • 143
10

To expand on @AndrewLi's answer, there are a bunch of alias-like things you can do with Object.defineProperty().

  • Read-only alias (same as his answer)
  • Synced alias modifies source when it's modified
  • Defaulted alias reads from or "defaults to" source until changed (at which point relationship is broken)

I'll write them as functions that relate a source object with property, sProp, and a target object with property, tProp. Source and target can be the same object (allowing a property to be an alias to another property on the same object), but this is not a requirement. Also, the source (or target) can be a prototype (like Object.prototype, String.prototype, etc..)

"Normal" assignment

This one's not really an alias, nor does it use Object.defineProperty(). The target is assigned the VALUE of the source, not a reference to it. This means that when the source changes, the target does not.

function assign(target, tProp, source, sProp) {
  target[tProp] = source[sProp];
  return target;
}

let myTarget = {}
let mySource = {b: 12}

myTarget = assign(myTarget, 'a', mySource, 'b')

// "alias" was assigned source value
console.log('"alias":',myTarget.a) // 12

// changes to source independent of "alias"
mySource.b = 13
console.log("source:", mySource.b) // 13
console.log('"alias":', myTarget.a) // still 12

Read-only Alias (the above solution)

When the property definition does not have a setter is it effectively a read-only value. Changes to the source property will be reflected in the alias; however, you cannot set the value of the alias.

function read(target, tProp, source, sProp){
  Object.defineProperty(target, tProp, {
    enumerable: true,
    configurable: true,
    get(){
      return source[sProp];
    }
  })
  return target;
}

let myTarget = {}
let mySource = {b: 12}

myTarget = read(myTarget, 'a', mySource, 'b')

// Alias gets value from source
console.log("alias:", myTarget.a) // 12

// No setter effectively means read-only
myTarget.a = 15
console.log("alias:", myTarget.a) // 12

// Changes to source are seen in target
mySource.b = 15
console.log("source:", mySource.b) //15
console.log("target:", myTarget.a) //15

Synced Alias

This alias modifies the read-only version above to set the source property whenever the alias (target) property is set. In this way the source and target always remain in sync.

function sync(target, tProp, source, sProp){
  Object.defineProperty(target, tProp, {
    enumerable: true,
    configurable: true,
    get(){
      return source[sProp];
    },
    set(value){
      source[sProp] = value;
    }
  })
  return target;
}

let myTarget = {}
let mySource = {b: 12}

myTarget = sync(myTarget, 'a', mySource, 'b')

// Alias gets value from source
console.log("alias:", myTarget.a) // 12

// Changing alias' value modifies the source
myTarget.a = 15
console.log("alias:", myTarget.a) // 15
console.log("source:", mySource.b) // 15

// Changing source modifies alias still
mySource.b = 20
console.log("source:", mySource.b) // 20
console.log("alias:", myTarget.a) // 20

Alias as Default

This allows you to default an alias/target value until otherwise updated. Unlike the read-only case, you can change the alias/target value, yet unlike the the sync, when you change the alias you do not update the source - instead the alias becomes a regular value.

function setDefault(target, tProp, source, sProp){
  Object.defineProperty(target, tProp, {
    enumerable: true,
    configurable: true,
    get(){
      return source[sProp];
    },
    set(value){
      delete target[tProp];
      target[tProp] = value;
    }
  })
  return target;
}

let myTarget = {}
let mySource = {b: 12}

myTarget = setDefault(myTarget, 'a', mySource, 'b')

// Alias gets value from source
console.log('alias:', myTarget.a) // 12

// Changing source modifies alias still
mySource.b = 15
console.log('source:', mySource.b) // 15
console.log('alias:', myTarget.a) // 15

// Changing alias' value DOES NOT modify source
myTarget.a = 20
console.log("alias:", myTarget.a) // 20
console.log("source:", mySource.b) // 15

// The relationship between source and alias is BROKEN
mySource.b = 100
console.log("source:", mySource.b) // 100
console.log("alias:", myTarget.a) // 20
Steve Ladavich
  • 3,472
  • 20
  • 27