详解 JS 中 new 调用函数原理
JavaScript 中经常使用构造函数创建对象(通过 new 操作符调用一个函数),那在使用 new 调用一个函数的时候到底发生了什么?先看几个例子,再解释背后发生了什么。
1)看三个例子
1.1 无 return 语句
构造函数最后没有 return 语句,这也是使用构造函数时默认情况,最后会返回一个新对象,如下:
function Foo(age) {
this.age = age;
}
var o = new Foo(111);
console.log(o);
这是常见的使用构造函数创建对象的过程,打印出来的是 {age: 111}。
1.2 return 对象类型数据
构造函数最后 return 对象类型数据:
function Foo(age) {
this.age = age;
return { type: "我是显式返回的" };
}
var o = new Foo(222);
console.log(o);
打印出来的是 {type: '我是显式返回的'},也就是说,return 之前的工作都白做了,最后返回 return 后面的对象。
1.3 return 基本类型数据
那是不是只要构造函数体内最后有 return,返回都是 return 后面的数据呢?
我们看下返回基本类型数据的情况:
function Foo(age) {
this.age = age;
return 1;
}
var o = new Foo(333);
console.log(o);
打印出来的是 {age: 333},和没有 return 时效果一样。跟预期不一样,背后你原理看下面分析。
2)背后原理
2.1 非箭头函数的情况
当使用 new 操作符创建对象是,ES5 官方文档在 函数定义 一节中做了如下定义 13.2.2 [[Construct]]:
When the [[Construct]] internal method for a Function object F is called with a possibly empty list of arguments, the following steps are taken:
- Let obj be a newly created native ECMAScript object.
- Set all the internal methods of obj as specified in 8.12.
- Set the [[Class]] internal property of obj to Object.
- Set the [[Extensible]] internal property of obj to true.
- Let proto be the value of calling the [[Get]] internal property of F with argument "prototype".
- If Type(proto) is Object, set the [[Prototype]] internal property of obj to proto.
- If Type(proto) is not Object, set the [[Prototype]] internal property of obj to the standard built-in Object prototype object as described in 15.2.4.
- Let result be the result of calling the [[Call]] internal property of F, providing obj as the this value and providing the argument list passed into [[Construct]] as args.
- If Type(result) is Object then return result.
- Return obj.
看第 8、9 步:
8)调用函数
F,将其返回值赋给result;其中,F执行时的实参为传递给[[Construct]](即F本身) 的参数,F内部this指向obj;
9)如果result是Object类型,返回result;
这也就解释了如果构造函数显式返回对象类型,则直接返回这个对象,而不是返回最开始创建的对象。
最后在看第 10 步:
10)如果
F返回的不是对象类型(第 9 步不成立),则返回创建的对象obj。
如果构造函数没有显式返回对象类型(显式返回基本数据类型或者直接不返回),则返回最开始创建的对象。
2.2 箭头函数的情况
那如果构造函数是箭头函数怎么办?
箭头函数中没有 [[Construct]] 方法,不能使用 new 调用,会报错。
NOTICE:其中 [[Construct]] 就是指构造函数本身。
相关规范在 ES6 的官方文档 中有提,但自从 ES6 以来的官方文档巨难懂,在此不做表述。
3)new 调用函数完整过程
3.1 中文描述及相关代码分析
除了箭头函数之外的任何函数,都可以使用 new 进行调用,背后发生了什么,上节英文讲述的很清楚了,再用中文描述如下:
1)创建 ECMAScript 原生对象 obj;
2)给 obj 设置原生对象的内部属性;(和原型属性不同,内部属性表示为 [[PropertyName]],两个方括号包裹属性名,并且属性名大写,比如常见 [[Prototype]]、[[Constructor]])
3)设置 obj 的内部属性 [[Class]] 为 Object;
4)设置 obj 的内部属性 [[Extensible]] 为 true;
5)将 proto 的值设置为 F 的 prototype 属性值;
6)如果 proto 是对象类型,则设置 obj 的内部属性 [[Prototype]] 值为 proto;(进行原型链关联,实现继承的关键)
7)如果 proto 是不对象类型,则设置 obj 的内部属性 [[Prototype]] 值为内建构造函数 Object 的 prototype 值;(函数 prototype 属性可以被改写,如果改成非对象类型,obj 的 [[Prototype]] 就指向 Object 的原型对象)
8)9)10)见上节分析。(决定返回什么)
对于第 7 步的情况,见下面代码:
function Foo(name) {
this.name = name;
}
var o1 = new Foo("xiaoming");
console.log(o1.__proto__ === Foo.prototype); // true
// 重写构造函数原型属性为非对象类型,实例内部 [[Prototype]] 属性指向 Object 原型对象
// 因为实例是一个对象类型的数据,默认会继承内建对象的原型,
// 如果构造函数的原型不满足形成原型链的要求,那就跳过直接和内建对象原型关联
Foo.prototype = 1;
var o2 = new Foo("xiaohong");
console.log(o2.__proto__ === Foo.prototype); // false
console.log(o2.__proto__ === Object.prototype); // true
3.2 更简洁的语言描述
若执行 new Foo(),过程如下:
1)创建新对象 o;
2)给新对象的内部属性赋值,关键是给[[Prototype]]属性赋值,构造原型链(如果构造函数的原型是 Object 类型,则指向构造函数的原型;不然指向 Object 对象的原型);
3)执行函数 Foo,执行过程中内部 this 指向新创建的对象 o;
4)如果 Foo 内部显式返回对象类型数据,则,返回该数据,执行结束;不然返回新创建的对象 o。
4)几点说明
4.1 判断是否是 Object 类型
关于一个数据是否是 Object 类型,可以通过 instanceof 操作符进行判断:如果 x instanceof Object 返回 true,则 x 为 Object 类型。
由上可知,null instanceof Object 返回 false,所以 null 不是 Object 类型,尽管typeof null 返回 "Object"。
4.2 instanceof 原理
instanceof 的工作原理是:在表达式 x instanceof Foo 中,如果 Foo 的原型(即 Foo.prototype)出现在 x 的原型链中,则返回 true,不然,返回 false。
因为函数的原型可以被改写,所以会出现在 x 通过 Foo new 出来之后完全改写 Foo 的原型 x instanceof Foo 返回 false 的情况。因为实例创建之后重写构造函数原型,实例指向的原型已经不是构造函数的新的原型了,见下面代码:
const Foo = function() {};
const o = new Foo();
o instanceof Foo; // true
// 重写 Foo 原型
Foo.prototype = {};
o instanceof Foo; // false
参考资料
What values can a constructor return to avoid returning this? [[Construct]] internal method
原文地址:https://segmentfault.com/a/1190000015424508
详解 JS 中 new 调用函数原理的更多相关文章
- 详解Webwork中Action 调用的方法
详解Webwork中Action 调用的方法 从三方面介绍webwork action调用相关知识: 1.Webwork 获取和包装 web 参数 2.这部分框架类关系 3.DefaultAction ...
- 举例详解Python中的split()函数的使用方法
这篇文章主要介绍了举例详解Python中的split()函数的使用方法,split()函数的使用是Python学习当中的基础知识,通常用于将字符串切片并转换为列表,需要的朋友可以参考下 函数:sp ...
- 详解js中的闭包
前言 在js中,闭包是一个很重要又相当不容易完全理解的要点,网上关于讲解闭包的文章非常多,但是并不是非常容易读懂,在这里以<javascript高级程序设计>里面的理论为基础.用拆分的方式 ...
- js中直接调用函数和new函数的区别
如果函数返回值为常规意义上的值类型(Number.String.Boolean)时,new函数将会返回一个该函数的实例对象,而如果函数返回一个引用类型(Object.Array.Function),则 ...
- js中定时器调用函数时为什么会有引号
之前在学习的时候并没有发现的细节,关于js中,定时器的问题 这里我们写两个延时器 setTimeout(func, 0); setTimeout("func()", 0);定时器中 ...
- 详解JS中 call 方法的实现
摘要:本文将全面的,详细解析call方法的实现原理 本文分享自华为云社区<关于 JavaScript 中 call 方法的实现,附带详细解析!>,作者:CoderBin. 本文将全面的,详 ...
- 详解js中的寄生组合式继承
寄生组合式继承是js中最理想的继承方式, 最大限度的节省了内存空间. js中的寄生组合式继承要求是: 1.子对象有父对象属性的副本, 且这些不应该保存在子对象的prototype上. 2. ...
- jQuery动画高级用法(上)——详解animation中的.queue()函数
如果你拿着一个疑问去找专业人士寻找答案,那么你的一个疑问会变成三个,因为他会用另外两个令你更加一头雾水的名词来解释你的这个疑问. 我想这是大多数,包括我在内,IT人在学习过程中碰到的最大问题.当你有一 ...
- 详解js中的this指向
this指向问题是个老生常谈的问题了,现在我给大家一个例子 var obj={ bar:'Cynthia' , foo:function(){ console.log(this.bar,"w ...
随机推荐
- Linux中ext2文件系统的结构
1.ext2产生的历史 最早的Linux内核是从MINIX系统过渡发展而来的.Linux最早的文件系统就是MINIX文件系统.MINIX文件系统几乎到处都是bug,采用的是16bit偏移量,最大容量为 ...
- datagrid 里面的formatter方法
A.{field:'station_staus',title:'工位状态',width:250,align:'center',formatter: function(value,row,index){ ...
- 让javascript加载速度倍增的方法(解决JS加载速度慢的问题)
通常我们的网站里面会加载一些js代码,统计啊,google广告啊,百度同盟啊,阿里妈妈广告代码啊,一堆,最后弄得页面加载速度很慢,很慢. 解决办法:换一个js包含的方式,让javascript加载速度 ...
- C 碎片四 流程控制
前面介绍了程序中用到的一些基本要素(常量,变量,运算符,表达式),他们是构成程序的基本成分,下面将介绍C语言中流程控制的三种结构:顺序结构.分支结构.循环结构 一.顺序结构 顺序结构的程序设计是最简单 ...
- 1269 匈牙利游戏 2012年CCC加拿大高中生信息学奥赛
1269 匈牙利游戏 2012年CCC加拿大高中生信息学奥赛 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description ...
- AngularJs Type error : Cannot read property 'childNodes' of undefined
参考博客: https://blog.csdn.net/u011127019/article/details/73087868 在AngularJs和JQuery插件共存咋项目中经常会遇到如下异常 T ...
- [QualityCenter]设置工作流脚本-根据某字段是否包含指定字符串来判断其他字段的选值
需求:当在创建或更改值时,自动判断A字段是否包含B值,然后自动填写相应的内容. 如以下例子: 在脚本编辑器新建一个函数TestPlan_Test_New,然后编写脚本如下: '通过主题判断项目内容 ...
- django ORM 简单示例简绍
简单 models 操作 class Host(models.Model): nid = models.AutoField(primary_key=True) #Nid为主键 hostname = m ...
- Azure School,让你系统化快速学习人工智能
要说目前最热门的技术,非人工智能莫属了,让计算机程序能够看懂.听懂.读懂.理解我们的世界!想想就激动!! 上至高大上的个人数字化助理,下至P图软件,各种应用都开始增加AI相关的功能,试问又有哪个技术爱 ...
- 二、C++复数的实现
C++复数的实现 在数字图像处理领域,复数这一类型会被经常使用到.但是在C++和Qt中都没有可以使用的复数类.为了今后的方便,我们可以自己定义一个C++复数类,以便将来使用. 一.复数的属性 复数包含 ...