JS 手写实现汇总
ALEX CODE PARK 2020-02-17
JavaScript
ES6
算法
# 手写函数防抖
# 原理
防抖:任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。
- 创建一个标记用来存放定时器返回值
- 每次点击清楚上一个定时器
clearTimeout()
- 清楚后创建一个新的定时器
# 应用
- 有个输入框,输入之后会调用接口,获取联想词。但是,因为频繁调用接口不太好,所以我们在代码中使用防抖功能,只有在用户输入完毕的一段时间后,才会调用接口,出现联想词。
# 代码
mybtn = document.getElementById("myBtn");
function debounce(fn, delay) {
var timer = null;
return function() {
var context = this;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.call(context, ...arguments);
}, delay || 500);
};
}
mybtn.onclick = debounce(() => {
console.log("debouncing!");
}, 1000); // 一秒内连续点击也只算最后那一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 手写函数节流
# 原理
节流:指定时间间隔内只会执行一次任务。
- 保存一个标记 canRun
- 每次进入闭包函数检测 canRun 状态
- 第一次设置为 true -> 闭包函数中 canRun 变 false 且启动定时器
- 在整个定时器阶段内 canRun 皆为 false 所以每次点击进入闭包都直接被中止
- 直至定时器完成同时设置 canRun 为 true
# 应用
- 懒加载要监听计算滚动条的位置,使用节流按一定时间的频率获取。
- 用户点击提交按钮,假设我们知道接口大致的返回时间的情况下,我们使用节流,只允许一定时间内点击一次。
# 代码
mybtn = document.getElementById("myBtn");
function throttle(fn, interval) {
let canRun = true;
return function() {
var context = this;
if (!canRun) {
return;
}
canRun = false;
setTimeout(() => {
fn.call(context, arguments);
canRun = true;
}, interval || 500);
};
}
mybtn.onclick = throttle(() => {
console.log("throttling!");
}, 1000); // 如一直点按 固定1s出现一次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 手写简易深拷贝
# 原理
- 先扫描第一层
- 如有对象则进入迭代
# 代码
function deepClone(obj) {
function isObject(obj) {
return (
Object.prototype.toString.call(obj) === "[object Function]" ||
Object.prototype.toString.call(obj) === "[object Object]"
);
}
function isArray(obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
}
if (!isObject(obj) && !isArray(obj)) {
return "error!";
}
let newObj = isObject(obj) ? { ...obj } : [...obj];
// 以上实现浅拷贝
// 循环第一层
Reflect.ownKeys(newObj).forEach(e => {
newObj[e] = isObject(newObj[e]) ? deepClone(newObj[e]) : newObj[e];
});
return newObj;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 手写 new
# 原理
- 优雅的处理 arguments
- 返回时确保 result 是一个 obj
切勿混淆
手写 newobj.__proto__ = con.prototype
组合继承son.prototype = new father()
# 代码
function myNew() {
let obj = {};
let con = [].shift.call(arguments);
obj.__proto__ = con.prototype;
let result = con.apply(obj, arguments);
return result instanceof Object ? result : obj;
}
function father(name) {
this.name = name;
}
father.prototype.sayName = function() {
console.log(this.name);
};
let son = myNew(father, "cool");
son.sayName(); // cool
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 手写实现 instanceof
# 原理
- 获取子原型
- 获取父原型
- while 循环判断是否全等 === 直到
- 左边原型===null||undefined (原型链遍历完)->false
- 子原型===父原型 -> true
- 否则往下走一步原型链继续判断
# 代码
function myInstanceof(son, father) {
let right = father.prototype;
let left = son.__proto__;
while (true) {
if (left === null || left === undefined) {
return false;
}
if (left === right) {
return true;
}
left = left.__proto__;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 手写 call
# 原理
- 该方法应该写在 Function 的原型方法上
- 参数 context 为需要函数执行的作用域对象
- 检测 this 类型 如不为 function 则抛出错误
- 为了避免这种情况
a = foo.mycall(); a(context)
该 this 指向 window
- 为了避免这种情况
- 如不传入参数 context 默认为 window
- 改变 this
context.fn = this
至此 fn 的 this 便指向 context - 分离传入参数 (arguments 是数组 直接使用数组方法即可)
- 执行函数并将结果赋值给 result 便于返回
- 删除 context.fn
# 代码
Function.prototype.mycall = function(context, ...args) {
if (typeof this != "function") {
throw new TypeError("error");
}
context = context || window;
const args = [...args];
context.fn = this;
const result = context.fn(args);
delete context.fn;
return result;
};
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 手写 apply
# 原理
- 参数处理和 call 有区别 其余相同
- 如存在第二个参数 则解构它作为变量传给函数
- 如不存在则直接执行函数
# 代码
Function.prototype.myapply(context){
if(typeof this!='function'){
throw new TypeError('error')
}
context = context || window
context.fn = this
let result
if(arguments[1]){
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 手写 bind
# 原理
- 判断调用 bind 的是函数 否则抛出错误
- 处理 arguments
- 返回函数 该函数内部判断 是否将该函数当做构造函数使用
if(this instance of F)
- 如是 则执行原函数并返回一个实例 (绑定 this 失效)
- 如不是 使用 .apply 实现绑定
- 因为传参可以分多次完成 注意拼接参数
# 代码
Function.prototype.mybind(context){
if(typeof this != 'function'){
throw new TypeError('error')
}
let self = this // 需要绑定的函数
let args = [...arguments].slice(1) // 除context外的参数 返回数组
return function F(){
if(this instanceof F){
return new self(...args,...arguments) //args数组拆开来调用
}
return self.apply(context,args.concat(...arguments))
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 手写简易 Promise
# 原理
- 构建大体框架
- state
- value
- resloved cb
- rejected cb
- resolve/reject 函数
- 赋状态
- 传入值
- 执行回调
- fn 函数
- try-catch 块执行传入 fn
- then 函数
- 检测传入是否为函数
- 状态判断
# 代码
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function _Promise(fn) {
console.log("promise");
const that = this;
that.state = PENDING;
that.value = null;
that.resolvedcb = [];
that.rejectedcb = [];
// 设定resolve态
function resolve(value) {
console.log("resolve(value)");
if (that.state === PENDING) {
that.state = RESOLVED;
that.value = value;
that.resolvedcb.map(cb => cb(that.value));
}
}
// 设定resolve态
function reject(value) {
if (that.state === PENDING) {
that.state = REJECTED;
that.value = value;
that.rejectedcb.map(cb => cb(that.value));
}
}
// 执行fn 控制什么时候改变状态
try {
fn(resolve, reject);
} catch (error) {
reject(error);
}
}
// 设置回调函数
_Promise.prototype.then = function(onResolved, onRejected) {
console.log("then");
const that = this;
onResolved = typeof onResolved === "function" ? onResolved : v => v;
onRejected =
typeof onRejected === "function"
? onRejected
: e => {
throw e;
};
if (that.state === PENDING) {
console.log("then.pending");
that.resolvedcb.push(onResolved);
that.rejectedcb.push(onRejected);
}
if (that.state === RESOLVED) {
console.log("then.resolved");
onResolved(that.value);
}
if (that.state === REJECTED) {
onRejected(that.value);
}
};
new _Promise((resolve, reject) => {
setTimeout(() => {
resolve(100);
}, 3000);
}).then(
v => {
console.log(v);
},
e => {
throw e;
}
);
// promise
// then
// then.pending
// resolve(value)
// 100
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
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
# 手写 getQueryString
# 原理
API
string.prototype.substring(start,end)
分割子串
# 代码
function getQueryValue(key, url) {
let index = url.indexOf("?");
if (index != -1) {
for (let item of url.substring(index + 1).split("&")) {
let obj = item.split("=");
if (obj[0] == key) {
return obj[1];
}
}
return "none";
}
}
console.log(
getQueryValue(
"id",
"https://www.baidu.com/s?id=123&name=why&phone=13876769797"
)
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 手写 JSONP
# 原理
通过 script 标签获取一段后端传回的 js 如callback({后端数据})
自动执行完成请求
拼接 querystring,如:传入 url+{a:1,b:2}+callback 需拼成 url?a=1&b:2&callback=cbname
定义全局函数
执行完后删除节点与函数
api:
new Date().getTime()
# 代码
// 前端
var jsonp = function(url, data, callback) {
// 拼接querystring
let cbname = "cb_" + new Date().getTime();
cbStr = "&callback=" + cbname;
let dataStr = "";
for (let key in data) {
let str = key + "=" + data[key] + "&";
dataStr += dataStr + str;
}
let queryStr = url + "?" + dataStr + cbStr;
let script = document.createElement("script");
script.src = queryStr;
window[cbname] = function(res) {
callback(res);
document.body.removeChild(script);
delete window[cbname];
};
document.body.appendChild(script);
};
jsonp("http://localhost:8080", { count: 1 }, function(data) {
console.log(data.data);
});
// 服务端
var http = require("http");
var urllib = require("url");
var port = 8080;
var data = { data: "world" };
http
.createServer(function(req, res) {
var params = urllib.parse(req.url, true);
console.log(params);
if (params.query.callback) {
//jsonp
var str = params.query.callback + "(" + JSON.stringify(data) + ")";
res.end(str);
} else {
res.end();
}
})
.listen(port, function() {
console.log("jsonp server is on");
});
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
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