JavaScript中toString()和valueOf()

编辑推荐: 掘金是一个高质量的技术社区,从 CSS 到 Vue.js,性能优化到开源类库,让你不错过前端开发的每一个技术干货。 点击链接查看最新前端内容,或到各大应用市场搜索「 掘金」下载APP,技术干货尽在掌握中。

在《JavaScript中数据类型转换》一文中主要学习了JavaScript中的数据类型、数据类型检测和数据类型转换。从这篇文章中了解到了通过String()toString()可以将值转换为字符串。那么在JavaScript中还有很多有意思的东西,也是让我这样的生手感到困惑的东西。比如String()new String()有何不同,又比如这篇文章的标题toString()valueOf()又有何不同。

String() vs toString()

通过前面的学习,知道String()toString()都可以转换为字符串类型,但这两者之间还是有所区别的。

在JavaScript中,可以将JavaScript中任何数据类型转换为字符串。

console.log(String(123), typeof String(123));
console.log(String('123'), typeof String('123'));
console.log(String(function fn(){return 1}), typeof String(function fn(){return 1}));
console.log(String([1,2,3]), typeof String([1,2,3]));
console.log(String({a: 1}), typeof String({a: 1}));
console.log(String(null), typeof String(null));
console.log(String(undefined), typeof String(undefined));
console.log(String(new Date()), typeof String(new Date()));
console.log(String(/^(.)*$/), typeof String(/^(.)*$/));

在JavaScript中,每个数据类型都有一个.toString()的方法:

在JavaScript中除了nullundefined之外的任何数据类型都可以通过.toString()方法转换成字符串。比如:

console.log('=========数组:array.toString()========')
var months = ['Jan', 'Feb', 'Mar'];
console.log(months.toString(), typeof months.toString());

console.log('=========布尔值:boolean.toString()========')
var bool = true;
console.log(bool.toString(), typeof bool.toString());

console.log('=========日期:date.toString()========')
var date = new Date();
console.log(date.toString(), typeof date.toString());

console.log('=========函数:function.toString()========')
var fn = function(a, b) {return a + b};
console.log(fn.toString(), typeof fn.toString());

console.log('=========数值:number.toString()========')
var count = 10;
console.log(count.toString(), typeof count.toString());

console.log('=========对象:object.toString()========')
var obj = {a: 123}
console.log(obj.toString(), typeof obj.toString());

console.log('=========正则:regExp.toString()========')
var reg = /^(.)*$/;
console.log(reg.toString(), typeof reg.toString())

console.log('=========字符串:string.toString()========')
var str = '123abc'
console.log(str.toString(), typeof str.toString());

console.log('=========symbol.toString()========')
var sym = Symbol('desc');
console.log(sym.toString(), typeof sym.toString())

前面也提到过,nullundefined值没有toString()。因此在对这两个值进行了toString()后,如果变量为null或者undefined的时候就会报错。

多数情况下,调用toString()方法不必传递参数。但是,在调用数值的number.toString()方法时,可以传递一个参数,输出数值的基数。默认情况下,number.toString()方法以十进制格式返回数值的字符串表示。而通过传递基数,number.toString()可以输出以二进制、八进制、十六进制,乃至其他任意有效进制格式表示的字符串。

var count = 10;

console.log(count.toString());   // => 10
console.log(count.toString(2));  // => 1010
console.log(count.toString(8));  // => 12
console.log(count.toString(10)); // => 10
console.log(count.toString(16)); // => a

通过这个例子可以看出,通过指定基数,number.toString()方法会改变输出的值。而数值10根据基数的不同,可以在输出时被转换为不同的数值格式。注意,默认的(没有参数的)输出值与指定基数10时的输出值相同。

在JavaScript中,如果不知道要转换的值是不是nullundefined的情况下,建议使用String(),因为这个函数能够将任何类型的值转换为字符串。如果值有toString()方法,则调用该方法(没有参数)并返回相应的结果。

toString()String()的主要区别就在于String()还能转换nullundefined值,也可以说String()toString()增强版,在开发中直接使用String()似乎更好,这样能避免潜在的转换风险。

String() vs new String()

在JavaScript中有各种内建类型,通常称为原生类型,比如String()就是一种原生类型。原生类型实际上是内建函数。

JavaScript的String()看起来像是曾经用来创建字符串的String(...)构造器。在很多时候可以看到这样的代码:

var str = new String('W3cplus');
console.log(str.toString()); // => 'W3cplus'

这些原生类型可以被用作一个原生类型的构造器。但是被构建出来的东西和我们想象的还是有所不同:

var str = 'W3cplus';
console.log(String(str), typeof String(str));
console.log(str.toString(), typeof str.toString());
console.log(new String(str), typeof new String(str));

上面的示例很明显看出来,它们的不同之处。String()toString()转出来的是string,但new String()转换出来的是一个object。而这个对象是一个对基本类型值str的包装器对象。

var a = new String('w3cplus');
typeof a; // => object
a instanceof String; // => true
Object.prototype.toString.call(a); // => [object String]

重点是:new String('w3cplus')'w3cplus'创建了一个字符串包装器对象,而不仅是基本类型值w3cplus本身:

var a = 'w3cplus';

String(a) === a.toString();     // => true
String(a) === new String(a);    // => false
a.toString() === new String(a); // => false

String作为普通函数时会产生一个字符串(一个原始值)。作为构造函数时会产生一个String对象的实例.后者在JavaScript中很少用到,所以基本上你可以忽略掉String作为构造函数的用法,但一定要记得它是个转换函数.

另外有趣的是:

var a = 'w3cplus';
console.log(new String(a));
console.log(typeof new String(a));
console.log(new String(a).toString());
console.log(String(new String(a)));

从上面的示例中,使用toString()String()将字符串对象转换为non-object字符串。可能很多人都从没有想过调用String.toString()可能是有用的。从这个示例中,也让初学者感到困惑:为什么在JavaScript中有两种字符串?

JavaScript中有两种类型的字符串:字面字符串String对象。他们的行为有所不同。两者之间的主要区别在于你可以向String对象添加其他方法和属性。

来看个简单的示例:

var strObj = new String('object mode');
strObj.string_mode = 'object';
strObj.get_string_mode = function () {
    return this.string_mode
}

str = strObj.toString(); // => 'object_mode'

这个时候,字符串文字只是临时转换为String对象来执行任何核心方法。 同样的概念也适用于其他数据类型

另外,new String()转换出来的是一个String对象。使用typeof来判断后,其结果为object,其实这个时候它就打上了一个内部的标签属性[[Class]]。这个也常常称为内部属性。这个内部属性不能直接被访问,但通常可以间接地通过在这个值上借用默认的Object.prototype.toString(...)方法调用来展示:

console.log(Object.prototype.toString.call([1,2,3]))  // => [object Array]
console.log(Object.prototype.toString.call(true))     // => [object Boolean]
console.log(Object.prototype.toString.call(42))       // => [object Number]

所以对于上面的示例而言,数组内部的[[Class]]值是'Array',布尔值它是'Boolean',数值是'Number'。在大多数情况下,这个内部的[[Class]]值对应于关联这个值的内建的原生类型构造器,但事实却不总是这样。

基本类型呢?首先,nullundefined

Object.prototype.toString.call( null );         // => "[object Null]"
Object.prototype.toString.call( undefined );    // => "[object Undefined]"

你会注意到,不存在Null()Undefined()原生类型构造器,但不管怎样"Null""Undefined"是被暴露出来的内部[[Class]]值。

但是对于像stringnumber,和boolean这样的简单基本类型,实际上会启动另一种行为,自动包装成一个封装对象,这种行为通常也被称为封箱

Object.prototype.toString.call( "abc" );    // => "[object String]"
Object.prototype.toString.call( 42 );       // => "[object Number]"
Object.prototype.toString.call( true );     // => "[object Boolean]"

另外在JavaScript中,基本类型没有 .length.toString()等这样的属性和方法,需要封装对象才可使用,此时JavaScript会为基本类型值自动包装成一个封装对象,浏览器对此也进行了优化,因此应优先考虑用基本类型值,而不是new String("abc")新对象。

var str = 'w3cplus';
str.length; // => 7

一般来说,基本上没有理由直接使用对象形式。让封箱在需要的地方隐式地发生会更好。换句话说,永远也不要做new String("abc")new Number(42)这样的事情——应当总是偏向于使用基本类型字面量"abc"42

在实际的使用中,一般不推荐直接使用封装对象,如果想要自行封装基本类型值,可以使用Object(...)函数,而不带new关键字

var a = 'w3cplus';
var b = new String(a);
var c = Object(a);

console.log(typeof a); // => string
console.log(typeof b); // => object
console.log(typeof c); // => object

console.log(a instanceof String); // => false
console.log(b instanceof String); // => true
console.log(c instanceof String); // => true

console.log(Object.prototype.toString.call(a)); // => [object String]
console.log(Object.prototype.toString.call(b)); // => [object String]
console.log(Object.prototype.toString.call(c)); // => [object String]

开箱 valueOf()

有封装就有开箱,如果你有一个包装对象,但你又想取出底层的基本类型值,这个时候就可以使用valueOf()方法。

var a = new String( "abc" );
var b = new Number( 42 );
var c = new Boolean( true );

a.valueOf(); // => "abc"
b.valueOf(); // => 42
c.valueOf(); // => true

在JavaScript中,valueOf()函数算是一个非常少用的内建函数,但这个valueOf()却十分重要。许多封装的对象都有这个valueOf()函数,比如Boolean.valueOf()Number.valueOf()String.valueOf()等。

console.log('=====boolean.valueOf()=====');
var bool = new Boolean();
console.log(typeof bool);
console.log(bool.valueOf());

console.log('=====number.valueOf()=====')
var count = new Number(100);
console.log(typeof count);
console.log(count.valueOf());

console.log('=====string.valueOf()=====')
var str = new String('w3cplus');
console.log(typeof str);
console.log(str.valueOf());

console.log('=====date.valueOf()=====')
var date = new Date()
console.log(typeof date);
console.log(date.valueOf());

console.log('=====object.valueOf()=====')
var obj = new Object();
obj.name = 'w3cplus';
obj.age = 7;
console.log(obj);
console.log(typeof obj);
console.log(obj.valueOf());

console.log('=====regExp.valueOf()=====')
var reg = new RegExp('^a*b+', 'g');
console.log(typeof reg);
console.log(reg.valueOf());

console.log('=====function.valueOf()=====')
var fn = function (a, b) {return a + b};
console.log(typeof fn);
console.log(fn.valueOf());

console.log('=====Symbol.valueOf()=====')
var sym = Symbol('foo');
console.log(typeof sym);
console.log(sym.valueOf());

前面也提到过了,nullundefined是属于特殊用途的,他们并没有new Null()new Undefined()这样的封装器,也就是说他们是没有对应的封装对象。从而没有对应的valueOf()

在JavaScript中,当我们比较两个变量时,使用==比较的是两个变量的值,那么像下面这样其值是true:

var str1 = 'w3cplus';
var str2 = new String('w3cplus');
typeof(str1); // => string
typeof(str2); // => object
str1 == str2  // => true

这个结果令我感到困惑,str1是一个String类型,而str2却是一个Object类型,但用==比较两个变量时,结果却为true。后来才知道,在JavaScript中会帮我们自动转换类型,然后再进行比对。

事实上,当JavaScript任意东西在进行比较运算时,比如==!=<<=>>=等等,都会先执行valueOf()toString()函数,取回其原始类型的值。当拿到了原始类型的值之后,再进行比较。

除了原始类型之外,还有其他类型存在。而这些类型在默认情况下是无法进行比较的。比如:

var obj1 = {};
var obj2 = {};
obj1 == obj2; // => false
obj1 < obj2;  // => false
obj1 > obj2;  // => false

这个时候,就算我们采用了valueOf()toString()方法,比较出来的结果依旧是false

Object.prototype.valueOf = function() {
    return this.name.length;
}
Object.prototype.toString = function() {
    return this.name
}

var a1 = {'name': 'w3cplus'}
var a2 = {'name': 'w3cplus'}

a1 == a2; // => false
a1 > a2;  // => false
a1 < a2;  // => false

所以,在JavaScript中,所有的对象都是不相等的,每一个都是独立的对象实例(Object Instance),这是一个非常重要的特性,即便你的操作中使用了valueOf()toString()方法,依旧是无法对自定义对象进行任何相等比较运算。

为了在JavaScript中进行比较运算,一定要先将对象通过valueOf()toString()方法转换成原始类型的值。比如下面这个小示例:

var obj1 = {'name': 'W3cplus'}
var obj2 = {'name': 'Edison'};
obj1.valueOf();
obj2.valueOf();
obj1.toString();
obj2.toString();

obj1 == obj2;
obj1 > obj2;
obj1 < obj2;

Object.prototype.valueOf= function (){
    return this.name.length;
}

obj1 > obj2;
obj1 < obj2;

obj1.valueOf();
obj2.valueOf();

Object.prototype.toString = function() {
    return this.name
}

obj1 > obj2;
obj1 < obj2;

delete Object.prototype.valueOf

obj1 > obj2;
obj1 < obj2;

总结

这篇文章是学习JavaScript中String()new String()toString()valueOf()的相关知识。整理的有点乱,但相对而言,对这几个东东有了更深一层的了解和理解。我想在今后的使用中,应该会更易了。因为只有了解了才能更好的使用,当然在实际使用的时候肯定还会碰到坑,只有填坑是最好的验证方法。

扩展阅读

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.w3cplus.com/javascript/toString-vs-String-vs-valueOf.html

返回顶部