用英雄联盟的方式讲解JavaScript设计模式!

简介

在JavaScript里,构造函数通常是认为用来实现实例的特殊的构造函数。通过new关键字来调用定义的构造函数,你可以告诉JavaScript你要创建一个新对象并且新对象的成员声明都是构造函数里定义的。在构造函数内部,this关键字引用的是新创建的对象。

作为一个老联盟fans,一定要亲手实现一下设计模式也可以融会贯通。

现在打算创建一个英雄联盟对象,需要地图,英雄,士兵,野怪,还有开始游戏的按钮。

  •  
function LOL(maps, heros, soldier, monster) {    this.maps = maps    this.heros = heros    this.soldier = soldier    this.monster = monster    this.start = function() {        return '地图:' + this.maps + '\n对战英雄:' + this.heros.join() + '\n小兵类型:' + this.soldier + '\n野怪:' + this.monster + '\n'    }}

var game1 = new LOL('召唤师峡谷', ['影流之主', '诡术妖姬'], '超级兵', '红buff')var game2 = new LOL('大乱斗', ['影流之主', '诡术妖姬'], '超级兵', '红buff')console.log(game1.start())console.log(game2.start())

这样写代码,每局游戏需要重新创建一个英雄联盟实例,

这样使用构造器,有多少个game就需要多少个start函数方法,如果共用一个start方法,可以节约很多内存

  •  
function LOL(maps, heros, soldier, monster) {    this.maps = maps    this.heros = heros    this.soldier = soldier    this.monster = monster}LOL.prototype.start = function() {     return '地图:' + this.maps + '\n对战英雄:' + this.heros.join() + '\n小兵类型:' + this.soldier + '\n野怪:' + this.monster + '\n'}

var game1 = new LOL('召唤师峡谷', ['影流之主', '诡术妖姬'], '超级兵', '红buff')var game2 = new LOL('大乱斗', ['影流之主', '诡术妖姬'], '超级兵', '红buff')
console.log(game1.start())console.log(game2.start())

如果让start方法变成大家通用的就好了,因此把LOL.prototype.start改写,这样所以的LOL实例就可以共用一个方法,从原型链上继承即可

上面的方式可以节省内存,start实例函数可以在所有LOL对象的实例中使用

如果不使用new 也可以有其他方式创建对象​​​​​​​

function LOL(maps, heros, soldier, monster) {    this.maps = maps    this.heros = heros    this.soldier = soldier    this.monster = monster    this.start = function() {        return '地图:' + this.maps + '\n对战英雄:' + this.heros.join() + '\n小兵类型:' + this.soldier + '\n野怪:' + this.monster + '\n'    }}

var game3 = new Object();LOL.call(game3, "扭曲丛林", ['影流之主', '剑圣'], '远程兵', '大龙');console.log(game3.start())
//也可以不使用new ,通过call方法在game3的作用域调用LOL

这种方式虽然可以创建新的构造函数,但却不能继承LOL原型上的函数

如果直接运行LOL()函数(不使用new的情况下),由于this指向的是window对象,因此start方法会变成window.start()

如果强制要求函数使用new 方法也可以如下创建:

function LOL(maps, heros, soldier, monster) {

    if (!(this instanceof LOL)) {        return new LOL(maps, heros, soldier, monster);    }    this.maps = maps    this.heros = heros    this.soldier = soldier    this.monster = monster}

通过判断this的instanceof,就可以知道究竟是来自new方法,还是说是直接调用。如果是直接调用的话,判断条件为true,还是会return一个新的实例。

e.g:​​​​​​​

var s = new String("lol");var n = new Number(101);var b = new Boolean(true);// s n b返回的是实例对象//String{//  0: 'l',//  1: 'o',//  2: 'l'//  }
//可以直接给变量赋值,或者不加new 关键词

外观模式

简介

外观模式最大的体现其实就是入口,比如init()函数,把一些内部的函数都放在这个门面之下,只需要调用这个门面函数,其他乱七八糟的功能都可以实现。

现在有一个英雄,叫做亚索,我希望给他一些配置,比如技能,衣服

function YaSuo() {}function Qskill (hero) {    hero.prototype.Qskill = function() {        console.log('hasaki!!')    }}function Wskill (hero) {    hero.prototype.Wskill = function() {        console.log('风墙')    }}function Eskill (hero) {    hero.prototype.Eskill = function() {        console.log('快乐')    }}function Rskill (hero) {    hero.prototype.Rskill = function() {        console.log('痛里唉该痛')    }}function Skin (hero, skin) {    hero.prototype.skin = skin}
function CreateYasuo () {    Qskill(YaSuo)    Wskill(YaSuo)    Eskill(YaSuo)    Rskill(YaSuo)    Skin(YaSuo, 'originSkin')    return new YaSuo()}CreateYasuo()// 创建成功 外观模式启动

通过上面的代码,成功创建了一个美丽的亚索。我们最后只需要了解,外观模式不仅简化类中的接口,而且对接口与调用者也进行了解耦。外观模式经常被认为开发者必备,它可以将一些复杂操作封装起来,并创建一个简单的接口用于调用

说白了就是用一个接口封装其它的接口。

外观模式优点就是易使用。缺点则是,当连续使用外观模式创建的接口时,可能会产生性能问题。

e.g.​​​​​​​

var addMyEvent = function (el, ev, fn) {    if (el.addEventListener) {        el.addEventListener(ev, fn, false);    } else if (el.attachEvent) {        el.attachEvent('on' + ev, fn);    } else {        el['on' + ev] = fn;    }};

这是最常见对监听事件的处理,前端必会。其中的addMyEvent就是对其他三个接口的封装,产生了一个门面,也就是外观模式。

代理模式

简介

其实代理模式我们生活中接触的很多了。比如es6中的proxy对象,还有我们平时上网用的VPN。那其实代理模式,就是让一个对象帮助其他的对象来做事。

比如我现在想创建一个英雄,名字叫做卡莉斯塔,俗称滑板鞋。这个英雄有个特点,当她放R技能的时候,会把一个对象拉过来到自己身边几秒,代理这个对象的走路行为,禁止他释放技能等等,那这就要用到代理模式了。​​​​​​​

// 声明走路动作function Walk (hero) { // 代理期间执行的操作    return function() {console.log(hero + ' is walk')}}function Kalisita () { // proxy    this.walk = Walk('Kalisita')    this.Rskill = function(hero) { // 传入要拉取的英雄        this.walk = function() {            Walk('Kalisita')() // 既需要自己走            hero.walk() // 还需要带着人一起走        }    }}function HeroA () { // 被代理走路的英雄    this.walk = Walk('heroA')}
var k = new Kalisita()var a = new HeroA()
k.walk() // Kalisita is walka.walk() // heroA is walk
k.Rskill(a) // k把a的walk事件代理了, 现在k触发walk的同时,也会带着a一起walk哦k.walk()// Kalisita is walk// heroA is walk

代理模式主要用于几点,

  • 要获取本来没有的对对象操作的权限

  • 要获取远程文件,需要代理模式作为跳板

  • 根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象,比如浏览器的渲染的时候先显示问题,而图片可以慢慢显示(就是通过虚拟代理代替了真实的图片,此时虚拟代理保存了真实图片的路径和尺寸。

  • 只当调用真实的对象时,代理处理另外一些事情。例如C#里的垃圾回收,使用对象的时候会有引用次数,如果对象没有引用了,GC就可以回收它了。

详情可以参考《大话设计模式》

而我们前端代码中用的比较多的,应该就是vue.js中对data中数据响应式的代理。vue3中也将使用大量ES6支持的Proxy对象来改写。

e.g.

通过代理,尝试设置私有属性​​​​​​​

function getPrivateProps(obj, filterFunc) {  return new Proxy(obj, {    get(obj, prop) {      if (!filterFunc(prop)) {        let value = Reflect.get(obj, prop);        // 如果是方法, 将this指向修改原对象        if (typeof value === 'function') {          value = value.bind(obj);        }        return value;      }    },    set(obj, prop, value) {      if (filterFunc(prop)) {        throw new TypeError(`Cant set property ${prop}`);      }      return Reflect.set(obj, prop, value);    },    has(obj, prop) {      return filterFunc(prop) ? false : Reflect.has(obj, prop);    },    ownKeys(obj) {      return Reflect.ownKeys(obj).filter(prop => !filterFunc(prop));    },    getOwnPropertyDescriptor(obj, prop) {      return filterFunc(prop) ? undefined : Reflect.getOwnPropertyDescriptor(obj, prop);    }  });}
function propFilter(prop) {  return prop.indexOf('_') === 0;}

总结

本次分享了三种设计模式,分别为构造函数模式,外观模式,代理模式;都是日常开发很常用的设计模式。

其中es6proxy可以访问阮老师博客查看详细api,具体使用也可以借鉴vue源码。代理模式虽然很好用,但也不是任何时候都建议使用,如果你的代码需要获得某些对象的权限,不妨可以使用一下代理模式。在比较简单的场景,可能就没有必要了。

外观模式是最常用的,毕竟每一个js文件总是需要一个入口的。无论是main函数还是init函数,都是起到一个外观包装的作用。当然外观模式并不是必须作为一个文件入口存在,只要能把重复的代码提炼出来,就是一个合理的外观模式。

构造函数模式就不多说了,简单好用。

  • 复制代码是危险的。如果有两段相同的代码,几乎可以说一定是有问题的,因为每次改动,要维护两段代码

  • 尽量减少IO操作,如操作数据库,网络发送,甚至printf ,这些操作比直接操作内存,慢很多倍、

  • 修改Bug时,一定要从最简单的基本的地方开始检查,不要检查到最底层没问题,发现是传入的某个参数是错的。先不要怀疑系统的部分。

  • 设计架构,同时了解细节,有些Bug,调起来可能费时费力,甚至花个二三天,其实当时写的时候,只要稍微注意,就可以轻松避免。避免Bug的代价与找出并修改Bug的代价,实在是差太多了。

  • 把一段长代码,分成很多小函数,便于维护,连自己都不愿看,不愿改的代码,百分百有问题。

  • 写程序时,先把流程搞清楚。把各个流程用的函数写清楚,函数可以留空,这样编程就变成了填空题。

  • 做新功能时,把数据结构的设计,放在较重要的位置

希望这次分享能够既有趣,又让你得到收获,谢谢阅读。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值