万物皆对象
将一个班的所有同学都比作一个普通对象,现在想对这个班学生的信息进行保存,操作如下:
1)声明学生对象,每一个学生对象都具有一些相同的属性,如果每一个学生都一个一个地声明,会很复杂。解决方法就是将相同的部分进行封装。(工厂模式)
var stu1={ //声明一个学生对象,包含name等属性
name:'lisi',
age:20,
gender:'men',
say:function( ){ }
...
}
var stu={ //声明一个学生对象,包含name等属性
name:'tom',
age:20,
gender:'women',
say:function( ){ }
....
}
1.工厂模式
function setStudent(name,age,gender){ //声明一个函数包含公共部分的属性,属性名都有
var obj=new Object( ); //创建一个空对象
obj.name=name; //往空对象里填属性
obj.age=age;
obj.gender=gender;
obj.say=function( ){ //声明一个公有的属性,属性值也相同 //在此声明say时是声明给了obj,obj是上面创建空对象创建来的,调用一次setStudent,创建一个新的对象,形成一个新的空间,stu1在调用setStudent和stu2在调用setStudent时,里面obj生成的内存空间不是一个,里面的say方法也不是一个,不在一个内存空间中引用地址也就不是一个了
alert("hello,say---------");
}
return obj; //返回对象包括它的内容
}
var stu1=setStudents("lisi",20,"men"); //调用函数,将实参传给形参,声明一个变量接收调用函数得到的返回值
var stu2=setStudents("tom",23,"men");
var stu3=setStudents("terry",25,"men");
console.log(stu1.constructor); //打印stu1和stu2的构造者,二者都被function Object( )构造,因为存储学生信息的空间是通过new Object( )得到的。
console.log(stu2.constructor);
stu1.say( ); //调用say( )
stu2.say( );
console.log(stu1.say==stu2.say); //say是引用数据类型,进行==比较,比较的是引用地址,引用地址在声明那儿找
相较于上面的一个一个变量的声明,工厂模式大大的减少了代码,即使以后需要声明的对象多也比上面一个一个声明少,对象写得越多,越能体现封装的好处。
考虑:stu1是否等于stu2? stu1.say( )是否等于stu2.say( )?
console.log(stu1==stu2); //不等于
console.log(stu1.say==stu2.say); //不等于
如下图为内存分析图:
工厂模式的优点:简化代码的操作(封装)
缺点:1.对象不能细分 2.公有属性和方法浪费内存
2.构造函数模式(解决工厂模式的对象不能细分问题)
function say(){ //公有部分放在外面
alert();
}
function Student(name,age,gender){ //声明构造函数Student //形参将值赋给谁需要调用一个对象出来
//new Object( ) 不需要
this.name=name; //不能new Object( ),不然类型仍为object类型,所以用this关键字
this.age=age; //this指向谁,this被属于Student一部分,它被new调用,new为堆区中的新的内存空间,相当于在新的内存空间添加属性
this.gender=gender;
this.say=say;
/*this.say=function(){ //存放公有属性,属性名+属性值都相同的部分
alert();
}*/
// return 不需要
}
var stu1=new Student("lisi",20,"men"); //调用构造函数Student,将实参传给形参,声明变量stu1接收Student的返回值
var stu2=new Student("tom",23,"men");
console.log(stu1.constructor); //Student
console.log(stu1==stu2) //反回false,因为stu1和stu2都是new Student,new意味着新开辟一个空间,两者的所属空间不同
stu1.say( );
stu2.say( );
console.log(stu1.say==stu2.say); //返回值false,原理同工厂模式
优点:在工厂模式的基础上解决了对象细分问题
缺点:内存问题没有彻底解决
有可能出现stu1.say==stu2.say的情况如下图:将say函数写在构造函数和原型对象和实例对象的外面,可暂时解决内存空间问题
公有属性放在外面会比较乱,放在原型对象中合适,原型对象就是用来存放静态属性和方法的。
3.原型对象模式
function Student( name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
}
/*Student.prototype.say=function( ){} //在原型对象中声明函数say
Student.prototype.run=function( ){}*/ //想简化操作,不要这两步,直接用下行的{ },但是stu1和stu2构造者仍为Student,分析如下图2
Student.prototype={ //为何此时say和run不能调用,分析如下图1 ,{ }相当于new Object(隐晦)
constructor:Student, //{ }(Object实例)含有constructor属性,将它的构造者强制指向Student
enjoy:function( ){},
work:function( ){}
}
var stu1=new Student("lisi",20,"men"); //调用Student函数,将实参传给形参,将函数返回值赋给stu1
var stu2=new Student("tom",23,"men");
stu1.say( ); //say在原型对象中,stu1和stu2都可以调用,且只有原型对象和实例对象可调用,stu1.say==stu2.say返回true
stu2.say( );
图1
图2
4.继承
让子类的原型指向父类的实例
父类实例对象的constructor指向子类的构造函数
function Rich(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
}
Rich.prototype.money=["card1","card2","card3"];
Rich.prototype.enjoy=function( ){
alert("enjoy");
}
function Poor(name,age,gender,color){
Rich.call(,this,name,age,gender); //不加.call指向window,加.call指向this
/*this.name=name;
this.age=age;
this.gender=gender;*/
this.color=color;
}
/*Poor.prototype=Rich.prototype; //这两步操作的过程
Rich.prototype.constructor=Poor; */ //分析图如下图1
Poor.prototype=new Rich( ); //子类的原型指向父类的实列
Poor.prototype.constructor=Poor;
Poor.prototype.work=function( ){
alert("work-------------");
}
var r1=new Rich("lisi",23,"men");
var p1=new Poor("tom",20,"men","black");
图1
图2