您的位置:首页 > Web前端 > JavaScript

通过JavaScript 柯里化求加法运算引发的对JavaScript类型转换的思考

2017-02-13 10:49 686 查看
建议先看看下面的这两篇文章,便于本文的理解。

http://blog.csdn.net/u010533180/article/details/54598283

http://blog.csdn.net/u010533180/article/details/54427200

各大JavaScript 面试题基本都有柯里化方面的面试题,比如网易就有如下的面试题:

实现一个函数,运算结果可以满足如下预期结果:

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


看到这样的面试题,首先就想到了使用加法运算,如下:

function add(m) {
var res = m;
function plus(n) {
res += n;
return plus;
}
return plus;
};


经过验证发现输出的结果都是plus里面的定义。如下:

function plus(n) {
res += n;
return plus;
}


后来经过查询各方面资料,可以重写toString()和valueOf()方法得到最终的结果。

function add(m) {
var res = m;
function plus(n) { res += n; return plus; }
plus.valueOf= function () {
return res;
};
return plus;
};


valueOf 和 toString

先来简单了解下这两个方法:

Object.prototype.valueOf()

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

JavaScript 调用 valueOf() 方法用来把对象转换成原始类型的值(数值、字符串和布尔值)。但是我们很少需要自己调用此函数,valueOf 方法一般都会被 JavaScript 自动调用。

记住上面这句话,下面我们会细说所谓的自动调用是什么意思。

Object.prototype.toString()

toString() 方法返回一个表示该对象的字符串。

每个对象都有一个 toString() 方法,当对象被表示为文本值时或者当以期望字符串的方式引用对象时,该方法被自动调用。

先了解下 javascript 的几种原始类型,除去 Object 和 Symbol,有如下几种原始类型:

Number
String
Boolean
Undefined
Null


在 JavaScript 进行对比或者各种运算的时候会把对象转换成这些类型,下面逐一说明:

String 类型转换

在某个操作或者运算需要字符串而该对象又不是字符串的时候,会触发该对象的 String 转换,会将非字符串的类型尝试自动转为 String 类型。系统内部会自动调用 toString 函数。举个例子:

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


转换规则:

如果 toString 方法存在并且返回原始类型,返回 toString 的结果。

如果 toString 方法不存在或者返回的不是原始类型,调用 valueOf 方法,如果 valueOf 方法存在,并且返回原始类型数据,返回 valueOf 的结果。

其他情况,抛出错误。

上面的例子实际上是:

var obj = {name: 'huluwa'};
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() 。

但是,我们可以自己改写对象的 toString,valueOf 方法:

var objOverWrite = {
toString: function() {
console.log('调用了 objOverWrite .toString');
return {};
},
valueOf: function() {
console.log('调用了objOverWrite .valueOf')
return '0';
}
}

alert(objOverWrite);//注意这里是alert
// 调用了 objOverWrite.toString
// 调用了 objOverWrite.valueOf
// 0

var objOverWrite = {
toString: function() {
console.log('调用了 objOverWrite .toString');
return {};
},
valueOf: function() {
console.log('调用了objOverWrite .valueOf')
return '0';
}
}

console.log(objOverWrite+1);//这里使用了加法运算,如果直接输出objOverWrite 咋把定义直接输出
调用了objOverWrite .valueOf
01


上面 alert(objOverWrite) ,objOverWrite会自动调用自己的 obj.toString() 方法转化为原始类型,如果我们不重写它的 toString 方法,将输出 [object Object] ,这里我们重写了 toString ,而且返回了一个原始类型字符串 0 ,所以最终 alert 出了 0。

上面的转化规则写了,toString 方法需要存在并且返回原始类型,那么如果返回的不是一个原始类型,则会去继续寻找对象的 valueOf 方法,如果valueOf返回也不是原始类型,则直接抛出错误。

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

console.log(obj + 1);
// 调用 valueOf
// 6


var obj = {
valueOf: function() {
console.log('调用 valueOf');
return {};
},
toString: function() {
console.log('调用 toString');
return 0;
}
}

console.log(obj + 1);
// 调用 valueOf
// 调用 toString
// 1


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 fun() {
var a = 1;
console.log(1);
}


如果我们仅仅是调用 fun而不是 fun() ,看看会发生什么?

function fun() {
var n = 1;
console.log(1);
}
fun
输出结果为:
function fun() {
var n = 1;
console.log(1);
}


试试直接输出fun.valueOf()

function fun() {
var n = 1;
console.log(1);
}
fun.valueOf();

输出结果为:
function fun() {
var n = 1;
console.log(1);
}


发现直接调用fun和调用fun.valueOf()方法,输出结果是一样的。现在让我们改写一下 fun函数的 valueOf 方法。

fun.valueOf = function() {
console.log('调用 valueOf 方法');
return 2;
}

fun;
// 输出如下:
// 调用 valueOf 方法
// 2


与 Number 转换类似,如果函数的 valueOf 方法返回的不是一个原始类型,会继续找到它的 toString 方法:

fun.valueOf = function() {
console.log('调用 valueOf 方法');
return {};
}

fun.toString= function() {
console.log('调用 toString 方法');
return 3;
}

fun;
// 输出如下:
// 调用 valueOf 方法
// 调用 toString 方法
// 3


现在让我们看看之前提到的柯里化加法运算:

function add(m) {
console.log("进入add方法");
var res = m;
function plus(n) {
console.log("进入plus方法");
res += n;
return plus;
}
plus.valueOf= function () {
console.log("valueOf方法");
return res;
};
plus.toString= function () {
console.log("toString方法");
return res;
};
return plus;
};


当调用一次 add 的时候,实际是是返回 plus 这个 function,实际是也就是返回 plus .valueOf();

add(1);
进入add方法
valueOf方法
function 1


当链式调用两次的时候:

add(1)(2);
进入add方法
进入plus方法
valueOf方法
function 3


当链式调用三次的时候:

add(1)(2)(3);
进入add方法
进入plus方法
进入plus方法
valueOf方法
function 6


我们通过谷歌浏览器输出的最后一条数据看到最终的结果前面有个function 代表是里面的plus方法返回的,但是我们重写了valueOf方法,则会调用这个valueOf 返回对应的原始值。如果没有重写valueOf或者toString()方法,则直接输出plus的定义了。

这里有个规律,如果只改写 valueOf() 或是 toString() 其中一个,会优先调用被改写了的方法,而如果两个同时改写,则会像 Number 类型转换规则一样,优先查询 valueOf() 方法,在 valueOf() 方法返回的是非原始类型的情况下再查询 toString() 方法。

建议读者可以仔细阅读下面这篇文章,讲的很详细。

http://www.cnblogs.com/coco1s/p/6509141.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息