1.深拷贝
概念:深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响
const deepCopy = obj => {
let result;
// 判断参数的数据类型是不是对象?
if (typeof obj === 'object') {
//参数是数组还是对象
result = Array.isArray(obj) ? [] : {};
//for in , i 是下标,不管是数组还是对象
for (let i in obj) {
//如果obj[i] 类型是object继续递归,不是就直接赋值
result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
}
return result;
} else {
return obj;
}
};
2.观察订阅者模式
概念:观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
class obsver {
constructor() {
//事件类型的集合
this.events = {};
}
// 实现订阅
on(type, callBack) {
if (!this.events[type]) {
// 没有这个事件类型,默认赋值一个数组格式的
this.events[type] = [callBack];
} else {
this.events[type].push(callBack);
}
}
// 触发事件
emit(type, ...rest) {
//对应的事件遍历执行
this.events[type] && this.events[type].forEach(fn => fn.apply(this, rest));
}
// 删除订阅
off(type, callBack) {
if (!this.events[type]) return;
// 过滤掉callBack,重新赋值
this.events[type] = this.events[type].filter(item => {
return item !== callBack;
});
}
// 只执行一次订阅事件
once(type, callBack) {
function fn() {
// 立马执行完,就清除该事件
callBack();
this.off(type, fn);
}
//先订阅
this.on(type, fn);
}
}
// 使用如下
const newObsver = new obsver();
const handle = rest => {
console.log(rest);
};
newObsver.on('click', handle);
newObsver.emit('click', 1);
newObsver.off('click', handle);
newObsver.emit('click', 1);
newObsver.once('dbClick', () => {
console.log(123456);
});
newObsver.emit('dbClick');
newObsver.emit('dbClick');
3.手写reduce
概念:reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。对空数组是不会执行回调函数的
Array.prototype.reduce = function(fn, defaultValue) {
//调用的数组
const arr = this;
//检验是不是空的数组
if (Array.isArray(arr) && !arr.length) {
throw Error('Reduce of empty array with no initial value');
return;
}
// 累加器的初始值,有默认值就用默认值,没有用数组的第一项
let result = defaultValue || arr[0];
// 循环遍历数组,有默认值,从数组的第0项开始遍历,没有默认值,从数组的第1项开始遍历,0项作为累加器的初始值。
for (let i = defaultValue ? 0 : 1; i < arr.length; i++) {
result = fn(result, arr[i], i, arr);
}
// 每次返回累加器的值
return result;
};
4.找出三个数组的相同元素
let a = ['1', '2', '3', '5'];
let b = ['2', '3', '5'];
let c = ['4', '5', '2'];
console.log(getCommon(a, b, c)); // ['2', '5']
function getCommon(arrA, arrB, ...others) {
// 如果没有其他项,就直接返回两个数组的相同值
if (others.length == 0) {
return _two(arrA, arrB);
} else {
// 有其他参数,继续递归调用,两个数组的交集和其他数组接着比较
return getCommon(_two(arrA, arrB), ...others);
}
// 两个数组取交集
function _two(arr1, arr2) {
return arr1.filter(el => arr2.includes(el));
}
}
5.平铺的数据转树形结构
var list = [
{ id: 1, name: '司令', pid: 0 },
{ id: 2, name: '军长', pid: 1 },
{ id: 3, name: '副军长', pid: 1 },
{ id: 4, name: '旅长', pid: 3 },
{ id: 5, name: '团长', pid: 4 },
];
function arrToTree(list) {
// 定义最终需要返回的树形结构数据
let treeData = [];
// 对传入的数组进行遍历
list.forEach(item => {
// 如果pid没有值或者为0,那就是顶级对象,直接添加进数组
if (!item.pid) {
treeData.push(item);
}
// 理解为二次遍历 :每个对象都找一遍自己的孩子添加到children
let objList = list.filter(data => data.pid === item.id);
if (objList.length) {
item.children = objList;
}
});
return treeData;
}
6.数组去重
const arr = [1, 1, 2, 2, 3, 4, 4];
//es6写法
[...new Set(arr)];
//es5的写法
function removeRrepeat(arr) {
const result = [];
arr.forEach(item => {
//在 result 中没有找到arr 的每一项,就添加进去
if (result.indexOf(item) === -1) {
result.push(item);
}
});
return result;
}
7.防抖
概念:不管触发多少次事件,都会等到事件触发 **n 秒后 **才会执行,如果在事件触发的 n 秒内 又触发了这个事件,那么就以新的事件的时间为准重新计算,总之,就是要等你触发事件 n 秒内 不再触发事件,我才会执行,一般用在用户表单输入实时搜索上
function debounce(fn, delay) {
let timer = null;
return function () {
const that = this;
const args = arguments;
clearTimeout(timer);
timer = settimeout(() => {
fn.apply(that, args);
}, delay);
};
}
8.节流
概念:顾名思义,可以减少一段时间内事件的触发频率。就是指连续触发事件但是在 n 秒中只执行一次函数。
function throttle(fn, delay) {
let timer = null;
return function () {
const that = this;
const args = arguments;
if (!timer) {
timer = setTimeout(function () {
fn.apply(that, args);
timer = null;
}, delay);
}
};
}
9.new 操作符
function myNew(Fn, ...args) {
//新生成一个对象
const obj = {};
//链接到原型
obj.__proto__ = Fn.prototype;
//绑定this
const res = Fn.call(obj, ...args);
// 若有返回值且是一个对象,返回执行的结果,否则返回新创建的对象
return res instanceof Object ? res : obj;
}
10.冒泡排序
概念:依次比较相邻的两个值,如果后面的比前面的小,则将小的元素排到前面。依照这个规则进行多次并且递减的迭代,直到顺序正确
function bubbleSort(arr) {
if (!Array.isArray(arr)) {
throw Error('The argument to the function should be an array');
return;
}
//比较轮数,最后一位不需要比较
for (i = 0; i < arr.length - 1; i++) {
//每轮比较次数,次数=长度-1-此时的轮数
for (j = 0; j < arr.length - 1 - i; j++) {
// 相邻元素两两对比,元素交换,大的元素交换到后面
if (arr[j] > arr[j + 1]) {
var temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
11.call
作用:改变this的指向,函数立即执行,参数是数据列表,也可以实现继承
function add(...res) {
console.log('this.name', this.name, ...res);
}
var obj = {
name: '小鱼',
};
Function.prototype.myCall = function(ctx, ...rest) {
// ctx 是不是对象,不是就赋值为Window
ctx = ctx instanceof Object ? ctx : window;
// this就是调用的函数
ctx.fn = this;
//执行函数
ctx.fn(...rest);
// 删除添加的fn属性,保证不改变ctx
delete ctx.fn;
};
add.myCall(1, 123);
12.bind
作用:改变this指向,参数是数据列形式,同call的传参。回返一个新的函数。
function add(...res) {
console.log('this.name', this.name, ...res);
}
var obj = {
name: '小鱼',
};
Function.prototype.myBind = function(ctx, ...arg1) {
// 储蓄this,this是调用的myBind的函数
const that = this;
return function(...arg2) {
//ctx 是不是对象,不是赋值window
ctx = ctx instanceof Object ? ctx : window;
ctx.fn = that;
//执行函数,保证参数不丢失。参数也是数据列传入
ctx.fn(...[...arg1, ...arg2]);
// 删除ctx的fn属性,保证不改变自身
delete ctx.fn;
};
};
add.myBind(obj, 1)(2, 3);
13.Promise
概念:Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大
class MyPromise {
constructor(executor) {
// 定义三种状态 进行中、成功、失败
this.status = 'pending';
// 保存成功的值
this.value = undefined;
// 保存失败的原因
this.reason = undefined;
// 成功回调函数的集合
this.onFulFnCallBack = [];
// 失败回调函数的集合
this.onFailFnCallBack = [];
const resolve = value=>{
// 如果在pending 的状态下,才能去修改状态的值,promise的状态是不可逆的
if (this.status === 'pending') {
this.status = 'fulfilled';
this.value = value;
// 循环遍历成功的回调函数
this.onFulFnCallBack.forEach(fn=>fn(value));
}
}
;
const reject = reason=>{
// 如果在pending 的状态下,才能去修改状态的值,promise的状态是不可逆的
if (this.status === 'pending') {
this.status = 'rejected';
this.reason = reason;
// 循环遍历失败的回调函数
this.onFailFnCallBack.forEach(fn=>fn(reason));
}
}
;
// 捕获执行器的错误
try {
executor(resolve, reject);
} catch (error) {
// 有错误就改变状态为失败
reject(error);
}
}
then(onFulFn, onFailFn) {
// promise2 解决的是promise的链式问题,需要return 出去
const promise2 = new MyPromise((resolve,reject)=>{
// 成功状态的时候
if (this.status === 'fulfilled') {
const that = this;
// queueMicrotask 模拟微任务
queueMicrotask(()=>{
try {
const onFailFnReturn = onFulFn(that.value);
that.resolvePromise(onFailFnReturn, resolve, reject, promise2);
} catch (error) {
reject(error);
}
}
);
}
if (this.status === 'rejected') {
reject(this.reason);
}
// pending 状态的时候记录成功、失败的函数
if (this.status === 'pending') {
this.onFulFnCallBack.push(onFulFn);
this.onFailFnCallBack.push(onFailFn);
}
}
);
return promise2;
}
resolvePromise(onFailFnReturn, resolve, reject, promise2) {
// 解决循环调用的问题,不能是同一个promise,并且上面加入微任务,才能保证promise2有值
if (promise2 === onFailFnReturn) {
return reject(new TypeError('Chaining cycle detected for promise #<Promise>'), );
}
// 检测 then的返回值是不是promise
if (onFailFnReturn instanceof MyPromise) {
onFailFnReturn.then(resolve, reject);
} else {
// 检测 then的返回值不是promise,就直接捕获成功
resolve(onFailFnReturn);
}
}
}
const p = new MyPromise((resolve,reject)=>{
// reject(500);
setTimeout(()=>{
resolve(200);
}
, 0);
}
);
p.then(res=>{
console.log('res', res);
return 600;
}
, error=>console.log('error', error), ).then(res=>console.log('res2', res));
14.Promse.all
概念:Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例,要所有的Promsie都成功才回返成功,有一个是失败,就返回失败的那个Promsie的原因。
Promise.all = function(arr) {
// 记录成功的值
const result = [];
// 计数
let count = 0;
// 返回promise
return new Promise((resolve, reject) => {
for (let i = 0; i < arr.length; i++) {
arr[i].then(res => {
// 每一个promise 成功的返回值,按顺序存在result中
result[i] = res;
// 没执行了一个promise,计数器自增1
count++;
// 如果计数器和传入的Promise个数相等,说明所有promise都执行完了,直接成功捕获result就可以了。
if (count === arr.length) {
resolve(result);
}
}, reject);
}
});
};
const a1 = Promise.resolve(200);
const a2 = Promise.resolve(300);
const a3 = Promise.resolve(400);
Promise.all([a1, a2, a3])
.then(res => {
console.log('res', res);
})
.catch(error => console.log('error', error));
15.Promise.race
概念:Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
成功还是失败,决定于,那个Promise先执行,成功就是成功失败就是失败。
Promise.race = function(arr) {
// 返回Promise
return new Promise((resolve, reject) => {
for (let i = 0; i < arr.length; i++) {
// 直接捕获成功或者失败,Promsie的状态是不可逆的
arr[i].then(resolve, reject);
}
});
};
const a1 = Promise.resolve(200);
const a2 = Promise.resolve(300);
const a3 = Promise.resolve(400);
Promise.race([a1, a2, a3])
.then(res => {
console.log('res', res);
})
.catch(error => console.log('error', error));
16.URL 中的 Query 部分做拆解,返回一个 Key - value 形式的 Object。
const handleUrlQueryToParse = arg => {
if (typeof arg !== 'string') return arg;
const arr = arg.split('&');
const result = {};
arr.forEach(item => {
const key = item.split('=')[0];
const value = item.split('=')[1];
if (key in result) {
result[key] = [result[key], value];
} else {
result[key] = value;
}
});
return result;
};
17.Object的参数拼接到一个已有的URL之上,返回拼接后的URL
const handleQueryToUrl = (url, query) => {
if (!url) return '';
if (query) {
let queryArr = [];
for (const key in query) {
queryArr.push(`${key}=${query[key]}`);
}
if (url.includes('?')) {
url = `${url}&${queryArr.join('&')}`;
} else {
url = `${url}?${queryArr.join('&')}`;
}
}
return url;
};
18.判断一个字符串中出现次数最多的字符,输出出现次数最多的字符和出现次数
const str = "abcddddefffedddg";
const maxstringload = (str) => {
var obj = {};
for (var i = 0; i < str.length; i++) {
var key = str[i];
if (obj[key]) {
obj[key]++;
} else {
obj[key] = 1;
}
}
var maxCount = 0;
var maxString = "";
for (var key in obj) {
if (maxCount < obj[key]) {
maxCount = obj[key];
maxString = key;
}
}
return "出现次数最多的字符:" + maxString + "出现了" + maxCount + "次";
};
19.编写一个函数 stringify,它的用途是把参数进行序列化
function getType(attr) {
const type = Object.prototype.toString.call(attr);
const newType = type.substr(8, type.length - 9);
return newType;
}
const stringify = obj => {
if (typeof obj !== 'object' || getType(obj) === null) {
return String(obj);
}
const json = [];
const isArr = obj ? getType(obj) === 'Array' : false;
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
let item = obj[key];
if (getType(obj) === 'Object') {
item = stringify(item);
} else if (getType(obj) === 'Array') {
item = stringify(item);
}
json.push((isArr ? '"' : '"' + key + '": "') + String(item) + '"');
}
}
return (isArr ? '[' : '{') + String(json) + (isArr ? ']' : '}');
};
20.匈牙利命名字符串转换成下划线命名,将驼峰命名的字符串(例如personFirstName转成匈牙利命名字符串(例如:person_first_name)
const str2 = 'personFirstName';
const formatStr = str => {
return str.replace(/[A-Z]/g, s => `_${s.toLocaleLowerCase()}`);
};
21. 给定一个编码字符,按编码规则进行解码,输出字符串。编码规则是count[letter]
,将letter的内容count次输出,count是0或正整数,letter是区分大小写的纯字母,支持嵌套形式
// 例子
//const s1 = '10[a]2[bc]'; decodeString(s); // 返回'aaaaaaaaaabcbc'
//const s2 = '2[3[a]2[bc]]'; decodeString(s); // 返回 'aaabcbcaaabcbc'
function decodeString(str) {
let stack = []
for (let i = 0; i < str.length; i++) {
const cell = str[i]
if (cell !== ']') {
stack.push(cell) //进栈
} else {
let count = []
let popCell = '' //循环个数
let loopArr = []
let loopStr = '' //结果
while ((popCell = stack.pop()) !== '[') {
loopArr.unshift(popCell)
}
// 循环输出count
while(stack[stack.length -1] >= 0 && stack[stack.length -1] <= 9){
count.unshift(stack.pop())
}
count = parseInt(count.join(''))
for (let j = 0; j < count; j++) {
loopStr += loopArr.join('')
}
stack.push(...(loopStr.split(''))) //转换结果入栈
}
}
return stack.join('')
}
const s2 = '2[3[a]10[bc]]';
console.log(decodeString(s2))
22.找出两个数组不同的元素?
var arr1=[1,3,6,8,9,10,20]
var arr2=[11,32,61,58,9,20,10]
arr1.filter(item=>!arr2.includes(item)).concat(arr2.filter(item=>!arr1.includes(item))) // [1, 3, 6, 8, 11, 32, 61, 58]
23.调用接口,网络请求延时,怎么容错处理?
// fetchData()模拟接口在6秒后才返回数据。handleTimeout()模拟接口超时后的处理逻辑。
// 假定接口响应超过5秒(时间可以任意指定),就表示接口已经延时了。
const fetchData=()=>{
const data=[1,2,3];
// 模拟接口请求数据在6秒后返回。
return new Promise((res,rej)=>{
setTimeout(()=>{
res(data)
},6000)
})
}
// 比如接口请求大于5秒,表示请求已经延时。直接返回错误信息。
const handleTimeout=(delay=5000)=>{
return new Promise((res,rej)=>{
setTimeout(()=>{
rej('网络请求超时')
},delay)
})
}
// 使用到Promise.race的特性,谁先跑完,返回谁的值。显然fetchData跑完需要6秒才能拿到数据,
// handleTimeout跑完指定是5秒跑完,所以Promise.race拿到的是handleTimeout返回的 '网络请求超时'
Promise.race([fetchData(),handleTimeout()]).then((res)=>console.log(res)).catch((error)=>console.log(error))