创建单个对象的缺点: 用同一个接口创建很多对象, 会产生大量的重复代码.
工厂模式就是为了解决这个问题.
工厂模式
解决了创建多个相似对象的问题
- 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 个步骤:
person1 和 person2 分别保存着 Person 的两个不同实例, 都有一个 constructor (构造函数) 属性, 指向 Person
- 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
缺点: 需要在全局作用域定义很多函数, 没有封装性可言
原型模式
好处: 所有对象实例可共享它所包含的属性和方法
- 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 的原型属性, 及两个实例之间的关系.
在实现中, 无法访问 [[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
Person.prototype = {}; // 对象字面量方式创建的
constructor 不会指向 Person
通过 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); // 打印出的结果见下图
兼容 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 的新原型对象上的属性, 因为他们是两个不同的对象.
下图展示了重写原型之前和重写原型之后各个对象之间的关系.
未完待续...
来源: https://www.cnblogs.com/lwl0812/p/9764936.html