6.2 创建对象
Object 构造函数和对象字面量都可以用来创建单个对象,但是会产生大量重复代码。
6.2.1 工厂模式
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;
}
const person1 = createPerson('Mike', 28, 'xxx');
console.log(person1);
person1.sayName();
const person2 = createPerson('Mike', 24, 'aaa');
console.log(person2);
person2.sayName();
缺点:无法识别对象的类型。
6.2.2 构造函数模式
构造函数可以用来创建特定类型的对象。
function Person(name, age , job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
}
}
const person1 = new Person('Mike', 28, 'teacher');
console.log(person1);
person1.sayName();
const person2 = new Person('Danie', 24, 'doctor');
console.log(person2);
person2.sayName();
与工厂模式的区别:
- 没有显示的创建对象
- 将属性和方法赋值给了this对象
- 没有return语句
要创建 Person 的新实例,必须使用 new 操作符。
经历四个步骤:
- 创建一个新对象
- 将作用域赋值给这个新对象--因此 this 指向了这个新对象
- 执行构造函数中的代码--给这个对象添加新属性
- 返回这个对象
person1 和 person2 都保存着 Person 的不同实例,都有一个 constructor (构造函数)属性,指向 Person。
function Person(name, age , job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
}
}
const person1 = new Person('Mike', 28, 'teacher');
console.log(person1);
/**
Person {name: "Mike", age: 28, job: "teacher", sayName: ƒ}
age: 28
job: "teacher"
name: "Mike"
sayName: ƒ ()
__proto__:
constructor: ƒ Person(name, age, job) <-- constructor 属性
__proto__: Object
*/
console.log(person1.constructor); // 打印 person1 的构造函数
/**
ƒ Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
};
}
*/
console.log(Person.constructor); // 打印 Person 的构造函数
// ƒ Function() { [native code] }
对象的 constructor 属性用来标识对象类型。
如果要检测对象类型,用 instanceof 操作符。
console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true 所有对象均继承自 Object
console.log(Person instanceof Object); // true 所有对象均继承自 Object
缺点:每个实例的方法都要重新创建一遍。实例上的同名方法不相等。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
};
}
const person1 = new Person('Mike', 28, 'teacher');
const person2 = new Person('Danie', 24, 'doctor');
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;
}
const person1 = new Person('Mike', 28, 'teacher');
const person2 = new Person('Danie', 24, 'doctor');
console.log(person1.sayName === person2.sayName); // true
缺点:在全局定义很多函数,没有封装性。
6.2.3 原型模式
每个实例都有一个 prototype 属性,prototype 是一个指针,指向一个对象,包含所有实例共享的属性和方法。
function Person() {}
Person.prototype.name = 'Mike';
Person.prototype.age = '24';
Person.prototype.sayName = function() {
console.log(this.name);
};
const person1 = new Person();
const person2 = new Person();
console.log(person1.sayName === person2.sayName); // true
console.log(Person.prototype); // 指向 Person 的原型对象 Prototype
/**
{name: "Mike", age: "24", sayName: ƒ, constructor: ƒ}
age: "24"
name: "Mike"
sayName: ƒ ()
constructor: ƒ Person()
__proto__: Object
*/
console.log(person1.__proto__); // 指向构造函数 Person 的原型对象 Prototype
/**
{name: "Mike", age: "24", sayName: ƒ, constructor: ƒ}
age: "24"
name: "Mike"
sayName: ƒ ()
constructor: ƒ Person()
__proto__: Object
*/
console.log(Person.prototype === person1.__proto__); // true
可以通过 Object.getPrototypeOf() 来获取原型对象。
console.log(Object.getPrototypeOf(person1));
/**
{name: "Mike", age: "24", sayName: ƒ, constructor: ƒ}
age: "24"
name: "Mike"
sayName: ƒ ()
constructor: ƒ Person()
__proto__: Object
*/
console.log(Object.getPrototypeOf(person1) === Person.prototype); // true
通过 hasOwnProperty() 方法可以检测一个属性是存在于对象实例中,还是原型中。
function Person() {}
Person.prototype.name = 'Mike';
Person.prototype.age = '24';
Person.prototype.sayName = function() {
console.log(this.name);
};
const person1 = new Person();
console.log(person1.hasOwnProperty('name')); // false
person1.age = 18;
console.log(person1.hasOwnProperty('age')); // true
in 操作符在可以访问对象的给定属性时返回 true,无论该属性存在于实例还是原型中。
function Person() {}
Person.prototype.name = 'Mike';
Person.prototype.age = '24';
Person.prototype.sayName = function() {
console.log(this.name);
};
const person1 = new Person();
console.log('name' in person1); // true
同时使用 hasOwnProperty()
和 in 操作符,可以判断属性是存在于实例中还是原型中。
function hasPrototypeProperty(obj, name) {
return !obj.hasOwnProperty(name) && name in obj;
}
console.log(hasPrototypeProperty(person1, 'name')); // true
person1.name = 'Gerge';
console.log(hasPrototypeProperty(person1, 'name')); // false
更简单的原型语法
function Person() {}
Person.prototype = {
name: 'Mike',
age: 21,
sayName() {
console.log(this.name);
},
};
console.log(Person.prototype);
/**
{name: "Mike", age: 21, sayName: ƒ}
age: 21
name: "Mike"
sayName: ƒ sayName()
// <-- 缺少了指向 Person 的 constructor
*/
function Person() {}
const person1 = new Person();
console.log(person1.constructor === Person); // true <-- 没有重写 Person.prototype
Person.prototype = {
name: 'Mike',
age: 21,
sayName() {
console.log(this.name);
},
};
const person2 = new Person();
console.log(person2.constructor === Person); // false <-- 重写了 Person.prototype
如果 constructor 很重要,可以设置为需要的值。
function Person() {}
Person.prototype = {
constructor: Person, // <-- 将 constructor 设为 Person
name: 'Mike',
age: 21,
sayName() {
console.log(this.name);
},
};
console.log(Person.prototype);
/**
{constructor: ƒ, name: "Mike", age: 21, sayName: ƒ}
age: 21
constructor: ƒ Person() <-- constructor 指向了 Person
name: "Mike"
sayName: ƒ sayName()
__proto__: Object
*/
const person2 = new Person();
console.log(person2.constructor === Person); // true <-- person2 的构造函数指向了 Person
缺点:
- 缺少传入初始参数,实例默认情况下获取相同属性。
- 共享,引用类型的值会一起改变