创建单个对象的缺点:用同一个接口创建很多对象,会产生大量的重复代码。

工厂模式就是为了解决这个问题。

工厂模式

解决了创建多个相似对象的问题

function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        console.log(this.name)
    }
    return o;
}

var person1 = createPerson('Mike', 28, 'xxx');
console.log(person1);
person1.sayName();
var person2 = createPerson('Mike', 24, 'aaa');
console.log(person2);
person2.sayName();

缺点:无法解决对象识别的问题——怎样知道一个对象的类型

构造函数模式

ECMAScript中的构造函数可以用来创建特定类型的对象。

function Person(name, age , job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        console.log(this.name);
    }
}

var person1 = new Person('Mike', 28, 'teacher');
console.log(person1);
person1.sayName();
var person2 = new Person('Danie', 24, 'doctor');
console.log(person2);
person2.sayName();

与工厂模式的区别:

  • 没有显示的创建对象
  • 将属性和方法赋值给了this对象
  • 没有return语句

构造函数本身也是函数,只不过可以用来创建对象

用new操作符新建构造函数的实例,经历4个步骤:

创建对象学习 —— 《高级教程》-LMLPHP

person1 和 person2 分别保存着 Person 的两个不同实例,都有一个 constructor (构造函数) 属性,指向 Person

创建对象学习 —— 《高级教程》-LMLPHP

console.log(person1.constructor == Person); // true
console.log(person2.constructor == Person); // true
console.log(person1.constructor == Object); // false
console.log(person2.constructor == Object); // false

person1 和 person2 都是 Person 的实例,也是 Object 的实例,可以通过 instanceof 操作符来检验。

console.log(person1 instanceof Person); // true
console.log(person2 instanceof Person); // true
console.log(person1 instanceof Object); // true
console.log(person2 instanceof Object); // true

缺点:每个构造函数中的方法都要在新的实例上创建一遍。

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

将相同的方法移到外部:

function sayName() { // 将相同的方法移到构造函数的外部
    console.log(this.name);
}

function Person(name, age , job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}

var person1 = new Person('Mike', 28, 'teacher');
console.log(person1);
person1.sayName();
var person2 = new Person('Danie', 24, 'doctor');
console.log(person2);
person2.sayName();
// 此时不同实例上的方法就相等了
console.log(person1.sayName == person2.sayName); // true

缺点:需要在全局作用域定义很多函数,没有封装性可言

原型模式

创建对象学习 —— 《高级教程》-LMLPHP

好处: 所有对象实例可共享它所包含的属性和方法

function Person() {

}

Person.prototype.name = 'Mike';
Person.prototype.age = 28;
Person.prototype.job = 'teacher';
Person.prototype.sayName = function() {
    console.log(this.name);
}

var person1 = new Person();
var person2 = new Person();
person1.sayName();
person2.sayName();

console.log(person1.sayName == person2.sayName); // true

下图以上面代码为例,展示了Person构造函数、Person的原型属性,及两个实例之间的关系。

 创建对象学习 —— 《高级教程》-LMLPHP

在实现中,无法访问 [[Prototype]],可以用 isPrototypeOf() 方法来确定对象之间是否有这种关系。

console.log(Person.prototype.isPrototypeOf(person1)); // true
console.log(Person.prototype.isPrototypeOf(person2)); // true

通过 Object.getPrototypeOf() 方法,访问原型对象上的属性

console.log(Object.getPrototypeOf(person1) == Person.prototype); // true
console.log(Object.getPrototypeOf(person1).name); // Mike

修改实例属性 

// 原型模式 修改实例属性

function Person() {

}

Person.prototype.name = 'Mike';
Person.prototype.age = 28;
Person.prototype.job = 'teacher';
Person.prototype.sayName = function() {
    console.log(this.name);
}

var person1 = new Person();
person1.name = 'Gray';
person1.job = 'doctor';
var person2 = new Person();
person1.sayName(); // Gray
person2.sayName(); // Mike

console.log(person1); // Person {name: "Gray", job: "doctor", __proto__: Object}
console.log(person2); // Person {__proto__: Object}

修改实例属性为 null, 不会恢复指向原型的链接

function Person() {

}

Person.prototype.name = 'Mike';
Person.prototype.age = 28;
Person.prototype.job = 'teacher';
Person.prototype.sayName = function() {
    console.log(this.name);
}

var person1 = new Person();
person1.name = 'Gray';
var person2 = new Person();
person1.sayName(); // Gray
person2.sayName(); // Mike
person1.name = null; // 不会恢复指向原型的链接
person1.sayName(); // null

删除实例属性 会重新恢复指向原型对象的链接

// 原型模式 删除实例属性 会重新恢复指向原型对象的链接

function Person() {

}

Person.prototype.name = 'Mike';
Person.prototype.age = 28;
Person.prototype.job = 'teacher';
Person.prototype.sayName = function() {
    console.log(this.name);
}

var person1 = new Person();
person1.name = 'Gray';
var person2 = new Person();
person1.sayName(); // Gray
person2.sayName(); // Mike
delete person1.name; // 会重新恢复指向原型对象的链接
person1.sayName(); // Mike

hasOwnProperty() 检测一个属性存在于实例中还是存在于原型中。

// 原型模式 hasOwnProperty()

function Person() {

}

Person.prototype.name = 'Mike';
Person.prototype.age = 28;
Person.prototype.job = 'teacher';
Person.prototype.sayName = function() {
    console.log(this.name);
}

var person1 = new Person();
console.log(person1.hasOwnProperty('name')); // false
person1.name = 'Gray';
console.log(person1.hasOwnProperty('name')); // true
var person2 = new Person();
console.log(person2.hasOwnProperty('name')); // false
delete person1.name;
console.log(person1.hasOwnProperty('name')); // false

Object.keys() 获得对象上所有可枚举的实例属性

// Object.keys()
function Person() {

}

Person.prototype.name = 'Mike';
Person.prototype.age = 28;
Person.prototype.job = 'teacher';
Person.prototype.sayName = function() {
    console.log(this.name);
}
console.log(Object.keys(Person.prototype)); // ["name", "age", "job", "sayName"]
var person1 = new Person();
console.log(Object.keys(person1)); // []
person1.name = 'Gray';
console.log(Object.keys(person1)); // ["name"]

更简单的原型语法

function Person() {

}

Person.prototype = { // prototype 的 constructor 属性不再指向 Person
    name: 'Mike',
    age: 28,
    job: 'Teacher',
    sayName: function() {
        console.log(this.name);
    }
}

var person1 = new Person();

console.log(person1)

以上方式,Person.prototype 的 constructor 属性将不再指向 Person。

对比下面两张图:

Person.prototype.name = 'Mike'; // 方式创建的

constructor 会指向 Person

创建对象学习 —— 《高级教程》-LMLPHP

Person.prototype = {}; // 对象字面量方式创建的

constructor 不会指向 Person

创建对象学习 —— 《高级教程》-LMLPHP

通过 instanceof 还能返回正确的结果,但是 constructor 已经不能确定对象的类型了。

console.log(person1 instanceof Object); // true
console.log(person1 instanceof Person); // true
console.log(person1.constructor == Object); // true
console.log(person1.constructor == Person); // false

如果 constructor 很重要,可以显示指定

// 显示指定 constructor
function Person() {

}

Person.prototype = { // 通过指定 constructor属性,指向 Person
    constructor: Person, // 以这种方式重设,会使它的[[Enumerable]]特性被设置为true
    name: 'Mike',
    age: 28,
    job: 'Teacher',
    sayName: function() {
        console.log(this.name);
    }
}

var person1 = new Person();
console.log(person1); // 打印出的结果见下图

创建对象学习 —— 《高级教程》-LMLPHP

兼容 ECMAScript5的浏览器引擎

Object.defineProperty()

function Person() {

}

Person.prototype = {
    name: 'Mike',
    age: 28,
    job: 'Teacher',
    sayName: function() {
        console.log(this.name);
    }
}

Object.defineProperty(Person.prototype, 'constructor', {
    enumerable: false,
    value: Person
});

原型的动态性

先创建实例,再在Person的原型对象上加方法,实例也可以调用。

// 原型的动态性

function Person() {

}

var person1 = new Person();

Person.prototype.sayHi = function() {
    console.log('Hi');
}

person1.sayHi(); // Hi

重写原型对象

function Person() {

}

var person1 = new Person();

Person.prototype = { // 通过指定 constructor属性,指向 Person
    constructor: Person,
    name: 'Mike',
    age: 28,
    job: 'Teacher',
    sayName: function() {
        console.log(this.name);
    }
}
console.log(person1);
person1.sayName(); // person1.sayName is not a function

可以看到,person1的原型对象上没有Person的新原型对象上的属性,因为他们是两个不同的对象。

创建对象学习 —— 《高级教程》-LMLPHP

下图展示了重写原型之前和重写原型之后各个对象之间的关系。

创建对象学习 —— 《高级教程》-LMLPHP

创建对象学习 —— 《高级教程》-LMLPHP

 未完待续...

注:以上所有的文字、代码都是本人一个字一个字敲上去的,图片也是一张一张画出来的,转载请注明出处,谢谢!

10-10 10:33