一直以来使用new关键字对构造函数进行实例化,但是从未追究过new实例化的时候到底做了什么事,今天来研究一下。
一、构造函数
function Animal(name){
this.name = name;
this.sleep = function(){
console.log(`${this.name}正在睡觉`);
}
}
/*使用new关键字实例化*/
let dog = new Animal("小花");
dog.sleep(); //小花正在睡觉
/*作为普通函数调用*/
let cat = Animal("小黄");
console.log(name); //小黄
sleep(); //小黄正在睡觉
从上面的示例中,可以看出,函数Animal没有通过new关键字调用的话,它只是直接执行了一遍代码中的函数体,函数体中的this指向window,给window增加了一个name属性和sleep方法;打印cat的话也只是显示undefined,因为只是执行了一遍函数体,并没有任何返回值。
但是我们在控制台打印dog时,结果如下:
dog是Animal的实例,拥有name属性和sleep方法,__prpto__
是dog的原型,constructor下的length属性统计的是函数命名参数的数量(不定参数对此影响),name是es6为所有函数新增了name属性。
说了这么多,好像还没有说明一下到底什么是构造函数呢?下面以普通函数作为对比来说一下:
命名规则:构造函数一般是首字母大写,普通函数遵照小驼峰式命名法。
函数调用(以fn为例):
构造函数使用new关键字调用fn,构造函数内部会创建一个新的对象,即f的实例。 函数内部的this指向新创建的f的实例。默认的返回值是f的实例。
普通函数直接调用fn, 在调用函数的内部不会创建新的对象。 函数内部的this指向调用函数的对象(如果没有对象调用,默认是window),返回值由函数体内显式的return语句决定。
二、new到底做了什么?
js函数有两个不同的内部方法:[[Call]]和[[Construct]]。
当通过new关键字调用函数时,执行的是[[Construct]]函数,它负责创建一个通常被称作实例的新对象,然后再执行函数体,将this绑定到实例上;构造函数的prototype属性被用做新对象的原型。
如果不通过new关键字调用函数,则执行的是[[Call]]函数,从而直接执行代码中的函数体,所以具有[[Construct]]方法的函数被统称为构造函数。
注意:不是所有的函数都有[[Construct]]方法,因此不是所有的函数都可以用new来调用,例如箭头函数就没有这个[[Construct]]方法。
总结一下,使用new命令时,它后面的函数调用就不是正常的调用,而是依次执行下面的步骤:
- 创建一个空对象,作为将要返回的对象实例
- 将这个空对象的原型,指向构造函数的 prototype 属性
- 将这个空对象赋值给函数内部的 this 关键字
- 开始执行构造函数内部的代码