如何使用来自对象的参数运行函数?

时间:2019-03-29 14:21:15

标签: javascript

我有很多类似以下的功能:

var runme = function(liked, disliked){ 
  console.log(`i like ${liked} but I dislike ${disliked}`) 
}

我有一个对象,我想用它来填充函数的参数

var obj = {liked: 'apple', disliked: 'pear'}

如何使用对象运行函数以指定参数?

我尝试使用传播语法:

runme(...obj)

但这会产生:

TypeError: Found non-callable @@iterator

如何使用对象中的参数运行函数?

我无法更改功能,因为我正在创建一个需要能够处理任意功能的包装器。

编辑:我将帖子修改为使用“喜欢”和“不喜欢”,而不是“一个”和“两个”,因为这更好地显示了排序的重要性。

我可以使用任何版本的JavaScript,直至ES9。

6 个答案:

答案 0 :(得分:2)

  

我无法更改功能,因为我正在创建一个需要能够处理任意功能的包装器。

这很不幸,因为让他们接受结构化的参数正是您所需要的。

不幸的是,除非您解析对该函数调用toString的结果,否则参数的名称不可用,这会带来很多危险。基本上,您需要一个完整的JavaScript解析器(因为如今参数列表很复杂,包括可能包含默认值的整个函数定义)和缩小符,因此可能需要重命名参数(例如,将liked更改为_0

您也不能指望对象中属性的顺序。 (他们确实有订单,但在这里……或几乎其他任何地方都没有帮助。)

您已经说过需要处理事先不知道其参数的函数,因此,关于将函数包装到需要传入参数名称的实用程序的各种想法不起作用。 (如果有人好奇,请查看revision list来查看。)

您可以从toString开始, 如果 ,我们可以做几个假设:

  1. 功能参数列表为简单。它们不包括解构或默认值。

  2. 在参数列表中未使用注释。

  3. 您的压缩程序不会重命名功能参数。

  4. 函数是所有传统的函数,方法或箭头函数,它们在参数列表中具有(例如,()而不是(x) => x * 2x => x * 2

  5. 您不介意效率会很低(每次解析)。

这是很多假设,我不建议这样做。但是,如果您可以依靠它们:

// LOTS of assumptions here!
function run(f, obj) {
  let params = /\(([\w\s,]*)\)/.exec(String(f));
  if (!params) {
    throw new Error("Couldn't parse function");
  }
  params = params[1].split(/\s*,\s*/).map(n => n.trim());
  return f.apply(this, params.map(param => obj[param]));
}

run(runme, obj);

实时示例:

// Traditional function
const runme = function(liked, disliked){ 
  console.log(`i like ${liked} but I hate ${disliked}`) 
}
// Traditional function with newlines
const runme2 = function(
    liked,
    disliked
  ){ 
  console.log(`i like ${liked} but I hate ${disliked}`) 
}
// Arrow function
const runme3 = (liked, disliked) => { 
  console.log(`i like ${liked} but I hate ${disliked}`) 
}
// Method
const {runme4} = {
  runme4(liked, disliked) { 
    console.log(`i like ${liked} but I hate ${disliked}`) 
  }
};

const obj = {liked: 'apple', disliked: 'pear'}

function run(f, obj) {
  let params = /\(([\w\s,]*)\)/.exec(String(f));
  if (!params) {
    throw new Error("Couldn't parse function");
  }
  params = params[1].split(/\s*,\s*/).map(n => n.trim());
  return f.apply(this, params.map(param => obj[param]));
}

run(runme, obj);
run(runme2, obj);
run(runme3, obj);
run(runme4, obj);

之所以可行,是因为Function.prototype.toString is standardized now,甚至在资源受限的环境中,都需要包括参数列表(但可能不包括其余的函数实现)。

答案 1 :(得分:2)

没有通用的方法可以完成此任务。

如果可以保证源的来源,那么可以在移植或加载阶段使用AST操作来构建包装器。

如果不能,那么尤其不走运,因为参数名称可能会混乱,无法进行对象-键到参数名称的转换。

答案 2 :(得分:1)

重新编写答案以考虑正确的顺序。

只要命名您的对象键以匹配参数,就可以解析如下函数:您可以看到,无论它们以什么顺序传递,输出都是正确的;

var obj = {liked: 'apple', disliked: 'pear'}

    var runme = function (liked, disliked) {
        console.log(`i like ${liked} but I dislike ${disliked}`)
    }

    var translateFunc = function (func, args) {
        let funcAsString = func.toString();
        let argNames     = funcAsString.slice(funcAsString.indexOf('(') + 1, funcAsString.indexOf(')')).match(/([^\s,]+)/g);
        let parsedArgs   = [];


        for (let a of argNames) {
            for (let k of Object.keys(args)) {
                if (k == a) {
                    parsedArgs.push(args[a]);
                }
            }
        }

        eval(func(...parsedArgs));
    }

    translateFunc(runme, obj);

    obj = {disliked: 'pear', liked: 'apple'}

    translateFunc(runme, obj);

答案 3 :(得分:1)

有点晚了,但是这是另一种尝试。我认为它可以工作,但是代码的缩小将更改您的函数参数,但不会更改对象属性,因此您需要解决该问题。

var runme = function(liked, disliked){ 
    console.log(`i like ${liked} but I dislike ${disliked}`) 
}

var obj = {liked: 'apple', disliked: 'pear'}

const wrap = runme => {
    const regex =  new RegExp(/\(([^\)]*)\)/)
    const args = regex.exec(runme.toString())[1].split(',').map(s => s.trim())
    return obj => {
        const result = args.map(a => obj[a])
        runme(...result)
    }
}

const wrappedRunme = wrap(runme);

console.log(wrappedRunme(obj))

答案 4 :(得分:0)

您可以按照以下步骤进行操作:

  • 首先将函数转换为string
  • 使用RegExp获取函数的参数。
  • 使用split()将参数字符串转换为参数数组。
  • 然后在该数组上使用reduce()并创建一个具有给定对象值的新的有序数组。

var runme = function(liked, disliked){ 
  console.log(`i like ${liked} but I dislike ${disliked}`) 
}

function wrapper(obj){
  let args = runme.toString().match(/\(.+\)/)[0]
  args = args.slice(1,-1);
  args = args.split(',').map(x => x.trim());
  let ordered = args.reduce((ac,ar) => [...ac,obj[ar]],[]);
  runme(...ordered);
}

wrapper({liked:"liked", disliked:"disliked"})
wrapper({ disliked:"disliked",liked:"liked"})

答案 5 :(得分:0)

我遇到了同样的错误,以防万一这是问题所在,这就是为我解决的问题...

代替:

runme(...obj)

我需要将对象散布到新对象中

runme({ ...obj })