开发者问题收集

在 JavaScript 中是否有直接的方法将字符串方法映射到字符串数组上?此外,如何解释为什么这不起作用?

2017-04-26
113

我忍不住注意到,做类似

["cat", "dog"].map(String.prototype.toUpperCase);

的事情是不可能的,它会抛出

Uncaught TypeError: String.prototype.toUpperCase called on null or undefined

下面的代码可以工作(为了方便打字,我使用了 es6 箭头符号),但似乎很奇怪地间接:

["cat", "dog"].map(s=>s.toUpperCase())

很奇怪地间接,因为它创建了一个额外的匿名函数只是为了包装一个已经存在的完好函数。也许这是人们必须忍受的事情,但味道不对。

所以我有两个问题:

  1. 是否有一种直接的方法可以将字符串方法映射到字符串数组上,而无需将其包装在匿名函数中?

  2. 我首先尝试的代码不起作用的技术解释是什么?我有两个猜测,但不知道哪个是正确的。

猜测 (a):这是因为装箱,即字符串文字与字符串对象不同,并且由于某种原因,将方法映射到它们上不会执行与仅在字符串上调用方法相同的安静强制。

猜测 (b):这是因为虽然函数是第一类对象,但方法却不是,并且永远无法映射?

猜测 (c):我应该使用其他语法吗?!( “”。prototype.toUpperCase ??)因为 JavaScript 小精灵?因为 null 既是又不是对象?在 ES2025 中修复了吗?只需使用 lodash 就可以解决所有问题?

谢谢!

3个回答

1. Is there a direct way to map a string method over an array of strings without wrapping it in an anonymous function?

否(假设“不将其包装在匿名函数中”更普遍的意思是“不 创建附加函数 ”)。

2. What's the technical explanation for why the code I tried first doesn't work? I have two guesses, but don't know which is right.

“问题”在于 toUpperCase 本质上是一种实例方法。它期望 this 引用应更改的值。但是 .map 会将值作为 参数 传递给函数。您的第一个示例基本上是执行

String.prototype.toUpperCase("dog")

而这根本不是该函数的工作方式。以下代码可以工作

 String.prototype.toUpperCase.call("dog")

但这反过来又不是 .map 的工作方式。

不猜测 a 和 b。关于 c,您应该使用的语法是您的第二个解决方案。当然还有其他方法。您可以编写一个辅助函数:

function toUpper(s) {
  return s.toUpperCase();
}

但这与使用箭头函数没有太大区别。

由于 toUpperCase 不接受参数,您甚至可以疯狂地使用类似

["cat", "dog"].map(String.prototype.toUpperCase.call.bind(String.prototype.toUpperCase));

但那

  • 也会创建一个新函数
  • 可能不太容易理解
  • 不普遍适用
Felix Kling
2017-04-26

当您尝试编写优雅的函数式程序时,对象方法(以及处理 this )可能非常麻烦。不用担心,只需将有问题的语法抽象到其自己的函数中即可

const send = (method, ...args) => obj =>
  obj[method](...args)

let x = ["cat", "dog"].map(send('toUpperCase'))
console.log(x) // ['CAT', 'DOG']

let y = ['cat', 'dog'].map(send('substr', 0, 1))
console.log(y) // ['c', 'd']

这是另一种可能不错的编写方法

const call = (f, ...args) => obj =>
  f.apply(obj, args)

let x = ["cat", "dog"].map(call(String.prototype.toUpperCase))
console.log(x) // ['CAT', 'DOG']

let y = ['cat', 'dog'].map(call(String.prototype.substr, 0, 1))
console.log(y) // ['c', 'd']

另一种方法是定义您自己的函数

const map = f => xs => xs.map(x => f(x))
const toUpperCase = x => x.toUpperCase()
const substr = (x,y) => z => z.substr(x,y)

let x = map (toUpperCase) (["cat", "dog"])
console.log(x) // ['CAT', 'DOG']

let y = map (substr(0,1)) (['cat', 'dog'])
console.log(y) // ['c', 'd']
Mulan
2017-04-27

要回答您的两个问题, 您不能在不使用回调函数的情况下将字符串方法映射到字符串数组。

您编写的第一个代码:

["cat", "dog"].map(String.prototype.toUpperCase);

不起作用,因为 .map() 方法需要一个回调函数来处理每个迭代元素并返回它,但您只是试图在 undefined 上调用 String.prototype.toUpperCase 函数,因为您尚未将其绑定到当前元素。

解决方案:

为了使其工作并等同于 ES6 提供的代码:

["cat", "dog"].map(s=>s.toUpperCase())

您需要修改代码以接受一个回调函数,该函数在通过传递的迭代元素上调用 String.prototype.toUpperCase .map() 方法:

["cat", "dog"].map(function(s){return s.toUpperCase()});
cнŝdk
2017-04-26