详解JS中new调用函数原理

开发 前端
JavaScript 中经常使用构造函数创建对象(通过 new 操作符调用一个函数),那在使用 new 调用一个函数的时候到底发生了什么?先看几个例子,再解释背后发生了什么。

[[235817]]

JavaScript 中经常使用构造函数创建对象(通过 new 操作符调用一个函数),那在使用 new 调用一个函数的时候到底发生了什么?先看几个例子,再解释背后发生了什么。

一、看三个例子

1.1 无 return 语句

构造函数***没有 return 语句,这也是使用构造函数时默认情况,***会返回一个新对象,如下:

 

  1. function Foo(age) {  
  2.   this.age = age;  
  3.   
  4.  
  5. var o = new Foo(111);  
  6. console.log(o); 

这是常见的使用构造函数创建对象的过程,打印出来的是 {age: 111}。

1.2 return 对象类型数据

构造函数*** return 对象类型数据:

 

  1. function Foo(age) { 
  2.   this.age = age; 
  3.   return { type: "我是显式返回的" }; 
  4.  
  5. var o = new Foo(222); 
  6. console.log(o); 

打印出来的是 {type: '我是显式返回的'},也就是说,return 之前的工作都白做了,***返回 return 后面的对象。

1.3 return 基本类型数据

那是不是只要构造函数体内***有 return,返回都是 return 后面的数据呢?

我们看下返回基本类型数据的情况:

 

  1. function Foo(age) {  
  2.   this.age = age;  
  3.  
  4.   return 1;  
  5.  
  6.  
  7. var o = new Foo(333);  
  8. console.log(o); 

打印出来的是 {age: 333},和没有 return 时效果一样。跟预期不一样,背后你原理看下面分析。

二、背后原理

2.1 非箭头函数的情况

当使用 new 操作符创建对象是,ES5 官方文档在 函数定义 一节中做了如下定义 13.2.2 [[Construct]]:

 

  1. When the [[Construct]] internal method for a Function object F is called with a possibly empty list of arguments, the following steps are taken:  
  2.  
  3.     Let obj be a newly created native ECMAScript object.  
  4.     Set all the internal methods of obj as specified in 8.12.  
  5.     Set the [[Class]] internal property of obj to Object.  
  6.     Set the [[Extensible]] internal property of obj to true 
  7.     Let proto be the value of calling the [[Get]] internal property of F with argument "prototype" 
  8.     If Type(proto) is Object, set the [[Prototype]] internal property of obj to proto.  
  9.     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.  
  10.     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.  
  11.     If Type(result) is Object then return result.  
  12.     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 以来的官方文档巨难懂,在此不做表述。

三、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 步的情况,见下面代码:

 

  1. function Foo(name) {  
  2.   this.name = name 
  3.  
  4.  
  5. var o1 = new Foo("xiaoming");  
  6. console.log(o1.__proto__ === Foo.prototype); // true  
  7.  
  8.  
  9. // 重写构造函数原型属性为非对象类型,实例内部 [[Prototype]] 属性指向 Object 原型对象  
  10. // 因为实例是一个对象类型的数据,默认会继承内建对象的原型,  
  11. // 如果构造函数的原型不满足形成原型链的要求,那就跳过直接和内建对象原型关联  
  12. Foo.prototype = 1;  
  13. var o2 = new Foo("xiaohong");  
  14. console.log(o2.__proto__ === Foo.prototype); // false  
  15. 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.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 的情况。因为实例创建之后重写构造函数原型,实例指向的原型已经不是构造函数的新的原型了,见下面代码:

 

  1. const Foo = function() {};  
  2. const o = new Foo();  
  3. o instanceof Foo; // true  
  4. // 重写 Foo 原型  
  5. Foo.prototype = {};  
  6. o instanceof Foo; // false  

 

责任编辑:庞桂玉 来源: segmentfault
相关推荐

2015-07-10 10:40:29

JSPatchOCJS

2015-07-10 10:34:57

JSPatchJS调用

2011-08-22 17:13:00

LuaC++函数

2017-03-12 19:51:38

js实用跨域

2011-06-28 13:12:07

Qt 调用 DLL DLL

2009-07-03 17:33:06

JSP中调用JavaB

2023-10-23 19:27:21

Go函数

2023-05-31 11:57:38

js函数计算高度

2010-09-08 14:03:41

SQL函数CONVERT

2021-09-06 13:12:05

前端JavaScript编程

2016-09-26 17:15:51

2023-10-27 11:27:14

Go函数

2010-09-09 11:12:49

SQL函数DATALENGTH

2017-01-13 15:45:05

Linuxfork函数详解

2010-09-06 14:58:42

SQL函数

2010-10-09 10:30:03

JS event

2022-06-09 08:17:30

Python__new__

2010-09-10 16:02:13

SQLCHARINDEX函数

2012-04-16 13:47:37

JavaMatlab

2017-07-27 15:52:10

函数调用堆栈结构
点赞
收藏

51CTO技术栈公众号