My Little World

learn and share


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于
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函数不存在条件分支,无需再进行判断
My Little World

angularjs directive编写组件小结

发表于 2018-08-30
1
<edit-modal dismiss="dismiss()" config="model.config" after-save="model.afterSave()" aaa="model.hello"></edit-modal>
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
export function editModalDirective() {
return {
restrict: 'E',
template: template,
controller: EditModalCtrl,
bindToController: true,
controllerAs: 'ctrl',
transclude: true,
scope: {
dismiss: '&',
afterSave: '&',
config: '=',
},
link: function(scope, ele, attrs) {
if (scope.ctrl.config.type === 'Strategy') {
if (scope.ctrl.config.type === 'Strategy') {
ele[0].className += 'strategy';
}
}


},
};
}
//在模块编译时注册指令
coreModule.directive('editModal', editModalDirective);
//参考:https://www.cnblogs.com/wangnuo/p/6305742.html

link函数

link函数仅在编译时执行一次,三个参数:scope, ele, attrs

ele

即是当前的指令DOM对象,因此可以在ele上绑定触发事件,

但谨慎在数据上利用事件订阅触发机制进行事件绑定,

因为如果在外层包react组件的情况下,可能会导致数据刷新,但没有事件绑定,从而无法触发事件

注意在获取深层子dom时可能会获取不到

attrs

指ele所在dom上的属性集合,没有在scope属性中声明的属性也能看到

但要注意,属性值就是标签上变量的名,不是标签上变量指代的值

标签上属性赋值变量的值在参数scope中
attrs

scope

link函数参数scope指向当前指令的作用域

参数scope的ctrl属性(controllerAs属性的值)即EditModalCtrl

因为这里配置了controller,bindToController,controllerAs,

所以在scope属性中声明的属性(config)方法(dismiss,after)会被归并到controller中,会以ctrl的属性形式出现,

即如果我想拿到属性scope中声明的config,可以这样:scope.ctrl.config

同时可以在EditModalCtrl中访问获取config,即this.config

另外如果没有在属性scope中声明的属性aaa,但在标签中进行了配置(aaa=”model.hello”),是无效的,ctrl上是不会有aaa属性的,即传递不进来

因此可以在link函数中拿到父作用域传递过来的值,分情况进行不同处理
scope

scope属性

建立子域,声明所在标签的属性名,以及传值方式,controler存在情况下被合并

@

@ 在 directive 中使用 xxx 属性绑定父 scope 中的属性。当改变父 scope 中属性的值的时候,directive 会同步更新值,当改变 directive 的 scope 的属性值时,父 scope 无法同步更新值。使用{{}}引用绑定值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
app.controller("myController", function ($scope) {
$scope.name = "hello world";
}).directive("isolatedDirective", function () {
return {
scope: {
name: "@"
},
template: 'Say:{{name}} <br>改变隔离scope的name:<input type="buttom" value="" ng-model="name" class="ng-pristine ng-valid">'
}
})


<div ng-controller="myController">
<div class="result">
<div>父scope:
<div>Say:{{name}}<br>改变父scope的name:<input type="text" value="" ng-model="name"/></div>
</div>
<div>隔离scope:
<div isolated-directive name="{{name}}"></div>
</div>
<div>隔离scope(不使用{{name}}):
<div isolated-directive name="name"></div>
</div>
</div>

=

= 无论是改变父 scope 还是隔离 scope 里的属性,父 scope 和隔离 scope 都会同时更新属性值,因为它们是双向绑定的关系,使用“”引用绑定值

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
app.controller("myController", function ($scope) {
$scope.user = {
name: 'hello',
id: 1
};
}).directive("isolatedDirective", function () {
return {
scope: {
user: "="
},
template: 'Say:{{user.name}} <br>改变隔离scope的name:<input type="buttom" value="" ng-model="user.name"/>'
}
})

<div ng-controller="myController">
<div>父scope:
<div>Say:{{user.name}}<br>改变父scope的name:<input type="text" value="" ng-model="user.name"/></div>
</div>
<div>隔离scope:
<div isolated-directive user="user"></div>
</div>
<div>隔离scope(使用{{name}}):
<div isolated-directive user="{{user}}"></div>
</div>
</div>

&

& 用来绑定函数,在directive中调用父域中的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 app.controller("myController", function ($scope) {
$scope.value = "hello world";
$scope.click = function () {
$scope.value = Math.random();
};
}).directive("isolatedDirective", function () {
return {
scope: {
action: "&"
},
template: '<input type="button" value="在directive中执行父scope定义的方法" ng-click="action()"/>'
}
})


<div ng-controller="myController">
<div>父scope:
<div>Say:{{value}}</div>
</div>
<div>隔离scope:
<div isolated-directive action="click()"></div>
</div>
</div>

参考: https://blog.coding.net/blog/angularjs-directive-isolate-scope?type=early

transclude

类似于VUE的slot,但不如slot的灵活强,更倾向于定制插入,在固定位置插入

单点嵌入

设置属性 transclude:true

在模板中将 <ng-transclude></ng-transclude>放到要插入的位置

应用时<my-labe><span>1233</span></my-label>;

<span>1233</span> 会自动插入<ng-transclude></ng-transclude>之间

多点嵌入

设置属性 transclude :{模板中flag:页面应用时标签名的驼峰式,}

在模板中制定位置放置flag :<div ng-transclude=”flag”></div>

应用时 <my-label><my-title>123</my-title></my-label>

123 会自动插入<div ng-transclude=”flag”></div>之间

参考:https://segmentfault.com/a/1190000004586636

scope绑定

使用transcludeFn给transclude进来的Dom手动制定scope,

transcludeFn可来自compile,link的参数,或者controller的注入

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
.directive("todo", function(){

return {
restrict:"E",
transclude:"true",
template:"<header>{{header}}</header><div><span>这里是自定义区域</span><content-transclude></content-transclude></div>"
scope:{
header:"@"
},
controller:["$transclude",function(transcludeFn){
this.transcludeFn = transcludeFn;
}]
};
})

.directive("contentTransclude",funtion(){
return {
restrict:"E",
require:"^todo",
link:function(scope,element,attr,todoController){
todoController.transcludeFn(scope.$parent, function(transcludeContent){
element.append(transcludeContent);
});
}
};
})

参考:https://blog.csdn.net/shut1k/article/details/49848637

My Little World

一点V8引擎知识

发表于 2018-06-02

1.V8占用操作系统内存很少,为什么?
类似于体育场办演唱会,演唱会结束后,清完垃圾才能开下一场,
如果场子小,2小时搞定,如果场子大,人多产生垃圾多,清理时间就会变长
在64位操作系统中占用1.4G,32位操作系统中占0.7G
表面原因是JS设计之初是用于给浏览器跑脚本,脚本跑完一次就结束了,不需要长期运行占用内存,以上这些空间足够
实际原因是垃圾回收机制在进行时,会停掉正在运行的整个程序,回收结束后继续运行,
而一次小型垃圾回收几MB会消耗50ms,程序运行就会停止50ms
如果说给的内存太大,那么跑的程序肯定会多来,产生的垃圾也会随之增多,回收时间就变长,结果就会导致程序运行间歇性执行
这肯定是不能让人接受的

2.垃圾回收算法
V8引擎将所占内存分成两块,
一个叫新生代空间,用于存放生命周期较短的变量,比如一个函数里的局部变量,函数运行完即释放
一个叫老生代空间,占引擎内存的90%以上,用于存放生命周期较长的变量,比如全局变量,从程序运行开始就存在

新生代空间垃圾回收算法就是复制
新生代空间又分两部分,一个叫from一个叫to
回收时会将from中活着的变量复制到TO空间,然后将from空间清空,再将from和to空间内容对调
原理是用空间换取时间,有一半新生代空间处于空置状态,而对调减少了清理磁盘空间的过程,从而节省了时间

老生代垃圾回收算法 就是标记删除整理
标记死掉的没有生命周期的变量,边删除标记的变量,边整理碎片(内存空间),将删除导致的磁盘碎片用活着的变量通过移动补齐

3.新生代晋升老生代条件
新生代空间的变量经历过一次复制,即经过一次垃圾回收,而且TO空间已经使用了25%,那么这个变量就会被安排到老生代空间

4.利用node查看内存情况
cmd 敲击node命令
使用process.memoryUsage()查看,返回包含以下属性对象
rss:V8占用的总内存(占用比使用会多一些)
heapTotal:当前程序被分配到的总内存
heapUsed:当前程序使用的内存
external:调用的部分C++内存

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 getMB(){
var mem = process.memoryUsage()
var format = function(bytes){
return (bytes/1024/1024).toFixed(2)+'MB'
}
console.log('heapTotal:'+format(mem.heapTotal)+' heapUsed:'+format(mem.heapUsed))
}
let a = ()=>{
let b = new Array(1024*1024)
return b
}
let b = a()
let d = a()
let e = a()
let f = a()
let g = a()
// let h = a()
// let i = a()
// let j = a()
// let k = a()
// let l = a()
// let m = a()
// let n = a()
// let o = a()
// let p = a()
// let q = a()
getMB()

在cmd中转到文件所在目录,node 包含以上代码的js文件,即可打印内存使用情况

5.优化内存技巧
尽量不要定义全局变量
全局变量记得销毁掉
用匿名自执行函数变全局为局部
尽量避免闭包

在使用立即执行函数实现闭包时,
函数内代码,为同步操作时,占用内存与不使用闭包相同
但如果包含异步操作,则函数内用到的外界变量会一直处于被引用状态
得不到释放从而导致内存增加
解决:使用完释放;避免使用闭包

6.导致内存泄露
滥用缓存

js中导致内存泄露的常见场景
闭包函数
全局变量
对象属性循环使用
Dom节点删除时未解绑事件
Map和Set数据类型的数据属性直接删除,导致指向的数据没有被删除

1…131415…27
YooHannah

YooHannah

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