面向对象和面向过程的区别
面向对象和面向过程的主要区别是面向过程主要是以动词为主,解决问题的方式是按照顺序一步一步调用不同的函数。而面向对象主要是以名词为主,将问题抽象出具体的对象,而这个对象有自己的属性和方法,在解决问题的时候是将不同的对象组合在一起使用。所以说面向对象的好处就是可扩展性更强一些,解决了代码重用性的问题。
-
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
-
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
具体的实现我们可以看一下最经典的 把大象放进冰箱这个功能
面向过程的解决办法
在面向过程的编程方式中实现“把大象放冰箱”这个问题答案是耳熟能详的,一共分三步:
1.开门(冰箱);
2.装进(冰箱,大象);
3.关门(冰箱);
面向对象的解决办法
1.冰箱.开门()
2.冰箱.装进(大象)
3.冰箱.关门()
可以看出来面向对象和面向过程的侧重点是不同的,面向过程是以动词为主,完成一个事件就是将不同的动作函数按顺序调用。面向对象是以主谓为主。将主谓看成一个一个的对象,然后对象有自己的属性和方法。比如说,冰箱有自己的id属性,有开门的方法。然后就可以直接调用冰箱的开门方法给其传入一个参数大象就可以了。
用自己的话理解就是面向过程是分步骤去完成事件,假使某处有调整还得从头开始预演验证,而面向对象是直接针对对象,如有调整直接更改调整其中的单一属性也就是局部修改就可以了
面向对象有三大特性,封装、继承和多态
封装
封装:把客观事物封装成抽象的类,隐藏属性和方法的实现细节,仅对外公开接口
因为对象都对自己负责,所以,对象的很多东西都不需要或者不可以暴露给外部。封装解决了数据的安全性,内在也体现了"每个对象都对自己负责"的原则。
通过构造函数添加
function Cat(name,color){
this.name = name;
this.color = color;
this.eat = function () {
alert('吃老鼠')
}
}
生成实例:
var cat1 = new Cat('tom','red')
通过this定义的属性和方法,我们实例化对象的时候都会重新复制一份 也就是会在内存中复制一份,这样就造成了内存的浪费。
通过原型prototype
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "猫科动物";
Cat.prototype.eat = function() {alert('吃老鼠')}
然后生成实例
var cat1 = new Cat('大毛','黄色');
var cat2 = new Cat('二毛','黑色');
alert(cat1.type); //猫科动物
cat1.eat(); //吃老鼠
Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。也就是说,对于那些不变的属性和方法,我们可以直接将其添加在类的prototype 对象上。
继承
继承:子类可以使用父类的所有功能,并且对这些功能进行扩展。继承的过程,就是从一般到特殊的过程 其实继承都是基于以上封装方法的三个特性来实现的
继承,没什么好说的,主要目的是解决了代码的复用。
类式继承
所谓的类式继承就是使用的原型的方式,将方法添加在父类的原型上,然后子类的原型是父类的一个实例化对象。
//声明父类
var SuperClass = function () {
var id =1;
this.name = ['javascript'];
this.superValue = function () {
console.log('superValue is true');
console.log(id)
}
};
//为父类添加共有方法
SuperClass.prototype.getSuperValue = function () {
return this.superValue()
};
// 声明子类
var SubClass = function () {
this.subValue = function () {
console.log('this is subValue')
}
};
// 继承父类
SubClass.prototype = new SuperClass();
// 为子类添加共有方法
SubClass.prototype.getSubValue = function () {
return this.subValue()
}
var sub = new SubClass();
var sub2 = new SubClass();
SubClass.prototype.getSuperValue() //superValue is true 1
sub.getSuperValue(); //superValue is true 1
sub.getSubValue(); //this is subValue
console.log(sub.id) //undefined
console.log(sub.name) //["javascript"]
sub.name.push('java')
console.log(sub2.name) //["javascript", "java"]
其中最核心的一句代码是SubClass.prototype = new SuperClass() ;
构造函数继承
// 构造函数继承
// 声明父类
function SuperClass(id) {
var name = 'javascript';
this.books=['javascript','html','css'];
this.id = id
}
// 声明父类原型方法
SuperClass.prototype.showBooks = function () {
console.log(this.books)
}
// 声明子类
function SubClass(id) {
SuperClass.call(this,id)
}
// 创建第一个子类实例
var subclass1 = new SubClass(10);
var subclass2 = new SubClass(11);
console.log(subclass1.books) //["javascript", "html", "css"]
console.log(subclass2.id) //11
console.log(subclass1.name) //undefined
subclass2.showBooks(); error
构造函数继承的核心思想就是SuperClass.call(this,id),直接改变this的指向,使通过this创建的属性和方法在子类中复制一份
组合式继承
所以组合式继承就是汲取两者的优点,即避免了内存浪费,又使得每个实例化的子类互不影响。
// 组合式继承
//声明父类
var SuperClass = function(name) {
this.name = name;
this.books=['javascript','html','css']
};
//声明父亲原型上的方法
SuperClass.prototype.showBooks = function () {
console.log(this.books)
};
//声明子类
var SubClass = function (name) {
SuperClass.call(this, name)
};
//子类继承父类(链式继承)
SubClass.prototype = new SuperClass();
// 实例化子类
var subclass1 = new SubClass('java');
var subclass2 = new SubClass('php');
subclass2.showBooks();
subclass1.books.push('ios');
console.log(subclass1.books);
console.log(subclass2.books);
这种组合继承也是有点小缺陷的,那就是它调用了两次父类的构造函数。
寄生组合继承
寄生组合式继承强化的部分就是在组合继承的基础上减少一次多余的调用父类的构造函数:
function Animal(color) {
this.color = color;
this.name = 'animal';
this.type = ['pig', 'cat'];
}
Animal.prototype.greet = function(sound) {
console.log(sound);
}
function Dog(color) {
Animal.apply(this,arguments);
this.name = 'dog';
}
//注意下面两行
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.getName = function() {
console.log(this.name);
}
var dog = new Dog('白色');
var dog2 = new Dog('黑色');
dog.type.push('dog');
console.log(dog.color); // '白色'
console.log(dog.type); // ['pig', 'cat', 'dog']
console.log(dog2.type); // ['pig', 'cat']
console.log(dog2.color); //'黑色'
dog.greet('汪汪'); //'汪汪'
在上面的例子中,我们并不像构造函数继承一样直接将父类Animal的一个实例赋值给Dog.prototype,而是使用Object.create()进行一次浅拷贝,将父类原型上的方法拷贝后赋给Dog.prototype,这样子类上就能拥有了父类的共有方法,而且少了一次调用父类的构造函数。Object.create()的浅拷贝的作用类式下面的函数:
function create(obj) {
function F() {};
F.prototype = obj;
return new F();
}
这里还需注意一点,由于对Animal的原型进行了拷贝后赋给Dog.prototype,因此Dog.prototype上的constructor属性也被重写了,所以我们要修复这一个问题:
Dog.prototype.constructor = Dog;
extends继承
Class和extends是在ES6中新增的,Class用来创建一个类,extends用来实现继承:
class Animal {
constructor(color) {
this.color = color;
}
greet(sound) {
console.log(sound);
}
}
class Dog extends Animal {
constructor(color) {
super(color);
this.color = color;
}
}
let dog = new Dog('黑色');
dog.greet('汪汪'); //汪汪
console.log(dog.color) //'黑色'
在上面的代码中,创建了父类Animal,然后Dog子类继承父类,两个类中都有一个constructor构造方法,实质就是构造函数Animal和Dog。
不知道你有没有注意到一点,我在子类的构造方法中调用了super方法,它表示父类的构造函数,用来新建父类的this对象。
注意:子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象
多态
同一个操作,作用于不同的对象,会产生不同的结果。多态最大的好处可以用2个词语来概括:“灵活”、“解耦”。我们经常说:“要面向接口编程,而不是面向实现编程”。不同的对象,相同的接口,但因为多态,有了不同的实现。这样面向接口编程,就降低了耦合度,很灵活
var makeSound = function(animal){
if(animal && animal.sound instanceof Function) {
animal.sound();
}
};
var Duck = function(){};
Duck.prototype.sound = function(){
console.log('嘎嘎嘎');
}
var Chicken = function(){}
Chicken.prototype.sound = function(){
console.log('咯咯咯');
}
makeSound( new Duck());
makeSound( new Chicken());