在 JavaScript 中,new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。创建一个对象很简单,为什么我们还要多此一举使用 new 运算符呢?它到底有什么样的魔力?

认识 new 运算符

通过下面的例子理解 new 运算符:

function Person (name) {
this.name = name
} Person.prototype.getName = function () {
console.log(this.name)
} var joe = new Person('joe') joe.sayHello = function () {
console.log('Hello!')
} joe.getName() // joe
joe.sayHello() // Hello! Person.sayHello() // Uncaught TypeError: Person.sayHello is not a function

Person 是一个普通的函数,当它与 new 运算符一起使用时,Person 就是一个构造函数。通过 new Person('joe') 得到的新对象 joe 继承了 Person 的属性,同时,this 也指向 joe 实例。为 joe 添加的属性 sayHello 不会影响 Person,即 joe 是区别与 Person 的一个新对象。

因此,通过 new 创建的实例对象和构造函数之间建立了一条原型链,并通过原型链赋予实例对象继承属性的能力

new 的原理和实现

通过上面的分析,new 运算符内部做了如下四个操作:

  • 创建一个空的简单 JavaScript 对象(即{});
  • 链接新对象(即设置该新对象的构造函数)到函数对象;
  • 将新创建的对象作为 this 的上下文;
  • 如果该函数没有返回对象,返回新创建的对象。

new 的实现如下:

function newOperator (ctor, ...args) {
var obj = {};
obj.__proto__ = ctor.prototype
var res = ctor.apply(obj, args)
return res || obj;
}

优化一下代码:

function newOperator (ctor, ...args) {
var o = Object.create(ctor.prototype) // 合并第一和第二步:创建一个空的简单 JavaScript 对象(即{}),链接新对象(即设置该新对象的构造函数)到函数对象
return fn.apply(o, args) || o
}

使用 newOperator 函数测试上面 Person 的例子:

function Person(name) {
this.name = name
} Person.prototype.getName = function () {
console.log(this.name)
} var joe = newOperator(Person, 'joe') joe.sayHello = function () {
console.log('Hello!')
} joe.getName() // joe
joe.sayHello() // Hello! Person.sayHello() // Uncaught TypeError: Person.sayHello is not a function

结果是一致的。

更好的检查方式是:

function Person(name) {
this.name = name
} console.log(new Person('joe')) // @1
console.log(newOperator(Person, 'joe')) // @2

@1 和 @2 在控制台的显示信息是一模一样的。

判断是否使用 new 关键字

在 JavaScript 中,一个实例对象的创建必须使用 new 关键字。但是限于 JavaScript 的语法特征,实际上构造函数同样可以像普通函数那样直接执行。那么构造函数内部如何判断是否使用了 new 关键字?

使用 instanceof 检测

通过理解 new 操作符的原理,可知,在执行 new 操作时,构造函数的 prototype 赋值给了实例的 proto 属性。在 JavaScript 中 instanceof 可以用来检测对象的原型链,如:a instanceof A 用来检测 a 是否是 A 的实例(即 a 的原型链中存在原型对象 A)。

function Person () {
if (this instanceof Person) {
console.log('new 调用')
} else {
console.log('普通函数调用')
}
} const foo = new Person() // new 调用
const bar = Person() // 普通函数调用

使用 new.target 属性

在 ES6 中引入了 new.target 属性,new.target 属性允许你检测函数或构造方法是否是通过 new 运算符被调用的。在通过 new 运算符被初始化的函数或构造方法中,new.target 返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是 undefined。

function Person () {
console.log(new.target)
} const foo = new Person() // ƒ Person () { console.log(new.target) }
const bar = Person() // undefined

深入理解new运算符的更多相关文章

  1. 理解spread运算符与rest参数

    理解spread运算符与rest参数 spread运算符与rest参数 是ES6的新语法.它们的作用是什么?能做什么事情? 1. rest运算符用于获取函数调用时传入的参数. function tes ...

  2. [.net 面向对象编程基础] (6) 基础中的基础——运算符和表达式

    [.net 面向对象编程基础] (6) 基础中的基础——运算符和表达式 说起C#运算符和表达式,小伙伴们肯定以为很简单,其实要用好表达式,不是一件容易的事.一个好的表达式可以让你做事半功倍的效果,比如 ...

  3. JavaScript instanceof 运算符深入剖析

    简介: 随着 web 的发展,越来越多的产品功能都放在前端进行实现,增强用户体验.而前端开发的主要语言则是 JavaScript.学好 JavaScript 对开发前端应用已经越来越重要.在开发复杂产 ...

  4. java中关于移位运算符的demo与总结

    首先,移位运算符有三种,其操作类型只支持:byte / short / char / int和long五种. << 左移运算符,表示将左边的操作数的二进制数据向左移动*位,移动后空缺位以0 ...

  5. JavaScript instanceof 运算符深入剖析【转载】

    http://www.ibm.com/developerworks/cn/web/1306_jiangjj_jsinstanceof/   instanceof 运算符简介 在 JavaScript ...

  6. 一分钟掌握位运算符—与(&)、非(~)、或(|)、异或(^)

    第一个版本:   位运算符的计算主要用在二进制中. 实际开发中也经常会遇到需要用到这些运算符的时候,同时这些运算符也被作为基础的面试笔试题. 所以了解这些运算符对程序员来说是十分必要的. 于此,记录下 ...

  7. JavaScript instanceof 运算符

    instanceof运算符简介 在 JavaScript 中 判断一个变量的类型常常会用 typeof 运算符 判断一个实例是否属于某种类型会使用instanceof 与 typeof 方法不同的是, ...

  8. 对象扩展运算符(…)与rest运算符

    对象扩展运算符(…) 当编写一个方法时,我们允许它传入的参数是不确定的.这时候可以使用对象扩展运算符来作参数,看一个简单的列子: function xzdemo(...arg){ console.lo ...

  9. JavaScript 扩展运算符

    扩展运算符格式扩展运算符格式很简单,就是三个点(...) 扩展运算符作用???扩展运算符允许一个表达式在期望多个参数(用于函数调用)或多个元素(用于数组字面量)或多个变量(用于解构赋值)的位置扩展. ...

随机推荐

  1. 基于 websocket 的多端桥接平台

    我们现在的业务是基于新闻客户端实现的,都要经过新闻客户端的环境,进行前后端数据上的交互.但是我们在调试过程中,非常的不方便. 通常使用的工具有:modheader, postman, fiddler ...

  2. Chrome EC框架探索_0.0_引言

    0.0 引言 嵌入式硬件抽象框架常常面临着这样的尴尬:封装层次较高的(arduino,mbed)不能充分暴露必要的API并面临着性能问题,封装层次较低的(HAL,LL)接口复杂且开发困难.近日发现的一 ...

  3. 深入Redis命令的执行过程

    深入Redis命令的执行过程 Redis 服务器: Redis 服务器实现与多个客户端的连接,并处理这些客户端发送过来的请求,同时保存客户端执行命令所产生的数据到数据库中.Redis 服务器依靠资源管 ...

  4. Java并发编程(03):多线程并发访问,同步控制

    本文源码:GitHub·点这里 || GitEE·点这里 一.并发问题 多线程学习的时候,要面对的第一个复杂问题就是,并发模式下变量的访问,如果不理清楚内在流程和原因,经常会出现这样一个问题:线程处理 ...

  5. vue封装axios

    一.安装axios npm install --save axios 二.在src下面创建文件夹api=>api.js(接口集合)+http.js(封装的请求) 三.在main.js中引用api ...

  6. 关于STM32F103系列从大容量向中容量移植的若干问题

    一.把STM32F103大容量移植到STM32F103C8T6上的步骤: 1.换启动文件 startup_stm32f10x_cl.s           ——互联型的器件 包括:STM32F105x ...

  7. .NET 5.0 Preview 2发布

    2020年4月2日微软.NET 团队的项目经理 Richard 在博客上 发布了.NET 5 Preview 2:https://devblogs.microsoft.com/dotnet/annou ...

  8. [codevs1227]草地排水<Dinic网络流最大流>

    题目链接:http://codevs.cn/problem/1993/ https://www.luogu.org/problemnew/show/P2740 之前一直都没去管网络流这算法,但是老师最 ...

  9. Python学习-第五节:面向对象

    概念: 核心是“过程”二字,“过程”指的是解决问题的步骤,即先干什么再干什么......,基于面向过程设计程序就好比在设计一条流水线,是一种机械式的思维方式.若程序一开始是要着手解决一个大的问题,面向 ...

  10. bootstrip安装

    什么是Bootstrap Bootstrap,来自 Twitter,是目前最受欢迎的前端框架.Bootstrap 是基于 HTML.CSS.JAVASCRIPT 的,它简洁灵活,使得 Web 开发更加 ...