代码题
手写题
1.手写防抖函数
设置clearTimeout为什么还要timer=null 设置延时器之前先清除下延时器,不然每次事件触发都会多一个延时器,延时器之间互相干扰,造成紊乱。
function debounce(fn,delay){
let timer;
return function(...args){
if(timer){
clearTimeout(timer);
timer=null;
}
timer=setTimeout(()=>{
fn.apply(this,args)
},delay);
}
}
//测试
function task(){
console.log('run task')
}
const debounceTask=debounce(task,1000)
window.addEventListener('scroll',debounceTask);
2.手写节流函数
function throttle(fn,delay){
let last=0;
return (...args)=>{
const now =Date.now();
if(now-last>delay){
last=now;
fn.apply(this,args)
}
}
}
function task(){
console.log('run task')
}
const throttleTask=throttle(task,1000);
window.addEventListener('scroll',throttleTask);
3.手写浅拷贝深拷贝
浅拷贝是指,一个新的对象对原始对象的属性值进行精确地拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址。如果其中一个对象的引用内存地址发生改变,另一个对象也会发生变化。
浅拷贝
Object.assign()
Object.assign() 是ES6中对象的拷贝方法,接受的第一个参数是目标对象,其余参数是源对象,用法: Object.assign(target, source1, ···) ,该方法可以实现浅拷贝,也可以实现一维对象 的深拷贝。
let target = {a: 1};
let object2 = {b: 2};
let object3 = {c: 3};
Object.assign(target,object2,object3);
console.log(target); // {a: 1, b: 2, c: 3}
扩展运算符
使用扩展运算符可以在构造字面量对象的时候,进行属性的拷贝。语法: let cloneObj = {...obj };
let obj1 = {a:1,b:{c:1}
let obj2 = {...obj1};
obj1.a = 2;
console.log(obj1);
//{a:2,b:{c:1}}
console.log(obj2);
//{a:1,b:{c:1}}
obj1.b.c = 2;
console.log(obj1);
//{a:2,b:{c:2}}
console.log(obj2);
//{a:1,b:{c:2
数组方法实现数组浅拷贝
Array.prototype.slice()
array.slice(start, end)
从已有数组中返回选定的元素,该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝
Array.prototype.concat()
concat() 用于合并两个或多个数组 返回一个新数组
手写浅拷贝
function shallowCopy(object){
if(!object||typeof object!=="object") return
let newObject=Array.isArray(object)?[]:{}
for(let key in object){
if(object.hasOwnProperty(key)){
newObject[key]=object[key]
}
}
return newObject
}
深拷贝
JSON.stringify()
JSON.parse(JSON.stringify(obj)) 原理就是利用 JSON.stringify 将 js 对象序列化(JSON字符串),再使用JSON.parse 来反序列化(还原)js 对象
拷贝的对象中如果有函数,undefined, symbol,当使用过 JSON.stringify() 进行处理之后,都会消失
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}}
手写深拷贝
function deepClone(obj) {
let result;
if (typeof obj == "object") {
if (obj == null) {
result = obj;
} else if (obj instanceof RegExp) {
result = new RegExp(obj);
} else if (obj instanceof Date) {
result = new Date(obj);
} else if (Array.isArray(obj)) {
result = [];
for (let key of obj) {
result.push(deepClone(key));
}
} else {
result = {};
for (let key in obj) {
if (!result.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
}
} else {
result = obj;
}
return result;
}
let m = {
a: 1,
b: ["name", "xxx"],
c: /^g/,
d: { e: 1 },
f: function (a, b) {
console.log(666);
},
};
console.log(deepClone(m));
function clone(target) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
解决循环引用和symbol
const obj = {
name: "cc",
age: 30,
job: { type: "porgrammer", com: "ali" },
cars: ["passat", "bmw"],
working: function (str) {
console.log(`我是${this.name},我正在${str}`)
},
da: new Date(),
reg: new RegExp(),
xx: undefined ,
};
obj.f=obj
function clone(obj, map = new Map()) {
if (typeof obj != 'object') return
var newObj = Array.isArray(obj) ? [] : {}
if (map.get(obj)) {
return map.get(obj);
}
map.set(obj, newObj);
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] == 'object') {
newObj[key] = clone(obj[key], map);
} else {
newObj[key] = obj[key];
}
}
}
return newObj;
}
var copyobj = clone(obj)
console.log(copyobj);
// 终极解决方案,满足循环引用和symbol
//判断是否是基本数据类型
function isPrimitive(value){
return (typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'symbol' ||
typeof value === 'boolean')
}
//判断是否是一个js对象
function isObject(value){
return Object.prototype.toString.call(value) === "[object Object]"
}
//深拷贝一个值
function cloneEnDeep(value){
// 记录被拷贝的值,避免循环引用的出现
let memo = {};
function baseClone(value){
let res;
// 如果是基本数据类型,则直接返回
if(isPrimitive(value)){
return value;
// 如果是引用数据类型,我们浅拷贝一个新值来代替原来的值
}else if(Array.isArray(value)){
res = [...value];
}else if(isObject(value)){
res = {...value};
}
// 检测我们浅拷贝的这个对象的属性值有没有是引用数据类型。如果是,则递归拷贝
//同时使用Reflect可以检测到Symbol类型的属性
Reflect.ownKeys(res).forEach(key=>{
if(typeof res[key] === "object" && res[key]!== null){
//此处我们用memo来记录已经被拷贝过的引用地址。以此来解决循环引用的问题
if(memo[res[key]]){
res[key] = memo[res[key]];
}else{
memo[res[key]] = res[key];
res[key] = baseClone(res[key])
}
}
})
return res;
}
return baseClone(value)
}
//======================测试====================
//定义一个员工个人对象
let objP = {
name:"cc",
age:30,
job:{type:"porgrammer",com:"ali"},
cars:["passat","bmw"],
working:function(str){
console.log(`我是${this.name},我正在${str}`)
},
da:new Date(),
reg: new RegExp(),
xx:undefined
}
objP.job = objP;//循环引用
console.log(obj);
console.log(getPerInf(obj));
console.log(obj);
obj.working("吃饭");
4.手写ajax
function handleGet(url) {
var response = "";
//1.创建对象
var xhr = new XMLHttpRequest();
//2.设置方法
xhr.open("GET", "http://127.0.0.1:8080/user");
//3.发送请求
xhr.send();
//4.返回结果
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300) {
response = xhr.responseText;
console.log(response, "1");
} else {
return "Error" + xhr.status;
}
}
};
}
5.使用promise实现ajax
// promise 封装实现:
function getJSON(url) {
// 创建一个 promise 对象
let promise = new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
// 新建一个 http 请求
xhr.open("GET", url, true);
// 设置状态的监听函数
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 当请求成功或失败时,改变 promise 的状态
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");
// 发送 http 请求
xhr.send(null);
});
return promise;
}
6.手写 apply call bind
apply实现
判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
判断传入上下文对象是否存在,如果不存在,则设置为 window 。
将函数作为上下文对象的一个属性。
判断参数值是否传入
使用上下文对象来调用这个方法,并保存返回结果。
删除刚才新增的属性
返回结果
Function.prototype.myApply=function (context,...args){
if(typeof this !=="function"){
return new Error('typeError')
}
context.fn=this
if(args){
result=context.fn(...args)
}else{
result=context.fn()
}
delete context.fn
return result
}
let obj = {
a: 10,
b: 20,
};
function sum(a, b) {
this.b = 100;
console.log(this.a, this.b);
return a + b;
}
sum.myApply(obj, 10, 20);
call实现
判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
判断传入上下文对象是否存在,如果不存在,则设置为 window 。
处理传入的参数,截取第一个参数后的所有参数。
将函数作为上下文对象的一个属性。
使用上下文对象来调用这个方法,并保存返回结果。
删除刚才新增的属性。
返回结果。
Function.prototype.myCall = function (context) {
context = context || window;
if (typeof this !== "function") {
throw new Error("typeError");
}
let result;
context.fn = this;
let args = [...arguments].slice(1);
if (args) {
result = context.fn(...args);
} else {
result = context.fn();
}
delete context.fn;
return result;
};
var obj = {
n: 15,
};
function sum(n, m) {
console.log(this);
return n + m;
}
console.log(sum.myCall(obj, 10, 20));
bind实现
// bind 函数实现
Function.prototype.myBind = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
};
var obj = {
n: 15,
};
function sum(n, m) {
console.log(this);
return n + m;
}
var ans=sum.myBind(obj, 10, 20)
console.log(ans());
7.手写promise.all
function myPromiseAll(arrayList) {
return new Promise((resolve, reject) => {
let resultArr = [],
count = 0;
let length = arrayList.length;
for (let i = 0; i < length; i++) {
Promise.resolve(arrayList[i]).then(
(result) => {
count++;
resultArr[i] = result;
if (count == length) {
resolve(resultArr);
}
},
(err) => {
return reject(err);
}
);
}
});
}
function test(num, delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
num == 4 ? reject(num) : resolve(num);
}, delay);
});
}
let p1 = test(1, 1000);
let p2 = test(2, 2000);
let p3 = test(3, 3000);
let p4 = test(4, 4000);
myPromiseAll([p1, p2, p3]).then((result) => {
console.log(result);
});
8.手写promise.race
function PromiseRace(arrayList) {
return new Promise((resolve) => {
for (let i = 0; i < arrayList.length; i++) {
Promise.resolve(arrayList[i]).then((result) => {
resolve(result);
});
}
});
}
function test(num, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
num == 4 ? reject() : resolve(num);
}, time);
});
}
let p1 = test(1, 2000);
let p2 = test(2, 1000);
let p3 = test(3, 5000);
PromiseRace([p3, p1, p2]).then((res) => {
console.log(res);
});
9.实现函数科里化
1.固定参数
理解 arg数组是不断被扩展的 length指的是传入的test函数的参数长度
function curry(fn) {
let length = fn.length;
return function temp() {
let arg = [...arguments];
if (arg.length >= length) {
return fn(...arg);
} else {
return function () {
return temp(...arg, ...arguments);
};
}
};
}
function add(a, b, c) {
return a + b + c;
}
let test = curry(add);
console.log(test(1)(2, 3));
2.不固定参数
function curry(fn) {
let arg = [...arguments].slice(1);
let temp = function temp() {
arg = [...arg, ...arguments];
return curry(fn, ...arg);
};
temp.toString = function () {
return fn.apply(null, arg);
};
return temp;
}
function fn() {
return [...arguments].reduce((pre, cru) => {
return pre + cru;
}, 0);
}
let test = curry(fn);
console.log(test(1)(2, 3)(4).toString());
3.固定参数的add函数
function add(x) {
let sum = x;
let temp = function (y) {
sum += y;
return temp;
};
temp.toString = function () {
return sum;
};
return temp;
}
let a = add(1)(2)(4);
console.log(a.toString());
4.不固定参数的add函数
function add() {
let arg = [...arguments];
let add = function () {
arg.push(...arguments);
return add;
};
add.valueOf = function () {
return arg.reduce((pre, cru) => {
return pre + cru;
});
};
return add;
}
console.log(add(1)(2)(3)(4)(5).valueOf());
10.实现new
function news(fn, ...arg) {
let obj = {};
obj.__proto__ = fn.prototype;
fn.apply(obj, arg);
return obj;
}
// 使用方法
function Person(name, age) {
this.name = name;
this.age = age;
}
let person1 = news(Person, "张三", 15);
console.log(person1);
let person2 = new Person("李四", 15);
console.log(person2);
11.实现Instanceof
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left),
prototype = right.prototype;
while (1) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
console.log(myInstanceof([1, 3, 4], Array));
console.log(myInstanceof({}, Array));
console.log(myInstanceof(new Date(), Object));
console.log(myInstanceof(2, Array));
12.手写promise
链式调用原理
我门常常用到 new Promise().then().then() ,这就是链式调用,用来解决回调地狱
1、为了达成链式,我们默认在第一个then里返回一个promise。规定了一种方法,就是在then里面返回一个新的promise,称为promise2: promise2 = new Promise((resolve, reject)=>{}) •将这个promise2返回的值传递到下一个then中 •如果返回一个普通的值,则将普通的值传递给下一个then中
2、当我们在第一个then中 return 了一个参数(参数未知,需判断)。这个return出来的新的promise就是onFulfilled()或onRejected()的值
规定onFulfilled()或onRejected()的值,即第一个then返回的值,叫做x,判断x的函数叫做resolvePromise
then(onFulfilled,onRejected) {
// 声明返回的promise2
let promise2 = new Promise((resolve, reject)=>{
if (this.state === 'fulfilled') {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
};
if (this.state === 'rejected') {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push(()=>{
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
})
this.onRejectedCallbacks.push(()=>{
let x = onRejected(this.value);
resolvePromise(promise2, x, resolve, reject);
})
}
});
// 返回promise,完成链式
return promise2;
}
规定了一段代码,让不同的promise代码互相套用,叫做resolvePromise •如果 x === promise2,则是会造成循环引用,自己等待自己完成,则报“循环引用”错误
let p = new Promise(resolve => {
resolve(0);
});
var p2 = p.then(data => {
// 循环引用,自己等待自己完成,一辈子完不成
return p2;
})
1、判断x
•x 不能是null •x 是普通值 直接resolve(x) • x 是对象或者函数(包括promise), let then = x.then 2、当x是对象或者函数(默认promise) •声明了then •如果取then报错,则走reject() •如果then是个函数,则用call执行then,第一个参数是this,后面是成功的回调和失败的回调 •如果成功的回调还是pormise,就递归继续解析 3、成功和失败只能调用一个 所以设定一个called来防止多次调用
function resolvePromise(promise2, x, resolve, reject){
// 循环引用报错
if(x === promise2){
// reject报错
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 防止多次调用
let called;
// x不是null 且x是对象或者函数
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
// A+规定,声明then = x的then方法
let then = x.then;
// 如果then是函数,就默认是promise了
if (typeof then === 'function') {
// 就让then执行 第一个参数是this 后面是成功的回调 和 失败的回调
then.call(x, y => {
// 成功和失败只能调用一个
if (called) return;
called = true;
// resolve的结果依旧是promise 那就继续解析
resolvePromise(promise2, y, resolve, reject);
}, err => {
// 成功和失败只能调用一个
if (called) return;
called = true;
reject(err);// 失败了就失败了
})
} else {
resolve(x); // 直接成功即可
}
} catch (e) {
// 也属于失败
if (called) return;
called = true;
// 取then出错了那就不要在继续执行了
reject(e);
}
} else {
resolve(x);
}
}
不完全实现(没有完全实现链式调用)
/* Promise有三个状态,包括Pending,resolved,rejected */
//状态定义
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise() {
//保存初始化状态
var self = this;
//初始化状态
this.state = PENDING;
//用于保存resolve或者rejected传入的值
this.value = null;
//用于保存resolve的回调函数
this.resolvedCallbacks = [];
//用于保存reject的回调函数
this.rejectedCallbacks = [];
//状态转变为resolved方法
function resolved(value) {
if (value instanceof MyPromise) {
return value.then(resolved, rejected);
}
//保证代码的执行顺序为本轮事件循环的末尾
setTimeout(() => {
// 只有状态pending才改变
if (self.state === PENDING) {
//改变状态
self.status = RESOLVED;
//设置传入的值
self.value = value;
//执行回调函数
self.resolvedCallbacks.forEach((callback) => {
callback(back);
});
}
}, 0);
}
//状态转变为rejected方法
function rejected(value) {
//保证代码的执行顺序为本轮事件循环的末尾
setTimeout(() => {
// 只有状态pending才改变
if (self.state === PENDING) {
//改变状态
self.status = REJECTED;
//设置传入的值
self.value = value;
//执行回调函数
self.rejectedCallbacks.forEach((callback) => {
callback(back);
});
}
}, 0);
}
//将两个方法传入函数执行
try {
fn(resolve, reject);
} catch (e) {
//遇到错误时,捕获错误,执行reject函数
reject(e);
}
MyPromise.prototype.then = function (onResolved, onRejected) {
//首先判断两个参数是否为函数类型,因为这两个参数是可选参数
onResolved =
typeof onResolved === "function"
? onResolved
: function (value) {
return value;
};
onRejected =
typeof onRejected === "function"
? onRejected
: function (error) {
throw error;
};
//如果是等待状态,则将函数加入对应列表中
if (this.status === PENDING) {
this.resolvedCallbacks.push(onResolved);
this.rejectedCallbacks.push(onRejected);
}
if (this.state === RESOLVED) {
onResolved(this.value);
}
if (this.state === REJECTED) {
onRejected(this.value);
}
};
}
完全规范实现
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) {
// onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// onRejected如果不是函数,就忽略onRejected,直接扔出错误
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let promise2 = new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
// 异步
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'rejected') {
// 异步
setTimeout(() => {
// 如果报错
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
// 异步
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
// 异步
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
};
});
// 返回promise,完成链式
return promise2;
}
}
全部方法实现
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) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let promise2 = new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
};
});
return promise2;
}
catch(fn){
return this.then(null,fn);
}
}
function resolvePromise(promise2, x, resolve, reject){
if(x === promise2){
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called;
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
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);
}
}
//resolve方法
Promise.resolve = function(val){
return new Promise((resolve,reject)=>{
resolve(val)
});
}
//reject方法
Promise.reject = function(val){
return new Promise((resolve,reject)=>{
reject(val)
});
}
//race方法
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject)
};
})
}
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
i++;
if(i == promises.length){
resolve(arr);
};
};
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject);
};
});
}
13.手写Reduce
Array.prototype.myReduce = function (cb, initialValue) {
const array = this//获取数组
let acc = initialValue || array[0]//acc相当于pre
const startIndex = initialValue ? 0 : 1
for (let i = startIndex; i < array.length; i++) {
const cur = array[i]
acc = cb(acc, cur, i, array)
}
return acc
}
14.手写promisify
手动实现一个promisify函数的意思是:我们把一个异步请求的函数,封装成一个可以具有 then方法的函数,并且在then方法中返回异步方法执行结果的这么一个函数
- 具有 then 方法
- then 方法里返回异步接口执行结果
// 首先定一个需要进行 promisify 的函数
function asyncFn(a, b, callback) {
// 异步操作,使用 setTimeout 模拟
console.log('异步请求参数', a, b)
setTimeout(function() {
callback('异步请求结果')
}, 3000)
}
// 我们希望调用的方式是
const proxy = promisify(asyncFn)
proxy(11,22).then(res => {
// 此处输出异步函数执行结果
console.log(res)
})
// 定义一个方法, 需要针对异步方法做封装,所以需要一个入参,既需要promisify的原异步方法
function promisify( asyncFn ) {
// 方法内部我们需要调用asyncFn方法,并传递原始参数,所以需要返回一个方法来接收参数
return function(...args) { // 由于需要接收参数,所以参数我们可以写为...args
// 我们需要执行异步操作,并返回一个结果,所以返回一个 promise实例
return new Promise(resolve => {
// asyncFn 需要执行一个回调,所以定义一个回调方法
const callback = function(...args) {
resolve(args)
}
args.push(callback)
asyncFn.apply(null, args)
})
}
}
15.用setTimeout实现setInterval
setTimeout() :在指定的毫秒数后调用函数或计算表达式,只执行一次。setInterval() :按照指定的周期(以毫秒计)来调用函数或计算表达式。方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。
使用递归函数,不断地去执行setTimeout从而达到setInterval的效果
setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。
针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件,这样解决了 setInterval 的问题。
实现思路是使用递归函数,不断地去执行 setTimeout 从而达到 setInterval 的效果
function mySetInterval(fn, delay){
function interval(){
setTimeout(interval, delay);
fn();
}
setTimeout(interval, delay)
}
限制调用次数
function mySetInterval(fn, delay,count){
function interval(){
if(typeof count==='undefined'||count-->0){
setTimeout(interval, delay);
try{
fn()
}catch(e){
count = 0;
throw e.toString();
}
}
}
setTimeout(interval, delay)
}
16.promise串行执行
const funcArr = [
() =>
new Promise((resolve) => {
setTimeout(() => resolve(1), 2000);
}),
() =>
new Promise((resolve) => {
setTimeout(() => resolve(2), 1000);
}),
() =>
new Promise((resolve) => {
setTimeout(() => resolve(3), 3000);
}),
];
/**
* @description: 实现Promise的串行
* @param {*}: 接收一个包含多个返回Promise对象的函数的数组
* @return {*}: 返回一个Promise对象
*/
function inOrder(arr) {
const res = []
return new Promise((resolve,reject) => {
arr.reduce((pre,cur) => {
return pre.then(cur).then(data => res.push(data))
},Promise.resolve()).then(() => resolve(res))
})
}
inOrder(funcArr).then(data => console.log(data))
17.基于Promise的fetch
const log = console.log;
function maxRequest(url = ``, times = 3) {
// 1. 闭包,保存私有属性
function autoRetry (url, times) {
console.log('times = ', times);
times--;
// 2. fetch 本身返回值就是 Promise,不需要再次使用 Promise 包裹
return fetch(url).then(value => {
if(value.status === 200) {
console.log(`✅ OK`, value);
// 3. 手动返回 Promise 的 value, 没有返回值 默认返回 undefined
return value;
} else {
throw new Error(`❌ http code error: ${value.status }`);
}
}).catch((err) => {
console.log(`❌ Error`, err);
if (times < 1) {
// 4. 方便后续的 thenable 处理 error
throw new Error('💩 over max request times!');
} else {
// 5. 返回递归方法
return autoRetry(url, times);
}
});
}
// 6. 返回一个 Promise 的结果 (成功 Promise 或失败 Promise)
return autoRetry(url, times);
}
// error test case
maxRequest(`https://cdn.xgqfrms.xyz/json/badges.js`)
.then(res => res.json())
.then(json=> console.log('json =', json))
.catch(err => console.error(`err =`, err))
.finally(() => {
console.log('👻 whatever close loading...');
});
// sucess test case
maxRequest(`https://cdn.xgqfrms.xyz/json/badges.json`)
.then(res => res.json())
.then(json=> console.log('json =', json))
.catch(err => console.error(`err =`, err))
.finally(() => {
console.log('👻 whatever close loading...');
});
数据处理题
1.实现日期格式化函数
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日
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
}
2.数组扁平化
简单版
(1)递归实现
普通的递归思路很容易理解,就是通过循环递归的方式,一项一项地去遍历,如果每一项还是一个数组,那么就继续往下遍历,利用递归程序的方法,来实现数组的每一项的连接:
let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
let result = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
flatten(arr); // [1, 2, 3, 4,5]
(2)reduce 函数迭代
从上面普通的递归函数中可以看出,其实就是对数组的每一项进行处理,那么其实也可以用reduce 来实现数组的拼接,从而简化第一种方法的代码,改造后的代码如下所示:
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.reduce(function(prev, next){
return prev.concat(Array.isArray(next) ? flatten(next) : next)
}, [])
}
console.log(flatten(arr));// [1, 2, 3, 4,5]
(3)扩展运算符实现
这个方法的实现,采用了扩展运算符和 some 的方法,两者共同使用,达到数组扁平化的目的:
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
(4)split 和 toString
可以通过 split 和 toString 两个方法来共同实现数组扁平化,由于数组会默认带一个 toString 的方法,所以可以把数组直接转换成逗号分隔的字符串,然后再用 split 方法把字符串重新转换为数组,如下面的代码所示:
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.toString().split(',');
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
通过这两个方法可以将多维数组直接转换成逗号连接的字符串,然后再重新分隔成数组。
(5)ES6 中的 flat
我们还可以直接调用 ES6 中的 flat 方法来实现数组扁平化。flat 方法的语法:arr.flat([depth])
其中 depth 是 flat 的参数,depth 是可以传递数组的展开深度(默认不填、数值是 1),即展开一层数组。如果层数不确定,参数可以传进 Infinity,代表不论多少层都要展开:
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.flat(Infinity);
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
可以看出,一个嵌套了两层的数组,通过将 flat 方法的参数设置为 Infinity,达到了我们预期的效果。其实同样也可以设置成 2,也能实现这样的效果。在编程过程中,如果数组的嵌套层数不确定,最好直接使用 Infinity,可以达到扁平化。
(6)正则和 JSON 方法 在第4种方法中已经使用 toString 方法,其中仍然采用了将 JSON.stringify 的方法先转换为字符串,然后通过正则表达式过滤掉字符串中的数组的方括号,最后再利用 JSON.parse 把它转换成数组:
let arr = [1, [2, [3, [4, 5]]], 6];
function flatten(arr) {
let str = JSON.stringify(arr);
str = str.replace(/([|])/g, '');
str = '[' + str + ']';
return JSON.parse(str);
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
限制层数
function flat(arr,num=1){
return num>0?
arr.reduce(pre,cur)=>pre.concat(Array.isArray(cur))?flat(cur,num-1):cur),[])
:arr.slice();
}
function flatten(arr,level){
function walk(arr,currLevel){
let res=[];
for(let item of arr){
if(Array.isArray(item)&&currLevel<level){
res=res.concat(walk(item,currLevel+1))
}else{
res.push(item)
}
}
return res
}
return walk(arr,1)
}
3.数组去重
给定某无序数组,要求去除数组中的重复数字并且返回新的无重复数组。
ES6方法(使用数据结构集合):
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
ES5方法:使用map存储不重复的数字
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
uniqueArray(array); // [1, 2, 3, 5, 9, 8]
function uniqueArray(array) {
let map = {};
let res = [];
for(var i = 0; i < array.length; i++) {
if(!map.hasOwnProperty([array[i]])) {
map[array[i]] = 1;
res.push(array[i]);
}
}
return res;
}
4.大数运算
大数相加
function add(a,b){
let a=a.split(''),b=b.split('');
let sum=[],flag=0;
while(a.length||b.length){
let num1=parseInt(a.pop())||0;
let num2=parseInt(b.pop())||0;
let temp=num1+num2+flag;
if(temp>9){
flag=1
temp=temp%10
}else{
flag=0
}
sum.unshift(temp)
}
if(flag)sum.unshift(1) //最后一次运算是否需要进位
return sum.join('')
}
大数相乘
以multiply('11', '99')为例,思路如下:
-
初始化一个数组
res用来存放计算结果 -
双重for循环,从后往前,遍历str1和str2,将
str1[i]*str2[j]+res[i+j]的结果存储到res中。
- 一次循环后,res = [empty, 9,9]
- 二次循环后,res = [9, 18,9]
-
从后往前遍历res
res[index - 1] += parseInt(res[index] / 10),res当前项取整和res前一项相加(完成>10进一的操作)res[index] %= 10,res当前项取余
function multiply(str1, str2) {
if (str1 == '0' || str2 == '0') return '0'
let res = [],
i = str1.length - 1,
j = str2.length - 1;
for (; i >= 0; i--) {
let n1 = str1[i] - '0'
j = str2.length - 1
for (; j >= 0; j--) {
let n2 = str2[j] - '0'
res[i + j] = res[i + j] || 0
res[i + j] += n1 * n2
}
}
let index = res.length - 1
while (index >= 1) {
res[index - 1] += parseInt(res[index] / 10)
res[index] %= 10
index--
}
return res.join('')
}
var num1 = '546212878237823';
var num2 = '42362078923598';
console.log(multiply(num1, num2))
5.数字千分位逗号隔开
简单写法
let number =123456789
function formatNum(number){
let str=''
let arr=number.toString().split('')
let length=arr.length
while(length>3){
str=`,${arr.splice(-3).join('')}`+str
length=arr.length
}
return arr.join('')+str
}
console.log(formatNum(number))
考虑小数
function formatNum(number) {
var arr = (number + '').split('.');
var int = arr[0].split('');
var fraction = arr[1] || '';
var r = "";
var len = int.length;
int.reverse().forEach(function (v, i) {
if (i !== 0 && i % 3 === 0) {
r = v + "," + r;
} else {
r = v + r;
}
})
return r + (!!fraction ? "." + fraction : '');
}
//!!相当于将转换成布尔类型
考虑负数
function formatNum(num) {
let numPrefix = ''
let numArr = ''
let numDist = ''
// 处理负数情况
if (num < 0) {
numPrefix = '-'
numArr = String(num).slice(1).split('').reverse()
} else {
numArr = String(num).split('').reverse()
}
for (let i = 0; i < numArr.length; i++) {
numDist += numArr[i]
if ((i + 1) % 3 === 0 && (i + 1) < numArr.length) {
numDist += ','
}
}
return numPrefix + numDist.split('').reverse().join('')
}
正则
let num = '12345678'
let reg = /(?=\B(\d{3})+$)/g
console.log(num.replace(reg,",")) //12,345,678
6.解析URL Params为对象
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
}
*/
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;
}
7.字符串和驼峰式转换
字符串转驼峰
数组法
//空格式
let a='hello world'
var b=a.split(' ').map(item=>{
return item[0].toUpperCase()+item.substr(1,item.length)
}).join('')
console.log(b)
//横线式
let str='hello-world'
function getCamelCase(str) {
let arr = str.split('-');
return arr.map((item, index) => {
if (index === 0) {
return item;
} else {
return item.chartAt(0).toUpperCase() + item.slice(1);
}
}).join('');
}
console.log(getCamelCase(str))
正则法
let a='hello world'
let b = a.replace((/\s\w/g),function(v){
return v.substring(1).toUpperCase()
})
console.log(b)
let a='hello-world'
let b = a.replace((/-\w/g),function(v){
return v.substring(1).toUpperCase()
})
console.log(b)
驼峰转字符串
数组法
//横线式
const str = 'helloWorld';
function getKebabCase(str) {
let arr = str.split('');
let result = arr.map((item) => {
if (item.toUpperCase() === item) {
return '-' + item.toLowerCase();
} else {
return item;
}
}).join('');
return result;
}
console.log(getKebabCase(str));
const str = 'helloWorld';
function getKebabCase(prev, cur, index, array) {
if (/[A-Z]/.test(cur)) {
cur = cur.toLowerCase();
if (index === 0) {
return prev + cur;
} else {
return prev + '-' + cur;
}
} else {
return prev + cur;
}
}
function toKebabCase(arr) {
if (typeof arr === 'string') {
arr = arr.split('');
}
return arr.reduce(getKebabCase, '');
}
let test1 = toKebabCase(str);
let test2 = [].reduce.call(st, getKebabCase, '');
正则法
const str = 'helloWorld';
function getKebabCase(str) {
let temp = str.replace(/[A-Z]/g, function(i) {
return '-' + i.toLowerCase();
})
if (temp.slice(0,1) === '-') {
temp = temp.slice(1); //如果首字母是大写,执行replace时会多一个-,需要去掉
}
return temp;
}
console.log(getKebabCase(str));
8.Js对象转树形结构
// 转换前:
source = [{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
// 转换为:
tree = [{
id: 1,
pid: 0,
name: 'body',
children: [{
id: 2,
pid: 1,
name: 'title',
children: [{
id: 3,
pid: 2,
name: 'div'
}]
}]
}]
source = [{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
//
function treeing (arr) {
let tree = []
const map = {}
for (let item of arr) {
// 一个新的带children的结构
let newItem = map[item.id] = {
...item,
children: []
}
if (map[item.pid]) { // 父节点已存进map则在父节点的children添加新元素
let parent = map[item.pid]
parent.children.push(newItem)
} else { // 没有父节点,在根节点添加父节点
tree.push(newItem)
}
}
return tree
}
//
function toTree(data) {
let result = []
if(!Array.isArray(data)) {
return result
}
data.forEach(item => {
delete item.children;
});
let map = {};
data.forEach(item => {
map[item.id] = item;
});
data.forEach(item => {
let parent = map[item.pid];
if(parent) {
(parent.children || (parent.children = [])).push(item);
} else {
result.push(item);
}
});
return result;
}
console.log(treeing(source))
9.字符串去重和反转
字符串去重
let str = '11223344aabbcc'
function strSeparate(s) {
return [...new Set([...s])].join('');
// or return [...new Set(s.split(''))].join('')
}
console.log(strSeparate(str))
let str = '11223344aabbcc'
function strSeparate(s) {
// 使用展开运算符,字符串转换成数组
s = [...str];
let arr = [];
for(let i = 0; i < s.length; i++) {
if(arr.indexOf(s[i]) == -1) {
arr.push(s[i])
}
}
return arr.join('');
}
console.log(strSeparate(str))
10.数字反转
var reverse = function(x) {
let res = 0;
while(x){
res = res * 10 + x % 10;
if(res > Math.pow(2, 31) - 1 || res < Math.pow(-2, 31)) return 0;
x = ~~(x / 10);
}
return res;
};
var reverse = function (x) {
let y = parseInt(x.toString().split("").reverse().join(""));
if (x < 0)
y = - y;
return y > 2147483647 || y < -2147483648 ? 0 : y;
};
11.计算目录树的深度
得出depth即为树的高度得出depth即为树
定义变量depth为0
定义一个空数组temp,然后遍历tree,如果tree有children,就push到temp里面
开始while循环,如果temp长度不为0,depth++;如果temp长度为0,停止
得出depth即为树的高度
得出depth即为树的高度得出depth即为树的高度得出depth即为树的高度
const tree = {
name: 'root',
children: [
{ name: '叶子1-1' },
{ name: '叶子1-2' },
{
name: '叶子2-1',
children: [{
name: '叶子3-1',
children: [{
name: '叶子4-1'
}]
}]
}
]
}
function getDepth(tree) {
let depth = 0
if (tree) {
let arr = [tree]
let temp = arr
while (temp.length) {
arr = temp
temp = []
for (let i = 0; i < arr.length; i++) {
if (arr[i].children && arr[i].children.length) {
for (let j = 0; j < arr[i].children.length; j++) {
temp.push(arr[i].children[j])
}
}
}
depth++
}
}
return depth
}console.log(getDepth(tree)); //输出4
12.树形结构获取路径名
const treeData = [
{
name: "root",
children: [
{ name: "src", children: [{ name: "index.html" }] },
{ name: "public", children: [] },
],
},
];
const RecursiveTree = (data) => {
data.map((item) => {
console.log(item.name);
if (item.children) {
RecursiveTree(item.children);
}
});
};
RecursiveTree(treeData);
13.数组转化成树形结构
递归形式
const arr = [
{id:"01", name: "张大大", pid:"", job: "项目经理"},
{id:"02", name: "小亮", pid:"01", job: "产品leader"},
{id:"03", name: "小美", pid:"01", job: "UIleader"},
{id:"04", name: "老马", pid:"01", job: "技术leader"},
{id:"05", name: "老王", pid:"01", job: "测试leader"},
{id:"06", name: "老李", pid:"01", job: "运维leader"},
{id:"07", name: "小丽", pid:"02", job: "产品经理"},
{id:"08", name: "大光", pid:"02", job: "产品经理"},
{id:"09", name: "小高", pid:"03", job: "UI设计师"},
{id:"10", name: "小刘", pid:"04", job: "前端工程师"},
{id:"11", name: "小华", pid:"04", job: "后端工程师"},
{id:"12", name: "小李", pid:"04", job: "后端工程师"},
{id:"13", name: "小赵", pid:"05", job: "测试工程师"},
{id:"14", name: "小强", pid:"05", job: "测试工程师"},
{id:"15", name: "小涛", pid:"06", job: "运维工程师"}
]
function toTree(list,parId){
let len = list.length
function loop(parId){
let res = [];
for(let i = 0; i < len; i++){
let item = list[i]
if(item.pid === parId){
item.children = loop(item.id)
res.push(item)
}
}
return res
}
return loop(parId)
}
let result = toTree(arr,'')
console.log(result);
非递归形式
const arr = [
{ 'id': '29', 'pid': '', 'name': '总裁办' },
{ 'id': '2c', 'pid': '', 'name': '财务部' },
{ 'id': '2d', 'pid': '2c', 'name': '财务核算部' },
{ 'id': '2f', 'pid': '2c', 'name': '薪资管理部' },
{ 'id': 'd2', 'pid': '', 'name': '技术部' },
{ 'id': 'd3', 'pid': 'd2', 'name': 'Java研发部' }
]
function tranListToTreeData(list) {
// 1. 定义两个中间变量
const treeList = [], // 最终要产出的树状数据的数组
map = {} // 存储映射关系
// 2. 建立一个映射关系,并给每个元素补充children属性.
// 映射关系: 目的是让我们能通过id快速找到对应的元素
// 补充children:让后边的计算更方便
list.forEach(item => {
if (!item.children) {
item.children = []
}
map[item.id] = item
})
// {
// "29": { 'id': '29', 'pid': '', 'name': '总裁办', children:[] },
// '2c': { 'id': '2c', 'pid': '', 'name': '财务部', children:[] },
// '2d': { 'id': '2d', 'pid': '2c', 'name': '财务核算部', children:[]},
// '2f': { 'id': '2f', 'pid': '2c', 'name': '薪资管理部', children:[]},
// 'd2': { 'id': 'd2', 'pid': '', 'name': '技术部', children:[]},
// 'd3': { 'id': 'd3', 'pid': 'd2', 'name': 'Java研发部', children:[]}
// }
// 3. 循环
list.forEach(item => {
// 对于每一个元素来说,先找它的上级
// 如果能找到,说明它有上级,则要把它添加到上级的children中去
// 如果找不到,说明它没有上级,直接添加到 tree3List
const parent = map[item.pid]
if (parent) {
parent.children.push(item)
} else {
treeList.push(item)
}
})
// 4. 返回出去
return treeList
}
const treeList = tranListToTreeData(arr)
console.log(treeList);
14.对象扁平化
1.带数组的
输入
{
a: 'a',
b: [1, { c: true }, [3]],
d: { e: undefined, f: 3 },
g: null,
}
输出
{
a: "a",
b[0]: 1,
b[1].c: true,
b[2][0]: 3,
d.f: 3
// null和undefined直接舍去
}
var myObj={
a: 'a',
b: [1, { c: true }, [3]],
d: { e: undefined, f: 3 },
g: null,
}
function treeToObj(myObj) {
function getObj(myObj, str, toObj) {
const level = Object.keys(myObj)
let levelStr = str.slice()
if (levelStr != '') {
levelStr = levelStr + '.'
}
for (key of level) {
if (myObj[key]) {
if (typeof (myObj[key]) === 'object') {
getObj(myObj[key], levelStr + key, toObj)
} else {
toObj[levelStr + key] = myObj[key]
}
}
}
return toObj
}
return getObj(myObj,'',{})
}
console.log(treeToObj(myObj))
var myObj = {
a: 'a',
b: [1, { c: true }, [3]],
d: { e: undefined, f: 3 },
g: null,
}
function treeToObj(myObj) {
const res = {}
function toObj(obj, str = null) {
for (let key in obj) {
if (typeof obj[key] === "number" || typeof obj[key] === "string" || typeof obj[key] === "boolean") {
if (str === null) {
res[key] = obj[key]
} else if (Array.isArray(obj)) {
res[str + '[' + key + ']'] = obj[key]
} else {
res[str + '.' + key] = obj[key]
}
} else if (Array.isArray(obj[key])) {
if (str === null) {
toObj(obj[key], key)
} else if (Array.isArray(obj)) {
toObj(obj[key], str + '[' + key + ']')
} else {
toObj(obj[key], str + '.' + key)
}
} else if (typeof obj[key] === 'object') {
if (str === null) {
toObj(obj[key], key)
} else if (Array.isArray(obj)) {
toObj(obj[key], str + '[' + key + ']')
} else {
toObj(obj[key], str + '.' + key)
}
}
}
return res;
}
return toObj(myObj,'')
}
console.log(treeToObj(myObj))
2.纯对象
var entry = {
a: {
b: {
c: {
dd: 'abcdd'
}
},
d: {
xx: 'adxx'
},
e: 'ae'
}
}
// 要求转换成如下对象
var output = {
'a.b.c.dd': 'abcdd',
'a.d.xx': 'adxx',
'a.e': 'ae'
}
var entry = {
a: {
b: {
c: {
dd: 'abcdd'
}
},
d: {
xx: 'adxx'
},
e: 'ae'
}
}
function flatObj(obj,parentKey="",result={}){
for(const key in obj){
if(obj.hasOwnProperty(key)){
let keyName=`${parentKey}${key}`
if(typeof obj[key]==='object'){
flatObj(obj[key],keyName+".",result)
}else{
result[keyName]=obj[key]
}
}
}
return result
}
console.log(flatObj(entry))
15.根据表达式计算字母数量
描述:输入一串字符串,根据字符串求出每个字母的数量并返回结果对象。(数字为1时可省略) 示例一:输入:A3B2,输出:{"A": 3, "B": 2} 示例二:输入:A(A(A2B)2)3C2,输出:{"A": 16, "B": 6, "C": 2}
var countOfAtoms = function(formula) {
//记录各个原子的值
let dict = {}
//栈中初始数字。用于表示单个原子的数字情况
let stack = [1]
//原子名
let str = ''
//暂且记录的原子数字情况,压入栈或者消耗后要重置。
let count = 1;
//从后往前遍历。
for(let i = formula.length - 1; i >= 0; i-- ){
const value = formula[i];
//如果是数字。则记录
if(!isNaN(value)){
if(count === 1){
count = value
}else{
count = parseInt(value + count)
}
};
//如果是小写字母,则记录
if(value >= 'a' && value <= 'z'){
str = str + value;
}
//如果是右括号,则将记录的数字和栈顶部数字相乘并输出
if(value === ')'){
stack.push(stack[stack.length - 1] * count);
count = 1;
}
//如果是左括号,则出栈
if(value === '('){
stack.pop();
}
//如果是大写字母,则将栈顶的值赋值给该原子。如果字典里有这个原子,则累加。重置临时记录的数字和原子值
if(value >= 'A' && value <= 'Z'){
let atomsValue = count * stack[stack.length-1];
str = value + str;
if(dict[str] === undefined){
dict[str] = atomsValue;
}else{
dict[str] = dict[str] + atomsValue
}
count = 1;
str = '';
}
}
//按照字典排序
let newkey = Object.keys(dict).sort();
var result = {};//创建一个新的对象,用于存放排好序的键值对
for (var i = 0; i < newkey.length; i++) {//遍历newkey数组
result[newkey[i]] = dict[newkey[i]];//向新创建的对象中按照排好的顺序依次增加键值对
}
//按照要求的格式输出。
let resultString = ''
for(var key in result){
resultString = resultString + key;
if(result[key] > 1){
resultString = resultString + result[key];
}
}
return resultString
};
类似于726. 原子的数量
16.对象树遍历
const tree = {
name: 'root',
children: [
{
name: 'c1',
children: [
{
name: 'c11',
children: []
},
{
name: 'c12',
children: []
}
]
},
{
name: 'c2',
children: [
{
name: 'c21',
children: []
},
{
name: 'c22',
children: []
}
]
}
]
}
// 深度优先的方式遍历 打印 name
// ['root', 'c1','c11', 'c12', 'c2', 'c21', 'c22']
function solve(root) {
let stack = [],
result = [];
if(!root) return [];
stack.push(root)
while(stack.length) {
let node = stack.pop()
if(node == null ) continue
result.push(node.name)
for(let i = node.children.length-1; i >= 0; i--) {
// 这里就是面试的重点,应该从后面的节点压入栈中
stack.push(node.children[i])
}
}
return result
}
17.手写JSON.stringify和JSON.parse
1.JSON.stringify JSON.stringify() 方法将一个 JavaScript 对象或值转换为 JSON 字符串,如果指定了一个 replacer 函数,则可以选择性地替换值,或者指定的 replacer 是数组,则可选择性地仅包含数组指定的属性。
JSON.stringify(value[, replacer [, space]])
Boolean | Number| String 类型会自动转换成对应的原始值。 undefined、任意函数以及symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成 null(出现在数组中时)。 不可枚举的属性会被忽略。 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略。 2.JSON.parse JSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的 reviver 函数用以在返回之前对所得到的对象执行变换(操作)。
JSON.parse(text[, reviver])
用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所得到的对象执行变换(操作)。
JSON.stringify
function jsonStringify (obj) {
let type = typeof obj;
if (type !== "object" || type === null) {
if (/string|undefined|function/.test(type)) {
obj = '"' + obj + '"';
}
return String(obj);
} else {
let json = []
arr = (obj && obj.constructor === Array);
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 ? "]" : "}")
}
}
jsonStringify({ x: 5 })
// "{"x":5}"
jsonStringify([1, "false", false])
// "[1,"false",false]"
jsonStringify({ b: undefined })
// "{"b":"undefined"}"
场景题
1.循环打印黄绿蓝
普通递归
const task = (timer, light, callback) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
callback()
}, timer)
}
const step = () => {
task(3000, 'red', () => {
task(2000, 'green', () => {
task(1000, 'yellow', step)
})
})
}
step()
使用promise实现
const task = (timer, light) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
resolve()
}, timer)
})
const step = () => {
task(3000, 'red')
.then(() => task(2000, 'green'))
.then(() => task(2100, 'yellow'))
.then(step)
}
step()
用 async/await 实现
const task = (timer, light) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
resolve()
}, timer)
})
const taskRunner = async () => {
await task(3000, 'red')
await task(2000, 'green')
await task(2100, 'yellow')
taskRunner()
}
taskRunner()
2.图片懒加载
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<style>
.container {
width: 1000px;
margin: 0 auto;
background-color: pink;
}
.container > img {
display: block;
width: 400px;
height: 400px;
margin-bottom: 50px;
}
</style>
<body>
<div class="container">
<img src="./img/loading.jpg" data-src="./img/pic.png" />
<img src="./img/loading.jpg" data-src="./img/pic.png" />
<img src="./img/loading.jpg" data-src="./img/pic.png" />
<img src="./img/loading.jpg" data-src="./img/pic.png" />
<img src="./img/loading.jpg" data-src="./img/pic.png" />
<img src="./img/loading.jpg" data-src="./img/pic.png" />
</div>
<script>
function lazyLoad() {
var scrollTop =
document.body.scrollTop || document.documentElement.scrollTop;
var winHeight = window.innerHeight;
for (var i = 0; i < imgs.length; i++) {
if (imgs[i].offsetTop < scrollTop + winHeight) {
imgs[i].src = imgs[i].getAttribute("data-src");
}
}
}
window.onscroll = lazyLoad();
</script>
</body>
</html>
3.promise实现图片异步加载
let imageAsync=(url)=>{
return new Promise((resolve,reject)=>{
let img=new Image();
img.src=url;
img.onload=()=>{
resolve(image)
}
img.onerror=(err)=>{
reject(err)
}
})
}
imageAsync("url").then(()=>{
console.log('加载成功')
}).catch((error)=>{
console.log('加载失败')
})
4.每隔一秒打印
// 使用闭包实现
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
}
// 使用 let 块级作用域
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
function sleep(time){
for(let i=0;i<5;i++){
Promise.resolve(i).then((result)=>{
setTimeout(()=>{
console.log(result)
},time*result)
})
}
}
sleep(1000)
5.双向数据绑定
//=>发布订阅的类
class EventEmitter {
constructor() {
this.arrayList = {};
}
on(name, fn) {
if (this.arrayList[name] && !this.arrayList[name].includes(fn)) {
this.arrayList[name].push(fn);
} else {
this.arrayList[name] = [fn];
}
console.log(this.arrayList);
}
off(name, fn) {
if (this.arrayList[name]) {
let index = this.arrayList[name].indexOf(fn);
this.arrayList[name].splice(index, 1);
console.log(this.arrayList);
}
}
emit(name, once = false, ...arg) {
if (this.arrayList[name]?.length > 0) {
for (let key of this.arrayList[name]) {
key.call(this, arg);
}
}
if (!once) {
delete this.arrayList[name];
}
console.log(this.arrayList);
}
}
let s1 = new EventEmitter();
let f1 = function () {
console.log(666);
};
let f2 = function () {
console.log(777);
};
var input = document.querySelector("#ipt");
input.oninput = function (e) {
obj.value = e.target.value;
};
let obj = {
value: "",
};
Object.defineProperty(obj, "value", {
get() {
console.log("我被读了");
},
set(newVal) {
s1.on("value1", function () {
console.log("我的值是" + newVal);
console.log("我被改了");
});
input.value = newVal;
return newVal;
},
});
6.观察者模式
在观察者模式中,只有两个主体,分别是目标对象Subject,观察者Observer。
- 观察者需
Observer要实现update方法,供目标对象调用。update方法中可以执行自定义的业务代码。 - 目标对象
Subject也通常被叫做被观察者或主题,它的职能很单一,可以理解为,它只管理一种事件。Subject需要维护自身的观察者数组observerList,当自身发生变化时,通过调用自身的notify方法,依次通知每一个观察者执行update方法。
// 观察者
class Observer {
/**
* 构造器
* @param {Function} cb 回调函数,收到目标对象通知时执行
*/
constructor(cb){
if (typeof cb === 'function') {
this.cb = cb
} else {
throw new Error('Observer构造器必须传入函数类型!')
}
}
/**
* 被目标对象通知时执行
*/
update() {
this.cb()
}
}
// 目标对象
class Subject {
constructor() {
// 维护观察者列表
this.observerList = []
}
/**
* 添加一个观察者
* @param {Observer} observer Observer实例
*/
addObserver(observer) {
this.observerList.push(observer)
}
/**
* 通知所有的观察者
*/
notify() {
this.observerList.forEach(observer => {
observer.update()
})
}
}
const observerCallback = function() {
console.log('我被通知了')
}
const observer = new Observer(observerCallback)
const subject = new Subject();
subject.addObserver(observer);
subject.notify();
- 角色很明确,没有事件调度中心作为中间者,目标对象
Subject和观察者Observer都要实现约定的成员方法。 - 双方联系更紧密,目标对象的主动性很强,自己收集和维护观察者,并在状态变化时主动通知观察者更新。
7.发布订阅模式
class PubSub {
constructor() {
// 维护事件及订阅行为
this.events = {}
}
/**
* 注册事件订阅行为
* @param {String} type 事件类型
* @param {Function} cb 回调函数
*/
subscribe(type, cb) {
if (!this.events[type]) {
this.events[type] = []
}
this.events[type].push(cb)
}
/**
* 发布事件
* @param {String} type 事件类型
* @param {...any} args 参数列表
*/
publish(type, ...args) {
if (this.events[type]) {
this.events[type].forEach(cb => {
cb(...args)
})
}
}
/**
* 移除某个事件的一个订阅行为
* @param {String} type 事件类型
* @param {Function} cb 回调函数
*/
unsubscribe(type, cb) {
if (this.events[type]) {
const targetIndex = this.events[type].findIndex(item => item === cb)
if (targetIndex !== -1) {
this.events[type].splice(targetIndex, 1)
}
if (this.events[type].length === 0) {
delete this.events[type]
}
}
}
/**
* 移除某个事件的所有订阅行为
* @param {String} type 事件类型
*/
unsubscribeAll(type) {
if (this.events[type]) {
delete this.events[type]
}
}
}
发布订阅模式中,对于发布者Publisher和订阅者Subscriber没有特殊的约束,他们好似是匿名活动,借助事件调度中心提供的接口发布和订阅事件,互不了解对方是谁。
松散耦合,灵活度高,常用作事件总线
易理解,可类比于DOM事件中的dispatchEvent和addEventListener。
8.单例模式
9.工厂模式
10.Eventbus
class EventBus{
constructor(){
this._cache = {}
}
on(type,callback){
this._cache[type] = this._cache[type] || []
this._cache[type].push(callback)
}
off(type,callback){
const fns = this._cache[type]
if(!Array.isArray(fns)){
return
}
if(!callback){
fns.length = 0
return
}
while(fns.indexOf(callback) !== -1){
fns.splice(fns.indexOf(callback),1)
}
}
emit(type,data){
const fns = this._cache[type]
if(!Array.isArray(fns)){
return
}
fns.forEach(item => {
item(data)
})
}
}
const bus = new EventBus()
function cyCallback1(res){
console.log('cy on1: ',res)
}
function cyCallback2(res){
console.log('cy on2: ',res)
}
bus.on('cy',cyCallback1)
bus.on('cy',cyCallback1)
bus.on('cy',cyCallback2)
bus.off('cy',cyCallback1)
bus.emit('cy','cy emit')
11.实现checkbox全选反选
js实现
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style type="text/css">
* {
padding: 0px;
margin: 0px auto;
}
body {
padding: 30px;
width: 300px;
height: 300px;
}
</style>
<script type="text/javascript">
window.onload = function() {
//获取五个多选框items
var items = document.getElementsByName("items");
//1.checkAllBtn绑定单击响应函数
var checkAllBtn = document.getElementById("checkAllBtn");
checkAllBtn.onclick = function() {
//遍历items,使每个复选框都被选中
for(var i = 0; i < items.length; i++) {
items[i].checked = true;
}
};
//2.checkNoBtn绑定单击响应函数
var checkNoBtn = document.getElementById("checkNoBtn");
checkNoBtn.onclick = function() {
//遍历items,使每个复选框都不被选中
for(var i = 0; i < items.length; i++) {
items[i].checked = false;
}
};
//3.checkRevBtn绑定单击响应函数
var checkRevBtn = document.getElementById("checkRevBtn");
checkRevBtn.onclick = function() {
//遍历items,进行反选
for(var i = 0; i < items.length; i++) {
// if(items[i].checked) {
// items[i].checked = false;
// } else if(items[i].checked == false) {
// items[i].checked = true;
// }
items[i].checked = !items[i].checked;
}
};
};
</script>
</head>
<body>
<input type="button" id="checkAllBtn" value="全选" />
<input type="button" id="checkNoBtn" value="不选" />
<input type="button" id="checkRevBtn" value="反选" />
<div id="select">
<input type="checkbox" name="items" value="旅游" />旅游<br />
<input type="checkbox" name="items" value="阅读" />阅读<br />
<input type="checkbox" name="items" value="运动" />运动<br />
<input type="checkbox" name="items" value="音乐" />音乐<br />
<input type="checkbox" name="items" value="跳舞" />跳舞
</div>
</body>
</html>
vue实现
<template>
<div>
<span>全选</span>
<input type="checkbox" v-model="checkAll" />
<div v-for="(item,index) in test" :key="index">
<span>{{item.name}}</span>
<input type="checkbox" v-model="item.isSelected" />
</div>
</div>
</template>
<script>
export default {
data() {
return {
test: [
{ name: "测试1", isSelected: true },
{ name: "测试2", isSelected: true },
{ name: "测试3", isSelected: true },
{ name: "测试4", isSelected: true },
{ name: "测试5", isSelected: true }
]
};
},
computed: {
checkAll: {
get() {
// 返回什么结果接赋予给 checkAll 属性
return this.test.every(item => item.isSelected);
},
set(val) {
// val 是给 checkAll 赋予值的时候传递过来的
return this.test.forEach(item => (item.isSelected = val));
}
}
}
}
</script>
12.实现可拖拽div
<div id="dragdiv"></div>
var dragging=false;
var position =null;
dragdiv.addEventListener('mousedown',function(e){
dragging=true
position=[e.clientX,e.clientY]
})
document.addEventListener('mousemove',function(e){
if(dragging===false) return null
const x=e.clientX
const y=e.clientY
const deltaX=x-position[0]
const deltaY=y-position[1]
const left=parseInt(dragdiv.style.left||0)
const top=parseInt(dragdiv.style.top||0)
dragdiv.style.left=left+deltaX+'px'
dragdiv.style.top=top+deltaY+'px'
position=[x,y]
})
document.addEventListener('mouseup',function(e){
dragging=false
})
13.封装异步的fetch,使用async await方式来使用
(async () => {
class HttpRequestUtil {
async get(url) {
const res = await fetch(url);
const data = await res.json();
return data;
}
async post(url, data) {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await res.json();
return result;
}
async put(url, data) {
const res = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(data)
});
const result = await res.json();
return result;
}
async delete(url, data) {
const res = await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(data)
});
const result = await res.json();
return result;
}
}
const httpRequestUtil = new HttpRequestUtil();
const res = await httpRequestUtil.get('http://golderbrother.cn/');
console.log(res);
})();
14.实现简单路由
// hash路由
class Route{
constructor(){
// 路由存储对象
this.routes = {}
// 当前hash
this.currentHash = ''
// 绑定this,避免监听时this指向改变
this.freshRoute = this.freshRoute.bind(this)
// 监听
window.addEventListener('load', this.freshRoute, false)
window.addEventListener('hashchange', this.freshRoute, false)
}
// 存储
storeRoute (path, cb) {
this.routes[path] = cb || function () {}
}
// 更新
freshRoute () {
this.currentHash = location.hash.slice(1) || '/'
this.routes[this.currentHash]()
}
}
15.判断对象是否存在循环引用
const isCycleObject = (obj,parent) => {
const parentArr = parent || [obj];
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;
}
const a = 1;
const b = {a};
const c = {b};
const o = {d:{a:3},c}
o.c.b.aa = a;
console.log(isCycleObject(o)
16.实现并发控制
同时进行2个并发请求
class Scheduler{
list=[];
maxNum=2;
workingNum=0;
add(promiseCreator){
this.list.push(promiseCreator)
}
start(){
for(let i=0;i<this.maxNum;i++){
this.doNext()
}
}
doNext(){
if(this.list.length&&this.workingNum<this.maxNum){
this.workingNum++
this.list.shift()().then(()=>{
this.workingNum--
this.doNext()
})
}
}
}
var timeout=time=>new Promise(resolve=>setTimeout(resolve,time))
var scheduler=new Scheduler()
var addTask=(time,order)=>{
scheduler.add(()=>timeout(time).then(()=>console.log(order)))
}
addTask(1000,1)
addTask(500,2)
addTask(300,3)
addTask(400,4)
scheduler.start()
\