Ich habe kürzlich eine Front-End-JS-Interviewfrage gefunden. Nachdem ich diese Front-End-JS-Interviewfrage sorgfältig geprüft hatte, stellte ich fest, dass sie immer noch sehr interessant ist, daher möchte ich sie teilen mit dir.
Bitte implementieren Sie eine Funktion, damit das Operationsergebnis die folgenden erwarteten Ergebnisse erfüllen kann:
add(1)(2) // 3
add(1 , 2, 3)(10) // 16
add(1)(2)(3)(4)(5) // 15
Für a neugierig Qie Für Tuzai konnte ich nicht anders, als es auszuprobieren. Als ich die Frage sah, dachte ich als erstes an die Verwendung von Funktionen höherer Ordnung und Array.prototype.reduce()
.
Empfohlene verwandte Artikel: Die umfassendste Sammlung von js-Interviewfragen im Jahr 2020 (aktuell)
Funktion höherer Ordnung: Eine Funktion höherer Ordnung bedeutet, dass sie eine andere Funktion als Parameter erhält. In JavaScript sind Funktionen erstklassige Bürger, die die Übergabe von Funktionen als Parameter oder Rückgabewerte ermöglichen.
Habe die folgende Lösung erhalten:
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; }); } }
Nachdem ich sie überprüft hatte, stellte ich fest, dass sie falsch war:
add(1)(2) // 3 add(1, 2)(3) // 6 add(1)(2)(3) // Uncaught TypeError: add(...)(...) is not a function(…)
Die obige Lösung ist nur in add()( ) Situation Folgendes ist richtig. Wenn die Kettenoperation mehr oder weniger als zwei Parameter hat, kann das Ergebnis nicht zurückgegeben werden.
Und das ist auch eine Schwierigkeit bei dieser Frage: Wie kann man bei add() sowohl einen Wert als auch eine Funktion für nachfolgende Aufrufe zurückgeben?
Später kann unter Anleitung eines Experten eine der Lösungen erhalten werden, indem die valueOf-Methode oder die toString-Methode der Funktion neu geschrieben wird:
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? Als ich diese Lösung zum ersten Mal sah, war ich verwirrt. Weil ich das Gefühl habe, dass fn.valueOf() nie von Anfang bis Ende aufgerufen wurde, aber ich habe das Ergebnis überprüft:
add(1) // 1 add(1,2)(3) //6 add(1)(2)(3)(4)(5) // 15
Magisch richtig! Dann muss das Rätsel in fn.valueOf = function() {} oben liegen. Warum ist das so? An welcher Stelle der Funktion wird diese Methode ausgeführt? Hören Sie mir einfach Schritt für Schritt zu.
valueOf
und toString
Werfen wir einen kurzen Blick auf diese beiden Methoden:
Object.prototype.valueOf()
In MDN-Begriffen gibt die Methode valueOf() den angegebenen Wert zurück Der ursprüngliche Wert des Objekts.
JavaScript ruft die Methode valueOf() auf, um ein Objekt in einen primitiven Werttyp (numerisch, Zeichenfolge und boolesch) zu konvertieren. Aber wir müssen diese Funktion selten selbst aufrufen. Die valueOf-Methode wird normalerweise automatisch von JavaScript aufgerufen.
Denken Sie an den obigen Satz, im Folgenden erklären wir Ihnen ausführlich, was der sogenannte automatische Anruf bedeutet. Die Methode
Object.prototype.toString()
toString()
gibt eine Zeichenfolge zurück, die das Objekt darstellt.
Jedes Objekt verfügt über eine toString()-Methode, die automatisch aufgerufen wird, wenn das Objekt als Textwert dargestellt wird oder wenn auf das Objekt auf eine Weise verwiesen wird, die eine Zeichenfolge erwartet.
Denken Sie hier daran, dass valueOf() und toString() sich bei bestimmten Gelegenheiten selbst aufrufen.
Primitive Typen
Okay, um die Bühne zu bereiten, lassen Sie uns zunächst die verschiedenen primitiven Typen von Javascript verstehen. Abgesehen von Objekt und Symbol gibt es die folgenden primitiven Typen:
Number String Boolean Undefined Null
Wenn JavaScript einen Vergleich oder verschiedene Vorgänge durchführt, wird das Objekt für nachfolgende Vorgänge in diese Typen konvertiert. Folgendes wird nacheinander erklärt:
String-Typ-Konvertierung
in Wenn eine Operation oder Berechnung eine Zeichenfolge erfordert und das Objekt keine Zeichenfolge ist, wird die String-Konvertierung des Objekts ausgelöst und der Nicht-String-Typ wird automatisch in den String-Typ konvertiert. Die toString-Funktion wird intern automatisch vom System aufgerufen. Beispiel:
var obj = {name: 'Coco'}; var str = '123' + obj; console.log(str); // 123[object Object]
Konvertierungsregeln:
Wenn die toString-Methode vorhanden ist und den ursprünglichen Typ zurückgibt, geben Sie das Ergebnis von toString zurück.
Wenn die toString-Methode nicht vorhanden ist oder der zurückgegebene Typ kein primitiver Typ ist, rufen Sie die valueOf-Methode auf. Wenn die valueOf-Methode vorhanden ist und Daten vom primitiven Typ zurückgibt, geben Sie das Ergebnis von valueOf zurück.
In anderen Fällen wird ein Fehler ausgegeben.
Das obige Beispiel ist tatsächlich:
var obj = {name: 'Coco'}; var str = '123' + obj.toString();
Unter diesen ist der Wert von obj.toString() „[object Object]“.
Angenommen, es handelt sich um ein Array:
var arr = [1, 2]; var str = '123' + arr; console.log(str); // 1231,2
+ arr oben. Da es sich um eine String-Additionsoperation handelt, muss das folgende arr in einen String-Typ konvertiert werden, also ist + arr tatsächlich aufgerufen. toString() .
Wir können jedoch die toString- und valueOf-Methoden des Objekts selbst umschreiben:
var obj = { toString: function() { console.log('调用了 obj.toString'); return {}; }, valueOf: function() { console.log('调用了 obj.valueOf') return '110'; } } alert(obj); // 调用了 obj.toString // 调用了 obj.valueOf // 110
Alert(obj + '1') oben, obj ruft automatisch sein eigenes obj.toString( ) Methode In primitiven Typ konvertieren. Wenn wir ihre toString-Methode nicht überschreiben, wird [Objekt Objekt]1 ausgegeben. Hier überschreiben wir toString und geben eine Zeichenfolge vom primitiven Typ 111 zurück, sodass die endgültige Warnung 1111 ist.
Die obigen Konvertierungsregeln sind geschrieben, die toString-Methode muss vorhanden sein und den ursprünglichen Typ zurückgeben. Wenn der zurückgegebene Typ kein primitiver Typ ist, sucht er weiterhin nach der valueOf-Methode des Objekts:
Versuchen wir es als nächstes. Demonstriert, was passiert, wenn die toString()-Methode nicht verfügbar ist, während ein Objekt versucht, in einen String konvertiert zu werden.
Zu diesem Zeitpunkt ruft das System die valueOf()-Methode erneut auf. Als nächstes schreiben wir den toString und valueOf des Objekts neu:
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面试题的解读也是我自己学习的一个过程,过程中我也遇到了很多困惑,所以即便查阅了官方文档及大量的文章,但是错误及疏漏仍然在所难免,欢迎指正及给出更好的方法。
到此本文结束,如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。
相关推荐:
Was ist JavaScript? Wie verwende ich JavaScript?
Das obige ist der detaillierte Inhalt vonFragen zum Front-End-JS-Interview. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!