欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JS面向对象之最常用创建对象的方式

程序员文章站 2022-06-15 13:45:40
...

目录

基本模式

工厂模式

构造函数模式

原型模式

组合模式(构造函数和原型模式组合)

动态原型模式


基本模式

我们都知道,创建对象的方法本质上都是把"属性"和"方法",封装成一个对象。普通创建对象的方法有缺陷,比如,如果创建多个对象会比较繁琐,效率低;实例与原型之间,没有任何办法可以看出有什么联系。如下:

        <script>
			var people1=new Object();
			people1.name='孙悟空';
			people1.weapon='金箍棒';
			//this是指的当前作用域下的对象,注意和谁调用这个方法有关,和在哪定义没啥关系
 			//这里也可以简单理解为this就指的这个函数属于谁,属于谁this就是指的谁
			people1.run=function(){
				return this.name+'的武器是'+this.weapon;
			}
			alert(people1.name);
			alert(people1.run());
			
			//该方法如果创建多个对象就会比较繁琐,如下......
			var people2=new Object();
			people2.name='猪八戒';
			people2.weapon='九齿钉耙';
			people2.run=function(){
				return this.name+'的武器是'+this.weapon;
			}
			alert(people2.name);
			alert(people2.run());
		</script>

工厂模式

所以为了解决重复创建多个对象的麻烦,我们用工厂模式的方法来创建对象,上面的方式我们称为基本模式。那先来解释一下什么是工厂模式:创建并返回特定类型的对象的工厂函数(其实就是普通函数,没啥区别,只是叫法不同)。创建过程类似于工厂生产产品的过程,即:原材料--加工--产品...(之后会在代码里详细解释)工厂模式解决了多次重复创建多个对象的麻烦。(工厂模式其实就是把创建对象的过程封装到一个函数里面

        <script>
			//工厂模式
			function createPeople(name,weapon){
				var people=new Object();//可以类比为加工对象的原材料
				people.name=name;
				people.weapon=weapon;
				people.run=function(){
					return this.name+'的武器是'+this.weapon;
				} //以上步骤可以类比为加工对象的过程
				return people;//注意一定要将创建的对象返回
				//可以类比为产品加工完毕出厂的工作
			}
			var wukong=createPeople('孙悟空','金箍棒');
			var bajie=createPeople('猪八戒','九齿钉耙');
			alert(wukong.run());
			alert(bajie.run());
		</script>

但这种方式同样存在一些弊端:

  • 创建出的实例之间没有内在的联系,不能反映出它们是同一个原型对象的实例。
  • 创建对象的时候没有使用 new 关键字(这里我们先埋下伏笔,之后会有详细解释
  • 会造成资源浪费,因为每生成一个实例,都增加一个重复的内容,多占用一些内存。
                <script>
			//工厂模式
			function createPeople(name,weapon){
				var people=new Object();//可以类比为加工对象的原材料
				people.name=name;
				people.weapon=weapon;
				people.run=function(){
					return this.name+'的武器是'+this.weapon;
				} //以上步骤可以类比为加工对象的过程
				return people;//注意一定要将创建的对象返回
				//可以类比为产品加工完毕出厂的工作
			}
			var wukong=createPeople('孙悟空','金箍棒');
			var bajie=createPeople('猪八戒','九齿钉耙');
			alert(wukong.run);
			alert(bajie.run);
			alert(wukong.run==bajie.run);//结果是false
                        //两个对象实例的地址是不同的,说明两个对象会占用两个地址空间的内存
		</script>

如上的代码中,我们弹出这两个函数本身,并判断它们是否相等,结果肯定是不相等的。也就是说,这两个对象存储的地址不一样,每创建一个对象,都会生成一个新的地址。我们知道,函数是引用类型的,所以复制函数后,它们的内容虽然是一样的,但其实存储的地址完全不同。所以,这就不难理解为什么这种方法会多占用一些内存,造成资源浪费了。(哈哈哈哈)

为了解决工厂模式留下的弊端,我接着来看看构造函数模式。

构造函数模式

我们先来认识一下什么是构造函数:构造函数和普通函数区别仅仅在于是否使用了new来调用。new 调用的函数即为构造函数。所谓“构造函数”,就是专门用来生成“对象”的函数。它提供模板,作为对象的基本结构。构造函数内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。而且,构造函数不需要使用 return语句返回对象,它的返回是自动完成的。但是构造函数模式仍然没有解决工厂模式遗留下来的问题:造成资源浪费,因为每生成一个实例,都增加一个重复的内容,多占用一些内存。

        <script>
			//构造函数模式
			//注意:构造函数不需要使用 return语句返回对象,它的返回是自动完成的
			function people(name,weapon){
				this.name=name;
				this.weapon=weapon;
				this.run=function(){
					return this.name+'的武器是'+this.weapon;
				}
			}
			var wujing=new people('沙悟净','禅杖');
			alert(wujing.run());
		</script>

所以,接着我们来看下(prototype)原型模式

原型模式

Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。可以把那些不变的属性和方法,直接定义在prototype对象上。

                <script>
			//原型模式
			function peopleobj(){};
			peopleobj.prototype.name='喽啰';
			peopleobj.prototype.weapon='大刀';
			peopleobj.prototype.run=function(){
				return this.name+'武器是'+this.weapon;
			}
			var  monster_1=new peopleobj();
			monster_1.job=[];
			var  monster_2=new peopleobj();
			alert(monster_1.name+'\n'+monster_1.run())
    		        alert(monster_2.name+'\n'+monster_2.run())
    		        alert(monster_1.run)
		        alert(monster_2.run)
		        alert(monster_1.run==monster_2.run) //说明他们的引用是同一个地址
		    //这时所有实例的方法,其实都是同一个内存地址,指向prototype对象,因此就提高了运行效率。
		</script>

(补充)proptotype模式的验证方法:

  • isPrototypeOf()这个方法用来判断,某个proptotype对象和某个实例之间的关系。
  • hasOwnProperty()每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。
  • in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。in运算符还可以用来遍历某个对象的所有属性。
  • 对象的constructor属性用于返回创建该对象的构造函数.在JavaScript中,每个具有原型的对象都会自动获得constructor属性。
                 <script>
			//原型模式
			function peopleobj(){};
			peopleobj.prototype.name='喽啰';
			peopleobj.prototype.weapon='大刀';
			peopleobj.prototype.run=function(){
				return this.name+'武器是'+this.weapon;
			}
			var  monster_1=new peopleobj();
			monster_1.job=[];
		        alert(peopleobj.prototype.isPrototypeOf(monster_1));
		        alert(monster_1.hasOwnProperty("name"));
		        alert(monster_1.hasOwnProperty("job"));
		        alert("jobb" in monster_1);
		</script>

原型模式的问题:

  • 构造函数没有参数。使用原型方式,不能通过给构造函数传递参数来初始化属性的值
  • 属性指向的是对象,而不是函数时。函数共享不会造成问题,但对象却很少被多个实例共享,如果共享的是对象就会造成问题。

组合模式(构造函数和原型模式组合)

目前最为常用的创建对象的方式。概念非常简单,即用构造函数定义对象的所有非函数属性,用原型方式定义对象的函数属性(方法)。结果是,所有函数都只创建一次,而每个对象都具有自己的对象属性实例。此外,组合模式还支持向构造函数传递参数,可谓是集两家之所长。在所接触的JS库中,jQuery类型的封装就是使用组合模式来实例的!!!

        <script>
			function Monster(){} 
    		Monster.prototype={
		        constructor: Monster, 
		        name:'喽啰', 
		        job:['巡山','打更'],
		        run:function() {return this.name+'的工作是'+this.job }
    		}
    		
    		//构造函数和原型组合模式
    		function Monster(name,arr){
    			constructor: Monster, 
		        this.name=name
		        this.job=arr
    		}
    		Monster.prototype={
		        run:function() {return this.name+'的工作是'+this.job }
		    }
    		var monsterI=new Monster('小旋风',['巡山','打更','砍柴'])
    		var monsterII=new Monster('小钻风',['巡山','打更','挑水'])
    		alert(monsterI.run())
    		alert(monsterII.run())
		</script>

本来这种方式是很完美了的,但还是有点点瑕疵:封装性不是很好。从上面的代码就能看出来,属性和方法各写了一个函数,但这并不影响我们使用。但总归还是一个瑕疵,所以接下来我们还要来认识一下动态原型模式

动态原型模式

态原型方法的基本想法与混合的构造函数原型方式相同,即在构造函数内定义非函数属性,而函数属性则利用原型属性定义。组合模式中实例属性与共享方法(由原型定义)是分离的,这与纯面向对象语言不太一致;动态原型模式将所有构造信息都封装在构造函数中,又保持了组合的优点。其原理就是通过判断构造函数的原型中是否已经定义了共享的方法或属性,如果没有则定义,否则不再执行定义过程。该方式只原型上方法或属性只定义一次,且将所有构造过程都封装在构造函数中,对原型所做的修改能立即体现所有实例。

        <script>
    		//动态原型模式
    		function MonsterGo(name,arr){
    			this.name=name
		        this.job=arr
		        if (typeof this.run!= "function"){
		        	//alert('对象初始化')
		        	MonsterGo.prototype.run=function(){
          			return this.name+'的工作是'+this.job 
        		}
		        //alert('初始化结束')
		        }
    		}
    		var monsterI=new MonsterGo('小旋风',['巡山','打更','砍柴'])
   			var monsterII=new MonsterGo('小钻风',['巡山','打更','挑水'])
   			var monsterI2=new MonsterGo('小旋风',['巡山','打更','砍柴'])
   			var monsterII2=new MonsterGo('小钻风',['巡山','打更','挑水'])
			//alert(monsterI.run())
     		//alert(monsterII.run())
		</script>

今天的小结就到这里啦,下一节分享面向对象的另一个重要内容——继承