当前位置: 首页 > 科技观察

JS中new调用函数原理详解

时间:2023-03-12 00:46:20 科技观察

JavaScript经常使用构造函数创建对象(通过new操作符调用函数),那么使用new调用函数会发生什么呢?在解释幕后发生的事情之前,让我们先看几个例子。1.看三个例子1.1Noreturnstatementconstructor***没有return语句,这也是使用构造函数时默认的,***会返回一个新的对象,如下:functionFoo(age){this.age=年龄;}varo=newFoo(111);console.log(o);这是使用构造函数创建对象的常见过程,输出为{age:111}。1.2返回对象类型数据构造器***返回对象类型数据:functionFoo(age){this.age=age;return{type:"我显式返回了"};}varo=newFoo(222);console.标识);打印出{type:'Ireturnitexplicitly'},也就是说return之前的工作白做了,***返回return之后的对象。1.3返回基本类型数据。是不是说只要构造函数体内有return,返回的数据就是return之后的数据?再看返回基本类型数据的情况:functionFoo(age){this.age=age;return1;}varo=newFoo(333);console.log(o);打印出{age:333},和no返回的效果是一样的。和预想的不一样。其背后的原因请看下面的分析。2.Behindthescenes2.1非箭头函数的情况在使用new操作符创建对象时,ES5官方文档在函数定义部分13.2.2定义如下[[Construct]]:当[[Construct]]Function对象Fiscalledwithpossiblyemptylistofarguments的内部方法,采取以下步骤:LetobjbeanewlycreatednativeECMAScriptobject.Setalltheinternalmethodsofobjasspecifiedin8.12.Setthe[[Class]]internalpropertyofobjtoObject.Setthe[[Extensible]]internalpropertyofobjtotrue.Letprotobethevalueofcallingthe[[Get]]internalpropertyofwithar.(proto)isObject,将对象的[[Prototype]]内部属性设置为proto。如果Type(proto)不是Object,则将obj的[[Prototype]]内部属性设置为标准内置对象原型对象,如15.2.4所述。让结果调用F的[[Call]]内部属性,提供objas这个值并提供参数列表]Type作为[[Consult)isObjectthenreturnresult.Returnobj。看第8、9步:8)调用FunctionF,将其返回值赋给result;其中,F执行时的实参是传递给[[Construct]]的参数(即F本身),F内部的this指向obj;9)如果结果是Object类型,则返回结果;这也解释了如果构造函数显式返回对象类型,那么直接返回对象,而不是返回最初创建的对象***看步骤10:10)如果F返回的不是对象类型(步骤9不成立),然后返回创建的对象obj。如果构造函数没有显式返回对象类型(要么显式返回基本数据类型,要么不直接返回),则返回最初创建的对象。2.2箭头函数如果构造函数是箭头函数怎么办?箭头函数中没有[[Construct]]方法,不能用new调用,会报错。注意:其中[[Construct]]指的是构造函数本身。相关规范在ES6的官方文档中有提到,但是ES6以来的官方文档非常难懂,这里就不表述了。3、调用new函数的完整过程3.1中文说明及相关代码分析除箭头函数外的任何函数都可以用new调用。幕后发生的事情在上一节用英文描述的很清楚,再用中文描述如下:1)创建一个ECMAScript原生对象obj;2)为obj设置native对象的内部属性;(与原型属性不同,内部属性表示为[[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的原型值;(函数原型属性可以改写,如果改成非对象类型,obj的[[Prototype]]指向Object的原型对象)8)9)10)见上节分析。(判断返回什么)对于第7步的情况,看下面代码:functionFoo(name){this.name=name;}varo1=newFoo("xiaoming");console.log(o1.__proto__===Foo.prototype);//true//重写构造函数的prototype属性为非对象类型,实例内部的[[Prototype]]属性指向Object原型对象//因为实例是对象类型数据的,默认会继承内置对象的原型,//如果构造函数的原型不满足形成原型链的要求,则跳过直接关联内置对象原型Foo。prototype=1;varo2=newFoo("xiaohong");console.log(o2.__proto__===Foo.prototype);//falseconsole.log(o2.__proto__===Object.prototype);//true3.2更简洁的语言描述如果执行newFoo(),流程如下:1)创建一个新的对象o;2)给新对象的内部属性赋值,关键是给[[Prototype]]属性赋值,构造原型链(如果构造函数的原型是Object类型,则指向指向构造函数的原型;否则指向Object对象的原型);3)执行函数Foo,在执行过程中,内部this指向新创建的对象o;4)如果Foo内部显式返回对象类型数据,则返回数据,执行结束;否则,返回新创建的对象o。4.一些解释4.1判断是否是Object类型一条数据是否是Object类型可以通过instanceof运算符来判断:如果xinstanceofObject返回true,则x是Object类型。上面可以看出,nullinstanceofObject返回false,所以null不是Object类型,虽然typeofnull返回的是“Object”。4.2instanceof原理instanceof的工作原理是:在表达式xinstanceofFoo中,如果Foo的原型(即Foo.prototype)出现在x的原型链中,则返回true,否则返回false。因为函数的原型是可以改写的,所以会出现x通过Foonew出来后Foo的原型被完全改写的情况,xinstanceofFoo返回false。因为实例创建后构造函数的原型被重写,实例指向的原型不再是构造函数的新原型,见如下代码:constFoo=function(){};consto=newFoo();oinstanceofFoo;//true//重复写入Foo原型Foo.prototype={};oinstanceofFoo;//false