阅读 77

JavaScript创建对象的方法演变

一、工厂模式->构造函数模式:解决对象类型识别问题

//工厂模式
function createPerson(name,age){
	var o=new Object();
	o.name=name;
	o.age=age;
	o.sayName=function(){
		alert(this.name);
	}
	return o;
}
var p=createPerson("Alice",20);
console.log(p);

//构造函数模式
function Person(name,age){
	this.name=name;
	this.age=age;
	this.sayName=function(){
		alert(this.name);
	}
}
var p=new Person("Cindy",20);
console.log(p);
复制代码

从两者输出的实例对象开看,原型链是不一样的。

工厂模式原型链:实例对象->Object构造函数的原型对象,p.constructor输出Object。

构造函数模式原型链:实例对象->Person构造函数的原型对象->Object构造函数的原型对象,p.constructor输出Person。

这点差异使得构造函数模式可以用来进行对象识别,就是说可以直到这个对象是什么类型对象。可以使用constructor和instanceof进行类型判断。

console.log(p.constructor==Person)  //true
console.log(p instanceof Person)    //true
复制代码

二、构造函数模式->原型模式:解决创建多对象时代码复用问题

1、对象属性搜索机制:沿着原型链进行查找,找到就中止查找。首先搜索对象实例本身是否有该属性,没有则沿着原型链(__proto__指向的对象)去查找原型对象中是否有该属性,直到找到原型链的最后一个原型对象。

2、对象、构造函数与原型对象的关系:一个构造函数对应一个函数原型对象,构造函数创建出来的n个对象对应同一个构造函数原型对象(就是构造函数的原型对象)。

基于以上两点,要想实现n个对象共享属性或方法,就把这些属性和方法定义在原型对象上,可以接受不少内存空间。而且原型具有动态性,修改原型中的属性值,可以在所有对象得到反映。

但是单独使用原型模式弊端会很明显,函数定义在原型上回很合适,但是如果引用类型放在原型上,就会导致所有对象都共享这个引用类型。

三、构造函数模式和原型模式相结合:处理共有与独有的关系,是最优的模式

构造函数定义对象独有的属性,原型上定义对象共有的属性。

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

var p1=new Person("Cindy",20);
var p2=new Person("Alice",25);
p1.sayName()  //Cindy
p2.sayName()  //Alice
复制代码

四、ES6 class语法——构造函数模式和原型模式的语法糖

class Person1{
	constructor(name,age){
		this.name=name
		this.age=age
	}
	country="China";
	sayName=function(){
		alert(this.name);
	}
	sayContry(){
		alert(this.country);
	}
}
var p=new Person1("Aclie",22)
复制代码

对象p输出的结果是:

Person1 {country: "China", name: "Alice", age:20, sayName: ƒ}
	age: 20
	country: "China"
	name: "Alice"
	sayName: ƒ ()
	[[Prototype]]: Object
		constructor: class Person1
		sayContry: ƒ sayContry()
		[[Prototype]]: Object
			constructor: ƒ Object()
			...
复制代码

1、构造器函数constructor()不是必须的,但是写个构造函数有助于通过传参,定义对象的独有属性。构造器中定义的属性时对象实例的属性。

2、class中通过表达式定义的属性,无论是基本类型(country)、引用类型还是函数(sayName),都是对象实例的属性。

3、class中使用函数声明定义的函数,才是构造函数原型上的方法。

五、动态原型模式——构造函数模式和原型模式封装在构造函数中

我觉得有了class语法糖之后,就不需要用这种方式强硬封装了。

function Person(name,age){
	this.name=name;
	this.age=age;
	if(typeof this.sayName!=="function"){
		Person.prototype.sayName=function(){
			console.log(this.name);
		}
	}
}
复制代码

六、构造函数模式延伸的两种特殊模式——特殊用途(扩展篇)

(一)寄生构造函数模式

构造函数不返回值,就是常规的构造函数模式。

构造函数返回值,就是寄生构造函数模式。函数调用上下文赋值给这个返回的对象,即this指向是这个新对象。

function Person(name,age){
	var o=new Object();
	o.name=name;
	o.age=age;
	o.sayName=function(){
		alert(this.name);
	}
	return o;
}
var p=new Person("Alice",20);
console.log(p);
复制代码

看到Person这个函数和工厂模式是一样的,但是调用时用new关键字调用,这种方式下,原型链也是,实例对象->Object构造函数的原型对象,因此无法用来识别对象类型,也没什么共享优势,那么它的用途是什么呢?

JavaScript有很多内置对象,比如Array,如果想给内置对象添加某些方法方便使用,就可以借助寄生构造器模式实现。

function SpecialArray(){
	var arr=new Array();
	arr.push(...arguments);
	//添加一个特殊方法
	arr.toPipedString=function(){
		return this.join("|");
	}
	return arr;
}
console.log(colors.toPipedString());  //输出 red|blue|green
console.log(colors.length)    //输出 3
colors.slice(0,2)  //输出 ["red", "blue"]
复制代码

可以看到,使用寄生构造器模式改造的Array对象,不仅有新增的方法,还可以使用Array对象原来的属性和方法,简直不要太nice。

(二)稳妥构造函数模式

这个模式适用于那种很特殊的this和new关键字都不给使用的环境。乍一看和工厂模式的代码几乎没有出入,除了不使用this关键字。和寄生构造器模式一样,无法用来识别对象类型,也没什么共享优势,最大的优势就是安全。

function createPerson(name,age){
	var o=new Object();
	o.name=name;
	o.age=age;
	o.sayName=function(){
		alert(name); 
                 //注意这里的差异,这个name在外层cretePerson的变量对象上,是可以沿着作用域链访问到的
	}
	return o;
}
var p=createPerson("Alice",20);
console.log(p);
p.sayName()  //输出"Alice"
复制代码
文章分类
前端
文章标签