手写常用函数

Array.prototype.map

1
2
3
// 样例
[1,2]._map(i => i * 2)
[2,4]

mdn 里的 map

1
2
map(function(element, index, array) { /* … */ }, thisArg)
// thisArg 可省略
1
2
3
4
5
6
7
8
9
10
11
// 不能写箭头函数, this会指向数组的父级
Array.prototype._map = function (fn) {
if (typeof fn !== 'function') return
const array = this
const newArray = []
for (let i = 0; i < array.length; i++) {
// 根据 mdn 的输入来写这里的输入
newArray.push(fn.call(arguments[1], array[i], i, array))
}
return newArray
}

Array.prototype.filter

和 map 没什么区别

1
2
3
// 样例
[1,2]._filter(i => i>1)
[2]
1
2
3
4
5
6
7
8
9
10
11
Array.prototype._filter = function(fn) {
if (typeof fn !== 'function') return
const array = this
const newArray = []
for (let i = 0; i < array.length; i++) {
if (fn.call(arguments[1], array[i], i, array)) {
newArray.push(array[i])
}
}
return newArray
}

Array.prototype.reduce

1
2
3
// 样例
[1,2,3]._reduce((left, right) => left + right)
6

mdn

1
reduce(function(previousValue, currentValue, currentIndex, array) { /* … */ }, initialValue)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Array.prototype._reduce = function(fn) {
if (typeof fn !== 'function') return
const array = this
let flag
if (arguments[1]) {
preValue = arguments[1]
flag = true
} else {
preValue = array[0]
flag = false
}
for (let i = flag ? 0 : 1; i < array.length; i++) {
preValue = fn(preValue, array[i], i, array)
}
return preValue
}

Object.create

创建一个新对象,使用现有的对象来作为新创建对象的原型

1
2
3
4
5
6
7
// 使用原型式继承的思想
const _objectCreate = proto => {
if (typeof proto !== 'object' || proto === null) return
function fn() {}
fn.prototype = proto
return new fn()
}

Function.prototype.call

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Function.prototype.myCall = function(context) {
let context = context || window
// 1.将函数设为对象的属性(this指向当前函数, context是传入的对象)
context.fn = this;

let args = [...arguments].slice(1)

// 2.执行该函数
let result = context.fn(...args)

// 3.执行完后删除函数
delete context.fn
return result
}

Function.prototype.bind

1
2
3
4
5
6
7
8
9
10
11
12
13
Function.prototype.myBind = function(context) {
if (typeof this !== 'function') throw new TypeError('Error')
let fn = this
let args = [...arguments].slice(1)
// 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new fn(...args, ...arguments)
}
return fn.apply(context, [...args, ...arguments])
}
}

new 实例化

1
2
3
4
5
6
7
8
9
// 样例
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.getName = function() {
return this.name
}
let p = _new(Person, "sillywa", 23)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function _new() {
// 1.创建一个空对象
let obj = {}

// 2.将该空对象的原型链连接到传入的对象
let [Con, ...args] = arguments
obj.__proto__ = Con.prototype

// 3.执行函数并绑定this
let res = Con.call(obj, ...args)

// 4.如果函数有返回值并且为object,则返回函数的返回值,否则返回obj
return res instanceof Object ? res : obj
}

instanceof

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function myInstanceof(left, right) {
let p = Object.getPrototypeOf(left)
let prototype = right.prototype
while (p) {
if (p === prototype) return true
p = Object.getPrototypeOf(p)
}
return false
}

// true true false
console.log(myInstanceof(5, Number));
console.log(myInstanceof(5, Object));
console.log(myInstanceof(5, String));

// false false true true
console.log(5 instanceof Number);
console.log(5 instanceof Object);
console.log(new Number(5) instanceof Number);
console.log(new Number(5) instanceof Object);

// myInstanceof 的实现还是有些问题,没有区分 5 和 new Number(5)

==扁平数据结构转Tree==

总的来说就两步:

  1. 创建当前 id 的 map

  2. 将当前 id 的 map 放入 pid 的 map 的 children 中

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
let arr = [
{id: 1, name: '部门1', pid: 0},
{id: 2, name: '部门2', pid: 1},
{id: 3, name: '部门3', pid: 1},
{id: 4, name: '部门4', pid: 3},
{id: 5, name: '部门5', pid: 4},
]

function arrayToTree(arr) {
const result = []
const map = {}

arr.forEach((item, index) => {
const { id, pid } = item
// 1.创建当前 id 的 map

// 还没有生成的话就生成一个, 避免下面报错
if (!map[id]) {
map[id] = {
children: [],
}
}

// 生成当前 id 的 树结构, 部分 children 可能已经在之前通过 pid 生成了
map[id] = {
...item,
children: map[id]['children']
}

const treeItem = map[id]

// 2.将当前 id 的 map 放入 pid 的 map 的 children 中
if (pid === 0) {
result.push(treeItem)
} else {
if (!map[pid]) {
map[pid] = {
children: []
}
}
map[pid].children.push(treeItem)
}
})

return result
}

只执行一次的 once

实现一个 once 函数,记忆返回结果只执行一次

类似于 lodash.once

1
2
3
4
5
6
7
8
9
10
// f 是一个函数
const f = (x) => x;

const onceF = once(f);

//=> 3
console.log(onceF(3))

//=> 3
console.log(onceF(100))
1
2
3
4
5
6
7
8
9
10
11
function once(f) {
let result
let flag = false

return (...args) => {
if (flag) return result
result = f(...args)
flag = true
return result
}
}

无限累加的 sum

参数无限,调用次数无限

1
2
3
4
5
sum(1, 2, 3).valueOf(); //6
sum(2, 3)(2, 4).valueOf(); //11
sum(1)(2)(3)(4).valueOf(); //10
sum(2)(4, 1)(2).valueOf(); //9
sum(1)(2)(3)(4)(5)(6).valueOf(); // 21
1
2
3
4
5
function sum(...args) {
const f = (...rest) => sum(...args, ...rest)
f.valueOf = () => args.reduce((x, y) => x + y, 0)
return f
}

用于函数合成的 compose

实现一个 compose 函数,进行函数合成,比如 redux 中的 compose,react 高阶组件连续调用时的 compose

1
2
3
4
5
6
const add10 = (x) => x + 10;
const mul10 = (x) => x * 10;
const add100 = (x) => x + 100;

// (10 + 100) * 10 + 10 = 1110
compose(add10, mul10, add100)(10);

错误的(我最初写的)

1
2
3
4
5
6
7
8
9
10
function compose() {
const funcs = Array.from(arguments).reverse()
const n = funcs.length
return (num) => {
for (let i = 0; i < n; i++) {
num = funcs[i](num)
}
return num
}
}

显然没有考虑函数的参数个数与返回值个数的问题,只用了一个num,默认一个参数了。

正确版

利用 array.reduce 函数

上一次的返回结果会作为下一次的参数,a 为上一个值,b为当前值

1
2
3
function compose(...funcs) {
return funcs.reduce((a,b) => (...args) => a(b(...args)))
}

是一个正向的,从左向右递推的过程。

如果 a 是函数,b是参数,就是 (a,b) => a(b)

但是要考虑到 b 也是函数,所以 b 也得调用一下,得到返回值后再作为 a 的参数传入

格式化时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function dateFormat(date) {
let year = date.getFullYear()
let month = showTime(date.getMonth() + 1)
let day = showTime(date.getDay())
let hours = showTime(date.getHours())
let minutes = showTime(date.getMinutes())
let second = showTime(date.getSeconds())
const str = `${year}-${month}-${day}-${hours}-${minutes}-${second}`

return str
}

// 不够两位数就补0
function showTime(t) {
let time
time = t > 10 ? t : '0' + t
return time
}

// 2023-02-01-21-00-56
console.log(dateFormat(new Date(Date.now())))

手写常用函数
http://example.com/2022/08/28/手写常用函数/
Author
John Doe
Posted on
August 28, 2022
Licensed under