網易前端微專業的一道js題求解?
題目有問題。
add() 返回的到底是函數還是數字?顯然你只能選擇一個。
一種勉強的方法是返回函數,且讓此函數對象的 valueOf() 返回數字結果。這樣可以做到 add() == number 的效果,但僅限於 == 而不能是 === 。這種方式在真實的代碼里是不可取的。function add(a) {
var sum = a
function f(b) {
sum += b
return f
}
f.toString = function() { return sum }
return f
}
alert(add(10)(20)(30)) //60
//看結果的時候必須用alert ,因為alert接收的必須是字元串,所以它會後台自動調用toString()方法
我覺得從代碼來看, @skyler 的代碼已經夠好了。有個破壞習慣的瑕疵是,add(x) 返回的結果會被自己的調用改變,但有些時候這正是被需要的 ,題目本身也沒規定:
var acc = add(10)(20)(30)
alert(acc) //60
alert(acc(10)) //70 這時acc的語義改變了
alert(acc) //70
不想要這個瑕疵的可以看 @欲三更的答案。 但是我會告訴你面試的時候我喜歡更簡單的代碼嗎? 摔~
有個無傷大雅的瑕疵是,按照題意toString 改成 valueOf 更好。(這也是看了其他人的答案揣測的題意)
這裡給一個 lazy evaluation 的答案,回答 @欲三更的補充問題:function add(a) {
function f(b) {
var exec = f.valueOf
f.valueOf = function() { return (exec() + b) }
return f
}
f.valueOf = function() { return a }
return f
}
add(1)(3) == 4
//true
add(1)(3)(2) == 6
//true
add(1)(3)(2)(-1) == 5
//true
add(1) == 1
//true
調用add的結果,並無副作用的答案
function add(a) {
function mid(f,b){
function r(c){
return mid(r,c)
}
r.valueOf = function(){
return f.valueOf() + b
}
return r
}
function start(b){
return mid(start,b)
}
start.valueOf = function() {return a}
return start
}
var acc = add(10)(20)(30)
acc==60 //true
acc(10)==70//true 這時acc的語義未改變
acc==60 //true
為什麼valueOf比toString好呢?貼一段v8代碼
Object -&> Primitive的過程:如果是Date類型的話,走DefaultString,否則走DefaultNumber
DefaultNumber會判斷Object有木有valueOf方法,有的話,直接調用return,沒有的話去判斷toString方法/* -------------------------------------
- - - C o n v e r s i o n s - - -
-------------------------------------
*/
// ECMA-262, section 9.1, page 30. Use null/undefined for no hint,
// (1) for number hint, and (2) for string hint.
function ToPrimitive(x, hint) {
// Fast case check.
if (IS_STRING(x)) return x;
// Normal behavior.
if (!IS_SPEC_OBJECT(x)) return x;
if (IS_SYMBOL_WRAPPER(x)) throw MakeTypeError("symbol_to_primitive", []);
if (hint == NO_HINT) hint = (IS_DATE(x)) ? STRING_HINT : NUMBER_HINT;
return (hint == NUMBER_HINT) ? %DefaultNumber(x) : %DefaultString(x);
}
// ECMA-262, section 8.6.2.6, page 28.
function DefaultNumber(x) {
if (!IS_SYMBOL_WRAPPER(x)) {
var valueOf = x.valueOf;
if (IS_SPEC_FUNCTION(valueOf)) {
var v = %_CallFunction(x, valueOf);
if (%IsPrimitive(v)) return v;
}
var toString = x.toString;
if (IS_SPEC_FUNCTION(toString)) {
var s = %_CallFunction(x, toString);
if (%IsPrimitive(s)) return s;
}
}
throw %MakeTypeError("cannot_convert_to_primitive", []);
}
如果嚴格按照題目描述,保證沒人能實現。如果 add(1)(2) 返回 3,那 add(1)(2)(3) 怎麼可能返回6?直接報錯了好嗎?
之所以實現不了,是因為 js 不能顯式指定數據類型,也不能自定義類型轉換。要是 js 能這麼寫的話,就能實現:int a = add(1)(2)(3);
最接近的結果,也不過是 add(1)(2)(3)() 返回 6 而已。具體寫法待我打開電腦。
==== 電腦打開了 ====
問題不難,但是我想寫的詳細點,把思路寫清楚。
首先咱們得明白一件事——add 函數及其返回值是無狀態的,所以不能把任何中間結果儲存在http://add.xxx或者全局變數中。那想必是要通過閉包儲存中間結果了。
我們先不考慮返回計算結果,只考慮返回函數,憑直覺寫一個最簡單的「描述」代碼:function add(a) {
return function (b) {
return a + b;
}
}
function add(a) {
return function (b) {
return add(a, b);
}
}
function add(a, b) {
var s = a + b;
return function (c) {
return add(s, c);
}
}
function add(a) {
function add_(a, b) {
var s = a + b;
return function (c) {
return add_(s, c);
}
}
return add_(0, a);
}
相當靠譜了!最後的問題就是怎麼通過一個無參數調用把結果返回出來,比如add(1)(2)() === 3。這個簡單,檢測一下參數就行了。
最終代碼如下:function add(a) {
if (typeof a === "undefined") {
return 0;
}
function add_(a, b) {
var s = a + b;
return function (c) {
if (typeof c === "undefined") {
return s;
}
return add_(s, c);
}
}
return add_(0, a);
}
console.log(add());
console.log(add(1)());
console.log(add(1)(2)(3)());
思考題:
上面的方法是每次有參數調用都把中間結果算出來,無參數調用時返回。另有一種方法是把所有的加數存下來(不使用全局變數),最後無參數調用時全部加起來返回,請問如何實現?答案:gist擴展討論:考慮一般情況:假設函數 f(a, b) 參數和返回值都是 number,那麼如何構造函數 g,使其滿足:g() === 規定值;
g(a)() === f(g(), a);
g(a)(b)() === f(a, b);
g(a)(b)(c)() === f(f(a, b), c);
……
條件?
參照上面的寫法,很容易實現:// g函數生成器,r為規定值
function generate(f, r) {
return function (a) {
if (typeof a === "undefined") {
return r;
}
function f_(a, b) {
var s = f(a, b);
return function (c) {
if (typeof c === "undefined") {
return s;
}
return f_(s, c);
}
}
return f_(r, a);
}
}
// 使用舉例
function f(a, b) {
return a * b;
}
var g = generate(f, 1, 1);
console.log(g());
console.log(g(1)());
console.log(g(1)(2)());
console.log(g(1)(2)(3)());
console.log(g(1)(2)(3)(4)());
function generate(f, r) {
function f_(a, b) {
return curry(f_, f(a, b));
}
return curry(f_, r);
}
generate函數完整代碼:gist
所以,為啥有人會提到柯里化呢?就是上面的原因,這段代碼的核心變換抽取出來,就是柯里化的應用。注意其中的 f_ 函數,它是一個不變的結構,它能把一個二目運算函數轉化為一個連續運算函數。這道題目前幾天在segmentfault上有過討論,其實是函數的柯里化問題:
Javascript 連續調用單參函數實現任意參函數關於柯里化可以參考這篇文章:函數式JavaScript(4):函數柯里化沒看答案自己試著寫了下,最還是終寫出來了(本來想過來裝逼一下,我早該想到知乎上牛逼的人很多,還是匿了吧):
function add(num) {
var result = num;
function plus(num) {
result = result + num;
return plus;
}
plus.toString = function() {
return result;
};
return plus;
}
alert("add(20)(20) = " + add(20)(20));
alert("add(10)(20)(50) = " + add(10)(20)(50));
alert("add(10)(20)(50)(100) = " + add(10)(20)(50)(100));
// 除此之外,還可以
var num = add(20)/2;
alert(num); // 10
因為返回的結果可能是計算的結果,也可能是一個函數,所以利用對象toString來自動識別使用場景。。
修改了下,把計算結果掛到函數上只能用 == 比較,調用==做了一個隱式子轉換,調用了valueOf
function add(arg){
add.r = arg + add.r||0
add.valueOf=function(){
var t=add.r
add.r=0
return t
}
return add
}
add(10)(2) == 12
add(1)(2)(3) == 6
Curry化技術是一種通過把多個參數填充到函數體中,實現將函數轉換為一個新的經過簡化的(使之接受的參數更少)函數的技術.
挺有意思的,我來寫一個function curry(fn){
var value = undefined;
var callback = function(next){
value = typeof value === "undefined" ? next : fn.apply(null,[value,next]);
return callback;
}
callback.valueOf = callback.toString = function(){
return value;
}
return callback
}
//加
function add(x,y){
return x + y
}
//減
function minus(x,y){
return x -y
}
//乘
function time(x,y){
return x * y;
}
//除
function divide(x,y){
return x / y;
}
curry(add)(2)(3)(4)(5)(6) //2+3+4+5+6=20
curry(minus)(2)(3)(4)(5)(6) //2-3-4-5-6=-16
curry(time)(2)(3)(4)(5)(6) //2*3*4*5*6=720
curry(divide)(2)(3)(4)(5)(6) //0.00555555...
我覺得有可能是面試在試探你能否對權威提出質疑。要麼是你記錯題目了。
柯里化的js實現
function add(num) {
var value = 0;
value += num;
function f(num) {
value += num;
return f;
}
f.valueOf = function () {
return value;
}
f.toString = function () {
return value.toString();
}
return f;
}
add(2); //2
add(2)(3); //5
add(2)(1)(3); //6
var add = function (digit) {
var sum = 0;
var t = function (digit2) {
sum+=digit2;
console.log(sum);
return arguments.callee;
}
t(digit);
return t;
}
// 貌似不太符合題主的要求
// 輸出:
// add(10) 10
// add(10)(10) 10,20
// add(10)(20)(30) 10,30,60
想法和@欲三更一樣, 沒法做到 add(1)(2) === 3 又 add(1)(2)(3) === 6, 那麼,
- 要麼退而求 add(1)(2) == 3 或 add(1)(2).toString() === "3" (即用 valueOf 或 toString),
- 要麼使 add(1)(2)() === 3.
用 ES6 來湊個熱鬧.
前一種, valueOf 和 toString:let add = (n = 0) =&> {
let ret = (m = 0) =&> add(n + m);
ret.toString = () =&> n.toString();
ret.valueOf = () =&> n;
return ret;
};
let inc = (a, b) =&> a + b,
sum = (...numbers) =&> numbers.reduce(inc);
let add = (...numbersOld) =&> {
let n = sum(...numbersOld);
return (...numbers) =&> numbers.length === 0 ? n : add(n + sum(...numbers));
};
let curry = (f, ...h) =&> (...t) =&> f(...h, ...t);
用法舉例:
let inc = (a, b) =&> a + b,
inc1 = curry(inc, 1);
起到的效果是 curry(f, ...h)(...t) === f(...h, ...t), 和這裡情況並不一樣.
丁磊只會養豬 ——玉伯
推薦閱讀:
※JS如何跨域操作DOM?
※學習js看書籍好還是上機直接敲代碼好?
※前端在什麼情況下應該跳槽?
※怎麼才能在四個月內把web前端學好學深入?
※如何看待Apache再次接受阿里開源產品捐贈 移動開發框架Weex進入孵化
TAG:前端開發 | JavaScript | 閉包 |