构造函数模式创建对象

创建对象的过程,以及对象标识

  1. function Person(name,age,job){
  2. this.name = name;
  3. this.age = age;
  4. this.job = job;
  5. this.sayName = function() {
  6. console.log(this.name);
  7. };
  8. }
  9. let person1 = new Person("Nicholas", 29, "Software Engineer");
  10. let person2 = new Person("Greg", 27, "Doctor");
  11. person1.sayName(); // Nicholas
  12. person2.sayName(); // Greg

按照约定 ,构造函数首字母要大写。这是从面向对象编程语言那里借鉴的,有助于在 ECMAScript 中区分构
造函数和普通函数。毕竟,构造函数就是一个能创建对象的函数。
通过new 创建一个对象,会执行一下操作:

  1. 在内存中创建一个新对象。
  2. 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
  3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
  4. 执行构造函数内部的代码(给新对象添加属性)。
  5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。

上述例子 person1 和 person2 分别保存着 Person 的不同实例。这两个对象都有一个
constructor 属性指向 Person,如下:

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

constructor 本来是用于标识对象类型的。不过,一般认为 instanceof 操作符是确定对象类型
更可靠的方式。前面例子中的每个对象都是 Object 的实例,同时也是 Person 的实例,如下面调用
instanceof 操作符的结果所示:

  1. console.log(person1 instanceof Object); // true
  2. console.log(person1 instanceof Person); // true
  3. console.log(person2 instanceof Object); // true
  4. console.log(person2 instanceof Person); // true

在实例化时,如果不想传参数,那么构造函数后面的括号可加可不加。只要有 new 操作符,就可以
调用相应的构造函数:

  1. function Person() {
  2. this.name = "Jake";
  3. this.sayName = function() {
  4. console.log(this.name);
  5. };
  6. }
  7. let person1 = new Person();
  8. let person2 = new Person; //不加括号
  9. person1.sayName(); // Jake
  10. person2.sayName(); // Jake

构造函数也是函数

构造函数与普通函数唯一的区别就是调用方式不同。除此之外,构造函数也是函数。并没有把某个
函数定义为构造函数的特殊语法。任何函数只要使用 new 操作符调用就是构造函数,而不使用 new 操
作符调用的函数就是普通函数

  1. // 作为构造函数
  2. let person = new Person("Nicholas", 29, "Software Engineer");
  3. person.sayName(); // "Nicholas"
  4. // 作为函数调用
  5. Person("Greg", 27, "Doctor"); // 添加到 window 对象
  6. window.sayName(); // "Greg"
  7. // 在另一个对象的作用域中调用
  8. let o = new Object();
  9. Person.call(o, "Kristen", 25, "Nurse");
  10. o.sayName(); // "Kristen"

构造函数的问题

构造函数虽然有用,但也不是没有问题。构造函数的主要问题在于,其定义的方法会在每个实例上
都创建一遍。因此对前面的例子而言,person1 和 person2 都有名为 sayName()的方法,但这两个方
法不是同一个 Function 实例。我们知道,ECMAScript 中的函数是对象,因此每次定义函数时,都会
初始化一个对象,如下展示,两个实例的sayName并不相等

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

因为都是做一样的事,所以没必要定义两个不同的 Function 实例。况且,this 对象可以把函数
与对象的绑定推迟到运行时。
要解决这个问题,可以把函数定义转移到构造函数外部:

  1. function Person(name, age, job){
  2. this.name = name;
  3. this.age = age;
  4. this.job = job;
  5. this.sayName = sayName;
  6. }
  7. function sayName() {
  8. console.log(this.name);
  9. }
  10. let person1 = new Person("Nicholas", 29, "Software Engineer");
  11. let person2 = new Person("Greg", 27, "Doctor");
  12. person1.sayName(); // Nicholas
  13. person2.sayName(); // Greg
  14. //两个实例的sayName相等
  15. console.log(person1.sayName == person2.sayName)//true

这样定义,就可以解决相同逻辑的函数重复定义的问题了。但这样也有很明显的问题。对象的方法定义在了外部,不能很好的和对象定义聚合在一快。另外就是,这里只有一个方法,如过对象有多个方法,就会扰乱全局作用域,造成混乱。这个问题可以通过原型模式来解决。