从多态到开放封闭原则
假设现在有一个第三方登录接口,他可以调用支付宝,微信,Tik Tok的登录接口,按照多态的原则我们可以这么实现
1 2 3 4 5 6 7 8 9 10 11 12
| enum LoginType { WeChat, TaoBao, TikTok, } function Login(API){ if(API.name === LoginType.WeChat){ }else if(API.name === LoginType.TaoBao){ } }
|
但是这样当我们要新增一个登录接口时,就需要改动Login函数里面的代码,多增加一个if else,这显然不太优雅,也不利于维护,我们希望能将具体的实现放在函数之外,Login函数内部只负责方法的调用,而外部实现具体的方法,这样新增接口将会变得更易于维护和扩展。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| enum LoginType { WeChat, TaoBao, TikTok, }
class WeChatHandle{ handler(){ console.log(LoginType.WeChat) } }
class TaoBaoHandle{ handler(){ console.log(LoginType.TaoBao) } }
class TikTokHandle{ handler(){ console.log(LoginType.TikTok) } }
function Login(API){ API.handler() }
Login(new TikTokHandle())
|
由于JavaScript的特殊性( 鸭子类型 ),可以使用这种方式来实现多态,而在传统的面向对象语言(静态类型语言)中,通常通过继承的方式来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public abstract class API { abstract void handler() }
public class WeChat extends API { public void handler(){ } }
public class TaoBao extends API { public void handler(){ } }
public class Login { public void login(API api){ api.handler() } }
public class Test{ public void main(String args[]){ Login login = new Login(); TaoBao taobao = new TaoBao(); login.login(taobao); } }
|
这种方式在Java中也叫做 向上转型, 即将接受参数的类型定义为需要类以及类的超类(基类),通过继承基类来实现具体的方法。
这也是多态的作用: 通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句 。
封装——封装变化
从设计模式的角度出发,封装在更重要的层面体现为封装变化。
《设计模式》一书曾提到如下文字:
“考虑你的设计中哪些地方可能变化,这种方式与关注会导致重新设计的原因相反。 它不是考虑什么时候会迫使你的设计改变,而是考虑你怎样才能够在不重新设计的情况 下进行改变。这里的关键在于封装发生变化的概念,这是许多设计模式的主题。”
这段文字即是《设计模式》提到的“找到变化并封装之”。
《设计模式》一书中共归纳总结了 23 种设计模式。从意图上区分,这 23种设计模式分别被划分为创建型模式、结构型模式和行为型模式。
拿创建型模式来说,要创建一个对象,是一种抽象行为,而具体创建什么对象则是可以变化的,创建型模式的目的就是 封装创建对象的变化 。而结构型模式 封装的是对象之间的组合关系 。 行为型模式封装的是 对象的行为变化 。
通过封装变化的方式, 把系统中稳定不变的部分和容易变化的部分隔离开来 ,在系统的演变过程中,我们只需要替换那些容易变化的部分,如果这些部分是已经封装好的,替换起来也相对容易。这可以最大程度地保证程序的稳定性和可扩展性。
原型模式和基于原型模式的JavaScript原型系统
- 所有的数据都是对象
- 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它。
- 对象会记住它的原型
- 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型
JavaScript中的数据存储
- 全局变量和被捕获变量(闭包)储存在堆中(全局中的基本类型的值是存在堆中,它的引用地址是存在全局执行上下文的栈内存中)。
-
被捕获变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function test () { let num = 1; let string = '前端'; let bool = true; let obj = { attr1: 1, attr2: '收割机', attr3: true, attr4: 'something' } return function log() { console.log(num, string, bool, obj); } }
|

-
全局变量

- 局部变量:如果是基础类型,那栈中储存的是数据本身。如果是对象类型,那栈中存储的是堆中对象的引用(对象本身储存在堆中)。
至于为什么被捕获的变量也存在堆中,个人觉得是由于在一个作用域中被捕获的变量可能不止一个,需要把这些变量聚合起来,类似于对象,因此也被放在了堆中。
AOP编程
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些 跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| Function.prototype.before = function( beforefn ){ var __self = this; return function(){ beforefn.apply( this, arguments ); return __self.apply( this, arguments ); } }
Function.prototype.after = function( afterfn ){ var __self = this; return function(){ var ret = __self.apply( this, arguments ); afterfn.apply( this, arguments ); return ret; } };
var func = function(){ console.log( 2 ); }; func = func.before(function(){ console.log( 1 ); }).after(function(){ console.log( 3 ); }); func();
|
惰性载入函数
1 2 3 4 5 6 7 8 9 10 11 12
| var addEvent = function (elem, type, handler) { if (window.addEventListener) { addEvent = function (elem, type, handler) { elem.addEventListener(type, handler, false); }; } else if (window.attachEvent) { addEvent = function (elem, type, handler) { elem.attachEvent("on" + type, handler); }; } addEvent(elem, type, handler); };
|