JS面向对象编程(二):构造函数

352 阅读4分钟

一、什么是构造函数?

  构造函数 ,是一种特殊的方法。主要用来为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中,对于JavaScript的内置对象Number()、String()、Boolean()、Object()、Array()、Function()、Date()、RegExp()、Error()等都是构造函数;

构造函数的特点:

  1. 构造函数的首字母大写,用来区分于普通函数
  2. 内部使用的this对象,来指向即将要生成的实例对象
  3. 使用New来生成实例对象

举个栗子:

function Person(name, age, job){ 
     this.name = name; 
     this.age = age; 
     this.job = job; 
     this.sayHello = function(){ 
        console.log(this.name+":Hello!"); 
     }; 
} 
var person1 = new Person("lilei", 26, "Teacher"); //实例对象 person1
var person2 = new Person("xiaom", 27, "Doctor");  //实例对象 person2

  构造函数与其他函数的唯一区别,就在于调用它们的方式不同。构造函数毕竟也是函数,不存在定义构造函数的特殊语法。任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数

二、构造函数与对象

  上一节我们说到在JavaScript中,几乎所有的事物都是对象:对象只是带有属性和方法的特殊数据类型
  但是基本类型值不是对象,因而从逻辑上讲它们不应该有方法,实际上我们创建 String 类型时后台自动做了一些处理:

  1. 创建 String 类型的一个实例;
  2. 在实例上调用指定的方法;
  3. 销毁这个实例。
var s1 = new String("some text"); 
var s2 = s1.substring(2); 
s1 = null; 

  基本类型值不是对象,只有引用类型是对象,但是 我们可以让String成为引用类型:

var s1 = new String("some text"); 

  当我们手动new一个字符串s1的时候,s1既是String又是引用类型,所以他是一个对象

  引用类型与基本包装类型的主要区别就是对象的生存期。使用 new 操作符创建的引用类型的实例, 在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,则只存在于一 行代码的执行瞬间,然后立即被销毁;

三、创建实例对象

要创建 Person 的新实例,必须使用 new 操作符,调用构造函数创建对象经过了以下几个过程:

  1. 创建一个新对象;
  2. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
  3. 执行构造函数中的代码(为这个新对象添加属性);
  4. 返回新对象;

  在前面例子中,person1 和 person2分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数) 属性,该属性指向 Person:

console.log(person1.constructor == Person); //true 
console.log(person2.constructor == Person); //true 

四、判断对象类型

  在JS中判断一个变量的类型经常会用 typeof 运算符,但是在使用 typeof 运算符来判断引用类型时,无论引用的是什么类型的对象,它都返回"object"。所以在判断对象的类型时我们可以使用 instanceof 运算符:

console.log(person1 instanceof Object); //true 

五、 构造函数缺点

  使用构造函数时每个方法都要在每个实例上重新创建一遍:

function Person(name, age, job){ 
     this.name = name; 
     this.age = age; 
     this.job = job; 
     this.sayHello = function(){ 
        console.log(this.name+":Hello!"); 
     }; 
     //this.sayHello = new Function('console.log(this.name+":Hello!")')
} 

new Function与声明函数在逻辑上是一样的,所以每次实例化Person对象时,sayHello 方法也是一个新实例,所以:

 console.log(person1.sayName == person2.sayName); //false 

注意:person1.sayName 与person1.sayName()不一样,person1.sayName()是指函数返回值,person1.sayName是指函数本身;

  创建两个完成同样任务的 Function 实例的确没有必要,因此,我们可以通过把函数定义转移到构造函数外部来解决这个问题。例如:

function Person(name, age, job){ 
     this.name = name; 
     this.age = age; 
     this.job = job; 
     this.sayHello = sayHello; 
} 
function sayHello(){ 
      console.log(this.name+":Hello!"); 
} 

   这样将 sayName 属性设置成指向全局函数 sayName() 的指针。sayName()只被实例化一次; 但是如果对象需要定义很多方法,那么就要定义很多个全局函数。而且在全局作用域中定义的函数实际上只被某个对象调用,这样不符合全局函数的理念。我们这个自定义的引用类型也丝毫没有封装性可言。好在这些问题可以通过使用原型模式来解决。所以下一节《 prototype(原型)》


文章参考:

《JavaScript 高级程序设计》中文译本 第三版