在 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. 【开源】使用Angular9和TypeScript开发RPG游戏

    RPG系统构造 通过对于斗罗大陆小说的游戏化过程,熟悉Angular的结构以及使用TypeScript的面向对象开发方法. 项目地址 人物 和其他RPG游戏类似,游戏里面的人物角色大致有这样的一些属性 ...

  2. Topshelf+Quartz3.0基于控制台应用程序快速开发可调度windows服务

    1.TopShelf TopShelf是一个开源的跨平台的宿主服务框架.可通过.Net Core/.Net Framwork控制台应用程序快速开发windows服务,更加便于服务调试. 本文基于.Ne ...

  3. gRPC(2):客户端创建和调用原理

    1. gRPC 客户端创建流程 1.1 背景 gRPC 是在 HTTP/2 之上实现的 RPC 框架,HTTP/2 是第 7 层(应用层)协议,它运行在 TCP(第 4 层 - 传输层)协议之上,相比 ...

  4. Django之Ajax传输数据

    MTV与MVC模型 MTV与MVC都是模型,只不过MTV是django自己定义的,具体看一下他们的意思 MTV模型(django) M:模型层(models.py) T:templates文件夹 V: ...

  5. vue 指令,成员,组件

    目录 复习 v-once指令 v-cloak指令(了解) 条件指令 v-pre指令(了解) 循环指令 todolist留言板案例 实例成员 - 插值表达式符号(了解) 计算属性 属性监听 组件 局部组 ...

  6. [Docker6] Docker compose多容器运行与管理

    六.Docker compose docker compose就是通过yml文件来定义和运行多个容器docker应用程序的工具,三步过程就能跑起一个compose: 定义应用程序的环境(yml中) 定 ...

  7. hdu1171kmp果题

    题目链接:http://icpc.njust.edu.cn/Problem/Hdu/1711/ #include<bits/stdc++.h> using namespace std; t ...

  8. UVa 11059 最大乘积 java 暴力破解

    题目链接: https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_proble ...

  9. Tarjan算法(模板)

    算法思想: 首先要明确强连通图的概念,一个有向图中,任意两个点互相可以到达:什么是强连通分量?有向图的极大连通子图叫强连通分量. 给一个有向图,我们用Tarjan算法把这个图的子图(在这个子图内,任意 ...

  10. coding++ :JS对日期的神操作封装版

    格式化日期: /** * 格式化日期 * @param fmt 例如:yyyy-MM-dd 等 * @returns {*} * @constructor */ Date.prototype.Form ...