最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • JavaScript 类型转换

    正文概述 掘金(闻朝)   2021-01-17   457

    类型转换

    现在介绍一个现象:类型转换。

    因为 JS 是弱类型语言,所以类型转换发生非常频繁,大部分我们熟悉的运算都会先进行类型转换。大部分类型转换符合人类的直觉,但是如果我们不去理解类型转换的严格定义,很容易造成一些代码中的判断失误。

    其中最为臭名昭著的是 JavaScript 中的“ == ”运算,因为试图实现跨类型的比较,它的规则复杂到几乎没人可以记住。这里我们当然也不打算讲解 == 的规则,它属于设计失误,并非语言中有价值的部分,很多实践中推荐禁止使用“ ==”,而要求程序员进行显式地类型转换后,用 === 比较。

    其它运算,如加减乘除大于小于,也都会涉及类型转换。幸好的是,实际上大部分类型转换规则是非常简单的,如下表所示:

    JavaScript 类型转换

    在这个里面,较为复杂的部分是 Number 和 String 之间的转换,以及对象跟基本类型之间的转换。我们分别来看一看这几种转换的规则。

    StringToNumber

    字符串到数字的类型转换,存在一个语法结构。类型转换支持十进制、二进制、八进制和十六进制,比如:

    • 30;
    • 0b111;
    • 0o13;
    • 0xFF。

    此外,JavaScript 支持的字符串语法还包括正负号科学计数法,可以使用大写或者小写的 e 来表示:

    • 1e3;
    • -1e-2。

    需要注意的是,parseInt 和 parseFloat 并不使用上面这个转换,所以支持的语法跟这里不尽相同。

    在不传入第二个参数的情况下,parseInt 只支持 16 进制前缀“0x”,而且会忽略非数字字符,也不支持科学计数法。在一些古老的浏览器环境中,parseInt 还支持 0 开头的数字作为 8 进制前缀,这是很多错误的来源。所以在任何环境下,都建议传入 parseInt 的第二个参数,而 parseFloat 则直接把原字符串作为十进制来解析,它不会引入任何的其他进制。

    多数情况下,Number 是比 parseInt 和 parseFloat 更好的选择。

    以下内容来自 MDN


    正常情况下,parseInt 的返回值是根据给定字符串解析后返回的数字,还有另一种可能的返回值是:NaN,有如下两种场景:

    1. 当遇到的非空白字符不能转化为数字时,返回 NaN,如:parseInt('321',2) 由于指定进制基数为2,字符串中的3不符合进制规则不能正常解析
    2. 传入的 radix 小于2(非0)或者大于36(请思考为什么)

    总结:当传入的字符串以"0x"开头时,默认是16进制数,其他的格式都是10进制数,所以想转化一个二进制数的格式是 parsetInt('011', 2) 而不是 parsetInt('0b11', 2),同理对8进制数也是,不使用 parseInt('0o11', 8) 而是使用 parseInt('011', 8)。

    下图来自标准规范中的定义,更多细节可以查看 标准中的关于 parseInt 的定义

    JavaScript 类型转换

    NumberToString

    在较小的范围内,数字到字符串的转换是完全符合你直觉的十进制表示。当 Number 绝对值较大或者较小时,字符串表示则是使用科学计数法表示的。这个算法细节繁多,我们从感性的角度认识,它其实就是保证了产生的字符串不会过长。具体的算法可以参考 JavaScript 的语言标准。

    装箱转换

    每一种基本类型 Number、String、Boolean、Symbol 在对象中都有对应的类,所谓装箱转换,正是把基本类型转换为对应的对象,它是类型转换中一种相当重要的种类。

    全局的 Symbol 函数无法使用 new 来调用,但我们仍可以利用装箱机制来得到一个 Symbol 对象,我们可以利用一个函数的 call 方法来强迫产生装箱。我们定义一个函数,函数里面只有 return this,然后我们调用函数的 call 方法到一个 Symbol 类型的值上,这样就会产生一个 symbolObject。我们可以用 console.log 看一下这个东西的 type of,它的值是 object,我们使用 symbolObject instanceof 可以看到,它是 Symbol 这个类的实例,我们找它的 constructor 也是等于 Symbol 的,所以我们无论从哪个角度看,它都是 Symbol 装箱过的对象:

    var symbolObject = (function(){ return this; }).call(Symbol("a"));
    
    console.log(typeof symbolObject); //object
    console.log(symbolObject instanceof Symbol); //true
    console.log(symbolObject.constructor == Symbol); //true
    

    装箱机制会频繁产生临时对象,在一些对性能要求较高的场景下,我们应该尽量避免对基本类型做装箱转换。

    使用内置的 Object 函数,我们可以在 JavaScript 代码中显式调用装箱能力

    var symbolObject = Object(Symbol("a"));
    
    console.log(typeof symbolObject); //object
    console.log(symbolObject instanceof Symbol); //true
    console.log(symbolObject.constructor == Symbol); //true
    

    每一类装箱对象皆有私有的 Class 属性,这些属性可以用 Object.prototype.toString 获取:

    var symbolObject = Object(Symbol("a"));
    
    console.log(Object.prototype.toString.call(symbolObject)); //[object Symbol]
    

    在 JavaScript 中,没有任何方法可以更改私有的 Class 属性,因此 Object.prototype.toString 是可以准确识别对象对应的基本类型的方法,它比 instanceof 更加准确。但需要注意的是,call 本身会产生装箱操作,所以需要配合 typeof 来区分基本类型还是对象类型。如下所示:

    const OBJECT_TYPE = 'object';
    const OBJECT_FLAG = '[object ';
    
    // 实现一个可以用来获取传入数据的私有 Class 属性的方法
    const GetValueClass = function (value) {
      // 判断是否是基础类型
      if (typeof value !== OBJECT_TYPE) {
        // 基础类型则转化后输出
        let type = typeof value;
        return type.charAt(0).toUpperCase() + type.substring(1);
      }
    
      let classString = Object.prototype.toString.call(value);
      return classString.substring(OBJECT_FLAG.length, classString.length - 1);
    };
    
    // 实现一个 MyInstanceof 功能类似于 instanceof
    const MyInstanceof = function (value, typeString) {
      // 获取私有属性
      let type = GetValueClass(value);
      // 判断私有属性的值
      return type === typeString;
    };
    

    拆箱转换


    在 JavaScript 标准中,规定了 ToPrimitive 抽象函数,它是对象类型到基本类型的转换,即拆箱转换。

    对象到 String 和 Number 的转换都遵循“先拆箱再转换”的规则。通过拆箱转换,把对象变成基本类型,再从基本类型转换为对应的 String 或者 Number。如果 valueOf 和 toString 都不存在或者没有返回基本类型,则会产生类型错误 TypeError。

    下面演示了先调用 valueOf 再调用 toString

        var o = {
            valueOf : () => {console.log("valueOf"); return {}},
            toString : () => {console.log("toString"); return {}}
        }
    
        o * 2
        // valueOf
        // toString
        // TypeError
    

    下面演示了先调用 toString 再调用 valueOf

    
        var o = {
            valueOf : () => {console.log("valueOf"); return {}},
            toString : () => {console.log("toString"); return {}}
        }
    
       	String(o)
        // toString
        // valueOf
        // TypeError
    

    在 ES6 以后拆箱转换时如果对象部署了[Symbol.ToPrimitive] 则会调用[Symbol.ToPrimitive] 然后结束,没有[Symbol.ToPrimitive] 则会调用 valueOf 和 toString(根据不同场景有不同的调用顺序) 来获得拆箱后的基本类型

    
        var o = {
            valueOf : () => {console.log("valueOf"); return {}},
            toString : () => {console.log("toString"); return {}}
        }
        o[Symbol.toPrimitive] = () => {console.log("toPrimitive"); return "hello"}
        console.log(o * 2);
        // toPrimitive
        // NaN
    
    		console.log(String(o));
    		// toPrimitive
        // "hello"
    
    		console.log(o + '');
    		// toPrimitive
        // "hello"
    

    从标准规范看拆箱转换


    关于拆箱转换这部分在标准规范中定义如下JavaScript 类型转换
    通过上图规范可以看到,在进行 ToPrimitive 转换时会依赖一个可选参数 preferredType,支持 number 和 string 两种类型,如果不传则如上图 2 c 处默认是 number ,最终会导致优先调用 valueOf。感兴趣的可以直接查看原文。

    那么如果想要知道在转换时到底先 valueOf 还是先 toString,只要查对应的 preferredType 即可。比如当你想要知道 a + b 的真实执行顺序时,就可以去查询二进制运算的标准
    JavaScript 类型转换
    首先需要验证传入数据是否可访问可获取需要借助 GetValue(规范原文在这),截图如下
    JavaScript 类型转换

    然后就可以得到下一步字符串或者数字运算标准

    JavaScript 类型转换
    这样就可以知道 a + b 的执行顺序是:

    1. 第一步让 a 成为表达式的左手端,b 成为表达式的右手端,然后分别校验是否可正常访问,未定义则报错,正常访问则获取值
    2. 获取到的值进入 ApplyStringOrNumericBinaryOperator 由于运算连接符 opText 是加号 “+”,满足 规范中的场景1
    3. 场景1中调用 ToPrimitive 时没有传递第二个参数 preferredType,所以默认是 number
    4. 得到结论:先执行 valueOf 再执行 toString (完结撒花✿✿ヽ(°▽°)ノ✿)

    typeof


    JavaScript 类型转换

    在表格中,多数项是对应的,但是请注意 object——Null 和 function——Object 是特例。


    起源地下载网 » JavaScript 类型转换

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元