JS中字符串与其它数据类型的互相转换

字符串是各种编程语言支持最广泛的数据类型,各编程语言都提供了强大的字符串处理能力。我们知道,JS是弱类型语言,各变量间可以互相转换,我们在写业务逻辑时,如果使用非全等运算符(==)或在 if 语句中常常会伴随隐式的类型自动转换。本文将介绍 JS 中字符串与非字符串类型间如何互相转换。

var flag = '123' == 123;
if ('1') {}

1. JS中的数据类型

对于 JS 我们通常的说法是:JS是面向对象的语言,一切都是对象。看下面的代码:

123 instanceof Object; // false
'str' instanceof Object; // false
Number(123) instanceof Object; // false;

new Number(123) instanceof Object; // true
new String('str') instanceof Object; // true
/abc/ instanceof Object; // true
[1,2,3] instanceof Object; // true

很明显,并不是所有的值都是对象。其实在JS中存在两种值:基础值和对象。直接使用 类型函数(),得到的是基础值,使用 new 类型函数() 得到的是对象。这些基础值并不是对象,这也解释了上面代码。JS中数据类型有:

  • 基本数据类型(基础值)

    • Undefined
    • Null
    • String
    • Boolean
    • Number
  • 复合数据类型(对象)

    • Object
    • Function
    • Regexp
    • Array

2. JS中常用数据类型如何转换成字符串

在 JS 中,一个值要转换成字符串有四种方法:

  • value.toString()
  • "" + value
  • String(value)
  • JSON.stringify(obj)

对于基本值,非字符串按下面规则转换成字符串:

  • undefined -> 'undefined'
  • null -> 'null'
  • boolean -> 'true'/'false'
  • number -> '$number'

对于对象:

1.'' + value,在转换成字符串时

1-1:先执行复合值的 valueOf()方法,如果结果为原始值,将此值转换成字符串返回;如果结果不为原始值,则执行复合值的 toString() 方法

1-2:执行 toString() 方法后,如果结果为原始值,将此值转换成字符串返回;如果结果仍不为原始值,则报错 'Uncaught TypeError: Cannot convert object to primitive value'

2.String(value),在转换字符串时,先执行 toString(),结果不为原始值再执行 valueOf(),刚刚与 '' + value 过程相反

大家可以拿下面的代码测试一下:

var obj = {
    valueOf: function () {
        console.log("valueOf");
        return 'x'; // not a primitive, keep going
    },
    toString: function () {
        console.log("toString");
        return 'y'; // not a primitive, keep going
    }
};

var obj2 = {
    valueOf: function () {
        console.log("valueOf-2");
        return {};
    },
    toString: function () {
        console.log("toString-2");
        return {};
    }
};
Object.prototype.toString = function () {
    console.log('Object.prototype.toString');
    return {}
};

Object.prototype.valueOf = function () {
    console.log('Object.prototype.valueOf');
    return {}
};

JSON.stringify({a: {}}) // 不会调用toString或valueOf'
'' + {} // 会先调用valueOf
String({}) // 会先调用toString()

3. JS中字符串如何转换成其它数据类型

看完了非字符串类型如何转换成字符串,我们再来看看各类型的字符串如何还原成原始类型,即:

'undefined' -> undefined
'null' -> 'null'
'true'|'false' -> true/false
'123' -> 123
'0.816' -> 0.816
'/abc/i' -> /abc/i    // 正则
'[1, 2, 3]' -> [1, 2, 3]  // 数组
'fucntion() {console.log("function")}' -> function() {console.log('function')} // 函数
'{a: 1, b: 2}' -> {a: 1, b:2} // 对象

3.1 字符串与基础值的互想转换

因为Undefined类型,Null类型分别都只有一个值undefined,null。Boolean类型有两个值:true/false。所以最容易想到的转换方法就是字符串比较:

function strToValue(str) {
    if (str === 'undefined') return undefined;
    if (str === 'null') return null;
    if (str === 'true') return true;
    if (str === 'false') return false;
}

对于数值类型,能想到的方法有:

  • +value
  • Number(value)
  • parseInt(value)/parseFloat(value)

其中 +value 效果与 Number(value) 效果一样,能将10进制整数与小数转换成对应的数值。但对8进制数(以0开始的数)都无能为力,而 parseInt() 可以通过第二个参数指定进制,可以解决这个问题。所以说,大部分情况下使用 +value 或 Number()都可以满足需求。

if (/^[\d.]$/.test(str)) return Number(str);

其实对于将基础值字符串转换成基础值还有更简单的方法:使用JSON.parse()和eval();

使用JSON.parse转换字符串

使用eval转换字符串

3.2 复合值(对象)字符串转换成复合值

这里的复合值我们主要考虑:正则,函数,对象,数组4种类型。这几种类型也是写代码时常用的类型。

3.2.1 对象与字符串的互想转换

如果将问题变一下:JSON字符串如果转换成对象。那么这个问题应该不需要过多讨论,JS 原生提供了 JSON.stringify(obj) 和 JSON.parse(jsonStr) 两个方法来处理 JSON 字符串与对象之间的互相转换。

由于 JSON 语法只支持 Number, String, Boolean, null 这几种基础值及由它们组成的数组和对象。所以如果对象中有正则,或函数,那么对其使用 JSON.stringify(obj) 时会有一些异常:

var obj = {
    a: 123,
    b: false,
    c: null,
    d: 'hello',
    e: /abc/,
    f: function() {},
    g: [1, 2, 3]
};

var str = JSON.stringify(obj);
// 结果: "{"a":123,"b":false,"c":null,"d":"hello","e":{},"g":[1,2,3]}"

我们发现,e 对应的正则变成了 {},而 f 对应的函数则完全消失,连 key 都没有剩下。这里我们要特别注意。对于嵌套的对象情况也是一样。

3.2.2 正则与字符串的互想转换

我们知道,在JS中创建正则有两种方式:字面量和使用构造函数:

var r1 = /^a/i;
var r2 = new Regexp('^a', 'i');

正则的toString()方法返回的是正则的字符串表式,如果通过此字符串来还原正则工作量相对大一些。其实对于一个正则,有flags和source属性,分别表示正则的标志和表达式主体。有了这两个属性,刚好可以利用正则构造函数创建正则。于是对于正则我们可以这样来设计储存信息:

var r = /^a/i;
var regexpCacheData = {
    flags: r.flags,
    source: r.source
};
var regexpCacheDataStr = JSON.stringify(regexpCacheData);  // 序列化成JSON字符串

var regexpObj = JSON.parse(regexpCacheDataStr);  // 解析成对象
var r = new Regexp(regexpObj.source, regexp.flags);  // 生成正则。这个正则即当初我们保存的正则

3.2.3 函数与字符串的互想转换

我们知道,函数也是对象,而函数的 toString() 方法刚好会返回函数的字符串形式。

var f = function () {
    return Math.random();
};

// f.toString() 结果为:
"function () {
    return Math.random();
}"

$.prototype.addClass.toString(); // jQuery.addClass方法的函数字符串
"function(a){var b,c,d,e,f,g,h=0,i=this.length,j="string"==typeof a&&a;if(m.isFunction(a))return this.each(function(b){m(this).addClass(a.call(this,b,this.className))});if(j)for(b=(a||"").match(E)||[];i>h;h++)if(c=this[h],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(uc," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=m.trim(d),c.className!==g&&(c.className=g)}return this}"

得到上面的函数字符串后,怎么再将字符串还原成函数呢?能不能像上面的正则一样利用函数的构造函数呢?答案是可以的

var buildFunctionWithFunctionString = function (fnStr) {
    var argsStr, bodyStr;

    argsStr = fnStr.match(/^function(?:\s?|\s+)\(([\w,\s]*)\)(?:\s?|\s+)\{/)[1];   // 匹配参数

    bodyStr = fnStr.replace(/^function(?:\s?|\s+)\([\w,\s]*\)(?:\s?|\s+)\{/, '');  // 匹配函数体
    bodyStr = bodyStr.slice(0, bodyStr.length -1).trim(); // 去掉最后的 }

    return new Function(argsStr, bodyStr);
}

上面的匹配比较麻烦,而且容易出错。有没有更简单的方法呢?有,利用 eval 能更简单实现字符串到函数的转换

var buildFunctionWithFunctionString = function (fnStr) {
    return eval('(' + fnStr + ')'); // 这里加的()主要用于求值
}

var a = function() {alert('hello')};
var a1 = buildFunctionWithFunctionString(a.toString())
a1(); // 可以执行,并弹出alert()

但这里有个问题是,函数要能正常执行需是作用域和上下文环境。所以只有不依赖上下文环境的函数才有从函数字符串恢复成函数的必要,不然即使函数恢复了,但由于丢失了对应的上下文环境,函数也不能正常执行。所以将函数字符串转换成函数的意义可能不是特别大。

3.2.4 数组字符串与数组的互相转换

理论上,JS中数组的各项可以是任意数据类型,但我们这里只讨论项为基本值的数组,即数组的元素只会是 JSON 数据类型值。这样,我们就可以像处理对象那样使用 JSON.parse 和 JSON.stringify 来处理数组了。当然,使用 JSON.parse 也可以换成 eval

使用JSON处理数组

使用eval处理数组

4. 参考

留言列表
  • 望哥:
    是否考虑开源到github上,以便大家可以直接引用?
    • u3xyz:
      暂时还没有这个想法
      2018年11月29日 05:01
    2018年11月24日 00:51 回复
  • 空气罐头:
    老哥,你这个标题导航做得不是很好啊。就以这篇文章为例,当页面滚动到最后的时候,导航的跳转就不正确了。
    • u3xyz:
      是的,的确有bug,感谢指出!
      2018年11月20日 21:32
    2018年11月20日 15:25 回复
  • 你猜我是谁:
    这个好
      2018年06月01日 16:50 回复
    • test1:
      test1
        2018年04月24日 14:59 回复
      • test:
        test
          2018年04月10日 10:41 回复

        发表评论: