JavaScript 类型转换深度学习
wpzd6657
8年前
<p><img src="https://simg.open-open.com/show/9a2c396a91bd0ece6ff952ac386c2ce9.jpg"></p> <p>JavaScript 是一门弱类型语言,刚接触的时候感觉方便快捷(不需要声明变量类型了耶!),接触久了会发现它带来的麻烦有的时候不在预期之内</p> <p>呵呵一笑,哪有这么夸张,可能有人看过这样一段代码</p> <pre> <code class="language-javascript">[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]+(![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+![]+[![]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+([]+[])[(![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[!+[]+!+[]]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])() </code></pre> <p>这个占了好大的篇幅哈 3167 个字符,粘贴到浏览器的 Console 控制台,直接弹出了 orange,随叫随到有不有</p> <p>JSFuck 的变态程度达到了极致,因为它的理念是 Write any JavaScript with 6 Characters: []()!+</p> <p>或许又有人说:这个只是搞怪的吧,实际谁这么写代码啊</p> <p>说的没错,当一段代码变得晦涩难懂的时候,甚至到上文的混乱字符(天书),却能实现任意功能这就变得不可预期,也就是说 JS 代码的安全性没有保障</p> <p>当然本文不会研究这些无意义的字符原理是怎么实现的因为人家的 Github 文档已经描述的特别全面了,感兴趣的可以研究下。</p> <p>我们聊一聊每天能看到用到的方法底层是怎么解析的,熟知转换分成两种一种是隐式转换,另一种是强制的类型转换</p> <h2><strong>隐式转换</strong></h2> <p>当遇到以下几种情况,JavaScript会自动转换数据类型:</p> <ul> <li> <p>不同类型的数据进行互相运算</p> </li> <li> <p>对非布尔值类型的数据求布尔值</p> </li> <li> <p>对非数值类型的数据使用一元运算符(即 “+” 和 “-“)</p> </li> </ul> <h3><strong>隐式转换为 Boolean</strong></h3> <p>大多数在做 if 判断时会用到,这里只需记住六个转换为 false,其它全部为 true</p> <ul> <li> <p>null</p> </li> <li> <p>undefined</p> </li> <li> <p>NaN</p> </li> <li> <p>‘’</p> </li> <li> <p>-0</p> </li> <li> <p>+0</p> </li> </ul> <h3><strong>隐式转换为 String</strong></h3> <p>字符串的自动转换,主要发生在加法运算时。当一个值为字符串,另一个值为非字符串,则后者转为字符串。</p> <pre> <code class="language-javascript">'1' + 2 // '12' '1' + true // "1true" '1' + false // "1false" '1' + {} // "1[object Object]" '1' + [] // "1" '1' + function (){} // "1function (){}" '1' + undefined // "1undefined" '1' + null // "1null" </code></pre> <h3><strong>隐式转换为 Number</strong></h3> <p>除了加法运算符有可能把运算子转为字符串,其他运算符都会把两侧的运算子自动转成数值</p> <pre> <code class="language-javascript">'5' - '2' // 3 '5' * '2' // 10 true - 1 // 0 false - 1 // -1 '1' - 1 // 0 '5' * [] // 0 false / '5' // 0 'abc' - 1 // NaN +'abc' // NaN -'abc' // NaN +true // 1 -false // 0 </code></pre> <p>隐式转换的基础表现都在这了,强调的是这些转换的背后都伴随着强制转换,使用 Boolean、Number 和 String,下面重点讲一下强制转换的原理</p> <h2><strong>强制转换</strong></h2> <p>看到上面例子也许你已经有些许疑问了,比如上面的这个 '1' + {} 怎么就输出 1[object Object] 了呢</p> <p>如上面强调的,你会猜测首先执行 String({}) 得到 "[object Object]" ,然后再字符串拼接,是的我们总能得到转换背后的实现原理,其实真实原理要比这个复杂,见下文</p> <h3><strong>强制转换为 Boolean</strong></h3> <p>这里略过因为与隐式转换相同,切记 []、{} 都转换成 true</p> <h3><strong>强制转换为 String</strong></h3> <p>基本类型的转换结果与隐式转换相同,这里说一下对象的转换,加深上面引用例子的解析</p> <p>对象转换字符串分成三步</p> <ol> <li> <p>先调用toString方法,如果toString方法返回的是原始类型的值,则对该值使用String方法,不再进行以下步骤</p> </li> <li> <p>如果toString方法返回的是复合类型的值,再调用valueOf方法,如果valueOf方法返回的是原始类型的值,则对该值使用String方法,不再进行以下步骤</p> </li> <li> <p>如果valueOf方法返回的是复合类型的值,则报错</p> </li> </ol> <p>再分解这个例子</p> <pre> <code class="language-javascript">String({}) // "[object Object]" </code></pre> <p>上面代码相当于下面这样</p> <pre> <code class="language-javascript">String({}.toString()) // "[object Object]" </code></pre> <p>如果 toString 方法和 valueOf 方法,返回的都不是原始类型的值,则 String 方法报错</p> <pre> <code class="language-javascript">var obj = { valueOf: function () { console.log("valueOf"); return {}; }, toString: function () { console.log("toString"); return {}; } }; String(obj) // TypeError: Cannot convert object to primitive value </code></pre> <p>我们不难看出可以对 toString 方法和 valueOf 方法进行改写,测试其先后运行的顺序也简单的多</p> <pre> <code class="language-javascript">String({toString:function(){return 3;}}) // "3" String({valueOf:function (){return 2;}}) // "[object Object]" String({valueOf:function (){return 2;},toString:function(){return 3;}}) // "3" </code></pre> <p>结果表示toString方法先于valueOf方法执行</p> <h3><strong>强制转换为 Number</strong></h3> <p>基本类型转换如下</p> <pre> <code class="language-javascript">Number("123") // 123 Number("123abc") // NaN Number("") // 0 Number(false) // 0 Number(undefined) // NaN Number(null) // 0 </code></pre> <p>对象转换一样要复杂些,与 String 唯一不同的就是 valueOf 方法在前, toString 方法在后,其它不赘述见上文例子。</p> <p>isNaN() 并不陌生, isNaN({}) //true 的内在转换过程是相同的</p> <h2><strong>总结</strong></h2> <p>其它的转换原则还有很多,看到这我们还是不能解释文章开始的代码转换的过程,掌握这些更多是保证正常书写代码规避错误的发生,十分好奇的可以研究下比较特殊的转化原则,还有好多好多。</p> <p> </p> <p>来自:http://orangexc.xyz/2016/10/31/JavaScript-type-conversion-depth-learning/</p> <p> </p>