- 教程
【免费下载链接】jstips
This is about useful JS tips!
在 JavaScript 中使用
new调用构造函数时,函数的return语句到底还能不能起作用?本篇文章以 jstips 仓库 《new 的返回值》(英文原版见 Return Values with the 'new' Operator)为骨架,结合仓库内的相关技巧,完整梳理new的底层行为、原始值与非原始值返回值差异,以及不加new调用构造函数带来的全局污染陷阱,帮助你彻底掌握这一易混淆的 JavaScript 机制。
一、new的本质:构造一个新对象实例
new是 JavaScript 中用于分配新对象的运算符。在合理的情况下,它总会返回一个新对象的实例。看一个最基础的构造函数:
function Thing() { this.one = 1; this.two = 2; } var myThing = new Thing(); myThing.one // 1 myThing.two // 2执行new Thing()时,引擎会做四件事:
- 创建一个全新的空对象;
- 将这个新对象绑定为函数体内的
this; - 执行构造函数体,通过
this.xxx = ...为对象挂载属性; - 根据函数的返回值决定最终返回什么(这正是本文要重点剖析的规则)。
在这个例子中,this指向的就是new产生的新对象,所以myThing拥有了one和two两个属性。
二、忘记new的代价:this指向全局对象与全局污染
文档中特别强调了一个易踩的坑:如果Thing()不是用new调用,那么不会创建新对象,此时函数内的this会指向全局对象——在浏览器环境中就是window。
Thing(); // 没有 new!这会导致两个后果:
- 突然产生两个全局变量
one和two——因为this.one = 1实际变成了window.one = 1,污染了全局命名空间; myThing为undefined——因为Thing()函数体内没有任何return语句,普通调用默认返回undefined。
这正是许多早期库代码里常见 bug 的根源:构造函数被误当成普通函数调用,属性全部泄漏到全局,而接收返回值的一方拿到的是undefined。ES6 引入的class语法在编译/运行层面进一步强化了这一约束——class构造函数如果不用new调用,会直接抛出TypeError,从语言层面杜绝了这种误用。
三、返回原始值:被静默忽略的return
接下来是本文最核心、最反直觉的部分。我们在构造函数里加一条return语句:
function Thing() { this.one = 1; this.two = 2; return 5; // 一个原始值 } var myThing = new Thing();此时myThing到底等于多少?是5?是一个对象?还是两者皆非?
答案出人意料——myThing仍然是一个对象:
myThing.one // 1 myThing.two // 2我们明明在构造函数里return 5,但这个5却"消失"了。这是因为:
使用
new调用函数时,如果构造函数返回的是原始值(primitive,如数字、字符串、布尔值、null、undefined),该返回值会被直接忽略,new表达式最终返回的是this实例——即你通过this.one = 1;之类语句挂载了属性的那个对象。
也就是说,return 5这种写法在new语义下是无效的"死代码"。
四、返回非原始值:覆盖this实例
既然原始值会被忽略,那返回非原始值(引用类型)会怎样?文档紧接着给出了第三个示例——在构造函数中返回一个对象字面量:
function Thing() { this.one = 1; this.two = 2; return { three: 3, four: 4 }; } var myThing = new Thing();用console.log直接打印全部内容:
console.log(myThing); /* Object {three: 3, four: 4} this.one 和 this.two 发生了什么!? 他们被覆盖了,朋友。 */结果截然不同:myThing变成了{three: 3, four: 4},而你在构造函数里辛苦设置的this.one、this.two全都被丢弃了。
这是因为:当构造函数返回一个非原始值(对象object、数组array或函数function)时,这个非原始值会"踩掉"(stomp)this实例,成为new表达式的最终返回值——相当于你之前对this的所有赋值工作全部白做。
五、规则总结:一张表看清所有情况
把上面三个示例归纳成一条完整的判定规则:
构造函数中的return语句 | new表达式最终返回值 | 说明 |
|---|---|---|
无return(或return;) | this实例 | 最常见的正常情况,如this.one = 1的赋值全部生效 |
return原始值(5、'str'、true、null等) | this实例 | 原始返回值被静默忽略,相当于没写 |
return非原始值(对象、数组、函数) | 返回的那个非原始值 | this实例被覆盖,之前的属性赋值失效 |
不用new直接调用 | 取决于函数体return(通常为undefined) | this指向全局对象,属性泄漏为全局变量 |
一句话记忆:返回原始值,拿回this;返回对象,对象说了算。
顺带一提,return原始值时对象本身并不会"消失"——this实例照样被创建并返回,只是那个原始值被丢弃;而返回对象时,this实例虽然在创建后被丢弃,但它在构造函数体内的赋值过程已经执行完毕(只是结果没人接收)。
六、仓库内相关技巧的印证与延伸
jstips 仓库中还有两篇与本文主题高度互补的技巧,可以加深理解:
第 20 期:返回对象,使方法可以链式调用:该技巧在构造函数的方法中主动
return this,从而实现person.sayName().changeName("Timmy").sayName()式的方法链。结合本文的规则看,return this返回的正是new创建的那个实例(非原始值),与"返回对象会覆盖默认返回"的规则完全自洽——这也是 jQuery 等库链式调用的底层模式。第 44 期:了解传值机制:该技巧讲解了对象作为参数时"引用值按值传递"的行为。理解这一点有助于区分:
this实例本质上是引擎内部创建的对象引用,无论是被默认返回、被原始值忽略,还是被新对象覆盖,其背后都是引用语义在起作用。
从仓库结构看,每篇 tip 均遵循 POST_TEMPLATE.md 的规范组织(front matter 含layout、title、tip-number、tip-username等元信息),本文所述内容对应tip-number: 52,完整技巧列表可参见 README.md。
七、实战建议:如何安全地使用new
基于以上规则,给出几条可直接落地的编码建议:
- 构造函数不要返回原始值:
return 5这类语句在new调用下是无效的,只会误导读者,应直接删除。 - 想覆盖默认实例时,显式返回对象:少数场景(如实现单例、返回工厂产物)可以利用"返回对象覆盖
this"的特性,但务必在注释中说明,避免团队其他成员误读。 - 防止忘记
new:在构造函数内部可以加防御性检查,例如if (!(this instanceof Thing)) return new Thing();,或在 ES6 中直接使用class,让引擎在未用new时抛错。 - 善用
return this实现链式调用:参考上文第 20 期技巧,在方法内返回this实例,可获得流畅的链式 API。
八、小结
new运算符的返回值规则虽然反直觉,但底层逻辑非常清晰:
- 有
new+ 返回原始值→ 返回this实例; - 有
new+ 返回非原始值→ 返回该非原始值,this实例被覆盖; - 无
new→ 不创建新对象,this指向全局对象,属性泄漏、返回undefined。
理解了这三条规则,你就不会再被"构造函数里的return到底有没有用"这类问题困扰,也能更自信地设计构造函数、class 与链式 API。
- 教程
【免费下载链接】jstips
This is about useful JS tips!
相关推荐
深入理解 JavaScript 逻辑或 `||`:从 `alert( null || 2 || undefined )` 看"返回第一个真值"的求值规则
深入理解 JavaScript 逻辑或 || :从 alert null || 2 || undefined 看"返回第一个真值"的求值规则 本篇指南围绕 Mo
文档/教程前端如何正确理解bash正则匹配:=~运算符的返回值解析
如何正确理解bash正则匹配:=~运算符的返回值解析 bash正则表达式匹配是Shell脚本编程中的核心技能之一,其中 =~ 运算符是实现模式匹配的关键工具。本
教程3大技术突破:ElasticFusion如何实现实时密集视觉SLAM的革命性创新
3大技术突破:ElasticFusion如何实现实时密集视觉SLAM的革命性创新 在机器人导航、增强现实和三维重建领域,实时密集视觉SLAM技术一直是研究的热点
计算机视觉
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考