这次我们一起来研究下js中toString、valueOf方法的实现机制及意义,以便在未来的工作中能够更好的对其进行应用。
首先还是做些准备工作吧:
c = console.log;
【1. 最常见也是最好理解的应用场景:值类型数据toString及valueOf方法的使用】
var str = "hello", n = 123, bool = true;
c(typeof(str.toString())+"_"+str.toString())
c(typeof(n.toString())+"_"+n.toString())
c(typeof(bool.toString())+"_"+bool.toString())
分别输出 “string_hello”、“string_123”、“string_true”
可见,toString方法对于值类型数据使用而言,其效果相当于类型转换,将原类型强转为字符串,这一特点符合该方法名“toString”,所以理解上问题不大
再来看下valueOf吧:
c(typeof(str.valueOf())+"_"+str.valueOf())
c(typeof(n.valueOf())+"_"+n.valueOf())
c(typeof(bool.valueOf())+"_"+bool.valueOf())
分别输出 “string_hello”、“number_123”、“boolean_true”
可见,valueOf方法对于值类型数据使用而言,其效果就相当于返回原数据,用代码表示即:
c(str.valueOf() === str)
c(n.valueOf() === n)
c(bool.valueOf() === bool)
结果全部为true,再次验证了我们之前的判断
小结:
1. toString方法对于值类型数据使用而言,其效果相当于类型转换,将原类型强转为字符串;
2. valueOf方法对于值类型数据使用而言,其效果就相当于返回原数据;
【2. 对象类型数据实现toString及valueOf方法】
在js中,object实现了toString方法,因此得到了全面继承,那么默认输出下object.toString\valueOf会发生什么?
var obj = {};
c(obj.toString());
c(obj.valueOf());
分别输出 “[object object]” 以及 对象本身
看来valueOf的表现还是一如既往,其效果对于复合对象类型还是相当于返回原数据:
c(obj.valueOf() === obj);就是这么个意思
不过 toString 方法却强转输出了“[object object]”这么个玩意儿;
再试试alert输出下obj对象呢:
alert(obj);
居然也返回了[object object],貌似和刚才执行toString方法的结果一致,那么不妨大胆猜测下,在alert输出obj时,其“偷偷”执行了toString方法
再来测试几个实例:
c(obj + "hello");
c(obj + 22);
分别输出了 “[object Object]hello”、“[object Object]22”,看来不仅是alert方法,在某种条件下,复合对象类型会自动执行toString方法,进行类型转换,以实现表达式计算
前面提到过,js中的任意对象都继承并实现了toString方法,那么我们重写下试试呢:
obj.toString = function(){
return 22;
}
c(obj + 3);
c(obj + "3");
分别输出 25,"223",再次应证了复合对象类型会自动执行toString方法,那么我们再来重写下valueOf方法试试呢:
obj.valueOf = function(){
return 12;
}
此时的obj对象,toString和valueOf方法都已被重写
c(obj + 3);
c(obj + "3");
分别输出 15,"223",看来在执行数字相加表达式时obj优先自动调用的是valueOf方法,不难看出当复合对象类型在和基础值类型进行表达式操作时,会基于“场景”自动调用toString或是valueOf方法,以最为恰当的方式,自动完成计算,这点其实也是javascript弱类型语言灵活性的一个体现
再来做个测试:
c(obj == "12");
c(obj === 12);
分别输出 true、false,貌似很奇怪,实际上是因为全等表达式会比较数据类型,obj不会进行隐式转换,即不执行toString或valueOf方法而直接参与比较计算,由于两者数据类型不同,所以返回false;
小结:
1. 复合对象类型在和基础值类型进行表达式操作时,会基于“场景”自动调用toString或是valueOf方法,以最为"恰当"的方式,自动完成表达式计算
2. 全等表达式会比较数据类型,复合对象类型不会进行隐式转换,即不执行toString或valueOf方法而直接参与比较计算
【3. 深究toString的实现机制】
接下来我们来看看Ecma5对toString内置方法的定义节选:
When the toString method is called, the following steps are taken:
If the this value is undefined, return “[object Undefined]“.
If the this value is null, return “[object Null]“.
...
Return the String value that is the result of concatenating the three Strings “[object ", class, and "]“.
大体翻译如下:
当调用toString方法时,下列步骤会被执行:
如果this未定义时,返回“[object Undefined]”
如果this为null时,返回“[object Null]”
...
返回三个字符串的拼接字符:"[object",class,"]"
*/
这也就解释了上面我们对obj进行输出的效果:"[object" + Object + "]" = "[object Object]"
通过官方说明,可见toString方法实际是返回this的类型,再通过特殊字符串以返回:
c(Object.prototype.toString());
this未定义时,返回“[object Undefined]
c(Object.prototype.toString.call(null));
this为null时,返回“[object Null]
var arr = [1,2];
c(Object.prototype.toString.call(arr));
this为数组,返回“[object Array]
小结:
1. toString方法的内置算法实际是获取this的底层“类名”,再通过特殊字符串以返回
【4. jquery是如何利用toString实现数据类型判断的】
开启jquery源码,查看type()相关实现流程:
var class2type = {}, core_toString = class2type.toString;
type: function (obj) {
if(obj == null) return String(obj);
return typeof obj === "object" || typeof obj === "function" ? class2type[core_toString.call(obj)] || "object" : typeof obj;
}
可见其方法流程:传入参数 -> 空值:直接返回null -> 值类型:直接通过typeof返回 -> 复合对象类型:利用obj.toString.call()封装返回
小结:
1. $.type(obj)方法可返回对象的数据类型,且比typeof更加细化,核心在于算法中巧妙应用了toString内置算法
|