构造函数实际上在前面我们已经使用过,在我们生成数组的时候,我们学过一个方式,即 var arr = new Array(); 这种方式来创造一个数组。我们发现,这个 Array 实际上就是一个构造函数,我们可以验证:

可以看到,Array 实际上就是一个函数,并且这个函数就是我们即将介绍的构造函数。
在介绍构造函数之前,我们先来考虑这样一个需求,我们如何通过一个函数来创造对象。我们自然能想到的一个方式是使用函数的返回值返回一个对象,如下例所示
js
function createObject(){
return {
name:"张三",
age:"18"
}
}
var user = createObject();//这里创造了一个对象
console.log(user.name);像这样一个函数,调用一次,就会生成一个对象,并且这个对象是通过字面量的形式生成的,所以每次创建的都是一个新的对象。这里每次创造的对象都是一样的,这其实不太好,我其实在创造对象的时候就通过参数告诉他姓名和年龄,他就会根据参数创造出指定对象,如下所示:
js
function createObject(name,age){
return {
name:name,
age:age,
}
}
var user = createObject("张三",22);//这里创造了一个对象
console.log(user.age);//输出22上面的例子巧妙利用了参数,在创造对象的时候就可以自定义对象的,从而创造出指定的对象。
注意,
name:name,这样的写法,我们知道,函数的属性名只能是字符串,所以第一个 name 会转换为字符串,是不会被执行的,而第二个 name 则是正儿八经的参数,是一个变量,是一个表达式,会取这个变量的值。形如这种属性名和变量名一样的情况,例如name:name,我们其实可以简写为name,。如果对象里面的属性是一个函数,例如say:function(){ console.log(1); },可以简写为say(){console.log(1);}
用函数来创建对象是一个很常用的方法,可以帮助我们快速多次创建对象。
除了这种通过函数返回值创造对象以外,还有另一种方式,就是 new 函数名(参数) 来创造对象。如果使用 new 关键字来创造对象,那么这个函数就是一个构造函数,显然,区分一个函数是不是构造函数,其实就是看他用来干什么,如果我用来执行,那就是一个普通函数,但如果我这个函数的功能是通过 new 关键字来创造对象,那这就是一个构造函数。所以实际上,构造函数本身和其他函数没有区别,只是功能上,构造函数就是通过 new 关键字来创造对象的,我们一般不会直接去调用构造函数。当然,为了实现通过 new 关键字创造对象的功能,构造函数内部就会有一些特殊的规则。
构造函数的具体规则如下:
- 在函数运行之前,会创建一个新对象,并让 this 关键字指向该对象
- 函数运行结束之后,返回 this 对象
js
function User(name,age){
//在函数运行之前,逻辑上相当于this = {};,当然,只是逻辑上,我们并不能给this赋值
this.name = name;
this.age = age;
//函数运行结束之后,逻辑上相当于return this;
}
//我们将他视为一个构造函数,使用构造函数的方式去使用它。
var u = new User("张三",12);
console.log(u.name);//此时这个对象就可以直接用了以上介绍了两种通过函数来创造对象的方式,以上两种方式都可以用于创造对象,但是如果我们这个函数的功能就是专门用来创造对象,更加推荐使用构造函数的方式,至于原因可以留在 JS 进阶部分介绍。
如果构造函数不需要传递参数,可以省略括号
可以看到,构造函数本身是不需要返回值的,因为他使用了 new 关键字后,不论如何都会创造一个对象,所以在其他很多语言里,构造函数其实都是不允许有返回值的,在 JS 里其实允许我们有返回值,但是只能返回一个对象,用户可以自行返回一个对象来替换返回结果。
js
function User(name,age){
this.name = name;
this.age = age;
return {};//这里直接返回空对象
}
var u = new User("张三",12);
console.log(u);//此时得到的始终是空对象虽然构造函数可以手动返回一个对象来替换生成的对象,但实际上我们在使用构造函数的时候肯定不会这么干。
我们可以认为,所有的对象都是通过构造函数来创建的,在前面,我们创造数组的时候使用 var arr = [1,2,3] 这种语法,实际上底层也是通过构造函数来实现的。例如 var obj = {} 这也是一个语法糖,也是表象,其底层是通过 var obj = new Object() 来实现的,即通过 Object 这一个构造函数来实现的对象创建。
接下来考虑一个问题
js
function User(name,age){
this.name = name;
this.age = age;
}对于这样一个构造函数,我肯定希望通过 new 来调用,对吧,但是实际上它是完全可以直接调用的,直接调用的话那此时 this 应该指向全局对象,显然,如果直接调用,就会污染全局变量,如下:
js
function User(name,age){
this.name = name;
this.age = age;
}
var obj = User("张三",12);//如果直接调用,此时函数内的this指向window
console.log(name);//此时name和age就变成了全局变量显然,构造函数和普通函数只有使用上的区别,当使用 new 关键字的时候,这就是一个构造函数,否则就是一个普通函数。但很明显,如果混用,像上面这样,将构造函数当做普通函数调用,就会产生很多问题。为了避免用户把构造函数当做普通函数来使用,我们必须要判断这个函数在调用的时候是普通调用还是通过 new 关键字来调用的。我们可以通过 new.target 来判断,如果是普通调用,则该表达式返回 undefined,如果是通过 new 关键字调用的,则返回构造函数本身。
js
function test(){
console.log(new.target);
}
test();//undefined
new test();//test函数本身有了这个表达式之后,我们就可以通过 new.target 来判断这个函数是通过 new 关键字来调用的还是通过普通方式调用的,从而避免一些问题。
其实 Array 函数本身就是这样搞得,读者可以自行尝试,不管是通过 new Array(1,2,3) 还是 Array(1,2,3),这两种调用方式,返回的都是一个正常的数组,我们其实就可以通过判断 new.target 来模拟这个效果。
js
function MyArray(a,b,c){//这里我默认只能传3个
if(new.target){//如果有,说明是new
this[0]=a;
this[1]=b;
this[2]=c;
}else{//否则是正常调用
return [a,b,c];
}
}