JS探秘——那些你理解存在偏差的问题
Posted MasterYao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS探秘——那些你理解存在偏差的问题相关的知识,希望对你有一定的参考价值。
javascript的连续赋值运算
var a = {n:1}; a.x = a = {n:2}; alert(a.x); // --> undefined
看 jQuery 源码 时发现的这种写法。 以上第二句 a.x = a = {n:2} 是一个连续赋值表达式。 这个连续赋值表达式在引擎内部究竟发生了什么?是如何解释的?
加一个变量 b,指向 a。
var a = {n:1}; var b = a; // 持有a,以回查 a.x = a = {n:2}; alert(a.x);// --> undefined alert(b.x);// --> [object Object]
实际执行过程:从右到左,a 先被赋值为 {n:2},随后 a.x 被赋值 {n:2}。第一步 a = {n:2} 的 a 指向的是新的对象 {n:2}, 第二步 a.x = {n:2} 中的 a 是 {a:1}。如下所示
a.x = a = {n:2} │ │ {n:1}<──┘ └─>{n:2}
为什么 ++[[]][+[]]+[+[]] = 10?
++[[]][+[]]+[+[]],它相等于:
++[[]][+[]] + [+[]]
在 JavaScript 里,+ 会把一些字符转化成数字,如 +"" === 0、+[] === 0,因此,我们可以简化一下(++ 比 + 有更高的优先级):
++[[]][0] + [0]
因为 [[]][0] 的意思是:获取 [[]] 的第一个元素,这就得出了下面的结果:
- [[]][0] 返回内部数组 ([])。根据语言规范,我们说 [[]][0] === [] 是不正确的,但让我们把这个内部数组称作 A,以避免错误的写法。
- ++[[]][0] == A + 1, 因为 ++ 的意思是”加一”。
- ++[[]][0] === +(A + 1);换句话说,你得到的永远是个数值( +1 并不一定得到的是个数值,但 ++ 一定是)。
我们可以把这一堆代码简化的更清晰。让我们把 A 换回成 [] :
+([] + 1) + [0]
在 JavaScript 里,这也是正确的:[] + 1 === "1",因为 [] == "" (这相当于一个空的数组的内部元素连接),于是:
+([] + 1) === +("” + 1),并且 +("” + 1) === +("1"),并且 +("1") === 1
让我们再次简化一下:
1 + [0]
同样,在 Javascript 里,这是正确的:[0] == "0",因为这是相当于一个有一个元素的数组的内部元素的连接。 各元素会使用,分隔。 当只有一个元素时,你可以推论出这个过程的结果就是它自身的第一个元素。
所以,最终我们得到 (数字 + 字符串 = 字符串):
1 + "0" === "10" // 耶!
[1,2] + [3,4]=="1,23,4"
JavaScript 的 + 运算符有两个目的:
- 将两个数相加
- 将两个字符串连接
规范并没有定义 + 运算符在数组上的行为,所以javascript 首先 把数组转换成字符串,然后在字符串上进行 + 运算。
如果想连接两个数组,可以使用数组的 concat 方法:
[1, 2].concat([3, 4]) // [1, 2, 3, 4]
{}+{}等于多少?
结果为:NaN
这个问题的原因是,JavaScript 把第一个 {} 解释成了一个空的代码块(code block)并忽略了它。 NaN 其实是表达式 +{} 计算的结果 (+ 加号以及第二个 {})。 你在这里看到的 + 加号并不是二元运算符「加法」,而是一个一元运算符,作用是将它后面的操作数转换成数字,和 Number() 函数完全一样。例如:
> +"3.65" 3.65
以下的表达式是它的等价形式:
+{} Number({}) Number({}.toString()) // {}.valueOf() isn’t primitive Number("[object Object]") NaN
为什么第一个 {} 会被解析成代码块(code block)呢? 因为整个输入被解析成了一个语句:如果左大括号出现在一条语句的开头,则这个左大括号会被解析成一个代码块的开始。 所以,你也可以通过强制把输入解析成一个表达式来修复这样的计算结果: (译注:我们期待它是个表达式,结果却被解析成了语句)
> ({} + {}) ‘[object Object][object Object]‘
{} === {},结果为false
每个对象都有自己唯一的标识符,因此通过字面量或构造函数创建的对象和任何其他对象都不相等,我们可以通过 === 进行比较。
{} === {} //false
对象是通过引用来比较的,只有两个对象有相同的标识,才认为这个对象是相等的。
var obj = {}; obj === obj //true
原始值没有内部标识,原始值是按值比较的: 比较两个原始值的依据是他们的内容,如果两个原始值的内容相同,这认为这两个原始值相同。
"abc" === "abc" //true
原始值和它们的包装类型
原始值类型 boolean, number 以及 string 都有自己对应的包装类型 Boolean, Number 和 String。 包装类型的实例都是对象值,两种类型之间的转换也很简单:
- 转换为包装类型:new String("abc")
- 转换为原始类型:new String("abc").valueOf()
原始值类型以及它们相应的包装器类型有很多不同点,例如:
typeof "abc" //‘string‘ typeof new String("abc") //‘object‘ "abc" instanceof String //false new String("abc") instanceof String //true "abc" === new String("abc") //false
包装类型的实例是一个对象,因此和 JavaScript 和对象一样,包装类型也无法进行值的比较(只能比较引用)。
var a = new String("abc"); var b = new String("abc"); a == b //false(虽然 a 和 b 有相同的内容,但是依然返回 false) a == a //true
5.toFixed(3),报错SyntaxError: Unexpected token ILLEGAL
原始值没有自己的方法
包装对象类型很少被直接使用,但它们的原型对象定义了许多其对应的原始值也可以调用的方法。 例如,String.prototype 是包装类型 String 的原型对象。 它的所有方法都可以使用在字符串原始值上。 包装类型的方法 String.prototype.indexOf 在字符串原始值上也有,它们并不是两个拥有相同名称的方法,而的的确确就是同一个方法:
"abc".charAt === String.prototype.charAt true
在数字的包装类型 Number 的原型对象有 toFixed 方法,即 Number.prototype.toFixed,但是当我们写如下代码时却发生错误:
5.toFixed(3) SyntaxError: Unexpected token ILLEGAL
此错误是解析错误(SyntaxError),5 后面跟着一个点号(.),这个点被当作了小数点,而小数点后面应该是一个数,以下代码可以正常运行:
(5).toFixed(3) "5.000" 5..toFixed(3) "5.000"
以上是关于JS探秘——那些你理解存在偏差的问题的主要内容,如果未能解决你的问题,请参考以下文章