1.包装对象
var str="hello";
str.charAt(0);
str.indexOf("e");
这里str是字符串类型,但是调用方法要是对象才行;这里js是怎么办到的?
就是包装对象实现的;
js中基本类型都有自己对应的包装对象;例如字符串的包装对象是String
执行str.charAt(0);的时候,基本类型会找到对应的包装对象,把包装对象的属性和方法给基本类型,然后包装对象消失;
var str="hello";
str.number=10;
alert(str.number);
得到undefined;因为在执行第二句的时候,会创建一个字符串对象,并且给它创建一个number属性为10,然后字符串对象消失;此时这个属性自然也消失了;
执行第3句的时候,又创建了一个字符串对象,此时他就不存在number属性了。
var str="hello";
String.prototype.lastChar=function(){
return this.charAt(this.length-1);
}
alert(str.lastChar());
这里得到o
lastChar是添加到String原型中的方法,所以即使不论创建多少个对象,包装对象消失这个方法也不会消失;
=============================================
2.原型链:实例对象和原型之间的连接就是原型链
function a(){
}
a.prototype.num=10;
var a1=new a();
alert(a1.num); //10
之前我们用原型定义方法,现在思考一下,用原型定义方法或者属性的时候,这个属性是原型prototype这个对象的方法和属性,可是为什么我们用a1对象也可以调用到这个num属性呢。
这是因为a1对象和prototype对象有一个连接,就是原型链;
这条原型链可以在fireBug中看到,就是__proto__
当访问a1.num,他其实是没有num属性的,所以他会继续查找原型链A的__proto__对象中有没有num,结果有,是10;
所以这就是为什么对象的属性比对象原型定义的属性的优先级高的原因;他找属性和方法是先找对象本身的方法和属性,找不到才在原型链里面找的。
原型链的最外层:Object.prototype
所有构造函数都和Object构造函数之间存在原型链:XX对象------XX构造函数的prototype------Object构造函数的prototype
function A(){
}
A.prototype.num=10;
Object.prototype.size=20;
var a1=new a();
alert(a1.num); //10
alert(a1.size); //20
当访问a1.size时,在a1和A的__proto__原型链都找不到size属性,此时他还会继续往上一层的原型链,Object的__proto__中找,结果就找到了;
所以如果你给不同的类添加共有的方法或属性的时候,就可以用Object的原型定义
=============================================
3.面向对象的一些属性和方法
hasOwnProperty():看属性或方法是不是对象自身下面的属性和方法
var arr=[];
arr.num=10;
Array.prototype.num2=20;
alert(arr.hasOwnProperty('num')); //true
alert(arr.hasOwnProperty('num2')); //false
不过用的很少
constructor :查看对象的构造函数
function A(){
};
var a1 = new A();
alert(a1.constructor); //得到 function A(){}
所以可以利用这个东西做判断
var arr=[];
alert(arr.constructor == Array); //true
constructor属性是你在创建构造函数的时候,程序会自动定义
"构造函数.prototype.constructor = xxxxx"
这个原型属性
所以说constructor这个属性是构造函数下的原型的属性,而不是对象本身的属性
而hasOwnProperty方法是Object下的原型的方法!
alert(a1.hasOwnProperty == Object.prototype.hasOwnProperty); //true
有时候,我们可能会不经意的改掉了"构造函数.prototype.constructor = xxxxx"
比如:
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype={
"showName":function(){
alert(this.name);
},
"showAge":function(){
alert(this.age);
}
}
var xiaoming=new person("小明",20);
alert(xiaoming.constructor); //得到的不是Person函数,而是Object函数
我们知道prototype也是一个对象,所以
Person.prototype.showName=function(){ ... }
Person.prototype.showAge=function(){ ... }
也可以写成上面那种形式;
但是,后者是往prototype中添加元素,而前者是把prototype重新定义;
所以前者的prototype中的constructor属性就相当于被干掉了!此时alert(xiaoming.constructor);会先在xiaoming中找这个属性,找不到再往Person.prototype中找,还找不到会往最外层的原型链Object.prototype中找,这才找到这个属性,故得到Object构造函数;
所以我们可以这样:
Person.prototype={
"constructor":Person,
"showName":function(){
alert(this.name);
},
"showAge":function(){
alert(this.age);
}
}
我们
for(var attr in Person.prototype){
alert(attr);
}
发现只能得到showName和ShowAge的函数内容,但是得不到constructor的,所以for in是遍历不到一些对象的属性的
instanceof 运算符
判断对象和某构造函数是否在同一原型链上
function A(){
}
var a1 = new A();
alert(a1 instanceof A); //true
alert(a1 instanceof Object); //true
toString()方法,他是Object构造函数的原型的方法
所以自己定义的类也都有toString方法。
不过系统对象都有自己的toString方法,如Array的toString就!=Object的toString
但是自己定义的类的toString==Object的toString
如:
alert(Array.prototype.toStirng == Object.prototype.toStirng); //false
alert(a1.toStirng == Object.prototype.toStirng); //true
我们可以利用toString做类型的判断:
var arr=[];
alert( Object.prototype.toStirng.call(arr) == "[Object Array]");
而且这是最完美的判断类型的方法;
typeof和instanceof判断类型都有缺陷;
总结:hasOwnProperty(),constructor,toSting(),instanceof
=============================================
4.继承
属性的继承:调用父类的构造函数,并用call来改this的指向
方法的继承:将父类的原型的所有元素赋给子类即可
function Person(name,sex){
this.name=name;
this.sex=sex;
}
Person.prototype.showName=function(){
alert(this.name);
}
function Star(name,sex,job){ //子类
Person.call(this,name,sex); //如果仅仅是Person(name,sex),那么里面的this不是Star对象也不是Person对象,而是window对象,用了call之后,里面的this就是Star对象
this.job=job;
}
Star.prototype=Person.prototype; //但是这样不好,因为prototype是对象,所以Star.prototype一做改动,Person.prototype也会改变!比如我想在Star.prototype添加一个showJob方法,那么Person也会多了一个showJob方法
而且Star的constructor也会被Person的constructor给覆盖掉,Star的constructor就变成了Person构造函数
我希望Star的修改不会影响到Person
那么就不要用对象赋值,而是用基本类型赋值,使用for in :
可以定义成函数
function extends(obj1,obj2){
for(var attr in obj2){
obj1[attr]=obj2[attr];
}
}
extends(Star.prototype,Person.prototype);
即可
下面写一个继承的拖拽
其实很简单,只要继承之后重写子类的prototype的move方法即可
如果子类的某方法想要扩展,可以这样:
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.say=function(){
console.log("My name is "+this.name+". My age is "+this.age);
}
function Coder(name,age,salary){
Person.call(this,name,age);
this.salary=salary;
}
//继承方法
extend(Coder.prototype,Person.prototype);
//扩展say方法
Coder.prototype.say=function(){
Person.prototype.say.call(this); //这里要用call来调整一下this的指向;
console.log("My salary is "+this.salary);
}
var p1=new Person("zbp",22);
p1.say();
var c1=new Coder("zbp",22,5000);
c1.say();
继承的其他形式:
a.类式继承 js没有类的概念,但构造函数就相当于类
function A(){
this.name="xm";
}
A.prototype.showName=function(){
alert(this.name);
}
function B(){
}
B.prototype=new A(); //这一句话就完成了类式继承
var b=new B();
这句话干了什么,他将实例化出来的A类的对象赋值给了B的原型,A的对象有name属性和showName方法,付给B的原型之后,B也有name属性和showName方法。然后我实例化出来一个b对象,但是此时name属性不是b本身的,而是B的prototype的属性,showName方法也不是b本身的,而是B的prototype的prototype的方法,此时B类是有两层prototype的哦!因为A把自己的所有属性都付给了B的prototype对象,而A的属性中有A的prototype属性,所以B的prototype下有A的prototype
也就是说当b调用showName的时候,他会先找b的showName,找不到,因为b没有showName方法;
然后他会往b.prototype里找showName方法,然后还是没有,他就会再往b.prototype的prototype中去找showName方法,就找到了,这要靠对原型链的理解
但是就这一句话还是不够的,他还有下面的问题,如果用B实例化多个对象b1,b2,b1的name改变,b2的name也会改变,因为这时他们的name是prototype下的共有属性;
还有,B的constructor会变成A而不是B;
作者最后改成:
function B(){
A.call(this); //继承属性
}
var F=function(){};
F.prototype=A.prototype;
B.prototype=new F(); //这三句代替B.prototype=new A();作用是只继承A的方法,此时B的prototype中就没有name!
B.prototype.constructor=B;
b.原型继承
如下:
var a={
name:"小明"
};
var b = cloneObj(a); //完成继承
function cloneObj(obj){
var F=function(){};
F.prototype=obj;
return new F();
}
对比上面3中继承:
普通继承: 比较通用,有new无new都可以;
通过调用父类的构造函数配合call来继承属性;通过将父类prototype下所有元素赋给子类prototype继承方法
类式继承: 比较适合用new实例化出来的对象的继承;通过调用父类构造函数继承属性;通过将父类的实例赋给子类的原型继承方法
原型继承:比较适合无new无构造函数的对象的继承;通过将父类对象的实例化赋给子类原型继承
===========================================
5.组件开发
继承是父子,组件是兄弟关系
组件就是展现不同的表现形式
以之前的拖拽为例子:我们做出一点改动,id的传入不放在构造函数而是放在init中
有4个色块,第一个色块只能拖拽,第二个色块能拖拽而且点下去的时候,title会出现字
第三个色块则点下去和弹起来title都会改变字,第四个色块只是弹起来的时候会改变字
<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();
dg1.init("d1");
var dg2=new Drag();
dg2.init("d2");
var dg3=new Drag();
dg3.init("d3");
var dg4=new Drag();
dg4.init("d4");
}
function Drag(){
this.d1=null
}
Drag.prototype.init=function(id,onDown,onUp){
this.d1=document.getElementById(id);
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;
onDown();
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(){
onUp();
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>
但是上面的程序是可以改进的,组件开发中对参数的处理有一套原则:
如果有多个可选参数,那么这多个可选参数就放在一个json中,将这个json作为参数传入,而必选参数则作为单独的参数传入;而对象会为可选参数设置一个默认值,这个默认值也是json,里面的元素要涵盖所有的可选参数;如果初始化的时候,用户传了可选参数,那么就用可选参数的值覆盖默认参数;
就像上面,toDown和toUp都是可选参数,就不应该分成两个参数去传,而是只应该作为一个参数传;
具体如下:
<script>
window.onload=function(){
var dg1=new Drag();
dg1.init("d1");
//*
var dg2=new Drag();
dg2.init("d2",{"toDown":function(){
document.title="2";
}});
//*
var dg3=new Drag();
dg3.init("d3",{
"toDown":function(){
document.title="3-1";
},
"toUp":function(){
document.title="3-2";
}
});
//*
var dg4=new Drag();
dg4.init("d4",{"toUp":function(){
document.title="4";
}});
}
function Drag(){
this.d1=null
//*默认参数设定
this.settings={
"toDown":function(){},
"toUp": function(){}
}
}
function extend(a,b){
for(var attr in b){
a[attr]=b[attr];
}
}
Drag.prototype.init=function(id,opt){
this.d1=document.getElementById(id);
//*将传进的可选参数覆盖默认参数
extend(this.settings,opt);
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;
//*调用可选参数的选项
this.settings.toDown();
document.onmousemove=function(ev){
var e = ev || event;
_this.move(e);
}
document.onmouseup=function(ev){
_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(){
this.settings.toUp();
document.onmousemove=null;
//document.onmouseup=null;
}
</script>
组件的开发主要是设置参数上面;我们可以通过传入不同的回填函数来实现不同的可选功能
==========================================
接下来做一个弹窗的组件开发 :
<html>
<head>
<meta charset="utf8">
<title>xxx</title>
<script src="jquery.js"></script>
<style>
.box{
width:300px;
height:300px;
position:absolute;
left:0;
top:0;
border:solid 1px black;
background:#fff;
}
.box .box-head{
height:30px;
line-height:30px;
font-size:20px;
background:#ccc;
padding:5px;
}
.box .box-head-title{
float:left;
}
.box .close{
float:right;
}
.mask{
position:absolute;
background:black;
opacity:0.7;
filter:alpha(opacity:70);
top:0;
left:0;
}
</style>
<script>
var tk1={},tk2={},tk3={},tk4={};
$(function(){
var btn_tk1=new btn_TK();
btn_tk1.init("#btn1",{title:"公告"});
var btn_tk2=new btn_TK();
btn_tk2.init("#btn2",{"pos":"bottom_right","width":100});
var btn_tk3=new btn_TK();
btn_tk3.init("#btn3",{"mask":true});
var btn_tk4=new btn_TK();
btn_tk4.init("#btn4",{"drag":true});
});
function btn_TK(){
this.btn=null;
}
btn_TK.prototype.init=function(btn,tk_opt){
var _this=this;
this.btn=$(btn);
this.id=this.btn.attr("id");
this.btn.click(function(){
_this.clickBtn(tk_opt);
});
}
btn_TK.prototype.clickBtn=function(tk_opt){
//防止多次点击按钮重复开启弹框
if($(".box[target="+this.id+"]").length>0){ //这句话的意思是判断页面是否存在这个弹框元素
return false;
}
var tk=new TK();
tk.init(tk_opt,this.id);
}
//构造函数
function TK(){
this.html={};
this.target="";
//弹框的默认参数
this.settings={
width:300,
height:300,
border_weight:1,
title:"标题",
mark:false, //遮罩层
pos:"center", //一共有center,top_left,top_right,bottom_left,bottom_right 5个方法
drag:false
}
}
function extend(obj1,obj2){
for(var attr in obj2){
obj1[attr]=obj2[attr];
}
}
//初始化
TK.prototype.init=function(opts,target=""){
var _this=this;
this.target=target;
//更新参数
extend(this.settings,opts);
//创建弹框
this.create();
$(document).on("click",".close",function(){_this.close($(this))});
}
//创建弹框
TK.prototype.create=function(){
//创建元素
this.html=$("<div class='box' target='"+this.target+"'><div class='box-head'><div class='box-head-title'>"+this.settings.title+"</div><div class='close'>x</div></div><div class='box-content'></div></div>");
//调节位置
if(this.settings.left == undefined && this.settings.top == undefined){
switch(this.settings.pos){
case "center":
this.settings.left=($(window).width()-this.settings.width)/2;
this.settings.top=($(window).height()-this.settings.height)/2;
break;
case "top_left":
this.settings.left=0;
this.settings.top=0;
break;
case "top_right":
this.settings.left=$(window).width()-this.settings.width-2*this.settings.border_weight;
this.settings.top=0;
break;
case "bottom_left":
this.settings.left=0;
this.settings.top=$(window).height()-this.settings.height-2*this.settings.border_weight;
break;
case "bottom_right":
this.settings.left=$(window).width()-this.settings.width-2*this.settings.border_weight;
this.settings.top=$(window).height()-this.settings.height-2*this.settings.border_weight;
break;
default:
this.settings.left=($(window).width()-this.settings.width)/2;
this.settings.top=($(window).height()-this.settings.height)/2;
}
}
this.html.css({
"width":this.settings.width+"px",
"height":this.settings.height+"px",
"top":this.settings.top+"px",
"left":this.settings.left+"px",
"border":"solid "+this.settings.border_weight+"px black"
});
$("body").append(this.html);
//添加遮罩层,要往body中添加了弹框之后才能添加遮罩层
if(this.settings.mask === true){
this.html.wrap("<div class='mask'></div>");
$(".mask").width($(window).width()).height($(window).width());
}
//添加拖拽
if(this.settings.drag === true){
var dg = new DG();
dg.init(this.html.find(".box-head"),this.html);
}
}
//关闭弹框事件
TK.prototype.close=function(close_btn){
var tk=close_btn.parent().parent();
if(tk.parent().hasClass("mask")){
tk.parent().remove();
}
tk.remove();
}
/**拖拽组件**/
function DG(){
this.click_el=null; //鼠标点击的元素
this.move_el=null; //要移动的元素
this.disX=0;
this.disY=0;
this.settings={};
}
DG.prototype.init=function(click_el,move_el){
var _this=this;
this.click_el=click_el;
this.move_el=move_el;
this.click_el.mousedown(function(e){
_this.mdown(e);
});
}
DG.prototype.mdown=function(e){
var _this=this;
this.disX=e.clientX-this.move_el.offset().left;
this.disY=e.clientY-this.move_el.offset().top;
$(document).mousemove(function(e){
_this.mmove(e);
});
$(document).mouseup(function(e){
$(document).unbind("mousemove");
$(document).unbind("mouseup");
});
}
DG.prototype.mmove=function(e){
x=e.clientX-this.disX;
y=e.clientY-this.disY;
if(x<0){
x=0;
}
if(x>$(window).width()-this.move_el.outerWidth(true)){
x=$(window).width()-this.move_el.outerWidth(true);
}
if(y<0){
y=0;
}
if(y>$(window).height()-this.move_el.outerHeight(true)){
y=$(window).height()-this.move_el.outerHeight(true);
}
this.move_el.css({
"left":x+"px",
"top":y+"px"
});
}
</script>
</head>
<body>
<div id="container">
<input type="button" id="btn1" value="1"> <!--正常居中弹框-->
<input type="button" id="btn2" value="2"> <!--右下角公告框-->
<input type="button" id="btn3" value="3"> <!--有遮罩层弹框-->
<input type="button" id="btn4" value="4"> <!--可拖拽的弹框-->
</div>
</body>
</html>
自定义事件:就是让函数具有事件的特性;事件的特性比如有事件对象,默认行为
自定义事件的好处是,有利于多人协作开发:
比如,有一个函数show,有3个人,老板分别让他们三个人分别在这个函数中扩展一个功能
div1.addEventListener("show",function(){
//A写的功能
alert(1);
});
div1.addEventListener("show",function(){
//B写的功能
alert(2);
});
div1.addEventListener("show",function(){
//C写的功能
alert(3);
});
show(); //要主动调用这个函数才能触发这个show事件
当然,上面这样调用是不会弹出1,2,3的,因为show不是事件,而是函数,js事件中没有show这个事件,所以把show添加到addEventListener()中是没用的;
下面就封装一个,能够定义普通事件,自定义事件的函数以及能够调用普通事件和自定义事件的函数
//定义事件,事件可以使自定义事件也可以是普通事件,obj可以是一个元素或者一个普通对象
function bindEvent(obj,event,fn){
//obj的一个事件可能有多个函数组成,这些函数叠加不覆盖
obj.listeners=obj.listeners || {}; //listeners属性存放着该对象的多个事件,是个数组
obj.listeners[event]=obj.listeners[event] || []; //listeners[event]也是个数组,因为一个事件可能有多个函数组成
obj.listeners[event].push(fn);
//定义普通事件,如果是自定义事件,绑定到下面的addEventListener()中是无效的,不过判断事件是普通事件还是自定义事件是比较麻烦的事,所以不管是普通事件还是自定义事件都绑到addEventListener()
if(obj.nodeType){ //判断obj是元素还是普通对象,是元素才addEventListener()
if(obj.addEventListener){ //这里是兼容性处理,低版本IE是没有addEventListener()方法的
obj.addEventListener(event,fn);
}else{
obj.attachEvent("on"+event,fn);
}
}
}
//主动触发事件;对于自定义事件,只能调用这个函数来主动触发事件;但是普通事件可以通过这个函数主动触发,也可以通过js事件触发
function fireEvent(obj,event){
if(obj.listeners && obj.listeners[event]){
for(var i=0;i<obj.listeners[event].length;i++){
obj.listeners[event][i]();
}
}
}
<html>
<head>
<meta charset="utf8">
<title>自定义事件</title>
<script>
//定义事件,事件可以使自定义事件也可以是普通事件,obj可以是一个元素或者一个普通对象
function bindEvent(obj,event,fn){
//obj的一个事件可能有多个函数组成,这些函数叠加不覆盖
obj.listeners=obj.listeners || {}; //listeners属性存放着该对象的多个事件,是个数组
obj.listeners[event]=obj.listeners[event] || []; //listeners[event]也是个数组,因为一个事件可能有多个函数组成
obj.listeners[event].push(fn);
//定义普通事件,如果是自定义事件,绑定到下面的addEventListener()中是无效的,不过判断事件是普通事件还是自定义事件是比较麻烦的事,所以不管是普通事件还是自定义事件都绑到addEventListener()
if(obj.nodeType){ //判断obj是元素还是普通对象,是元素才addEventListener()
if(obj.addEventListener){ //这里是兼容性处理,低版本IE是没有addEventListener()方法的
obj.addEventListener(event,fn);
}else{
obj.attachEvent("on"+event,fn);
}
}
}
//主动触发事件;对于自定义事件,只能调用这个函数来主动触发事件;但是普通事件可以通过这个函数主动触发,也可以通过js事件触发
function fireEvent(obj,event){
if(obj.listeners && obj.listeners[event]){
for(var i=0;i<obj.listeners[event].length;i++){
obj.listeners[event][i]();
}
}
}
window.onload=function(){
var d1=document.getElementById("d1");
var s1=document.getElementById("s1");
bindEvent(d1,"click",function(){
alert("div");
});
bindEvent(s1,"show",function(){
alert("span");
});
//可以叠加
bindEvent(s1,"show",function(){
alert("span++");
});
//主动触发
fireEvent(d1,"click"); //d1可以主动触发,也可以点击触发
fireEvent(s1,"show"); //s1的show事件只能主动触发,其实s1的show方法就是一个普通函数,这里就相当于调用一个普通函数而已,但是好处是可以多人协作,比如我想添加alert("span++")这个功能就可以不写在第一个bindEvent(s1,"show",function(){alert("span");});的后面,而是再写一个bindEvent(s1,"show",function(){alert("span++");});
}
</script>
</head>
<div id='d1'>div</div>
<span id="s1">span</span>
</html>