学习打卡 JavaScript面向对象编程-创建对象

JavaScript面向对象编程-创建对象


1. 工厂模式:

        考虑到em5无法创建类(我也不知道em6那个玩意儿算不算类),开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节;
        【注】工厂模式解决不了对象识别的问题(即怎样知道一个对象的类型)

//工厂模式
function Person(name, age, job){
	var o = new Object();
	o.name = name;
	o.age = age;
	o.job = job;
	o.greeting = function(){
		console.log(`Hi,I am ${this.name}`);
	};
	return o;
}

var person1 = new Person("xhy", 21, "student");
var person2 = new Person("yhs", 28, "rapper");
	
person1.greeting();//Hi,I am xhy

2. 构造函数模式:

        借鉴了其它OO语言,我们可以创建一个 名字首字母大写的自定义构造函数,从而定义自定义对象类型的属性和方法:

//构造函数模式
function Person(name, age, job){
	this.name = name;
	this.age = age;
	this.job = job;
	this.greeting = function(){
		console.log(`Hi,I am ${this.name}`);
	};
}

var person1 = new Person("xhy", 21, "student");
var person2 = new Person("yhs", 28, "rapper");
	
person1.greeting();//Hi,I am xhy

        要创建Person的实例,需要经历以下四个步骤:
        (1) 创建一个新对象;
        (2) 将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
        (3) 执行构造函数中的代码(为新对象添加属性);
        (4) 返回新对象;

        在本例中,person1和person2 这两个对象都有一个constructor(构造函数)属性,该属性指向Person:

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

        【注】构造函数模式也有其问题所在,每当某一个实例调用构造函数中定义的方法时,每个方法都要被重新创建一遍;而创建方法函数function时,实际上也是实例化了一个对象,即

// this.name = funciton{...} 等价于 this.name = new Function(...);
// 毕竟 JS 里面万物皆可为对象

        因此,不同实例上的同名函数其实是不相等甚至没有联系的,例如本例中:

console.log(person1.greeting == person2.greeting);//false

        以这种方式创建函数,会导致不同的作用域链和标识符解析,莫名感觉浪费 ~

3. 原型模式

        在上例代码上,当我把person1直接输出到控制台时,呈现的结果是这样的:
在这里插入图片描述
        上边那几个属性,方法都很好理解,但最后一排这个__proto__又是什么呢

        【注】无论何时,当一个新函数被创建出来时,就会为该函数自动创建一个prototype属性,这个属性指向函数的原型对象;而调用构造函数创建一个新实例后,该实例内部将产生一个名为 [[ Prototype ]] 的指针——指向构造函数的原型对象;
        虽然在脚本中没有标准的方式可以去访问 [[ Prototype ]] 指针,但Firefox,Safari和Chrome在每个对象上都支持__proto__属性;

        构造函数的原型对象默认只会取得constructor属性
在这里插入图片描述
        而我们则可以将创建对象所需要的所有信息直接添加到原型对象中,这样就不必在构造函数里定义对象实例的信息了,并且我们可以让所有对象实例共享它所包含的属性和方法:

//原型模式
function Person(){}

Person.prototype.name = 'xhy';
Person.prototype.age = 21;
Person.prototype.job = 'student';
Person.prototype.greeting = function(){
	console.log(`Hi,I am ${this.name}`);
}

var person1 = new Person();
var person2 = new Person();

console.log(person1.name);//xhy
person1.greeting();//Hi,I am xhy
 
console.log(person1);

        此时的__proto__:
在这里插入图片描述
        (就都在里面惹~)

        下图展示了本例中,各个对象之间的关系:
在这里插入图片描述
        具体实现过程中,例如,此时要访问 person1.name 的值,解析器就会先从person1实例中寻找name,发现找不到之后,再根据指针去找person1的原型,并将原型内部的name值进行返回;
        显然,当我们在实例化对象后,再给person1实例赋予一个name值时,新的name值将被优先返回;

console.log(person1.name);//xhy —— 来自原型
 person1.name = 'yhs';
 console.log(person1.name);//yhs —— 来自实例

        【注】:为对象实例添加一个属性,这个属性只会屏蔽原型对象中保存的属性,并不会修改;

———————————————————————————

        上边例子中每添加一个属性和方法都要敲一遍 Person.prototype,我们可以按照如下方式简化代码:

Person.prototype = {
	name : 'xhy',
	age : 21,
	job : 'student',
	greeting : function(){
		console.log(`Hi,I am ${this.name}`);
	}
};

        【注】:当采用此种写法时,constructor属性就不再指向Person了;原因在于我们在这里的写法本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person。
        如果constructor值真的很重要,我们可以在上例创建对象的开头写入:

constructor : Person,

———————————————————————————

        由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来——即使是先创建了实例后修改原型也照样如此,如:

var fakeFriend = new Person();
		 
Person.prototype.showFeeling = function(){
	 console.log('我一直一直把你看的很重要很重要');
 }
 
 fakeFriend.showFeeling();//我一直一直把你看的很重要很重要

        从这里就可以很明显地反映出,实例与原型之间的连接只不过是一个指针,而非一个副本,因此 fakeFriend 就可以在原型中找到新的 showFeeling() 方法并返回;
        【注】尽管可以很方便的为原型添加属性方法并快速地在实例中反映出来,但如果是重写整个原型对象,情况就很糟糕了:

function Person(){}
		
var fakeFriend = new Person();

Person.prototype = {
	constructor : Person;
	name : 'xhy',
	age : 21,
	job : 'student',
	greeting : function(){
		console.log(`Hi,I am ${this.name}`);
	},
	showFeeling : function(){
		console.log('我一直一直把你看的很重要很重要');
	}
};

fakeFriend.showFeeling();//fakeFriend.showFeeling is not a function

        直接报错~ (果然,一个不真诚的人 在其他人眼里的形象完全颠覆之时,ta再怎么 晓之以情都是不好使的嘿嘿嘿)
        嘛,出现这个问题的原因说来也简单,当重写整个原型对象时,相当于切断了构造函数和最初的原型对象之间的联系(在本例中,因为重写时赋予了constructor指向,它仍然会指向新的Person),而实例呢,它仍然指向的是初始的原型对象,其中自然不包含重写时候顺带添加上去的新方法。调用该方法时自然就报错了。

———————————————————————————

        原型对象有缺点吗? 毋庸置疑,而且问题还不小;
        首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值,这在一定程度上给操作带了不方便;
        其次,也是最大的问题: 原型中所有属性是被很多实例共享的,这种共享对于函数极为合适,对于普通数据类型的属性倒也说的过去(前边提到过的覆盖方法),但对于引用数据类型来说,麻烦就很大了,例:

function Person(){}
		
Person.prototype = {
	name : 'xhy',
	age : 21,
	job : 'student',
	greeting : function(){
		console.log(`Hi,I am ${this.name}`);
	},
	friend : ['yhs', 'zy', 'jonyJ']
};

var person1 = new Person();//(4) ["yhs", "zy", "jonyJ", "PGone"]
var person2 = new Person();//(4) ["yhs", "zy", "jonyJ", "PGone"]

        当我们通过person1实例修改friend数组值时,person2中的数组值不自觉的也受到了影响(背后的原因可以去看笔者以前的一篇博客JavaScript数组引用问题),这样实例之间的引用数据类型的区别就体现不出来了。
        为了解决此类问题,我们可以 组合使用构造函数模式与原型模式。

4. 组合使用构造函数模式和原型模式

//组合使用模式
 function Person(name,age,job){
	this.name = name;
	this.age = age;
	this.job = job;
	this.friend = ['yhs', 'zy', 'jonyJ'];
 }
 
 Person.prototype = {
	 constructor : Person ,
	 greeting : function(){
		console.log(`Hi,I am ${this.name}`);
	}
 }
 
 var person1 = new Person('xhy',21,'student');
 var person2 = new Person('yhs',28,'rapper');
 
 person1.friend.push('PGone');
 
 console.log(person1.friend);//(4) ["yhs", "zy", "jonyJ", "PGone"]
 console.log(person2.friend);//(3) ["yhs", "zy", "jonyJ"]
 console.log(person1.friend === person2.friend);//false
 console.log(person1.greeting() === person2.greeting());//true

        问题完美解决有没有~
        这种构造函数和原型混成的模式,是目前em中使用最为广泛,认同度最高的一种创建自定义类型的方法,可以说这是用来定义引用类型的一种默认模式;

5. 其它

        其它的构造对象的模式还包括:
        (1) 动态原型模式;
        (2) 寄生构造函数模式;
        (3) 稳妥构造函数模式;
        感兴趣的小伙伴可以去搜集资料查阅一下~ 在此就不整理啦(其实只是笔者自己也没太搞懂~)

——————————————————————————————————————END——————————-——————

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值