当前位置: 首页 > 科技观察

浅谈JavaScript的面向对象及其封装、继承、多态

时间:2023-03-20 18:45:19 科技观察

写在前面由于是简单的讨论,就不从原理上深入分析了,但是可以帮助我们更好的理解...object面向对象和面向过程面向对象和面向过程是两种不同的编程思想。我们刚开始接触编程的时候,大部分都是从面向过程开始的。毕竟像我一样,大家最早接触的计算机语言大概就是C语言了。C语言是典型的面向过程的计算机语言。面向过程主要是基于动词,解决问题的方法是依次调用不同的函数。面向对象是以名词为基础,把问题抽象成一个具体的对象,这个对象有自己的属性和方法。解决一个问题的时候,就是把不同的对象组合起来使用。//面向过程加载大象1.Open(冰箱)2.(大象)Loading(冰箱)3.关闭(冰箱)//面向对象加载大象1.Refrigerator.Open()2.Refrigerator.装载(大象)3.冰箱。关上门()从这个例子可以看出面向对象是基于主语和谓语的,主语和谓语可以一一调用对象,然后对象有自己的属性和方法。面向对象是按功能划分问题,而不是按步骤划分。功能的统一保证了面向对象设计的可扩展性,解决了代码的复用性问题。这也是在漫长的编程开发过程中得到的验证结果。面向对象的编程思想优于面向过程的封装。面向对象具有三大特点:封装、继承、多态。封装:就是把事物封装成类,隐藏事物的属性和方法的实现细节,对外只暴露接口。在ES5中,没有类的概念,但是由于js的函数级作用域(函数内的变量无法在函数外访问)。这样我们就可以模拟上课了。在es5中,一个类其实就是一个变量,里面持有一个函数,这个函数有自己的属性和方法。将属性和方法组合成一个类的过程就是封装。1、通过构造函数添加JavaScript提供了构造函数(Constructor)模式,用于在创建对象时对对象进行初始化。构造函数其实就是一个普通的函数,但是它有以下几个特点①首字母大写(建议构造函数首字母大写,也就是用大驼峰来命名,非构造函数的首字母是小写)②内部使用this③通过构造函数使用new生成实例添加属性和方法其实就是通过this添加属性和方法。因为this总是指向当前对象,所以通过this添加的属性和方法只是添加到当前对象上,是对象本身拥有的。所以当我们实例化一个新的对象时,this指向的属性和方法都会相应的创建,也就是在内存中复制一份,这样会造成内存的浪费。functionCat(name,color){this.name=name;this.color=color;this.eat=(()=>{console.log("fish!")})}//生成实例varcat1=newCat("tom","gray")通过this定义的属性和方法,当我们实例化对象时,Tawau会新建一个副本2.通过prototype原型封装类,通过this添加属性和方法会造成内存浪费,是有什么办法可以让实例化类使用的属性和方法直接用指针指向相同的属性和方法。这就是原型方法JavaScript规定每个构造函数都有一个指向另一个对象的原型属性。该对象的所有属性和方法都将由构造函数的实例继承。也就是说,对于那些不变的属性和方法,我们可以直接将它们添加到类的原型对象中。functionCat(name,color){this.name=name;this.color=color;}Cat.prototype.type="英短";Cat.prototype.eat=(()=>{alert("fish!")})//生成实例varcat1=newCat('Tom','gray');varcat2=newCat('Kobe','purple');console.log(cat1.type);//英文简称cat2.eat();//鱼!此时所有实例的type属性和eat()方法实际上都是同一个内存地址,指向原型对象,从而提高了运行效率。但是这样做也有缺点,因为实例化对象的原型都指向同一个内存地址,改变一个对象的属性可能会影响到其他对象。es6中的类和包声明一个类①构造函数:在构造函数内部创建自己的属性②方法:声明方法classCat{//相当于Cat构造函数constructor(name){this.name=name;}//相当于声明类的内部函数//等价于Cat.prototype.eaat(){console.log("fish!");}}//生成实例varcat1=newCat("tom");cat1.eat();//fish!console.log(cat1instanceofCat);//trueconsole.log(cat1instanceofObject);//trueconsole.log(typeofCat);//functionconsole.log(typeofCat.prototype.eat);//函数是上面Cat类声明的一个例子:Cat类是一个带有构造函数的类Behavioralfunctions,内部方法eat其实就是Cat.prototype.eat(),所以es6的类封装class本质上是es5实现的语法糖。主要区别在于类class的属性不能被重新赋值和不可枚举,Cat.prototype是只读属性类和自定义类型的区别(1)类的声明不会被提升,类似于let(2)类的声明自动以严格模式运行(3)类声明的方法不能枚举(4)类的内部方法没有constructor属性,不能new(5)类的构造函数必须调用new(6)类的内部方法不能与类class重名。在js中使用类作为一等公民,可以直接作为值使用//1.类名作为参数传递给函数functioncreateObj(ClassName){returnnewClassName()}//2。立即执行,实现单例模式letcat1=newclass{constructor(name){this.name=name}eat(){console.log("fish!")}}("tom”)cat1.eat()//鱼!继承继承是指子类可以使用父类的所有功能,扩展和继承这些功能的过程就是从一般到特殊的过程。1.类继承所谓类继承就是利用原型的方式,将方法添加到父类的原型中,然后子类的原型就是父类的一个实例化对象。//声明父类varSuperClass=function(){letid=1;this.name=['java'];this.superValue=function(){console.log('thisissuperValue!')}}//添加到父类PublicmethodSuperClass.prototype.getSuperValue=function(){returnthis.superValue();};//声明子类varSubClass=function(){this.subValue=(()=>{console.log('thisissubValue!')})}//继承父类SubClass.prototype=newSuperClass();//为子类添加一个公共方法SubClass.prototype.getSubValue=function(){returnthis.subValue()}//生成一个实例varsub1=新子类();varsub2=newSubClass();sub1.getSuperValue();//thisissuperValue!sub1.getSubValue();//thisissubValue!console.log(sub1.id);//undefinedconsole.log(sub1.name);//["java"]sub1.name.push("php");console.log(sub1.name);//["java","php"]console.log(sub2.name);//["java","php"]其核心是SubClass.prototype=newSuperClass();类的原型对象原型对象的作用是为类的原型添加常用的方法,但是类不能直接访问这些方法,只能在实例化类之后,新创建的对象复制类的属性和方法父类的构造函数,并将原型proto指向父类的原型对象。这样子类就可以访问父类的属性和方法,同时父类中定义的属性和方法不会被子类继承。但是使用类继承的方法,如果父类的构造函数中有引用数据类型,那么子类中的所有实例都会共享,所以如果子类的实例改变了引用数据类型,就会影响其他子类。类的实例。构造函数继承为了克服类继承的缺点,出现了构造函数继承。构造函数继承的核心思想是SuperClass.call(this,id),直接改变this的方向,使得通过this创建的属性和方法在子类中复制一份,因为是单独复制的,所以每个实例化的子类互不影响。但是会造成内存浪费//构造函数继承//声明父类varSuperClass=function(id){varname='java'this.languages=['java','php','ruby'];this.id=id}//声明子类varSubClass=function(id){SuperClass.call(this,id)}//生成实例varsub1=newSubClass(1);varsub2=newSubClass(2);console.log(sub2.id);//2console.log(sub1.name);//undefinedsub1.languages.push("python");console.log(sub1.languages);//['java','php','ruby','python']console.log(sub2.languages);//['java','php','ruby']组合继承组合继承借鉴了两者的优点,既避免了内存浪费,又使得每一个实例化的子类都不会互相影响。//组合继承//声明父类varSuperClass=function(name){this.languages=['java','php','ruby'];this.name=name;}//声明父类原型方法SuperClass.prototype.showLangs=function(){console.log(this.languages);}//声明子类varSubClass=function(name){SuperClass.call(this,name)}//子类继承父类(链式继承)SubClass.prototype=newSuperClass();//生成实例varsub1=newSubClass('python');varsub2=newSubClass('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']但是警告:组合继承方式是好的,但是会导致一个问题,父类的构造函数会被创建两次(一次是call(),new再次)寄生组合继承组合继承的缺点关键在于父类的构造函数是在两边创建的类继承和构造函数继承相结合,但是在类继承中我们不需要创建父类的Constructor,只要子类继承父类的原型即可。所以我们先复制父类的原型,然后修改子类的constructor属性,***只是设置子类的原型//prototypeinheritance//prototypeinheritance其实就是对类继承的封装,实现的函数返回一个实例,其原型继承传入的o对象functioninheritObject(o){//声明一个过渡函数functionF(){}//过渡对象的原型链继承父对象F.prototype=o;//返回原型继承父对象的过渡对象实例returnnewF();}//寄生继承//寄生继承是对原型继承的二次封装,使得子类的原型等于父类的Prototype。并且在第二次封装过程中,继承的对象是extendedfunctioninheritPrototype(subClass,superClass){//复制一份父类的原型保存在变量中,这样p的原型就等于原型oftheparentclassvarp=inheritObject(superClass.prototype);//修正子类constructor属性因重写子类原型p.constructor=subClass;//设置子类原型subClass.prototype=p;}//定义父类varSuperClass=function(name){this.name=name;this.languages=["java","php","python"]}//定义父类原型方法SuperClass.prototype.showLangs=function(){console.log(this.languages);}//定义子类varSubClass=function(name){SuperClass.call(this,name)}inheritPrototype(SubClass,SuperClass);varsub1=newSubClass('go');InheritedclassSuperClass{constructor(name)ines6){this.name=namethis.languages=['java','php','go'];}showLangs(){console.log(this.languages);}}classSubClassextendsSuperClass{constructor(name){super(name)}//重写父类中的方法showLangs(){this.languages.push(this.name)console.log(this.languages);}}//生成实例varsub=newSubClass('韩二胡');console.log(sub.name);//韩二胡sub.showLangs();//["java","php","go","韩文《两只老虎》】多态多态其实就是不同的对象,同一个操作的不同效果。多态的思想其实就是把“你想做什么”和“谁来做”分开。然后问对象“是什么类型的”是你吗”,然后根据得到的答案调用对象的某个行为。你只要调用这个行为,其他的都可以由多态来处理。按照规范,多态是最基本的功能是通过将过程条件语句转换为对象多态来消除这些条件分支语句,由于JavaScript中提到多态的详细介绍不多,这里简单举例介绍一下Just//non-polymorphicvarhobby=function(animal){if(animal=='cat'){cat.eat()}elseif(animal=='dog'){dog.eat()}}varcat={eat:function(){alert("fish!")}}vardog={eat:function(){alert("meat!")}}console.log(123);hobby('cat');//fish!hobby('dog');//我在!从上面的例子可以看出,虽然hobby函数目前保持了一定的灵活性,但是这种灵活性是非常脆弱的。一旦需要替换或添加到其他动物,就必须改Hobby函数,继续堆条件分支语句。我们把程序的同一部分抽象出来,就是吃东西。然后重新编程。//多态varhobby=function(animal){if(animal.eatinstanceofFunction){animal.eat();}}varcat={eat:function(){alert("fish!")}}vardog={eat:function(){alert("meat!")}}现在看看这段代码中的多态性。当我们向两种动物发送eat消息时,会分别调用它们的eat方法,会产生不同的执行结果。对象的多态性提醒我们,“做什么”和“怎么做”可以分开,从而大大增强了代码的灵活性。即使以后加入其他动物,爱好功能也不会做任何改变。//多态varhobby=function(animal){if(animal.eatinstanceofFunction){animal.eat();}}varcat={eat:function(){alert("fish!")}}vardog={eat:function(){alert("meat!")}}varaoteman={eat:function(){alert("lil-monster!")}}hobby(cat);//fish!hobby(dog);//肉!hobby(aoteman);//小怪物!