一道面试题引发的对javascript类型转换的想

日前群里有人发了底这题:
实现一个函数,运算结果好满足如下预期结果:

add(1)(2) // 3
add(1, 2, 3)(10) // 16
add(1)(2)(3)(4)(5) // 15

对于一个怪的切图仔来说,忍不住动手尝试了一晃,看到题目首先想到的凡会为此到高阶函数以及 Array.prototype.reduce()

style=”font-size: 14px; font-family: verdana, geneva;”>高阶函数(Higher-order
function):高阶函数的意是它接受另一个函数作为参数。在 javascript
中,函数是千篇一律相当于国民,允许函数作为参数或者返回值传递。

取了下是解法:

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;
        });
    }
}

说明了瞬间,发现错了:

add(1)(2) // 3
add(1, 2)(3) // 6
add(1)(2)(3) // Uncaught TypeError: add(...)(...) is not a function(…)

地方的解法,只有在 add()() 情形下是天经地义的。而当链式操作的参数多于两个或少两单之上,无法赶回结果。

若是以此也是这题之一个难处所在,add()的上,如何既归一个价又赶回一个函数以供后续继续调用?

新生由此高人点,通过更写函数的 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.valueOf = function () {
        return args.reduce(function(a, b) {
            return a + b;
        })
    }

    return fn;
}

哦?第一眼睛观望是解法的下,我是懵逼的。因为自发 fn.valueOf() 从头到尾都没有给调用了,但是验证了生结果:

add(1) // 1
add(1,2)(3) //6
add(1)(2)(3)(4)(5) // 15

神奇之对了!那么玄机必然是于面的 fn.valueOf = function() {} 内了。为何会是这样为?这个办法是当函数的啊时刻执行之?且听自己平步一步道来。

 

valueOf 和 toString

先期来简单询问下这片独办法:

Object.prototype.valueOf()

用 MDN 的话语来说,valueOf()
方法返回指定对象的原始值。

JavaScript
调用 valueOf()
方法用来管对象转换成原始类型的值(数价值、字符串和布尔值)。但是咱死少用好调用此函数,valueOf
方法一般还见面叫 JavaScript 自动调用。

牢记上面立句话,下面我们会细说所谓的机动调用是呀意思。

Object.prototype.toString()

toString()
方法返回一个代表该目标的字符串。

每个对象还生一个
toString()
方法,当目标被代表为文本值时还是当因为盼字符串的法子引用对象时,该方式给机关调用。

这边先记住,valueOf()
和 toString() 在一定的场子下会自行调用。

原始类型

吓,铺垫一下,先了解下
javascript 的几栽原始类型,除去 Object 和
Symbol,有如下几栽原始类型:

  • Number
  • String
  • Boolean
  • Undefined
  • Null


JavaScript
进行对照或者各种运算的时段会把对象转换成这些品种,从而进行后续的操作,下面逐一说明:

 

String 类型转换

于有操作还是运算需要字符串而该对象又不是字符串的时,会触发该对象的
String 转换,会将非字符串的品类尝试自动转为 String
类型。系统间会自行调用 toString 函数。举个例子:

var obj = {name: 'Coco'};
var str = '123' + obj;
console.log(str);  // 123[object Object]

转换规则:

  1. 如果 toString 方法是而返回原始类型,返回 toString 的结果。
  2. 如果 toString 方法无在或者返回的非是原始类型,调用 valueOf 方法,如果 valueOf 方法存在,并且返回原始类型数据,返回 valueOf 的结果。
  3. 旁情况,抛来荒唐。

面的例证实际上是:

var obj = {name: 'Coco'};
var str = '123' + obj.toString();

其中,obj.toString() 的值为 "[object Object]"

若是数组:

var arr = [1, 2];
var str = '123' + arr;

console.log(str); // 1231,2

上面 + arr ,由于此地是独字符串加操作,后面的 arr 需要中转为一个字符串类型,所以实际上是调用了 + arr.toString() 。

唯独,我们可协调改写对象的 toStringvalueOf 方法:

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') ,obj
会自动调用自己的 obj.toString() 方法转化为原始类型,如果我们不还写她的 toString 方法,将输出 [object Object]1 ,这里我们又写了 toString ,而且回去了一个原始类型字符串 111 ,所以最终
alert 出了 1111。

方的转速规则写了,toString 方法需要有以返回原始类型,那么要回去的无是一个原始类型,则会去继续查找目标的 valueOf 方法:

脚我们尝试证明如当一个靶尝试换为字符串的历程遭到,如果 toString() 方法无可用之下,会生出啊。

夫时刻系统会再次去调动用 valueOf() 方法,下面我们改写对象的 toString 和 valueOf

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 方法均无可用之景况下,系统会一直回一个谬误。

长于
2017-03-07:在查明了 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 方法:

  1. 如果 valueOf 存在,且返回原始类型数据,返回 valueOf 的结果。
  2. 如果 toString 存在,且返回原始类型数据,返回 toString 的结果。
  3. 另外情形,抛来荒唐。

论上述手续,分别品尝一下:

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
规范,拒绝成为伸手党,自己多品尝。另外评论处起很多丁提出了自己的疑点,值得一看。

诸如阮一峰先生所说之,“炫耀从来不是自个儿撰文的念,好奇才是”。本文行文过程为是自自己学习的一个历程,过程遭到自我呢碰到了好多疑惑,所以就查阅了合法文档及大气之篇章,但是错误和疏漏仍然免不了,欢迎指正及被起又好的办法。

 

交这个本文结束,如果还有呀问题还是建议,可以多交流,原创文章,文笔有限,才疏学浅,文中若发生不正之处,万望告知。