I'm trying to make a deep copy of an object with $.extend
.
The odd behaviour appear when I call the function display
after making the deep copy in a new object {}
.
This is happening because the function is using the closure of the object
returned by something()
. You can see this behaviour in the below image.
The result of the code below is Name[undefined]
. The expected result is Name[1]
.
function something() {
var self = {};
self.name = "Name";
self.display = function() {
return self.name + "[" + self.index +"]";
}
return self;
}
var obj = $.extend(true, {}, something(), { index : 1 });
console.log(obj);
console.log(obj.display());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I know that removing the {}
from parameters will solve my problem.
var obj = $.extend(true, something(), { index : 1 });
Or extracting the object
in a new variable and adding the property after extending.
var smth = something();
var obj = $.extend(true, {}, smth, { index : 1 });
smth.index = 2;
What causes this behaviour? There are other ways to solve it?
答案 0 :(得分:3)
Extend only effects property references. It cannot enter functions and magically change their references.
The function display
is scoped to the function something
. As a result the object it references is self, and not obj
or any of the new properties. A fun example is this:
var obj = $.extend(true, {}, something(), { name : "World" });
console.log(obj.display()); // Name[undefined]
Perhaps this would be just as surprising to you. In your use, extend is taking a blank object, and it is then using the object returned from something()
to recursively collect every property in order to copy its value.
So obj
ends up with the value of name
, the value of display
, and the value of index
from the third argument's object.
The core issue here is the value of display. Display's value is a function. That function has an Execution ContextECMA. Among other things, inside of this execution context are two environments for variables. A lexical environment, and a variable environment. The lexical environment is going to hold a set of variables from the parent scope of the context, and the variable environment is going to hold a set of variables from the executing scope.
The parent scope of the function for display is something
so the lexical environment for display
contains a reference to self
. This reference will not be changed by extend because the value of display is still the function whose execution context is scoped to that specific self
object.
In order to avoid using the reference to self
which is stored in the execution context's lexical environment for display
, you could use this
. The execution context will also contain a reference to this
. However, the binding for this
can change, and will change during extend
. So changing your code to use this
as opposed to self inside of display
function something() {
var self = {};
self.name = "Name";
self.display = function() {
return this.name + "[" + this.index +"]";
}
return self;
}
will properly show any new values because it's this binding will change during extend and that will mean the new this
's execution context is used, and by relation its variable and lexical environments will also be used.
console.log(obj.display()); //Name[1]
答案 1 :(得分:1)
You can turn something()
to a constructor function. You would have to instantiate it but you would get a new instance each time with it's own scope:
function something() {
this.name = "Name";
this.display = function() {
return this.name + "[" + this.index +"]";
}
}
var obj = $.extend(true, {}, new something(), { index : 1 });
console.log(obj);
console.log(obj.display());