本节开始讲解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.先写出面向过程的选项卡,再改成面向对象的;
b.然后变形,变形的原则:
I. 尽量不要有函数嵌套函数,比如window.onload=function(){}就是一个函数,里面还有btns[i].onclick=function(){}这就是函数嵌套函数。所以函数中尽量不要有事件的定义
也就是说,方法和属性的声明要放在全局中(即window.onload外);
所以就变形成:
上面就是把事件(方法)和属性的定义放在了全局
II.把onload中不是赋值的语句单独放在全局的函数中;给元素添加事件也有等号,但是不算是赋值语句;这里说的赋值语句是指给属性赋值的语句;
c.改成面向对象,原则是
1.全局变量就是对象的属性,所以所有全局变量都放在构造函数中定义,而且属性前都要加上this,全局函数就是对象的方法,而对象的方法要用原型定义
2.在onload中创建对象,并且初始化运行
改成:
3.改this的指向,记住一点,事件和定时器中的this会改变,此时要重新指定this
上面是会报错的,因为有些this是不对的,比如change中的this代表的是按钮,而不是t1这个对象,所以我们要改一下this中的指向;
原则是:方法中的this必须全都代表实例化的对象(在这里是t1),而不能是事件的元素(在这里是btn)
所以改成:
这样做的好处是,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()方法:
*小练习:用面向对象写拖拽
先写出面向过程的写法:
再是面向对象的写法:
这里比选项卡多了一个事件对象ev,这个时候var e= ev || event;就不是写在Drag的方法中,而是写下调用Drag方法的外面那层匿名函数中;因为ev和event只有在事件的函数中才有,而mouseUp,mouseDown这个是普通的方法而已,没有事件对象的。