既然是浅谈,就不会从原理上深度分析,只是帮助咱们更好地理解...php
面向对象和面向过程是两种不一样的编程思想,刚开始接触编程的时候,咱们大都是从面向过程起步的,毕竟像我同样,你们接触的第一门计算机语言大几率都是C语言,C语言就是一门典型的面向过程的计算机语言。
面向过程主要是以动词为主,解决问题的方式是按照顺序一步一步调用不一样的函数。
面向对象是以名词为主,将问题抽象出具体的对象,而这个对象有本身的属性和方法,在解决问题的时候,是将不一样的对象组合在一块儿使用。java
//面向过程装大象 1.开(冰箱) 2.(大象)装进(冰箱) 3.关(冰箱)
//面向对象装大象 1. 冰箱.开门() 2. 冰箱.装进(大象) 3. 冰箱.关门()
从这个例子能够看出,面向对象是以主谓为主,将主谓堪称一个一个的对象,而后对象有本身的属性和方法。
面向对象是以功能来划分问题的,而不是步骤。功能上的统一保证了面向对象设计的可扩展性,解决了代码重用性的问题。
这也是在漫长的程序设计的发展过程当中获得的验证结果,面向对象的编程思想较之于面向过程较好一点python
面向对象有封装、继承和多态三大特性。
封装:就是把事物封装成类,隐藏事物的属性和方法的实现细节,仅对外公开接口。es6
在ES5中,并无class的概念,可是因为js的函数级做用域(函数内部的变量函数外访问不到)。因此咱们能够模拟class。在es5中,类其实就是保存了一个函数的变量,这个函数有本身的属性和方法。将属性和方法组成一个类的过程就是封装。编程
JavaScript提供了一个构造函数(Constructor)模式,用来在建立对象时初始化对象。构造函数其实就是普通的函数,只不过有如下的特色ruby
①首字母大写(建议构造函数首字母大写,即便用大驼峰命名,非构造函数首字母小写) ②内部使用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定义的属性和方法,咱们实例化对象的时候斗湖从新复制一份this
在类上经过this的方式添加属性和方法会致使内存浪费的现象,有什么办法可让实例化的类所使用的属性和方法 直接使用指针 指向同一个属性和方法。es5
这就是原型的方法prototype
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声明一个类
①构造器:构造器内建立自有属性
②方法:声明类实例具备的方法
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!
继承就是子类可使用父类的全部功能,而且对这些功能进行扩展。继承的过程,就是从通常到特殊的过程。
所谓的类式继承就是使用的原型的方式,将方法添加在父类的原型上,而后子类的原型是父类的一个实例化对象。
//声明父类 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');
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!
快乐面向对象😁