J'ai récemment trouvé une question d'entretien JS front-end Après avoir soigneusement examiné cette question d'entretien JS front-end, j'ai trouvé qu'elle était toujours très intéressante, j'aimerais donc la partager. avec toi.
Veuillez implémenter une fonction afin que le résultat de l'opération puisse répondre aux résultats attendus suivants :
add(1)(2) // 3
add(1 , 2, 3)(10) // 16
add(1)(2)(3)(4)(5) // 15
Pour un curieux Qie Pour Tuzai, je n'ai pas pu m'empêcher d'essayer Quand j'ai vu la question, la première chose à laquelle j'ai pensé était l'utilisation de fonctions d'ordre supérieur et Array.prototype.reduce()
.
Articles connexes recommandés : La collection la plus complète de questions d'entretien js en 2020 (dernière)
Fonction d'ordre supérieur : Une fonction d'ordre supérieur signifie qu'elle reçoit une autre fonction en tant que paramètre. En JavaScript, les fonctions sont des citoyens de première classe, permettant aux fonctions d'être transmises en tant que paramètres ou valeurs de retour.
J'ai obtenu la solution suivante :
function add() { var args = Array.prototype.slice.call(arguments); return function() { var arg2 = Array.prototype.slice.call(arguments); return args.concat(arg2).reduce(function(a, b){ return a + b; }); } }
Après l'avoir vérifiée, j'ai trouvé que c'était faux :
add(1)(2) // 3 add(1, 2)(3) // 6 add(1)(2)(3) // Uncaught TypeError: add(...)(...) is not a function(…)
La solution ci-dessus se trouve uniquement dans add()( ) situation Ce qui suit est correct. Lorsque les paramètres de l'opération en chaîne sont supérieurs à deux ou inférieurs à deux, le résultat ne peut pas être renvoyé.
Et c'est aussi une difficulté dans cette question. Lorsque add(), comment renvoyer à la fois une valeur et une fonction pour les appels suivants ?
Plus tard, avec les conseils d'un expert, l'une des solutions peut être obtenue en réécrivant la méthode valueOf ou la méthode toString de la fonction :
function add () { var args = Array.prototype.slice.call(arguments); var fn = function () { var arg_fn = Array.prototype.slice.call(arguments); return add.apply(null, args.concat(arg_fn)); } fn.valueOf = function () { return args.reduce(function(a, b) { return a + b; }) } return fn; }
Hmm ? Quand j’ai vu cette solution pour la première fois, j’étais confus. Parce que j'ai l'impression que fn.valueOf() n'a jamais été appelé du début à la fin, mais j'ai vérifié le résultat :
add(1) // 1 add(1,2)(3) //6 add(1)(2)(3)(4)(5) // 15
Par magie ! Ensuite, le mystère doit être dans le fn.valueOf = function() {} ci-dessus. Pourquoi est-ce ainsi ? A quel moment de la fonction cette méthode est-elle exécutée ? Écoutez-moi étape par étape.
valueOf
et toString
Jetons un bref aperçu de ces deux méthodes :
Object.prototype.valueOf()
En termes MDN, la méthode valueOf() renvoie le spécifié La valeur originale de l'objet.
JavaScript appelle la méthode valueOf() pour convertir un objet en un type primitif de valeur (numérique, chaîne et booléenne). Mais nous avons rarement besoin d'appeler cette fonction nous-mêmes. La méthode valueOf est généralement appelée automatiquement par JavaScript.
N'oubliez pas la phrase ci-dessus, ci-dessous nous expliquerons en détail ce que signifie ce que l'on appelle l'appel automatique. La méthode
Object.prototype.toString()
toString()
renvoie une chaîne représentant l'objet.
Chaque objet possède une méthode toString(), qui est automatiquement appelée lorsque l'objet est représenté sous forme de valeur texte ou lorsque l'objet est référencé d'une manière qui attend une chaîne.
N'oubliez pas ici que valueOf() et toString() s'appelleront à des occasions spécifiques.
Types primitifs
D'accord, pour préparer le terrain, comprenons d'abord les différents types primitifs de javascript. Outre l'objet et le symbole, il existe les types primitifs suivants :
Number String Boolean Undefined Null
Lorsque JavaScript effectue une comparaison ou diverses opérations, l'objet sera converti en ces types pour les opérations ultérieures. Les éléments suivants sont expliqués un par un :
Conversion de type de chaîne
<. 🎜> in Lorsqu'une opération ou un calcul nécessite une chaîne et que l'objet n'est pas une chaîne, la conversion String de l'objet sera déclenchée et le type non-string sera automatiquement converti en type String. La fonction toString est automatiquement appelée en interne par le système. Par exemple :var obj = {name: 'Coco'}; var str = '123' + obj; console.log(str); // 123[object Object]
Règles de conversion :
Si la méthode toString existe et renvoie le type d'origine, renvoie le résultat de toString. Si la méthode toString n'existe pas ou si le type renvoyé n'est pas un type primitif, appelez la méthode valueOf. Si la méthode valueOf existe et renvoie des données de type primitif, renvoyez le résultat de valueOf. Dans d'autres cas, une erreur est générée. L'exemple ci-dessus est en fait :var obj = {name: 'Coco'}; var str = '123' + obj.toString();
var arr = [1, 2]; var str = '123' + arr; console.log(str); // 1231,2
var obj = { toString: function() { console.log('调用了 obj.toString'); return {}; }, valueOf: function() { console.log('调用了 obj.valueOf') return '110'; } } alert(obj); // 调用了 obj.toString // 调用了 obj.valueOf // 110
var obj = { toString: function() { console.log('调用了 obj.toString'); return {}; }, valueOf: function() { console.log('调用了 obj.valueOf') return '110'; } } alert(obj); // 调用了 obj.toString // 调用了 obj.valueOf // 110
从结果可以看到,当 toString 不可用的时候,系统会再尝试 valueOf 方法,如果 valueOf 方法存在,并且返回原始类型(String、Number、Boolean)数据,返回valueOf的结果。
那么如果,toString 和 valueOf 返回的都不是原始类型呢?看下面这个例子:
var obj = { toString: function() { console.log('调用了 obj.toString'); return {}; }, valueOf: function() { console.log('调用了 obj.valueOf') return {}; } } alert(obj); // 调用了 obj.toString // 调用了 obj.valueOf // Uncaught TypeError: Cannot convert object to primitive value
可以发现,如果 toString 和 valueOf 方法均不可用的情况下,系统会直接返回一个错误。
在查证了 ECMAScript5 官方文档后,发现上面的描述有一点问题,Object 类型转换为 String 类型的转换规则远比上面复杂。转换规则为:1.设原始值为调用 ToPrimitive 的结果;2.返回 ToString(原始值) 。关于 ToPrimitive 和 ToString 的规则可以看看官方文档:ECMAScript5 — ToString
Number 类型转换
上面描述的是 String 类型的转换,很多时候也会发生 Number 类型的转换:
调用 Number() 函数,强制进行 Number 类型转换
调用 Math.sqrt() 这类参数需要 Number 类型的方法
obj == 1 ,进行对比的时候
obj + 1 , 进行运算的时候
与 String 类型转换相似,但是 Number 类型刚好反过来,先查询自身的 valueOf 方法,再查询自己 toString 方法:
如果 valueOf 存在,且返回原始类型数据,返回 valueOf 的结果。
如果 toString 存在,且返回原始类型数据,返回 toString 的结果。
其他情况,抛出错误。
按照上述步骤,分别尝试一下:
var obj = { valueOf: function() { console.log('调用 valueOf'); return 5; } } console.log(obj + 1); // 调用 valueOf // 6 var obj = { valueOf: function() { console.log('调用 valueOf'); return {}; }, toString: function() { console.log('调用 toString'); return 10; } } console.log(obj + 1); // 调用 valueOf // 调用 toString // 11 var obj = { valueOf: function() { console.log('调用 valueOf'); return {}; }, toString: function() { console.log('调用 toString'); return {}; } } console.log(obj + 1); // 调用 valueOf // 调用 toString // Uncaught TypeError: Cannot convert object to primitive value
Boolean 转换
什么时候会进行布尔转换呢:
布尔比较时
if(obj) , while(obj) 等判断时
简单来说,除了下述 6 个值转换结果为 false,其他全部为 true:
undefined
null
-0
0或+0
NaN
Boolean(undefined) // false Boolean(null) // false Boolean(0) // false Boolean(NaN) // false Boolean('') // false
Function 转换
好,最后回到我们一开始的题目,来讲讲函数的转换。
我们定义一个函数如下:
function test() { var a = 1; console.log(1); }
如果我们仅仅是调用 test 而不是 test() ,看看会发生什么?
可以看到,这里把我们定义的 test 函数的重新打印了一遍,其实,这里自行调用了函数的 valueOf 方法:
我们改写一下 test 函数的 valueOf 方法。
test.valueOf = function() { console.log('调用 valueOf 方法'); return 2; } test; // 输出如下: // 调用 valueOf 方法 // 2
与 Number 转换类似,如果函数的 valueOf 方法返回的不是一个原始类型,会继续找到它的 toString 方法:
test.valueOf = function() { console.log('调用 valueOf 方法'); return {}; } test.toString= function() { console.log('调用 toString 方法'); return 3; } test; // 输出如下: // 调用 valueOf 方法 // 调用 toString 方法 // 3
破题
再看回我正文开头那题的答案,正是运用了函数会自行调用 valueOf 方法这个技巧,并改写了该方法。我们稍作改变,变形如下:
function add () { console.log('进入add'); var args = Array.prototype.slice.call(arguments); var fn = function () { var arg_fn = Array.prototype.slice.call(arguments); console.log('调用fn'); return add.apply(null, args.concat(arg_fn)); } fn.valueOf = function () { console.log('调用valueOf'); return args.reduce(function(a, b) { return a + b; }) } return fn; }
当调用一次 add 的时候,实际是是返回 fn 这个 function,实际是也就是返回 fn.valueOf();
add(1); // 输出如下: // 进入add // 调用valueOf // 1 其实也就是相当于: [1].reduce(function(a, b) { return a + b; }) // 1
当链式调用两次的时候:
add(1)(2); // 输出如下: // 进入add // 调用fn // 进入add // 调用valueOf // 3
当链式调用三次的时候:
add(1)(2)(3); // 输出如下: // 进入add // 调用fn // 进入add // 调用fn // 进入add // 调用valueOf // 6
可以看到,这里其实有一种循环。只有最后一次调用才真正调用到 valueOf,而之前的操作都是合并参数,递归调用本身,由于最后一次调用返回的是一个 fn 函数,所以最终调用了函数的 fn.valueOf,并且利用了 reduce 方法对所有参数求和。
除了改写 valueOf 方法,也可以改写 toString 方法,所以,如果你喜欢,下面这样也可以:
function add () { var args = Array.prototype.slice.call(arguments); var fn = function () { var arg_fn = Array.prototype.slice.call(arguments); return add.apply(null, args.concat(arg_fn)); } fn.toString = function() { return args.reduce(function(a, b) { return a + b; }) } return fn; }
这里有个规律,如果只改写 valueOf() 或是 toString() 其中一个,会优先调用被改写了的方法,而如果两个同时改写,则会像 Number 类型转换规则一样,优先查询 valueOf() 方法,在 valueOf() 方法返回的是非原始类型的情况下再查询 toString() 方法。
后记
在尝试了更多的浏览器之后,发现了上述解法的诸多问题,在 chrome 56 55 下,结果正常。在更新到最新的 chrome57 ,控制台下,结果都会带上 function 字段,在 firefox 下,直接不生效,感觉自己可能陷入了追求某种解法而忽略了一些底层的具体规范,会在彻底弄清楚后给出另一篇文章。
对于类型转换,最好还是看看 ECMAScript 规范,拒绝成为伸手党,自己多尝试。另外评论处有很多人提出了自己的疑问,值得一看。
正如俗话所说:“炫耀从来不是我写作的动机,好奇才是”。此前端JS面试题的解读也是我自己学习的一个过程,过程中我也遇到了很多困惑,所以即便查阅了官方文档及大量的文章,但是错误及疏漏仍然在所难免,欢迎指正及给出更好的方法。
到此本文结束,如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。
相关推荐:
Qu'est-ce que JavaScript ? Comment utiliser Javascript ?
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!