在 JavaScript
当中,运算符的优先级决定了表达式中运算执行的先后顺序,优先级高的运算符最先被执行,在一些看上去比较复杂的运算程序,理清了其中的先后关系后更利于我们得出最后的结果
比如下面这个例子,结果是多少
1 | var str = 'Hello' + true ? 'World' : 'JavaScript' |
最后的结果为 World
, 因为 +
运算符优先级是高于条件运算符的,其实就相当于执行了 ('Hello' + true) ? 'World' : 'JavaScript'
优先级
看完了开胃菜,我们再来通过一个稍微复杂点的示例来进行深入的了解,如下
1 | var provider = { |
针对于 new provider[type].$get()(config)
的执行结果,一眼看上去就感觉十分复杂,在理清它的执行顺序之前,我们需要先明确两点
- 一个是构造函数的返回
- 另一个是
new
操作符的执行顺序
我们先来理清上面这两点
构造函数的返回
简单来说
- 如果返回的是一个非引用类型的值时,实际上返回的是仍然是新创建的实例对象
- 如果返回的是一个引用类型的值时,返回的是引用对象本身
比如如下示例
1 | function Person() { } |
new 操作符的执行顺序
在 MDN
的 new
操作符描述中,语法是
1 | new constructor[([arguments])] |
可以发现,参数 arguments
是可缺省的,那么就意味着,对于不含参数的构造函数而言,new Person()
和 new Person
是一样的,那么又会涉及到一个问题,为什么执行的时候是执行的 new Person()
,而不是 (new Person)()
呢,这里就涉及到操作符的执行顺序,这里也只列举几个本章可能涉及到的运算符,更多详细的可以参考 MDN - 运算符优先级
优先级 | 运算类型 | 运算符 |
---|---|---|
20 | 圆括号 | () |
19 | 成员访问,new (带参数列表),函数调用 |
. [] () |
18 | new(无参数列表) | new … |
17 | 后置递增(运算符在后),后置递减(运算符在后) | … ++ … -- |
16 | 逻辑非,按位非,一元加法,一元减法,前置递增(运算符在前),前置递减(运算符在前) | ! ~ + - ++ … -- … |
14 | 乘法,除法,取模 | * / % |
13 | 加法,减法 | + - |
11 | 小于,小于等于,大于,大于等于 | < <= > >= |
10 | 等号,非等号,全等号,非全等号 | == != === !== |
6 | 逻辑与 | && |
5 | 逻辑或 | || |
4 | 条件运算符 | … ? … : … |
3 | 赋值 | = += -= …… |
0 | 逗号 | , |
了解了原理之后,我们可以很轻松的将上面的问题解析为
1 | // 因为带参数列表的 new 优先级高于函数调用,所以不会先执行函数调用 |
实战
在了解完运算符优先级以后,我们在来通过一系列的示例来加深一下印象,如下
1 | function Foo() { |
一个一个来看,首先我们来整理一下上面的代码,有几个需要注意的地方
1 | function Foo() { |
还有下面这个,两者都会提升,但是函数声明的提升级别是要比 var
高的,所以实际执行的是
1 | function getName() { |
Foo.getName()
函数 Foo
本身并没有执行,执行的是函数的属性 getName
,输出的是 2
getName()
这是在全局执行 getName()
,根据我们上面的分析可知,输出的结果是 4
Foo().getName()
因为 ()
的优先级最高,所以首先运行 Foo()
,全局的 getName
被覆盖成输出 console.log('1')
,并且返回的 this
此时代表的是 window
,也就是相当于执行了 window.getName()
,所以输出的结果为 1
getName()
这个因为之前调用了 Foo()
,所以输出的结果仍然是 1
new Foo.getName()
因为 .
操作符要比 new
优先级要高,所以执行的是 new (Foo.getName)()
,所以输出为 2
new Foo().getName()
根据优先级可知,带参数的 new
操作符是优先级最高的,所以执行的就是 (new Foo()).getName()
,而 new Foo()
又会生成一个实例对象,但是生成的对象身上没有 getName()
的方法,那么就会去 prototype
当中寻找,所以输出的是 3
new new Foo().getName()
老规矩,按照优先级添加括号,首先带参数的 new
操作符优先级最高,则为 new (new Foo()).getName()
,然后就会发现和上面是类似的,.
操作符要比 new
优先级要高,所以可以转换为 new ((new Foo()).getName)()
,所以输出的也为 3
升级版
另外还有一个升级版本,其中的基本原理和上面那个差不多,只需要理清两点即可
- 一个是构造函数的返回值
- 另一个是构造函数公有方法和原型链方法的优先级
1 | function Foo() { |
关于最后两个结果
new Foo().getName().getName()
根据之前的结论,其实执行的是 ((new Foo()).getName)().getName()
,这里需要注意的是 ((new Foo()).getName)
返回的结果是
1 | function () { |
然后在进行调用 ((new Foo()).getName)()
,所以会输出 3
,但是又再次返回了函数内部的一个对象
1 | { |
然后再次调用这个对象的 getName
方法,所以会再次返回 1
new new Foo().getName()
理清楚了上一步的操作,这里就很好理解了,其实调用的是 new ((new Foo()).getName)()
,又因为 (new Foo()).getName
返回的是一个函数,就相当于 new
了一个函数,所以输出结果是 3
,但是还会返回一个 getName
的对象,跟之前是一样的