首先欢迎大家关注我的 GitHub 博客 https://github.com/MrErHu/blog , 也算是对我的一点鼓励, 毕竟写东西没法变现, 坚持下去也是靠的是自己的热情和大家的鼓励. 各位读者的 Star 是激励我前进的动力, 请不要吝惜.
起源
写了两年的 JavaScript 的我, 原以为是不会在语法上阴沟里翻船的, 可是事实上被打脸, 最近在产品开发中组里的一个帅小伙找我讨论一个问题, 为了方便大家阅读, 我将这个问题的模型抽象出来:
- var provider = {
- test: {
- $get: function(){
- return function anonymous(config){
- };
- }
- }
- };
- var type = "test";
- var config = {};
- new provider[type].$get()(config);
上面的语句运行时候为什么函数 anonymous 中的 this 指向的是 Windows 而不是 new 创建的新对象. 我当时听到这个问题的第一时刻想的是: 纳尼 ! 怎么可能 new 操作符对应的构造函数中的 this 指向的不是新创建的对象实例呢? 当时由于并没有仔细地将问题从业务中抽象出来, 其实我也有点迷糊, 但仔细一想, 这个语句到底要表达什么呢?
思考
在说这个表达式所要表达的含义之前, 先说一个关于 new 操作符的几个小知识:
构造函数的返回
JavaScript 构造函数中可以返回值, 也可以不返回值, 比如:
- function Person(){
- }
- var person = new Person()
我们知道这个时候构造函数返回的是创建的实例对象, 也就是构造函数中 this 所指向的对象. 但是当你构造函数有返回值时, 就要分情况区分. 如果返回的是一个非引用类型的值时, 实际上返回的是仍然是新创建的实例对象. 但是当返回的是一个引用类型的值时, 返回的是引用对象本身. 比如:
- function Person(){
- return function(){}
- }
- var person = new Person()
- typeof person // "function"
在 JavaScript 中函数作为一等公民, 实质上就是引用类型, 因此 person 就是返回的匿名函数.
new 操作符的两种形态
其实在 MDN 的 new 操作符描述中, 语法是
new constructor[([arguments])]
你会发现 ([arguments]) 被中括号所包围也就意味着可缺省, 因此, 如果对于不含参数的构造函数而言:
new Person()与 new Person
二者并无区别, 那我们接着思考一个问题, 对于前面返回函数的 Person 而言, 当
new Person()的时候为什么执行的是 new Person()而不是 (new Person)() 呢. 之前如果阅读过我之前的一篇文章 https://github.com/MrErHu/blog/issues/9 的同学知道, 带有参数的 new 操作符的优先级大于无参数列表的 new 操作符. 因此总是会执行第一种而不是第二种.
了解上面的步骤之后, 我们已经接近了问题的本质, 对于表达式
new provider[type].$get()(config);
JavaScript 引擎到底是解析成:
(new provider[type].$get())(config);
还是
new (provider[type].$get())(config);
对于第一种形式而言,(new provider[type].$get())返回的是 anonymous 函数, 因此在 anonymous(config)中内部 this 指向是 Windows. 而第二种模式中 provider[type].$get()返回的是 anonymous 函数, 因此运行 new anonymous(config)时内部的 this 指针指向的是新创建的实例 this.
当然我们从问题: this 为什么指向的是 Windows 而不是 new 创建的新对象中可以看出来, 其实作者当时想要表达的是第二种含义, 但实际上却以第一种方式在运行. 为什么? 原因非常简单, 第一种执行方式 JavaScript 引擎首先解析的是带参数列表的的 new 操作符, 而第二种方式则是先执行了函数调用, 再执行的是 new 操作符, 我们对照上面的优先级图可以看到, 带参数列表的 new 优先级高于函数调用, 因此肯定是以第一种方式去运行.
其实这篇文章并没有多少干货, 但是从中还是有两点感悟吧, 第一, 从上一篇同类文章中我就强调避免使用这种模糊不清的表达式, 多用几个括号一切问题都迎刃而解, 比如有的同学会写出类似于:
var str = "Hello" + true ? "World" : "JavaScript";
那请问 str 内容是什么呢, 有的人可能认为是 Hello World, 有的人会认为是 World, 实质上运算的结果是 World, 因为 + 运算符优先级是高于条件运算符的, 这时候添加括号会让你的代码变得更加易于阅读. 第二, 保持对技术的敬畏吧, 最怕的就是你觉得你都会了, 其实你一无所知.
来源: https://juejin.im/post/5bfa94eb6fb9a04a0c2e1c62