js 手写
# js 手写
# 1.js 手写 ajax
(1) 简单实现
// 1.创建异步对象
let xhr = new XMLHttpRequest();
// 2.设置请求的参数
xhr.open(method, url, async);
// 3.发送请求
xhr.send(data);
// 4.注册事件
xhr.onreadystatechange = () => {
if (xhr.readyStatus === 4 && xhr.status === 200) {
// 5.获取返回的数据。
console.log(xhr.responseText);
}
};
2
3
4
5
6
7
8
9
10
11
12
13
(2) 基于 promise 实现
function ajax(options) {
// 请求地址
const url = options.url;
// 请求方法
const method = options.method.toLocaleLowerCase() || "get";
// 默认为异步true
const async = options.async;
// 请求参数
const data = options.data;
// 实例化
const xhr = new XMLHttpRequest();
// 请求超时
if (options.timeout && options.timeout > 0) {
xhr.timeout = options.timeout;
}
// 返回一个Promise实例
return new Promise((resolve, reject) => {
xhr.ontimeout = () => reject && reject("请求超时");
// 监听状态变化回调
xhr.onreadystatechange = () => {
if (xhr.readyState == 4) {
// 200-300 之间表示请求成功,304资源未变,取缓存
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
resolve && resolve(xhr.responseText);
} else {
reject && reject();
}
}
};
// 错误回调
xhr.onerror = (err) => reject && reject(err);
let paramArr = [];
let encodeData;
// 处理请求参数
if (data instanceof Object) {
for (let key in data) {
// 参数拼接需要通过 encodeURIComponent 进行编码
paramArr.push(
encodeURIComponent(key) + "=" + encodeURIComponent(data[key])
);
}
encodeData = paramArr.join("&");
}
// get请求拼接参数
if (method === "get") {
// 检测url中是否已存在 ? 及其位置
const index = url.indexOf("?");
if (index === -1) url += "?";
else if (index !== url.length - 1) url += "&";
// 拼接url
url += encodeData;
}
// 初始化
xhr.open(method, url, async);
// 发送请求
if (method === "get") xhr.send(null);
else {
// post 方式需要设置请求头
xhr.setRequestHeader(
"Content-Type",
"application/x-www-form-urlencoded;charset=UTF-8"
);
xhr.send(encodeData);
}
});
}
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
# 2.js 手写 promise
https://www.cnblogs.com/sugar-tomato/p/11353546.html (opens new window)
class Promise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn());
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
// 解决 onFufilled,onRejected 没有传值的问题
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
// 每次调用 then 都返回一个新的 promise Promise/A+ 2.2.7
let promise2 = new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
resolvePromise(promise2, onFulfilled(this.value), resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'rejected') {
setTimeout(() => {
try {
resolvePromise(promise2, onRejected(this.reason), resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
resolvePromise(promise2, onFulfilled(this.value), resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
resolvePromise(promise2, onRejected(this.reason), resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
};
});
return promise2;
}
// catch 方法
catch(onRejected) {
return this.then(undefined, onRejected);
}
static resolve(val) {
return new Promise((resolve, reject) => {
resolve(val)
});
}
//添加 reject 方法
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
}
//添加 all 方法
static all(promises) {
//返回结果为promise对象
return new Promise((resolve, reject) => {
//声明变量
let count = 0;
let arr = [];
//遍历
for (let i = 0; i < promises.length; i++) {
//
promises[i].then(
(v) => {
//得知对象的状态是成功
//每个promise对象 都成功
count++;
//将当前promise对象成功的结果 存入到数组中
arr[i] = v;
//判断
if (count === promises.length) {
//修改状态
resolve(arr);
}
},
(r) => {
reject(r);
}
);
}
});
}
// 添加 race 方法
static race(promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(
(v) => {
//修改返回对象的状态为 『成功』
resolve(v);
},
(r) => {
//修改返回对象的状态为 『失败』
reject(r);
}
);
}
});
}
// 添加 finally 方法
static finally(onFinished) {
return this.then((val) => {
onFinished();
return val;
}).catch((err) => {
onFinished();
return err;
});
}
}
// 解决链式调用和循环引用问题
function resolvePromise(promise2, x, resolve, reject) {
// 循环引用报错 Promise/A+ 2.3.1
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called;
// x不是null 且x是对象或者函数
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
// 如果then是函数,就默认是promise了
if (typeof then === 'function') {
// 就让then执行 第一个参数是this 后面是成功的回调 和 失败的回调
then.call(x, y => {
// 成功和失败只能调用一个
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err => {
// 成功和失败只能调用一个
if (called) return;
called = true;
reject(err);
})
} else {
resolve(x);
}
} catch (e) {
// 也属于失败
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
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
参考:https://developer.aliyun.com/article/613412 (opens new window)
# 3.js 实现简单双向绑定
<input type="text" id="input" />
<div id="show"></div>
<script>
function defineProperty(obj, attr) {
var val;
Object.defineProperty(obj, attr, {
get: function() {
return val;
},
set: function(newValue) {
if (newValue === val) {
return;
}
val = newValue;
document.getElementById("input").value = newValue;
document.getElementById("show").innerHTML = newValue;
},
});
}
var obj = {};
defineProperty(obj, "txt");
document.getElementById("input").addEventListener("keyup", function(e) {
obj.txt = e.target.value;
});
</script>
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
# 4. js 手写发布订阅
//声明类
class EventBus {
// 事件存储中心
constructor() {
this.eventList = {}; //创建对象收集事件
}
// 订阅:订阅者注册事件到调度中心
$on(eventName, fn) {
this.eventList[eventName]
? this.eventList[eventName].push(fn)
: (this.eventList[eventName] = [fn]);
}
// 发布:发布者发布事件到调度中心,调度中心处理代码
$emit(eventName) {
if (!eventName) throw new Error("请传入事件名");
//获取订阅传参
const data = [...arguments].slice(1);
if (this.eventList[eventName]) {
this.eventList[eventName].forEach((i) => {
try {
i(...data); // 轮询事件
} catch (e) {
console.error(e + "eventName:" + eventName); // 收集执行时的报错
}
});
}
}
//执行一次
$once(eventName, fn) {
const _this = this;
function onceHandle() {
fn.apply(null, arguments);
_this.$off(eventName, onceHandle); //执行成功后取消监听
}
this.$on(eventName, onceHandle);
}
//取消订阅
$off(eventName, fn) {
//不传入参数时取消全部订阅
if (!arguments.length) {
return (this.eventList = {});
}
//eventName传入的是数组时,取消多个订阅
if (Array.isArray(eventName)) {
return eventName.forEach((event) => {
this.$off(event, fn);
});
}
//不传入fn时取消事件名下的所有队列
if (arguments.length === 1 || !fn) {
this.eventList[eventName] = [];
}
//取消事件名下的fn
this.eventList[eventName] = this.eventList[eventName].filter(
(f) => f !== fn
);
}
}
const event = new EventBus();
let b = function(v1, v2, v3) {
console.log("b", v1, v2, v3);
};
let a = function() {
console.log("a");
};
event.$once("test", a);
event.$on("test", b);
event.$emit("test", 1, 2, 3, 45, 123);
event.$off(["test"], b);
event.$emit("test", 1, 2, 3, 45, 123);
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
# 5. js 手写 jsonp
function jsonp(url, data = {}, callback = "callback") {
//处理json对象,拼接url
data.callback = callbak;
let params = [];
for (let key in data) {
params.push(key + "=" + data[key]);
}
let script = document.creatElement("script");
script.src = url + "?" + params.join("&");
document.body.appendChild(script);
//返回Promise
return new Promise((resolve, reject) => {
window[callback] = (data) => {
try {
resolve(data);
} catch (e) {
reject(e);
} finally {
//移除script元素
script.parentNode.removeChild(script);
console.log(script);
}
};
});
}
//请求数据
jsonp(
"http://baidu.cn/aj/index",
{
page: 1,
cate: "recommend",
},
"jsoncallback"
).then((data) => {
console.log(data);
});
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
# 6. js 大数相加
function bigNumAdd(num1, num2) {
// 首先检查传来的大数是否是字符串类型,如果传Number类型的大数,在传入的时候已经丢失精度了,
// 就如 如果传入11111111111111111,处理的时候已经是丢失精度的11111111111111112了,则需要传入
// 字符串类型的数字 '11111111111111111'
const checkNum = (num) => typeof num === "string" && !isNaN(Number(num));
if (checkNum(num1) && checkNum(num2)) {
// 将传入的数据进行反转,从前向后依次加和,模拟个,十,百依次向上加和
const tmp1 = num1.split("").reverse();
const tmp2 = num2.split("").reverse();
const result = [];
// 格式化函数,主要针对两个大数长度不一致时,超长的数字的格式化为0
const format = (val) => {
if (typeof val === "number") return val;
if (!isNaN(Number(val))) return Number(val);
return 0;
};
let temp = 0;
// 以较长的数字为基准进行从前往后逐个加和,为避免两个数相加最高位进位后,导
// 致结果长度大于两个数字中的长度,for循环加和长度为最长数字长度加一
for (let i = 0; i <= Math.max(tmp1.length, tmp2.length); i++) {
const addTmp = format(tmp1[i]) + format(tmp2[i]) + temp;
// 当加和的数字大于10的情况下,进行进位操作,将要进位的数字赋值给temp,在下一轮使用
result[i] = addTmp % 10;
temp = addTmp > 9 ? 1 : 0;
}
// 计算完成,反转回来
result.reverse();
// 将数组for中多加的一位进行处理,如果最高位没有进位则结果第一个数位0,
// 结果第一个数位1,则发生了进位。 如99+3,最大数字长度位2,结果数长度位3
// 此时结果的第一位为1,发生了进位,第一位保留,如果是2+94,第一位为0,则不保留第一位
const resultNum =
result[0] > 0 ? result.join("") : result.join("").slice(1);
console.log("result", resultNum);
} else {
return "big number type error";
}
}
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
# JS 手写函数方法
# 1.函数节流
https://www.cnblogs.com/momo798/p/9177767.html (opens new window)
当持续触发事件时,保证一定时间段内只调用一次事件处理函数
防抖和节流的区别在于连续触发时执行的次数。如果只想在最后执行一次的场景,需要用防抖技术。如果想在期间以固定频率触发,则使用节流。
(1) 节流 throttle 代码(时间戳)
// 节流函数:fn:要被节流的函数,delay:规定的时间
function throttle(func, delay) {
var prev = Date.now();
return function() {
var context = this;
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
节流 throttle 代码(定时器):
var throttle = function(func, delay) {
var timer = null;
return function() {
var context = this;
var args = arguments;
if (!timer) {
timer = setTimeout(function() {
func.apply(context, args);
timer = null;
}, delay);
}
};
};
2
3
4
5
6
7
8
9
10
11
12
13
节流 throttle 代码(时间戳+定时器):当第一次触发事件时马上执行事件处理函数,最后一次触发事件后也还会执行一次事件处理函数。
var throttle = function(func, delay) {
var timer = null;
var startTime = Date.now();
return function() {
var curTime = Date.now();
var remaining = delay - (curTime - startTime);
var context = this;
var args = arguments;
clearTimeout(timer);
if (remaining <= 0) {
func.apply(context, args);
startTime = Date.now();
} else {
timer = setTimeout(func, remaining);
}
};
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2.函数防抖
当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
function debounce(fn, delay) {
var timer = null;
// 清除上一次延时器
return function() {
clearTimeout(timer);
// 重新设置一个新的延时器
timer = setTimeout(() => {
fn.call(this);
}, delay);
};
}
2
3
4
5
6
7
8
9
10
11
# 3.js 实现柯里化
// 支持多参数传递
function currying(fn, ...args) {
return function() {
let rest = [...args, ...arguments];
if (rest.length < fn.length) {
return currying.call(this, fn, ...rest);
} else {
return fn.apply(this, rest);
}
};
}
2
3
4
5
6
7
8
9
10
11
# 4.用 setTimeout 实现 setInterval
function mySetInterval(fn, millisec) {
function interval() {
setTimeout(interval, millisec);
fn();
}
setTimeout(interval, millisec);
}
2
3
4
5
6
7
# js 手写对象方法
# 1. js 深拷贝
function deepClone(data) {
// 可遍历类型 Map Set Object Array
const typeMap = "[object Map]";
const typeSet = "[object Set]";
const typeObject = "[object Object]";
const typeArray = "[object Array]";
// 非原始类型的 不可遍历类型 Date RegExp Function
const typeDate = "[object Date]";
const typeRegExp = "[object RegExp]";
const typeFunction = "[object Function]";
// 非原始类型的 不可遍历类型的 集合(原始类型已经被过滤了不用再考虑了)
const notForType = [typeDate, typeRegExp, typeFunction];
// 是否是引用类型
const isObject = (target) => {
if (target === null) {
return false;
} else {
const type = typeof target;
return type === "object" || type === "function";
}
};
// 获取标准类型
const getType = (target) => {
return Object.prototype.toString.call(target);
};
/*
* 1、处理原始类型 Number String Boolean Symbol Null Undefined
* 2、处理不可遍历类型 Date RegExp Function
* 3、处理循环引用情况 WeakMap
* 4、处理可遍历类型 Set Map Array Object
* */
const clone = (target, map = new WeakMap()) => {
// 处理原始类型直接返回(Number BigInt String Boolean Symbol Undefined Null)
if (!isObject(target)) {
return target;
}
// 处理不可遍历类型
const type = getType(target);
if (notForType.includes(type)) {
switch (type) {
case typeDate:
// 日期
return new Date(target);
case typeRegExp:
// 正则
const reg = /\w*$/;
const regExp = new RegExp(target.source, reg.exec(target)[0]);
regExp.lastIndex = target.lastIndex; // lastIndex 表示每次匹配时的开始位置
return regExp;
case typeFunction:
// 函数
return target;
default:
return target;
}
}
// 用于返回
let result;
// 处理循环引用
if (map.get(target)) {
// 已经放入过map的直接返回
return map.get(target);
}
// 处理可遍历类型
switch (type) {
case typeSet:
// Set
result = new Set();
map.set(target, result);
target.forEach((item) => {
result.add(clone(item, map));
});
return result;
case typeMap:
// Map
result = new Map();
map.set(target, result);
target.forEach((item, key) => {
result.set(key, clone(item, map));
});
return result;
case typeArray:
// 数组
result = [];
map.set(target, result);
target.forEach((item, index) => {
result[index] = clone(item, map);
});
return result;
case typeObject:
// 对象
result = {};
map.set(target, result);
[
...Object.keys(target),
...Object.getOwnPropertySymbols(target),
].forEach((item) => {
// item为键
result[item] = clone(target[item], map);
});
return result;
default:
return target;
}
};
return clone(data);
}
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
# 2. js 手写 new 方法
/*
* 1.创建一个空对象
* 2.链接到原型
* 3.绑定this值
* 4.返回新对象
*/
function createNew() {
let obj = {}; // 1.创建一个空对象
// 把类数组对象转为数组对象,shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
let constructor = [].shift.call(arguments); // 取出并删除类数组中的第一个构造函数元素
obj.__proto__ = constructor.prototype; // 2.链接到原型
// 3.绑定this值 使用apply,将构造函数中的this指向新对象,这样新对象就可以访问构造函数中的属性和方法
let result = constructor.apply(obj, arguments);
// 如果fn返回的是null或undefined(也就是不返回内容),我们返回的是obj,否则返回rel
return typeof result === 'object' ? result : obj; // 4.返回新对象
}
function People(name, age) {
this.name = name;
this.age = age;
}
let peo = createNew(People, 'Bob', 22);
console.log(peo.name);
console.log(peo.age);
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
# 3. js 实现 create
// 思路:将传入的对象作为原型
function create(obj) {
function F() {}
F.prototype = obj;
return new F();
}
2
3
4
5
6
# 4.js 实现 instanceof
// 思路:右边变量的原型存在于左边变量的原型链上
function instanceof(left, right) {
let leftValue = left.__proto__;
let rightValue = right.prototype;
while (true) {
if (leftValue === null) {
return false;
}
if (leftValue === rightValue) {
return true;
}
leftValue = leftValue.__proto__;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 5. js 实现 call
参考地址:https://www.cnblogs.com/vickylinj/p/14427534.html (opens new window)
// 思路:将要改变this指向的方法挂到目标this上执行并返回
Function.prototype.mycall = function(context) {
if (typeof this !== "function") {
throw new TypeError("not funciton");
}
context = context || window;
context.fn = this;
let arg = [...arguments].slice(1); // 取出除了第一个参数的其它参数
let result = context.fn(...arg);
delete context.fn;
return result;
};
2
3
4
5
6
7
8
9
10
11
12
# 6. js 实现 apply
// 思路:将要改变this指向的方法挂到目标this上执行并返回
Function.prototype.myapply = function(context) {
if (typeof this !== "function") {
throw new TypeError("not funciton");
}
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;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 7. js 实现 bind
// 思路:类似call,但返回的是函数
Function.prototype.mybind = function(context) {
if (typeof this !== "function") {
throw new TypeError("Error");
}
let _this = this;
let arg = [...arguments].slice(1); // 取出除了第一个参数的其它参数
return function F() {
// new的方式调用,不会被任何方式改变this,所以对于这种情况我们需要忽略传入的this
if (this instanceof F) {
return new _this(...arg, ...arguments);
} else {
// 函数直接调用
return _this.apply(context, arg.concat(...arguments));
}
};
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 8.对象扁平化
Object.flatten = function(obj) {
var result = {};
function recurse(src, prop) {
var toString = Object.prototype.toString;
if (toString.call(src) == "[object Object]") {
var isEmpty = true;
for (var p in src) {
isEmpty = false;
recurse(src[p], prop ? prop + "." + p : p);
}
if (isEmpty && prop) {
result[prop] = {};
}
} else if (toString.call(src) == "[object Array]") {
var len = src.length;
if (len > 0) {
src.forEach(function(item, index) {
recurse(item, prop ? prop + ".[" + index + "]" : index);
});
} else {
result[prop] = [];
}
} else {
result[prop] = src;
}
}
recurse(obj, "");
return result;
};
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
# 9.对象反扁平化
Object.unflatten1 = function(data) {
if (Object(data) !== data || Array.isArray(data)) return data;
var result = {},
cur,
prop,
idx,
last,
temp;
for (var p in data) {
(cur = result), (prop = ""), (last = 0);
do {
idx = p.indexOf(".", last);
temp = p.substring(last, idx !== -1 ? idx : undefined);
cur = cur[prop] || (cur[prop] = !isNaN(parseInt(temp)) ? [] : {});
prop = temp;
last = idx + 1;
} while (idx >= 0);
cur[prop] = data[p];
}
return result[""];
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# js 手写数组方法
# 1. js 手写 map
Array.prototype._map = function(callback, thisArg = this) {
if (this == undefined) {
throw new TypeError("this is null or not undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
let res = [];
let arr = this; // [1, 2, 3]
for (let i = 0; i < arr.length; i++) {
res[i] = callback.call(thisArg, arr[i], i, arr);
}
return res;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2. js 手写 forEach
Array.prototype._forEach = function(callback, thisArg = this) {
if (this == undefined) {
throw new TypeError("this is null or not undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
let arr = this; // [1, 2, 3]
for (let i = 0; i < arr.length; i++) {
callback.call(thisArg, arr[i], i, arr);
}
};
2
3
4
5
6
7
8
9
10
11
12
# 3. js 手写 filter
Array.prototype._filter = function(callback, thisArg = this) {
if (this == undefined) {
throw new TypeError("this is null or not undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
let arr = this;
let res = [];
for (let i = 0; i < arr.length; i++) {
if (callback.call(thisArg, arr[i], i, arr)) {
res.push(arr[i]);
}
}
return res;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4. js 手写 reduce
Array.prototype._reduce = function(callback, init) {
if (this == undefined) {
throw new TypeError("this is null or not undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
let arr = this;
let index = arguments.length === 1 ? 1 : 0; // 求索引。如果没有传初始值,那么index就是1,因为没有传初始值,prev就是初始值,下标是0,那么自然curr下标就是1
let prev = arguments.length === 1 ? arr[0] : init; // 求初始值。如果没有传入初始值,那么初始值就是数组的第一项,否则就是传入的初始值init
for (let i = index; i < arr.length; i++) {
prev = callback(prev, arr[i], i, arr); // 迭代累加
}
return prev;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 5. js 手写 every
Array.prototype._every = function(callback, thisArr = this) {
if (this == undefined) {
throw new TypeError("this is null or not undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
let arr = this;
for (let i = 0; i < arr.length; i++) {
if (!callback.call(thisArr, arr[i], i, arr)) {
return false;
}
}
return true;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 6. js 手写 some
Array.prototype._some = function(callback, thisArr = this) {
if (this == undefined) {
throw new TypeError("this is null or not undefined");
}
if (typeof callback !== "function") {
throw new TypeError(callback + " is not a function");
}
let arr = this;
for (let i = 0; i < arr.length; i++) {
if (callback.call(thisArr, arr[i], i, arr)) {
return true;
}
}
return false;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 7. js 手写 includes
Array.prototype._includes = function(searchElement, formIndex = 0) {
let arr = this;
let len = arr.length;
if (fromIndex >= len || !len) return false; // 如果传入起始位大于数据的长度,或者没有数据的
for (let i = formIndex; i < len; i++) {
if (arr[i] === searchElement)
// 当传入的数据和查找的数据一致,返回true
return true;
}
return false;
};
2
3
4
5
6
7
8
9
10
11
# 8. js 手写 indexOf
Array.prototype._indexOf = function(searchElement, formIndex = 0) {
let indexOf = this;
let len = indexOf.length;
if (formIndex >= len || !len) return -1; // 如果传入起始位大于数据的长度,或者没有数据的
for (let i = formIndex; i < len; i++) {
if (indexOf[i] === searchElement)
// 当传入的数据和查找的数据一致,返回true
return i;
}
return -1;
};
2
3
4
5
6
7
8
9
10
11
# 9.数组扁平化
function flatten(arr) {
return arr.reduce((result, item) => {
return result.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
2
3
4
5
# js 手写字符串方法
# 1.js 实现 IndexOf 方法
function indexfun(arr,val){
for(var i=0;i<arr.length;i++){
if(arr[i]===val){
return i;
}
}
return -1;
}
2
3
4
5
6
7
8
# 2.js 实现千分位
- 第一种方法 循环
function qianff(num) {
num = num + ""; //数字转字符串
var str = ""; //字符串累加
for (var i = num.length - 1, j = 1; i >= 0; i--, j++) {
if (j % 3 == 0 && i != 0) {
//每隔三位加逗号,过滤正好在第一个数字的情况
str += num[i] + ","; //加千分位逗号
continue;
}
str += num[i]; //倒着累加数字
}
return str
.split("")
.reverse()
.join(""); //字符串=>数组=>反转=>字符串
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
第一种方法 字符串截取拼接,
function qianff(str) { var strn = str.toString(), len = strn.length, ind = len % 3, newstr = strn.substr(0, ind); if (ind == 0) { newstr = strn.substr(0, 3); ind = 3; } for (i = ind; i < len; i = i + 3) { newstr += ',' + strn.substr(i, 3); } return newstr; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14第二种方法 正则
- 1、如果数字带有小数点的话,可以使用 toLocaleString()方法实现这个需求
var a = 8462948.24; b.toLocaleString(); //8,462,948.24
1
2
3- 2、不带小数点我们可以简单的写个正则去实现
(有小数点我们可以用 split()分割,得到数组,里面存储这整数和小数部分,使用正则匹配实现整数加入逗号,最后拼接起来。但是不如直接使用上面方法,简单粗暴。)
num.toString().replace(/(\d)(?=(?:\d{3})+$)/g,'$1,')
匹配规则: 将该num转化为字符串后,全局(/g)正向匹配,看是否符合断言(?=(?:\d{3})+$)部分,直到匹配结束。即遇到 数字 + 该数字后面紧跟连续的三位数字(并且不管这连续的三位数字出现多少次),符合则在该数字(’$1’)后加入逗号,替换的时候忽略(?:)这连续的三位数.
3、封装为函数
判读是否带有小数点,有则以情景 1 的方式实现。 没有小数点,就用正则匹配实
function numFormat(num) { var c = num.toString().indexOf(".") !== -1 ? num.toLocaleString() : num.toString().replace(/(\d)(?=(?:\d{3})+$)/g, "$1,"); return c; }
1
2
3
4
5
6
7
# 3.实现正则切分银行卡卡号(像实体卡一样四位一个空格)
/**
* 银行卡号格式化 每4位一分隔, 分隔符用","
* @param str: 字符串 必传
*/
function format_bankCard(str) {
str = str.toString(); // 必须是字符串
var yushu = str.length % 4;
if (yushu > 0) {
return str.match(/\d{4}/g).join(",") + "," + str.substr(-yushu, yushu);
} else {
return str.match(/\d{4}/g).join(",");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 4. js 替换字符串所有空格
> 使用库函数
function replaceSpace(str) {
return str.split(" ").join("");
}
> 不使用库函数
function replaceSpace(s) {
let res = '';
for(let i = 0 ; i < s.length; i++){
const ch = s[i];
if(ch == " ") {
res += "";
}
else {
res += ch;
}
}
return res;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 5. js 手写 JSON.stringify
function jsonStringify(obj) {
let type = typeof obj;
if (type !== "object") {
if (/string|undefined|function/.test(type)) {
obj = '"' + obj + '"';
}
return String(obj);
} else {
let json = [];
let arr = Array.isArray(obj);
for (let k in obj) {
let v = obj[k];
let type = typeof v;
if (/string|undefined|function/.test(type)) {
v = '"' + v + '"';
} else if (type === "object") {
v = jsonStringify(v);
}
json.push((arr ? "" : '"' + k + '":') + String(v));
}
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 6. js 手写 trim
//删除左右两端的空格
function trim(str) {
return str.replace(/(^\s*)|(\s*$)/g, "");
}
//删除左边的空格
function ltrim(str) {
return str.replace(/(^\s*)/g, "");
}
//删除右边的空格
function rtrim(str) {
return str.replace(/(\s*$)/g, "");
}
2
3
4
5
6
7
8
9
10
11
12
# js 手写 url 方法
# 1. js 把 url query 转成对象
> 首先用location.search获取url中"?"符后的字串,再调用GetRequest
function GetRequest(url) {
// var url = location.search; //获取url中"?"符后的字串
var theRequest = new Object();
if (url.indexOf('?') != -1) {
var str = url.substr(1);
strs = str.split('&');
for (var i = 0; i < strs.length; i++) {
theRequest[strs[i].split('=')[0]] = strs[i].split('=')[1];
}
}
return theRequest;
}
2
3
4
5
6
7
8
9
10
11
12
13
# 2. js 中的对象转成 url 参数
function queryParams(data, isPrefix) {
isPrefix = isPrefix ? isPrefix : false;
let prefix = isPrefix ? "?" : "";
let _result = [];
for (let key in data) {
let value = data[key];
// 去掉为空的参数
if (["", undefined, null].includes(value)) {
continue;
}
if (value.constructor === Array) {
value.forEach((_value) => {
_result.push(
encodeURIComponent(key) + "[]=" + encodeURIComponent(_value)
);
});
} else {
_result.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
}
}
return _result.length ? prefix + _result.join("&") : "";
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 3. js 获取 url 参数 query
function getQueryString(key, queryStr) {
var reg = new RegExp("(^|&)" + key + "=([^&]*)(&|$)", "i");
var r = queryStr.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}
2
3
4
5
6
7
8
前端面试手写题:https://github.com/Mayandev/fe-interview-handwrite (opens new window)