手写函数汇总

JavaScript 基础

1. 手写 Object.create

1
2
3
4
5
function create(obj) {
function F() {}
F.prototype = obj
return new F()
}

2. 手写 instanceof 方法

1
2
3
4
5
6
7
8
9
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left),
prototype = right.prototype;
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}

3. 手写 new 操作符

1
2
3
4
5
6
7
8
9
10
11
12
function newOperator(ctor, ...args) {
if(typeof ctor !== 'function'){
throw 'newOperator function the first param must be a function';
}
let obj = Object.create(ctor.prototype);
let res = ctor.apply(obj, args);

// 如果构造函数的返回值 res 是一个对象或函数,则直接返回它。否则,返回新创建的对象 obj
let isObject = (typeof res === 'object' && res !== null);
let isFunction = (typeof res === 'function');
return (isObect || isFunction ) ? res : obj;
};

4. 手写 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
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";

class Promise {
constructor(executor) {
this.value = undefined;
this.reason = undefined;
this.status = PENDING;
// 定义两个数组解决异步调用resolve或者reject的问题
// 充当队列把then里边的回调存起来
// 有的时候,我们会给同一个promise实例执行多次then方法, 那么相应的onFulfilled和onRejected回调必须按照其发起调用的顺序执行。
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];

const resolve = (value) => {
// 当状态为pending状态的时候才可以去改变状态,并且分别将value和reason赋值给对应值,并去执行相应回调函数
if (this.status === PENDING) {
this.value = value;
this.status = FULFILLED;
this.onResolvedCallbacks.forEach((fn) => fn());
}
};
const reject = (reason) => {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
this.onRejectedCallbacks.forEach((fn) => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err); //有报错会直接执行reject函数将状态变为失败rejected
}
}

then(onFulfilled, onRejected) {
//当执行到then的时候,状态已经是fulfilled状态或者是rejected状态,那么就直接执行回调,并且将value/reason作为第一个参数
if (this.status === FULFILLED) {
onFulfilled && onFulfilled(this.value);
}
if (this.status === REJECTED) {
onRejected && onRejected(this.reason);
}
//当执行到then的时候,状态还是pending状态,那么需要将回调存起来,等到状态改变的时候再去执行
if (this.status === PENDING) {
this.onResolvedCallbacks.push(() => {
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
});
}
}
}

module.exports = Promise;

5. 手写 Promise 的各种方法

Promise 的实例方法有 then/catch/finally 三种,静态方法有 all/race/allSettled/any/resolve/reject 六种

5.1. 手写 Promise.resolve

以下几种关于 Promise 各种方法实现的具体细节可看JavaScript八股

1
2
3
4
5
6
7
8
9
10
11
Promise.resolve = (param) => {
if(param instanceof Promise) return param;
return new Promise((resolve, reject) => {
if(param && param.then && typeof param.then === 'function') {
// param 状态变为成功会调用resolve,将新 Promise 的状态变为成功,反之亦然
param.then(resolve, reject);
}else {
resolve(param);
}
})
}

5.2. 手写 Promise.reject

1
2
3
4
5
Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
}

5.3. 手写 Promise.prototype.finally

1
2
3
4
5
6
7
8
9
10
11
Promise.prototype.finally = function(callback) {
this.then(value => {
return Promise.resolve(callback()).then(() => {
return value;
})
}, error => {
return Promise.resolve(callback()).then(() => {
throw error;
})
})
}

5.4. 手写 Promise.all

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
if(!Array.isArray(promises)) throw new TypeError(`promises must be a array`)
let result = [];
let index = 0;
let len = promises.length;
if(len === 0) {
resolve(result);
return;
}

for(let i = 0; i < len; i++) {
// 为什么不直接 promise[i].then, 因为promise[i]可能不是一个promise
Promise.resolve(promise[i]).then(data => {
result[i] = data;
index++;
if(index === len) resolve(result);
}).catch(err => {
reject(err);
})
}
})
}

5.5. 手写 Promise.allSettled

因为 Promise.allSettled() 和 Promise.all() 都是对所有 Promise 实例的一种处理,下面就可以利用 Promise.all() 来实现 Promise.allSettled() 方法。

1
2
3
4
5
6
7
8
9
10
MyPromise.allSettled = function (promises) {
return Promise.all(
promises.map((item) =>
Promise.resolve(item).then(
(value) => ({ status: 'fulfilled', value }),
(reason) => ({ status: 'rejected', reason })
)
)
);
};

5.6. 手写 Promise.race

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
let len = promises.length;
if(len === 0) return;
for(let i = 0; i < len; i++) {
Promise.resolve(promise[i]).then(data => {
resolve(data);
return;
}).catch(err => {
reject(err);
return;
})
}
})
}

6. 手写防抖函数

1
2
3
4
5
6
7
8
9
function debounce(fn, delay) {
let timer;
return function (...args) {
timer && clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}

7. 手写节流函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 定时器版本
function throttle(fn, delay) {
let timer;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
};
}

// 时间戳版本
function throole(fn, delay) {
let st = 0;
return function (...args) {
let now = Date.now();
if (delay - (now - st) <= 0) {
fn.apply(this, args);
st = now;
}
};
}

8. 手写类型判断函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function getType(value) {
// 判断数据是 null 的情况
if (value === null) {
return value + "";
}
// 判断数据是引用类型的情况
if (typeof value === "object") {
let valueClass = Object.prototype.toString.call(value), // 重点
type = valueClass.split(" ")[1].split("");
type.pop();
return type.join("").toLowerCase();
} else {
// 判断数据是基本数据类型的情况和函数的情况
return typeof value;
}
}

9. 手写 call 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 给函数对象添加方法: mycall
Function.prototype.mycall = function(thisArg, ...otherArgs) {
// this -> 调用的函数对象
// thisArg -> 传入的第一个参数, 要绑定的this
// console.log(this) // -> 当前调用的函数对象

// 判断调用对象
if (typeof this !== "function") throw new TypeError("Error");

// 获取thisArg, 并且确保是一个对象类型
thisArg = (thisArg === null || thisArg === undefined)? window: Object(thisArg)

const fnKey = Symbol('fn');
Object.defineProperty(thisArg, fnKey, {
enumerable: false, // 不可枚举
configurable: true,
value: this
})
thisArg[fnKey](...otherArgs) // 通过thisArg调用方法, 相当于改变了this指向

delete thisArg[fnKey] // 最后要删除
}

10. 手写 apply 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Function.prototype.hyapply = function(thisArg, otherArgs) {

// 判断调用对象
if (typeof this !== "function") throw new TypeError("Error");

// 获取thisArg, 并且确保是一个对象类型
thisArg = (thisArg === null || thisArg === undefined)? window: Object(thisArg)
// 上方的window可以改成globalThis更严谨

const fnKey = Symbol('fn'); // 防止篡改thisArg原有的属性
Object.defineProperty(thisArg, fnKey, {
enumerable: false,
configurable: true,
value: this // this指向调用者
})
thisArg[fnKey](...otherArgs) // 和call的区别就是后面的参数是数组形式
delete thisArg[fnKey];
}

11. 手写 bind 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Function.prototype.hybind = function(thisArg, ...otherArgs) {

// 判断调用对象
if (typeof this !== "function") throw new TypeError("Error");

thisArg = thisArg === null || thisArg === undefined ? window: Object(thisArg)
const fnKey = Symbol('fn'); // 防止篡改thisArg原有的属性
Object.defineProperty(thisArg, fnKey, {
enumerable: false,
configurable: true,
writable: false,
value: this
})

return (...newArgs) => { // 返回一个函数拿去调用
var allArgs = [...otherArgs, ...newArgs] // 可能还会传参数
thisArg[fnKey](...allArgs)
// 因为下次调用还会使用这个fnKey , 所以不要删除
}
}

12. 手写柯里化函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function foo(a,b,c){
return a+b+c;
}
function curring(fn){
function cur(...args){
if(args.length>=fn.length){
return fn.apply(this,args);
}else{
return function(...newargs){
return cur.apply(this,args.concat(newargs));
}
}
}
return cur;
}
let add=curring(foo);
console.log(add(1,2,3)); // 6
console.log(add(1)(2)(3)); // 6

13. 实现AJAX请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 创建 Http 请求
xhr.open("GET", SERVER_URL, true);
// 设置状态监听函数
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 当请求成功时
if (this.status === 200) {
handle(this.response);
} else {
console.error(this.statusText);
}
};
// 设置请求失败时的监听函数
xhr.onerror = function() {
console.error(this.statusText);
};
// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 发送 Http 请求
xhr.send(null);

14. 使用Promise封装AJAX请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function getJSON(url) {
let promise = new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
if (this.status === 200) {
resolve(this.response); // 注意
} else {
reject(new Error(this.statusText));
}
};
xhr.onerror = function() {
reject(new Error(this.statusText));
};
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
xhr.send(null);
});
return promise;
}

15. 浅拷贝的各种实现方法

手写浅拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
const shallowClone = (target) => {
if (typeof target === 'object' && target !== null) {
const cloneTarget = Array.isArray(target) ? []: {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = target[prop];
}
}
return cloneTarget;
} else {
return target;
}
}

Object.assign

1
2
3
let obj = { name: 'sy', age: 18 };
const obj2 = Object.assign({}, obj, {name: 'sss'});
console.log(obj2);//{ name: 'sss', age: 18 }

数组方法 concat

1
2
3
4
let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr);//[ 1, 2, 3 ]

数组方法 slice

1
2
3
4
5
let arr = [1, 2, 3];
let newArr = arr.slice();
newArr[0] = 100;

console.log(arr);//[1, 2, 3]

…展开运算符

1
2
let arr = [1, 2, 3];
let newArr = [...arr];//跟arr.slice()是一样的效果

16. 深拷贝的实现方法

JSON.parse(JSON.stringify(obj))

它的原理就是利用JSON.stringify 将js对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象。这时候两个对象的引用就不相同了

1
2
3
4
5
6
let obj1 = { a: 0, b: {c: 0} };
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}

函数库lodash的_.cloneDeep方法

1
2
3
4
5
6
7
8
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);// false

⭐ 手写实现深拷贝函数

具体优化细节可以看18_深拷贝-事件总线

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
function isObject(originValue) {
const myType = typeof originValue;
return myType != null && (myType === "function" || myType === "object");
}

function deepCopy(originValue, map = new WeakMap()) {

// 1.如果值是Symbol的类型
if (typeof originValue === "symbol") {
return Symbol(originValue.description)
}

// 2.如果是原始类型或者promise, 直接返回
// 如果你深拷贝一个 Promise 对象,可能会破坏其异步行为,并且导致意外行为。
// 这样可以避免破坏异步行为和意外行为。
if (!isObject(originValue) || originValue instanceof Promise) {
return originValue
}

// 3.如果是set类型
if (originValue instanceof Set) {
const newSet = new Set()
for (const setItem of originValue) {
newSet.add(deepCopy(setItem))
}
return newSet
}

// 4. 判断值是否为map类型
if (originValue instanceof Map) {
const newMap = new Map();
for (const [i, j] of originValue) {
newMap.set(deepCopy(i, map), deepCopy(j, map));
}
return newMap;
}

// 5. 处理日期对象
if (originValue instanceof Date) {
return new Date(originValue.getTime());
}

// 6. 处理正则表达式对象
if (originValue instanceof RegExp) {
return new RegExp(originValue);
}

// 7.如果是函数function类型, 不需要进行深拷贝
if (typeof originValue === "function") {
return originValue
}

// 8.处理循环引用
if (map.get(originValue)) {
return map.get(originValue)
}

// 9.判断是对象或者数组类型
const newObj = Array.isArray(originValue) ? []: {}
map.set(originValue, newObj)

// 遍历普通的key
for (const key in originValue) {
newObj[key] = deepCopy(originValue[key], map);
}

// 单独遍历symbol
const symbolKeys = Object.getOwnPropertySymbols(originValue)
for (const symbolKey of symbolKeys) {
newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey], map)
}

return newObj
}

17. 判断循环引用的方法

原生

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
const isCycleObject = (obj, parent) => {
const parentArr = parent || [obj]; // 如果没有传入父级, parentArr就代表包含当前对象的数组
console.log(parentArr);
for (let i in obj) {
if (typeof obj[i] === "object") {
let flag = false;
parentArr.forEach((pObj) => {
if (pObj === obj[i]) {
flag = true;
}
});
if (flag) return true;
flag = isCycleObject(obj[i], [...parentArr, obj[i]]); // 递归调用这个子对象
if (flag) return true;
}
}
return false;
};

var a = { b: { c: {} } };

a.b.c.d = a;

console.log(isCycleObject(a));
// true

递归 + 哈希表

1
2
3
4
5
6
7
8
9
10
11
function isCircular(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return false
if (hash.has(obj)) return true
hash.set(obj, true)
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
if (isCircular(obj[key], hash)) return true
}
}
return false
}

数据处理

1. 实现数组的扁平化

实现数组扁平化有 6 种方法

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
ary = ary.flat(Infinity); // Infinity 代表扁平多少层
ary = str.replace(/(\[|\])/g, '').split(',') // 把括号全部替换为空字符串
let arr = [1, [2, [3, 4, 5]]];
let fn = function(ary) {
let result = [];
for(let i = 0; i < ary.length; i++) {
let item = ary[i];
if (Array.isArray(item)){
result = result.concat(fn(item));
} else {
result.push(item);
}
}
return result
}
console.log(fn(arr)) // [ 1, 2, 3, 4, 5 ]
function flatten(ary) {
return ary.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
}
//只要有一个元素有数组,那么循环继续
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.toString().split(',').map(Number);
}
console.log(flatten(arr)); // [ 1, 2, 3, 4 ]

以上数组 arr.toString() 之后 直接变成了 1,2,3,4

2. 实现数组各个方法

flat

1
2
3
4
5
6
7
8
9
10
11
12
function _flat(arr, depth) {
if(!Array.isArray(arr) || depth <= 0) {
return arr;
}
return arr.reduce((prev, cur) => {
if (Array.isArray(cur)) {
return prev.concat(_flat(cur, depth - 1))
} else {
return prev.concat(cur);
}
}, []);
}

push

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
// 简便写法
Array.prototype.push = function() {
for( let i = 0 ; i < arguments.length ; i++){
this[this.length] = arguments[i] ;
}
return this.length;
}



Array.prototype.push = function(...items) {
let O = Object(this);
let len = this.length >>> 0;
let argCount = items.length >>> 0;
// 2 ** 53 - 1 为JS能表示的最大正整数
if (len + argCount > 2 ** 53 - 1) {
throw new TypeError("The number of array is over the max value restricted!")
}
for(let i = 0; i < argCount; i++) {
O[len + i] = items[i];
}
let newLength = len + argCount;
O.length = newLength;
return newLength;
}

pop

1
2
3
4
5
6
7
8
9
10
11
12
13
Array.prototype.pop = function() {
let O = Object(this);
let len = this.length >>> 0;
if (len === 0) {
O.length = 0;
return undefined;
}
len --;
let value = O[len];
delete O[len];
O.length = len;
return value;
}

filter

1
2
3
4
5
6
7
8
9
10
Array.prototype._filter = function(fn) {
if (typeof fn !== "function") {
throw Error('参数必须是一个函数');
}
const res = [];
for (let i = 0, len = this.length; i < len; i++) {
fn(this[i]) && res.push(this[i]);
}
return res;
}

map

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
// 简便写法
Array.prototype.map = function(cb,thisArg){
if(!Array.isArray(this) || typeof cb != 'function'){
throw new Error('has Error, please check!')
}
var res = []
for(var i = 0; i < this.length; i++){
//使用call方法来改变this指向即可
res.push(cb.call(thisArg,this[i],i,this))
}
return res;
}



Array.prototype._map = function(fn) {
if (typeof fn !== "function") {
throw Error('参数必须是一个函数');
}
const res = [];
for (let i = 0, len = this.length; i < len; i++) {
res.push(fn(this[i]));
}
return res;
}

reduce

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
Array.prototype.reduce  = function(callbackfn, initialValue) {
// 异常处理,和 map 一样
if (this === null || this === undefined) {
throw new TypeError("Cannot read property 'reduce' of null or undefined");
}
if (Object.prototype.toString.call(callbackfn) != "[object Function]") { //注意中间无逗号
throw new TypeError(callbackfn + ' is not a function')
}
let k = 0; // 后面要重复利用
let O = Object(this); // 先将调用者转为对象
let len = O.length >>> 0; // 确认为整数
let accumulator = initialValue; // 积累值
if (accumulator === undefined) { // 没传初始值
for(; k < len ; k++) {
if (k in O) {
accumulator = O[k];
k++;
break;
}
}
}
// 表示数组全为空
if(k === len && accumulator === undefined)
throw new Error('Each element of the array is empty');

for(;k < len; k++) {
if (k in O) {
accumulator = callbackfn.call(undefined, accumulator, O[k], k, O); // 注意,核心!
}
}
return accumulator;
}

sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var quick_sort = function(a, l, r){
if(l >= r)return;
let i = l - 1, j = r + 1;
let mid = l + r >> 1;
let x = a[mid];
while(i < j){
do i++; while(a[i] < x);
do j--; while(a[j] > x);
if(i < j){
let z = a[i];
a[i] = a[j];
a[j] = z;
}
}
quick_sort(a, l, j);
quick_sort(a, j + 1, r);
}
var q = [1,44,6,713,76];
quick_sort(q, 0, q.length-1);
console.log(q); // [ 1, 6, 44, 76, 713 ]

splice 方法(了解原理)

代码
Array.prototype.splice = function(startIndex, deleteCount, ...addElements)  {
  // 1. 初始工作
  let argumentsLen = arguments.length;
  let array = Object(this);
  let len = array.length;
  let deleteArr = new Array(deleteCount);

// 2. 参数的清洗工作

// 当用户传来非法的 startIndex 和 deleteCount 或者负索引的时候,需要我们做出特殊的处理。
if (startIndex < 0) {
startIndex = startIndex + len > 0 ? startIndex + len: 0;
}
else startIndex = startIndex >= len ? len: startIndex;

// 删除数目没有传,默认删除startIndex及后面所有的
if (argumentsLen === 1) deleteCount = len - startIndex;
else if (deleteCount < 0) deleteCount = 0 // 删除数目过小
else if (deleteCount > len - startIndex) deletCount = len - startIndex; //删除数目过大

// 3. 判断 sealed 对象和 frozen 对象, 即 密封对象 和 冻结对象
if (Object.isSealed(array) &amp;&amp; deleteCount !== addElements.length) {
throw new TypeError('the object is a sealed object!')

} else if(Object.isFrozen(array) && (deleteCount > 0 || addElements.length > 0)) {
throw new TypeError(‘the object is a frozen object!’)
}

// 4.拷贝删除的元素
for (let i = 0; i < deleteCount; i++) {
let index = startIndex + i;
if (index in array) {
let current = array[index];
deleteArr[i] = current;
}
}

// 5.移动删除元素后面的元素

// 对删除元素后面的元素进行挪动, 挪动分为三种情况:
// * 添加的元素和删除的元素个数相等
// * 添加的元素个数小于删除的元素
// * 添加的元素个数大于删除的元素

if (deleteCount === addElements.length) // 就什么也不管
else if(deleteCount > addElements.length) {
// 删除的元素比新增的元素多,那么后面的元素整体向前挪动
for (let i = startIndex + deleteCount; i < len; i++) {
let fromIndex = i; // 移动元素的起始位置
let toIndex = i - (deleteCount - addElements.length); // 将要挪动到的目标位置
if (fromIndex in array) {
array[toIndex] = array[fromIndex];
} else {
delete array[toIndex];
}
}
// 注意注意!这里我们把后面的元素向前挪,相当于数组长度减小了,需要删除冗余元素
// 目前长度为 len + addElements - deleteCount
for (let i = len - 1; i >= len + addElements.length - deleteCount; i –) {
delete array[i];
}
}
else if(deleteCount < addElements.length) {
// 删除的元素比新增的元素少,那么后面的元素整体向后挪动
// 思考一下: 这里为什么要从后往前遍历?从前往后会产生什么问题?
for (let i = len - 1; i >= startIndex + deleteCount; i–) {
let fromIndex = i; // 挪动元素的初始位置
let toIndex = i + (addElements.length - deleteCount); // 将要挪动到的目标位置
if (fromIndex in array) {
array[toIndex] = array[fromIndex];
} else {
delete array[toIndex];
}
}
}

// 6.插入新元素
for (let i = 0; i < addElements.length; i++) {
array[startIndex + i] = addElements[i];
}
array.length = len - deleteCount + addElements.length;

// 7.返回被删除元素组成的数组
return deleteArr;
}

实现数组的乱序输出

1
2
3
4
5
6
7
var arr = [1,2,3,4,5,6,7,8,9,10];
for (var i = 0; i < arr.length; i++) {
// 注意最后加i就行, 否则会重复输出同一个值
const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i;
[arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
}
console.log(arr)

实现字符串的 repeat 方法

1
2
3
function repeat(s, n) {
return (new Array(n + 1)).join(s);
}

3. 实现字符串各个方法

repeat

1
2
3
function repeat(s, n) {
return (new Array(n + 1)).join(s);
}

4. 将数字每千分位用逗号隔开

数字无小数版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let format = n => {
let num = n.toString()
let len = num.length
if (len <= 3) {
return num
} else {
let remainder = len % 3
if (remainder > 0) { // 不是3的整数倍
// 每隔三个数字用逗号分隔为字符串
return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',')
} else { // 是3的整数倍
return num.slice(0, len).match(/\d{3}/g).join(',')
}
}
}
format(1232323) // '1,232,323'

数字有小数版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let format = n => {
let num = n.toString() // 转成字符串
let decimals = ''
// 判断是否有小数
num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals
let len = num.length
if (len <= 3) {
return num
} else {
let temp = ''
let remainder = len % 3
decimals ? temp = '.' + decimals : temp
if (remainder > 0) { // 不是3的整数倍
return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp
} else { // 是3的整数倍
return num.slice(0, len).match(/\d{3}/g).join(',') + temp
}
}
}
format(12323.33) // '12,323.33'

5. 手写日期格式化函数

1
2
3
4
5
6
7
8
9
10
11
12
const dateFormat = (dateInput, format)=>{
var day = dateInput.getDate()
var month = dateInput.getMonth() + 1
var year = dateInput.getFullYear()
format = format.replace(/yyyy/, year)
format = format.replace(/MM/,month)
format = format.replace(/dd/,day)
return format
}
dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01
dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01
dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日

6. 实现大整数相加

来自算法篇章

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let a = '13124342343353535235'
let b = '34423434234234243233'
// 两个字符串a, b
let arr1 = a.split('').map(Number);
let arr2 = b.split('').map(Number);
let res = [];
let flag = 0;
while(arr1.length > 0 || arr2.length > 0) {
let t1 = arr1.pop() || 0; // 判断arr1是否已经为空
let t2 = arr2.pop() || 0; // 判断arr2是否已经为空
let tmp = t1 + t2 + flag;
flag = Math.floor(tmp / 10)
res.unshift(tmp % 10)
}
if(flag) res.unshift(flag);
res = res.join('')
console.log(res); // 47547776577587778468

7. 解析 URL Params 为对象

1
2
3
4
5
6
7
8
9
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 结果
{ user: 'anonymous',
id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
city: '北京', // 中文需解码
enabled: true, // 未指定值得 key 约定为 true
}
*/

exec 是正则的方法

exec() 方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function parseParam(url) {
const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来❗
const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach(param => {
if (/=/.test(param)) { // 处理有 value 的参数, 存在 '='
let [key, val] = param.split('='); // 分割 key 和 value
val = decodeURIComponent(val); // 解码❗ 注意还需解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
paramsObj[key] = [].concat(paramsObj[key], val); // 将其变为数组, 因为有多个值, 就用数组存起来
} else { // 如果对象没有这个 key,创建 key 并设置值
paramsObj[key] = val;
}
} else { // 处理没有 value 的参数
paramsObj[param] = true;
}
})
return paramsObj;
}

手写函数汇总
http://example.com/2023/09/15/手写函数/
作者
weirdo
发布于
2023年9月15日
更新于
2023年10月24日
许可协议