澳门新葡萄京官网首页 2

一道面试题引发的对 JavaScript 类型转换的思考

近些日子群里有人发了上边那题:

兑现叁个函数,运算结果能够满意如下预期结果:

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

对于八个感叹的切图仔来讲,忍不住出手尝试了刹那间,看见标题首先想到的是会用到高阶函数以致 Array.prototype.reduce()

高阶函数(Higher-order
functionState of Qatar:高阶函数的意趣是它选用另一个函数作为参数。在 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(State of Qatar方法再次回到钦赐对象的原始值。

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(State of Qatar 那类参数必要 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() ,看看会生出怎么着?

澳门新葡萄京官网首页 1

能够看来,这里把大家定义的 test
函数的再一次打字与印刷了叁回,其实,这里活动调用了函数的 valueOf 方法:

澳门新葡萄京官网首页 2

咱俩改写一下 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() 方法。

后记

像阮一峰先生所说的,“炫人眼目向来不是本人创作的念头,好奇才是”。本文行乔装打扮程也是本人要好上学的三个历程,进度中自身也越过了众多吸引,所以纵然查阅了法定文书档案及大气的稿子,可是错误及脱漏照旧免不了,应接指正及给出越来越好的法子。

对此类型调换,最棒照旧看看 ECMAScript
标准,谢绝成为伸手党,本人多尝试。别的议论处有广大人提议了投机的疑团,值得一看。

到此本文甘休,倘使还会有啥样疑难依旧建议,能够多多沟通,原创作品,文笔有限,吴下阿蒙,文中若有不正之处,万望告知。

发表评论

电子邮件地址不会被公开。 必填项已用*标注