2011-02-13 8 views
13

J'ai rencontré une situation dans laquelle j'ai besoin d'accéder à un objet javascript du serveur. Le serveur renvoie le nom de la chaîne de la fonction ou de l'objet et, en fonction d'autres métadonnées, j'évaluerai l'objet différemment. À l'origine, j'évaluais (eval([string])) et tout allait bien. Récemment, je mettais à jour la fonction pour ne pas utiliser eval pour la tranquillité d'esprit, et j'ai rencontré un problème avec les objets/fonctions namespaced. Plus précisément, j'ai essayé de remplacer un eval([name]) par un window[name] pour accéder à l'objet via la syntaxe du carré de l'objet global par rapport à eval.Accès à l'objet javascript avec nom de chaîne par nom de chaîne sans utiliser eval

Mais je suis tombé sur un problème avec des objets namespaced, par exemple:

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

// but if I do 
var obj = window[strObjName]; // doesn't work 

Quelqu'un peut-il trouver une bonne solution pour éviter l'utilisation de eval avec des chaînes namespaced?

Répondre

22

Vous pouvez diviser . et résoudre chaque propriété à tour de rôle. Ce sera bien tant que aucun des noms de propriété dans la chaîne contient un caractère .:

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); 
+0

@Tim c'est ce que je pensais, mais vous l'avez devant moi! :) –

+0

¬ Vous l'avez eu en premier? :(haha ​​+1 – JCOC611

+0

@TimDown mentionne que l'utilisation d'espaces de noms comme celui-ci n'est pas vraiment supportée nativement et peut rendre le code assez compliqué et difficile à maintenir. meilleures alternatives – Raynos

2

je suis venu avec ceci:

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 
-1

Ce qui suit suppose que les parties de strObjName sont séparés par . et boucles à travers à partir de la fenêtre jusqu'à ce qu'il se met à la fonction que vous voulez:

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

'for ... in' ne fonctionnera pas en général pour cet exemple où l'ordre est requis, puisque l'ordre d'énumération de' for ... in' n'est pas garanti. Utilisez une boucle 'for' normale. –

+0

N'utilisez pas for-in pour faire une boucle sur les tableaux en Javascript. C'est diabolique. – hugomg

+0

@Tim: Merci. Je n'ai pas réalisé que cet ordre n'était pas garanti. @missingno: Je ne suis pas sûr de ce que vous entendez par mal. –

1

Je sais que cela a été répondu Satisfact Ory au demandeur, mais j'ai récemment eu ce problème, mais avec ECRITURE à la valeur. Je suis venu avec ma propre classe statique pour gérer la lecture et l'écriture basée sur un chemin de chaîne. Le séparateur de chemin par défaut est . mais vous pouvez le modifier pour être n'importe quoi, tel que /. Le code est également commenté au cas où vous vous demandez comment cela fonctionne.

(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; 
    } 
})(); 
2

J'ai écrit une solution différente, en utilisant la récursivité. J'utilisais ceci à namespace l'attribut de nom des éléments d'entrée. Prenez, par exemple, ce qui suit:

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

Et disons que nous définissons la valeur de cette entrée à "25". Nous avons aussi un objet dans notre code quelque part:

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

L'objectif est donc de mettre automatiquement la valeur ici dans notre élément d'entrée dans la bonne position dans cette structure de données.

J'ai écrit cette petite fonction pour créer un objet imbriqué basé sur tout tableau de namespaces vous passez à elle:

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

    output[namespaceArray[pos]] = obj; 

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

    return output; 
}; 

Comment ça marche? Il commence à la fin du tableau namespaceArray et crée un objet avec une propriété, dont le nom est celui qui se trouve dans le dernier emplacement namespaceArray et dont la valeur est obj.Il récursive ensuite, en enveloppant cet objet dans des objets supplémentaires jusqu'à ce qu'il manque de noms dans namespaceArray. pos correspond à la longueur du tableau namespaceArray. Vous pourriez juste le faire dans le premier appel de fonction, quelque chose comme if (typeof(pos) === "undefined") pos = namespaceArray.length - 1, mais alors vous auriez une instruction supplémentaire à évaluer chaque fois que la fonction est récurée.

D'abord, nous avons divisé l'attribut name du input dans un tableau autour des séparateurs d'espace de noms et d'obtenir la valeur de l'entrée:

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

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

Ensuite, il suffit d'appeler notre fonction et affecter le résultat à une variable:

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

myObj va maintenant ressembler à ceci:

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

À ce stade, j'utilise la fonction étendre jQuery pour fusionner cette structure de nouveau dans l'objet de données d'origine:

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

Cependant, la fusion de deux objets est pas très difficile à faire en JavaScript sans cadre et il y a beaucoup de existing code cela fait bien le travail.

Je suis sûr qu'il existe des moyens plus efficaces pour y parvenir, mais cette méthode répond bien à mes besoins.

0

Mon échantillon sur pastebin: http://pastebin.com/13xUnuyV

i aimé votre code, et je l'ai fait beaucoup de choses similaires en utilisant PHP. Mais ici c'était dur comme je le pensais ... Donc j'ai utilisé les réponses @LordZardeck (https://stackoverflow.com/a/9338381/1181479) et @Alnitak (https://stackoverflow.com/a/6491621/1181479).

Et voici ce que j'ai, simplement l'utiliser:

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); 
6

Je pensais juste que je partage cela parce que j'ai fait l'autre jour. Je n'ai même pas réalisé que réduire était disponible dans JS!

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

quelqu'un Hope trouve cela utile ..

+0

J'aime l'idée! Légèrement adapté à mes objectifs .. http://codepen.io/anon/pen/taExD/ –

0

Il est possible de le faire en utilisant des techniques de programmation fonctionnels tels que

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

Cela peut être affecté à une fonction globale pour la réutilisation

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

Puis nous pouvons faire ce qui suit

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

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