JS中创建自定义类型的常用模式总结【工厂模式

虽然在 ES6 中,已经出了 class 的语法,貌似好像不用了解 ES5 中的这些老东西了,但是越深入学习,你会发现理解这些模式的重要性。

在本文中,我会描述 7 种常用的创建自定义类型的模式:工厂模式、构造函数模式、原型模式、组合使用构造函数模式、动态原型模式、寄生构造函数模式、稳妥构造函数模式。分别给出他们的示例代码,并分析他们的利弊,方便读者选择具体的方式来构建自己的自定义类型。

最后,我会指出 ES6 中的 class 语法,本质上其实还是利用了组合使用构造函数模式进行创建自定义类型。

1. 工厂模式

废话不多说,先上工厂模式的实例代码:

function createPerson(name, age, job){ var o = new Object(); // 创建对象 o.name = name; // 赋予对象细节 o.age = age; // 赋予对象细节 o.job = job; // 赋予对象细节 o.sayName = function(){ // 赋予对象细节 alert(this.name); }; return o; // 返回该对象 } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor");

优点:解决了创建多个相似对象的问题;

缺点:没有解决对象识别的问题(即不知道这个对象是什么类型),对于对象的方法没有做到复用。

2. 构造函数模式

function Person(name, age, job){ this.name = name; // 对象的所有细节全部挂载在 this 对象下面 this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");

说到构造函数模式就不得不提到 new 操作符了。我们来看看 new 这个操作符到底做了什么:

① 创建一个对象;
② 将构造函数内的 this 指向这个新创建的对象,同时将该函数的 prototype 的引用挂载在新对象的原型下;
③ 执行函数内的细节,也就是将属性和方法挂载在新对象下;
④ 隐式的返回新创建的对象。

优点:解决了对象识别的问题;

缺点:对于自定义类型的方法每次都要新创建一个方法函数实例,没有做到函数复用。如果把所有方法函数写到父级作用域中,是做到了函数复用,但同时方法函数只能在父级作用域的某个类型中进行调用,这对于父级作用域有点名不副实,同时对于自定义引用类型没有封装性可言。

3. 原型模式

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

理解要点:

① 无论什么时候,只要创建了一个新函数,就会根据一组特定规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。
② 在默认情况下,所有原型对象都会自动获得一个 constructor 属性,这个属性包含一个指向 prototype 属性所在函数的指针。至于原型中的其他方法则都是从 Object 继承而来。
③ 当调用构造函数创建了一个新实例后,该实例的内部将包含一个指针 [[prototype]](内部属性) ,指向构造函数的原型对象。
④ 当调用构造函数创建一个新实例后,该实例的实例环境,即构造函数,会针对原型对象上的非引用类型的原型属性,在构造函数中自动构建相应的实例环境属性。也就是说,之后根据构造函数创建的实例,它的实例属性中的非引用类型属性,都仍是根据构造函数中的实例环境属性创建的。

但是为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象。如下所示:

function Person(){ } Person.prototype = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };

但是这种写法,其本质上完全重写了默认的 prototype 对象,因此 constrctor 属性也就变成了新对象的 constructor 属性(指向 Object 构造函数),不在指向 Person 函数。尽管此时,instanceOf 操作符还能返回正确的结果。

如果 constructor 属性真的很重要,可以像下面这样特意将它设置回适当的值:

function Person(){ } Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/c3859d632a540105979bc3e14ad0a406.html