开发者

Access namespaced javascript object by string name without using eval

I ran into a situation where I need access to a javascript object from the server. The server returns the string name of the function or object and based on other metadata I will evaluate the object differently.

Originally I was evaluating (eval([string])) and everything was fine. Recently I was updating the function to not use eval for security peace of mind, and I ran into an issue with namespaced objects/functions.

Specifically I tried to replace an eval([name]) with a window[name] to access the object via the square bracket syntax from the global object vs eval.

But I ran into a problem with namespaced objects, for example:

var strObjName = 'namespace.serviceArea.function';
// if I do
var obj = eval(strObjName); // works

// but开发者_开发知识库 if I do
var obj = window[strObjName]; // doesn't work

Can anyone come up with a good solution to avoid the use of eval with namespaced strings?


You could split on . and resolve each property in turn. This will be fine so long as none of the property names in the string contain a . character:

var strObjName = 'namespace.serviceArea.function';
var parts = strObjName.split(".");
for (var i = 0, len = parts.length, obj = window; i < len; ++i) {
    obj = obj[parts[i]];
}
alert(obj);


Just thought I'd share this because I made this the other day. I didn't even realize reduce was available in JS!

function getByNameSpace(namespace, obj) {
    return namespace.split('.').reduce(function(a,b) {
            if(typeof a == 'object') return a[b];
            else return obj[a][b]; 
    });
}

Hope someone finds this useful..


I came up with this:

function reval(str){
   var str=str.split("."),obj=window;
   for(var z=0;z<str.length;z++){
     obj=obj[str[z]];
   }
   return obj;
}
eval("window.this.that");
reval("this.that"); //would return the object
reval("this.that")(); //Would execute


I have written a different solution, using recursion. I was using this to namespace the name attribute of input elements. Take, for example, the following:

<input type="number" name="testGroup.person.age" />

And let's say we set the value of that input to "25". We also have an object in our code somewhere:

var data = {
    testGroup: {
        person: {
            name: null,
            age: null,
            salary: null
        }
    }
};

So the goal here is to automatically put the value in our input element into the correct position in that data structure.

I've written this little function to create a nested object based on whatever array of namespaces you pass to it:

var buildObject = function ( obj, namespaceArray, pos ) {
    var output = {};

    output[namespaceArray[pos]] = obj;

    if ( pos > 0 ) {
        output = buildObject(output, namespaceArray, pos-1);
    }

    return output;
};

How does it work? It starts at the end of the namespaceArray array, and creates an object with one property, the name of which is whatever is in the last slot in namespaceArray, and the value of which is obj. It then recurses, wrapping that object in additional objects until it runs out of names in namespaceArray. pos is the length of the namespaceArray array. You could just work it out in the first function call, something like if ( typeof(pos) === "undefined" ) pos = namespaceArray.length - 1, but then you'd have an extra statement to evaluate every time the function recursed.

First, we split the name attribute of the input into an array around the namespace separators and get the value of the input:

var namespaceArray = inputElement.name.split("."),
    obj = inputElement.value;

// Usually you'll want to do some undefined checks and whatnot as well

Then we just call our function and assign the result to some variable:

var myObj = buildObject(obj, namespaceArray, namespaceArray.length - 1);

myObj will now look like this:

{
    testGroup: {
        person: {
            age: 25
        }
    }
}

At this point I use jQuery's extend function to merge that structure back into the original data object:

data = $.extend(true, data, myObj);

However, merging two objects is not very difficult to do in framework-free JavaScript and there is plenty of existing code that gets the job done well.

I'm sure there are more efficient ways to get this done, but this method meets my needs well.


I know this has been answered satisfactory to the asker, but I recently had this issue, but with WRITING to the value. I came up with my own static class to handle reading and writing based on a string path. The default path separator is . but you can modify it to be anything, such as /. The code is also commented incase you wonder how it works.

(function(){
    var o = {}, c = window.Configure = {}, seperator = '.';

    c.write = function(p, d)
    {
        // Split the path to an array and assaign the object
        // to a local variable
        var ps = p.split(seperator), co = o;

        // Iterate over the paths, skipping the last one
        for(var i = 0; i < ps.length - 1; i++)
        {
            // Grab the next path's value, creating an empty 
            // object if it does not exist
            co = (co[ps[i]])? co[ps[i]] : co[ps[i]] = {};
        }

        // Assign the value to the object's last path
        co[ps[ps.length - 1]] = d;
    }

    c.read = function(p)
    {
        var ps = p.split(seperator), co = o;
        for(var i = 0; i < ps.length; i++)
        {
            co = (co[ps[i]])? co[ps[i]] : co[ps[i]] = {};
        }
        return co;
    }
})();


My sample on pastebin: http://pastebin.com/13xUnuyV

i liked you code, and i did a lot of similar stuff using PHP. But here that was tough as i though... So i used answers @LordZardeck (https://stackoverflow.com/a/9338381/1181479) and @Alnitak (https://stackoverflow.com/a/6491621/1181479).

And here what i have, just use it:

    var someObject = {
        'part1' : {
            'name': 'Part 1',
            'size': '20',
            'qty' : '50'
        },
        'part2' : {
            'name': 'Part 2',
            'size': '15',
            'qty' : '60'
        },
        'part3' : [
            {
                'name': 'Part 3A РУКУ!!!',
                'size': '10',
                'qty' : '20'
            }, {
                'name': 'Part 3B',
                'size': '5',
                'qty' : '20'
            }, {
                'name': 'Part 3C',
                'size': '7.5',
                'qty' : '20'
            }
        ]
    };

    //var o = {}, c = window.Configure = {}, seperator = '.';

    var c = function(){
        this.o = {};
        this.seperator = ".";
        this.set = function(obj){
            this.o = obj;
        }
        this.write = function(p, d) {
            p = p.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
            p = p.replace(/^\./, ''); // strip leading dot
            // Split the path to an array and assaign the object
            // to a local variable
            var ps = p.split(this.seperator), co = this.o;

            // Iterate over the paths, skipping the last one
            for(var i = 0; i < ps.length - 1; i++)
            {
                // Grab the next path's value, creating an empty 
                // object if it does not exist
                co = (co[ps[i]])? co[ps[i]] : co[ps[i]] = {};
            }

            // Assign the value to the object's last path
            co[ps[ps.length - 1]] = d;
        }
        this.read = function(p) {
            p = p.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
            p = p.replace(/^\./, ''); // strip leading dot
            var ps = p.split(this.seperator), co = this.o;
            /*
            for(var i = 0; i < ps.length; i++)
            {
                co = (co[ps[i]])? co[ps[i]] : co[ps[i]] = {};
            }
            */
            while (ps.length) {
                var n = ps.shift();
                if (n in co) {
                    co = co[n];
                } else {
                    return;
                }
            }
            return co;
        }

    };

    var n = new c();
    n.set(someObject);
    console.log('whas');
    console.log('n.read part.name', n.read('part1.name'));
    n.write('part3[0].name', "custom var");
    console.log('part1.name now changed');
    n.write('part1.name', "tmp");
    console.log('n.read part.name', n.read('part1.name'));
    console.log('----');
    console.log('before', someObject);
    console.log('someObject.part1.name', someObject.part1.name);
    console.log('someObject.part3[0].name', someObject.part3[0].name);


It is possible to do this using functional programming techniques such as

(function (s) {return s.split('.').reduce(function(p,n) { p[n] = p[n] || {}; return p[n];},window);})("some.cool.namespace");

This can be assigned to a global function for re-use

window.ns = (function (s) {return s.split('.').reduce(function(p,n) { p[n] = p[n] || {}; return p[n];},window);})

Then we can do the following

ns("some.cool").namespace = 5;

if (5 != ns("some.cool.namespace")) { throw "This error can never happen" }


The following assumes that the parts of strObjName are separated by . and loops through starting at window until it gets down to the function you want:

var strObjParts = strObjName.split('.');
var obj = window;
for(var i in strObjParts) {
  obj = obj[strObjParts[i]];
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜