ECMAScript 中的构造函数可用来创建特定类型的对象。像 Object 和 Array 这样的原生构造函数,在运行时会自动出现在执行环境中。此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。例如,可以使用构造函数模式将前面的例子重写如下:
一、构造函数示例
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 Enginner');
let person2 = new Person('Greg', 27, 'Doctor');
这个例子中,Person() 函数取代了 createPerson() 函数。我们注意到, Person() 中的代码除了与 createPerson() 中相同的部分外,还存在以下不同之处:
- 没有显示地创建对象
- 直接将属性和方法赋给了 this 对象
- 没有 return 语句
此外,还应该注意到函数名 Person 使用的是大写字母 P。按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。这个做法借鉴子其他 OO 语言,主要是为了区别 ESMAScript 中的其他函数。因为构造函数本身也是函数,只不过可以用来创建对象而已。
二、需要注意
要创建 Person 的新实例,必须使用 new 操作符。以这种方式调用构造函数史记会经历以下步骤:
- 创建一个新对象
- 将构造函数的作用域给新对象(因此 this 就指向了这个新对象)
- 执行构造函数中的代码 (为了这个新对象添加属性)
- 返回新对象
在前面例子的最后,person1 和 person2 分别保存着 Person 的一个不同的实例。这两个对象都有一个 constructor (构造函数) 属性,该属性指向 Person, 如下所示:
console(person1.constructor == Person); // true
console(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
创建自定义的构造函数意味着将来可以将它的实例标识为一种特殊的类型。而这正是构造函数模式胜过工厂模式的地方。在这个例子中,person1 和 person2 之所以可以同时是 Object 的实例,是因为所有对象均继承自 Object。
三、将够着函数当作函数
构造函数与其他函数的唯一区别,就在于调用他们的方式不同。不过,构造函数毕竟也是函数,不存在定义构造函数的特殊语法。任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过 new 操作符来调用,那么它跟普通函数差不多。
// 1、当前构造函数使用
let person = new Person('Nicholas', 29, 'software Enginner');
person.sayName(); // Nicholas
// 2、作为普通函数调用
Person('Grep', 27, 'Doctor'); // 添加到window
window.sayName(); // 'Grep'
// 3、在另一个对象的作用域中调用
let o = new Object();
Person.call(o, 'Kristen', 25, 'Nurse');
o.sayName(); // 'Kristen'
1、展示了构造函数的典型用法,即使用 new 操作符来创建一个新对象。
2、展示了不使用 new 操作符调用 Person() ,属性和方法都被添加给 window 对象了。
3、通过使用call (或者 apply()) 在某个特殊对象的作用域中调用 Person() 函数。
在全局作用域中调用一个函数时, this 对象总是指向 Global 对象(在浏览器中就是指向 window 对象 )。
四、构造函数的问题(问题)
使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍,不同实例上的同名函数是不相等的
console.log(person1.sayName === person2.sayName); // false
然而创建两个完成同样任务的 Function 实例的确没有必要;
function sayName() {
console.log(this.name);
}
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
let person1 = new Person('Nicholas', 29, 'software Enginner');
let person2 = new Person('Greg', 27, 'Doctor');
上述例子中,我们把 sayName() 函数的定义转移到了构造函数外部。而在构造函数内部,我们将 sayName 属性设置成等于全局的 sayName 函数。这样一来,由于 sayName 包含的是一个指向函数的指针,因此 person1 和 person2 对象就共享了全局作用域中定义的同一个 sayName() 函数。
五、新问题
在全局作用域中定义的函数实际上只能被某个对象调用,如果对象需要定义很多方法,那么就要定义很多个全局函数,那么我们自定义的引用类型就没有封装性可言了。