【问题标题】:Dynamically create a method from a string in ES6 javascript (non browser)从 ES6 javascript(非浏览器)中的字符串动态创建方法
【发布时间】:2017-09-08 06:57:10
【问题描述】:

在 javascript 代码中,我有一个定义对象内属性路径的字符串。比如

var def = "contact.email"

如何从这个字符串中得到下面的函数?

o => o.contact.email

以便我可以通过以下方式使用它:

var person = {
    name: 'Test',
    contact: { email: 'test@test.test' }
}

var emailGetter = MagicCompileFunction('contact.email');

var email = emailGetter(person);
// here, email should hold the value of person.contact.email

编译时路径字符串未知。它也可以由用户提供。

该解决方案也应该适用于非浏览器环境(没有window 对象),例如在NodeJS 服务器端javascript 中。

我知道一种解决方案是创建一个将对象和字符串作为参数的通用方法(例如valueGetter(person, "contact.email")),其中字符串定义对象内的路径,然后在每个点上拆分字符串'。并按照对象上的路径。但我不希望这个算法在每次调用getter 函数时都执行。我需要一个动态编译的方法,它会给我一个最终的 getter,可以立即访问所需的(子)属性。

【问题讨论】:

  • 为什么你需要一个最终的吸气剂?解决方案(如果您完全按照指定需要)将使用eval,这在大多数情况下都是一个糟糕的主意,包括这个。通用方法将是更好的解决方案。
  • 我会尝试一种“通用”方法,看看它的性能如何,然后再尝试编写一些科学项目。
  • 事实上我什至不认为你问什么有意义,除非你想直接“编译”成运行时中间代码,这肯定是“科学项目”。一旦知道了属性名称,仍然需要从链中的每个对象中按顺序获取属性值。
  • @Pointy 这是一个可能会被执行数百或数千次的方法。我认为包含字符串拆分和 for 循环的通用方法来循环路径中的每个属性将比字典查找效率低。另一方面,使用泛型方法可以更轻松地避免访问未定义对象的属性,但这会在循环中添加额外的 if 检查。
  • 如果确实如此,那么也许您应该重新考虑构建数据结构的方式。我的意思是,如果有重要的性能考虑,那么探索系统的其他方面可能会改变可能会很好。

标签: javascript node.js ecmascript-6 code-generation dynamic-code


【解决方案1】:

Archer's answer稍微多一点ES6:

function MagicCompileFunction(mapping) {
	return (obj => mapping.split(".").reduce((curr, name) => curr[name], obj));
}

var person = {
    name: "Test",
    contact: {
		email: "test@test.test"
	}
}

var emailGetter = MagicCompileFunction("contact.email");
var email = emailGetter(person);

var nameGetter = MagicCompileFunction("name");
var name = nameGetter(person);

console.log(email);
console.log(name);

【讨论】:

    【解决方案2】:

    您可以使用 Function constructor

    实现此目的
    var person = {
        name: 'Test',
        contact: { email: 'test@test.test' }
    }
    var maginFunction = (def) => new Function('o','return o.'+ def);
    
    var emailGetter = maginFunction('contact.email');
    
    var email = emailGetter(person);
    console.log(email);
    

    【讨论】:

    • 这被标记为最佳解决方案,因为它似乎完全符合我的要求。所有其他答案都不能避免循环、字符串拆分或数组操作。
    • 但是,请务必小心您允许的“def”。正如您所说,它可能来自用户并在服务器上运行。你要非常小心,他们不会给你“constructor && require('child_process').exec('rm -rf /')”。
    • @Redu 好吧,如果仅用于站点控制下的数据,“黑客”可能不是问题。我想弄清楚的是,使用 Function 构造函数创建的函数是否是优化的候选者。 (当然,未优化的版本可能比迭代解决方案更快。)
    • 好吧,是的,这可能是有风险的。然而,在其他新闻中,函数 (emailGetter) 确实得到了优化,至少在 V8 中是这样。 (See this article.)。
    • 这确实是一种危险的方法。输入字符串可能来自用户端,但在进入 Function 构造函数之前会匹配某个正则表达式。
    【解决方案3】:

    我喜欢这个问题,所以我想出了一个解决方案。这应该可以满足您的要求,而无需使用 eval...

    function MagicCompileFunction(mapping) {
    	mapping = mapping.split(".");
    	return (function(obj) {
    		var result = obj;
    		for (var idx in mapping) {
    			result = result[mapping[idx]];
    		}
    		return result;
    	});
    }
    
    var person = {
        name: "Test",
        contact: {
    		email: "test@test.test"
    	}
    }
    
    var emailGetter = MagicCompileFunction("contact.email");
    var email = emailGetter(person);
    
    var nameGetter = MagicCompileFunction("name");
    var name = nameGetter(person);
    
    console.log(email);
    console.log(name);

    MagicCompileFunction() 返回一个函数,该函数使用您在创建 getter 对象时使用的映射进行初始化。然后您可以将任何人传递给该对象以返回相关数据。

    【讨论】:

    • 您的for 循环可能应该是一个普通的for 循环而不是for ... in,但是是的,这可能是最好的主意。
    【解决方案4】:

    您可以简单地如下动态访问嵌套对象的属性;

    function dynPropAcc(o,s){
      return s.split(".")
              .reduce((p,q,i) => i-1 ? p[q] : o[p][q]);
    }
    
    var def = "contact.email.2",
        obj = {contact: {name: "John", email: { 1: "john@x.com", 2: "john@y.com"}}};
    
    console.log(dynPropAcc(obj,def));

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-26
      • 1970-01-01
      • 2012-03-20
      • 2011-11-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-16
      相关资源
      最近更新 更多