Writeups

Just a blog.

妙味课堂——JS面向对象

js 妙味课堂 前端基础 学习笔记

第一次接触面向对象,现在仅能说是稍稍有一点点感觉。把近半个月来看妙味视频学习的面向对象的开发笔记备份在这里:

##什么是面向对象编程

  • 用对象的思想去写代码,就是面向对象编程
    • 过程式写法
    • 面向对象写法
  • 我们一直都在使用对象
    • 数组Array 时间Date

###面向对象编程(OOP)的特点 - 抽象:抓住核心问题 - 封装:只能通过对象来访问方法 - 继承:从已有对象上继承出新的对象 - 多态:多对象的不同形态

###对象的组成

  • 方法(行为、操作) —— 对象下面的函数:过程、动态的
  • 属性 —— 对象下面的变量:状态、静态的

###创建第一个面向对象程序

//var obj = {};
var obj = new Object(); //创建了一个空的对象

obj.name = '小明'; //属性
obj.showName = function(){ //方法
	alert(this.name);
}

obj.showName();
  • 为对象添加属性和方法
    • Object对象
    • this指向
    • 创建两个对象:重复代码过多
//var obj = {};
var obj = new Object(); //创建了一个空的对象

obj.name = '小明'; //属性
obj.showName = function(){ //方法
	alert(this.name);
}

obj.showName();

var obj2 = new Object(); 

obj2.name = '小强';
obj.showName = function(){
	alert(this.name);
}

obj2.showName();

###工厂方式

  • 面向对象中的封装函数
//工厂方式:封装函数

function createPerson(name){
    //1. 原料
	var obj = new Object();
	
	//2. 加工
	obj.name = name;
	obj.showName = function(){
		alert(this.name);
	};
	
	//3. 出厂
	return obj;
}

var p1 = createPerson('小明');
p1.showName();
var p2 = createPerson('小强');
p2.showName();
  • 改成与系统对象类似的写法
    • 首字母大写
    • New关键字提取
    • this指向为新创建的对象
/* 当new去调用一个函数:这个时候函数中的this就是创建出来的对象,而且函数的返回值直接就是this啦。(这叫做隐式返回) */

// new后面调用的函数:构造函数

function CreatePerson(name){
	this.name = name;
	this.showName = function(){
		alert(this.name);
	};
	// return obj; 隐式返回,所以这一行不用写了
}

var p1 = new CreatePerson('小明');
p1.showName();
var p2 = new CreatePerson('小强');
p2.showName();
  • 构造函数
    • 用来创建对象的函数,叫做构造函数
  • 存在的问题
    • 对象的引用
    • 浪费内存
/* 当new去调用一个函数:这个时候函数中的this就是创建出来的对象,而且函数的返回值直接就是this啦。(这叫做隐式返回) */

// new后面调用的函数:构造函数

function CreatePerson(name){
	this.name = name;
	this.showName = function(){
		alert(this.name);
	};
	// return obj; 隐式返回,所以这一行不用写了
}

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

//alert(p1.showName == p2.showName); //false
/*
	var a = [1, 2, 3];
	var b = [1, 2, 3];

	alert(a == b); //false;
*/

/*
	var a = 5;
	var b = a;
	b += a;
	alert(b); //8
	alert(a); //5 基本类型:赋值的时候只是值的复制
*/

/*
	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;
	b = [1, 2, 3, 4];
	alert(b); //[1, 2, 3, 4]
	alert(a); //[1, 2, 3] 只要赋值就会在内存中重新生成,所以a,b互补影响
*/

/*
	var a = 5;
	var b = 5;
	alert(a == b); //true 基本类型的比较:只要值相同就可以
*/

/*
	var a = [1, 2, 3];
	var b = [1, 2, 3];
	alert(a == b); //false 对象类型:值和引用都相同才行
*/

/*
	var a = [1, 2, 3];
	var b = a;
	alert(a == b); //true
*/

###原型 —— prototype

  • 概念
    • 去改写对象下面公用的方法或者属性,让公用的方法或者属性在内存中仅存在一份(好处:提高性能)
  • 学习原型
    • 类比:原型就是CSS中的class(普通方法就是CSS中的style)
    • 普通方法的优先级比原型要高
    • 原型可以复用,普通方法不可以复用
var arr = []; 
arr.number = 10;
Array.prototype.number = 20;
alert(arr.number); //10 普通方法的优先级高于原型
//原型:prototype:要写在构造函数下面
var arr = [1, 2, 3, 4, 5];
var arr2 = [2, 2, 2, 2, 2];
Array.prototype.sum = function(){
	var result = 0;
	for(var i=0; i<this.length; i++){
		result += this[i];
	}
	return result;
}

/*
arr.sum = function(){
	var result = 0;
	for(var i=0; i<this.length; i++){
		result += this[i];
	}
	return result;
}

alert(arr.sum()); //15
*/
alert(arr.sum());
alert(arr2.sum());
  • 通过原型改写工厂方式
    • 原则:
      • 相同的属性和方法可以加在原型上
      • 混合的编程模式
function CreatePerson(name){
	this.name = name; //变化的,不能公用的属性不能写在原型上
}
CreatePerson.prototype.showName = function(){
	alert(this.name);
}	

var p1 = new CreatePerson('小明');
var p2 = new CreatePerson('小强');

alert(p1.showName == p2.showName); //true

混合的编程模式

//面向对象的写法
function 构造函数(){
    this.属性
}
构造函数.原型.方法 = function(){};

//面向对象的使用
var 对象1 = new 构造函数();
对象1.方法();
  • 总结面向对象写法
    • 构造函数加属性,原型加方法

###面向对象的选项卡

  • 原则
    • 先写出普通的写法,然后改成面向对象写法
      • 普通方法变型
        • 尽量不要出现函数嵌套函数
        • 可以有全局变量
        • 把onload中不是赋值的语句放到单独的函数中
      • 改成面向对象
        • 全局变量就是属性
        • 函数就是方法
        • onload中创建对象
        • 改this指向问题,要尽量让面向对象中的this指向对象

先写出普通方法

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>面向对象的选项卡</title>
	<style>
		#div1 div { width: 200px; height: 200px; border: 1px solid #000; display: none; }
		.active { background: red; }
	</style>
	<script>
		window.onload = function(){
			var oParent = document.getElementById('div1');
			var aInput = oParent.getElementsByTagName('input');
			var aDiv = oParent.getElementsByTagName('div');

			for(var i=0; i<aInput.length; i++){
				aInput[i].index = i;
				aInput[i].onclick = function(){
					for(var i=0; i<aInput.length; i++){
						aInput[i].className = '';
						aDiv[i].style.display = 'none';
					}
					this.className = 'active';
					aDiv[this.index].style.display = 'block';
				}
			}
		}
	</script>
</head>
<body>
	<div id="div1">
		<input type="button" value="1" class="active">
		<input type="button" value="2">
		<input type="button" value="3">
		<div style="display: block">11111</div>
		<div>22222</div>
		<div>33333</div>
	</div>
</body>
</html>

普通方法变型

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>面向对象的选项卡</title>
	<style>
		#div1 div { width: 200px; height: 200px; border: 1px solid #000; display: none; }
		.active { background: red; }
	</style>
	<script>

		var oParent = null;
		var aInput = null;
		var aDiv = null;

		window.onload = function(){
			oParent = document.getElementById('div1');
			aInput = oParent.getElementsByTagName('input');
			aDiv = oParent.getElementsByTagName('div');

			init();
			
		};

		function init(){ //初始化的函数方法
			for(var i=0; i<aInput.length; i++){
				aInput[i].index = i;
				aInput[i].onclick = change;
			}
		}

		function change(){
			for(var i=0; i<aInput.length; i++){
				aInput[i].className = '';
				aDiv[i].style.display = 'none';
			}
			this.className = 'active';
			aDiv[this.index].style.display = 'block';
		}

		/*
		- 普通方法变型
            - 尽量不要出现函数嵌套函数
            - 可以有全局变量
            - 把onload中不是赋值的语句放到单独的函数中
        */
	</script>
</head>
<body>
	<div id="div1">
		<input type="button" value="1" class="active">
		<input type="button" value="2">
		<input type="button" value="3">
		<div style="display: block">11111</div>
		<div>22222</div>
		<div>33333</div>
	</div>
</body>
</html>

关于this的指向

oDiv.onclick = function(){
    this: oDiv
};

---

oDiv.onclick = show;
function show(){
    this: oDiv
}

---

oDiv.onclick = function(){
    show();
};
function show(){
    this: window
}

改写成面向对象

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>面向对象bianxie</title>
	<style>
		#div1 div { width: 200px; height: 200px; border: 1px solid #000; display: none; }
		.active { background: red; }
	</style>
	<script>

		var oParent = null;
		var aInput = null;
		var aDiv = null;

		window.onload = function(){
			
			var t1 = new Tab();
			t1.init();
			
		};

		function Tab(){
			this.oParent = document.getElementById('div1');
			this.aInput = this.oParent.getElementsByTagName('input');
			this.aDiv = this.oParent.getElementsByTagName('div');
		}

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

		Tab.prototype.change = function(obj){
			for(var i=0; i<this.aInput.length; i++){
				this.aInput[i].className = '';
				this.aDiv[i].style.display = 'none';
			}
			obj.className = 'active';
			this.aDiv[obj.index].style.display = 'block';
		}

		/*
		- 改成面向对象
            - 全局变量就是属性
            - 函数就是方法
            - onload中创建对象
            - 改this指向问题:注意事件或者是定时器里面的this。要尽量保持面向对象中的this指向对象
        */
	</script>
</head>
<body>
	<div id="div1">
		<input type="button" value="1" class="active">
		<input type="button" value="2">
		<input type="button" value="3">
		<div style="display: block">11111</div>
		<div>22222</div>
		<div>33333</div>
	</div>
</body>
</html>

面向对象的复用

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>面向对象bianxie</title>
	<style>
		#div1 div, #div2 div { width: 200px; height: 200px; border: 1px solid #000; display: none; }
		.active { background: red; }
	</style>
	<script>

		var oParent = null;
		var aInput = null;
		var aDiv = null;

		window.onload = function(){
			
			var t1 = new Tab('div1');
			t1.init();

			var t2 = new Tab('div2');
			t2.init();
			t2.autoplay();
			
		};

		function Tab(id){
			this.oParent = document.getElementById(id);
			this.aInput = this.oParent.getElementsByTagName('input');
			this.aDiv = this.oParent.getElementsByTagName('div');
			this.iNow = 0;
		}

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

		Tab.prototype.change = function(obj){
			for(var i=0; i<this.aInput.length; i++){
				this.aInput[i].className = '';
				this.aDiv[i].style.display = 'none';
			}
			obj.className = 'active';
			this.aDiv[obj.index].style.display = 'block';
		}
		Tab.prototype.autoplay = function(){
			var This = this;
			setInterval(function(){
				if(This.iNow == This.aInput.length - 1){
					This.iNow = 0;
				} else {
					This.iNow ++;
				}
				for(var i=0; i<This.aInput.length; i++){
					This.aInput[i].className = '';
					This.aDiv[i].style.display = 'none';
				}
				This.aInput[This.iNow].className = 'active';
				This.aDiv[This.iNow].style.display = 'block';
			}, 2000)
		}
	</script>
</head>
<body>
	<div id="div1">
		<input type="button" value="1" class="active">
		<input type="button" value="2">
		<input type="button" value="3">
		<div style="display: block">11111</div>
		<div>22222</div>
		<div>33333</div>
	</div>

	<div id="div2">
		<input type="button" value="1" class="active">
		<input type="button" value="2">
		<input type="button" value="3">
		<div style="display: block">11111</div>
		<div>22222</div>
		<div>33333</div>
	</div>
</body>
</html>

###面向对象的拖拽

  • 注意
    • Event对象(event对象一定要写到事件函数里面)
    • 事件函数中用来阻止默认行为的return false也要写到事件函数里面
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>面向对象编写拖拽</title>
	<style>
		#div1 { width: 100px; height: 100px; background: red; position: absolute; }
	</style>
	<script>

	/* 普通拖拽
		window.onload = function(){
			var oDiv = document.getElementById('div1');
			var disX = 0;
			var disY = 0;

			oDiv.onmousedown = function(ev){
				var ev = ev || window.event;

				disX = ev.clientX - oDiv.offsetLeft;
				disY = ev.clientY - oDiv.offsetTop;

				document.onmousemove = function(ev){
					var ev = ev || window.event;

					oDiv.style.left = ev.clientX - disX + 'px';
					oDiv.style.top = ev.clientY - disY + 'px';
				}

				document.onmouseup = function(){
					document.onmousemove = null;
					document.onmouseup = null;
				}

				return false;

			}
		}
	*/

	/* 第一步:普通方法变型
	//先变型
		var oDiv = null;
		var disX = 0;
		var disY = 0;

		window.onload = function(){
			oDiv = document.getElementById('div1');
			init();			
		}

		function init(){
			oDiv.onmousedown = fnDown;
		}

		function fnDown(ev){
			var ev = ev || window.event;

				disX = ev.clientX - oDiv.offsetLeft;
				disY = ev.clientY - oDiv.offsetTop;

				document.onmousemove = fnMove;
				document.onmouseup = fnUp;

				return false;
		}

		function fnMove(ev){
			var ev = ev || window.event;

			oDiv.style.left = ev.clientX - disX + 'px';
			oDiv.style.top = ev.clientY - disY + 'px';
		}

		function fnUp(){
			document.onmousemove = null;
			document.onmouseup = null;
		}
		*/

		//改成面向对象
		window.onload = function(){
			var d1 = new Drag('div1');
			d1.init();			
		}

		function Drag(id){
			this.oDiv = document.getElementById(id);
			this.disX = 0;
			this.dixY = 0;
		}

		Drag.prototype.init = function(){
			var This = this;
			this.oDiv.onmousedown = function(ev){
				var ev = ev || window.event;
				This.fnDown(ev);
				return false;
			};
		}

		Drag.prototype.fnDown = function(ev) {
			var ev = ev || window.event;
			var This = this;

				this.disX = ev.clientX - this.oDiv.offsetLeft;
				this.disY = ev.clientY - this.oDiv.offsetTop;

				document.onmousemove = function(ev){
					var ev = ev || window.event;
					This.fnMove(ev);
				};
				document.onmouseup = this.fnUp;			
		}

		Drag.prototype.fnMove = function(ev){
			this.oDiv.style.left = ev.clientX - this.disX + 'px';
			this.oDiv.style.top = ev.clientY - this.disY + 'px';
		}

		Drag.prototype.fnUp = function(){
			document.onmousemove = null;
			document.onmouseup = null;
		}
	</script>
</head>
<body>
	<div id="div1"></div>
</body>
</html>

###本课练习

  1. 为数组对象添加求和,最大值
  2. 为字符串对象添加判断是不是最后一个字母
  3. 面向对象的选项卡
  4. 给选项卡添加一个自动播放的方法
  5. 任意学过的效果改写成面向对象
  6. 面向对象的面试题

##高级面向对象

###包装对象

  • JS基于原型的程序
  • String Number Boolean
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title></title>
	<style>	

	</style>
	<script>

	/*
		function Aaa(){
			this.name='小明';
		}

		Aaa.prototype.showName=function(){
			alert(this.name);
		}

		var a1 = new Aaa();
		a1.showName();

		var arr = new Array();
		arr.push();
		arr.sort();

		// 在js源码中:系统对象也是基于原型的程序

		function Array(){
			this.length = 0;
		}

		Array.prototype.push = function(){};
		Array.prototype.sort = function(){};
	*/

	//尽量不要去修改或添加系统对象下面的方法和属性
	var arr = [1, 2, 3];
	//Arr.prototype.push = function(){}; //加上这句话,就修改了源码中的push方法,那么后面那一句中的4、5、6就添加不进去了
	Array.prototype.push = function(){ //自己写一个数组的push方法
		//this : 1, 2, 3
		//arguments: 4, 5, 6

		for(var i=0; i<arguments.length; i++){
			this[this.length] = arguments[i];
		}

		return this.length;
	}
	arr.push(4, 5, 6);
	alert(arr);
	</script>
</head>
<body>
</body>
</html>

给基本类型添加方法

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title></title>
	<style>	

	</style>
	<script>
	/*
		var str = 'hello';
		alert( typeof str); //string
		str.charAt(0); //字符串为什么下面会有方法呢?
		str.indexOf('e');
	*/

	//包装对象:基本类型都有自己对应的包装对象(String, Number, Boolean)(null和undefined没有包装对象)

	/*
	var str = new String('hello');
	alert(typeof str); //object
	alert(str.charAt(1)); //弹出e

	String.prototype.charAt = function(){}; //基本类型的方法都是放在它们的包装对象上
	*/

	/*
	var str = 'hello'; //str是字符串
	str.charAt(0); //基本类型找到对应的包装对象类型,然后包装对象把所有的属性和方法给了基本类型,然后包装对象消失
	*/

	//给基本类型添加对象的时候,就是把方法添加到基本类型对应的包装对象下面
	var str = 'hello';
	String.prototype.lastValue = function(){
		return this.charAt(this.length-1);
	};
	alert(str.lastValue()); //o
	
	var str1 = 'hello';
	str1.number = 10; //在str1的包装对象上创建了一个number,然后包装对象就消失了
	alert(str1.number); //undefined 再去调用这句话的时候,此时又重新创建了一个对象,这个对象与刚才那个对象不是同一个	

	</script>
</head>
<body>
</body>
</html>

###原型链

  • 实例对象与原型之间的连接,叫做原型链
  • proto (隐式连接)
  • Object对象类型是原型链的最外层
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>面向对象中的原型链</title>
	<style></style>
	<script>
	
		//原型链:实例对象与原型之间的连接叫作原型链
		//_proto_ (隐式连接)
		//原型链的最外层是Object.prototype

		function Aaa(){
			
		}

		Aaa.prototype.num = 10;

		var a1 = new Aaa();
		alert(a1.num); //10 a1现在它自己下面找,找不到这个num,然后又通过原型链到了Aaa.prototype,到它下面去找,最终找到了num

		function Bbb(){
			this.num = 20;
		}

		Bbb.prototype.num = 10;
		var b1 = new Bbb();
		alert(b1.num); //20 简单来讲,实例上的方法的优先级高于原型上的方法;本质上来讲,是从b1下面找num直接就找到了,于是就不需要再进一步通过原型链去找到Bbb.prototype上的num了。

		function Ccc(){};
		var c1 = new Ccc();
		Object.prototype.num = 30; //弹出30 c1下找不到num;顺着原型链找到Ccc也找不到num,继续顺着原型链往上找,找到Object.prototype,找到了num
		alert(c1.num);
	</script>
</head>
<body>
</body>
</html>

###面向对象的一些属性和方法

####hasOwnProperty()

  • 看是不是对象自身下面的属性
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>面向对象中的原型链</title>
	<style></style>
	<script>
		//hasOwnProperty: 看是不是对象自身下面的属性

		var arr = [];
		arr.num = 10;
		Array.prototype.num2 = 20;

		alert(arr.hasOwnProperty('num')); //true
		alert(arr.hasOwnProperty('num2')); //false
		alert(arr.hasOwnProperty == Object.prototype.hasOwnProperty); //true hasOwnProperty其实是Object.prototype下面的方法
	</script>
</head>
<body>
</body>
</html>

####constructor

  • 查看对象的构造函数
    • 每个原型都会自动添加constructor属性
    • for in 的时候,有些属性是找不到的
    • 避免修改constructor属性
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>面向对象中的原型链</title>
	<style></style>
	<script>
		//constructor: 查看对象的构造函数

		function Aaa(){

		}

		var a1 = new Aaa();

		alert(a1.constructor); //function Aaa

		var arr = [];
		alert(arr.constructor == Array); //true

		/*
		在写完一个构造函数之后
		function Aaa(){}
		程序会自动添加一句话:
		Aaa.prototype.constructor = Aaa; //每一个函数都会有的,都是自动生成的
		这时候new一个Aaa
		var a1 = new Aaa();
		alert(a1.constructor); //弹出Aaa
		如果手动写一句:
		Aaa.prototype.constructor = Array;
		那么再去弹a1.constructor的话,弹出的就是Array了。

		而hasOwnProperty这个属性是添加在Object.prototype下面的,所以每个对象下面去查这个hasOwnProperty也都会有。但是hasOwnProperty这个方法不是在具体对象下面的,而都是沿着原型链找到Object.prototype身上找到的。跟constructor是不一样的。
		*/

		function Bbb(){

		}

		Bbb.prototype.name = '小明';
		Bbb.prototype.age = 20;

		var b1 = new Bbb();
		alert(b1.constructor); //function Bbb()

		//下面这种写法就无意识地改变了c1的constructor,因为json直接赋值给了Ccc.prototype,而不是向上面那段代码是添加操作。那么原本系统自动生成的那句话Ccc.prototype.constructor = Ccc这句话就被覆盖掉了。然后通过c1.constructor去找的话,其实找的就是这个json所对应的constructor了。
		function Ccc(){

		}
		Ccc.prototype = {
			name: '小明',
			age: 20
		}
		var c1 = new Ccc();
		alert(c1.constructor); //function Object

		//为了避免上述问题,应该注意,用json简写的时候,要把constructor修正过来,如下:

		function Ddd(){

		}
		Ddd.prototype = {
			constructor: Ddd, //通过这一句来修正一下
			name: '小明',
			age: 20
		}
		var d1 = new Ddd();
		alert(d1.constructor); //Ddd

	</script>
</head>
<body>
</body>
</html>

for in循环,有些属性找不到

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>面向对象中的原型链</title>
	<style></style>
	<script>
		function Aaa(){

		}
		Aaa.prototype.name = 10;
		Aaa.prototype.constructor = Aaa;
		for (var attr in Aaa.prototype){
			alert(attr); //只能找到自己添加的,系统自动生成的比如constructor,for in循环是找不到的
		}
	</script>
</head>
<body>
</body>
</html>

####instanceof

  • 运算符
    • 对象与构造函数在原型链上是否有关系
    • 可以用来作类型判断
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>instanceof()</title>
	<style></style>
	<script>
		// instanceof: 对象与构造函数在原型链上是否有关系

		function Aaa(){

		}
		var a1 = new Aaa();
		alert(a1 instanceof Aaa); //true 看a1是否与Aaa在同一个原型链上
		alert(a1 instanceof Array); //false
		alert(a1 instanceof Object); //true
	</script>
</head>
<body>
</body>
</html>

####toString()

  • object上的方法
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>toString()</title>
	<style></style>
	<script>
		// toString(): 
		// 位置:系统对象下面都是自带的;自己写的对象都是通过原型链找Object下面的
		// 作用:把对象转成字符串

		var arr = [];
		alert(arr.toString); //找得到
		alert(arr.toString == Object.prototype.toString); //false 系统对象下面的toString不在Object原型上

		function Aaa(){

		}
		var a1 = new Aaa();
		alert(a1.toString); //找得到
		alert(a1.toString == Object.prototype.toString); //true 构造函数生成的对象的toString方法在Object原型上

		var arr = [1, 2, 3];
		alert(typeof arr.toString()); //string

		alert(arr.toString()); //"1, 2, 3"

		//因为知道toString在哪儿了,所以可以手动修改toString方法
		Array.prototype.toString = function(){
			return this.join('+');
		}
		alert(arr.toString()); //"1+2+3"

		var num = 255;
		alert(num.toString(16)); //"ff" 将255转成16进制

		//利用toString进行类型判断(用constructor和instanceof也都可以进行类型判断)。推荐toString来判断例如数组的类型
		var arr = []
		var date = new Date;
		var json = {}
		var reg = new RegExp;
		var n = null;
		alert(Object.prototype.toString.call(arr)); //[object Array]
		alert(Object.prototype.toString.call(date)); //[object Date]
		alert(Object.prototype.toString.call(json)); //[object Object]
		alert(Object.prototype.toString.call(reg)); //[object RegExp]
		alert(Object.prototype.toString.call(n)); //[object Null]
		//判断类型时直接进行比较就可以了,例如判断arr是否是数组:
		alert(Object.prototype.toString.call(arr) == '[object Array]'); //true

		//举例说明用instanceof和constructor来判断数组失效,但是toString依然有效的例子
		window.onload = function(){
			var oF = document.createElement('iframe');
			document.body.appendChild(oF);
			var ifArray = window.frames[0].Array; //ifArray就是iframe里面的数组
			var arr = new ifArray(); //ifArray就是iframe里面的数组 这时候跨页面了

			alert(arr.constructor == Array); //false constructor判断iframe下面的数组失效
			alert(arr instanceof Array); //false 判断失效
			alert(Object.prototype.toString.call(arr) == '[object Array]'); //true 判断依然有效
		}
	</script>
</head>
<body>
</body>
</html>

###对象的继承

  • 什么是继承
    • 在原有对象的基础上,略作修改,得到一个新的对象
    • 不影响原有对象的功能
  • 如何添加继承
    • 属性:call
    • 方法:for in
    • 例子:继承的拖拽
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>继承</title>
	<style></style>
	<script>
		// 继承:子类不影响父类;子类可以继承父类的一些功能(实现代码复用)

		function CreatePerson(name, sex){ //父类
			this.name = name;
			this.sex = sex;
		}
		CreatePerson.prototype.showName = function(){
			alert(this.name);
		}

		var p1 = new CreatePerson('小明', '男');
		p1.showName();

		//属性的继承:调用父类的构造函数 用call改this指向
		//方法的继承:用for in循环

		function CreateStar(name, sex, job){ //子类
			// this.name = name;
			// this.sex = sex;

			CreatePerson.call(this, name, sex); //调用父类构造函数 需要修改指向
			this.job = job;

		}

		/*
		CreateStar.prototype = CreatePerson.prototype; //将父类的原型赋给子类,那么子类就获得了父类下所有的属性值,实现了方法的继承 但是这里有对象的引用问题,造成互相干涉
		例如:
		CreateStar.prototype.showJob = function(){}
		上面子类的原型添加的方法,那么父类CreatePerson.prototype下面也有了showJob的方法
		*/

		//方法的继承应该用for in循环,将父类的所有属性拷贝给子类,这叫作“拷贝继承”。jQuery也是采用拷贝继承extend

		function extend(obj1, obj2){
			for(var attr in obj2){
				obj1[attr] = obj2[attr];
			}
		}

		extend(CreateStar.prototype, CreatePerson.prototype )
		var p2 = new CreateStar('黄晓明', '男', '演员')
		p2.showName();

	</script>
</head>
<body>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>继承</title>
	<style></style>
	<script>
		var a = {
			name: '小明'
		};

		//var b = a;
		
		// var b = {};
		// for(var attr in a){
		// 	b[attr] = a[attr];
		// }
		// b.name = "小强";
		// alert(a.name);

		//将对象属性的拷贝封装成一个函数
		function extend(obj1, obj2){
			for(var attr in obj2){
				obj1[attr] = obj2[attr];
			}
		}

		var b = {};
		extend(b, a); 
		b.name = '小强';
		alert(b.name);
		alert(a.name);
	</script>
</head>
<body>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>继承</title>
	<style></style>
	<script>
		var a = [1, 2, 3];
		var b = a;
		// b.push(4); //修改 a、b之间引用链条存在
		// alert(a); //1, 2, 3, 4 a被b影响了

		b = [1, 2, 3, 4]; //重新赋值,内存中这个b又重新生成了。a和b之间这个引用的链条已经断开,a、b没有关系
		alert(a); //1, 2, 3 a未被影响

		//在for in循环中,子类原型的方法也直接等于了父类原型的方法,因为方法是函数,也是个对象,为什么这种“对象 = 对象”没有互相干涉呢?这是因为函数有些特殊,不能被修改,只能被重新赋值
	</script>
</head>
<body>
</body>
</html>

继承的拖拽

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title></title>
	<style>
		#div1 {width: 100px; height: 100px; background: red; position: absolute;}
		#div2 {width: 100px; height: 100px; background: yellow; position: absolute; left: 100px;}
	</style>
	<script src="jquery-1.11.1.js"></script>
	<script>
		window.onload = function(){
			var d1 = new Drag('div1');
			d1.init();

			var d2 = new ChildDrag('div2');
			d2.init();
		}

		//父类的面向对象的拖拽开始
		function Drag(id){ //父类
			this.obj = document.getElementById(id);
			this.disX = 0;
			this.disY = 0;
		}
		
		Drag.prototype.init = function(){
			var This = this;
			this.obj.onmousedown = function(ev){
				var ev = ev || window.event;
				This.fnDown(ev);

				document.onmousemove = function(ev){
					var ev = ev || window.event;
					This.fnMove(ev);
				}
				document.onmouseup = function(){
					This.fnUp();
				}
				return false;
			}
		}

		Drag.prototype.fnDown = function(ev){
			this.disX = ev.clientX - this.obj.offsetLeft;
			this.disY = ev.clientY - this.obj.offsetTop;
		}
		Drag.prototype.fnMove = function(ev){
			this.obj.style.left = ev.clientX - this.disX + 'px';
			this.obj.style.top = ev.clientY - this.disY + 'px';
		}
		Drag.prototype.fnUp = function(){
			document.onmousemove = null;
			document.onmouseup = null;
		}
		//父类的面向对象的拖拽结束
		
		//子类继承父类开始
		function ChildDrag(id){ //子类
			Drag.call(this, id);
		}

		extend(ChildDrag.prototype, Drag.prototype);

		//通过改变ChildDrap原型下的fnMove,给子类添加了限制拖拽时,元素超出可视区左右边界的功能
		ChildDrag.prototype.fnMove = function(ev){
			var L = ev.clientX - this.disX;
			var T = ev.clientY - this.disY;

			if(L < 0){
				L = 0;
			} else if (L > document.documentElement.clientWidth - this.obj.offsetWidth){
				L = document.documentElement.clientWidth - this.obj.offsetWidth;
			}

			this.obj.style.left = L + 'px';
			this.obj.style.top = T + 'px';
		}

		function extend(obj1, obj2){
			for(var attr in obj2){
				obj1[attr] = obj2[attr];
			}
		}
		//子类继承父类结束

	</script>
</head>
<body>
	<div id="div1"></div>
	<div id="div2"></div>
</body>
</html>
  • 继承的其他形式
    • 类式继承
      • 利用构造函数(类)继承的方式
    • 原型继承
      • 借助原型来实现对象继承对象

类式继承

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title></title>
	<style>
	</style>
	<script>
		//类:JS是没有类的概念的,可以把JS中的构造函数看做是类
		//要做属性和方法继承的时候,要分开继承
		
		function Aaa(){ //父类
			this.name = '小明';
			this.arr = [1, 2, 3];
		}
		Aaa.prototype.showName = function(){
			alert(this.name);
		}

		function Bbb(){ //子类		
		}

		Bbb.prototype = new Aaa(); //这句话就是类式继承
		//只写上面一句话是有问题的,修改了constructor指向。
		//Bbb.prototype.constructor = Bbb; //修正指向问题

		var b1 = new Bbb();
		b1.showName();
		alert(b1.name);
		alert(b1.constructor); //弹出的并不是Bbb,而是Aaa。只有写了上面修正指向的那句话,这里才会变成Bbb

		//真正规范的类式继承要用下面几句话:
		// function Bbb(){ //这里只继承了属性
		// 	Aaa.call(this)
		// }
		//var F = function(){}; //创建一个空的构造函数
		//F.prototype = Aaa.prototype;
		//Bbb.prototype = new F(); //这里只继承了方法
		//Bbb.prototype.constructor = Bbb;

		b1.arr.push(4);

		var b2 = new Bbb();
		alert(b2.arr); //[1, 2, 3, 4] 这里看到上面的b1.arr.push(4)影响到了这里 要避免这种问题,应该用上面的类式继承的规范写法才行
	</script>
</head>
<body>
</body>
</html>

原型继承

<head>
	<meta charset="UTF-8">
	<title>原型继承</title>
	<style>
	</style>
	<script>
		var a = {
			name: '小明'
		}

		var b = cloneObj(a); //让b继承a
		b.name = '小强'; //这一句不会更改a的name,因为这一句在b的下面又新建了一个属性name,值为“小强”
		alert(b.name); //小强
		alert(a.name); //小明

		function cloneObj(obj){
			var F = function(){};
			F.prototype = obj;
			return new F();
		}
	</script>
</head>
<body>
</body>
</html>
  • 拷贝继承:通用型的 有new或无new的时候都可以
  • 类式继承:适合通过new构造函数形式
  • 原型继承:比较适合无new的对象

###组件开发

  • 对象的多种表现形式
    • 提高对象的复用性
    • 如何配置参数和默认参数
    • 例子:拖拽
    • 例子:弹窗
  • 什么是组件?
    • 对面向对象的深入应用(UI组件、功能组件)
    • 将配置参数、方法、事件、三者进行分离
  • 创建自定义事件
    • 有利于多人协作开发代码
    • 如何去挂载自定义事件与事件函数
  • 例子:基于JQ的选项卡的组件开发模式
    • trigger() extend()等方法的使用
  • 本课练习
    • 组件开发的练习
    • http://tangram.baidu.com/magic/

如何配置参数和默认参数

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>组件开发中的参数问题</title>
	<script>
	//用json来解决参数数量问题
		function show(opt){

		}

		show({
			id: 'div1',
			toDown: function(){},
			toUp: function(){}
		});

	//用下面的方法解决参数顺序问题
		var a = { //配置参数
			//name: '小明'
			name: '小明'
		}
		var b = { //默认参数
			name: '小强'
		}

		extend(b, a); //当有配置的时候,走配置,没有配置的时候,走默认
		alert(b.name);

		function extend(obj1, obj2){
			for(var attr in obj2){
				obj1[attr] = obj2[attr];
			}
		}
	</script>
</head>
<body>
	
</body>
</html>

拖拽的组件开发

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title></title>
	<style>
		#div1 {width: 100px; height: 100px; background: red; position: absolute;}
		#div2 {width: 100px; height: 100px; background: yellow; position: absolute; left: 100px;}
		#div3 {width: 100px; height: 100px; background: blue; position: absolute; left: 200px;}
		#div4 {width: 100px; height: 100px; background: green; position: absolute; left: 300px;}
	</style>
	<script src="jquery-1.11.1.js"></script>
	<script>
	/*
		组件开发:像兄弟之间的关系(代码复用的一种形式)
	*/
		window.onload = function(){
			var d1 = new Drag();
			d1.init('div1');

			var d2 = new Drag();
			d2.init('div2', { //配置参数
				toDown: function(){
					document.title = 'hello';
				}
			});

			var d3 = new Drag();
			d3.init('div3', { //配置参数
				toDown: function(){
					document.title = '妙味';
				},
				toUp: function(){
					document.title = '课堂';
				}
			});

			var d4 = new Drag();
			d4.init('div4', { //配置参数
				toUp: function(){
					document.title = 'byebye';
				}
			});
		}

		function Drag(){
			this.obj = null;
			this.disX = 0;
			this.disY = 0;

			this.settings = { //默认参数
				//id不应该属于配置参数当中,它属于必填项
				toDown: function(){},
				toUp: function(){}
			}
		}
		
		Drag.prototype.init = function(id, opt){
			var This = this;
			this.obj = document.getElementById(id);

			extend(this.settings, opt); //用配置覆盖默认

			this.obj.onmousedown = function(ev){
				var ev = ev || window.event;
				This.fnDown(ev);

				This.settings.toDown();

				document.onmousemove = function(ev){
					var ev = ev || window.event;
					This.fnMove(ev);
				}
				document.onmouseup = function(){
					This.fnUp();
					This.settings.toUp();
				}
				return false;
			}
		}

		Drag.prototype.fnDown = function(ev){
			this.disX = ev.clientX - this.obj.offsetLeft;
			this.disY = ev.clientY - this.obj.offsetTop;
		}
		Drag.prototype.fnMove = function(ev){
			this.obj.style.left = ev.clientX - this.disX + 'px';
			this.obj.style.top = ev.clientY - this.disY + 'px';
		}
		Drag.prototype.fnUp = function(){
			document.onmousemove = null;
			document.onmouseup = null;
		}

		function extend(obj1, obj2){
			for(var attr in obj2){
				obj1[attr] = obj2[attr];
			}
		}

	</script>
</head>
<body>
	<div id="div1"></div> <!-- 红色方块是老大 -->
	<div id="div2"></div> <!-- 黄色方块是老二 黄色的按下之后title有一个变化 -->
	<div id="div3"></div> <!-- 老三 按下title变化,抬起title变化 -->
	<div id="div4"></div> <!-- 老四 鼠标抬起时title有变化 -->
</body>
</html>

弹窗的组件开发

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>弹窗的组件开发</title>
	<style>
		* {margin : 0; padding; 0}
		.login {background: white; border: 1px #000 solid; position: absolute; left: 0; top: 0; z-index: 2;}
		.title {height: 30px; background: gray; color: white;}
		.title .close {float: right;}
		#mask {width: 500px; height: 500px; background: black; filter: alpha(oapcity=50); opacity: 0.5; position: absolute; left: 0; top: 0; z-index: 1;}
	</style>
	<script>
		window.onload = function(){
			var aInput = document.getElementsByTagName('input');

			aInput[0].onclick = function(){
				var d1 = new Dialog();
				d1.init({//配置参数
					iNow: 0,
					title: '登陆'
				});
			}

			aInput[1].onclick = function(){
				var d2 = new Dialog();
				d2.init({//配置参数
					iNow: 1,
					w : 100,
					h : 400,
					dir: 'right',
					title: '公告'
				});
			}

			aInput[2].onclick = function(){
				var d3 = new Dialog();
				d3.init({//配置参数
					iNow: 2,
					mask: true
				});
			}
		}

		function Dialog(){
			this.oLogin = null;
			this.settings = { //默认参数
				w: 300,
				h: 300,
				dir: 'center',
				title: '',
				mask: false
			}
		}

		Dialog.prototype.json = {}; //防止添加多个弹窗

		Dialog.prototype.init = function(opt){

			extend(this.settings, opt);

			if(this.json[opt.iNow] == undefined){
				this.json[opt.iNow] = true;
			}

			if(this.json[opt.iNow]){ //防止添加多个弹窗
				this.create();
				this.fnClose();

				if(this.settings.mask){
					this.createMask();
				}

				this.json[opt.iNow] = false;
			}
			

		}

		Dialog.prototype.create = function(){

			this.oLogin = document.createElement('div');
			this.oLogin.className = 'login';
			this.oLogin.innerHTML = '<div class="title"><span>' + this.settings.title + '</span><span class="close">X</span></div><div class="content"></div>';
			document.body.appendChild(this.oLogin);
			this.setData();

		}

		Dialog.prototype.setData = function(){
			this.oLogin.style.width = this.settings.w + 'px';
			this.oLogin.style.height = this.settings.h + 'px';

			if(this.settings.dir == 'center'){
				this.oLogin.style.left = (viewWidth() - this.oLogin.offsetWidth)/2 + 'px';
				this.oLogin.style.top = (viewHeight() - this.oLogin.offsetHeight)/2 + 'px';
			} else if (this.settings.dir == 'right'){
				this.oLogin.style.left = (viewWidth() - this.oLogin.offsetWidth) + 'px';
				this.oLogin.style.top = (viewHeight() - this.oLogin.offsetHeight) + 'px';
			}

		}

		Dialog.prototype.fnClose = function(){
			var oClose = this.oLogin.getElementsByTagName('span')[1];
			var This = this;
			oClose.onclick = function(){
				document.body.removeChild(This.oLogin);
				if(This.settings.mask){
					document.body.removeChild(This.oMask);
				}

				This.json[This.settings.iNow] = true;
			}
		}

		Dialog.prototype.createMask = function(){
			var oMask = document.createElement('div');
			oMask.id = 'mask';
			document.body.appendChild(oMask);
			this.oMask = oMask;

			oMask.style.width = viewWidth() + 'px';
			oMask.style.height = viewHeight() + 'px';
		}

		function extend(obj1, obj2){
			for(var attr in obj2){
				obj1[attr] = obj2[attr];
			}
		}

		function viewWidth(){
			return document.documentElement.clientWidth;
		}

		function viewHeight(){
			return document.documentElement.clientHeight;
		}
	</script>
</head>
<body>
	<input type="button" value="1">
	<input type="button" value="2">
	<input type="button" value="3">

	<!-- <div class="login">
		<div class="title">
			<span>标题</span><span class="close">X</span>
		</div>
		<div class="content"></div>
	</div> -->
	<!-- <div id="mask"></div> -->
</body>
</html>

理解自定义事件

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>自定义事件</title>
	<script>
		// 自定义事件:主要是跟函数有关系,让函数能够具备事件的某些特性

		// 三个人都针对show来添加功能。都添加到一个地方会乱,于是三个人分开来写
		/*
		function show(){
			alert(1); //第一个人写
		}

		function show(){
			alert(2); //第二个人写
		}

		function show(){
			alert(3); //第三个人写
		}

		show(); //只能执行第三个人写的,因为函数不能修改只能覆盖
		*/
		
		//看看是否能让函数具备事件的特性,多人写的可以绑定上去,就解决了多人协作的问题。原理就如以下情形。当然以下代码不是添加自定义事件的代码,只是为了方便理解:
		/*
		window.addEventListener('show', function(){
			alert(1); //第一个人写的
		}, false);
		window.addEventListener('show', function(){
			alert(2); //第二个人写的
		}, false);
		window.addEventListener('show', function(){
			alert(3); //第三个人写的
		}, false);

		show(); //主动触发自定义事件
		*/
	</script>
</head>
<body>
	
</body>
</html>

用原生js实现自定义事件

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>自定义事件</title>
	<script>
	//自定义事件重点在于将函数和元素以及事件名称建立关系,在执行某个函数的时候,主动触发某个事件名称下面相关的所有函数
		window.onload = function(){
			var oDiv = document.getElementById('div1');
			var oSpan = document.getElementById('span1');
			
			bindEvent(oDiv, 'click', function(){
				alert(1);
			})

			bindEvent(oDiv, 'click', function(){
				alert(2);
			})

			bindEvent(oSpan, 'show', function(){ //这里的show就是个自定义事件
				alert(3);
			})

			bindEvent(oSpan, 'show', function(){ //这里的show就是个自定义事件
				alert(4);
			})

			bindEvent(oSpan, 'hide', function(){ //这里的show就是个自定义事件
				alert(5);
			})

			fireEvent(oSpan, 'show'); //主动触发,弹出3, 4
			fireEvent(oDiv, 'click'); //主动触发,弹出1, 2
		}



		function bindEvent(obj, events, fn){

			//fn: 看作一本书 《西游记》
			//obj: 相当于图书馆的楼层 文学楼
			//events: 相当于书架 古典文学书架

			obj.listeners = obj.listeners || {}; //先找到楼层,没有楼层就创建楼层
			obj.listeners[events] = obj.listeners[events] || []; //再找到书架,没有书架就创建书架
			obj.listeners[events].push(fn); //把fn这本书放到书架上
			/* 通过以上的方式,将obj,events和fn建立了关系*/

			if(obj.addEventListener){
				obj.addEventListener(events, fn, false);
			} else {
				obj.attachEvent('on' + events, fn);
			}	
		}

		function fireEvent(obj, events){ //主动触发自定义事件
			for(var i=0; i<obj.listeners[events].length; i++){
				obj.listeners[events][i]();
			}
		}

	</script>
</head>
<body>
	<div id="div1">div</div>
	<span id="span1">span</span>
</body>
</html>

配置、方法、自定义事件分离——正规组件的写法——基于拖拽的组件进行的修改

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>自定义事件的拖拽组件</title>
	<style>
		#div1 {width: 100px; height: 100px; background: red; position: absolute;}
		#div2 {width: 100px; height: 100px; background: yellow; position: absolute; left: 100px;}
		#div3 {width: 100px; height: 100px; background: blue; position: absolute; left: 200px;}
		#div4 {width: 100px; height: 100px; background: green; position: absolute; left: 300px;}
	</style>
	<script src="jquery-1.11.1.js"></script>
	<script>
		window.onload = function(){
			var d1 = new Drag();
			d1.init('div1');

			var d2 = new Drag();
			d2.init('div2');

			bindEvent(d2, 'toDown', function(){
				document.title = 'hello';
			});
			
			bindEvent(d2, 'toDown', function(){ //新来的一个同事扩展d2的toDown事件的时候,更容易
				document.body.style.background = 'black';
			})

			var d3 = new Drag();
			d3.init('div3');

			bindEvent(d3, 'toDown', function(){
				document.title = '妙味';
			})
			bindEvent(d3, 'toUp', function(){
				document.title = '课堂';
			})

			var d4 = new Drag();
			d4.init('div4');

			bindEvent(d4, 'toUp', function(){
				document.title = 'byebye';
			})
		}

		function Drag(){
			this.obj = null;
			this.disX = 0;
			this.disY = 0;

			this.settings = { 
				
			}
		}
		
		Drag.prototype.init = function(id, opt){
			var This = this;
			this.obj = document.getElementById(id);

			extend(this.settings, opt); 

			this.obj.onmousedown = function(ev){
				var ev = ev || window.event;
				This.fnDown(ev);
				fireEvent(This, 'toDown');

				document.onmousemove = function(ev){
					var ev = ev || window.event;
					This.fnMove(ev);
				}
				document.onmouseup = function(){
					This.fnUp();
					fireEvent(This, 'toUp');
				}
				return false;
			}
		}

		Drag.prototype.fnDown = function(ev){
			this.disX = ev.clientX - this.obj.offsetLeft;
			this.disY = ev.clientY - this.obj.offsetTop;
		}
		Drag.prototype.fnMove = function(ev){
			this.obj.style.left = ev.clientX - this.disX + 'px';
			this.obj.style.top = ev.clientY - this.disY + 'px';
		}
		Drag.prototype.fnUp = function(){
			document.onmousemove = null;
			document.onmouseup = null;
		}

		function extend(obj1, obj2){
			for(var attr in obj2){
				obj1[attr] = obj2[attr];
			}
		}

		function bindEvent(obj, events, fn){

			obj.listeners = obj.listeners || {}; 
			obj.listeners[events] = obj.listeners[events] || []; 
			obj.listeners[events].push(fn); 

			if(obj.nodeType){ //如果传进来的是DOM元素的话,走着下面;如果传进来的不是DOM,是对象的话,就不走下面,只走上面了
				if(obj.addEventListener){
					obj.addEventListener(events, fn, false);
				} else {
					obj.attachEvent('on' + events, fn);
				}	
			}
		}

		function fireEvent(obj, events){ 
			if(obj.listeners && obj.listeners[events]){
				for(var i=0; i<obj.listeners[events].length; i++){
					obj.listeners[events][i]();
				}
			}
		}

	</script>
</head>
<body>
	<div id="div1"></div> 
	<div id="div2"></div> 
	<div id="div3"></div> 
	<div id="div4"></div> 
</body>
</html>

**用JQ实现选项卡的组件开发

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>基于JQ的选项卡组件开发</title>
	<style>
		html, body {margin: 0; padding: 0;}
		#div1 div, #div2 div, #div3 div, #div4 div {width: 200px; height: 200px; border: 1px solid #000; display: none;}
		#div1 .active, #div2 .active, #div3 .active, #div4 .active {background: red;}
	</style>
	<script src="jquery-1.11.1.js"></script>
	<script>
	/*
		title: 基于JQ的选项卡组件
		Options: 
			- event 
			- delay
		Methods: 
			- nowSel() - 设置开始的tab数
			- getContent() - 获取当前内容
		Events:
			- beforeClick 点击前触发
			- afterClick 点击后触发
	*/

	// JQ中的主动触发:trigger()

		$(function(){

			var t1 = new Tab();
			t1.init('div1', {});

			var t2 = new Tab();
			t2.init('div2', {
				event: 'mouseover'
			});

			var t3 = new Tab();
			t3.init('div3', {
				event: 'mouseover',
				delay: 200
			});

			var t4 = new Tab();
			t4.init('div4', {});
			t4.nowSel(2);

			$('#input1').click(function(){
				alert(t4.getContent());
			})

			$(t4).on('beforeClick', function(){
				alert(t4.getContent());
			})

			$(t4).on('afterClick', function(){
				alert(t4.getContent());
			})

		})

		function Tab(){
			this.oParent = null;
			this.aInput = null;
			this.aDiv = null;
			this.iNow = 0;

			this.settings = { //默认参数
				event: 'click',
				delay: 0
			}
		}

		Tab.prototype.init = function(oParent, opt){
			$.extend(this.settings, opt);
			this.oParent = $('#' + oParent);
			this.aInput = this.oParent.find('input');
			this.aDiv = this.oParent.find('div');

			this.change();
		}

		Tab.prototype.change = function(){
			var This = this;
			var timer = null;

			this.aInput.on(this.settings.event, function(){

				if(This.settings.event == 'mouseover' && This.settings.delay){
					var _this = this;

					timer = setTimeout(function(){
						show(_this);
					}, This.settings.delay)
				} else {
					show(this);
				}
				
			}).mouseout(function(){
				clearTimeout(timer);
			});

			function show(obj){

				$(This).trigger('beforeClick');

				This.aInput.attr('class', '');
				This.aDiv.css('display', 'none');
				$(obj).attr('class', 'active');
				This.aDiv.eq($(obj).index()).css('display', 'block');

				This.iNow = $(obj).index();

				$(This).trigger('afterClick');
			}

			Tab.prototype.nowSel = function(index){
				this.aInput.attr('class', '');
				this.aDiv.css('display', 'none');
				this.aInput.eq(index).attr('class', 'active');
				this.aDiv.eq(index).css('display', 'block');

				this.iNow = index;
			}

			Tab.prototype.getContent = function(){
				return this.aDiv.eq(this.iNow).html();
			}
		}
	</script>
</head>
<body>
	<div id="div1">
		<input type="button" value="1" class="active">
		<input type="button" value="2">
		<input type="button" value="3">
		<div style="display: block">111111</div>
		<div>222222</div>
		<div>333333</div>
	</div>
	<div id="div2">
		<input type="button" value="1" class="active">
		<input type="button" value="2">
		<input type="button" value="3">
		<div style="display: block">111111</div>
		<div>222222</div>
		<div>333333</div>
	</div>
	<div id="div3">
		<input type="button" value="1" class="active">
		<input type="button" value="2">
		<input type="button" value="3">
		<div style="display: block">111111</div>
		<div>222222</div>
		<div>333333</div>
	</div>
	<div id="div4">
		<input type="button" value="1" class="active">
		<input type="button" value="2">
		<input type="button" value="3">
		<div style="display: block">111111</div>
		<div>222222</div>
		<div>333333</div>
	</div>
	<input type="button" value="点击" id="input1">
</body>
</html>

##妙味综合案例

###图片放大镜效果

子集影响父级的bug

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title></title>
	<style>
		#div1 {width: 200px; height: 200px; background: red;}
		#div2 {width: 100px; height: 100px; background: yellow;}
	</style>
	<script>
		window.onload = function(){

			var oDiv = document.getElementById('div1');

			oDiv.onmouseover = function(){
				document.title += '1';
			}
			oDiv.onmouseout = function(){
				document.title += '2';
			}

			// 会发现鼠标移到黄色div上的时候,title部分会一下子出现21,这个2是从div1移到div2触发的,而1是通过div2冒泡到div1上触发的

		}
	</script>
</head>
<body>
	<div id="div1">
		<div id="div2"></div>
	</div>
</body>
</html>

上述问题的解决方式(1)

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title></title>
	<style>
		#div1 {width: 200px; height: 200px; background: red;}
		#div2 {width: 100px; height: 100px; background: yellow;}
	</style>
	<script>
		// 解决方案:	
		// 1. js: 用onmouseener和onmouseleave事件替代onmouseover和onmouseout。onmouseenter和onmouseleave事件,子级不会影响到父级
		// 2. css

		window.onload = function(){

			var oDiv = document.getElementById('div1');

			oDiv.onmouseenter = function(){
				document.title += '1';
			}
			oDiv.onmouseleave = function(){
				document.title += '2';
			}

		}
	</script>
</head>
<body>
	<div id="div1">
		<div id="div2"></div>
	</div>
</body>
</html>

上述问题的解决方式(2)

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title></title>
	<style>
		#div1 {width: 200px; height: 200px; background: red;}
		#div2 {width: 100px; height: 100px; background: yellow;}
	</style>
	<script>
		// 解决方案:	
		// 1. js: 用onmouseener和onmouseleave事件替代onmouseover和onmouseout。onmouseenter和onmouseleave事件,子级不会影响到父级
		// 上述两个事件有兼容性问题,但是兼容性问题不大,而且做兼容非常的麻烦 兼容方式如下:
		// 2. css

		window.onload = function(){

			var oDiv = document.getElementById('div1');

			oDiv.onmouseover = function(ev){
				var ev = ev || window.event;
				var a = this, b = ev.relatedTarget; //ev.relatedTarget是相对目标,可以理解为之前的目标

				/*
				1. 从body移到div1:a - div1;b - body
				2. 从div1移到div2:a - div1;b - div1
				3. 从div2移到div1:a - div1;b - div2
				 */

				if(!elContains(a, b) && a!=b){
					document.title += '1';
				}
			}
			oDiv.onmouseout = function(ev){
				var ev = ev || window.event;
				var a = this, b = ev.relatedTarget;

				if(!elContains(a, b) && a!=b){
					document.title += '2';
				}
			}

		}

		function elContains(a, b){ //判断两个元素是否是嵌套关系,看a是否包含b
			return a.contains ? a != b && a.contains(b) : !!(a.compareDocumentPosition(b) & 16);
		}
	</script>
</head>
<body>
	<div id="div1">
		<div id="div2"></div>
	</div>
</body>
</html>

图片的放大镜效果

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>图片放大镜效果</title>
	<style>
		html, body {margin: 0; padding: 0;}
		#div1 {width: 180px; height: 180px; overflow: hidden; position: relative;}
		#div1 span {width: 100px; height: 100px; background: yellow; opacity: 0.5; filter: alpha(opacity=50); position: absolute; left: 0; top: 0; display: none;}
		#mask {width: 180px; height: 180px; background: red; position: absolute; left: 0; top: 0; opacity: 0; filter: alpha(oapcity=0);}
		#div2 {width: 500px; height: 500px; position: absolute; left: 250px; top: 50px; overflow: hidden;}
		#div2 img {position: absolute; left: 0; top: 0;}
	</style>
	<script>
	//这里的onmouseover和onmouseout是用过CSS方式解决的问题,方法是添加了一个透明的层
	
		window.onload = function(){
			var oDiv = document.getElementById('div1');
			var oSpan = oDiv.getElementsByTagName('span')[0];
			var oDiv2 = document.getElementById('div2');
			var img2 = oDiv2.getElementsByTagName('img')[0];

			oDiv.onmouseenter = function(){ 
				oSpan.style.display = 'block';
			}
			oDiv.onmouseleave = function(){
				oSpan.style.display = 'none';
			}

			oDiv.onmousemove = function(ev){
				var ev = ev || window.event;
				var L = ev.clientX - oDiv.offsetLeft - oSpan.offsetWidth / 2;
				var T = ev.clientY - oDiv.offsetTop - oSpan.offsetHeight / 2;

				if(L < 0){
					L = 0;
				} else if (L > oDiv.offsetWidth - oSpan.offsetWidth){
					L = oDiv.offsetWidth - oSpan.offsetWidth;
				}

				if(T < 0){
					T = 0;
				} else if (T > oDiv.offsetHeight - oSpan.offsetHeight){
					T = oDiv.offsetHeight - oSpan.offsetHeight;
				}

				oSpan.style.left = L + 'px';
				oSpan.style.top = T + 'px';

				var scaleX = L / (oDiv.offsetWidth - oSpan.offsetWidth);
				var scaleY = T / (oDiv.offsetHeight - oSpan.offsetHeight);

				img2.style.left = -scaleX * (img2.offsetWidth - oDiv2.offsetWidth) + 'px';
				img2.style.top = -scaleY * (img2.offsetHeight - oDiv2.offsetHeight) + 'px';
			}
		}
	</script>
</head>
<body>
	<div id="div1">
		<img src="b2.jpg">
		<span></span>
		<div id="mask"></div>
	</div>
	<div id="div2">
		<img src="b1.jpg">
	</div>
</body>
</html>

###苹果菜单

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>苹果菜单</title>
	<script>
		// Math.pow(3, 2); //3的平方 
		// Math.pow(2, 3); //2的立方
		// Math.sqrt(9); //9的开方
		// Math.pow(9, 1/3); //9的开立方
	</script>
	<style>
		html, body {margin: 0; padding: 0;}
		#div1 img {width: 64px;}
		#div1 {width: 100%; height: auto; position: absolute; bottom: 0; text-align: center;}
		input {width: 300px;}
	</style>
	<script>
		window.onload = function(){
			var aInput = document.getElementsByTagName('input');
			var oDiv= document.getElementById('div1');
			var aImg = oDiv.getElementsByTagName('img');

			document.onmousemove = function(ev){
				var ev = ev || window.event;

				for(var i=0; i<aImg.length; i++){
					var x = aImg[i].offsetLeft + aImg[i].offsetWidth/2;
					var y = aImg[i].offsetTop + aImg[i].offsetHeight/2 + oDiv.offsetTop;

					var a = ev.clientX - x;
					var b = ev.clientY - y;

					var c = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));

					var scale = 1 - c/300;
					if(scale < 0.5){
						scale = 0.5;
					}


					aInput[i].value = 'x轴:' + x + ',Y轴:' + y + ',与鼠标距离:' + c;
					aImg[i].style.width = scale * 128 + 'px';
					aImg[i].style.height = scale * 128 + 'px';

				}
			}

				
		}
	</script>
</head>
<body>
	<input type="text">
	<input type="text">
	<input type="text">
	<input type="text">
	<input type="text">
	<div id="div1">
		<img src="images/1.png">
		<img src="images/2.png">
		<img src="images/3.png">
		<img src="images/4.png">
		<img src="images/5.png">
	</div>
</body>	
</html>

###妙味照片墙效果

  • 可拖拽换为
  • 可随机排序

找最小值和位置

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>找最小值和位置</title>
	<script>
		var arr = [34, 71, 8, 934, 10];

		var value = 9999;
		var index = -1;

		for(var i=0; i<arr.length; i++){
			if(arr[i]< value){
				value = arr[i];
				index = i;
			}
		}

		alert('最小值:' + value + ',位置:' + index);
	</script>
</head>
<body>
	
</body>
</html>

照片墙的实现

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>妙味照片墙效果</title>
	<style>
		html, body {margin: 0; padding: 0;}
		#ul1 {width: 660px; position: relative; margin: 10px auto;}
		li {width: 200px; height: 150px; margin: 10px; float: left; list-style: none;}
	</style>
	<script>
		window.onload = function(){
			//布局转换,将浮动布局转成定位布局
			var aLi = document.getElementsByTagName('li');
			var izIndex = 2;
			var arr = [];
			var oInput = document.getElementById('input1');

			for(var i=0; i<aLi.length; i++){
				arr.push([aLi[i].offsetLeft, aLi[i].offsetTop]);
			}

			for(var i=0; i<aLi.length; i++){
				aLi[i].style.position = 'absolute';
				aLi[i].style.left = arr[i][0] + 'px';
				aLi[i].style.top = arr[i][1] + 'px';
				aLi[i].style.margin = 0; //去掉影响定位的值。例如margin、padding都已经算到left和top里面了,所以不需要再算进去
			}

			for(var i=0; i<aLi.length; i++){
				aLi[i].index = i;
				drag(aLi[i]);
			}

			oInput.onclick = function(){
				var randomArr = [0, 1, 2, 3, 4, 5, 6, 7, 8];
				randomArr.sort(function(n1, n2){
					return Math.random() - 0.5;
				})
				for(var i=0; i<aLi.length; i++){
					startMove(aLi[i], {left: arr[randomArr[i]][0], top: arr[randomArr[i]][1]});
					aLi[i].index = randomArr[i];
				}
			}

			function drag(obj){
				var disX = 0;
				var disY = 0;

				obj.onmousedown = function(ev){
					var ev = ev || window.event;
					obj.style.zIndex = izIndex ++;
					disX = ev.clientX - obj.offsetLeft;
					disY = ev.clientY - obj.offsetTop;

					document.onmousemove = function(ev){
						var ev = ev || window.event;
						obj.style.left = ev.clientX - disX + 'px';
						obj.style.top = ev.clientY - disY + 'px';

						// for(var i=0; i<aLi.length; i++){
						// 	if(pz(obj, aLi[i]) && obj != aLi[i]){
						// 		aLi[i].style.border = '2px solid red';
						// 	} else {
						// 		aLi[i].style.border = '';
						// 	}
						// }

						var nL = nearLi(obj);
						for(var i=0; i<aLi.length; i++){
							aLi[i].style.border = '';
						}

						if(nL){
							nL.style.border = '2px solid red';
						}
						

					}

					document.onmouseup = function(){
						document.onmousemove = null;
						document.onmouseup = null;

						var nL = nearLi(obj);
						var tmp = 0;

						if(nL){
							startMove(nL, {left: arr[obj.index][0], top: arr[obj.index][1]});

							startMove(obj, {left: arr[nL.index][0], top: arr[nL.index][1]});
							nL.style.border = '';

							tmp = obj.index;
							obj.index = nL.index;
							nL.index = tmp;
						} else {
							startMove(obj, {left: arr[obj.index][0], top: arr[obj.index][1]});
						}
						
					}

					return false; 
				}
			}

			function nearLi(obj){
				var value = 9999;
				var index = -1;
				for(var i=0; i<aLi.length; i++){
					if(pz(obj, aLi[i]) && obj != aLi[i]){
						
						var c = jl(obj, aLi[i]);

						if(c<value){
							value = c;
							index = i;
						}
					}
				}

				if(index != -1){
					return aLi[index];
				} else {
					return false;
				}
			}

			function jl(obj1, obj2){
				var a = obj1.offsetLeft - obj2.offsetLeft;
				var b = obj1.offsetTop - obj2.offsetTop;

				return Math.sqrt(a*a + b*b);
			}

			function pz(obj1, obj2){
				var L1 = obj1.offsetLeft;
				var R1 = obj1.offsetLeft + obj1.offsetWidth;
				var T1 = obj1.offsetTop;
				var B1 = obj1.offsetTop + obj1.offsetHeight;

				var L2 = obj2.offsetLeft;
				var R2 = obj2.offsetLeft + obj2.offsetWidth;
				var T2 = obj2.offsetTop;
				var B2 = obj2.offsetTop + obj2.offsetHeight;

				if(R1 < L2 || L1 > R2 || B1 < T2 || T1 > B2){ //碰不到的情况
					return false;
				} else { //其余都是碰到的情况
					return true;
				}
			}

			//以下是运动框架move.js的代码
			function css(obj, attr){
			    if(obj.currentStyle){
			        return obj.currentStyle[attr];
			    } else {
			        return getComputedStyle(obj, false)[attr];
			    }
			}

			function startMove(obj, json, fn){
			    clearInterval(obj.iTimer);
			    var iCur = 0;
			    var iSpeed = 0; //速度初始化
			    
			    obj.iTimer = setInterval(function(){
			    
			        var iBtn = true;

			        for(var attr in json){
			            
			            var iTarget = json[attr];
			            
			            if(attr == 'opacity'){
			                iCur = Math.round(css(obj, 'opacity') * 100);
			            } else {
			                iCur = parseInt(css(obj, attr));
			            }
			            
			            iSpeed = (iTarget - iCur) / 8;
			            iSpeed = iSpeed > 0 ? Math.ceil(iSpeed) : Math.floor(iSpeed);
			            
			            if(iCur != iTarget){
			                iBtn = false;
			                if(attr == 'opacity'){
			                    obj.style.opacity = (iCur + iSpeed) / 100;
			                    obj.style.filter = 'alpha(opacity=' + (iCur + iSpeed) + ')';
			                } else {
			                    obj.style[attr] = iCur + iSpeed + 'px';
			                }
			            }
			        }

			        if(iBtn){
			            clearInterval(obj.iTimer);
			            fn && fn.call(obj);
			        }
			    
			    }, 30);
			}			

		}
	</script>
</head>
<body>
	<input type="button" value="随机" id="input1">
	<ul id="ul1">
		<li><img src="photo/1.jpg"></li>
		<li><img src="photo/2.jpg"></li>
		<li><img src="photo/3.jpg"></li>
		<li><img src="photo/4.jpg"></li>
		<li><img src="photo/5.jpg"></li>
		<li><img src="photo/1.jpg"></li>
		<li><img src="photo/2.jpg"></li>
		<li><img src="photo/3.jpg"></li>
		<li><img src="photo/4.jpg"></li>
	</ul>
</body>
</html>