为什么部分应用程序在 currying 时有效,但在 .bind() 中无效?
Why does partial application work when currying but not with .bind()?
我正在练习函数的部分应用,即固定函数参数。我学会了两种实现它的方法:
- 首先柯里化原始函数。
- 通过使用
.bind()
方法。
在下面的示例中,我将展示只有第一种策略,即首先 currying 有效。我的问题是为什么使用 .bind()
不起作用。
例子
考虑以下数据:
const genderAndWeight = {
john: {
male: 100,
},
amanda: {
female: 88,
},
rachel: {
female: 73,
},
david: {
male: 120,
},
};
我想创建两个将此数据重新格式化为新对象的实用函数:
- 函数 A -- returns 人们将名称作为键,将权重作为值
- 函数 B -- returns 人们将名字作为键,将性别作为值
因为预计这两个函数非常相似,所以我想创建一个主函数,然后从中派生出两个版本,从而遵守 DRY 原则。
// master function
const getGenderOrWeightCurried = (fn) => (obj) =>
Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)]));
此解决方案的核心是我要提供给 fn
参数的内容。所以要么
const funcA = (x) => Number(Object.values(x)); // will extract the weights
或
const funcB = (x) => Object.keys(x).toString(); // will extract the genders
现在正在做部分应用:
const getWeight = getGenderOrWeightCurried(funcA);
const getGender = getGenderOrWeightCurried(funcB);
效果很好:
console.log({
weight: getWeight(genderAndWeight),
gender: getGender(genderAndWeight),
});
// { weight: { john: 100, amanda: 88, rachel: 73, david: 120 },
// gender:
// { john: 'male',
// amanda: 'female',
// rachel: 'female',
// david: 'male' } }
到目前为止一切顺利。下面的方法用了.bind()
,不行
// master function
const getGenderOrWeightBothParams = (fn, obj) =>
Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)]));
// same as before
const funcA = (x) => Number(Object.values(x));
const funcB = (x) => Object.keys(x).toString();
// partial application using .bind()
const getWeight2 = getGenderOrWeightBothParams.bind(funcA, null);
const getGender2 = getGenderOrWeightBothParams.bind(funcB, null);
// log it out to console
console.log({weight: getWeight2(genderAndWeight), gender: getGender2(genderAndWeight)})
TypeError: fn is not a function
还值得注意的是,在不同的情况下,.bind()
确实允许部分应用。例如:
const mySum = (x, y) => x + y;
const succ = mySum.bind(null, 1);
console.log(succ(3)); // => 4
来自哪里
柯里化和部分应用是函数式继承,因此在此上下文之外使用它们将阻止您获得它们的全部好处,并且可能成为 self-inflicted 混乱的根源。
提议的数据结构充满了问题,最大的问题是数据在数据对象的两个值 和 键之间混合。姓名、性别和体重都是 值 。 name
、gender
和 weight
是键。这会将您的数据更改为这种合理的形状,它也采用合理的名称,people
。
柯里化
pick
很容易实现它的目标,因为 name
、gender
和 weight
在语义上都是相邻的,即它们都是从对象中挑选的键。当数据跨值和键混合时,它会更难导航结构并给您的程序带来不必要的复杂性。
const people = [
{ name: "john", gender: "male", weight: 100 },
{ name: "amanda", gender: "female", weight: 88 },
{ name: "rachel", gender: "female", weight: 73 },
{ name: "david", gender: "male", weight: 120 }
]
// curried
const pick = (fields = []) => (from = []) =>
from.map(item => Object.fromEntries(fields.map(f => [f, item[f]])))
const nameAndGender =
pick(["name", "gender"]) // ✅ apply one argument
const nameAndWeight =
pick(["name", "weight"]) // ✅ apply one argument
console.log(nameAndGender(people))
console.log(nameAndWeight(people))
.as-console-wrapper { min-height: 100%; top: 0; }
部分申请
partial
完全足以增进您对这一点的理解。您不需要 .bind
,因为它的第一个参数与动态上下文有关,这是 object-oriented 风格的原则。
这是使用未柯里化 pick
并应用 partial
应用程序的相同演示 -
const people = [
{ name: "john", gender: "male", weight: 100 },
{ name: "amanda", gender: "female", weight: 88 },
{ name: "rachel", gender: "female", weight: 73 },
{ name: "david", gender: "male", weight: 120 }
]
// uncurried
const pick = (fields = [], from = []) =>
from.map(item => Object.fromEntries(fields.map(f => [f, item[f]])))
const partial = (f, ...a) =>
(...b) => f(...a, ...b)
const nameAndGender =
partial(pick, ["name", "gender"]) // ✅ partial application
const nameAndWeight =
partial(pick, ["name", "weight"]) // ✅ partial application
console.log(nameAndGender(people))
console.log(nameAndWeight(people))
.as-console-wrapper { min-height: 100%; top: 0; }
"是否强制更改数据结构?"
当然不会,但你很快就会运行惹上麻烦。让我们把你的练习进行到底,看看问题出在哪里。正如您所演示的,柯里化程序运行良好 -
const genderAndWeight = {
john: {male: 100},
amanda: {female: 88},
rachel: {female: 73},
david: {male: 120},
}
const getGenderOrWeightCurried = (fn) => (obj) =>
Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)]));
const funcA = (x) => Number(Object.values(x));
const funcB = (x) => Object.keys(x).toString();
const getWeight = getGenderOrWeightCurried(funcA);
const getGender = getGenderOrWeightCurried(funcB);
console.log({
weight: getWeight(genderAndWeight),
gender: getGender(genderAndWeight),
});
.as-console-wrapper { min-height: 100%; top: 0; }
您问题中的部分应用程序使用.bind
不正确。上下文 (null
) 作为第二个位置传递,但 .bind
期望此参数位于 第一个 位置 -
const getWeight2 =
getGenderOrWeightBothParams.bind(funcA, null); // ❌
const getWeight2 =
getGenderOrWeightBothParams.bind(null, funcA); // ✅
您可以用同样的方法修复 getGender2
,但让我们用 partial
来代替这个。动态上下文是一种 object-oriented 机制,在学习函数式编程基础知识时无需关注它。 partial
允许您绑定函数的参数而无需提供上下文 -
const partial = (f, ...a) =>
(...b) => f(...a, ...b)
const getGender2 =
getGenderOrWeightBothParams.bind(funcB, null); // ❌
const gender2 =
partial(getGenderOrWeightBothParams, funcB); // ✅
这为您提供了两个使用原始建议数据结构的部分应用的工作示例 -
const genderAndWeight = {
john: {male: 100},
amanda: {female: 88},
rachel: {female: 73},
david: {male: 120},
}
const partial = (f, ...a) =>
(...b) => f(...a, ...b)
const getGenderOrWeightBothParams = (fn, obj) =>
Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)]));
const funcA = (x) => Number(Object.values(x));
const funcB = (x) => Object.keys(x).toString();
const getWeight2 =
getGenderOrWeightBothParams.bind(null, funcA); // ✅ .bind
const getGender2 =
partial(getGenderOrWeightBothParams, funcB) // ✅ partial
console.log({
weight: getWeight2(genderAndWeight),
gender: getGender2(genderAndWeight),
});
.as-console-wrapper { min-height: 100%; top: 0; }
“那么问题出在哪里?”
就在这里-
const funcA = (x) => Number(Object.values(x)); // ⚠️
const funcB = (x) => Object.keys(x).toString(); // ⚠️
“但它有效!”
您知道您的 funcA
创建一个数字数组,将其转换为字符串,然后再转换回数字吗?事实上,它似乎可以正常工作的唯一原因是因为每个人都是一个具有 单个 key/value 对的对象。一旦添加更多条目,模型就会中断 -
const o1 = { female: 73 }
const o2 = { female: 73, accounting: 46000 }
const o3 = { gender: "female", weight: 73, role: "accounting", salary: 46000 }
const funcA = x => Number(Object.values(x))
console.log(funcA(o1)) // 73
console.log(funcA(o2)) // NaN
console.log(funcA(o3)) // NaN
funcB
也发生了类似的问题。您的函数似乎可以正常工作,因为单个字符串 ["foo"]
的数组在转换为字符串时将导致 "foo"
。在任何更大的阵列上尝试此操作,您将得到无法使用的结果 -
const o1 = { female: 73 }
const o2 = { female: 73, accounting: 46000 }
const o3 = { gender: "female", weight: 73, role: "accounting", salary: 46000 }
const funcB = x => Object.keys(x).toString()
console.log(funcB(o1)) // "female"
console.log(funcB(o2)) // "female,accounting"
console.log(funcB(o3)) // "gender,weight,role,salary"
当向树中添加更多数据时,funcA
和 funcB
将如何工作?
地狱再回来
我们知道 funcA
在原始数据中每个项目被调用一次。随机选择一个人,让我们看看当funcA
达到rachel
的值时会发生什么。到底有多糟糕,真的吗?
Number(Object.values(x)) x := { female: 73 }
Number(value) value := [73]
When Number is called with argument value
, the following steps are taken:
- If
value
is present, then ✅
- Let
prim
be ? ToNumeric(value
). ✅
- If Type(
prim
) is BigInt, let n
be (ℝ(prim
)). ❌
- Otherwise, let
n
be prim
. ✅
- Else,
- Let
n
be +0.
- If NewTarget is undefined, return
n
. ✅
- Let
O
be ? OrdinaryCreateFromConstructor(NewTarget, "%Number.prototype%", « [[NumberData]] »).
- Set
O.[[NumberData]]
to n
.
- Return
O
.
ToNumeric(value) value := [73]
The abstract operation ToNumeric takes argument value
and returns either a normal completion containing either a Number or a BigInt, or a throw completion. It returns value
converted to a Number or a BigInt. It performs the following steps when called:
- Let
primValue
be ? ToPrimitive(value
, number). ✅
- If Type(
primValue
) is BigInt, return primValue
. ❌
- Return ? ToNumber(
primValue
). ✅
ToPrimitive(input[, preferredType]) input := [73], preferredType := number
The abstract operation ToPrimitive takes argument input
(an ECMAScript language value) and optional argument preferredType
(string or number) and returns either a normal completion containing an ECMAScript language value or a throw completion. It converts its input
argument to a non-Object type. If an object is capable of converting to more than one primitive type, it may use the optional hint preferredType
to favour that type. It performs the following steps when called:
- If Type(
input
) is Object, then ✅
- Let
exoticToPrim
be ? GetMethod(input
, @@toPrimitive). ✅
- If
exoticToPrim
is not undefined, then ❌
- If
preferredType
is not present, let hint be "default".
- Else if
preferredType
is string, let hint be "string".
- Else,
- Assert:
preferredType
is number.
- Let hint be "number".
- Let
result
be ? Call(exoticToPrim
, input
, « hint »).
- If Type(
result
) is not Object, return result
.
- Throw a TypeError exception.
- If
preferredType
is not present, let preferredType
be number. ❌
- Return ? OrdinaryToPrimitive(
input
, preferredType
). ✅
- Return
input
. ✅
OrdinaryToPrimitive(O, hint) O := [73] hint := number
The abstract operation OrdinaryToPrimitive takes arguments O
(an Object) and hint
(string or number) and returns either a normal completion containing an ECMAScript language value or a throw completion. It performs the following steps when called:
- If
hint
is string, then ❌
- Let
methodNames
be « "toString", "valueOf" ».
- Else, ✅
- Let
methodNames
be « "valueOf", "toString" ». ✅
- For each element
name
of methodNames
, do ✅
- Let
method
be ? Get(O
, name
). ✅
- If IsCallable(
method
) is true, then ✅
- Let result be ? Call(
method
, O
). ✅
- If Type(result) is not Object, return result. ⚠️
- Throw a TypeError exception.
我们正在深入这里,但我们几乎已经到达底部。到标记 ⚠️ 的点,数组的 [[3.2.2]]、valueOf
将 return 数组本身,它仍然具有 Object 类型。因此循环 [[3.]] 继续 name := "toString"
O := [73] name := "toString"
- Let
method
be ? Get(O
, name
). ✅
- If IsCallable(
method
) is true, then ✅
- Let result be ? Call(
method
, O
). ✅
- If Type(result) is not Object, return result. ✅
OrdinaryToPrimitive(O, hint) O := [73] hint := number
Return => "73"
ToPrimitive(input[, preferredType]) input := [73], preferredType := number
Return => "73"
ToNumeric(value) value := [73]
Return => ToNumber("73")
ToNumber(argument) argument := "73"
The abstract operation ToNumber takes argument argument
and returns either a normal completion containing a Number or a throw completion. It converts argument
to a value of type Number according to Table 13 (below):
Argument Type
Result
Undefined
Return NaN.
Null
Return +0.
Boolean
If argument
is true, return 1. If argument
is false, return +0.
Number
Return argument
(no conversion).
String
Return ! StringToNumber(argument
). ✅
Symbol
Throw a TypeError exception.
BigInt
Throw a TypeError exception.
Object
Apply the following steps:
...
1. Let primValue
be ? ToPrimitive(argument
, number).
...
2. Return ? ToNumber(primValue
).
我们到达了 StringToNumber("73")
,现在没有继续向下 the rabbit hole 的意义了。由于您 self-inflicted 选择了错误的数据结构,这整罐蠕虫都被打开了。想知道这个人的体重吗?
const person = { name: "rachel", weight: 73 }
console.log(person.weight) // 73
没有不必要的中间数组,没有array-to-string转换,没有string-to-number转换,没有NaN的可能性,没有地狱。
阅读更多
对您正在使用的每个其他功能重复“地狱”练习。自己确定这是否真的是您想要走的路 -
功能组合
柯里化函数与另一种称为 function composition 的技术搭配得很好。当一个函数只接受一个参数而 return 是另一个参数时,您可以 compose 或 sequence 它们,有时称为“管道&uot;或“管道”。这开始证明函数式编程应用于整个系统时的效果 -
const gte = (x = 0) => (y = 0) =>
y >= x
const filter = (f = Boolean) => (a = []) =>
a.filter(f)
const prop = (k = "") => (o = {}) =>
o[k]
const pipe = (...fs) =>
x => fs.reduce((r, f) => f(r), x)
const heavyWeights =
filter(pipe(prop("weight"), gte(100)))
const people = [
{ name: "john", gender: "male", weight: 100 },
{ name: "amanda", gender: "female", weight: 88 },
{ name: "rachel", gender: "female", weight: 73 },
{ name: "david", gender: "male", weight: 120 }
]
console.log(heavyWeights(people))
.as-console-wrapper { min-height: 100%; top: 0; }
[
{
"name": "john",
"gender": "male",
"weight": 100
},
{
"name": "david",
"gender": "male",
"weight": 120
}
]
如果您觉得本节有趣,我邀请您阅读
我正在练习函数的部分应用,即固定函数参数。我学会了两种实现它的方法:
- 首先柯里化原始函数。
- 通过使用
.bind()
方法。
在下面的示例中,我将展示只有第一种策略,即首先 currying 有效。我的问题是为什么使用 .bind()
不起作用。
例子
考虑以下数据:
const genderAndWeight = {
john: {
male: 100,
},
amanda: {
female: 88,
},
rachel: {
female: 73,
},
david: {
male: 120,
},
};
我想创建两个将此数据重新格式化为新对象的实用函数:
- 函数 A -- returns 人们将名称作为键,将权重作为值
- 函数 B -- returns 人们将名字作为键,将性别作为值
因为预计这两个函数非常相似,所以我想创建一个主函数,然后从中派生出两个版本,从而遵守 DRY 原则。
// master function
const getGenderOrWeightCurried = (fn) => (obj) =>
Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)]));
此解决方案的核心是我要提供给 fn
参数的内容。所以要么
const funcA = (x) => Number(Object.values(x)); // will extract the weights
或
const funcB = (x) => Object.keys(x).toString(); // will extract the genders
现在正在做部分应用:
const getWeight = getGenderOrWeightCurried(funcA);
const getGender = getGenderOrWeightCurried(funcB);
效果很好:
console.log({
weight: getWeight(genderAndWeight),
gender: getGender(genderAndWeight),
});
// { weight: { john: 100, amanda: 88, rachel: 73, david: 120 },
// gender:
// { john: 'male',
// amanda: 'female',
// rachel: 'female',
// david: 'male' } }
到目前为止一切顺利。下面的方法用了.bind()
,不行
// master function
const getGenderOrWeightBothParams = (fn, obj) =>
Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)]));
// same as before
const funcA = (x) => Number(Object.values(x));
const funcB = (x) => Object.keys(x).toString();
// partial application using .bind()
const getWeight2 = getGenderOrWeightBothParams.bind(funcA, null);
const getGender2 = getGenderOrWeightBothParams.bind(funcB, null);
// log it out to console
console.log({weight: getWeight2(genderAndWeight), gender: getGender2(genderAndWeight)})
TypeError: fn is not a function
还值得注意的是,在不同的情况下,.bind()
确实允许部分应用。例如:
const mySum = (x, y) => x + y;
const succ = mySum.bind(null, 1);
console.log(succ(3)); // => 4
来自哪里
柯里化和部分应用是函数式继承,因此在此上下文之外使用它们将阻止您获得它们的全部好处,并且可能成为 self-inflicted 混乱的根源。
提议的数据结构充满了问题,最大的问题是数据在数据对象的两个值 和 键之间混合。姓名、性别和体重都是 值 。 name
、gender
和 weight
是键。这会将您的数据更改为这种合理的形状,它也采用合理的名称,people
。
柯里化
pick
很容易实现它的目标,因为 name
、gender
和 weight
在语义上都是相邻的,即它们都是从对象中挑选的键。当数据跨值和键混合时,它会更难导航结构并给您的程序带来不必要的复杂性。
const people = [
{ name: "john", gender: "male", weight: 100 },
{ name: "amanda", gender: "female", weight: 88 },
{ name: "rachel", gender: "female", weight: 73 },
{ name: "david", gender: "male", weight: 120 }
]
// curried
const pick = (fields = []) => (from = []) =>
from.map(item => Object.fromEntries(fields.map(f => [f, item[f]])))
const nameAndGender =
pick(["name", "gender"]) // ✅ apply one argument
const nameAndWeight =
pick(["name", "weight"]) // ✅ apply one argument
console.log(nameAndGender(people))
console.log(nameAndWeight(people))
.as-console-wrapper { min-height: 100%; top: 0; }
部分申请
partial
完全足以增进您对这一点的理解。您不需要 .bind
,因为它的第一个参数与动态上下文有关,这是 object-oriented 风格的原则。
这是使用未柯里化 pick
并应用 partial
应用程序的相同演示 -
const people = [
{ name: "john", gender: "male", weight: 100 },
{ name: "amanda", gender: "female", weight: 88 },
{ name: "rachel", gender: "female", weight: 73 },
{ name: "david", gender: "male", weight: 120 }
]
// uncurried
const pick = (fields = [], from = []) =>
from.map(item => Object.fromEntries(fields.map(f => [f, item[f]])))
const partial = (f, ...a) =>
(...b) => f(...a, ...b)
const nameAndGender =
partial(pick, ["name", "gender"]) // ✅ partial application
const nameAndWeight =
partial(pick, ["name", "weight"]) // ✅ partial application
console.log(nameAndGender(people))
console.log(nameAndWeight(people))
.as-console-wrapper { min-height: 100%; top: 0; }
"是否强制更改数据结构?"
当然不会,但你很快就会运行惹上麻烦。让我们把你的练习进行到底,看看问题出在哪里。正如您所演示的,柯里化程序运行良好 -
const genderAndWeight = {
john: {male: 100},
amanda: {female: 88},
rachel: {female: 73},
david: {male: 120},
}
const getGenderOrWeightCurried = (fn) => (obj) =>
Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)]));
const funcA = (x) => Number(Object.values(x));
const funcB = (x) => Object.keys(x).toString();
const getWeight = getGenderOrWeightCurried(funcA);
const getGender = getGenderOrWeightCurried(funcB);
console.log({
weight: getWeight(genderAndWeight),
gender: getGender(genderAndWeight),
});
.as-console-wrapper { min-height: 100%; top: 0; }
您问题中的部分应用程序使用.bind
不正确。上下文 (null
) 作为第二个位置传递,但 .bind
期望此参数位于 第一个 位置 -
const getWeight2 =
getGenderOrWeightBothParams.bind(funcA, null); // ❌
const getWeight2 =
getGenderOrWeightBothParams.bind(null, funcA); // ✅
您可以用同样的方法修复 getGender2
,但让我们用 partial
来代替这个。动态上下文是一种 object-oriented 机制,在学习函数式编程基础知识时无需关注它。 partial
允许您绑定函数的参数而无需提供上下文 -
const partial = (f, ...a) =>
(...b) => f(...a, ...b)
const getGender2 =
getGenderOrWeightBothParams.bind(funcB, null); // ❌
const gender2 =
partial(getGenderOrWeightBothParams, funcB); // ✅
这为您提供了两个使用原始建议数据结构的部分应用的工作示例 -
const genderAndWeight = {
john: {male: 100},
amanda: {female: 88},
rachel: {female: 73},
david: {male: 120},
}
const partial = (f, ...a) =>
(...b) => f(...a, ...b)
const getGenderOrWeightBothParams = (fn, obj) =>
Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)]));
const funcA = (x) => Number(Object.values(x));
const funcB = (x) => Object.keys(x).toString();
const getWeight2 =
getGenderOrWeightBothParams.bind(null, funcA); // ✅ .bind
const getGender2 =
partial(getGenderOrWeightBothParams, funcB) // ✅ partial
console.log({
weight: getWeight2(genderAndWeight),
gender: getGender2(genderAndWeight),
});
.as-console-wrapper { min-height: 100%; top: 0; }
“那么问题出在哪里?”
就在这里-
const funcA = (x) => Number(Object.values(x)); // ⚠️
const funcB = (x) => Object.keys(x).toString(); // ⚠️
“但它有效!”
您知道您的 funcA
创建一个数字数组,将其转换为字符串,然后再转换回数字吗?事实上,它似乎可以正常工作的唯一原因是因为每个人都是一个具有 单个 key/value 对的对象。一旦添加更多条目,模型就会中断 -
const o1 = { female: 73 }
const o2 = { female: 73, accounting: 46000 }
const o3 = { gender: "female", weight: 73, role: "accounting", salary: 46000 }
const funcA = x => Number(Object.values(x))
console.log(funcA(o1)) // 73
console.log(funcA(o2)) // NaN
console.log(funcA(o3)) // NaN
funcB
也发生了类似的问题。您的函数似乎可以正常工作,因为单个字符串 ["foo"]
的数组在转换为字符串时将导致 "foo"
。在任何更大的阵列上尝试此操作,您将得到无法使用的结果 -
const o1 = { female: 73 }
const o2 = { female: 73, accounting: 46000 }
const o3 = { gender: "female", weight: 73, role: "accounting", salary: 46000 }
const funcB = x => Object.keys(x).toString()
console.log(funcB(o1)) // "female"
console.log(funcB(o2)) // "female,accounting"
console.log(funcB(o3)) // "gender,weight,role,salary"
当向树中添加更多数据时,funcA
和 funcB
将如何工作?
地狱再回来
我们知道 funcA
在原始数据中每个项目被调用一次。随机选择一个人,让我们看看当funcA
达到rachel
的值时会发生什么。到底有多糟糕,真的吗?
Number(Object.values(x)) x := { female: 73 }
Number(value) value := [73]
When Number is called with argument
value
, the following steps are taken:
- If
value
is present, then ✅
- Let
prim
be ? ToNumeric(value
). ✅- If Type(
prim
) is BigInt, letn
be (ℝ(prim
)). ❌- Otherwise, let
n
beprim
. ✅- Else,
- Let
n
be +0.- If NewTarget is undefined, return
n
. ✅- Let
O
be ? OrdinaryCreateFromConstructor(NewTarget, "%Number.prototype%", « [[NumberData]] »).- Set
O.[[NumberData]]
ton
.- Return
O
.
ToNumeric(value) value := [73]
The abstract operation ToNumeric takes argument
value
and returns either a normal completion containing either a Number or a BigInt, or a throw completion. It returnsvalue
converted to a Number or a BigInt. It performs the following steps when called:
- Let
primValue
be ? ToPrimitive(value
, number). ✅- If Type(
primValue
) is BigInt, returnprimValue
. ❌- Return ? ToNumber(
primValue
). ✅
ToPrimitive(input[, preferredType]) input := [73], preferredType := number
The abstract operation ToPrimitive takes argument
input
(an ECMAScript language value) and optional argumentpreferredType
(string or number) and returns either a normal completion containing an ECMAScript language value or a throw completion. It converts itsinput
argument to a non-Object type. If an object is capable of converting to more than one primitive type, it may use the optional hintpreferredType
to favour that type. It performs the following steps when called:
- If Type(
input
) is Object, then ✅
- Let
exoticToPrim
be ? GetMethod(input
, @@toPrimitive). ✅- If
exoticToPrim
is not undefined, then ❌
- If
preferredType
is not present, let hint be "default".- Else if
preferredType
is string, let hint be "string".- Else,
- Assert:
preferredType
is number.- Let hint be "number".
- Let
result
be ? Call(exoticToPrim
,input
, « hint »).- If Type(
result
) is not Object, returnresult
.- Throw a TypeError exception.
- If
preferredType
is not present, letpreferredType
be number. ❌- Return ? OrdinaryToPrimitive(
input
,preferredType
). ✅- Return
input
. ✅
OrdinaryToPrimitive(O, hint) O := [73] hint := number
The abstract operation OrdinaryToPrimitive takes arguments
O
(an Object) andhint
(string or number) and returns either a normal completion containing an ECMAScript language value or a throw completion. It performs the following steps when called:
- If
hint
is string, then ❌
- Let
methodNames
be « "toString", "valueOf" ».- Else, ✅
- Let
methodNames
be « "valueOf", "toString" ». ✅- For each element
name
ofmethodNames
, do ✅
- Let
method
be ? Get(O
,name
). ✅- If IsCallable(
method
) is true, then ✅
- Let result be ? Call(
method
,O
). ✅- If Type(result) is not Object, return result. ⚠️
- Throw a TypeError exception.
我们正在深入这里,但我们几乎已经到达底部。到标记 ⚠️ 的点,数组的 [[3.2.2]]、valueOf
将 return 数组本身,它仍然具有 Object 类型。因此循环 [[3.]] 继续 name := "toString"
O := [73] name := "toString"
- Let
method
be ? Get(O
,name
). ✅- If IsCallable(
method
) is true, then ✅
- Let result be ? Call(
method
,O
). ✅- If Type(result) is not Object, return result. ✅
OrdinaryToPrimitive(O, hint) O := [73] hint := number
Return => "73"
ToPrimitive(input[, preferredType]) input := [73], preferredType := number
Return => "73"
ToNumeric(value) value := [73]
Return => ToNumber("73")
ToNumber(argument) argument := "73"
The abstract operation ToNumber takes argument
argument
and returns either a normal completion containing a Number or a throw completion. It convertsargument
to a value of type Number according to Table 13 (below):
Argument Type Result Undefined Return NaN. Null Return +0. Boolean If argument
is true, return 1. Ifargument
is false, return +0.Number Return argument
(no conversion).String Return ! StringToNumber( argument
). ✅Symbol Throw a TypeError exception. BigInt Throw a TypeError exception. Object Apply the following steps: ... 1. Let primValue
be ? ToPrimitive(argument
, number).... 2. Return ? ToNumber( primValue
).
我们到达了 StringToNumber("73")
,现在没有继续向下 the rabbit hole 的意义了。由于您 self-inflicted 选择了错误的数据结构,这整罐蠕虫都被打开了。想知道这个人的体重吗?
const person = { name: "rachel", weight: 73 }
console.log(person.weight) // 73
没有不必要的中间数组,没有array-to-string转换,没有string-to-number转换,没有NaN的可能性,没有地狱。
阅读更多
对您正在使用的每个其他功能重复“地狱”练习。自己确定这是否真的是您想要走的路 -
功能组合
柯里化函数与另一种称为 function composition 的技术搭配得很好。当一个函数只接受一个参数而 return 是另一个参数时,您可以 compose 或 sequence 它们,有时称为“管道&uot;或“管道”。这开始证明函数式编程应用于整个系统时的效果 -
const gte = (x = 0) => (y = 0) =>
y >= x
const filter = (f = Boolean) => (a = []) =>
a.filter(f)
const prop = (k = "") => (o = {}) =>
o[k]
const pipe = (...fs) =>
x => fs.reduce((r, f) => f(r), x)
const heavyWeights =
filter(pipe(prop("weight"), gte(100)))
const people = [
{ name: "john", gender: "male", weight: 100 },
{ name: "amanda", gender: "female", weight: 88 },
{ name: "rachel", gender: "female", weight: 73 },
{ name: "david", gender: "male", weight: 120 }
]
console.log(heavyWeights(people))
.as-console-wrapper { min-height: 100%; top: 0; }
[
{
"name": "john",
"gender": "male",
"weight": 100
},
{
"name": "david",
"gender": "male",
"weight": 120
}
]
如果您觉得本节有趣,我邀请您阅读