JavaScript 对象设计模式详解

1. 在对象设计模式提出之前,使用 new Object() 或者 对象字面量 的方式声明对象是可以的,但如果需要多个结构类似的对象,那么会写很多重复性代码。工厂模式解决了这个问题:

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("Nicholas", 29, "Software Engineer"); 
let person2 = createPerson("Greg", 27, "Doctor");

工厂模式在普通函数内部显式声明了一个Object对象,之后给这个对象赋值,最终返回这个对象。

解决了声明同样结构对象时,代码重复的问题;

缺点:无法进行对象类型判断,都是Object类型.

2. 由此引出构造函数模式:

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 Engineer"); 
let person2 = new Person("Greg", 27, "Doctor"); 
person1.sayName(); // Nicholas
person2.sayName(); // Greg

构造函数模式没有显式的创建对象,同时属性和方法直接赋值给了 this,也没有返回对象.

注意:构造函数名一般首字母都要大写

使用 new 创建构造函数对象实例的过程:

(1) 在内存中创建一个新对象。
(2) 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
(3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
(4) 执行构造函数内部的代码(给新对象添加属性)。
(5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。
 

相比较于工厂模式,构造函数模式解决了无法判断对象类型的问题,在每个由构造函数创建的实例对象上都具有一个 constructor 属性,它的值为对象的类型,但是一般认为采用 instanceof 判断类型更加准确.

缺点:构造函数模式的属性和方法所有对象都会创建一遍,而有些方法我们只需要创建一遍即可使得所有对象共享即可满足需求.

 3. 由此引出原型模式:

  每个函数都会创建一个 prototype 属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。

function Person() {} 
Person.prototype.name = "Nicholas"; 
Person.prototype.age = 29; 
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function() { 
 console.log(this.name); 
}; 
let person1 = new Person(); 
person1.sayName(); // "Nicholas" 
let person2 = new Person(); 
person2.sayName(); // "Nicholas" 
console.log(person1.sayName == person2.sayName); // true

  每个函数对象都会有 prototype 属性,指向原型对象,所有原型对象都有一个 constructor 属性,指向构造函数。例如 Person.prototype.constructor 指向 Person. 默认自定义构造函数时,对象只会获得 constructor 属性,其他属性均继承自 Object. 同样的,每个构造函数实例也都会有一个内部属性:[[ prototype ]],内部属性无法被访问到,但是浏览器给我们暴露了 __proto__ 属性,其指向构造函数的原型对象.

 

【图片来源于 黑马 JS 高级教程 —— 原型链 图示,仅做个人学习使用】

【图片来源于 红宝书 JS 高级教程 —— 原型模式 图示,仅做个人学习使用】

 Object.getPrototypeOf() 以及 构造函数原型对象.isPrototypeOf() 的使用示例:

console.log(Person.prototype.isPrototypeOf(person1)); // true 
console.log(Person.prototype.isPrototypeOf(person2)); // true
console.log(Object.getPrototypeOf(person1) == Person.prototype); // true 
console.log(Object.getPrototypeOf(person1).name); // "Nicholas"

  当你使用原型模式为一个构造函数的原型对象添加属性和方法时,每次都要书写 xxx.prototype.xxx = xxx; 也许你在想,可不可以使用对象字面量的形式,直接将所有需要添加的属性和方法,作为一个对象整体赋值给 xxx.prototype ,那么这样做会出现一个问题:一个构造函数的 prototype 默认是有 constructor 属性的,而刚刚的做法则完全重写了 prototype,这样会丢失 constructor 属性,所以如果有必要,可以显式将 constructor 添加上,同时其默认 enumerable 特性为 false,而对象字面量形式默认为 true,因此建议使用 Object.defineProperty() 修改其 enumerable 特性.

function Person() {} 
Person.prototype = { 
 name: "Nicholas", 
 age: 29, 
 job: "Software Engineer", 
 sayName() { 
 console.log(this.name); 
 } 
}; 
// 恢复 constructor 属性
Object.defineProperty(Person.prototype, "constructor", { 
 enumerable: false, 
 value: Person 
});

 原型模式缺点:所有实例共享数据,没有实例自身特有数据【构造函数模式特点】

 

 总结一下:一般采用构造函数模式 + 原型模式 结合使用.

 

posted @ 2021-09-22 20:29  TwinkleG  Views(65)  Comments(0)    收藏  举报