1.创建对象
1.1 创建对象的方式
- Object构造函数
- 对象字面量
虽然这两种方式可以方便的创建对象,但是在创建具有同样接口的多个对象时,需要重复编写很多代码。
1.2 工厂模式
js
function createPerson(name, age, job) {
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
console.log("我的名字是", this.name);
};
return o;
}
let person1 = createPerson("狗蛋", 23, "Softwrae Engineer");
let person2 = createPerson("翠花", 18, "Doctor");
console.log("person1", person1);
console.log("person2", person2);
函数createPerson()接收三个参数,每次调用都会返回三个属性和一个方法的对象,虽然工厂模式可以解决创建多个类似对象的问题,但是没有解决对象的标识问题(新创建的对象是什么类型)。
1.3 构造函数模式
ECMAScript中的构造函数是用来创建特定类型对象的,像Object、Array这样的原生构造函数可以直接使用,当然我们也可以自定义构造函数
js
function Student(name, age, job) {
// let o = new Object(); 不用显示地创建对象
// 属性和方法直接赋值给this
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log("我的名字是", this.name);
};
// return o; 不需要return
}
let student1 = new Student("狗蛋", 23, "Softwrae Engineer");
let student2 = new Student("翠花", 18, "Doctor");
console.log("student1", student1);
console.log("student2", student2);
注意: 函数名Student首字母大写(按照惯例),用于区分普通函数和构造函数。
要创建Student实例,应使用new操作符,创建实例对象的过程,构造函数做了什么?
js
在内存中创建一个新对象
在这个新对象内部的 [[Prototype]] 特性被赋值为构造函数的prototype属性
构造函数内部的this被赋值为这个新对象(即this指向这个新对象)
执行构造函数内部的代码(给新对象赋值)
如果构造函数返回非空对象,则返回该对象;否则返回刚创建的新对象
student1 和 student2都保存着Student的不同实例,这两个对象都有一个constructor属性指向Student
js
console.log(student1.constructor === Student); // true
console.log(student2.constructor === Student); // true
constructor本来是用来标识对象类型的,但是一般认为instanceof操作符是确定对象类型更可靠的方式。
js
console.log(student1 instanceof Student); // true
console.log(student1 instanceof Object); // true
console.log(student2 instanceof Student); // true
console.log(student2 instanceof Object); // true
自定义构造函数可以确保实例被标识为特定类型,这里student1和student2之所以也被认定为Object的实例,是因为所有自定义对象都继承自Object。
值得注意的是:构造函数不一定要写成函数声明式,同时也可以写成赋值给变量的函数表达式、在实例化时如果不传参数,可以不写括号,只要有new操作符就可以调用相应的构造函数。
1.4构造函数也是函数
构造函数与函数的区别是:调用方式不同,任何函数只要使用new操作符来调用就是构造函数,否则就是普通函数。
js
// 作构造函数
let student1 = new Student("狗蛋", 23, "Softwrae Engineer");
student1.sayName(); // 我的名字是 狗蛋
// 作为函数调用
Student("狗蛋2", 23, "Softwrae Engineer"); // 添加到window对象
window.sayName(); // 我的名字是 狗蛋2
// 在另一个对象的作用域中调用
let person = new Object();
Student.call(person, "狗蛋爸爸", "35", "teacher");
person.sayName(); // 我的名字是 狗蛋爸爸
从上面三个例子可以得知:
- 没有使用new操作符调用,属性和方法会添加到window对象
- 说明this默认是指向全局Global对象的,浏览器中就是window对象
- 通过call()/apply()调用函数,可以改变this的指向
1.5 构造函数的问题
构造函数的问题在于,其定义的方法会在每个实例都创建一遍,比如如下代码:
js
function Student(name, age, job) {
// let o = new Object(); 不用显示地创建对象
// 属性和方法直接赋值给this this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
console.log("我的名字是", this.name);
};
// return o; 不需要return
}
let student1 = new Student("狗蛋", 23, "Softwrae Engineer");
let student2 = new Student("翠花", 18, "Doctor");
student1和student2都有名为sayName的方法,但是它们不是同一个Function实例。在ECMAScript中函数就是对象,每次定义函数都会初始化一个对象。因此,不同实例上的函数虽然同名但却不相等。
js
console.log(student1.sayName === student2.sayName); // false
所以,都是做同样的事情,所以没有必要定义两个不同的Function实例。
我们可以尝试将函数定义在构造函数的外部:
js
function Student(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(name) {
console.log("我的名字是", this.name);
}
let student1 = new Student("狗蛋", 23, "Softwrae Engineer");
let student2 = new Student("翠花", 18, "Doctor");
student1.sayName(); // 我的名字是 狗蛋
student2.sayName(); // 我的名字是 翠花
这样就能解决相同逻辑的函数重复定义的问题,但是这样全局作用域会因此变得混乱,因为这个函数只能在一个对象上调用,如果该对象需要多个方法,那么就要在全局作用域中定义多个函数,这样代码会非常的分散。这个问题可以通过原型模式来解决!待续......