Why $.extend keep the function scope of the old object

时间:2015-09-01 21:28:42

标签: jquery

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.

enter image description here

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?

2 个答案:

答案 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());