My Little World

learn and share


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于
My Little World

开发思想

发表于 2018-12-04

源码
git仓库

传统框架存在问题

1.传统 UI操作关注太多细节
DOM API 繁多,需要手动调用,且要经过多步骤进行更新
react 解决办法:始终整体‘刷新’页面 ,无需关心细节,(不管谁是哪部分新增或更改的,根据现在状态整体刷新页面)

2.应用程序状态分散在各处f难以追踪和维护
传统数据模型存在问题:传统 MVC结构复杂,难以扩展和维护
react 解决办法:Flux 单向数据流

创建组件
1.创建静态 UI
2.考虑组件的状态组成 :是由state内部维护还是用外部传进来的props维护
3.考虑组件的交互方式:内部进行了操作,如何告知/把状态暴露到父组件

受控组件VS非受控组件

受控组件: 表单元素状态由使用者维护
组件发生变化,利用onchange将状态给到到父组件(使用者),父组件更新状态,再通过props传递状态给组件,组件重新渲染

非受控组件:表单元素状态 DOM自身维护

创建原则:单一职责原则

1.每个组件只做一件事
2.如果组件变得复杂,那么应该拆分成小组件
拆分成小组件好处:
a.降低复杂度;
b.提高性能,如果一个组件足够大之后,任何一种状态变化,都会引起整个组件刷新,
拆分成小组件后,未涉及到的小组件,没有状态变化,就不会刷新

数据状态管理:DRY(Don’t Repeat Yourself)原则

1.能计算得到的状态就不要单独存储 :
2.组件尽量无状态,所需数据通过 props 获取

JSX

本质:不是模板语言,而是一种语法糖(相当于使用React.createElement创建组件)
优点:
1.声明式创建界面的直观(可以使用自定义和原生标签直接进行创建,方便构建UI)
2.代码动态创建界面的灵活 (节点的创建,属性更改,消失完全可以向写JS一样进行操作)
3.无需学习新的模板语言(JS+JSX特性)
约定:自定义组件以大写字母开头
1.react 认为小写的 tag是原生 dom 节点,如 div
2.大写字母开头为自定义组件
3.JSX 标记可以直接使用属性语法,例如<meau.Item />,使用属性语法,可以不以大写字母开头
JSX防注入攻击
React DOM 在渲染之前默认会 过滤 所有传入的值。它可以确保你的应用不会被注入攻击。
所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(跨站脚本) 攻击

生命周期方法

constructor
js标准类的构造函数
1.用于初始化内部状态,很少使用(初始化工作一般会在其他声明周期内使用)
2.唯一可以直接修改state的地方(可以这么写this.state.xxx = ‘abc’)

getDerivedStateFromProps
react 16.3新引入的API
从属性props初始化内部状态state
1.当 state 需要从 props 初始化时使用
2.尽量不要使用:维护两者状态一致性会增加复杂度
如果state需要从props获得,一般使用props可以通过动态计算得到,如果用state进行单独存储,就有必要时刻维护二者的一致性
维护一致性则增加了复杂度,容易出BUG
3.每次 render都会调用
该方代替原来的componentwillreceiveProps方法
4.典型场景:表单控件获取默认值
初始值来自props,用户输入后为用户输入的值

componentDidMount
1.UI渲染完成后调用
2.只执行一次
3.典型场景:获取外部资源(只需要获取一次资源)

componentWillUnmount
1.组件移除时被调用
2.典型场景:资源释放

getSnapshotBeforeUpdate
1.在页面 render之前调用,state 已更新
2.典型场景:获取 render之前的 DOM 状态

componentDidUpdate
捕获每一次更新
1.每次 UI 更新时被调用
2.典型场景:页面需要根据 props 变化重新获取数据

shouldComponentUpdate
1.组件决定 Virtual DOM是否要重绘
2.一般可以由 PureComponent 自动实现
3.典型场景:细节性能优化

在react 16.3中去掉的方法有
componentWillMount
componentWillReceive
componentWillUpdate

状态更新可能是异步的

1
2
3
4
5
6
7
8
//此代码可能无法更新计数器
this.setState({
counter: this.state.counter + this.props.increment,
});
//更改为以下写法即可
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));

1.算法复杂度O(n):通过广度优先分层比较/遍历实现线性算法

2.虚拟 DOM如何计算 diff
虚拟 DOM的两个假设
a.组件的 DOM结构是相对稳定的
b.类型相同的兄弟节点可以被唯一标识
根结点比较
属性变化及顺序:利用标识调整位置
节点类型发生变化:删除原有结点,构建新类型结点
节点跨层移动:删除原有结点,在应有(新)位置构建相同类型新结点

3.key属性的作用:提高性能

组件复用设计模式

1.高阶组件

传入原始组件,结合生命周期创建一些额外的功能,然后附加到原始组件上,返回增加功能后的原始组件
额外的功能一般都是一些通用的功能,避免在原始组建上增加额外功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from "react";

export default function withTimer(WrappedComponent) {
return class extends React.Component {
state = { time: new Date() };
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
}

componentWillUnmount() {
clearInterval(this.timerID);
}

tick() {
this.setState({
time: new Date()
});
}
render() {
return <WrappedComponent time={this.state.time} {...this.props} />;
}
};
}

2.函数子组件

将子组件以函数返回值形式作为children属性传入原始组件,在原始组件中,在需要显示原始组件的地方执行函数,
或者在合适的生命周期中执行函数拿到多个子组件,再决定在什么位置显示不同的子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//写法一
<Mouse children={mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}/>
//写法二
<Mouse>
{mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}
</Mouse>

//在原始组件应用子组件
class Mouse extends React.Component{
render(){
<div>
{this.props.children({x:50,y:100})}
</div>
}
}

context API

应用场景:全局性的一些字段,如theme的切换

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import React from "react";

const enStrings = {
submit: "Submit",
cancel: "Cancel"
};

const cnStrings = {
submit: "提交",
cancel: "取消"
};
//传入原始数据/默认值,创建context
const LocaleContext = React.createContext(enStrings);

class LocaleProvider extends React.Component {
state = { locale: cnStrings };
toggleLocale = () => {
const locale =
this.state.locale === enStrings
? cnStrings
: enStrings;
this.setState({ locale });
};
render() {
return (
// context的 Provider属性是一个组件,包裹需要用到context数据子组件的组件,其value值为相应在子组件中需要用到的数据
<LocaleContext.Provider value={this.state.locale}>
<button onClick={this.toggleLocale}>
切换语言
</button>
{this.props.children}
</LocaleContext.Provider>
);
}
}

class LocaledButtons extends React.Component {
render() {
return (
//context的 Consumer属性也是一个组件,包裹需要使用context数据的子组件,
<LocaleContext.Consumer>
{locale => (//local 即Context.Provider 的value值
<div>
<button>{locale.cancel}</button>
&nbsp;<button>{locale.submit}</button>
</div>
)}
</LocaleContext.Consumer>
);
}
}

export default () => (
<div>
<LocaleProvider>
<div>
<br />
<LocaledButtons />//使用context数据的组件必须为Provider组件的子组件,
</div>
</LocaleProvider>
<LocaledButtons />//如果子组件在Context.Provider形成组件的外面使用,则只会应用默认数据,不会随Value属性发生变化
</div>
);

为什么需要脚手架

react:进行UI开发
redux:进行状态的管理
react/router:进行路由的管理
babel:把最新的js特性翻译成兼容浏览器的旧的JS语法
webpack:进行打包
eslint:语法检查
因而需要引入以上几个技术栈的几十个个package,不但要了解,还要知道如何让进行相应的配置
在开启一个项目时,配置过程做完一次基本/只要实现一次,就基本可以复用,用来再开启新项目
所以为方便开发,用脚手架实现配置过程

脚手架有:
Create React App 基础(webpack,)
Rekit 开源项目
Codesandbox 在线开发工具
dva 蚂蚁金服的一套脚手架工具

为什么需要打包

1.编译ES6语法特性,编译JSX(保证浏览器可以兼容执行)
2.整合资源,例如图片,Less/Sass,svg
3.优化代码体积(对变量名进行缩短,去除注释,去除空格,去除不必要的变量等等)

webpack
将项目中所有资源进行整合,将每种资源(js,png,sass…)使用插件的方式载入对应的Loader,
loader能够决定自己支持什么样的语法,loader的输出是可以执行的js语言,可以兼容多平台多版本浏览器
可以部署的语言

打包注意事项
1.设置nodejs环境为production
开发环境可能需要检查输入的参数的类型,语法,拼写等问题,但生产环境不需要

2.禁用开发时专用代码,比如logger
打包时可以根据环境判断决定是否执行一定的代码,所以开发用到的代码在打包时不应被打到生产环境,要做区分
3.设置应用根路径
告诉应用到哪里获取地址

My Little World

命令模式

发表于 2018-11-08

应用情景

有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。
此时希望用一种松耦合的方式设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系

主要思想

将命令接收者的具体执行函数存入 命令堆栈,命令发起者需要执行/撤销动作时,对堆栈进行处理
命令接收者可以以闭包形式被执行,也可以作为对象属性被执行到

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
47
48
49
50
51
52
53
54
//闭包形式
var setCommand = function(btn,fn){
btn.onclick = function(){
fn()
}
}
var MenuBar = {
refresh:function(){
console.log('refresh menu')
}
}
var RefreshMenuBarCommand = function(reciver){
return function(){reciver.refresh()}
}
var refreshMenuBarCommand = RefreshMenuBarCommand(Menubar)
setCommand(btn1,refreshMenuBarCommand)

//宏命令,组装一个可以一次性触发多个命令的命令;属性形式,统一使用execute触发
var MacroCommand = function(){
return {
commandsList:[],
add:function(command){
this.commandsList.push(command);
},
execute:function(){
for(var i=0,cammand;command = this.commandsList[i++];){
command.execute();
}
},
undo:function(){
for(var i=0,cammand;command = this.commandsList[i++];){
command.undo();
}
},

}
}
var closeDoorCom ={
execute:function(){
console.log('close door')
},
undo:function(){console.log('open door')}
}
var openTvCom ={
execute:function(){
console.log('open TV')
},
undo:function(){console.log('close Tv')}
}
var macroCommand = MacroCommand()
macroCommand.add(closeDoorCom)
macroCommand.add(openTvCom)
macroCommand.execute()
macroCommand.undo()

与策略区别

策略模式指向问题域更小,所有策略对象目标一致,他们只是达到这个目的的不同手段,内部实现针对‘算法’
命令对象解决的目标更具发散性,还可以完成撤销,排队等功能

My Little World

订阅发布模式

发表于 2018-11-08

主要思想

通过逻辑处理函数/对象event,使事件订阅和发布过程忽略事件的具体处理过程
订阅时只需要给到event要订阅的事件A,和响应函数,
发布时给到event事件A被触发和相关参数,即可执行订阅时的响应函数
event 充当中介者的角色,管理事件响应的存储和执行

1.简单实现

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
let event = {
caches:{},
on:function(name,fn){
if(!this.caches[name]){
this.caches[name] = []
}
this.caches[name].push(fn)
},
emit:function(name,...args){
if(!this.caches[name]){return}
for(let i=0;i<this.caches[name].length;i++){
let fn=this.caches[name][i]
fn(args)
}
},
remove:function(name,fn){
if(this.caches[name].length<1){return}
for(let i=0;i<this.caches[name].length;i++){
let fn1=this.caches[name][i]
if(fn === fn1){
this.caches[name].splice(i,1)
}
}
}
}
let fn1 = (val)=>{console.log(val,1)}
let fn2 = (val)=>{console.log(val,2)}
event.on('aaa',fn1)
event.on('aaa',fn2)
event.emit('aaa','fire!')

2.全局事件冲突

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
var Event = (function(){ 
var global = this,
Event,_default = 'default';
Event = function(){
var _listen,
_trigger,
_remove,
_slice = Array.prototype.slice,
_shift = Array.prototype.shift,
_unshift = Array.prototype.unshift,
namespaceCache = {},
_create,
find,
each = function( ary, fn ){
var ret;
for ( var i = 0, l = ary.length; i < l; i++ ){
var n = ary[i];
ret = fn.call( n, i, n);
}
return ret;
};

_listen = function( key, fn, cache ){
if ( !cache[ key ] ){
cache[ key ] = [];
}
cache[key].push( fn );
};

_remove = function( key, cache ,fn){
if ( cache[ key ] ){
if( fn ){
for( var i = cache[ key ].length; i >= 0; i-- ){
if( cache[ key ][i] === fn ){
cache[ key ].splice( i, 1 );
}
}
}else{
cache[ key ] = [];
}
}
};

_trigger = function(){
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret,
stack = cache[ key ];

if ( !stack || !stack.length ){
return;
}

return each( stack, function(){
return this.apply( _self, args );
});
};

_create = function( namespace ){
var namespace = namespace || _default;
var cache = {},
offlineStack = [], // 离线事件
ret = {
listen: function( key, fn, last ){
_listen( key, fn, cache );
if ( offlineStack === null ){
return;
}
if ( last === 'last' ){
offlineStack.length && offlineStack.pop()();
}else{
each( offlineStack, function(){
this();
});
}

offlineStack = null;
},

one: function( key, fn, last ){
_remove( key, cache );
this.listen( key, fn ,last );
},

remove: function( key, fn ){
_remove( key, cache ,fn);
},
trigger: function(){
var fn,
args,
_self = this;

_unshift.call( arguments, cache );
args = arguments;
fn = function(){
return _trigger.apply( _self, args );
};

if ( offlineStack ){
return offlineStack.push( fn );
}
return fn();
}
};
return namespace ?( namespaceCache[ namespace ] ? namespaceCache[ namespace ] : namespaceCache[ namespace ] = ret ): ret;
};

return {
create: _create,
one: function( key,fn, last ){
var event = this.create( );
event.one( key,fn,last );
},
remove: function( key,fn ){
var event = this.create( );
event.remove( key,fn );
},
listen: function( key, fn, last ){
var event = this.create( );
event.listen( key, fn, last );
},
trigger: function(){
var event = this.create( );
event.trigger.apply( this, arguments );
}
};
}();

return Event;

})();

My Little World

AOP(切面)编程/装饰者模式

发表于 2018-11-07

定义

在不改变对象自身的基础上,在程序运行期间给对象动态的添加职责

主要思想

在每次要执行具体函数B时,先执行A,在每次执行完B之后都执行C,
以此类推,有必要的话,在C执行完再执行D,从而形成执行链

具体实现

先用一个temp函数包住AB,再用一个final函数包住temp和C,这样,在执行final函数时,会按顺序A-B-C执行

1.使用普通函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let before=(fn,beforeFn)=>{
return (...args)=>{
beforeFn(args)
return fn(args)
}
}
let after = (fn,afterFn)=>{
return (...args)=>{
let ret = fn(args)
afterFn(args)
return ret
}
}

let a = ()=>{console.log(2)}
a = before(a,()=>{console.log(1)})
a = after(a,()=>{console.log(3)})
a() //1,2,3

2.改造Function对象,可以实现链式添加职责

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Function.prototype.before = function(beforeFn){
return ()=>{
beforeFn(arguments);
return this(arguments);
}
}
Function.prototype.after = function(afterFn){
return ()=>{
let ret = this(arguments);
afterFn(arguments);
return ret;
}
}
var func = function (){
console.log(2);
return 15}
func = func.before(()=>{console.log(1)}).after(()=>{console.log(3)})
func() //1,2,3 返回15

应用

1.数据统计上报
将点击响应事件包含的上报动作隔离出来,以after的顺序,装饰到点击事件响应函数上

2.动态改变函数参数
组装ajax请求参数时,在请求函数前装饰before函数,进行不同参数参加处理

3.插件式表单验证
在submit函数前装饰before函数进行不同条件验证

区分代理模式

代理模式的意图是当直接访问本体不方便或者不符合需要时,为这个本体提供一个替代者,
本体定义了关键功能,而代理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情
通常只有一层代理-本体的引用

AOP的作用用于对对象加入行为,一开始不能确定对象的全部功能时,
经常会形成一条装饰链

My Little World

迭代器模式

发表于 2018-11-06

定义

提供一种方法顺序访问一个聚合对象种的各个元素,而又不需要暴露该对象的内部表示
把迭代过程从业务逻辑中分离出来,不用关心对象的内部构造,可以按顺序访问其中的每个元素

用途/场景

查找可用组件,进行数据处理

主要思想

内部迭代器
函数内部定义好迭代规则,完全接手整个迭代过程,外部只需要一次初始调用

外部迭代器
需要调用进行下一轮迭代的函数,可以手工控制迭代的过程或者顺序

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
//使用内部迭代器实现
var iteratorInner=(arr,fn)=>{
for(let i=0;i<arr.length;i++){
fn(arr,i)
}
return arr
}
console.log(iteratorInner([1,2,3],(arr,i)=>{arr[i]++})) //[2,3,4]

//使用外部迭代器
let iteratorOuter = (arr,fn)=>{
let count = 0;
let nextFn =()=>{
if(count <arr.length){
fn(arr,count)
}
return {
result:arr,
count:count === arr.length ? count:++count,,
done:count === arr.length
}
}
return {
next:nextFn,
isdone:count === arr.length
}
}
var it = iteratorOuter([1,2,3],(arr,i)=>{
arr[i]++
})
console.log(it.next())
//count: 1
//done: false
//result: (3) [2, 3, 4]
console.log(it.next())
//count: 2
//done: false
//result: (3) [2, 3, 4]
console.log(it.next())
//count: 3
//done: true
//result: (3) [2, 3, 4]
My Little World

适配器模式

发表于 2018-11-06

定义
解决俩个软件实体间接口不兼容的问题,使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体,可以一起工作

用途/场景
1.调用第三方(地图/支付)接口时,不同平台提供的相同功能的接口函数名不同,或者调用时需要进行的操作过程不同,
将这些过程封装/分类,通过适配器决定如何使用

2.处理数据,将不符合使用要求的数据结构转换成预期的样子

主要思想
将处理过程封装在适配器函数中,直接在适配器函数中进行处理,或者让适配器返回处理后的可适用结果
调用适配器传入需要处理的数据/条件,适配器进行处理或返回处理结果

注意事项
那些繁琐的处理代码始终是存在的,只不过在写业务的时候看不到它罢了

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
1.转换数据结构
//将如下数据结构
var guangdongCity = [{
name: 'shenzhen',
id: 11,
}, {
name: 'guangzhou',
id: 12,
}];
//转换成如下数据结构
var guangdongCity = { shenzhen: 11, guangzhou: 12};

var adapter = (data)=>{
let temp = {};
for(let i=0;i<data.length;i++){
temp[data[i].name] = data[i].id
}
return temp
}
console.log(adapter(guangdongCity))

2.SDK适配例子1
var googleMap = {
show: function(){
console.log( '开始渲染谷歌地图' );
}
};
var baiduMap = {
display: function(){
console.log( '开始渲染百度地图' );
}
};
var baiduMapAdapter = {
show: function(){
return baiduMap.display();
}
};
var renderMap = function( map ){
if ( map.show instanceof Function ){
map.show();
}
};
renderMap( googleMap ); // 输出:开始渲染谷歌地图
renderMap( baiduMapAdapter ); // 输出:开始渲染百度地图
3.SDK适配例子2
function pay ({
price,
goodsId
}) {
return new Promise((resolve, reject) => {
const config = {}

switch (platform) {
case 'wechat':
// 微信的处理逻辑
config.price = price
config.goodsId = goodsId
config.appId = 'XXX'
config.secretKey = 'XXX'
wx.pay(config).then((err, data) => {
if (err) return reject(err)

resolve(data)
})
break
case 'QQ':
// QQ的处理逻辑
config.price = price * 100
config.gid = goodsId
config.appId = 'XXX'
config.secretKey = 'XXX'
config.success = resolve
config.error = reject
qq.pay(config)
break
case 'alipay':
// 支付宝的处理逻辑
config.payment = price
config.id = goodsId
config.token = 'XXX'
alipay.pay(config, resolve, reject)
break
}
})
}
// run anywhere
await pay({
price: 10,
goodsId: 1
})

对于SDK提供方,仅仅需要知道自己所需要的一些参数,然后按照自己的方式进行数据返回。

对于SDK调用方,仅仅需要我们约定好的通用的参数,以及按照约定的方式进行监听回调处理。

整合多个第三方SDK的任务交由适配器来做,然后将适配器的代码压缩,混淆,放在一个看不见的角落里去,这样的代码逻辑就会变得很清晰了
参考资料:适配器在JavaScript中的体现

My Little World

代理模式

发表于 2018-11-06

分类

保护代理
代理B可以帮助A过滤掉一些请求,可以直接在代理B中拒绝掉
用于控制不同权限的对象对目标对象的访问

虚拟代理
把一些开销很大的对象,延迟到真正需要它的时候才去创建

单一职责原则

就一个类(通常也包括对象和函数)而言,应该仅有一个引起它变化的原因。

主要思想

A想调用C,要经过B,B在中间做C的代理,用于判断什么时候可以执行C
比如多次调用缓存起来再一起执行C;传入相同参数只计算一次,将结果保存,以后传入相同参数直接返回结果,不再计算

用途/实现

1.虚拟代理合并 HTTP请求

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
//可以通过一个代理函数 proxySynchronousFile 来收集一段时间之内的请求, 后一次性发送给服务器。
var synchronousFile = function( id ){
console.log( '开始同步文件,id 为: ' + id );
};

var proxySynchronousFile = (function(){
var cache = [], // 保存一段时间内需要同步的 ID
timer; // 定时器
return function( id ){
cache.push( id ); //每次进来收集
if ( timer ){
// 保证不会覆盖已经启动的定时器
return;
}
timer = setTimeout(function(){
synchronousFile( cache.join( ',' ) ); // 2 秒后向本体发送需要同步的 ID 集合 到时间统一处理 clearTimeout( timer ); // 清空定时器
timer = null;
cache.length = 0; // 清空 ID 集合
}, 2000 );
}
})();
var checkbox = document.getElementsByTagName( 'input' );
for ( var i = 0, c; c = checkbox[ i++ ]; ){
c.onclick = function(){
if ( this.checked === true ){
proxySynchronousFile( this.id );
}
}
};

2.缓存代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var mult = function(){     
console.log( '开始计算乘积' );
var a = 1;
for ( var i = 0, l = arguments.length; i < l; i++ ){
a = a * arguments[i];
}
return a;
};
// 创建缓存代理的工厂
var createProxyFactory = function( fn ){
var cache = {};
return function(){
var args = Array.prototype.join.call( arguments, ',' );
if ( args in cache ){
return cache[ args ];
}
return cache[ args ] = fn.apply( this, arguments );
}
};
var proxyMult = createProxyFactory( mult ),
proxyMult( 1, 2, 3, 4 ); // 输出:24
proxyMult( 1, 2, 3, 4 ); // 输出:24

扩展:用于ajax异步请求数据,下次请求相同数据时,是否可以取消请求,直接用之前缓存的数据,适用固定数据

My Little World

策略模式

发表于 2018-11-06

主要思想

定义一系列的算法,把它们一个个封装起来,通过逻辑函数使他们可以相互替换
避免if-else多分支判断

具体实现

定义一些系列算法(具体处理过程),把他们各自封装成策略类,算法被封装在策略类内部方法里。在客户对Context(如何调用算法的处理函数)发起请求时,Context总是把请求委托给这些策略对象中间的某一个进行计算

注意点

编写代理函数时,注意如何调用,让算法得到执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let strategies = { //算法集合
"S": function( salary ){
return salary * 4;
},
"A": function( salary ){
return salary * 3;
},
"B": function( salary ){
return salary * 2;
}
};
var calculateBonus = function( level, salary ){ //Context,委托函数,负责调用哪个算法
return strategies[ level ]( salary );
};

console.log( calculateBonus( 'S', 20000 ) );//80000
console.log( calculateBonus( 'A', 10000 ) );//30000

变形:
var S = function( salary ){ return salary * 4; };
var A = function( salary ){ return salary * 3; };
var B = function( salary ){ return salary * 2; };
var calculateBonus = function( func, salary ){ return func( salary ); };
calculateBonus( S, 10000 )

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//具体策略方法
var strategies = {
isNonEmpty: function( value, errorMsg ){
if ( value === '' ){
return errorMsg;
}
},
minLength: function( value, length, errorMsg ){
if ( value.length < length ){
return errorMsg;
}
},
isMobile: function( value, errorMsg ){
if ( !/(^1[3|5|8][0-9]{9}$)/.test( value ) ){
return errorMsg;
}
}
};
//执行代理 Context
var Validator = function(){
this.cache = [];
};

Validator.prototype.add = function( dom, rules ){
var self = this;
for ( var i = 0, rule; rule = rules[ i++ ]; ){
(function( rule ){
var strategyAry = rule.strategy.split( ':' );
var errorMsg = rule.errorMsg;
self.cache.push(function(){
var strategy = strategyAry.shift();
strategyAry.unshift( dom.value );
strategyAry.push( errorMsg ); //strategyAry,传递到具体算法函数参数,这里进行处理
return strategies[ strategy ].apply( dom, strategyAry );
});
})( rule )
}
};
Validator.prototype.start = function(){
for ( var i = 0, validatorFunc; validatorFunc = this.cache[ i++ ]; ){
var errorMsg = validatorFunc();
if ( errorMsg ){
return errorMsg;
}
}
};
//具体应用
var registerForm = document.getElementById( 'registerForm' );
var validataFunc = function(){
var validator = new Validator();
validator.add( registerForm.userName, [{
strategy: 'isNonEmpty',
errorMsg: '用户名不能为空'
}, {
strategy: 'minLength:6',
errorMsg: '用户名长度不能小于 10 位'
}]);
validator.add( registerForm.password, [{
strategy: 'minLength:6',
errorMsg: '密码长度不能小于 6 位'
}]);
validator.add( registerForm.phoneNumber, [{
strategy: 'isMobile',
errorMsg: '手机号码格式不正确'
}]);
var errorMsg = validator.start();
return errorMsg;
}
registerForm.onsubmit = function(){
var errorMsg = validataFunc();
if ( errorMsg ){
alert ( errorMsg );
return false;
}
}
My Little World

单例模式

发表于 2018-11-04

单例模式定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点

主要思想<通用惰性单例>
管理单例逻辑(保证实现单例的过程)和创建过程(具体业务逻辑)分开,创建过程函数作为参数给到管理单例逻辑函数中,
通过调用单例逻辑函数,业务逻辑函数只执行一次

用途
1.创建单一对象
2.利用业务逻辑仅执行一次,进行事件挂载

注意事项
业务逻辑函数一定要return 一个真正

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
var getSingle = function(fn){
var ret;
return function(){
return ret || (ret = fn.apply(this,arguments));
//第一次执行时,会运行函数,返回创建的实例给到ret,
//以后再执行时,ret 即第一次创建的实例直接返回
}
}

//返回单例
var creatObj = function(name){
this.aaaa = {
name:name,
getName:function(){console.log(this)}
}
return this
}
var creatObj1 = getSingle(creatObj)
var obj = creatObj1('aaa')
console.log(obj) //返回window 对象(满足全局可访问),带有aaaa属性,值为字面量对象
obj.aaaa.getName()//{name: "kitty", getName: ƒ}

var obj1 = creatObj1('aaa')
console.log(obj=== obj1) //true

obj1.aaaa.getName() //{name: "kitty", getName: ƒ}

//事件仅挂载一次

var bindEvent = getSingle(function(){
document.getElementById('div1').onclick = function(){
console.log('click')
}
return true //保证只执行一次
})

var render = function(){
console.log('开始渲染');
bindEvent()
}

render()
render()
render()
//渲染多次,div1只挂载一次事件
My Little World

基础知识

发表于 2018-11-03

一些有思想提醒作用的话

把不变的部分隔离出来,把可变的部分封装起来

多态最根本的作用就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句

(每个对象应该做什么,已成为了该对象的一个方法,被安装在对象的内部,每个对象负责它们自己的行为,所以这些对象可以根据同一个消息,有条不紊的分别进行各自的工作 ====> 将行为分布在各个对象中,并让对象各自负责自己的行为<—面向对象设计优点)

对象以方法的形式包含了过程,而闭包则是在过程中以环境的形式包含了数据

小知识点

1.原型继承过程:对象把请求委托给它的构造器的原型

1
2
3
4
5
6
7
8
9
10
var A = function(){};
A.prototype = {name:'seven'};
var B = function(){};
B.color='red';
B.prototype = new A();
B.prototype.color = 'green'
var b = new B();
console.log(b.name,b.color)

//seven green

2.用new 调用构造器时,如果构造器显示返回了一个object类型的对象,那么此次运算结果最终会返回这个对象,而不是this;
若不显示返回任何数据,或者是返回一个非对象类型的数据,则会正常返回this

3.使用Dom 方法时,注意是否会造成this 丢失

1
2
3
4
<script>
let getId = document.getElementById; //原来 getElementById this指向document
getId('DIV1') //现在指向window
</script>

4.使用call/apply时,如果第一个参数传入null,那么函数体内的this会指向window

5.闭包作用:封装变量;延续局部变量寿命
闭包和内存泄露有关系的地方是,使用闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些DOM节点,就比较可能造成内存泄露。因为IE浏览器中,由于BOM和DOM对象是使用C++以COM对象的方式实现的,而COM对象的垃圾收集机制采用引用计数策略,这时,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收。

解决办法是将循环引用中的变量设为null,方便回收。

高阶函数一些例子

一个单例模式

1
2
3
4
5
6
var getSingle = function(fn){
var ret;
return function(){
return ret || (ret = fn.apply(this,arguments));
}
}

AOP(切面编程) 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 Function.prototype.before = function(beforeFn){
return ()=>{
beforeFn.apply(this,arguments);
return this.apply(this,arguments);
}
}
Function.prototype.after = function(afterFn){
return ()=>{
this.apply(this,arguments);
afterFn.apply(this,arguments);
}
}
var func = function (){console.log(2)}
func = func.before(()=>{console.log(1)}).after(()=>{console.log(3)})
func()
//1
//2
//3

部分求值 currying

一个curring的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来,待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值。

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
 var currying = function(fn){
let args = [];
return function(){
if(arguments.length === 0){
return fn.apply(this,args);
}else{
[].push.apply(args,arguments);
return arguments.callee;
}
}
};
var cost = (function(){
var money = 0;
return function (){
for(var i = 0,l=arguments.length;i<l;i++){
money += arguments[i];
}
return money;
}
})();

var cost = currying(cost);
cost(100);
console.log(cost(200))
console.log(cost())

函数节流

多次重复调用,按时间限制调用次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var throttle = function(fn,interval){
var _self = fn,timer,firstTime = true;
return fucntion(){
var args = arguments,_me = this;
if(fiestTime){
_self.apply(_me,args);
return firstTime = false;
}
if(timer){
return false;
}
timer = setTimeout(function(){
clearTimeout(timer);
timer = null;
_self.apply(_me,args);
},interval||500)
}
}

分时函数

大批量数据要执行同一函数,分批次执行任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 var timeChunk = function(ary,fn,count){
var obj,t;
var len = ary.length;
var start = function(){
for(var i = 0;i<Math.min(count||1,ary.length);i++){
var obj = ary.shift();
fn(obj);
}
}
return function(){
t=setInterval(function(){
if(ary.length === 0){
return clearInterval(t)
}
start();
},200)
}
}

惰性加载函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 var addEvent = function(elem,type,handler){
if(window.addEventListener){
addEvent = function(elem,type,handler){
elem.addEventListerner(type,handler,false)
}
}else if(window.attachEvent){
addEvent = function(elem,type,handler){
elem.attachEvent('on'+type,handler)
}
}
addEvent(elem,type,handler);
}
//addEvent 在第一次进入条件分支之后,在函数内部会重写这个函数,重写之后的函数就是我们期望的函数,在下一次进入addEvent
//函数时,addEvent函数不存在条件分支,无需再进行判断
1…131415…27
YooHannah

YooHannah

264 日志
1 分类
23 标签
RSS
© 2025 YooHannah
由 Hexo 强力驱动
主题 - NexT.Pisces