json方式的面向对象首先要知道，js中出现的东西都能够放到json中。关于json数据格式这里推荐一篇博客：JSON 数据格式先看下json创建的简单对象：相比基础篇中的构造函数、原型等的创建方式，json方式简单方便；但是缺点很明显，如果想创建多个对象，那么会产生大量重复代码，不可取。JSON方式适用于只创建一个对象的情况，代码简介又优雅。 JSON在JS面向对象的应用中，主要的一个作用就是命名空间：如果有大量常用的js函数，利用json，我们可以将同一类函数放在一个“类”里，类似于java那样，这样我们就能很好的管理和查找使用这些js函数，看下面的例子就很好理解了。 面向对象的继承先举个简单的例子来说一下JS中的继承，Student <extends> Person；在js中，通过call来调用父类的构造方法继承父类的属性(第33行)，通过原型来继承父类的方法(第39行)。注意：先调用父类构造函数，再添加自己的属性；先继承父类的方法，再添加自己的方法。这里解释下为什么调用Person.call(this, name, sex)就相当于是在调用父类的构造方法：先问一下这个call中的this是谁？这里指向对象student吧。所以，在子构造函数中调用Person.call()时，那么构造函数Person里的两行代码this.name=name, this.sex=sex中this就是代表student了，所以这两行代码相当于是在为student添加name和sex属性。但是，下面的通过原型来继承父类的方法，即Student.prototype = Person.prototype，是有问题的，这种方式将影响父类(继承是不能影响父类的)，此时Person的原型中有了个showMajor方法(第50行)，为什么呢？先思考下，下面解释。 第50行弹出的信息：为了解释为什么通过Student.prototype = Person.prototype来继承父类的方法会影响父类，下面举一个数组的例子，一看就知道怎么回事了。为什么arr1和arr2弹出来的一样呢？第15、16行显示arr1和arr2是一个对象。对象！应该很清楚了吧，arr1和arr2都是指向这个数组对象的一个引用，所以改变arr2时，arr1也变了。 其实我们主要是想获得arr1数组的一个副本，怎么做才能不改变arr1呢，看下面： 同样的，我们也可以通过这种方式为继承的子类添加父类原型中的方法，而又不影响父类(38-41行)： 第53行弹出信息：Person中没有showMajor方法了。/** * 拖拽 * @param {Object} id div的id */ function Drag(id){ this.oBox = document.getElementById(id); this.disX = 0; this.disY = 0; var _this = this; this.oBox.onmousedown = function(){ _this.fnDown(); } } //鼠标按下 Drag.prototype.fnDown = function(ev){ var oEvent = ev || event; this.disX = oEvent.clientX - this.oBox.offsetLeft; this.disY = oEvent.clientY - this.oBox.offsetTop; var _this = this; document.onmousemove = function(){ _this.fnMove(); }; document.onmouseup = function(){ _this.fnUp(); }; } //鼠标移动 Drag.prototype.fnMove = function(ev){ var oEvent= ev || event; this.oBox.style.left = oEvent.clientX - this.disX + 'px'; this.oBox.style.top = oEvent.clientY - this.disY + 'px'; } //鼠标抬起 Drag.prototype.fnUp = function(){ document.onmousemove = null; document.onmouseup = null; }drag.html 拖拽 效果：可以看到红色和蓝色的都出边界了，但我们又不想去修改代码，那我们怎么做？学过java的应该都知道可以写一个子类来做一些更加具体的操作，又保留了父类的功能，就是继承。DragLimit.js：DragLimit继承自Drag，控制了不能出边界/** * 限制边界的拖拽，继承自Drag * @param {Object} id */ function DragLimit(id){ Drag.call(this, id); } //继承方法 for(var p in Drag.prototype){ DragLimit.prototype[p] = Drag.prototype[p]; } /** * 覆写父类的鼠标移动方法，控制不能移出边界 */ DragLimit.prototype.fnMove = function(ev){ var oEvent= ev || event; var left = oEvent.clientX - this.disX; var top = oEvent.clientY - this.disY; //控制边界 if(left < 0){ left = 0; } else if(left > document.documentElement.clientWidth-this.oBox.offsetWidth){ left = document.documentElement.clientWidth-this.oBox.offsetWidth; } if(top <= 0){ top = 0; } else if(top > document.documentElement.clientHeight-this.oBox.offsetHeight){ top = document.documentElement.clientHeight-this.oBox.offsetHeight; } this.oBox.style.left = left + 'px'; this.oBox.style.top = top + 'px'; }dragLimit.html 拖拽 效果：蓝色是不能移出边界的。JS中的对象js中的对象分为本地对象、内置对象、宿主对象，这里给出W3School文档供参考：ECMAScript 对象类型原文地址：https://www.cnblogs.com/chiangchou/p/js-oop3.html
案例——面向对象的选项卡把面向过程的程序一步步改成面向对象的形式，使其能够更加的通用(但是通用的东西，一般会比较臃肿)。下面是一个简单的选项卡，也是我们常见的面向过程的创建形式。 选项卡 GTA5、孤岛惊魂 澳大利亚、西藏 暗里着迷、一生有你 效果：图下面来一步步改成面向对象的形式。1.首先将嵌套的函数拿到window.onload外面，不能有函数嵌套，可以有全局变量。如下：所有的改写最终效果都不变。 选项卡 GTA5、孤岛惊魂 澳大利亚、西藏 暗里着迷、一生有你 2.将全局的变量变为对象的属性，全局的函数变为对象的方法；将window.onload里的代码提取到一个构造函数里面，在window.onload里创建对象即可；(下面的代码执行起来是有问题的)。这里必须注意：在构造函数Tab里的this跟之前this所代表的是不同的(此处是通过new来创建对象的)；在上面的示例中，this指的是调用者；在构造函数里，this指向的是var tab = new Tab() ，即tab这个对象，注意是对象。说一下这段代码的问题：我们在Tab的原型上添加clickBtn方法后，clickBtn方法里的this本应该是指向var tab = new Tab()的，但是我们在第44行将clickBtn添加给了this.tabBtn[i]，即input按钮，clickBtn的所属由Tab对象变成了input按钮(第49行)。clickBtn的所属变成input按钮后，那么clickBtn里的this指向按钮，那再来看clickBtn里的代码，this.tabBtn、this.tabDiv，input按钮里有这两个属性吗？没有，所以会出错！ 选项卡 GTA5、孤岛惊魂 澳大利亚、西藏 暗里着迷、一生有你 将clickBtn赋给input按钮后，方法内的this也指向了input按钮：执行第49行代码的效果图3.将clickBtn的调用放在一个函数里，这样就不会改变clickBtn的所属了。看第41-47行，注意在function里需要用到外部保存的变量_this。再看第52行，此时弹出的是一个Object，说明clickBtn的所属关系没变，还是Tab对象。但是还有另一个问题，此时clickBtn里的this指向对象，那么看第57、58行中this.className、this.index，此处的this指的是tab对象，那么对象中有这两个属性吗？没有，还会出错！ 选项卡 GTA5、孤岛惊魂 澳大利亚、西藏 暗里着迷、一生有你 4. 以参数的形式将点击的按钮传入clickBtn中，看第44行代码，以及54,55行代码 选项卡 GTA5、孤岛惊魂 澳大利亚、西藏 暗里着迷、一生有你 5.最终版 —— 将代码提取到一个单独的js文件中，在用的时候引入即可。一般花大时间去写一个面向对象的程序，就是为了能够复用，以及方便的使用。tab.js/** * 选项卡 * @param {Object} id 选项卡id */ function Tab(id){ var tabBox = document.getElementById(id); this.tabBtn = tabBox.getElementsByTagName('input'); this.tabDiv = tabBox.getElementsByTagName('div'); for(var i=0;i<this.tabBtn.length;i++){ this.tabBtn[i].index = i; var _this = this; this.tabBtn[i].onclick = function(){ _this.clickBtn(this); }; } }; /** * 为Tab原型添加点击选项卡方法 * @param {Object} btn 点击的按钮 */ Tab.prototype.clickBtn = function(btn){ for(var j=0;j<this.tabBtn.length;j++){ this.tabBtn[j].className=''; this.tabDiv[j].style.display='none'; } btn.className='active'; this.tabDiv[btn.index].style.display='block'; };使用：tab.html 可以看到使用的时候，就可以很简单的创建两个选项卡出来了。效果： ![图][3] 看的有点晕吧，一定要好好理解JS面向对象中的this。js中面向对象大部分时候出问题就是出在this的处理上，需要注意。 面向过程：简洁、美观、容易维护； 面向对象：容易出错、混乱、难以维护；面向对象相对面向过程来说，写代码的时候麻烦些，但是用起来很方便，面向过程则相反。 开发面向对象的程序需要有一个好的面向对象的思维，即将具体对象抽象成一个构造函数的过程。 再来简单总结一下JS面向对象中的this，this一般会在两种情况下出问题，一是使用定时器、二是事件，从上面的例子中也可以看出来。注意下面的说法是在构造函数里哦，其它情况下，this指向的是调用者。 可以看到效果没有将姓名显示出来，其实看到这里原因应该很清楚了，就是第14行代码中this.name，此处的this指向谁？指向window，因为setInterval是属于window的。 效果： ![图][4] 解决办法：上面例子中已经列出来了，就是用一个function将要执行的代码包起来，使其所属关系不会发生变化，注意function里调用方法时使用的是外部变量'_this'。事件的处理在上面的例子中已经说明了。 再附上一个案例 —— 拖拽 原始的面向过程代码： 拖拽 下面是面向对象的代码 drag.js /**拖拽@param {Object} id div的id */function Drag(id){this.oBox = document.getElementById(id); this.disX = 0; this.disY = 0; var _this = this; this.oBox.onmousedown = function(){ _this.fnDown(); }}//鼠标按下Drag.prototype.fnDown = function(ev){var oEvent = ev || event; this.disX = oEvent.clientX - this.oBox.offsetLeft; this.disY = oEvent.clientY - this.oBox.offsetTop; var _this = this; document.onmousemove = function(){ _this.fnMove(); }; document.onmouseup = function(){ _this.fnUp(); };}//鼠标移动Drag.prototype.fnMove = function(ev){var oEvent= ev || event; this.oBox.style.left = oEvent.clientX - this.disX + 'px'; this.oBox.style.top = oEvent.clientY - this.disY + 'px';}//鼠标抬起Drag.prototype.fnUp = function(){document.onmousemove = null; document.onmouseup = null;}drag.html 拖拽 效果： ![5][5] 原文链接：[https://www.cnblogs.com/chiangchou/p/js-oop2.html][6] [1]: https://echo.solo90.com/usr/uploads/2020/01/4035616526.jpg [2]: https://echo.solo90.com/usr/uploads/2020/01/2954603307.jpg [3]: https://echo.solo90.com/usr/uploads/2020/01/1506937552.jpg [4]: https://echo.solo90.com/usr/uploads/2020/01/3573167301.jpg [5]: https://echo.solo90.com/usr/uploads/2020/01/3169727987.jpg
面向对象(Java面向对象亦是如此)1.对象：对象是一个整体，对外提供一些操作。2.面向对象：使用对象时，只关注对象提供的功能，不关注其内部细节。比如电脑——有鼠标、键盘，我们只需要知道怎么使用鼠标，敲打键盘即可，不必知道为何点击鼠标可以选中、敲打键盘是如何输入文字以及屏幕是如何显示文字的。总之我们没必要知道其具体工作细节，只需知道如何使用其提供的功能即可，这就是面向对象。3.JS的对象组成：方法 和 属性在JS中，有函数、方法、事件处理函数、构造函数，其实这四个都是函数，只是作用不同。函数是独立的存在，方法属于一个对象，事件处理函数用来处理一个事件，构造函数用来构造对象。首先通过常用的数组来认识下JS的对象： 4.认识下JS中的this以及全局对象window 此处的show()函数为一个全局函数，调用show()，alert(this)弹出来的是window对象，说明全局函数属于window。上面定义的show()等于为window添加一个方法，全局的函数和变量都是属于window的，上面的定义等价于下面。 同样的我们也可以根据自己的需求为其它的对象添加方法，比如显示数组：但是我们不能在系统对象中随意附加方法和属性，否则可能会覆盖已有方法、属性。 从上面的例子也可以看出来，this即表示当前函数的调用者是谁，但是在一种情况下不是的，就是使用new 来创建对象时，this并不是指向调用者的，在后面会有说明。window是全局对象，可以看下属于window的全局属性和全局方法：JS中自定义对象，逐步分析JS中的创建对象1.通过Object创建简单对象：这种方式有一个非常大的弊端，就是如果我有多个人怎么办，每次都要新建一个对象，然后添加属性、方法，这种方式是一次性的，会产生大量重复代码，这是不可取的。 2.用工厂方式来构造对象：工厂简单来说就是投入原料、加工、出厂。通过构造函数来生成对象，将重复的代码提取到一个函数里面，避免像第一种方式写大量重复的代码。这样我们在需要这个对象的时候，就可以简单地创建出来了。 但是，这种方式有两个缺点：①一般我们创建对象是通过new来创建，比如new Date()，这里使用的是方法创建。使用new来创建可以简化一些代码，也带来一些新的特性。②每个对象都有一套自己的方法，浪费资源(虽然对于现在的计算机来说不算什么，但我们尽量将设计做到最好就行了)。这里为什么说每个对象都有自己的一套方法呢，是因为创建function()的时候其本质是通过new Function()来创建的，会诞生一个新的函数对象，所以每个对象的方法是不一样的，这样就存在资源浪费的问题了。看第25行代码。 使用new 来创建JS对象 弹出信息显示this即是一个Object(第13行代码)。在方法调用前使用new来创建，function内的this会指向一个新创建的空白对象，而不是指向方法调用者，而且会自动返回该对象。但是这种方式只解决了第一个问题，每个对象还是有自己的一套方法(第35行代码)。4.在function原型(prototype)上进行扩展原型添加的方法不会有多个副本，不会浪费资源，所有的对象只有一套方法(看第29行代码)。 至于为什么是用的一套方法呢，看第31行代码：因为所有的方法都等于原型上的方法。 通过prototype我们还可以很方便的扩展系统对象，按照自己的需求来扩展，而且又能适用于所有地方，又不会浪费资源。如下面对Array进行扩展，求数组和的方法。 原文地址：https://www.cnblogs.com/chiangchou/p/js-oop1.html
php面向对象编程（十五）异常捕获和处理
php中的异常扩展PHP内置的异常处理类；可以通过实例化Exception类或者它的子类来创建一个异常实例。Exception::__construct — 异常构造函数Exception::getMessage — 获取异常消息内容Exception::getPrevious — 返回异常链中的前一个异常Exception::getCode — 获取异常代码Exception::getFile — 创建异常时的程序文件名称Exception::getLine — 获取创建的异常所在文件中的行号Exception::getTrace — 获取异常追踪信息Exception::getTraceAsString — 获取字符串类型的异常追踪信息Exception::__toString — 将异常对象转换为字符串Exception::__clone — 异常克隆内置异常子类Exception是所有异常类的基类、下面两个直属子类LogicException 逻辑处理异常类,和RuntimeException 运行时异常类。php中异常处理异常处理功能提供了处理程序运行时出现的错误或异常情况的方法。PHP5提供了一种新的面向对象的错误处理方法。使用检测(try）、抛出(throw)和捕获(catch)异常。try 使用异常的函数应该位于”try”代码块内。如果没有出发异常,则代码将照常继续执行。如果异常被出发，会抛出一个异常。throw 这里规定如何出发异常，每一个“throw”,必须对应至少一个”catch”.catch “catch”代码块会捕获异常，并创建一个包含异常信息的对象。一个 try 至少要有一个与之对应catch。定义个多个catch可以捕获不同的对象.php会按这些catch被定义的顺序执行，知道完成最后一个为止。php异常处理语句$e = new Exception('我是错误',2); echo "错误信息：",$e->getMessage()," "; echo "错误行号：",$e->getLine(); throw $e; //如果抛出异常不捕获 将会报错Fatal error: Uncaught Exceptio<?php //正确语法 class A{ public function __construct(){ throw new Exception('错误信息'); } } try{ $a=new A(); echo "没有异常、我会执行"; }catch(Exception $e){ echo '在文件',$e->getFile(),'中的第',$e->getLine(),'行，中产生异常信息:',$e->getMessage(); }多个catch捕获不同的异常<?php class A{ public function __construct(){ $i=mt_rand(0,5); if($i>2){ throw new OutOfBoundsException('这是一个异常错误',2); }else{ throw new OutOfBoundsException('这是数组越界异常',3); } } } try{ $a=new A(); echo "没有异常、我会执行"; }catch(OutOfBoundsException $e){ echo '在文件',$e->getFile(),'中的第',$e->getLine(),'行，产生异常信息:',$e->getMessage(); }catch(Exception $e){ echo "这是普通异常：",$e->getMessage(); }嵌套try{}catch(){}异常<?php class A{ public function __construct(){ $i=mt_rand(0,10); if($i>2 && $i<5){ throw new OutOfBoundsException('这是一个异常错误',2); }elseif($i<2){ throw new OutOfBoundsException('这是数组越界异常',3); } } } try{ $a=new A(); echo "没有异常、我会执行 "; try{ throw new RangeException('这是新的异常',5); }catch (Exception $ee){ echo $ee->getMessage(); } }catch(OutOfBoundsException $e){ echo '在文件',$e->getFile(),'中的第',$e->getLine(),'行，产生异常信息:',$e->getMessage(); }catch(Exception $e){ echo "这是普通异常：",$e->getMessage(); }自定义异常注意事项自定义异常必须继承自Exception 类或者它的子类。自定义的异常需要重新定义构造函数的话，建议同时掉用parnet::__construct()来检查所有的变量是否已被赋值。当对象要输出字符串的时候，可以重载__toString()并自定义输出的样式。<?php class MyException extends Exception { public function __construct($msg,$code){ //如果重写异常类方法、构造函数 需要传递变量、不然调用方法没有参数 parent::__construct($msg,$code); } public function __toString() { return "这是自定义异常类：".__CLASS__." "; } } try{ throw new MyException("这是我自定义的异常",2); echo "没有异常、我会执行 "; }catch(MyException $e){ echo '在文件',$e->getFile(),'中的第',$e->getLine(),'行，产生异常信息:',$e->getMessage(); echo " ",$e; }设置顶层异常处理器set_exception_handler()函数可以设置处理所有为捕获异常的用户定义函数。<?php class MyException extends Exception { } //设置顶层处理异常函数 function my_exception_handler(Exception $e){ echo $e->getMessage()," "; } set_exception_handler('my_exception_handler'); class Test{ function __construct() { throw new MyException('我是异常在TEST里面',3); } } $t= new Test();
php面向对象编程（十四）反射机制和动态代理
Reflection反射机制php5添加了一项新的功能:Reflection。这个功能使得程序员可以reflection class,interface,function,method …..。通过PHP代码,就可以得到某类/方法等的所有信息，并且可以和它交互。反射类可得到的信息ReflectionFunction()函数定义所在的文件以及起始位置等。 ReflectionClass()常量/属性/方法/命名空间/类是否为flnal或者abstract等。 ReflectionMethod()方法修饰类型/方法名/方法注释等。 使用方法ReflectionFunction<?php /** * 测试函数 * @author 启东 */ function test(){ echo "我是一个测试函数 "; } test(); $ref_fun = new ReflectionFunction('test');//反射函数类 echo $ref_fun->getDocComment()," ";//函数注释 如果new的是类 使用时类注释 //如果是方法 就是获取方法注释 echo "定义的所在文件名：",$ref_fun->getFileName()," ";//函数定义文件 echo "代码开始行".$ref_fun->getStartLine()." ";//开始行 echo "代码结束行".$ref_fun->getEndLine()." ";//结束行 try { new ReflectionFunction("test1"); }catch(ReflectionException $e){ echo $e->getMessage()," "; };ReflectionClass<?php class A{ function t1(){ echo "我是一个不带参数的方法 "; } function t2($str){ echo "我是一个带一个参数的方法t2,参数是：",$str," "; } private function t3(){ } static function t4(){ echo "我是一个静态方法 "; } } $a=new A(); $ref_cls= new ReflectionClass('A');//参数可以是类名，也可以是类的一个实例 //获取类中的所有方法 $class_all_method=$ref_cls->getMethods();//获取这个类的所有方法 var_dump($class_all_method); /** F:\Work\test\test.php:21: array (size=4) 0 => object(ReflectionMethod)[3] public 'name' => string 't1' (length=2) public 'class' => string 'A' (length=1) 1 => object(ReflectionMethod)[4] public 'name' => string 't2' (length=2) public 'class' => string 'A' (length=1) 2 => object(ReflectionMethod)[5] public 'name' => string 't3' (length=2) public 'class' => string 'A' (length=1) 3 => object(ReflectionMethod)[6] public 'name' => string 't4' (length=2) public 'class' => string 'A' (length=1) */ //判断类中是否有某个方法 hasMethod 返回布尔值 var_dump($ref_cls->hasMethod('t1')); //true var_dump($ref_cls->hasMethod('tt'));//false //判断方法的修饰符 public protected private static //判断方式:is+修饰符 返回布尔值。 //判断之前获取类中的方法 $ref_method=$ref_cls->getMethod('t1'); var_dump($ref_method->isPublic());//判断是否是公开方法ReflectionMethod<?php class A{ function t1(){ echo "我是一个不带参数的方法 "; } function t2($str){ echo "我是一个带一个参数的方法t2,参数是：",$str," "; } private function t3(){ } static function t4(){ echo "我是一个静态方法 "; } } $a=new A(); $ref_cls= new ReflectionClass('A');//参数可以是类名，也可以是类的一个实例 //获取类中的方法的反射类 $ref_method=new ReflectionMethod($a,'t3'); var_dump($ref_method->isPublic());//是否公开方法 $ref_method=new ReflectionMethod('A','t4'); var_dump($ref_method->isStatic());//是否静态方法 //获取方法 然后执行方法 $ref_method=$ref_cls->getMethod('t1'); //静态方法调用 invoke(null，参数) //普通方法调用 invoke(实例对象，参数) //私有或受保护的方法不能被invoke调用 if($ref_method->isPublic() && !$ref_method->isAbstract()){ if($ref_method->isStatic()){ $ref_method->invoke(null); }else{ $ref_method->invoke($a); } } //静态方法invoke调用 $ref_method=$ref_cls->getMethod('t4'); if($ref_method->isPublic() && !$ref_method->isAbstract()){ if($ref_method->isStatic()){ $ref_method->invoke(null); }else{ $ref_method->invoke($a); } } //带参数的invoke调用 $ref_method=$ref_cls->getMethod('t2'); if($ref_method->isPublic() && !$ref_method->isAbstract()){ if($ref_method->isStatic()){ $ref_method->invoke(null); }else{ $ref_method->invoke($a,'什么'); } }动态代理动态代理:也叫委托模式，在委托模式中，有两个对象参与处理同意个请求，接受请求的对象将请求委托给另一个对象来处理。代码示例：<?php class A{ function showInfo(){ echo "Class A showInfo "; } } class B{ private $obj; function __construct(){ $this->obj = new A(); } function addObj($obj){ $this->obj[]=$obj; } function __call($name,$args){ $ref_cls= new ReflectionClass($this->obj); if($ref_cls->hasMethod($name)){ $ref_method=$ref_cls->getMethod($name); if($ref_method->isPublic() && !$ref_method->isAbstract()){ if($ref_method->isStatic()){ $ref_method->invoke(null); }else{ $ref_method->invoke($this->obj); } } } } } $b= new B(); $b->showInfo(); <?phpclass A{function showInfo(){ echo "Class A showInfo "; }}class B{private $obj; function __construct(){ $this->obj = new A(); } function addObj($obj){ $this->obj[]=$obj; } function __call($name,$args){ $ref_cls= new ReflectionClass($this->obj); if($ref_cls->hasMethod($name)){ $ref_method=$ref_cls->getMethod($name); if($ref_method->isPublic() && !$ref_method->isAbstract()){ if($ref_method->isStatic()){ $ref_method->invoke(null); }else{ $ref_method->invoke($this->obj); } } } }}$b= new B();$b->showInfo();插件系统：利用反射机制自动获取插件列表以及其他的相关信息。定义test.php文件代码示例如下<?php interface Plugin{ function showMenu(); } class MyPlugin implements Plugin{ //获取插件菜单 function showMenu() { $menu=array( array( 'name'=>'menu1', 'link'=>'index.php?act=link1' ), array( 'name'=>'menu2', 'link'=>'index.php?act=link2' ) ); return $menu; } } class YourPlugin implements Plugin{ //获取插件菜单 function showMenu() { $menu=array( array( 'name'=>'menu3', 'link'=>'index.php?act=your_link3' ), array( 'name'=>'menu4', 'link'=>'index.php?act=your_link4' ) ); return $menu; } }定义代码示例a.php 并运行a.php<?php include_once __DIR__.'/test.php'; /** * 获取插件的菜单 */ function get_plugin_menus(){ $all_class=get_declared_classes(); $menus=$menu=[]; foreach ($all_class as $cls){ $ref_cls= new ReflectionClass($cls); //判断这个类是否实现某个接口 if($ref_cls->implementsInterface('plugin')){ if($ref_cls->hasMethod('showMenu')){ $ref_method=$ref_cls->getMethod('showMenu'); if($ref_method->isPublic() && !$ref_method->isAbstract()) { if ($ref_method->isStatic()) { $menu=$ref_method->invoke(null); } else { //通过反射类获取类的一个实例 $instance=$ref_cls->newInstance(); $menu=$ref_method->invoke($instance); //$ref_method->invoke(new $cls); } } } } $menus=array_merge($menus,$menu); } return $menus; } $menu_list=get_plugin_menus(); var_dump($menu_list);
php面向对象编程（十三）三大特性之多态
什么是多态？多态按字面上意思理解就是”多种形状”。可以理解为多种表现形式，即“一个对外接口(方法)，多个内部实现”。在面向对象的理论中，多态性的一般定义为：同一个操作(函数)做用于不同的类的实例（那电脑的USB接口举例、你插U盘是读数据、你插音箱是放音乐），将产生不同的执行结果。也即不同类的对象收到相同的消息时，将得到不同的结果。流程控制实现多态//流程控制实现多态 class Light{ public function show($type=0){ switch($type){ case 0: echo "灯光是红色~ "; break; case 1: echo "灯光是绿色~ "; break; case 2: echo "灯光是蓝色~ "; break; default: echo "随机颜色~ "; break; } } } class User{ function openLight($type=0){ $light=new Light(); $light->show($type); } } $list=new User(); $list->openLight(0); 继承方式实现多态<?php //继承方式实现多态 class Light{ //也可把他设置为 abstract public function show(){ echo "灯光是随机颜色~~ "; } } class BlueLight extends Light { function show(){ echo "灯光颜色是蓝色~~ "; } } class GreenLight extends Light { function show(){ echo "灯光颜色是绿色~~ "; } } class User{ function openLight(Light $obj){ $obj->show(); } } $list=new User(); $light=new Light(); $list->openLight($light); $BlueLight= new BlueLight(); $list->openLight($BlueLight); $GreenLight= new GreenLight(); $list->openLight($GreenLight);接口方式实现多态<?php //接口方式实现多态 interface Light{ //也可把他设置为 abstract public function show(); } class BlueLight implements Light { function show(){ echo "灯光颜色是蓝色~~ "; } } class GreenLight implements Light { function show(){ echo "灯光颜色是绿色~~ "; } } class User{ function openLight(Light $obj){ $obj->show(); } } $list=new User(); $BlueLight= new BlueLight(); $list->openLight($BlueLight); $GreenLight= new GreenLight(); $list->openLight($GreenLight);
PHP中的接口php与大多数面向对象编程语言一样，不支持多重继承，也就是说每个类只能继承一个父类。为了解决这个问题，PHP引入了接口，接口的思想是指定了一个实现了该接口的类必须实现的一系列方法。定义：接口(interface)是抽象方法和静态常量定义的集合。在PHP中，声明接口语法类似声明类，只不过使用关键词interface,interface 接口名{….}规范：接口是一种特殊的抽象类，这种抽象类中只包含抽象方法和静态常量。在接口中的抽象方法只能是public的，默认也是public权限。abstract和final修饰符也不能修饰接口中的抽象方法。<?php interface User{ //public $name 用访问控制修饰符的属性 是报错的 const GROUP =12; //抽象方法是不能有方法体 public function t(); //没有public修饰的方法默认为public }接口的实现和继承接口的实现使用implements来实现以个接口。一个类可以实现多个接口。只要使用逗号(,)将多个接口连接起来就可以。<?php interface User{ function getName(); function setName(); } interface Demo{ function Demo1(); } class Person implements User{ private $name; function getName(){ echo "My name is",$this->name," "; } function setName($name=''){//这里必须的有一个默认值 $this->name = $name; } } //实现继承多个接口 class Stu implements User,Demo{ private $name; function getName(){ echo "My name is",$this->name," "; } function setName($name=''){//这里必须的有一个默认值 $this->name = $name; } function Demo1(){ echo "Demo接口中的demo1方法"; } } $lisi= new Person(); $lisi->setName('李四'); $lisi->getName(); $zhangsan= new stu(); $zhangsan ->setName('张三'); $zhangsan->getName(); $zhangsan->demo1();一个类可以继承类并实现接口<?php interface User{ function getName(); function setName(); } class AdminUser{ public function pubNews(){ echo "管理员发布新闻功能 "; } } class CommonUser extends AdminUser implements User{ private $name; function getName(){ echo "my name is ",$this->name," "; } function setName($name=""){//实现接口的方法如果传变量必须给一个默认值 $this->name=$name; } public function Age($age){ echo "我的年龄是:".$age; } } $lisi = new CommonUser(); $lisi->setName('lisi'); $lisi->getName(); $lisi->age(25);接口的继承一个接口可以继承自另外的接口。要注意只有接口和接口之间使用继承关键字extends。一个接口继承其它接口时候，直接继承父接口的静态常量属性和抽象方法。类实现接口必须实现其抽象方法，使用实现关键字implements。php中的类是单继承，但是接口很特殊。一个接口可以继承自多个接口。抽象类实现接口，可以不实现其中的抽象方法，而将抽象方法的实现交付给具体能被实例化的类去处理。<?php interface User{ function getName(); function setName(); } interface demo{ function demo1(); } //接口的多重继承 interface CommonUser extends User,demo{ function text(); } //类继承接口是必须实现方法的 class Test implements CommonUser{ function getName(){ } function setName(){ } function text(){ } function demo1(){ } } abstract class test1 implements CommonUser{ //抽象类继承接口是可以不实现的 }
php面向对象编程（十）魔术方法和自动引入类机制
魔术方法（魔术函数）类内部使用的方法__construct() :实例化对象时被调用；__destuct()：当删除一个对象或者对象销毁前是被执行；__get()：调用对象不存在的属性或没有声明的属性值被调用；__set()：给一个未定义的属性或没有声明的属性赋值时调用此方法；__call()：调用对象不存在方法时被调用；这里的没有声明包括当使用对象调用时，访问控制修饰符为proteced或private的属性（即没有权限访问的属性）。<?php class Human { //定义属性 public $name = "Jack"; //姓名属性 public $sex = "female"; //性别属性 public $age = 23; //年龄属性 var $addr; //通讯属性 PHP4里面声明属性 public $data = array(); //定义data属性 用来存放数据 //定义方法 public function reading() { } public function talking() { echo "Hellow world! "; } //PHPdocuments function html() {//how to make love } /** * 魔术方法 当满足特定场景时 自动调用的方法 * @param string $name 要设置的未定义属性名称 * @param mixed $value 设置的值 */ public function __set($name, $value) { // echo "你正在尝试给一个未定义的属性{$name}赋值！{$value}"; //将未定义的属性和值 以键值对 存入data属性中 在类的内部使用$this指代当前调用对象 访问属性和方法 $this->data[$name] = $value; } //读取一个未定义的属性时 自动调用的魔术方法 public function __get($name) { // echo "你正在读取未定义的属性{$name}"; echo $this->data[$name]; // return $this->data[$name]; } //访问未定义的方法时 自动调用的魔术方法 public function __call($name, $arguments) { // echo $name; // var_dump($arguments); echo "你调用了一个未定义的方法：{$name},并且携带参数："; var_dump($arguments); } } //实例化一个对象 $Jack = new Human(); //通过对象去获取属性和方法 通过"->"运算符 . echo $Jack->name."的年龄是：".$Jack->age." "; $Jack->talking(); //给一个未定义的属性赋值时 会调用魔术方法 __set(); $Jack->from = "USA"; var_dump($Jack->data); echo $Jack->from." "; //访问未定义的方法 $Jack->abc("1", "a");__callStatic()__callStatic()，用静态方式中调用一个不可访问方法时调用class Person { function say() { echo "Hello, world! "; } /** * 声明此方法用来处理调用对象中不存在的方法 */ public static function __callStatic($funName, $arguments) { echo "你所调用的静态方法：" . $funName . "(参数：" ; // 输出调用不存在的方法名 print_r($arguments); // 输出调用不存在的方法时的参数列表 echo ")不存在！ \n"; // 结束换行 } } $Person = new Person(); $Person::run("teacher"); // 调用对象中不存在的方法，则自动调用了对象中的__call()方法 $Person::eat("小明", "苹果"); $Person->say();_toString()_toString():打印一个对象时被调用，比如echo $obj ，print($obj) 。__toString()，类被当成字符串时的回应方法此方法必须返回一个字符串，否则报错或500。不能在 __toString() 方法中抛出异常。这么做会导致致命错误。<?php class Person { public $sex; public $name; public $age; public function __construct($name="", $age=25, $sex='男') { $this->name = $name; $this->age = $age; $this->sex = $sex; } public function __toString() { return 'go go go'; } } $person = new Person('小明'); // 初始赋值 echo $person;__clone()__clone():克隆对象时被调用，比如$t = new Test()，$tt = clone $t;在单例模式中已经介绍__sleep()__sleep() :如过存在此方法serialize之前被调用，此功能可以用于清理对象，并返回一个包含对象中所有应被序列化的变量名称的数组。注意：[scode type="red"]__sleep() 不能返回父类的私有成员的名字。这样做会产生一个 E_NOTICE 级别的错误。可以用 Serializable 接口来替代。作用：__sleep() 方法常用于提交未提交的数据，或类似的清理操作。同时，如果有一些很大的对象，但不需要全部保存，这个功能就很好用。[/scode]__wakeup()__wakeup()：unserialize之前被调用，__wakeup() 经常用在反序列化操作中，例如重新建立数据库连接，或执行其它初始化操作。但不需要全部保存，这个功能就很好用。与serialize相反，unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在，则会先调用 __wakeup 方法，预先准备对象需要的资源。<?php class Person { public $sex; public $name; public $age; public function __construct($name="", $age=25, $sex='男') { $this->name = $name; $this->age = $age; $this->sex = $sex; } public function __sleep() { echo "当在类外部使用serialize()时会调用这里的__sleep()方法 "; $this->name = base64_encode($this->name); return array('name', 'age'); // 这里必须返回一个数值，里边的元素表示返回的属性名称 } public function __wakeup() { echo "当在类外部使用unserialize()时会调用这里的__wakeup()方法 "; $this->name = 2; $this->sex = '男'; // 这里不需要返回数组 } } $person = new Person('小明'); // 初始赋值 var_dump(serialize($person)); var_dump(unserialize(serialize($person)));__isset和__unset()__isset($property)：当对一个未定义的属性或者没有生命的属性调用isset()函数时调用此方法，如 isset($c->name)；__unset():当对一个未定义的属性或没有声明的属性调用unset()函数时调用此方法，如：unset($c->name);这里的没有声明包括当使用对象调用时，访问控制修饰符为proteced或private的属性（即没有权限访问的属性）。<?php class Human { public $name; private $sex; public function __isset($name) { echo "__isset被调用了:属性$name "; } function __unset($name) { echo "__unset被调用了:属性$name "; } } //实例化对象 $test = new Human(); isset($test->sex);//调用私有属性 被自动调用了 isset($test->age);//调用未定义的属性 被自动调用了 echo " "; unset($test->sex);//删除私有属性 被自动调用了 unset($test->age);//输出未定义的属性 被自动调用了__invoke()__invoke()，调用函数的方式调用一个对象时的回应方法<?php class Person { public $sex; public $name; public $age; public function __construct($name="", $age=25, $sex='男') { $this->name = $name; $this->age = $age; $this->sex = $sex; } public function __invoke() { echo '这可是一个对象哦'; } } $person = new Person('小明'); // 初始赋值 $person();__set_state()__set_state()，调用var_export()导出类时，此静态方法会被调用。<?php class Person { protected $sex; public $name; private $age; public function __construct($name="", $age=25, $sex='男') { $this->name = $name; $this->age = $age; $this->sex = $sex; } public function t(){} public static function __set_state($an_array){ $a = new self(); $a->name = $an_array['name']; return $a; } } $person = new Person('小明'); // 初始赋值 $person->name = '小红'; $a=var_export($person,true); var_dump($a);_debugInfo()_debugInfo()，打印所需调试信息、自动调用 PHP5.6以后出的魔术方法<?php class C { private $prop; public function __construct($val) { $this->prop = $val; } public function __debugInfo() { return [ 'propSquared' => $this->prop ** 2, ]; } } //这里的 `**` 是乘方的意思，也是在PHP5.6.0及其以上才可以使用，详情请查看PHP手册 var_dump(new C(42));类外部使用的方法__autoload()：实例化一个对象时，如果对应的类不存在，在该方法被调用。类的自动加载在系统开发过程中，不可能把所有的类都写在一个PHP文件中，当在一个PHP文件中需要调用另一个文件中声明的类时，就需要通过include或者require把这个文件引入。在类文件众多的项目中，要将所有类的文件都逐个引入，是一个很让人头痛的工作。那么我们能不能再用到什么类的时候，在把这个类所在的PHP文件引入呢？__autoload()魔术方法实现在PHP5中，可以定义个__autoload函数，它会在试图使用尚未被定义的类时自动调用，通过调用此函数，脚本引擎在PHP出错失败前有了最后一个机会加载所需的类。__autoload($className)函数接受的一个参数，就是你想加载类的类名，所以你做项目时，在组织定义类的文件名时，需要按照一定的规则，最好以类名为中心，也可以加上同意的前缀或后缀形成文件名。set_include_path(),是在脚本里动态地对PHP.ini中include_path进行修改的。动态设置包含目录(预定义include和require的路径范围)。加载lib目录下的类set_include_path(get_include_path().PATH_SEPAPATOR."lib/");<?php function __autoload($class){ var_dump($class); include $class.".php"; } //set_include_path(get_include_path().PATH_SEPARATOR."lib/"); $a=new a(); $a->say(); $b= new b(); $b->say();其实这个__autuload 并不是最好的选择，用spl_autoload_register()函数spl_autoload_register()spl_autoload_register()此函数的功能就是把函数注册至spl的autoload函数栈中,并移除系统默认的__autolad()函数。spl_autoload_register()可以调用多次。它实际上创建了autoload函数的队列,按定义时的顺序逐个执行。而__autoload()只可以定义一次<?php class test { public static function loadprint( $class ) { $file = $class . '.class.php'; if (is_file($file)) { require_once($file); } } } spl_autoload_register( array('test','loadprint') ); //另一种写法：spl_autoload_register( "test::loadprint" ); $obj = new PRINTIT(); $obj->doPrint();?>printit.class.php文件<?php class PRINTIT { function doPrint() { echo 'hello world2'; } } ?>spl_autoload_register()多次调用示例<?php set_include_path(get_include_path().PATH_SEPARATOR."lib/"); function __autoload($class){ echo "__autoload".$class," "; include $class.".php"; } function __myautoload($class){ echo "__myautoload".$class," "; include $class.".php"; } function __toload($class){ echo "__toload中".$class," "; } spl_autoload_register("__myautoload"); spl_autoload_register("__toload");//这个函数是多次执行的 // 如果上一个函数没找到 会执行下一个spl的注册函数进行查找 会进入__toload //以 “栈 ”的形式进行查找 // $a=new a(); $a->say(); $b= new b(); $b->say(); $c= new c(); //new 一个没有的类 对象测试 spl_autoload_regiter的测试结果命名空间引入类的原理在当前文件夹在新建dong然后在里面新建个Text.php<?php namespace dongge; use dong\Text; use function var_dump; class test { public static function loadprint( $class ) { $file = $class . '.php'; $file=str_replace("\\",'/',$file); if(is_file($file)){ include $file; } } } //另一种写法： spl_autoload_register( array('test','loadprint') ,true,true); spl_autoload_register( "dongge\\test::loadprint" ); $obj = new Text(); $obj->doPrint();dong文件夹的Text.php<?php namespace dong; class Text{ function doPrint() { echo 'hello world2'; } } ?>
延迟绑定static::在PHP5.3中加入了一个新特性(后期静态绑定),该特性最明显的标志就是新关键字static,用法类似于self。“后期静态绑定”的意思是说,static::不在被解析为定义当前方法所在的类,而是在实际运行时计算的。因此也称之为执行期绑定。比如当一个子类继承了父类的静态属性和方法的时候,它的值并不能改变,有是时不希望看到这种情况。<?php class Person{ static $name="张三"; const AGE=22; public static function intro(){ echo "我的名字是 ",self::$name," "; echo "我的年龄是".self::AGE," "; } public static function intro1(){ echo "我的名字是 ",static::$name," "; echo "我的年龄是".static::AGE," "; } } class stu extends Person{ static $name ="王五"; //如果我想让继承的intro方法 输出的是 "王五" 必须重写覆盖。 //那么有没有一种方法可以 在子类里面运算子类的属性呢？ //就用 static::$age延迟绑定 也就是执行期绑定 实际运算的时候计算 } Person::intro(); echo " "; Stu::intro(); echo " "; Stu::intro1();
const数据语法结构 define属于函数const的使用语法:const 常量标识符 = 常量值;const关键字一般在类的声明里定义常量时使用；PHP5.3以后，可以使用const关键字在类定义的外部定义常亮const定义的常量默认为大小写敏感，通常常量标识符总是大写的。开发中尽量使用大写<?php const TEST ='test'; //等同于 define('TEST','test'); class test{ const NAME = '李四'; const name ='张三'; public function d(){ echo self::NAME," "; //name属性 echo self::name," "; //区分大小写 echo TEST," "; } } $t= new test(); $t->d(); echo test::NAME; //外部访问类常量const与definde的区别const一般用于类成员变量的定义，一经定义，不可修改。define不可用于类成员变量的定义，一般用于定义全局常量。const不能在条件语句中定义常量。const采用一个普通的常量名称，define可以采用表达式作为名称。const只能接受静态的标量，而define可以采用任何表达式。const定义的常量时大小写敏感,而define可以通过第三个参数(为true表示大小写不敏感)来指定大小写是否敏感<?php const TEST1 ='test1'; //等同于 define('TEST','test'); define('TEST','test'); class test{ // define('T1',1); //类的内部不能使用define定义常量 public function tt() { echo TEST1," "; echo TEST," "; } } $t = new test(); $t->tt(); $i=10; if($i<15){ define('AGE',12); // const CAGE =12; //const声明的常量不能出现在逻辑语句中 } echo AGE," "; //echo CAGE," "; $n = "tt".rand(1111,9999); define($n,19); //define可以用表达式$n echo constant($n); //const $n =100; //const 是不能使用表达式最为常量名称 echo " "; const TT = 2+"2"; echo TT," "; //在PHP 老版本是不支持 const值是表达式的 PHP7是可以支持的 define('DD',100+1,true); echo DD," \n",dd; //define第三个值为true的时候对大小写不敏感traitsPHP是支持单继承的。在开发这种，经常会碰见一个问题，就是这个类的方法或者属性在其他类里经常用到。但如果每个类都声明了这个属性或方法、会造成不需要的代码沉余、类也变得不是单一职责了。自PHP5.4.0起PHP实现了代码复用的一个方法，称为traits。代码案例<?php trait tDebug { //定义一个打印这个类的方法 public function dumpObject() { $class = get_class($this); $attributes = get_object_vars($this); $methods = get_class_methods($this); echo " $class 对象的信息 "; echo ' 属性 '; foreach ($attributes as $k => $v) { echo " $k: $v "; } echo ' '; echo ' 方法 '; foreach ($methods as $v) { echo " $v "; } echo ' '; } //如果引用trait的类，的方法和trait类方法相同、trait的会被覆盖 function setSize($w = 0, $h = 0) { echo "宽$w","高$h"; } function trait_setSize($w = 0, $h = 0) { echo "宽$w","高$h"; } } //定义一个矩形类 class Rectangle { use tDebug; public $width = 0; public $height = 0; function __construct($w = 0, $h = 0) { $this->width = $w; $this->height = $h; } function setSize($w = 0, $h = 0) { $this->width = $w; $this->height = $h; } function getArea() { return ($this->width * $this->height); } function getPerimeter() { return ( ($this->width + $this->height) * 2 ); } function isSquare() { if ($this->width == $this->height) { return true; } else { return false; } } } $r = new Rectangle(42, 37); $r->dumpObject();