运算符优先级

运算符优先级

JavaScript 当中,运算符的优先级决定了表达式中运算执行的先后顺序,优先级高的运算符最先被执行,在一些看上去比较复杂的运算程序,理清了其中的先后关系后更利于我们得出最后的结果

比如下面这个例子,结果是多少

1
var str = 'Hello' + true ? 'World' : 'JavaScript'

最后的结果为 World, 因为 + 运算符优先级是高于条件运算符的,其实就相当于执行了 ('Hello' + true) ? 'World' : 'JavaScript'

优先级

看完了开胃菜,我们再来通过一个稍微复杂点的示例来进行深入的了解,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var provider = {
test: {
$get: function () {
return function anonymous(config) {
// window
console.log(this)
}
}
}
}

var type = 'test'
var config = {}
new provider[type].$get()(config) // ?

针对于 new provider[type].$get()(config) 的执行结果,一眼看上去就感觉十分复杂,在理清它的执行顺序之前,我们需要先明确两点

  • 一个是构造函数的返回
  • 另一个是 new 操作符的执行顺序

我们先来理清上面这两点

构造函数的返回

简单来说

  • 如果返回的是一个非引用类型的值时,实际上返回的是仍然是新创建的实例对象
  • 如果返回的是一个引用类型的值时,返回的是引用对象本身

比如如下示例

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person() { }

const person = new Person()
console.log(typeof person) // object

// ----

function Person() {
return function () { }
}

const person = new Person()
console.log(typeof person) // function

new 操作符的执行顺序

MDNnew 操作符描述中,语法是

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
2
// 因为带参数列表的 new 优先级高于函数调用,所以不会先执行函数调用
(new provider[type].$get())(config)

实战

在了解完运算符优先级以后,我们在来通过一系列的示例来加深一下印象,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function Foo() {
getName = function () {
console.log('1')
}
return this
}

Foo.getName = function () {
console.log('2')
}

Foo.prototype.getName = function () {
console.log('3')
}

var getName = function () {
console.log('4')
}

function getName() {
console.log(5)
}

// 输出结果依次为多少
Foo.getName()
getName()
Foo().getName()
getName()
new Foo.getName()
new Foo().getName()
new new Foo().getName()

// 结果为 2 4 1 1 2 3 3

一个一个来看,首先我们来整理一下上面的代码,有几个需要注意的地方

1
2
3
4
5
6
7
function Foo() {
// 注意这里是全局的
getName = function () {
console.log('1')
}
return this
}

还有下面这个,两者都会提升,但是函数声明的提升级别是要比 var 高的,所以实际执行的是

1
2
3
4
5
6
7
8
function getName() {
console.log(5)
}

// 会覆盖上面的
var getName = function () {
console.log('4')
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function Foo() {
this.getName = function () {
console.log(3)
return {
getName: getName
}
}
getName = function () {
console.log(1)
}
return this
}

Foo.getName = function () {
console.log(2)
}

Foo.prototype.getName = function () {
console.log(6)
}

var getName = function () {
console.log(4)
}

function getName() {
console.log(5)
}


Foo.getName() // 2
getName() // 4
console.log(Foo()) // window
Foo().getName() // 1
getName() // 1
new Foo.getName() // 2
new Foo().getName() // 3

new Foo().getName().getName() // 3 1
new new Foo().getName() // 3

关于最后两个结果

new Foo().getName().getName()

根据之前的结论,其实执行的是 ((new Foo()).getName)().getName(),这里需要注意的是 ((new Foo()).getName) 返回的结果是

1
2
3
4
5
6
function () {
console.log(3)
return {
getName: getName
}
}

然后在进行调用 ((new Foo()).getName)(),所以会输出 3,但是又再次返回了函数内部的一个对象

1
2
3
4
5
6
7
8
9
10
11
{
getName: getName
}

// ==>

{
getName: function () {
console.log(1)
}
}

然后再次调用这个对象的 getName 方法,所以会再次返回 1

new new Foo().getName()

理清楚了上一步的操作,这里就很好理解了,其实调用的是 new ((new Foo()).getName)(),又因为 (new Foo()).getName 返回的是一个函数,就相当于 new 了一个函数,所以输出结果是 3,但是还会返回一个 getName 的对象,跟之前是一样的

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×