初涉JavaScript模式 (4) : 构造函数
什么是构造函数?
构造函数 是一种特殊的方法 主要用来在创建对象时初始化对象 即为对象成员变量赋初始值,总与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方法,但是新问题又来了,如果这种方法很多,我们就不得不在全局定义很多函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了,好在这些我们可以用原型模式来解决(下一篇,呵呵)
后记 :
关于这篇,我开始是想写的非常全,但是奈何经验不足,大体框架也是按照书上的逻辑,最大的收获就是真的用心去看书了。如果在文中发现错误,请指正,共同进步