Promise

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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//实现:
/*
首先搭建基本框架: 自定义Promise函数模块,使用立即执行的匿名函数书写模块
*/
(function(window){
/*
构造函数,接收的参数为执行器函数(同步执行)
*/
function Promise(executor){
/*
Promise的属性:
status: 给promise对象指定status属性,初始值为pending
data: 给promise对象指定一个用于存储结果数据的属性
callback: 回调队列,其中每个元素的结构为{onResolved(){}, onRejected(){}}
*/
this.status = 'pending';
this.data = undefined;
this.callback = [];

//这里必须使用箭头函数,因为函数里面的this需要指向Promise,而不是window
const resolve = value => {
//promise的状态只能修改一次
if(this.status!=='pending')
return;
this.status = 'resolved';
this.data = value;
/*
如果有待执行callback函数,立即异步执行所有回调函数。这里有多个回调函数是因为用户可能会为一个promise指定多个then中的回调函数,这些回调函数均需执行。
*/
if(this.callback.length>0){
setTimeout(()=>{
this.callback.forEach(callbackObj=>{
callbackObj.onResolved(value);
})
})
}
}
const reject = reason => {
if(this.status!=='pending')
return;
this.status = 'rejected';
this.data = reason;
if(this.callback.length>0){
setTimeout(()=>{
this.callback.forEach(callbackObj=>{
callbackObj.onRejected(reason);
})
})
}
}
try{
//立即同步执行执行器函数
executor(resolve, reject);
}catch(err){
reject(err);
}
}

/*
then方法指定成功和失败的回调函数,并且返回一个新的promise对象
*/
Promise.prototype.then = function(onResolved, onRejected){
/*
指定回调函数的默认值必须是函数
onResolved向后传递成功的value
onRejected可以实现异常传透
*/
onResolved = typeof onResolved==='function'?onResolved:value=>value;
onRejected = typeof onRejected==='function'?onRejected:reason=>{throw reason};

const that = this;
return new Promise((resolve, reject)=>{
function handle(callback){
try{
const res = callback(that.data);
if(res instanceof Promise)
//result是Promise对象,为了得到result的结果,必须使用then方法
res.then(resolve, reject);
else
resolve(res);
}catch(err){
reject(err);
}
}
if(that.status==='pending'){
/*
若当前状态还是pending状态,将回调函数保存起来。不直接只传onResolved和onRejected是因为此处还需要修改新返回的promise的状态
*/
this.callback.push({
onResolved(){
handle(onResolved);
},
onRejected(){
handle(onRejected);
}
})
}else if(that.status==='resolved'){
//因为回调函数是异步执行的,故此处需要setTimeout
setTimeout(()=>{
/*
1.如果抛出异常,return的promise就失败,reason就是error
2.如果回调函数返回不是promise,return的promise就成功,value就是返回的值
3.如果回调函数返回是promise,return的promise结果就是这个promise
*/
handle(onResolved);
})
}else{
setTimeout(()=>{
handle(onRejected);
})
}
})
}

/*
catch方法指定失败的回调函数,并且返回一个新的promise对象
*/
Promise.prototype.catch = function(onRejected){
return this.then(undefined, onRejected);
}

/*
promise.finally方法用于指定不管promise对象最后状态如何,都会执行的操作.Promise.resolve执行回调,并在then中return结果传递给后面的Promise
*/
Promise.prototype.finally = function(callback){
return this.then(
value => {
return Promise.resolve(callback()).then(()=>value);
},
reason=>{
return Promise.resolve(callback()).then(()=>{throw reason});
}
)
}
Promise.resolve = function(value){
return new Promise((resolve, reject)=>{
if(value instanceof Promise)
value.then(resolve, reject);
else
resolve(value);
})
}
Promise.reject = function(reason){
return new Promise((resolve, reject)=>{
reject(reason);
})
}
Promise.race = function(promises){
return new Promise((resolve, reject)=>{
if(promises.length===0)
return;
promises.forEach(p=>{
Promise.resolve(p).then(
value => {resolve(value)},
reason => {reject(reason)}
)
})
})
}
//返回一个promise,只有当所有promise都成功时才成功,否则就失败
Promise.all = function(promises){
let values = new Array(promises.length);
let count = 0;
return new Promise((resolve, reject)=>{
if(promises.length===0)
resolve([]);
promises.forEach((p, i)=>{
//考虑到p可能是一个值的情况
Promise.resolve(p).then(
value => {
count++;
values[i] = value;
if(count===promises.length)
resolve(values);
},
reason => {
reject(reason);
}
)
})
})
}
Promise.allSettled = function(promises){
let values = new Array(promises.length);
let count = 0;
return new Promise((resolve, reject)=>{
if(promises.length===0)
resolve([]);
promise.forEach((p, i)=>{
Promise.resolve(p).then(
value=>{
count++;
values[i] = {
status: 'resolved',
data: value
};
if(count===promises.length)
resolve(values);
},
reason=>{
count++;
values[i]={
status: 'rejected',
data: reason
};
if(count===promises.length)
//这里也是resolve
resolve(values);
}
)
})
})
}
Promise.any = function(promises){
let count = 0;
return new Promise((resolve, reject)=>{
if(promises.length===0)
resolve([]);
promises.forEach(p=>{
Promise.resolve(p).then(
value=>{
resolve(value)
},
reason=>{
count++;
if(count===promises.length)
reject(new AggregateError('promises all fail!'));
}
)
})
})
}
//向外暴露Promise函数
window.Promise = Promise;
})(window);
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
<!--调用:-->
<script src="promise.js"></script>
<script>
//如果是一般值,p1成功,value就是这个值
const p1 = Promise.resolve(2);
//如果是成功的promise,p2成功,value是这个promise的value
const p2 = Promise.resolve(Promise.resolve(3));
//如果是失败的promise,p3失败,reason是这个promise的reason
const p3 = Promise.resolve(Promise.reject(4));
p1.then(value=>{console.log('p1', value)})//p12
p2.then(value=>{console.log('p2', value)})//p23
p3.catch(reason=>{console.log('p3', reason)})//p34
const pAll = Promise.all([p1, p2]);
pAll.then(
values=>{
console.log('race onResolved()', values);
},
reason=>{
console.log('race onRejected()', reason);
}
)
const pRace = Promise.race([p1, p2, p3]);
pRace.then(
value=>{
console.log('race onResolved()', value);
},
reason=>{
console.log('race onRejected()', reason);
}
)
const pAllsettled = Promise.allSettled([p1, p2, p3]);
pAllsettled.then(
values=>{
console.log('allsettled', values);
},
reason=>{
console.log('allsettled', reason);
}
)
const pAny = Promise.any([p3]);
pAny.then(
value=>{
console.log('pany resolved', value);
},
reason=>{
console.log('pany rejected', reason);
}
)
</script>

数组

数组排序算法的时间复杂度:时间复杂度图

数组排序

数组排序:冒泡排序 o(n^2)

1
2
3
4
5
6
7
8
9
function bubbleSort(arr){
const len = arr.length;
//循环len轮,每一轮最大的数往后移
for(let i=0;i<len;i++)
for(let j=0;j<len-1;j++)
if(arr[j]>arr[j+1])
[arr[j], arr[j+1]] = [arr[j+1], arr[j]]
return arr;
}

数组排序:选择排序 o(n^2)

1
2
3
4
5
6
7
8
9
10
11
12
13
function selectSort(arr){
const len = arr.length;
let minIndex;
for(let i=0;i<len-1;i++){
minIndex = i;
for(let j=i;j<len;j++)
if(arr[j]<arr[minIndex])
minIndex = j;
if(minIndex!==i)
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
}
return arr;
}

数组排序:插入排序 o(n^2)

1
2
3
4
5
6
7
8
9
10
11
12
function insertSort(arr){
for(let i=1;i<arr.length;i++){
let j=i;
let target = arr[j];
while(j>0&&arr[j-1]>target){
arr[j]=arr[j-1];
j--;
}
arr[j] = target;
}
return arr;
}

数组排序:快速排序 o(nlogn-n^2)

1
2
3
4
5
6
7
8
function quickSort(arr){
if(arr.length<2)
return arr;
let cur = arr[arr.length-1];
const left = arr.filter((v, i)=>v<=cur&&i!==arr.length-1);
const right = arr.filter(v=>v>cur);
return [...quickSort(left), cur, ...quickSort(right)];
}

数组排序:归并排序 o(nlogn)

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 merge(left, right){
let res = [];
let i = 0;
let j = 0;
while(i<left.length&&j<right.length){
if(left[i]<right[j]){
res.push(left[i]);
i++;
}else{
res.push(right[j]);
j++;
}
}
if(i<left.length)
res.push(...left.slice(i));
else
res.push(...right.slice(j));
return res;
}
function mergeSort(arr){
if(arr.length<2)
return arr;
const mid = Math.floor(arr.length/2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
}
数组去重

数组去重:双重for+splice

1
2
3
4
5
6
7
8
9
10
function unique(arr){
for(let i=0;i<arr.length;i++){
for(let j=0;j<arr.length;j++)
if(arr[i]===arr[j]){
arr.splice(j, 1);
j--;
}
}
return arr;
}

数组去重:indexOf+新数组

1
2
3
4
5
6
7
8
function unique(arr){
var res = [];
for(let i=0;i<arr.length;i++){
if(res.indexOf(arr[i])===-1)
res.push(arr[i]);
}
return res;
}

数组去重:快慢指针需要对数组进行排序,会打乱数组顺序

1
2
3
4
5
6
7
8
9
10
11
function unique(arr){
arr.sort((a,b)=>a-b);
let slow = 1, fast = 1;
while(fast < arr.length){
if(arr[fast]!==arr[fast-1])
arr[slow++]=arr[fast]
fast++;
}
arr.length = slow;
return arr;
}

数组去重:ES6 set

1
2
3
function unique(arr){
return [...new Set(arr)];
}

数组去重:map

1
2
3
4
5
6
7
8
9
10
11
function unique(arr){
let map = new Map();
let res = [];
for(let i=0;i<arr.length;i++){
if(!map.has(arr[i])){
map.set(arr[i], true);
res.push(arr[i]);
}
}
return res;
}

数组去重:filter

1
2
3
4
5
function unique(arr){
return arr.filter((item, index, arr)=>{
return arr.indexOf(item)===index;
})
}

数组去重:reduce

1
2
3
4
5
6
7
8
function unique(arr){
let res = arr.reduce((acc, cur)=>{
if(!acc.includes(cur))
acc.push(cur);
return acc;
}, []);
return res;
}
数组实现: reduce与indexOf的参数与别的方法不一样
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
(function(window){
function Array(){
this.arr = [1, 2, 3];
return this.arr;
}
/*
forEach
用途:调用数组的每个元素,执行一遍回调函数
参数:(callback, thisValue),其中callback为回调函数,thisValue绑定this
无返回值
*/
Array.prototype.forEach = function(callback){
if(this===null||this===undefined)
throw new TypeError('cannot read property forEach of null');
if(Object.prototype.toString.call(callback)!=='[object Function]')
throw new TypeError('callback is not a function');
var _arr = this.arr, thisArg = arguments[1]||window;
for(var i=0;i<_arr.length;i++)
callback.call(thisArg, _arr[i], i, _arr);
}
/*
map
用途:调用数组每个元素,并执行回调函数
参数:(callback, thisArg)
callback为回调函数,thisArg为回调函数的this指向
返回值为每个回调函数返回值组成的数组
*/
Array.prototype.map = function(callback){
if(Object.prototype.toString.call(callback)!=='[object Function]')
throw new TypeError('callback is not a function');
let res = [];
for(let i=0;i<this.arr.length;i++){
let tempres = callback.call(arguments[1]||window, this.arr[i], i, this.arr);
res.push(tempres);
}
return res;
}
/*
reduce
用途:调用数组的每个元素,并执行回调函数,将其结果返回为单个值
参数:(callback, initValue)
其中callback为回调函数,initValue为第一次调用时的初始值
如果没有提供初始值,则使用数组第一个值,如果没有初始值并且空数组,则报错
返回最终结果
*/
Array.prototype.reduce = function(callback){
if(Object.prototype.toString.call(callback)!=='[object Function]')
throw new TypeError('callback is not a function');
var i = 0;
var acc = arguments[1];
//reduce函数接受的参数,包括一个callback还有一个初始化参数
if(arguments[1]===undefined){
if(this.arr.length===0)
throw new Error('initval and array.length===0');
acc = this.arr[i++];
}
for(;i<this.arr.length;i++)
acc = callback.call(this, acc, this.arr[i]);
return acc;
}
/*
filter
用途:调用数组每个元素,并执行回调函数
参数:(callback, thisArg)
callback为回调函数,thisArg为回调函数的this指向
返回值为满足条件的数组元素组成的数组
*/
Array.prototype.filter = function(callback){
if(Object.prototype.toString.call(callback)!=='[object Function]')
throw new TypeError('callback is not a function');
let res = [];
for(let i=0;i<this.arr.length;i++)
if(callback.call(arguments[1]||window, this.arr[i], i, this.arr))
res.push(this.arr[i]);
return res;
}
/*
find
用途:返回数组中满足回调函数的第一个元素的值,否则返回undefined.注意!!!!是返回值,而不是索引!!!
参数:(callback, thisArg)
callback为回调函数,thisArg为回调函数的this指向
*/
Array.prototype.find = function(callback){
if(Object.prototype.toString.call(callback)!=='[object Function]')
throw new TypeError('callback is not a function');
if(this.arr.length===0)
return true;
for(let i=0;i<this.arr.length;i++)
if(callback.call(arguments[1]||window, this.arr[i], i, this.arr))
return this.arr[i];
return undefined;
}
/*
indexOf
用途:返回数组中满足回调函数的第一个元素的值,否则返回-1
参数:(findVal, beginIndex)
findVal为需要查找的元素的值,beginIndex为查找的起始位置
*/
Array.prototype.indexOf = function(findVal, beginIndex=0){
if(this.arr.length<1||beginIndex>this.arr.length)
return -1;
if(!findVal)
return 0;
beginIndex=beginIndex<=0?0:beginIndex;
for(let i=0;i<this.arr.length;i++)
if(this.arr[i]===findVal)
return i;
return -1;
}
window.Array = Array;
})(window);
数组扁平化

示例:

1
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "弹铁蛋同学" }];

代码:

1
2
3
4
5
6
7
8
9
10
// num 规定扁平化的层数
function flat(arr, num=1){
if(num<0)
return arr;
return num>1
?arr.reduce((pre, cur)=>{
return pre.concat(Array.isArray(cur)?flat(cur, num-1):cur)
}, [])
:arr.slice()
}
数组转树
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
[{id: , pid: }, {id: , pid: }]

[{
id: ,
children: [{
pid: ,
id: ,
children: []
},{
pid: ,
id: ,
children: []
}]
},{
id: ,
children: [{
pid: ,
id: ,
children: []
},{
pid: ,
id: ,
children: []
}]
}]

function arrayToTree(items){
const result = [];
const itemMap = {};
for(const item of items){
const id = item.id;
const pid = item.pid;
if(!itemMap[id])
itemMap[id] = {
children: []
}
itemMap[id] = {
...item,
children: itemMap[id]['children']
}
const treeItem = itemMap[id];
if(pid===0)
result.push(treeItem);
else{
if(!itemMap[pid])
itemMap[pid]={
children: []
}
itemMap[pid].children.push(treeItem);
}
}
return result;
}

树转数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function treeToList(data){
let res = [];
const dfs = tree=>{
tree.forEach(item=>{
if(item.children){
dfs(item.children);
delete item.children;
}
res.push(item);
})
}
dfs(data);
return res;
}

类数组转化为数组

1
2
3
4
5
[...arr];
Array.from(arr);
Array.apply(null, arr);
Array.prototype.slice.call(arr);
Array.prototype.concat.apply([], arr);

防抖节流:

  • 种类:非立即执行防抖节流和立即执行防抖节流

非立即执行防抖节流:

  • 事件触发->延时->执行回调函数

  • 如果在延时阶段,继续触发事件,则会重新进行延时。在延时结束后执行回调函数。

    立即执行防抖节流:(这种方法用户体验度更高)

  • 事件触发->执行回调函数->延时

  • 如果在延时中,继续触发事件,则会重新继续延时。在延时结束后,不再执行回调函数。clearTimeout能够取消setTimeout设定的延时事件的发生

防抖

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 debounce(func, wait, immediate){
let timeout, result;
var debounced = function(){
let context = this;
let args = arguments;
if(timeout)
clearTimeout(timeout);
if(immediate){
let callnow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait);
if(callnow){
result = func.apply(context, args);
}
}else{
timeout = setTimeout(function(){
result = func.apply(context, args);
timeout = null;
}, wait);
}
return result;
};
debounced.cancel = function(){
clearTimeout(timeout);
timeout = null;
}
return debounced;
}

节流

两种解决思路:1.类似debounce的方法2.采用时间戳的差

两种解决方式的差异

  • 第一种先等待够规定时间,再执行

  • 第二种先执行目标函数,再等待规定时间

有option选项,{leading: boolean, trailing: boolean}

  • leading用于设置第一次是否执行

  • boolean用于设置最后一次是否执行

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
function throttle(fn, wait, option){
let timeout;
let old = 0;
if(!option)
option = {};
return function(){
let context = this;
let args = arguments;
let now = new Date().getTime();
if(!old&&option.leading===false)
old = now;
let remain = wait - (now - old);
if(remain<=0||remain>wait){
if(timeout){
clearTimeout(timeout);
timeout = null;
}
fn.apply(context,args);
old = now;
//这个地方不能再写timeout=....
//因为如果写了,当option.trailing为true的话会进不去下面的语句
}else if(!timeout && option.trailing){
timeout = setTimeout(()=>{
fn.apply(context, args);
timeout = null;
old = option.leading===false?0:new Date().getTime()
}, remain);
}
}
}

setTimeout模拟setInterval

1
2
3
4
5
6
7
8
9
10
let timer = null;
function myInterval(func, wait){
let interv = function(){
func();
timer = setTimeout(interv, wait);
};
timer = setTimeout(interv, wait);
}
//调用
myInterval(function(){}, 20);

setInterval模拟setTimeout

1
2
3
4
5
6
const mySetTimeout = (fn, time)=>{
interval = setInterval(()=>{
fn();
clearInterval(interval);
}, time);
}

Ajax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function ajax({url=null, method='GET',dataType='JSON',async=true}){
return new Promise((resolve, reject)=>{
let xhr = new XMLHttpRequest();
xhr.open(method, url, async);
xhr.responseType = dataType;
xhr.onreadystatechange = ()=>{
if(!(xhr.status>=200&&xhr.status<300))
return;
if(xhr.readyState===4)
resolve(xhr.responseText);
}
xhr.onerror = err=>{
reject(err);
}
xhr.send();
})
}

call

1
2
3
4
5
6
7
8
Function.prototype.call = function(context, ...args){
let context = context || window;
//保存当前调用的函数
context.fn = this;
let res = context.fn(...args);
delete context.fn;
return res;
}

apply

1
2
3
4
5
6
7
8
9
10
11
12
Function.prototype.apply = function(context, args){
let context = context || window;
context.fn = this;
let res;
if(args){
res = context.fn(...args);
}else{
res = context.fn();
}
delete context.fn;
return res;
}

bind

1
2
3
4
5
6
7
8
9
10
11
12
13
Function.prototype.bind = function(context, ...args1){
let context = context || window;
context.fn = this;
return function(...args2){
let res;
if(args1)
res = context.fn(...args1);
else
res = context.fn(...args2);
delete context.fn;
return res;
}
}

需要考虑柯里化以及new构造函数的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Function.prototype.bind = function(){
// 取上下文
let context = arguments[0];
// 取外部入参
let outArgs = Array.from(arguments).slice(1);
// 存外部this,也就是需要调用的函数
let outThis = this;
if(typeof outThis !== 'function')
throw new Error('the caller is not a function');
const cb = function(){
// 判断函数是否被new过
let isNew = typeof new.target !== 'undefined';
// 取内部入参
let inArgs = Array.from(arguments);
// 改变指向,合并函数入参
return outThis.apply(isNew?this:context, outArgs.concat(inArgs));
}
// 继承构造函数原型
cb.prototype = outThis.prototype;
return cb;
}

new

1
2
3
4
5
6
function new(constructor, arguments){
let obj = Object.create(null);
obj.prototype = constructor.prototype;
let res = Object.apply(obj, arguments);
return typeof res === 'object'?res:obj;
}

create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Object.prototype.create = function(proto, property){
if(proto===null||typeof proto!=='function'&&typeof proto!=='object')
throw TypeError('prototype should be an object');
function F(){}
F.prototype = proto;
let obj = new F();
if(property){
Object.keys(property).forEach(key=>{
let value = property[key];
if(key===null||typeof key!=='object')
throw TypeError('property should be an object');
else
Object.defineProperty(obj, key, value);
})
}
return obj;
}

instanceof

1
2
3
4
5
6
7
8
function instanceof(left, right){
while(left){
if(left.__proto__===right.prototype)
return true;
left = left.__proto__;
}
return false;
}

Object.is

1
2
3
4
5
6
7
8
9
10
/*
Object.is作用:比较两个值是否相等
不同于==:Object.is不会将两个值进行类型转换
不同于===: Object.is中Nan=Nan, +0!=-0,但在===中NaN!==Nan, +0=-0
*/
Object.is = function(x, y){
if(x===y)
return x!==0 || 1/x===1/y;
return x!==x&&y!==y;
}

deepclone

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
const getType = target=>Object.prototype.toString.call(target);
const isObject = target=>target!==null&&(typeof target==='object'||typeof target==='function')
const canTranverse = {
'[object Object]': true,
'[object Map]': true,
'[object Set]': true,
'[object Array]': true,
'[object Argument]': true
}
const handleFunc = target=>{
const funcString = target.toString();
//有原型说明不是箭头函数
if(target.prototype){
let bodyexp = /(?<={)(.|\n)+(?=})/;
let paramexp = /(?<=\().+(?=\))\s+{/;
let body = bodyexp.exec(funcString)[0];
let param = paramexp.exec(funcString)[0];
if(param){
return new Function(...param, body);
}else
return new Function(body);
}else{
return eval(funcString);
}
}
const handleExp = target=>{
const {source, flags} = target;
let res = new RegExp(source, flag);
res.lastIndex = target.lastIndex;
return res;
}
const handle = (target, type)=>{
switch(type){
case '[object Function]':
return handleFunc(target);
case '[object RegExp]':
return hanldeExp(target);
case '[object Error]':
case '[object Date]':
return new target.constructor(target);
case '[object Boolean]':
return new Object(Boolean.prototype.valueOf.call(target));
case '[object Number]':
return new Object(Number.prototype.valueOf.call(target));
case '[object String]':
return new Object(String.prototype.valueOf.call(target));
case '[object Symbol]':
return new Object(Symbol.prototype.valueOf.call(target));
default:
return new target.constructor(target);
}
}
//map是为了考虑循环引用的问题
//使用weakmap的理由:当我们要拷贝的对象过大时,使用Map会对内存造成很大的额外消耗,
//需要我们手动清除Map属性才能释放这块内存,但是WeakMap会自动回收这块内存
function deepClone(target, weakmap=new WeakMap()){
let type = getType(target);
if(!isObject(type))
return target;
let cloneTarget;
if(!canTranverse[type])
handle(target, type);
else
cloneTarget = new target.constructor();
if(weakmap.get(target))
return target;
else
weakmap.set(target, true);
if(type==='[object Array]'){
for(let index in target)
cloneTarget[index] = deepClone(target[index],weakmap);
}
if(type==='[object Map]'){
Object.keys(target).forEach(key=>{
cloneTarget.set(deepClone(key, weakmap), deepClone(target[key], weakmap));
})
}
if(type==='[object Set]'){
target.forEach((item, key)=>{
cloneTarget.add(key, deepClone(item, weakmap));
})
}
return cloneTarget;
}

继承

原型链继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Father(){
this.property = true;
}
Father.prototype.getFatherValue = function(){
return this.property;
}
function Son(){
this.subproperty = false;
}
Son.prototype = new Father();
Son.prototype.getSonValue = function(){
return this.subproperty;
}
var instance = new Son();
instance.getFatherValue();

/*
缺点:
1. 在父类型构造函数中定义的引用类型值的实例属性,会在子类型原型上变成原型属性被所有子类型实例所共享
2. 在创建子类型的实例时,不能向父类型的构造函数中传递参数
*/

构造函数式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
function Father(){
this.colors = ['red', 'green'];
}
function Son(){
//通过使用apply或call,实际上是在将要创建的Son实例的环境下调用了Father构造函数
Father.call(this);
}
var instance = new Son();
instance.colors.push('purple');
/*
优点:解决了原型链继承的缺点。
缺点:方法都在构造函数中定义,无法实现函数复用。父类型的原型中定义的方法,对子类型而言是不可见的,结果所有类型都只能使用构造函数模式。
*/

组合式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Father(name){
this.name = name;
this.colors = ['red'];
}
Father.prototype.sayName = function(){
alert(this.name);
}
function Son(name, age){
Father.call(this, name);
this.age = age;
}
Son.prototype = new Father();
Son.prototype.constructor = Son;
Son.prototype.sayAge = function(){
alert(this.age);
}
var instance = new Son('aaa', 12);
instance.colors.push('purple');
//缺点:无论在什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部

原型式继承,浅拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//方法一
function object(o){
function F(){}
F.prototype = o;
return new F();
}
//方法二
var person = {
name: 'hh',
colors: ['red']
}
var anotherPerson = Object.create(person, {
name: {
value: 'tom'
}
})
//缺点:包含引用类型值的属性始终会在所有实例中共享相同的值

寄生式继承

1
2
3
4
5
6
7
8
9
function createPerson(original){
var clone = Object.create(original);
clone.sayGood = function(){
alert('hello world');
}
return clone;
}
//原理:在函数内部以某种方式来增强对象,最后返回对象
//缺点:函数难以复用

寄生组合式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Father(name){
this.name = name;
this.colors = ['red'];
}
Father.prototype.sayName = function(){
alert(this.name);
}
function Son(name, age){
Father.call(this, name);
this.age = age;
}
var anotherPrototype = Object.create(Father.prototype);
anotherPrototype.constructor = Son;
Son.prototype = anotherPrototype;
Son.prototype.sayAge = function(){
alert(this.age);
}
var instance = new Son('lu', 22);

大数相加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function add(a, b){
let maxlength = Math.max(a.length, b.length);
a = a.padStart(maxlength, 0);
b = b.padStart(maxlength, 0);
let t = 0;
let f = 0;
let sum = "";
for(let i=maxlength-1;i>=0;i--){
t = parseInt(a[i])+parseInt(b[i])+f;
f = Math.floor(t/10);
sum = t%10+sum;
}
if(f!=0)
sum=''+f+sum;
return sum;
}

表单校验

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
const strategies = {
isNoEmpty: function(value, errorMsg){
if (value === '')
return errorMsg;
},
minLength: function(value, length, errorMsg){
if(value.length<length)
return errorMsg;
},
isMobile: function(value, errorMsg){
if(!/^$/.test(value))
return errorMsg;
}
};
class Validator{
constructor(){
this.cache = [];
}
add(dom, rules){
for(let i=0,rule;rule=rules[i++];){
let strategyAry = rule.strategy.split(':');
let errorMsg = rule.errorMsg;
this.cache.push(()=>{
let strategy = strategyAry.shift();
strategyAry.unshift(dom.value);
strategyAry.push(errorMsg);
return strategies[strategy].apply(dom, strategyAry);
})
}
}
start(){
for(let i=0,validatorFunc;validatorFunc=this.cache[i++];){
let errorMsg = validatorFunc();
if(errorMsg)
return errorMsg;
}
}
}
let registerForm = document.getElementById('registerForm');
let validateFunc = function(){
let validator = new Validator();
validator.add(registerForm.username, [{
strategy: 'isNoEmpty',
errorMsg: 'cannot input empty'
},{
strategy: 'minLength:2',
errorMsg: 'input is too short'
}])
validator.add(registerForm.mobile, [{
strategy: 'isMobile',
errorMsg: 'mobile is invalid'
}])
return validator.start();
}
registerForm.onsubmit = function(){
let errorMsg = validateFunc();
if(errorMsg)
alert(errorMsg);
}

发布订阅者模式

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 EventEmitter{
constructor(){
this.events = {};
}
//on: 实现订阅
on(type, callback){
if(!this.events[type])
this.events[type] = [callback];
else
this.events[type].push(callback);
}
off(type, callback){
if(!this.events[type])
return;
this.events[type] = this.events[type].filter(item=>{
return item!=callback;
})
}
once(type, callback){
//在执行完callback函数之后,立马将其移除
//因为这两个步骤需要捆绑先后执行,所以需要将它们打包在一个函数中加入数组
function fn(){
callback();
this.off(type, fn);
}
this.on(type, fn);
}
emit(type, ...rest){
this.events[type]&&this.events[type].forEach(fn=>fn.apply(this, rest));
}
}
//调用
const event = new EventEmitter();
const handle = (...rest)=>{
console.log(rest);
}
event.on('click', handle);
event.emit('click', 1, 2, 3, 4);
event.off('click', handle);

小数据:分片渲染大数据量数据 requestAnimationFrame版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let ul = document.getElementById('container');
let total = 100000;
let once = 20;
let index = 0;
function loop(curTotal, curIndex){
if(curTotal<=0)
return;
let pageCount = Math.min(curTotal, once);
window.requestAnimationFrame(function(){
for(let i=0;i<pageCount;i++){
let li = document.createElement('li');
li.innerText = curIndex+i+":"+~~(Math.random()*total);
ul.appendChild(li);
}
})
loop(curTotal-pageCount, curIndex+pageCount);
}
loop(total, index);

DocumentFragment版

DocumentFragment,文档片段接口,表示一个没有父级文件的最小文档对象。它被作为一个轻量版的Document使用,用于存储已排好版的或尚未打理好格式的XML片段。最大的区别是因为DocumentFragment不是真实DOM树的一部分,它的变化不会触发DOM树的(重新渲染) ,且不会导致性能等问题。可以使用document.createDocumentFragment方法或者构造函数来创建一个空的DocumentFragment。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let ul = document.getElementById('container');
let total = 100000;
let once = 20;
let index = 0;
function loop(curTotal, curIndex){
if(curTotal<=0)
return;
let pageCount = Math.min(curTotal, once);
window.requestAnimationFrame(function(){
let fragment = document.createDocumentFragment();
for(let i=0;i<pageCount;i++){
let li = document.createElement('li');
li.innerText = curIndex+i+":"+~~(Math.random()*total);
fragment.appendChild(li);
}
ul.appendChild(fragment);
})
loop(curTotal-pageCount, curIndex+pageCount);
}
loop(total, index);

模板字符串解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
name: '姓名',
age: 18
}
render(template, data); // 我是姓名,年龄18,性别undefined
*/
function render(template, data){
let computed = template.replace(/\{\{(\w+)\}\}/g, function(match, key){
return data[key];
})
return computed;
}

LRUcache

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
class LRUcache{
constructor(capacity){
//使用map是因为map不仅能记录key和value,还能记录插入顺序
this.secretKey = new Map();
this.capacity = capacity;
}
get(key){
if(this.secretKey.has(key)){
let tempValue = this.secretKey.get(key);
this.secretKey.delete(key);
this.secretKey.set(key, tempValue);
return tempValue;
}else
return -1;
}
put(key, value){
if(this.secretKey.has(key)){
this.secretKey.delete(key);
this.secretKey.set(key, value);
}else if(this.secretKey.size<this.capacity)
this.secretKey.set(key, value);
else{
this.secretKey.set(key, value);
//keys()返回一个引用的Iterator对象
this.secretKey.delete(this.secretKey.keys().next().value);
}
}
}

LazyMan

场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
实现一个LazyMan,可以按照以下方式调用:
LazyMan(“Hank”)输出:
Hi! This is Hank!

LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~

LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出
Hi This is Hank!
Eat dinner~
Eat supper~

LazyMan(“Hank”).eat(“supper”).sleepFirst(5)输出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper

代码

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
class _LazyMan{
constructor(name){
this.tasks = [];
const task = ()=>{
console.log(`hi! this is ${name}`);
this.next();
};
this.tasks.push(task);
setTimeout(()=>{
this.next();
}, 0);
}
next(){
const task = this.tasks.shift();
task && task();
}
sleep(time){
this._sleepWrapper(time, false);
return this;
}
sleepFirst(time){
this._sleepWrapper(time, false);
return this;
}
_sleepWrapper(time, first){
const task = ()=>{
setTimeout(()=>{
console.log(`wake up after ${time}`);
this.next();
}, time*1000);
}
if(first)
this.tasks.unshift(task);
else
this.tasks.push(task);
}
eat(name){
const task = ()=>{
console.log(`Eat ${name}`);
this.next();
};
this.tasks.push(task);
return this;
}
}
function LazyMan(name){
return new _LazyMan(name);
}

compose

场景

1
2
3
4
5
compose 是函数式编程中很重要的函数之一。
compose 函数的作用就是组合函数的,将函数串联起来执行,将多个函数组合起来,
一个函数的输出结果是另一个函数的输入参数,一旦第一个函数开始执行,
就会像多米诺骨牌一样推导执行了。
compose(f,g,m,n)(x) === f(g(m(n(x))))

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//迭代方式实现
function compose1(){
var fns = [].slice.call(arguments);
return function(initArg){
var res = initArg;
for(var i=fns.length-1;i>=0;i--)
res = fns[i](res);
return res;
}
}
//reduce方法实现
function compose2(...funcs){
if(funcs.length===0)
return v=>v;
if(funcs.length===1)
return funcs[0];
return funcs.reduce((a, b)=>{
return function(...args){
return a(b(...args));
}
})
}

curry

场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
所谓”柯里化”,简单来说就是把一个多参数的函数,转化为单参数函数
// 普通的add函数
function add(x, y) {
return x + y
}

// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}

add(1, 2) // 3
curryingAdd(1)(2) // 3

代码

1
2
3
4
5
6
7
8
9
10
11
12
function currying(fn, ...args){
const length = fn.length;
let allargs = [...args];
const res = (...newArgs)=>{
allargs = [...allargs, ...newArgs];
if(allargs.length === length)
return fn(...allargs);
else
return res;
}
return res;
}

add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function add(...args){
let allargs = [...args];
function fn(...args2){
allargs = [...allargs, ...args2];
return fn;
}
fn.toString = function(){
if(!allargs.length)
return;
return allargs.reduce((sum, cur)=>{
return sum+cur;
})
}
return fn;
}

通用事件侦听器函数

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
const EventUtils = {
addEvent: function(element, type, handler){
if(element.addEventListener)//DOM2
element.addEventListener(type, handler, false);
else if(element.attachEvent)//IE
element.attachEvent("on"+type, handler);
else//DOM0
element["on"+type]=handler;
},
removeEvent: function(element, type, handler){
if(element.removeEventListener)
element.removeEventListener(type, handler, false);
else if(element.detachEvent)
element.detachEvent('on'+type, handler);
else
element['on'+type]=null;
},
stopPropagation: function(ev){
if(ev.stopPropagation)
ev.stopPropagation();
else
ev.cancelBubble = true;
},
preventDefault: function(ev){
if(ev.preventDefault)
ev.preventDefault();
else
ev.returnValue = false;
},
//获取事件源对象,点谁谁就是事件源
getTarget: function(ev){
//target为火狐写法,srcElement为IE写法
//chrome都有
return ev.target || ev.srcElement;
}
}

dom2json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
<div>
<span>
...
</span>
</div>
to
{
tag: 'DIV',
children: [
tag: 'SPAN',
children: []
]
}
*/
function dom2json(dom){
let obj = {};
obj.tag = dom.tagName;
obj.children = [];
dom.childNodes.forEach(child=>{
obj.children.push(dom2json(child));
})
}

json2dom

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
// {
// tag: 'DIV',
// attrs:{
// id:'app'
// },
// children: [
// {
// tag: 'SPAN',
// children: [
// { tag: 'A', children: [] }
// ]
// },
// {
// tag: 'SPAN',
// children: [
// { tag: 'A', children: [] },
// { tag: 'A', children: [] }
// ]
// }
// ]
// }
// 把上诉虚拟Dom转化成下方真实Dom
// <div id="app">
// <span>
// <a></a>
// </span>
// <span>
// <a></a>
// <a></a>
// </span>
// </div>
function json2dom(vdom){
if(typeof vdom === 'number')
vdom = String(vdom);
if(typeof vdom === 'string')
return document.createTextNode(vdom);
const dom = document.createElement(vdom.tag);
if(vdom.attrs){
Object.keys(vdom.attrs).forEach(key=>{
const value = vdom.attrs[key];
dom.setAttribute(key, value);
})
}
vdom.children.forEach(child=>{
dom.appendChild(json2dom(child));
})
return dom;
}

async/await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function run(gen){
return new Promise((resolve, reject)=>{
let g = gen();
function next(value){
let res;
try{
res = g.next(value);
}catch(err){
return reject(err);
}
if(res.done)
return resolve(res.value);
Promise.resolve(res.value).then(
value => next(value),
reason=>g.throw(reason)
)
}
})
}

数组乱序

1
2
3
4
5
6
7
function shuffle(arr){
let i = arr.length;
while(i){
let j=Math.floor(Math.random()*i--);
[arr[j], arr[i]]=[arr[i], arr[j]];
}
}

并行promise

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 Schedule {
constructor(limit) {
this.maxCount = limit;
this.queue = [];
this.runCount = 0;
}
add(time, order) {
const createPromise = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(order);
}, time);
})
};
this.queue.push(createPromise);
}
taskStart() {
for (let i = 0; i < this.queue.length; i++) {
this.request();
}
}
request() {
if (this.runCount >= this.maxCount || !this.queue || !this.queue.length) {
return;
}
this.runCount++;
this.queue.shift()().then(() => {
this.runCount--;
this.request();
})
}
}

双向数据绑定

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
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title></title>
</head>

<body>
<div>
show: <h1></h1>
input: <input type="text">
</div>
<script>
function defined(){
let obj = {};
let val;
Object.defineProperties(obj, {
val: {
get(){
return val;
},
set(newVal){
val = newVal;
document.querySelector('h1').innerText = val;
}
}
});
return obj;
}
let newObj = defined();
document.querySelector('input').addEventListener('input', function(){
newObj.val = document.querySelector('input').value;
})
</script>
</body>

</html>

tips

数组

  • reduce \ forEach \ filter \ every \ some会跳过空位
  • map \ slice 不会跳过空位