手写call
手写Promise
手写call
手写apply
手写bind
手写instanceof
数组的复合和拍平
手写Ajax
节流和防抖
手写EventEmitter
函数柯里化
手写LRU缓存机制
手写router路由
手写图片懒加载
手写node获取当前目录下的文件
手写Promise.all()
// 手写promise.all
function PromiseAll(promises) {
return newPromise((resolve, reject) => {
//第一个坑, 如果不是一个数组, 返回的应该是错误信息
if(!Array.isArray(promises)) {
return reject(newError('传入的参数必须得是数组格式!'))
}
let res = []
let count = 0
const promiseLength = promises.length
promises.forEach((promise, index) => {
// 第二个点, 这里应该是要判断promise是否是promise格式的, 因为可能是数字等其他格式。
Promise.resolve(promise).then(result => {
// 第三个点, 这里应该要有一个计数器, 因为promiseall是按照顺序返回的, 使用i就不是按照顺序了
count ++
res[index] = result
// 第四个点, 这里如果使用length会有错, 因为, js中数组, 如果有第7个前面没有, 长度还是7(前面是undefined)
if(count === promiseLength) resolve(res)
}).catch(e => reject(e))
})
})
}
手写Promise
- 手写Promise
class PromiseV2 {
static PENDING = 'pending'
static FULFILLED = 'fulfilled'
static REJECTED = 'rejected'
// 构造器, 会在类被创建的时候自动执行
constructor(exector) {
//设置初始化状态
this.value = null
this.status = PromiseV2.PENDING
// 没有立即执行的回掉函数, 被压进这个数组中
this.callbacks = []
try {
exector(this.resolve.bind(this), this.reject.bind(this))
} catch (err) {
this.reject(err)
}
}
// 两个类方法
resolve(value) {
// 给个判断,如果不是pending状态, 就不改变状态。 保证状态一旦改变,就不进行修改了。
if (this.status === PromiseV2.PENDING) {
setTimeout(() => {
this.value = value
this.status = PromiseV2.FULFILLED
this.callbacks.map(callback => {
callback.onFulfilled(value)
})
})
}
}
reject(reason) {
if (this.status === PromiseV2.PENDING) {
setTimeout(() => {
this.value = reason
this.status = PromiseV2.REJECTED
this.callbacks.map(callback => {
callback.onRejected(reason)
})
})
}
}
then(onFulfilled, onRejected) {
console.log(onFulfilled)
// 里面的三种状态和下面的两个方法是相关联的
if (typeof onFulfilled !== 'function') {
// onFulfilled 定义为一个直接返回参数的函数
onFulfilled = value => value
}
if (typeof onRejected !== 'function') {
onRejected = reason => reason
}
// 因为then是链式回调的, 所以,then 应该也返回的是一个Promise
return new PromiseV2((resolve, reject) => {
// 如果是回调, 那么就直接压进去, 等着后来拿出来执行
if (this.status === PromiseV2.PENDING) {
this.callbacks.push({
onFulfilled: data => {
this.parse(onFulfilled(this.value), resolve, reject)
},
onRejected: err => {
this.parse(onRejected(this.value), resolve, reject)
}
})
}
// 如果是fulfilled, 直接onFulfilled
// 其实这里就相当于使用了, if(onFulfilled)
if (this.status === PromiseV2.FULFILLED) {
setTimeout(() => {
this.parse(onFulfilled(this.value), resolve, reject)
})
}
// 如果是rejected 直接onRejected
// 其实这里就相当于使用了, if(onRejected)
if (this.status === PromiseV2.REJECTED) {
setTimeout(() => {
this.parse(onRejected(this.value), resolve, reject)
})
}
})
}
// parse类函数, 抽离相同的函数部分
parse(result, resolve, reject) {
try {
if (result instanceof PromiseV2) {
result.then(resolve, reject)
} else {
resolve(result)
}
} catch (err) {
reject(err)
}
}
}
- 包含all和race的完整版本:
class PromiseV3 {
staticPENDING= 'pending'
staticFULFILLED= 'fulfilled'
staticREJECTED= 'rejected'
constructor(executor) {
this.value = ''
this.status = PromiseV3.PENDING
this.callbacks = []
if (typeof executor === "function") {
try {
executor(this.resolve.bind(this), this.reject.bind(this))
} catch (e) {
console.log(e)
}
} else {
try {
console.log(executor)
} catch (e) {
this.reject(e)
}
}
}
resolve(value) {
this.value = value
this.status = PromiseV3.FULFILLED
this.callbacks.map(callback => {
callback.onfulfilled(value)
})
}
reject(reason) {
console.log('reject')
}
then(onFulfilled, onRejected) {
if (this.value === PromiseV3.PENDING) {
try {
this.callbacks.push({
onFulfilled: (data) => {
try {
this.resolve(data)
} catch (e) {
this.reject(e)
}
},
onRejected: (reason) => {
try {
this.reject(reason)
} catch (e) {
this.reject(e)
}
}
})
} catch (e) {
this.reject(e)
}
}
if (this.status === PromiseV3.FULFILLED) {
setTimeout(() => {
try {
onFulfilled(this.value)
} catch (e) {
onRejected(e)
}
})
}
if (this.status === PromiseV3.REJECTED) {
setTimeout(() => {
try {
onRejected(this.value)
} catch (e) {
onRejected(e)
}
})
}
}
staticresolve(value) {
return new PromiseV3((resolve, reject) => {
if (value instanceof PromiseV3) {
value.then(resolve, reject)
} else {
resolve(value)
}
})
}
staticreject(reason) {
return new PromiseV3((resolve, reject) => {
if (reason instanceof PromiseV3) {
reason.then(resolve, reject)
} else {
reject(reason)
}
})
}
staticall(promises) {
const values = []
return new PromiseV3((resolve, reject) => {
promises.forEach(promise => {
promise.then(value => {
values.push(value)
if(promises.length === values.length){
resolve(values)
}
}, reason => {
reject(reason)
})
})
})
}
staticrace(promises) {
return new PromiseV3((resolve, reject) => {
promises.map(promise => {
promise.then(value => {
resolve(value)
}, reason => {
reject(reason)
})
})
})
}
}
// const p = new PromiseV3()
手写call
Function.prototype.myCall = function(context, args) {
context = context || window
const symbol = Symbol('fn')
context[symbol] = this
// 这里相对于apply来说, 就是需要将参数展开, 不是数组的形式执行的
context[symbol](...args)
// 别忘记删除了, 防止污染
delete context[symbol]
}
手写apply
Function.prototype.myApply = (context, args) => {
// 1. 上下文
context = context || window
// 2. 定义对象,改变this
const symbol = Symbol('fn')
context[symbol] = this
// 3. 带入参数, 执行方法
context[symbol](args)
delete context[symbol]
}
手写bind
Function.prototype.myBind=(context, args)=>{
context = context || window
const symbol = Symbol('fn')
context[symbol] = this
return function(_args){
const finArgs = [...args, ..._args]
// bind里面的参数是数组的格式, 不用展开
context[symbol](args)
delete context[symbol]
}
}
手写instanceof
/**
* 先确定使用, 再进行手写instanceof
* 1 左右两边, 左边如果是普通对象, 就直接返回false
* 2 左边是引用对象, 再向左边的隐式对象上找, 只要有就一直找
*/
const a = function (){}
console.log(a instanceof Function ) // trues
function instanceofV1(L, R) {
// 如果是除了null以外的普通对象, 就直接返回false
const baseType = [ 'undefined', 'boolean','number', 'string', 'symbol']
if(baseType.includes(typeof L)) return false
let RP = R.prototype
while (true){
if(L === null){ // 找到最高层, 没有, 返回false
return false
}
if(L === RP){ // 如果隐式对象和R的原型相等, 返回 true
return true
}
L = L.__proto__ // 如果没有, 那么就继续向上查找
}
}
console.log(instanceofV1(a, Function)); // true
数组的复合和拍平
- 先将数组转成二维数组
// 这是将一维数组转成二维数组
let newArr= []
function arr2(){
for(let i = 0; i<arr.length;){
newArr.push(arr.slice(i, i+=5))
}
}
- 将二维数组拍平
// 将二维数组转成一维数组, 拍平
function flat(arr) {
for(const item of arr){
// 如果是数组, 递归再来一次.
// 和对象的深拷贝是一样的.
if(Array.isArray(item)){
flat(item)
}else {
newArr.push(item)
}
}
}
- Es6有专门的flat拍平的方法
let arr = [
[ 1, 2, 3, 4, 5 ],
[ 6, 7, 8, 9, 10 ],
[ 11, 12, 13, 14, 15 ],
[ 16, 17, 18, 19, 20 ]
]
const newArr = arr.flat()
输出:
/*
[
1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20
]
*/
- toString 方法
// 将数组 toString方法后, 输出的是纯内容了, 自动将内部数组去掉了
const arr1 = [1, [2, 3, 4, 5], 6]
arr.toString(arr1).split(',').map(item => parseInt(item))
手写Ajax
/**
* 封装ajax
*/
class Ajax {
constructor(executor) {
this.xhr= null
executor(this.createXhr)
}
createXhr() {
if(window.XMLHttpRequest) {
this.xhr = newXMLHttpRequest()
}else {
this.xhr = newActiveXObject()
}
}
staticget(method, url, params){
this.xhr(method, url, params)
this.xhr.onReadystatechange=()=> {
if(this.xhr.readySate === 2){
console.log('接受到响应头')
}else if(this.xhr.readyState === 3) {
console.log('响应体加载中')
}else if(this.xhr.readyState === 4) {
console.log('响应体加载完成!这里进行后续操作!')
}
}
}
}
/*
* 0: 建立来xhr, 还没发起请求,还没发起open请求。
* 1: 进行连接, open方法被调用
* 2: 连接完成, send方法被调用, 接受响应头
* 3: 响应体的返回
* 4: 响应体下载完毕, 可以进行其他的操作了。
*
*
* ajax的第三个参数为 是否异步, 默认的是 true,
* 也就是不用等待这完成, 可以先执行下面的任务, 不用等待
*
* 如果第三个参数设置为false, 那么就是同步的, 必须等待这个玩意儿没收到数据, 才能进行后续的其他操作。
*
* */
function get() {
let xhr;
xhr.open('method', 'url', 'isAsync')
xhr.send()
if(xhr.onreadystatechange){
if(xhr.readyState === 4){
console.log('获取到数据')
}
}
}
function post() {
let xhr;
xhr.open('method', 'url', 'isAsync')
// 设置请求头的格式, 告诉服务器传输的数据格式是什么。
xhr.setRequestHeader("content-Type","application/x-www-form-urlencoded")
// 这里使用的是和url中query一样的格式
xhr.send('name=ck&age=20')
if(xhr.onreadystatechange) {
if(xhr.readyState === 4) {
console.log(xhr.responseText)
}
}
}
/*
* post方法, 或者传值的三种方法:
* 1. query格式: name=ck&age=18
* 2. 对象的字符串格式: {"name":"ck", "age":18}
* 2. JSON.stringfy({name:"ck", age:18}), 将方法二中的参数转成字符串格式的
* */
手写EventEmitter
/**
* eventEmitter 的使用,
* on(eventName, fn), 将事件绑定到eventEmitter中,
* once(eventName, fn), 注册一个只会调用一次的事件
* emit(eventName, args), 触发eventName事件, 后面是携带的参数
* remove(eventName), 移除事件队列中的指定事件名的事件
*/
class EventBus{
constructor(max) {
this.events = {}
this.maxListener = max || Infinity
console.log(this.maxListener)
}
on(event, cb){
// 先要声明一个数组, 如果当前事件不存在的话, 初始化一个数组
if(!this.events[event]) {
this.events[event] = []
}
if(this.maxListener !== Infinity && this.events[event].length > this.maxListener) {
console.log(`事件${event}超过最大监听数`)
return this;
}
this.events[event].push(cb)
return this;
}
once(event, cb){
const fn = (...args) => {
this.off(event, fn)
cb.call(this, args)
}
this.on(event, fn)
return this;
}
emit(event, ...args){
const cbs = this.events[event]
if(!cbs) {
console.log('没有该事件')
return this;
}
cbs.forEach(cb => cb.call(this, args))
return this;
}
off(event, cb){
// 一个事件上可以挂载好多的函数, 如果没有函数, 那么就删除该事件上的所有函数
if(!cb) {
this.events[event] = null
}else{
// 删除事件上指定的cb
this.events[event] = this.events[event].filter(item => item !== cb)
}
return this;
}
}
const events = new EventBus(2);
events.on('sum', (a, b) => {console.log(a + b)})
events.on('sum', (a, b) => {console.log(a + b + 1)})
events.on('sum', (a, b) => {console.log(a + b + 2)})
events.emit('sum', 1, 2)
函数柯里化
// 传入的参数是函数, 然后获取第二个参数起的参数.
function curry(fn){
const oldArg = Array.prototype.slice.call(arguments, 1)
return function (){
// 合并两个参数, 然后调用传入的那个目标函数进行调用
const newArgs = Array.prototype.slice.call(arguments)
const finalArg = [...oldArg, ...newArgs]
fn.call(null, finalArgs)
}
}
手写LRU
- 先弄清楚LRU的使用:
- 对使LRU最近最先使用:
- 如果被使用到了, 就那到第一个位置, 否则, 就按照使用顺序入队.
/**
* LRU 缓存机制, 最近最新使用, 也就是淘汰最早使用的,
* 比如一个浏览器, 浏览了100个网页, 那么, 假如需要缓存80个网页, 怎么做呢
*
*/
class LRU {
constructor(opacity) {
// 设置我们要缓存的容器大小
this.opacity = opacity;
this.list = []
}
put(key, value) {
// 如果队列中有该缓存, 将原来位置删掉, 将其放置到最新的位置
if(this.isNodeInList(key)){
this.remove(key)
this.list.unshift({key:value})
}else {
if(this.isFull()){
this.list.pop()
this.list.unshift({key:value})
}
}
}
// 获取指定的节点, 并将该节点放到最新的位置, 因为这里已经被使用过了, 所以, 需要更新到最前面。
// 其他的好像都是和cache一样进行普通的处理, 但是这里需要的是,使用过后, 需要进行更新操作。
get(key){
if(this.isNodeInList(key)) {
const node = this.remove(key)
this.list.unshift(key, node)
}else {
return -1;
}
}
// 删除队列中的指定节点
remove(key) {
for(let i=0; i<this.list.length; i++) {
if(this.list[i].key === key) {
const node = this.list.splice(i, 1)
// 删除的哪一个, 返回删除的key
return node.key
}
}
}
// 判断目标节点是否在队列中
isNodeInList(key) {
for(let item of this.list) {
if(item.key === key) {
return item
}
}
return false;
}
// 判断队列是否满了
isFull(){
return this.list.length === this.opacity
}
}
class LRUV2 {
constructor(opacity) {
this.opcacity = opacity
this.list = []
}
put(key,value) {
if(this.isNodeInList(key)) {
this.remove(key)
this.list.unshift({key:value})
}else {
this.list.unshift({key:value})
}
}
get(key) {
if(!this.isNodeInList(key)) return null
for(let item of this.list) {
if(item.key === key) {
// 在原有位置删除node
const node = this.remove(key)
// 将删除后的node入队
this.list.unshift(node)
// 最后返回这个node, 就是最近使用的一个
return node;
}
}
}
remove(key) {
if(this.isNodeInList(key)) {
for(let i=0; i<this.list.length; i++) {
if(this.list[i].key === key) {
const node = this.list[i]
this.list.splice(i, 1)
return node
}
}
}else {
return -1;
}
}
isNodeInList(key){
for(let i =0; i<this.list.length; i++) {
if(this.list[i].key=== key) {
return true
}
}
return false;
}
isFull(){
return this.list.length === this.opcacity;
}
}
class LUR{
constructor(maxListener) {
this.maxListener = maxListener
this.events = []
}
put(key, value){
if(this.isNodeInList(key)){
this.remove(key)
this.events.unshift({key, value})
}else {
this.events.unshift({key, value})
}
}
get(key){
if(this.isNodeInList(key)){
this.remove(key)
this.events.unshift({key, value})
}else {
console.log('该方法不存在哦~')
}
}
remove(key){
if(this.isNodeInList(key)){
this.events.forEach((item,index) => {
item.key === key && this.events.splice(index, 1)
})
}else {
console.log('该方法不存在哦!')
}
}
isFull(){
return this.events.length === this.maxListener
}
isNodeInList(key){
this.events.map(item => {
return item.key === key
})
}
}
用NodeJs获取目录下的所有文件
const file = require('fs')
const Path = require('path')
//TODO: 获取当前目录下的所有文件。 递归和非递归两种方式
function getAllFiles(path) {
const res = []
F(path)
function F(path) {
file.readdir(Path.resolve(__dirname, path), (err, files) => {
if (!err) {
files.map(item => {
(function (item) {
file.stat(item, (err1, stats) => {
if (!err) {
if (stats.isFile()) {
res.push(item)
} else if (stats.isDirectory() && item !== 'node_modules') {
F(item)
}
console.log(res)
}
})
})(item)
})
}
})
}
}
getAllFiles(__dirname)
手写路由Router
- 基于window路由
class Router {
/**
* options: {
* path:'/index',
* component: Main
* }
*
* 基于js原生的window.addEventListener('load')
* window.addEventListener('hashchange', function(){}, isScrape)// 是否是在捕获阶段执行
*/
constructor() {
this.routes = {}
this.currentHash = ''
this.refreshRoute = this.refreshRoute.bind(this)
this.init()
}
init() {
window.addEventListener('load', this.storeRoute, false)
window.addEventListener('hashchange', this.refreshRoute, false)
}
storeRoute(path, cb) {
this.routes[path] = cb || function () {
}
}
refreshRoute() {
this.currentHash = location.hash.slice(1) || '/'
this.routes[this.currentHash]()
}
}
- 基于vue-router
class RouterVueRouter{
constructor(options) {
this.options= options
this.routes = {}
this.currentHash = ''
// 创建hash表
this.createRouterMap.bind(this,options)
// 初始化, 用于监听页面路由改变
this.init()
}
init() {
window.addEventListener('load', this.setHash.bind(this), false)
window.addEventListener('hashchange', this.setHash.bind(this), false)
}
createRouterMap(){
this.options.map(item => {
this.routes[item.path] = item.component
})
}
// 获取当前的hash
getHash() {
return location.hash.slice(1) || '/'
}
// 设置当前hash
setHash() {
this.currentHash = this.getHash()
}
}
手写数据双向绑定
let obj = {}
let OInput = document.querySelector('#input')
let OSpan = document.querySelector('#span')
Object.defineProperties(obj,'text', {
configurable:true,
enumerable:true,
get(){
console.log('获取到了数据')
},
set(val) {
console.log('数据更新了')
OInput.value = val
OSpan.innerHTML = val
}
})
OInput.addEventListener('keyup', function (e){
obj.text = e.target.value
})