为什么需要面向对象编程?
我们以前可能还是熟悉面向过程来进行解决需求,也就是说编写一个个的函数来解决我们项目需求.这种方式不利于团队协作,以及后期维护.
什么是面向对象编程?
面向对象编程:就是一种思维,你把你需要解决的需求抽象为一个对象或者类,再根据这个对象分析它具备什么特性(属性)与动作(方法).
我们熟悉的面向对象编程三大特征,封装,多态,继承.JS虽然不是不像Java是真正意义上的面向对象,但是也是可以利用自身独特的特性,实现上述特征.
##### 面向对象编程-封装
- 首先学习如何用JS来创建一个类
默认类名我们首字母大写,以示区分.见demo:
//创建一个学生类
var Student = function(name,age,sex){
//在函数内部通过this指向该对象,来给类添加属性与方法.
this.name = name;
this.age = age;
this.sex = sex;
this.display = function(){
console.log("name is "+ this.name + ",age is " + this.age + ",sex is " + this.sex);
}
}
// 也可以通过类的原型来增加属性与方法,这里有两种方式.一个是给原型对象属性赋值,一个是把对象赋值给原型对象.
Student.prototype = {
job : "study" ,
display : function(){
console.log('我是一个学生');
}
}
//上面这段代码等同于
//Student.prototype.job = "study";
//Student.prototype.display = function(){
//console.log('我是一个学生');
// }
var xiaoM = new Student("xiaoMing" , 20 , "boy");
xiaoM.display(); //我是一个学生
xiaoM.show(); //name is xiaoMing,age is 20,sex is boy
var xiaoH = new Student("xiaoHong" , 25 , "girl");
xiaoH.display(); //我是一个学生
xiaoH.show(); //name is xiaoHong,age is 25,sex is girl
- 理解通过this给类添加属性与方法与通过prototype添加属性与方法的区别
通过上面那个demo我们应该也明白了,通过this添加的是对象本身拥有的,也就是你每次通过new 一个实例时候生成的(你传入参数),而prototype是你继承过来的.你每个new创建的实例都有的(与你的传参无关)。
- 这里顺带说下constructor属性
当你创建一个对象或者函数时候,就会创建一个原型对象prototype.在prototype对象里面又会创建constructor属性,可以理解constructor属性就是构造器函数.
var arr1 = new Array(1,2,3);
console.log(Array.prototype.constructor) //function Array() { [native code] }
- 封装: 就是利用JS的函数级作用域,把对象(类)的一些属性或者方法隐藏在其内部,函数外面不可以访问,JS不像Java有private等关键字,但是也可以实现类似的效果.
//大多数时候我们是借助闭包来实现数据的隐藏
// 闭包: 闭包首先是一个函数,是一个可以访问另外一个函数里面变量的函数,
var user = (function(){
var name = 'xyz' ;
var password = '123456';
return {
getName : function(){
return name;
},
getPsd : function(){
return password;
},
setName : function(newName){
name = newName;
},
setPsd : function(newPsd){
password = newPsd ;
}
}
}
)();
//获取原始姓名
console.log(user.getName()); // 'xyz'
//获取原始密码
console.log(user.getPsd()); // '123456'
//修改新的名字与密码并打印
user.setName('kobe');
user.setPsd('00000');
console.log("name is " + user.getName() + ",password is " + user.getPsd() ); //name is kobe,password is 00000
面向对象编程-继承
JS由于自身独特的自由性,可以有很多方法来实现继承.
- 类式继承:把对象的实例赋值给原型
var Person = function(){
this.info = 'person';
this.select = [1,2,3];
};
Person.prototype.getInfo = function(){
return 'person';
};
var Student = function(){
this.name = 'student';
};
Student.prototype = new Person();
Student.prototype.getName = function(){
return 'student';
};
var S1 = new Student();
var name = S1.getName();
var info = S1.getInfo();
console.log(name);//student
console.log(info); //person
console.log(S1.info + " " + S1.name); //person student
console.log(Student.prototype instanceof Person); //true
console.log(S1.select); //[1, 2, 3]
S1.select.push(4);
var S2 = new Student();
console.log(S1.select); //[1, 2, 3, 4]
console.log(S2.select); //[1, 2, 3, 4]
类式继承缺点:
-
类式继承是通过prototype来实现,如果父类的共有属性是引用类型,那么一个子类的实例改变了它,那么会影响到其他子类.
-
子类不是父类的实例,子类的原型才是父类的实例.
- 构造函数继承: 在子类的环境作用域中执行一次父类的构造函数
//父类
var Person = function(sex){
this.info = 'person';
this.select = [1,2,3];
this.sex = sex;
};
//在父类原型添加方法
Person.prototype.show = function(){
console.log('i am person');
};
//子类
function P1(sex){
Person.call(this,sex);
}
//创建实例
var p1 = new P1('boy');
var p2 = new P1('girl');
console.log(p1.info + "," + p1.sex); //person,boy
console.log(p2.info + "," + p2.sex); // person,girl
p1.show(); // p1.show is not a function
p1.select.push(4);
console.log(p2.select); // [1, 2, 3]
console.log(p1.select); // [1, 2, 3, 4]
console.log(p2.select); // [1, 2, 3]
知识点:
- 构造函数继承不涉及prototype,所以父类的原型方法不会共享,每个实例都是独立的.
- 组合继承: 融合类式继承与构造函数继承的优点,去除两者缺点.
//父类构造函数
function Person(name) {
this.name = name;
this.lists = [1,2,3];
};
//父类原型方法
Person.prototype.show = function(){
console.log('i am a person');
};
// 子类构造函数
function Boy (age){
Person.call(this,name);
this.age = age;
};
Boy.prototype = new Person();
// 子类原型方法
Boy.prototype.showInfo = function(){
console.log('i am a boy');
};
// 实例创建
var b1 = new Boy(30);
var b2 = new Boy(20);
b1.show(); // i am a person
b2.show(); // i am a person
console.log(b1.lists); //[1, 2, 3]
b1.lists.push(4);
console.log(b1.lists); //[1, 2, 3, 4]
console.log(b2.lists); //[1, 2, 3]
优点:
- 可以继承父类的原型方法
- 实例修改父类中的引用类型属性不会互相干扰
- 原型式继承: 通过prototype,根据一个已存在的对象来创建一个新的对象.
// 继承函数
function inheritObject(obj) {
// 替代品
function fx(){}
fx.prototype = obj;
return new fx();
};
//已存在对象
var book = {
page : 30,
name : ['json','js','java']
};
var b1 = inheritObject(book);
b1.page = 40;
b1.name.push('Python');
var b2 = inheritObject(book);
console.log( b1.name ); // ["json", "js", "java", "Python"]
console.log(b2.page); // 30
console.log(b2.name); // ["json", "js", "java", "Python"]
可见原型式继承对引用类型属性还是公用的.
- 寄生组合式继承
组合继承存在一个问题就是,子类不是父类的实例,子类的原型才是父类的实例.寄生组合就是来解决这个问题.
// 继承函数
function inheritObject(obj) {
// 替代品
function fx(){}
fx.prototype = obj;
return new fx();
};
// 继承原型
function inheritPrototype(subClass , superClass){
//保存父类原型
var obj = inheritObject(superClass.prototype);
// 设置原型的constructor
obj.constructor = subClass;
subClass.prototype = obj;
}
// 父类 Person
function Person (){
this.info = 'person';
this.lists = [1,2,3];
};
// 给父类添加原型方法
Person.prototype.show = function(){
console.log('person');
};
// 定义子类
function Boy(){
Person.call(this);
}
// 子类继承原型
inheritPrototype(Boy , Person);
// 给子类原型添加方法
Boy.prototype.infos = function(){
console.log('Boy');
}
// 创建实例
var B1 = new Boy();
var B2 = new Boy();
B1.show(); //person
B1.infos(); //Boy
B1.lists.push(4);
console.log(B1.lists); // [1, 2, 3, 4]
console.log(B2.lists); // [1, 2, 3]
console.log(B2 instanceof Boy); // true
寄生组合式继承知识点:
- 父类引用类型在子类实例中的不共享.
- 子类数父类的实例
- 可以继承父类的原型方法
JS的多继承
众所周知,一些传统的面向对象语言是具备多继承的.那么JS呢?理论上JS是基于原型链来实现继承,而原型链是单链不具备多继承特性,但是JS可以通过自身特性模拟多继承.
Object.prototype.extend = function(){
var i = 0,
len = arguments.length,
args;
for(; i < len ; i++){
args = arguments[i];
//浅复制
for(var property in args){
this[property] = args[property];
}
}
};
var Person = {
info : "person",
types : 'china'
}
var Boy = {
sex : "boy" ,
name : ['kobe','cpul']
}
var Student = [];
Student.extend(Person , Boy);
console.log(Student.sex + ','+ Student.types); //boy,china
多态
在传统面向对象语言中,多态就是同一种方法多种不同调用方式.
var baiduMap = {
show: function(){
console.log("百度地图API");
}
};
var gooleMap = {
show: function(){
console.log("谷歌地图API");
}
}
var rentMap = function( map ){
if(map.show instanceof Function ){
map.show();
}
}
rentMap( baiduMap ); //百度地图API
rentMap( gooleMap ); //谷歌地图API
JS的多态更多的是对传参进行判断.