更多优质内容
请关注公众号

JS面向对象之基本原则-张柏沛IT博客

正文内容

JS面向对象之基本原则

栏目:Web前端 系列:JS面向对象编程系列 发布时间:2019-12-12 15:32 浏览量:1999

本节开始讲解js高级部分,使用面向对象的方式编写代码,内容包括定义对象的几种方式,属性和方法,对象的引用,原型以及通过面向对象的方式写几个小实例。


1.创建一个对象
var obj = new Object();  //创建空对象
或者
var obj = {};

这两句话完全一样!


2.创建属性和方法
obj.name="小明";
obj.showName=function(){
    alert(obj.name);
}

obj.showName();

面向对象难的地方是this;
obj.showName=function(){
    alert(this.name);
}


3.工厂方式和构造函数
构造函数相当于php中的类,可以批量生产相同的对象
工厂方式就是一个函数

function createPerson(name){
    var obj=new Object();
    obj.name = name;
    obj.showName=function(){
        alert(this.name);
    }
    
    //最关键的一步,返回obj
    return obj;
}

var p1 = createPerson("小明");
var p2 = createPerson("小强");
p1.showName();
p2.showName();

这里自定义的对象和系统对象的区别,系统对象定于要new如
var p1=createPerson("xxx");
var a1=new Array();

现在我们想要将自定义的对象的实例化形式也弄得和系统对象一样,要有new;
这个时候,会利用到this;

createPerson(){ this }中的this是window对象;
但是当new去调用一个函数的时候,这个函数中的this就是实例化出来的对象,而且此时即使你return obj,他也会自动return this,这个叫做隐式返回。
隐式返回是说,如果一个函数不return东西,他会自动return undefined,而如果用new 调用一个函数,那么他会自动return this 

所以上面的工厂方法可以改成:工厂方法最好首字母大写,表示是类而不是一般的函数;
function CreatePerson(name){
    this.name=name;
    this.showName=function(){
        alert(this.name);  //这里的this就是showName前的对象,所以里面的this和匿名函数外面的this是同一个对象
    }
    
    //无需return
}

var p1=new CreatePerson("小明");
p1.showName();
var p2=new CreatePerson("小强");
p2.showName();


4.对象的引用:
承接上面,执行:
alert(p1.showName == p2.showName);
得到false;

我们知道赋值的话,基本类型只是复制值
对象类型是复制引用
var a= [1,2,3];
var b=a;
b.push(4);alert(b); //1,2,3,4
alert(a); //1,2,3,4

但是 
var a=[1,2,3];
var b=a;
var b=[1,2,3,4];
此时b是1,2,3,4
a是1,2,3
此时是两个地址

如果是比较的话,基本类型只要值相同就相等
对象类型,必须要值和引用都相同才为true
a=[1,2,3];
b=[1,2,3];
alert(a==b);  //false,值一样,地址不一样

a=[1,2,3];
b=a;
alert(a==b);  //true,值和地址都一样

alert(p1.showName == p2.showName);
得到false;是一样的道理,值相同,地址不同
因为p1和p2是两个对象;

所以如果我用这个工厂方法创建很多个对象,那么showName()就会在内存中存很多份,占了很多的地址;浪费资源和性能;
为了解决这个问题提出了原型;

5.原型:去改写对象下公用的方法或属性,是的方法和属性只在内存中存一份
例如:
var arr=[1,2,3,4,5];
//写一个sum方法
arr.sum=function(){
    var result=0;
    //this是sum之前的对象arr
    for(var i=0;i<this.length;i++){
        result+=this[i];
    }
    
    return result;
}

//现在用原型来定义:
原型要写在构造函数下面,构造函数是new之后的函数
数组的构造函数是Array()
Array.prototype.sum=function(){
var result=0;
    //this是sum之前的对象arr
    for(var i=0;i<this.length;i++){
        result+=this[i];
    }
    
    return result;
}

关于优先级,原型的优先级低于对象自己的对象和方法的优先级:
var arr=new Array();
arr.n=10;
Array.prototype.n=20;
alert(arr.n);  //10

前面如果用prototype来定义showName方法,那么
alert(p1.showName == p2.showName);就是true

如果经常变化的属性,不可以用原型来定义,因为经常变就不是公用的;

总结:所以一般面向对象的写法,是属性定义在构造方法内,不用原型定义;方法定义在构造函数外,用原型定义

function 构造方法(){
    this.属性 = xxx;
}

构造方法.prototype.方法名=function(){
    xxx
}


6.小实例:用面向对象编写选项卡
原则是:


a.先写出面向过程的选项卡,再改成面向对象的;

<html>
	<head>
	<style>
		#d1 div{
			width:200px;
			height:200px;
			border:1px solid black;
			display:none;
		}
		.active{
			background:red;
		}
	</style>
	<script>
	window.onload=function(){
		var d1=document.getElementById("d1");
		var btns=d1.getElementsByTagName("input");
		var divs=d1.getElementsByTagName("div");
		
		for(var i=0;i<btns.length;i++){
			btns[i].index=i;
			btns[i].onclick=function(){
				for(var j=0;j<btns.length;j++){
					btns[j].className="";
					divs[j].style.display="none";
				}
				
				this.className="active";
				divs[this.index].style.display="block";
				
			}
		}
	}
		
	</script>
	</head>
	<body>
		<div id="d1">
			
			<input type="button" value="1" class="active">
			<input type="button" value="2" >
			<input type="button" value="3" >
			
			<div style="display:block">111</div>
			<div>222</div>
			<div>333</div>
		</div>
	</body>
</html>


b.然后变形,变形的原则:

I. 尽量不要有函数嵌套函数,比如window.onload=function(){}就是一个函数,里面还有btns[i].onclick=function(){}这就是函数嵌套函数。所以函数中尽量不要有事件的定义
也就是说,方法和属性的声明要放在全局中(即window.onload外);
所以就变形成:

<script>
	var d1,btns,divs;
	
	window.onload=function(){
		d1=document.getElementById("d1");
		btns=d1.getElementsByTagName("input");
		divs=d1.getElementsByTagName("div");
		
		for(var i=0;i<btns.length;i++){
			btns[i].index=i;
			btns[i].onclick=change;
		}
	}
	
	function change(){
		for(var j=0;j<btns.length;j++){
			btns[j].className="";
			divs[j].style.display="none";
		}
		
		this.className="active";
		divs[this.index].style.display="block";
		
	}
		
	</script>
	


    上面就是把事件(方法)和属性的定义放在了全局


II.把onload中不是赋值的语句单独放在全局的函数中;给元素添加事件也有等号,但是不算是赋值语句;这里说的赋值语句是指给属性赋值的语句;

<script>
	var d1,btns,divs;
	
	window.onload=function(){
		d1=document.getElementById("d1");
		btns=d1.getElementsByTagName("input");
		divs=d1.getElementsByTagName("div");
		
		init();
	}
	
	function init(){
		for(var i=0;i<btns.length;i++){
			btns[i].index=i;
			btns[i].onclick=change;
		}
	}
	
	function change(){
		for(var j=0;j<btns.length;j++){
			btns[j].className="";
			divs[j].style.display="none";
		}
		
		this.className="active";
		divs[this.index].style.display="block";
		
	}
		
	</script>	


c.改成面向对象,原则是
1.全局变量就是对象的属性,所以所有全局变量都放在构造函数中定义,而且属性前都要加上this,全局函数就是对象的方法,而对象的方法要用原型定义
2.在onload中创建对象,并且初始化运行
改成:

window.onload=function(){
	var t1=new Tab();
	t1.init();
}
	
	function Tab(){
		this.d1=document.getElementById("d1");
		this.btns=this.d1.getElementsByTagName("input");
		this.divs=this.d1.getElementsByTagName("div");
	}
	
	Tab.prototype.init=function(){
		for(var i=0;i<this.btns.length;i++){
			this.btns[i].index=i;
			this.btns[i].onclick=this.change;
		}
	}
	
	Tab.prototype.change=function(){
		for(var j=0;j<this.btns.length;j++){
			this.btns[j].className="";
			this.divs[j].style.display="none";
		}
		
		this.className="active";
		this.divs[this.index].style.display="block";
		
	}


3.改this的指向,记住一点,事件和定时器中的this会改变,此时要重新指定this
上面是会报错的,因为有些this是不对的,比如change中的this代表的是按钮,而不是t1这个对象,所以我们要改一下this中的指向;
原则是:方法中的this必须全都代表实例化的对象(在这里是t1),而不能是事件的元素(在这里是btn)
所以改成:

window.onload=function(){
	var t1=new Tab();
	t1.init();
}
	
function Tab(){
	this.d1=document.getElementById("d1");
	this.btns=this.d1.getElementsByTagName("input");
	this.divs=this.d1.getElementsByTagName("div");
}

//里面的this都是指代实例化出来的对象没错
Tab.prototype.init=function(){
	var _this=this;  //改的地方
	for(var i=0;i<this.btns.length;i++){
		this.btns[i].index=i;
		this.btns[i].onclick=function(){  //改的地方
			//这里的this是按钮,把按钮的this作为参数传入change方法中,而change方法中的this就是调用change方法的对象,就是.change前的_this就是t1对象
			_this.change(this);
		}
	}
}

//这里面的this是指代实例化出来的对象,而且按原则也应该是这样
Tab.prototype.change=function(btn){
	for(var j=0;j<this.btns.length;j++){
		this.btns[j].className="";
		this.divs[j].style.display="none";
	}
	
	btn.className="active";
	this.divs[btn.index].style.display="block";
	
}


这样做的好处是,onload中的代码简洁了很多很多;

面向对象在简单的开发中不适合用,因为面向对象比写面向过程要复杂很多;在简单开发中就没有必要;
但是如果在复用性要求很高的程序中,就很管用!
而且可以组合使用里面的方法;

比如我有多个选项卡,只需要下面这样即可:

function Tab(){
    this.d1=document.getElementById("d1");
    this.btns=this.d1.getElementsByTagName("input");
    this.divs=this.d1.getElementsByTagName("div");
}

改成
function Tab(id){
    this.d1=document.getElementById(id);
    this.btns=this.d1.getElementsByTagName("input");
    this.divs=this.d1.getElementsByTagName("div");
}

onload写成:
window.onload=function(){
    var t1=new Tab("d1");
    t1.init();
    
    var t2=new Tab("d2");
    t2.init();
}
其他都不用改,这样就有两个选项卡


如果第二个选项卡要自动播放,第一个不要,我们可以写一个autoPlay()方法:

window.onload=function(){
		var t1=new Tab("d1");
		t1.init();
		
		var t2=new Tab("d2");
		t2.init();
		t2.autoPlay(); //改的地方
	}
	function Tab(id){
		this.d1=document.getElementById(id);
		this.btns=this.d1.getElementsByTagName("input");
		this.divs=this.d1.getElementsByTagName("div");
		this.iNow=0; //改的地方
	}

	Tab.prototype.init=function(){
		var _this=this;  
		for(var i=0;i<this.btns.length;i++){
			this.btns[i].index=i;
			this.btns[i].onclick=function(){
				_this.change(this);
			}
		}
	}

	//这里面的this是指代实例化出来的对象,而且按原则也应该是这样
	Tab.prototype.change=function(btn){
		for(var j=0;j<this.btns.length;j++){
			this.btns[j].className="";
			this.divs[j].style.display="none";
		}
		
		btn.className="active";
		this.divs[btn.index].style.display="block";
		this.iNow=btn.index;  //改的地方
		
	}
	
	Tab.prototype.autoPlay=function(){
		var _this=this;
		
		//setInterval内的this是window
		setInterval(function(){
			_this.eachChange();    //这里之所以还要再定义一个eachChange方法,而不是将eachChange方法的方法体直接作为匿名函数放到定时器的第一参是为了符合面向对象编程的函数中不能嵌套函数的原则
		},2000);
	}
	
	Tab.prototype.eachChange=function(){
		if(this.iNow >= this.btns.length-1){
			this.iNow=0;
		}else{
			this.iNow++;
		}
		
		this.change(this.btns[this.iNow]);
	}




*小练习:用面向对象写拖拽

先写出面向过程的写法:

<html>
	<head>
	<style>
		#d1{
			width:100px;
			height:100px;
			position:absolute;
			background:red;
		}
	</style>
	<script>
	window.onload=function(){
		var d1=document.getElementById("d1");
		
		d1.onmousedown=function(ev){
			var e = ev || event;
			
			var x1=e.clientX-d1.offsetLeft;
			var y1=e.clientY-d1.offsetTop;
			
			document.onmousemove=function(ev){
				var e = ev || event;
				var x = e.clientX-x1;
				var y = e.clientY-y1;
				var cw= document.documentElement.clientWidth || document.body.clientWidth;
				var ch= document.documentElement.clientHeight || document.body.clientHeight;
				
				if(x<0){
					x=0;
				}
				
				if(y<0){
					y=0;
				}
				
				if(x>cw-d1.offsetWidth){
					x=cw-d1.offsetWidth;
				}
				
				if(y>ch-d1.offsetHeight){
					y=ch-d1.offsetHeight;
				}
				
				d1.style.left=x+"px";
				d1.style.top=y+"px";
			}
			
			d1.onmouseup=function(ev){
				document.onmousemove=null;
			}
		}
	}
		
	</script>
	</head>
	<body>
		<div id="d1"></div>
		
	</body>
</html>


再是面向对象的写法:

<html>
	<head>
	<style>
		#d1{
			width:100px;
			height:100px;
			position:absolute;
			background:red;
		}
		#d2{
			width:100px;
			height:100px;
			position:absolute;
			bottom:0;
			background:green;
		}
		#d3{
			width:100px;
			height:100px;
			position:absolute;
			right:0;
			background:blue;
		}
		#d4{
			width:100px;
			height:100px;
			position:absolute;
			bottom:0;
			right:0;
			background:black;
		}
	</style>
	<script>
	window.onload=function(){
		var dg1=new Drag("d1");
		dg1.init();
		
		var dg2=new Drag("d2");
		dg2.init();
		
		var dg3=new Drag("d3");
		dg3.init();
		
		var dg4=new Drag("d4");
		dg4.init();
	}
	
	function Drag(id){
		this.d1=document.getElementById(id);
	}
	
	Drag.prototype.init=function(){
		var _this=this;
		this.d1.onmousedown=function(ev){
			var e = ev || event;
			_this.mouseDown(e);
		}
	}
	
	Drag.prototype.mouseDown=function(e){
		var _this=this;
		this.x1=e.clientX-this.d1.offsetLeft;
		this.y1=e.clientY-this.d1.offsetTop;
		
		document.onmousemove=function(ev){
			var e = ev || event;
			_this.move(e);
		}
		
		document.onmouseup=_this.mouseUp;
	
	}
	
	Drag.prototype.move=function(e){
		var x = e.clientX-this.x1;
		var y = e.clientY-this.y1;
		var cw= document.documentElement.clientWidth || document.body.clientWidth;
		var ch= document.documentElement.clientHeight || document.body.clientHeight;
		
		if(x<0){
			x=0;
		}
		
		if(y<0){
			y=0;
		}
		
		if(x>cw-this.d1.offsetWidth){
			x=cw-this.d1.offsetWidth;
		}
		
		if(y>ch-this.d1.offsetHeight){
			y=ch-this.d1.offsetHeight;
		}
		
		this.d1.style.left=x+"px";
		this.d1.style.top=y+"px";
	}
	
	Drag.prototype.mouseUp=function(){
		document.onmousemove=null;
		document.onmouseup=null;
		
	}
	</script>
	</head>
	<body>
		<div id="d1"></div>
		<div id="d2"></div>
		<div id="d3"></div>
		<div id="d4"></div>
	</body>
</html>

这里比选项卡多了一个事件对象ev,这个时候var e= ev || event;就不是写在Drag的方法中,而是写下调用Drag方法的外面那层匿名函数中;因为ev和event只有在事件的函数中才有,而mouseUp,mouseDown这个是普通的方法而已,没有事件对象的。




更多内容请关注微信公众号
zbpblog微信公众号

如果您需要转载,可以点击下方按钮可以进行复制粘贴;本站博客文章为原创,请转载时注明以下信息

张柏沛IT技术博客 > JS面向对象之基本原则

热门推荐
推荐新闻