构造函数模式创建对象
创建对象的过程,以及对象标识
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
};
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg
按照约定 ,构造函数首字母要大写。这是从面向对象编程语言那里借鉴的,有助于在 ECMAScript 中区分构
造函数和普通函数。毕竟,构造函数就是一个能创建对象的函数。
通过new 创建一个对象,会执行一下操作:
- 在内存中创建一个新对象。
- 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
- 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
- 执行构造函数内部的代码(给新对象添加属性)。
- 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
上述例子 person1 和 person2 分别保存着 Person 的不同实例。这两个对象都有一个
constructor 属性指向 Person,如下:
console.log(person1.constructor == Person); // true
console.log(person2.constructor == Person); // true
constructor 本来是用于标识对象类型的。不过,一般认为 instanceof 操作符是确定对象类型
更可靠的方式。前面例子中的每个对象都是 Object 的实例,同时也是 Person 的实例,如下面调用
instanceof 操作符的结果所示:
console.log(person1 instanceof Object); // true
console.log(person1 instanceof Person); // true
console.log(person2 instanceof Object); // true
console.log(person2 instanceof Person); // true
在实例化时,如果不想传参数,那么构造函数后面的括号可加可不加。只要有 new 操作符,就可以
调用相应的构造函数:
function Person() {
this.name = "Jake";
this.sayName = function() {
console.log(this.name);
};
}
let person1 = new Person();
let person2 = new Person; //不加括号
person1.sayName(); // Jake
person2.sayName(); // Jake
构造函数也是函数
构造函数与普通函数唯一的区别就是调用方式不同。除此之外,构造函数也是函数。并没有把某个
函数定义为构造函数的特殊语法。任何函数只要使用 new 操作符调用就是构造函数,而不使用 new 操
作符调用的函数就是普通函数
// 作为构造函数
let person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); // "Nicholas"
// 作为函数调用
Person("Greg", 27, "Doctor"); // 添加到 window 对象
window.sayName(); // "Greg"
// 在另一个对象的作用域中调用
let o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); // "Kristen"
构造函数的问题
构造函数虽然有用,但也不是没有问题。构造函数的主要问题在于,其定义的方法会在每个实例上
都创建一遍。因此对前面的例子而言,person1 和 person2 都有名为 sayName()的方法,但这两个方
法不是同一个 Function 实例。我们知道,ECMAScript 中的函数是对象,因此每次定义函数时,都会
初始化一个对象,如下展示,两个实例的sayName并不相等
console.log(person1.sayName == person2.sayName); // false
因为都是做一样的事,所以没必要定义两个不同的 Function 实例。况且,this 对象可以把函数
与对象的绑定推迟到运行时。
要解决这个问题,可以把函数定义转移到构造函数外部:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
console.log(this.name);
}
let person1 = new Person("Nicholas", 29, "Software Engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName(); // Nicholas
person2.sayName(); // Greg
//两个实例的sayName相等
console.log(person1.sayName == person2.sayName)//true
这样定义,就可以解决相同逻辑的函数重复定义的问题了。但这样也有很明显的问题。对象的方法定义在了外部,不能很好的和对象定义聚合在一快。另外就是,这里只有一个方法,如过对象有多个方法,就会扰乱全局作用域,造成混乱。这个问题可以通过原型模式来解决。