写在前面
既然是浅谈,就不会从原理上深度剖析,只是协助我们更好地明白…
面向对象与面向历程
面向对象和面向历程是两种差别的编程头脑,刚开始打仗编程的时刻,我们大都是从面向历程起步的,毕竟像我一样,人人打仗的第一门计算机言语大几率都是C言语,C言语就是一门典范的面向历程的计算机言语。
面向历程重要是以动词为主,处理题目的体式格局是依据递次一步一步挪用差别的函数。
面向对象是以名词为主,将题目笼统出细致的对象,而这个对象有本身的属性和要领,在处理题目的时刻,是将差别的对象组合在一起运用。
//面向历程装大象
1.开(冰箱)
2.(大象)装进(冰箱)
3.关(冰箱)
//面向对象装大象
1. 冰箱.开门()
2. 冰箱.装进(大象)
3. 冰箱.关门()
从这个例子能够看出,面向对象是以主谓为主,将主谓可谓一个一个的对象,然后对象有本身的属性和要领。
面向对象是以功用来离别题目的,而不是步骤。功用上的一致保证了面向对象设想的可扩大性,处理了代码重用性的题目。
这也是在冗长的顺序设想的生长历程当中获得的考证结果,面向对象的编程头脑较之于面向历程较好一点
封装
面向对象有封装、继承和多态三大特征。
封装:就是把事物封装成类,隐蔽事物的属性和要领的完成细节,仅对外公然接口。
在ES5中,并没有class的观点,然则由于js的函数级作用域(函数内部的变量函数外接见不到)。所以我们能够模仿class。在es5中,类实在就是保留了一个函数的变量,这个函数有本身的属性和要领。将属性和要领构成一个类的历程就是封装。
1.经由过程组织函数增加
JavaScript供应了一个组织函数(Constructor)情势,用来在建立对象时初始化对象。组织函数实在就是平常的函数,只不过有以下的特性
①首字母大写(发起组织函数首字母大写,即运用大驼峰定名,非组织函数首字母小写)
②内部运用this
③运用new天生实例
经由过程组织函数增加属性和要领实际上也就是经由过程this增加的属性和要领。由于this老是指向当前对象的,所以经由过程this增加的属性和要领只在当前对象上增加,是该对象本身具有的。所以我们实例化一个新对象的时刻,this指向的属性和要领都邑获得响应的建立,也就是会在内存中复制一份,如许就形成了内存的糟蹋。
function Cat(name,color){
this.name = name;
this.color = color;
this.eat = (() => {
console.log("fish!")
})
}
//天生实例
var cat1 = new Cat("tom", "gray")
经由过程this定义的属性和要领,我们实例化对象的时刻斗湖从新复制一份
2.经由过程原型prototype封装
在类上经由过程this的体式格局增加属性和要领会致使内存糟蹋的征象,有什么方法能够让实例化的类所运用的属性和要领 直接运用指针 指向统一个属性和要领。
这就是原型的要领
JavaScript划定,每一个组织函数都有一个prototype属性,指向另一个对象。这个对象的统统属性和要领,都邑被组织函数的实例继承。
也就是说,关于那些稳定的属性和要领,我们能够直接将其增加在类的prototype对象上。
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "英短";
Cat.prototype.eat = ( () => {
alert("fish!")
} )
//天生实例
var cat1 = new Cat('Tom', 'gray');
var cat2 = new Cat('Kobe', 'purple');
console.log(cat1.type); //英短
cat2.eat(); //fish!
这时候统统实例的type属性和eat()要领,实在都是统一个内存地址,指向prototype对象,因而就提高了运转效力。
然则如许做也有弊病,由于实例化的对象的原型都是指向统一内存地址,修改个中一个对象的属性可能会影响到其他的对象
es6中的类和封装
es6声明一个类
①组织器:组织器内建立自有属性
②要领:声明类实例具有的要领
class Cat {
//等价于Cat组织器
constructor(name) {
this.name = name;
}
//越发简朴的声明类的内部函数
//等价于 Cat.prototype.eat
eat() {
console.log("fish!");
}
}
//天生实例
var cat1 = new Cat("tom");
cat1.eat(); //fish!
console.log(cat1 instanceof Cat); //true
console.log(cat1 instanceof Object); //true
console.log(typeof Cat); //function
console.log(typeof Cat.prototype.eat); //function
从上面class声明的Cat为例:Cat类是一个具有组织函数行动的函数,个中内部要领eat实际上就是Cat.prototype.eat()
所以说es6的class封装类,本质上是es5完成体式格局的语法糖
最重要的区分在于,class类的属性是不可从新赋值和不可枚举的,Cat.prototype就是一个只读属性
class和自定义范例的区分
(1)class的声明不会提拔,与let相似
(2)class的声明自动运转于严厉情势之下
(3)class声明的要领不可枚举
(4)class的内部要领没有 constructor 属性,没法new
(5)挪用class的组织函数必需new
(6)class内部要领不能同名
class类的运用
class作为js中的一级国民,能够被看成值来直接运用
//1.类名作为参数传入函数
function createObj (ClassName) {
return new ClassName()
}
//2.马上实行,完成单例情势
let cat1 = new class{
constructor (name) {
this.name = name
}
eat() {
console.log("fish!")
}
}("tom”)
cat1.eat() //fish!
继承
继承就是子类能够运用父类的统统功用,而且对这些功用举行扩大。继承的历程,就是从平常到特别的历程。
1.类式继承
所谓的类式继承就是运用的原型的体式格局,将要领增加在父类的原型上,然后子类的原型是父类的一个实例化对象。
//声明父类
var SuperClass = function(){
let id = 1;
this.name = ['java'];
this.superValue = function() {
console.log('this is superValue!')
}
}
//为父类增加共有要领
SuperClass.prototype.getSuperValue = function () {
return this.superValue();
};
//声明子类
var SubClass = function() {
this.subValue = (() => {
console.log('this is subValue!')
})
}
//继承父类
SubClass.prototype = new SuperClass();
//为子类增加共有要领
SubClass.prototype.getSubValue = function() {
return this.subValue()
}
//天生实例
var sub1 = new SubClass();
var sub2 = new SubClass();
sub1.getSuperValue(); //this is superValue!
sub1.getSubValue(); //this is subValue!
console.log(sub1.id); //undefined
console.log(sub1.name); //["java"]
sub1.name.push("php");
console.log(sub1.name); //["java", "php"]
console.log(sub2.name); //["java", "php"]
个中最中心的是SubClass.prototype = new SuperClass();
类的原型对象prototype对象的作用就是为类的原型增加共有的要领的,然则类不能直接接见这些要领,只需将类实例化今后,新建立的对象复制了父类组织函数的属性和要领,并将原型 proto 指向了父类的原型对象。如许子类就能够接见父类的属性和要领,同时,父类中定义的属性和要领不会被子类继承。
but运用类继承的要领,假如父类的组织函数中有援用数据范例,就会在子类中被统统实例共用,因而一个子类的实例假如更改了这个援用数据范例,就会影响到其他子类的实例。
组织函数继承
为了战胜类继承的瑕玷,才有了组织函数继承,组织函数继承的中心头脑就是SuperClass.call(this, id),直接转变this的指向,使经由过程this建立的属性和要领在子类中复制一份,由于是零丁复制的,所以各个实例化的子类互不影响。but会形成内存糟蹋的题目
//组织函数继承
//声明父类
var SuperClass = function(id){
var name = 'java'
this.languages = ['java', 'php', 'ruby'];
this.id = id
}
//声明子类
var SubClass = function(id){
SuperClass.call(this, id)
}
//天生实例
var sub1 = new SubClass(1);
var sub2 = new SubClass(2);
console.log(sub2.id); // 2
console.log(sub1.name); //undefined
sub1.languages.push("python");
console.log(sub1.languages); // ['java', 'php', 'ruby', 'python']
console.log(sub2.languages); // ['java', 'php', 'ruby']
组合式继承
组合式继承是汲取了二者的长处,既避免了内存糟蹋,又使得每一个实例化的子类互不影响。
//组合式继承
//声明父类
var SuperClass = function(name){
this.languages = ['java', 'php', 'ruby'];
this.name = name;
}
//声明父类原型要领
SuperClass.prototype.showLangs = function () {
console.log(this.languages);
}
//声明子类
var SubClass = function(name){
SuperClass.call(this, name)
}
//子类继承父类(链式继承)
SubClass.prototype = new SuperClass();
//天生实例
var sub1 = new SubClass('python');
var sub2 = new SubClass('go');
sub2.showLangs(); //['java', 'php', 'ruby']
sub1.languages.push(sub1.name);
console.log(sub1.languages);//["java", "php", "ruby", "python"]
console.log(sub2.languages);//['java', 'php', 'ruby']
but正告:组合式继承要领当然好,然则会致使一个题目,父类的组织函数会被建立两次(call()的时刻一遍,new的时刻又一遍)
寄生组合继承
组合式继承的瑕玷的关键是 父类的组织函数在类继承和组织函数继承的组合情势被建立了双方,然则在类继承中我们并不须要建立父类的组织函数,我们只需子类继承父类的原型即可。
所以我们先给父类的原型建立一个副本,然后修改子类的 constructor 属性,末了在设置子类的原型就能够了
//原型式继承
//原型式继承实在就是类式继承的封装,完成的功用返回一个实例,该实例的原型继承了传入的o对象
function inheritObject(o) {
//声明一个过渡函数
function F() {}
//过渡对象的原型链继承父对象
F.prototype = o;
//返回一个过渡对象的实例,该实例的原型继承了父对象
return new F();
}
//寄生式继承
//寄生式继承就是对原型继承的第二次封装,使得子类的原型即是父类的原型。而且在第二次封装的历程当中对继承的对象举行了扩大
function inheritPrototype(subClass, superClass){
//复制一份父类的原型保留在变量中,使得p的原型即是父类的原型
var p = inheritObject(superClass.prototype);
//修改由于重写子类原型致使子类constructor属性被修改
p.constructor = subClass;
//设置子类的原型
subClass.prototype = p;
}
//定义父类
var SuperClass = function(name) {
this.name = name;
this.languages = ["java", "php", "python"]
}
//定义父类原型要领
SuperClass.prototype.showLangs = function() {
console.log(this.languages);
}
//定义子类
var SubClass = function(name) {
SuperClass.call(this,name)
}
inheritPrototype(SubClass, SuperClass);
var sub1 = new SubClass('go');
es6中的继承
class SuperClass {
constructor(name) {
this.name = name
this.languages = ['java', 'php', 'go'];
}
showLangs() {
console.log(this.languages);
}
}
class SubClass extends SuperClass {
constructor(name) {
super(name)
}
//重写父类中的要领
showLangs() {
this.languages.push(this.name)
console.log(this.languages);
}
}
//天生实例
var sub = new SubClass('韩二虎');
console.log(sub.name); //韩二虎
sub.showLangs(); //["java", "php", "go", "韩二虎"]
多态
多态实际上是差别对象作用与统一操纵发生差别的结果。多态的头脑实际上是把 “想做什么” 和 “谁去做” 离开。
多态的优点在于,你没必要再向对象讯问“你是什么范例”后依据获得的答案再去挪用对象的某个行动。你只管去挪用这个行动就是了,其他的统统能够由多态来担任。范例来讲,多态最基础的作用就是经由过程吧历程化的前提语句转化为对象的多态性,从而消弭这些前提分支语句。
由于JavaScript中提到的关于多态的细致引见并不多,这里简朴的经由过程一个例子来引见就好
//非多态
var hobby = function(animal){
if(animal == 'cat'){
cat.eat()
}else if(animal == 'dog'){
dog.eat()
}
}
var cat = {
eat: function() {
alert("fish!")
}
}
var dog = {
eat: function() {
alert("meat!")
}
}
console.log(123);
hobby('cat'); //fish!
hobby('dog'); //meat!
从上面的例子能看到,虽然 hobby 函数如今坚持了肯定的弹性,但这类弹性很软弱的,一旦须要替代或许增加成其他的animal,必需修改hobby函数,继承往里面堆砌前提分支语句。我们把顺序中雷同的部份笼统出来,那就是吃某个东西。然后再从新编程。
//多态
var hobby = function(animal){
if(animal.eat instanceof Function){
animal.eat();
}
}
var cat = {
eat: function() {
alert("fish!")
}
}
var dog = {
eat: function() {
alert("meat!")
}
}
如今来看这段代码中的多态性。当我们向两种 animal 发出 eat 的音讯时,会离别挪用他们的 eat 要领,就会发生差别的实行结果。对象的多态性提醒我们,“做什么” 和 “怎样去做”是能够离开的,如许代码的弹性就增强了许多。纵然今后增加了其他的animal,hobby函数依旧不会做任何转变。
//多态
var hobby = function(animal){
if(animal.eat instanceof Function){
animal.eat();
}
}
var cat = {
eat: function() {
alert("fish!")
}
}
var dog = {
eat: function() {
alert("meat!")
}
}
var aoteman = {
eat: function(){
alert("lil-monster!")
}
}
hobby(cat); //fish!
hobby(dog); //meat!
hobby(aoteman); //lil-monster!
快活面向对象😁