64. 关于JS中一些重要的api实现
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
// {value: 4, done: false} // {value: 2, done: false}
// {value: 1, done: false} // {value: undefined, done: true}
咦?返回值有value和done属性,生成器也可以调用 next,返回的也是这样的数据结构,这么巧?!
没错,生成器本身就是一个迭代器。
既然属于迭代器,那它就可以用for...of遍历了吧?
当然没错,不信来写一个简单的斐波那契数列(50以内):
function* fibonacci(){ let [prev, cur] = [0, 1];
console.log(cur);
while(true) { [prev, cur] = [cur, prev + cur];
yield cur;
} }
for(let item of fibonacci()) { if(item > 50) break;
console.log(item);
} // 1
// 1 // 2
// 3 // 5
// 8 // 13
// 21 // 34
64. 关于JS中一些重要的api实现
用ES5实现数组的map方法
核心要点:
1)回调函数的参数有哪些,返回值如何处理。
for (var i = 0; i < arr.length; i++ ){ mappedArr.push(fn.call(context, arr[i], i, this));
} return mappedArr;
}
用ES5实现数组的reduce方法
核心要点:
1)初始值不传怎么处理
2)回调函数的参数有哪些,返回值如何处理。
Array.prototype.myReduce = function(fn, initialValue) { var arr = Array.prototype.slice.call(this);
var res, startIndex;
res = initialValue ? initialValue : arr[0];
startIndex = initialValue ? 0 : 1;
for(var i = startIndex; i < arr.length; i++) { res = fn.call(null, res, arr[i], i, this);
} return res;
}
实现call/apply
思路: 利用this的上下文特性。
//实现apply只要把下一行中的...args换成args即可 Function.prototype.myCall = function(context = window, ...args) {
let func = this;
let fn = Symbol("fn");
context[fn] = func;
let res = context[fn](...args);//重点代码,利用this指向,相当于 context.caller(...args)
delete context[fn];
return res;
}
实现Object.create方法(常用)
F.prototype.constructor = F;
return new F();
}
实现bind方法
核心要点:
1)对于普通函数,绑定this指向
2)对于构造函数,要保证原函数的原型对象上的属性不能丢失
Function.prototype.bind = function(context, ...args) { let self = this;//谨记this表示调用bind的函数
let fBound = function() { //this instanceof fBound为true表示构造函数的情况。如new func.bind(obj)
return self.apply(this instanceof fBound ? this : context || window, args.concat(Array.prototype.slice.call(arguments)));
} fBound.prototype = Object.create(this.prototype);//保证原函数的原型对象上的属性不
丢失 return fBound;
}
实现new关键字
核心要点:
1)创建一个全新的对象,这个对象的proto要指向构造函数的原型对象
2)执行构造函数
3)返回值为object类型则作为new方法的返回值返回,否则返回上述全新对象
function myNew(fn, ...args) { let instance = Object.create(fn.prototype);
let res = fn.apply(instance, args);
return typeof res === 'object' ? res: instance;
}
实现instanceof的作用
核心要点:原型链的向上查找。
if(proto == null) return false;
if(proto == right.prototype) return true;
proto = Object.getPrototypeof(proto);
} }
实现单例模式
核心要点: 用闭包和Proxy属性拦截
function proxy(func) { let instance;
let handler = { construct(target, args) {
if(!instance) { instance = Reflect.construct(func, args);
} return instance;
} }
return new Proxy(func, handler);
}
实现防抖功能
核心要点:
如果在定时器的时间范围内再次触发,则重新计时。
const debounce = (fn, delay) => { let timer = null;
return (...args) => { clearTimeout(timer);
timer = setTimeout(() => { fn.apply(this, args);
}, delay);
};
};
实现节流功能
核心要点:
如果在定时器的时间范围内再次触发,则不予理睬,等当前定时器完成,才能启动下一个定时器。
if (!flag) return;
flag = false;
setTimeout(() => { fn.apply(this, args);
flag = true;
}, delay);
};
};
实现深拷贝
以下为简易版深拷贝,没有考虑循环引用的情况和Buffer、Promise、Set、Map的处理,如果一一实现,过于复杂,面试短时间写出来不太现实
const clone = parent => { // 判断类型
const isType =(target, type) => `[object ${type}]` === Object.prototype.toString.call(target)
// 处理正则 const getRegExp = re => {
let flags = "";
if (re.global) flags += "g";
if (re.ignoreCase) flags += "i";
if (re.multiline) flags += "m";
return flags;
};
const _clone = parent => { if (parent === null) return null;
if (typeof parent !== "object") return parent;
let child, proto;
if (isType(parent, "Array")) { // 对数组做特殊处理
child = [];
} else if (isType(parent, "RegExp")) { // 对正则对象做特殊处理
child = new RegExp(parent.source, getRegExp(parent));
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
} else if (isType(parent, "Date")) { // 对Date对象做特殊处理
child = new Date(parent.getTime());
} else { // 处理对象原型
proto = Object.getPrototypeOf(parent);
// 利用Object.create切断原型链 child = Object.create(proto);
return _clone(parent);
};
实现Promise
重点难点,比较复杂
用法
new Promise((resolve, reject) => { //异步成功执行resolve,否则执行reject
}).then((res) => { //resolve触发第一个回调函数执行
}, (err) => { //reject触发第二个回调函数执行
}).then(res => { //需要保证then方法返回的依然是promise
//这样才能实现链式调用 }).catch(reason => {
});
//等待所有的promise都成功执行then, //反之只要有一个失败就会执行catch
Promise.all([promise1, ...]).then();
js
(1)初步实现Promise:
1)实现三种状态:‘pending’, 'fulfilled', 'rejected'
2)能够实现then方法两种回调函数的处理
//promise.js class Promise{
//传一个异步函数进来 constructor(excutorCallBack){
this.status = 'pending';
this.value = undefined;
this.fulfillAry = [];
this.rejectedAry = [];
//=>执行Excutor let resolveFn = result => {
if(this.status !== 'pending') return;
let timer = setTimeout(() => { this.status = 'fulfilled';
this.value = result;
this.fulfillAry.forEach(item => item(this.value));
}, 0);
})
};
try{ //执行这个异步函数
excutorCallBack(resolveFn, rejectFn);
} catch(err) { //=>有异常信息按照rejected状态处理
rejectFn(err);
} }
then(fulfilledCallBack, rejectedCallBack) { //resolve和reject函数其实一个作为微任务
//因此他们不是立即执行,而是等then调用完成后执行 this.fulfillAry.push(fulfilledCallBack);
this.rejectedAry.push(rejectedCallBack);
//一顿push过后他们被执行 }
}
module.exports = Promise;
测试如下:
let Promise = require('./promise');
let p1 = new Promise((resolve, reject) => { setTimeout(() => {
Math.random()<0.5?resolve(100):reject(-100);
}, 1000)
}).then(res => { console.log(res);
}, err => { console.log(err);
})
(2)完成链式效果
最大的难点在于链式调用的实现,具体来说就是then方法的实现。
//then传进两个函数 then(fulfilledCallBack, rejectedCallBack) {
//保证两者为函数 typeof fulfilledCallBack !== 'function' ? fulfilledCallBack = result =>
result:null;
typeof rejectedCallBack !== 'function' ? rejectedCallBack = reason => { throw new Error(reason instanceof Error? reason.message:reason);
} : null //返回新的Promise对象,后面称它为“新Promise”
//把then里面的方法拿过来执行 //执行的目的已经达到
let x = fulfilledCallBack(this.value);
//下面执行之后的下一步,也就是记录执行的状态,决定新Promise如何表现 //如果返回值x是一个Promise对象,就执行then操作
//如果不是Promise,直接调用新Promise的resolve函数, //新Promise的fulfilAry现在为空,在新Promise的then操作后.新Promise的resolve执
行 x instanceof Promise ? x.then(resolve, reject):resolve(x);
}catch(err){ reject(err)
} });
//以下同理 this.rejectedAry.push(() => {
try { let x = this.rejectedCallBack(this.value);
x instanceof Promise ? x.then(resolve, reject):resolve(x);
}catch(err){ reject(err)
} })
}) ;
}
测试用例:
let p1 = new Promise((resolve, reject) => { setTimeout(() => {
Math.random()<0.5?resolve(100):reject(-100);
}, 1000)
})
let p2 = p1.then(result => { //执行then返回的是一个新的Promise
return result + 100;
})
let p3 = p2.then(result => { console.log(result);
}, reason => { console.log(reason)
})
简单画图来模拟一下链式调用的内部流程:

有了then方法,catch自然而然调用即可:
catch(rejectedCallBack) { return this.then(null, rejectedCallBack);
}
(3)Promise.all()
接下来实现Promise.all()
//为类的静态方法,而不是在原型上 static all(promiseAry = []) {
let index = 0, result = [];
return new Promise((resolve, reject) => { for(let i = 0; i < promiseAry.length; i++){
promiseAry[i].then(val => { index++;
result[i] = val;
if( index === promiseAry.length){ resolve(result)
} }, reject);
} })
}
(4)Promise.race()
接下来是race方法
static race(promises) { return new Promise((resolve, reject) => {
if (promises.length === 0) {
}, reject);
} }
});
}
(5)Promise.resolve()
static resolve (value) { if (value instanceof Promise) return value
return new Promise(resolve => resolve(value))
}
(6)Promise.reject()
static reject (value) { return new Promise((resolve, reject) => reject(value))
}
完整代码
现在手写一个简陋但是功能较为完备的Promise就大功告成了。
class Promise{ constructor(excutorCallBack){
this.status = 'pending';
this.value = undefined;
this.fulfillAry = [];
this.rejectedAry = [];
//=>执行Excutor let resolveFn = result => {
if(this.status !== 'pending') return;
let timer = setTimeout(() => { this.status = 'fulfilled';
this.value = result;
this.fulfillAry.forEach(item => item(this.value));
}, 0);
};
let rejectFn = reason => { if(this.status !== 'pending')return;
let timer = setTimeout(() => { this.status = 'rejected';
this.value = reason;
this.rejectedAry.forEach(item => item(this.value))
} }
then(fulfilledCallBack, rejectedCallBack) { typeof fulfilledCallBack !== 'function' ? fulfilledCallBack = result =>
result:null;
typeof rejectedCallBack !== 'function' ? rejectedCallBack = reason => { throw new Error(reason instanceof Error? reason.message:reason);
} : null
return new Promise((resolve, reject) => { this.fulfillAry.push(() => {
try { let x = fulfilledCallBack(this.value);
x instanceof Promise ? x.then(resolve, reject ):resolve(x);
}catch(err){ reject(err)
} });
this.rejectedAry.push(() => { try {
let x = this.rejectedCallBack(this.value);
x instanceof Promise ? x.then(resolve, reject):resolve(x);
}catch(err){ reject(err)
} })
}) ;
} catch(rejectedCallBack) {
return this.then(null, rejectedCallBack);
} static all(promiseAry = []) {
let index = 0, result = [];
return new Promise((resolve, reject) => { for(let i = 0; i < promiseAry.length; i++){
promiseAry[i].then(val => { index++;
result[i] = val;
if( index === promiseAry.length){ resolve(result)
} }, reject);
} })
} static race(promiseAry) {
return new Promise((resolve, reject) => { if (promiseAry.length === 0) {
return;
}
全站分卷索引 · 147 卷
- 1HTML
- 22. canvas 相关
- 36. 说说 title 和 alt 属性
- 42. 浮动布局的优点?有什么缺点?清除浮动有哪些方式?
- 53. 使用display:inline-block会产生什么问题?解决方法?
- 64. 布局题:div垂直居中,左右10px,高度始终为宽度一半
- 75. 盒模型
- 87. CSS如何进行圣杯布局
- 98. CSS如何实现双飞翼布局?
- 109. 什么是BFC?
- 1112. 应用场景
- 1213. 画一个对话框
- 1314. 画一个平行四边形
- 1415. 用一个div画五角星
- 15JavaScript
- 163. null是对象吗?为什么?
- 178. 如何创建并使用BigInt?
- 189. typeof 是否能正确判断类型?
- 1910. instanceof能否判断基本数据类型?
- 2012. Object.is和===的区别?
- 2115. == 和 ===有什么区别?
- 2217. 如何让if(a == 1 && a == 2)条件成立?
- 2320. 闭包有哪些表现形式?
- 2421. 如何解决下面的循环输出问题?
- 2522. 原型对象和构造函数有何关系?
- 2623. 能不能描述一下原型链?
- 2725. 函数的arguments为什么不是数组?如何转化成数组?
- 2826. forEach中return有效果吗?如何中断forEach循环?
- 2928. JS中flat---数组扁平化
- 3029. 什么是高阶函数
- 3131. 能不能实现数组map方法 ?
- 3232. 能不能实现数组reduce方法 ?
- 3333. 能不能实现数组 push、pop 方法 ?
- 3434. 能不能实现数组filter方法 ?
- 3536. 能不能实现数组sort方法?
- 3637. 能不能模拟实现一个new的效果?
- 3739. 能不能实现一个 call/apply 函数?
- 3840. 谈谈你对JS中this的理解
- 3941. JS中浅拷贝的手段有哪些?
- 4042. 能不能写一个完整的深拷贝?
- 4143. 数据是如何存储的?
- 4244. V8 引擎如何进行垃圾内存的回收?
- 4345. 描述一下 V8 执行一段JS代码的过程?
- 4446. 宏任务(MacroTask)引入
- 4547. 微任务(MicroTask)引入
- 4649. 理解EventLoop:nodejs
- 4750. nodejs 和 浏览器关于eventLoop的主要区别
- 4853. JS异步编程有哪些方案?为什么会出现这些方案?
- 4954. 能不能简单实现一下 node 中回调函数的机制?
- 5055. Promise 凭借什么消灭了回调地狱?
- 5156. 为什么Promise要引入微任务?
- 5258. 实现Promise的 resolve、reject 和 finally
- 5359. 现Promise的 all 和 race
- 5460. 谈谈你对生成器以及协程的理解
- 5561. 如何让 Generator 的异步代码按顺序执行完毕?
- 5662. 解释一下async/await的运行机制。
- 5763. forEach 中用 await 会产生什么问题?怎么解决这个问题?
- 5864. 关于JS中一些重要的api实现
- 5965. 事件流向
- 602. HTTP有哪些请求方法?
- 615. 如何理解 HTTP 状态码?
- 626. 简要概括一下 HTTP 的特点?HTTP 有哪些缺点?
- 637. 对 Accept 系列字段了解多少?
- 648. 对于定长和不定长的数据,HTTP 是怎么传输的?
- 659. HTTP 如何处理大文件的传输?
- 6610. HTTP 中如何处理表单数据的提交?
- 6711. HTTP1.1 如何解决 HTTP 的队头阻塞问题?
- 6812. 对 Cookie 了解多少?
- 6913. 如何理解 HTTP 代理?
- 7014. 如何理解 HTTP 缓存及缓存代理?
- 7117. 客户端的缓存控制
- 7219. 传统 RSA 握手
- 7320. RSA 和 ECDHE 握手过程的区别
- 7422. HTTP/2 有哪些改进?
- 7523. HTTP/2 中的二进制帧是如何设计的?
- 76TCP协议
- 771. 能不能说一说 TCP 和 UDP 的区别?
- 783. 为什么是三次而不是两次、四次?
- 794. 三次握手过程中可以携带数据么?
- 806. 说说 TCP 四次挥手的过程
- 817. 为什么是四次挥手而不是三次?
- 829. 说说半连接队列和 SYN Flood 攻击的关系
- 8310. 如何应对 SYN Flood 攻击?
- 8412. 说说 TCP 快速打开的原理(TFO)
- 8513. 能不能说说TCP报文中时间戳的作用?
- 8614. TCP 的超时重传时间是如何计算的?
- 8715. 能不能说一说 TCP 的流量控制?
- 8816. 能不能说说 TCP 的拥塞控制?
- 8917. 能不能说说 Nagle 算法和延迟确认?
- 9018. 如何理解 TCP 的 keep-alive?
- 911. 能不能说一说浏览器缓存?
- 922. 能不能说一说浏览器的本地存储?各自优劣如何?
- 933. 说一说从输入URL到页面呈现发生了什么?(网络)
- 944. 说一说从输入URL到页面呈现发生了什么?(解析算法)
- 955. 说一说从输入URL到页面呈现发生了什么?(渲染过程)
- 966. 谈谈你对重绘和回流的理解
- 977. 能不能说一说XSS攻击?
- 988. 能不能说一说CSRF攻击?
- 999. HTTPS为什么让数据传输更安全?
- 10010. 能不能实现事件的防抖和节流?
- 10111. 能不能实现图片懒加载?
- 102Vue
- 1032. mvvm和mvc区别?它和其它框架(jquery)的区别是什么?哪些场景适合?
- 1045. 描述下 vue 从初始化页面--修改数据--刷新页面 UI 的过程?
- 1057. 虚拟 DOM 实现原理
- 10610. Vue 组件间通信有哪些方式?
- 10714. route 和 router 的区别是什么?
- 10816. Vue 的 nextTick 的原理是什么?
- 10919. vuex
- 1101. 简单的反转链表
- 1112. 区间反转
- 1123. 两个一组翻转链表
- 1134. K个一组翻转链表
- 1145. 如何检测链表形成环?
- 1156. 如何找到环的起点
- 1167. 合并两个有序链表
- 1178. 合并 K 个有序链表
- 1189. 判断回文链表
- 119栈和队列
- 1203. 普通的层次遍历
- 1214. 二叉树的锯齿形层次遍历
- 1225. 二叉树的右视图
- 1236. 完全平方数
- 1247. 单词接龙
- 1258. 优先队列
- 1269. 关于堆的说明
- 12711. 实现优先队列
- 12812. 前 K 个高频元素
- 12913. 合并 K 个排序链表
- 13014. 什么是双端队列?
- 13116. 栈实现队列
- 13217. 队列实现栈
- 133二叉树
- 1342. 中序遍历
- 1353. 后序遍历
- 1364. 最大深度
- 1375. 最小深度
- 1386. 对称二叉树
- 1397. 二叉树的最近公共祖先
- 1408. 二叉搜索树的最近公共祖先
- 1419. 二叉树的直径
- 14210. 二叉树的所有路径
- 14311. 二叉树的最大路径和
- 14412. 验证二叉搜索树
- 14513. 将有序数组转换为二叉搜索树
- 14614. 二叉树展开为链表
- 14715. 不同的二叉搜索树II