Skip to content
This repository

Object.extend #16

Open
Raynos opened this Issue · 49 comments

16 participants

Raynos (Jake Verbaten) Luís Couto Ben Alman Marco Rogers Pedro Del Gallego Gordon Brander Kit Cambridge Rick Waldron Harold Thétiot Rasmus Rønn Nielsen Felix Böhm Brian J Brennan Ryan Florence Sebastian Porto James M. Greene Juan Ignacio Dopazo
Raynos (Jake Verbaten)
Raynos commented

We don't have a native extend / mixin / merge function standardized yet. There are lots of implementations of this in libraries

es-discuss has spoken numerous times about a deep extend which has been ignored due to implementation detail problems

So instead we should propose a shallow n-ary extend.

var o = Object.extend({}, { 
  a: "a"
}, {
  b: "b"
}, {
  c: "c"
})

VOTE: http://goo.gl/mod/yNNM

Luís Couto
Couto commented

I think that right now, this is the feature that's lacking in Javascript.
It would help a lot mixing/merging and together with Object.create, makes the perfect match to not need classes at all…

but it's just my two cents…

Ben Alman
cowboy commented

+1 if the mixin properties are figured out at access-time, not mixin-time (like properties inherited from prototypes). No idea what that's called.

Raynos (Jake Verbaten)
Raynos commented

To expand, I currently emulate this extend function as

function extend(target) {
    Array.prototype.slice.call(arguments, 1).forEach(function(source) {
        Object.getOwnPropertyNames(source).forEach(function (name) {
            target[name] = source[name]
        })
    })
    return target
}
Marco Rogers
polotek commented

+1 on figuring this out.

@cowboy can you clarify what you mean a bit? Maybe an example?

Raynos (Jake Verbaten)
Raynos commented

@cowboy to do what you want is to have multiple inheritance using prototypes which is something that will be incredibly difficult to get into es-next. So basically no.

extend would mixin properties at mixin-time and they would become own properties of the first parameter.

Ben Alman
cowboy commented

Once you mixin properties, they are copied over. If you then change the original object the properties were copied from, the target object doesn't see the new properties. We can already copy properties over, but It would be nice if there was some internal awesomeness that allows us to do stuff we can't already do, like have those property changes to auto-propagate.

If that makes any sense at all.

Luís Couto
Couto commented

@cowboy while merging at mixing-time solves conflicts (same name prop/method) by the last property/method merged, how would you suggest to fix conflicts that way? (just curious, your idea has some beauty although I agree with @Raynos)

Ben Alman
cowboy commented

@Couto not sure, Ruby's include might be a good model. Either way, this issue is for just copying properties which seems to me to be less of a "mixin" and more of an "extend" or "merge".

Pedro Del Gallego

@cowboy, @Couto : There are many language with "lookup" mixins. I.e ruby

module A
  def hola
    puts "hola"
  end
end

module B
  def hola
    puts "adios"
  end
end

class C
  include A
  include B
end

C.new.hola
# => adios
Gordon Brander

Es.next is proposing to solve this with the Object Extension Literal. I think the Object Extension Literal is lovely, but perhaps a pragmatic alternative would be the easily polyfill-able Object.extend method.

Kit Cambridge
Owner

@cowboy I think the <| ("prototype for") operator addresses that, no?

Marco Rogers
polotek commented

@cowboy that's exactly what the prototype chain does. I definitely want more tools around managing the prototype chain (though there be dragons). But this is separate I think. To keep things simple, we shouldn't have any one thing try to do too much IMO.

Rick Waldron

+1 to everything @polotek just said.

Harold Thétiot

+1

JsPerf for polyfill:
http://jsperf.com/object-extend

Rasmus Rønn Nielsen

I want this!

But the word "extend" smells of class inheritance to me. While this method can be used for inheritance, it really is more general than that.

Object.merge communicates its use much better I think.

Felix Böhm
fb55 commented

All the polyfills shown so far overwrite all previous properties. -1e3 for that. It should behave like setting the objects [[Prototype]] (or __proto__ property), just that it's not changing the objects [[Prototype]]. But I really like the basic idea and would love to see it.

Just another thought: How about instanceof? When this should work like the extend keyword in Java, instanceof should return true.

Raynos (Jake Verbaten)
Raynos commented

People are confusing extend or merge with [[Prototype]]

It has and should have absolutely nothing to do with [[Prototype]] nor does it have anything to do with notion of classes.

extend should merge in properties as own properties.

Felix Böhm
fb55 commented

When I use Object.extend (please don't suggest merge as it implies the generation of a new object), I want to extend an objects properties with the properties of another object. Replacing properties comes unexpected and is confusing. That's why I compared it to setting an objects [[Prototype]].

I'm not quite sure about the instanceof keyword. -1 for the fact that you can't write a polyfill for it, +.5 for allowing something like multi-class inheritance. I mentioned it because I think it's worth a thought.

Raynos (Jake Verbaten)
Raynos commented

You want to extend an object with properties of another object. That means setting and overwriting properties. It cannot be done in any other fashion unless it's multiple inheritance and that's not what we are talking about.

Property conflicts have to be handled somehow, the cleanest way is last property write wins.

Felix Böhm
fb55 commented

@Raynos That's not how the extend keyword is used in other languages and will confuse new users. Fixing this problem is quite easy:

function extend(a, b) {
    for (var i in b) {
        if (!(i in a) && Object.prototype.hasOwnProperty.call(b, i)) a[i] = b[i];
    }
    return a;
}
Raynos (Jake Verbaten)
Raynos commented

How the extend keyword is used in other language is irrelevant. extend is just a name, it has zero relation to the extend notion in other languages.

As for simply refusing to extend properties onto an object if that key is already taken. That's silly

extend(defaults, options)

is a common pattern where you extend default property values with custom property values and you want overwriting

Felix Böhm
fb55 commented
Raynos (Jake Verbaten)
Raynos commented
var defaults = { validate: false, limit: 5, name: "foo" };
var options = { validate: true, name: "bar" };

/* merge defaults and options, without modifying defaults */
var settings = extend({}, defaults, options);

There is a solid need for the overwriting of properties.

If the consistencies across languages is important (it's not) then we should use a name that is not extend. The name is just bike shedding.

Felix Böhm
fb55 commented
extend(options, defaults);

works as good as your solution, doesn't require the generation of any new objects (less GC) and is (probably) easier to optimize.

What you are trying to do should be named Object.copy, not extend. Otherwise, the name is pretty confusing. My +1 for this topic is only for Object.extend, as Object.copy is pretty trivial (ie. can be done with a single line of code).

Raynos (Jake Verbaten)
Raynos commented

both are trivial, but incredibly useful and should just exist as a function.

Just like Object.keys is trivial and useful

Rick Waldron

+9001 to everything @Raynos has said

Felix Böhm
fb55 commented

Just a comparison:

Object.copy(a, b);
//becomes
Object.keys(b).forEach(x=>a[x]=b[x]);

while

Object.extend(a, b);
//becomes
Object.keys(b).forEach(x=>x in a||a[x]=b[x]);

Okay, I agree, both are pretty trivial.

Having too many weapons isn't helping, as I'm experiencing with the three substring methods (substr, substring and slice). There are plenty of cases where String#length is still used inside these functions, which is totally unnecessary.

Even better, many people often use String#indexOf to search for a string at a specific location. My favorite example is the proposal for some extensions of the string prototype - it's a secret why they don't use String#substr, especially for String#endsWith, which contains incredibly complicated code and could be a one-liner.

So when even the ones writing the language are confused by having too many weapons, how could adding two functions add any value to the language?

The most users of Object.extend would be the ones coming from other languages and searching for an extend mechanism in JS. Why should we confuse them by defining an unexpected behavior?

Brian J Brennan

+:thumbsup: to @Raynos as well, my experience with the current extend implementations (Underscore for example) is that last property in will overwrite existing properties. extend(defaults, options) is a very common pattern that's in use now.

Rick Waldron

@FB55 with regard to endsWith et al, you're forgetting that error mitigation and optional argument handling is rolled into the production of standard built-ins.

Specifically, endsWith and startsWith will exist to preclude the need for awkward indexOf calls.

"foo".indexOf("f") === 0

vs.

"foo".startsWith("f")

The intention of the latter is infinitely more obvious

Raynos (Jake Verbaten)
Raynos commented

coming from other languages

Why should we confuse them by defining an unexpected behavior?

User X comes from language Y and expects extend to behave in manner Z
User A comes from language B and expects extend to behave in manner C

You can't do this. You can find a useful extend function (in my opinion nary, shallow and overwriting properties) and document and promote it.

People should never be using a function and expecting it to behave in a certain fashion based on the documentation.

Also an n-ary extend is a double for loop and requires a utility function. This utility function shouldn't have to be custom written, JS should provide one.

Felix Böhm
fb55 commented

@rwldrn I would prefer an implementation that doesn't search the whole string for matches just to check if that match is at index 0. This function would do the same:

String.prototype.startsWith = function (str) {
    return this.substr(0, str.length) === str;
}

The code for endsWith would also be much nicer and easier to read:

String.prototype.endsWith = function (str) {
    return this.substr(-str.length) === str;
}

I don't need to run a benchmark to know which implementation is faster, as well as easier to read and understand.

@Raynos Okay, I only know Java well enough to be sure of what extends does. Could you please give an example of a language that extends an object/class by replacing it's properties?

Brian J Brennan

@FB55 While not dealing specifically with dicts, the semantics of Ruby's #extend is that the last property overwrites: http://ruby-doc.org/core-1.9.3/Object.html#method-i-extend

module Mod
  def hello
    "Hello from Mod.\n"
  end
end

class Klass
  def hello
    "Hello from Klass.\n"
  end
end

k = Klass.new
k.hello         #=> "Hello from Klass.\n"
k.extend(Mod)   #=> #<Klass:0x401b3bc8>
k.hello         #=> "Hello from Mod.\n"
Raynos (Jake Verbaten)
Raynos commented

@FB55 a comparison to Java is ridiculous. The extends keyword in Java is an inheritance concept that has an equivalent in JavaScript as <| and [[Prototype]].

Confusing these two principles is like confusing Java and JavaScript.

Marco Rogers
polotek commented

@FB55 I think you've made an okay case that some people might expect extend to be different. But it's not the common opinion in JavaScript. Since jsfixed was formed to focus on the needs of js devs, you'll have to make a better case that this isn't the way we want it. Maybe find more people to add weight to your argument. We are pretty much talking about a de facto standard here, so changing how it works requires shifting a lot of momentum.

Rick Waldron

@FB55 but you've omitted startsWith's optional position argument, and endsWith's optional endPosition argument, which means you've made an incompatible implementation.

Kit Cambridge
Owner

@FB55:

I would prefer an implementation that doesn't search the whole string for matches just to check if that match is at index 0.

String.prototype.startsWith = function(value, position) {
  return this.indexOf(value) == ((position -= position % 1) || 0);
};

String.prototype.endsWith = function(value, position) {
  var length = this.length - value.length;
  position = typeof position == "undefined" ? length : ((position -= position % 1) || 0);
  return length > -1 && this.indexOf(value, position) == position;
};
Ryan Florence

JavaScript already has a concept of what extend means.

Most of us use shallow _.extend or deep $.extend(true, ...) or something that behaves similarly to one or the other.

I find shallow _.extend to have fewer unexpected side effects than deep. Would :sparkles::heart::sparkles: to have it in JS exactly as implemented in underscore.

Some may use dojo.extend, which works on constructor's prototypes instead of just a plain object (like dojo.mixin). So there's a little potential confusion there too, but not much, since its doing the same thing as _.extend, but just on prototypes.

If people feel like extend is too loaded from other languages (again, please read my first line), then I wouldn't mind Object.merge, but then there might be some confusion/conflict for MooTools users since Object.merge in MooTools is a deep merge and I'm proposing a shallow merge.

But again, I think _.extend and $.extend have already paved the way for what the word means in JavaScript.

Sebastian Porto
sporto commented

+1 on extend

We use this all the time using jQuery or underscore. It will be great to see a native way.

James M. Greene

+1

I use jQuery's $.extend all the time. I would much prefer if deep copying was also supported like in $.extend, though, as I rarely use the shallow copying version.

Raynos (Jake Verbaten)
Raynos commented

Deep copying requires a "deep copy algorithm with no gotchas". The jQuery algorithm has a lot of badly handled edgecases.

It's best to avoid / ignore deep copying as no good algorithm for it has been proposed.

Rick Waldron

@Raynos Feel free to file a ticket outlining the poorly handles edge cases: http://bugs.jquery.com - thanks!

Marco Rogers
polotek commented

I don't think it's a matter of poorly handled edge cases, but more that there's no good default for many of the edge cases.

  • Do you want to deep copy array elements or just make a new array with the same elements?
  • Do you take the values of accessor properties or should you transfer the actual accessor? If you take the accessor, you've got potential data leakage.
  • If an object is a custom type e.g. has a proto, what if you really want to clone the type and not just copy the properties?

I tend to agree that deep copy is too use case specific to be something everybody agrees on. Shallow copy is pretty well defined and still abundantly useful.

Juan Ignacio Dopazo

+1 to Object.merge as a shallow copy. Let's keep extend for prototype related actions.

Ryan Florence

Function.extend for constructor inheritance?

Felix Böhm
fb55 commented

@rpflorence The Function object should only contain functions to work with functions. Besides, there are other usecases for such a function.

Juan Ignacio Dopazo

@rpflorence yes. Actually, I'd like to see a Function.prototype.extend. Which is way I prefer Object.merge, because Object is a function and so it will inherit extend from Function.prototype. See https://gist.github.com/1367191

Ryan Florence

@FB55 exactly: a constructor is a function...

Felix Böhm
fb55 commented

@rpflorence But the prototype isn't. It's an object. And you don't want a function that only manipulates a specific property of an object, so adding this to Function isn't a good idea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.