JavaScript 创建对象 (工厂模式构造函数模式原型模式组合使用构造函数模式与原型模式)

Posted Kabukiyo Lin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript 创建对象 (工厂模式构造函数模式原型模式组合使用构造函数模式与原型模式)相关的知识,希望对你有一定的参考价值。


总结JS创建对象的几个模式及各自优缺点
此笔记供后续学习查看自用

工厂模式

function createPerson(name,age,job){
	var obj = new Object();
	obj.name = name;
	obj.age = age;
	obj.job = job;
	obj.sayName = function(){
		console.log(this.name);
	};
	return obj;
}
var person1 = createPerson('John',22,engineer);

过程:

  1. 使用 new Object() 显示创建一个obj对象
  2. 把属性和方法直接添加到 obj 对象上
  3. return 返回该obj对象

缺点:工厂模式不能识别对象的类型。

构造函数模式

改写上述代码:

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('John',22,engineer);
var person2 = new Person('Lin',22,engineer);

createPerson函数改成了Person函数
构造函数模式与工厂模式的区别还有:

  1. 没有显示地创建对象(没有用 new Object()
  2. 把属性和方法直接添加到this对象
  3. 不需要return语句

过程:

  1. 通过 new Person() 创建一个新对象
  2. 构造函数Person的作用域指向新对象,即this指向了新对象
  3. 执行构造函数中的代码,即把属性和方法添加到this上
  4. 返回新对象

注意:

person1与person2保存着Person不同的实例。
两个对象都有一个constructor属性,constructor属性指向Person;

console.log(person1.constructor); //Person
console.log(person2.constructor); //Person

也就是说,自定义构造函数的实例可以通过constructor属性来唯一标识其为特定的类型。(这是工厂模式做不到的)

alert(person1 instanceof Person); //true;
alert(person2 instanceof Person); //true;
alert(person1 instanceof Object); //true;
alert(person2 instanceof Object); //true;

构造函数模式的缺点

缺点一、会创建多个完成相同任务的同名Function实例

同上面的例子:

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('John',22,engineer);
var person2 = new Person('Lin',22,engineer);

Person的两个实例person1和person2,各自都有一个sayName方法。但person1.sayName和person2.sayName不是一样的,即这两个方法各自都是Function的一个实例。

因为,ECMAScript的函数都是对象,其实可写作new Function():

this.sayName = function(){
	console.log(this.name);
}
//可以写作
this.sayName = new Function("console.log(this.name)");

也就是说:通过自定义构造函数模式,每通过 new Person()实例一个新的对象,也就创建了一个新的Function实例。每个person中的sayName方法是不一样的,是不同的实例。虽然他们之间有相同的名字、处理的代码相同。

每创建一个新的Person实例,就又附带创建了新的Function实例。而这些Function实例,同名又干一样的事情,确实是没有必要!

如何规避?

将Person中sayName函数的定义放到构造函数的外部,改写如下:

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

相当于在构造函数Person内部的sayName属性设置为全局函数sayName()。this.sayName则是一个指针,指向这个全局函数。

这样就可以让多个Person实例的this.sayName属性都指向这个全局函数,从而没有创建过多的同名Function实例(干同样的事)。

缺点二、全局函数
看样子把构造函数内部的方法,定义到外部作全局函数就可以解决问题了。

但是这个定义在全局作用域的函数,却只能给某类对象使用,听起来就怪怪的。

还有,如果某构造函数需要很多方法,为了避免创建过多同名Function实例,难道我们要在全局作用域定义很多个函数吗?!

原型模式

原型模式可以改善构造函数模式的缺点:

function Person(){
	Person.prototype.name = 'John';
	Person.prototype.age = 22;
	Person.prototype.job = 'engineer';
	Person.prototype.sayName = function(){
		console.log(this.name);
	}
}
var person1 = new Person();
var person2 = new Person();

关于原型对象:

  1. 我们创建的函数都具有一个prototype属性,该属性是个指针,指向该函数的原型对象。
  2. 原型对象:可以让所有对象的实例共享它所包含的属性和方法
  3. 原型对象都有一个constructor属性,该属性也是个指针,指向prototype属性所在的函数。

因为Person.prototype原型对象,可以让Person的所有实例都共享它所包含的属性和方法。

那也就不会像构造函数模式那样,需要 创建许多同名Function实例 或者 在全局作用域定义许多个函数了。

在这里插入图片描述
即,Person.prototype指向原型对象;
Person.prototype.constructor指向Person;

Person创建的实例person1与person2,[[prototype]],仅仅指向Person.prototype(原型对象),与构造函数Person没有直接关系。

原型模式的搜索

当代码读取某个对象实例的某个属性时,是先从该实例上搜索查找有无该属性,若在实例本身check到了,那就返回该属性的值;如果在实例上找不到,那继续往上找,找实例的原型对象(Person.prototype)看有没有该属性。

这也是为什么不同实例能共享原型对象里的属性和方法。

简单的语法

可以这么写:

function Person(){};
Person.prototype = {
	name : 'John',
	age : 22,
	job : 'enginee',
	sayName : function(){
		console.log(this.name);
	}
}
var person1 = new Person;
console.log(person1.constructor == Object);//true;
console.log(person1.constructor == Person);//false;

因为说过,Person.prototype会有个constructor属性,这个属性指向prototype属性所在的函数。可见prototype现在没有写在Person里面了,而是在Object上。

但是可以手动修改

function Person(){};
Person.prototype = {
	constructor: Person,
	name : 'John',
	age : 22,
	job : 'enginee',
	sayName : function(){
		console.log(this.name);
	}
}

组合使用构造函数模式与原型模式

原型模式中的prototype原型对象,是给实例提供所有共享的方法和属性,但有的若是引用类型,则不适合被共享。

那么就在构造函数模式中,定义实例属性(不希望被共享);用原型模式来为实例定义可以被共享的属性和方法。

function Person(name,age,job){
	this.name = name;
	this.age = age;
	this.job = job;
	this.friends = ['Kay','Alex'];
}
Person.prototype = {
	constructor: Person;
	sayName : function(){
		console.log(this.name);
	}
}

以上是关于JavaScript 创建对象 (工厂模式构造函数模式原型模式组合使用构造函数模式与原型模式)的主要内容,如果未能解决你的问题,请参考以下文章

javascript工厂模式和构造函数模式创建对象

javascript--面向对象(工厂模式,构造函数,原型模式)

JavaScript创建对象之单例工厂构造函数模式

JavaScript面向对象OOM 2(JavaScript 创建对象的工厂模式和构造函数模式)

JavaScript 创建对象之单例工厂构造函数模式

Javascript-基础-Object创建对象