初涉JavaScript模式 (4) : 构造函数

时间:2022-05-09 09:36:09

什么是构造函数?

构造函数 是一种特殊的方法 主要用来在创建对象时初始化对象 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中 特别的一个类可以有多个构造函数 可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载 ---引自百度百科

在JavaScript中是没有类的,但是我们有构造函数(是不是很怪),很深的东西我也说不来,直接上代码:

```javascript
//工厂模式
function createCat(name){
var o = {};
o.name = name;
o.cry = function(){
console.log(this.name + " : " + "喵喵喵!");
};
return o;
}
var tom = createCat("tom");
tom.cry();
//构造函数模式
function Mouse(name){
this.name = name;
this.cry = function(){
console.log(this.name + " : " + "吱吱吱!");
}
}
var jerry = new Mouse("jerry",5);
jerry.cry();
```

以上实例代码分别是工厂模式和构造函数模式,构造函数模式相比于工厂模式,存在一下几个不同之处:

  • 没有显式的创建对象
  • 直接将属性和方法赋给了this对象
  • 没有return语句

实际上,调用构造函数会经历以下几个步骤:

  • 创建一个新对象
  • 将构造函数的作用域赋给新对象(因此this就指向了新对象)
  • 执行构造函数中的代码(给这个新对象添加方法和属性)
  • 返回这个新对象

我们要注意以上是JS隐式执行的,我们也可以显式的来,上代码:

```javascript
function Mouse(name){
var o = {};
var that = o;
that.name = name;
that.cry = function(){
console.log(that.name);
}
return that;
}
var jerry = new Mouse("jerry",5);
jerry.cry();
```

在隐式的构造函数中,我们要注意一个问题,return是隐式返回this的,但是我们可以手动return ,可是这有可能会return我们意料之外的结果:

```javascript
function Mouse(name){
this.name = name;
this.cry = function(){
console.log(this.name + " : " + "吱吱吱!");
}
return "WeiRan"
}
Mouse.prototype.a = {};
var jerry = new Mouse("jerry",5);
jerry.cry(); // jerry : 吱吱吱!
```

以上代码,虽然我们手动返回的是字符串但是真正返回的还是this,其实JS会判断我们返回的是不是对象,数组,function 如果不是这还是会返回this(即便是undefined,null,空)

对象识别

虽然工厂模式解决了多个相似对象的问题,却没有解决对象识别的问题,但是构造函数模式却解决了这个问题,在构造函数模式中,每一个实例都有一个属性constructor,该属性指向他么的构造函数,可以这样来检测对象的类型

```javascript
console.log(jerry.constructor == Mouse); //true
```

但是这样做其实是不严谨的,可能会出现以下的问题:

```javascript
//构造函数模式
function Mouse(name){
this.name = name;
this.cry = function(){
console.log(this.name + " : " + "吱吱吱!");
}
return;
}
var jerry = new Mouse("jerry",5);
Mouse.prototype.constructor = {};
console.log(jerry.constructor);//Object {}
```

这段代码说明,jerry的constructor其实指向的是Mouse的prototype的constructor,这样很不严谨,故我们可以采用instanceof来检测对象类型

```javascript
//构造函数模式
function Mouse(name){
this.name = name;
this.cry = function(){
console.log(this.name + " : " + "吱吱吱!");
}
return;
}
var jerry = new Mouse("jerry",5);
Mouse.prototype.constructor = {};
console.log(jerry.constructor == Mouse);//false
console.log(jerry instanceof Mouse); //true
```

将构造函数当作函数

构造函数与其他函数的唯一不同,就在于调用他们的方式不同。但是构造函数也是函数,故我们可以以普通函数的方式执行构造函数,任何函数也可以通过new来调用(慎用),下面我列举了几个调用方式:

```javascript
//构造函数模式
function Mouse(name){
this.name = name;
this.cry = function(){
console.log(this.name + " : " + "吱吱吱!");
}
}
//当作构造函数使用
var m1 = new Mouse("m1");
m1.cry(); //m1 : 吱吱吱!
//当作普通函数使用
Mouse("m2");
window.cry(); //m2 : 吱吱吱!
//在其他作用域调用
var obj = {};
Mouse.call(obj,"m3");
obj.cry(); //m3 : 吱吱吱!
```

第一种是用最常见的调用构造函数的方式,第二种,所有的属性和方法都被添加到Global对象上去了(在浏览器中就是window),第三种,我们制定了作用域,所以obj就有了所有的属性和方法

构造函数的问题

构造函数虽然好用,但是他也有缺点,使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍,即每个实例的方法都是不同的(虽然代码一样),上代码:

```javascript
//构造函数模式
function Mouse(name){
this.name = name;
this.cry = function(){
console.log(this.name + " : " + "吱吱吱!");
}
}
var m1 = new Mouse();
var m2 = new Mouse();
console.log(m1.cry == m2.cry); //false
```

然而,创建两个完成相同功能的Function实例的确没有必要,而且有this对象在,根本没有必要在执行代码前就把函数绑定到特定的对象上去,以此我们可以用下面的代码来解决这个问题:

```javascript
//构造函数模式
function Mouse(name){
this.name = name;
this.cry = cry;
}
function cry(){
alert(this.name);
}
```

在上面的例子中我们把cry方法定义在了构造函数外部,这样大大避免了资源的浪费,我们创建的实例调用的都是同一个cry方法,但是新问题又来了,如果这种方法很多,我们就不得不在全局定义很多函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了,好在这些我们可以用原型模式来解决(下一篇,呵呵)

后记 :

关于这篇,我开始是想写的非常全,但是奈何经验不足,大体框架也是按照书上的逻辑,最大的收获就是真的用心去看书了。如果在文中发现错误,请指正,共同进步