1.Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)。
Object.prototype._create = function (proto) {
const Fn = function () { }
Fn.prototype = proto
return new Fn()
}
function A() { }
const obj = Object.create(A)
const obj2 = Object._create(A)
console.log(obj.__proto__ === A) // true
console.log(obj.__proto__ === A) // true
2.手写call
Function.prototype._call = function(othis,...args) {
let _this = othis || window; //如果othis为undefined(不传),则默认为window
let fn = Symbol();
_this[fn] = this;//对象增加唯一属性调用,以此来改变this指向
let res = _this[fn](...args);//call传入的是数组
delete _this[fn];
return res;
}
3.手写bind
Function.prototype._bind = function(othis,args) {
let _this = this;
let fn = function(...ars) {
return _this.call(othis,...args,...ars)
}
return fn
}
4.New 关键字
new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
const _new = function(fn) {
const obj = {};
obj.__proto__ = fn.prototype;
const result = fn.call(obj);
return typeof (result === 'object' && result !== null) ? result : obj
}
5. 浅拷贝
const _shallowClone = target => {
if(typeof target === 'object' && target !== null) {
const constructor = target.constructor;
if(/^(Function|RegExp|Date|Set|Map)$/i.test(constructor.name)) return target;
const cloneTarget = Array.isArray(target) ? [] : {};
for(p in target) {
if(target.hasOwnProperty(p)) {
cloneTarget[p] = target[p]
}
}
return cloneTarget
}else{
return target
}
}
6.深拷贝
const _deepClone = (target,map = new WeakMap) => {
if(typeof target === 'object' && target !== null) {
const constructor = target.constructor;
if(/^(Function|RegExp|Date|Set|Map)$/i.test(constructor.name)) return new constructor(target);
if(map.get(target)) return map.get(target)//避免循环引用
map.set(target,true)
const cloneTarget = Array.isArray(target) ? [] : {};
for(p in target) {
if(target.hasOwnProperty(p)) {
cloneTarget[p] = _deepClone(target[p],map) //
}
}
return cloneTarget
}else{
return target
}
}
7.寄生组合式继承
一图胜千言
function Parent(name) {
this.name = name
}
Parent.prototype.getName = function () {
return this.name
}
function Son(name, age) {
// 这里其实就等于 this.name = name
Parent.call(this, name)
this.age = age
}
Son.prototype.getAge = function () {
return this.age
}
Son.prototype.__proto__ = Object.create(Parent.prototype)
const son1 = new Son('shao', 20)
console.log(son1.getName()) // shao
console.log(son1.getAge()) // 20
8.发布订阅者模式
class EventEmitter {
constructor () {
this.events = {}
}
on(name,callBack) {
if(this.events[name]) {
this.events[name].push(callBack)
}else{
this.events[name] = [callBack]
}
}
emit(name,...args) {
if(!this.events[name]) return;
this.events[name].forEach(cb => cb(...args))
}
off(name,callBack) {
if(!this.events[name]) return;
if(!callBack) {
this.events[name] = undefined
}
this.events[name] = this.events[name].filter(item => item !== callBack)
}
}
9.观察者模式
class Watchered {
constructor() {
this.watchers = []
}
addWatcher(watcher) {
this.watchers.push(watcher)
}
notify() {
this.watchers.forEach(w => w.doHandle())
}
}
class Watcher {
constructor(callBack) {
this.callBack = callBack
}
doHandle () {
console.log(this.callBack)
}
}
const w1 = new Watcher('喝水')
const w2 = new Watcher('吃饭')
const dw = new Watchered()
dw.addWatcher(w1)
dw.addWatcher(w2)
dw.notify()
怎么理解发布订阅者和观察者的区别呢 ?
其实发布订阅者模式只有一个中间者,好像啥事情都需要它亲自来做。而且仔细观察的话,发布订阅者模式会存在一个事件名和事件的对应关系,今天可以发布天气预报,只有订阅了天气预报的才会被通知,订阅了 KFC疯狂星期四闹钟事件 的不会被提醒。
而观察者模式,等被观察者发出了一点动静(执行notify),所有观察者都会被通知。
10.节流
函数节流是在一定时间内只执行一次,按照时间设置阈值
const throttle = (fn,delay = 300) => {
const isThrottling = false;
return function(...args) {
if(!isThrottling ) {
isThrottling = true
setTimeout(() => {
isThrottling = false
fn.apply(this,args)
},delay)
}
}
}
11.防抖
防抖是指动作只执行一次
const debounce = (fn,delay = 300) => {
let Timer = null;
return function(...args) {
clearTimeout(Timer)
setTimeout(() => {
fn.apply(this,args)
},delay)
}
}
12.once 函数
函数返回结果会被缓存下来,只会计算一次。
const f = (x) => x;
const onceF = once(f);
//=> 3
onceF(3);
//=> 3
onceF(4);
const once = (fn) => {
let res, isFirst = true
return function (...args) {
if (!isFirst) return res
res = fn.call(this, ...args)
isFirst = false
return res
}
}
13.函数柯里化
function fn(a, b, c,d) {
return a + b + c+d
}
function curry(fn) {
// 获取原函数的参数长度
const argLen = fn.length;
// 保存预置参数, 当预置参数 有三个的时候 直接返回了函数,需要单独执行一次
const presetArgs = [].slice.call(arguments, 1)
// 返回一个新函数
return function() {
// 新函数调用时会继续传参
const restArgs = [].slice.call(arguments)
const allArgs = [...presetArgs, ...restArgs]
if (allArgs.length >= argLen) {
// 如果参数够了,就执行原函数
return fn.apply(null, allArgs)
} else {
// 否则继续柯里化
return curry.call(null, fn, ...allArgs)
}
}
}
let curried = curry(fn);
console.log(curried(1, 2, 3,4)) // 6
console.log(curried(1, 2)(3,4)) // 6
console.log(curried(1)(2, 3)(4)) // 6
console.log(curried(1)(2)(3)(4)) // 6
14.累加函数应用
实现一个累加函数,下面的几种情况都能正确的调用。
console.log(sum(1, 2)(3)()) // 6
console.log(sum(1)(2)(3)()) // 6
console.log(sum(1, 2, 4)(4)()) // 11
function sum(...args) {
let params = args
const _sum = (...newArgs) => {
if (newArgs.length === 0) {
return params.reduce((pre, cur) => pre + cur, 0)
} else {
params = [...params, ...newArgs]
return _sum
}
}
return _sum
}
15. 实现 repeat 方法
function repeat(fn, times, delay) {
return async function (...args) {
for (let i = 0; i < times; i++) {
await new Promise((resolve, reject) => {
setTimeout(() => {
fn.call(this, ...args)
resolve()
}, delay)
})
}
}
}
const repeatFn = repeat(console.log, 4, 1000)
// 函数调用四次,每次间隔 1s 打印 hello
repeatFn('hello')
16.实现Promise.all/race/allSettled/any
-
Promise 身上的这些方法返回的都是一个 Promise
-
Promise.resolve 接受一个 Promise,若非 promise 则将其变成功状态的 Promise
// 有一个失败则返回失败的结果,全部成功返回全成功的数组
Promise.all = function (promiseList = []) {
return new Promise((resolve, reject) => {
const result = []
let count = 0
if (promiseList.length === 0) {
resolve(result)
return
}
for (let i = 0; i < promiseList.length; i++) {
Promise.resolve(promiseList[i]).then(res => {
result[i] = res
count++
// 不能直接通过 result.length 进行比较,因为 会存在下标大的先赋值
// 例如 i = 3 第一个返回结果,此时数组变为[empty,empty,empty,res]
if (count === promiseList.length) {
resolve(result)
}
}).catch(e => {
reject(e)
})
}
})
}
// 返回第一个成功或失败的结果
Promise.race = function (promiseList = []) {
return new Promise((resolve, reject) => {
if (promiseList.length === 0) {
return resolve([])
}
for (let i = 0; i < promiseList.length; i++) {
Promise.resolve(promiseList[i]).then(res => {
resolve(res)
}).catch(e => {
reject(e)
})
}
})
}
// 无论成功约否都返回,但是会添加一个 status 字段用于标记成功/失败
Promise.allSettled = function (promiseList = []) {
return new Promise((resolve, reject) => {
const result = []
let count = 0
const addRes = (i, data) => {
result[i] = data
count++
if (count === promiseList.length) {
resolve(result)
}
}
if (promiseList.length === 0) return resolve(result)
for (let i = 0; i < promiseList.length; i++) {
Promise.resolve(promiseList[i]).then(res => {
addRes(i, { status: 'fulfilled', data: res })
}).catch(e => {
addRes(i, { status: 'rejected', data: e })
})
}
})
}
// AggregateError,当多个错误需要包装在一个错误中时,该对象表示一个错误。
// 和 Promise.all 相反,全部失败返回失败的结果数组,有一个成功则返回成功结果
Promise.any = function (promiseList = []) {
return new Promise((resolve, reject) => {
if (promiseList.length === 0) return resolve([])
let count = 0
const result = []
for (let i = 0; i < promiseList.length; i++) {
Promise.resolve(promiseList[i]).then(res => {
resolve(res)
}).catch(e => {
count++
result[i] = e
if (count === promiseList.length) {
reject(new AggregateError(result))
}
})
}
})
}
17. 整数千分位加逗号
const subQ = (string,reStr = '') => {
if(string.length <= 3){
reStr = string+reStr;
return reStr
}else{
let nString = string.substring(0,string.length - 3)
let reStr1 = ','+string.substring(string.length - 3)
reStr = reStr1 + reStr
return subQ(nString,reStr)
}
return reStr
}
console.log(subQ('1234567'))
18.洗牌函数
有几张牌张牌,用 js 来进行乱序排列,要保持公平性
const shuffle = (arr) => {
// 不影响原来的数组
const result = [...arr]
for (let i = result.length; i > 0; i--) {
// 随机从 [0,i - 1] 产生一个 index, 将 i - 1 于 index 对应数组的值进行交换
const index = Math.floor(Math.random() * i);
[result[index], result[i - 1]] = [result[i - 1], result[index]]
}
return result
}
const arr = [1, 2, 3, 4, 5]
console.log(shuffle(arr)) // [ 3, 1, 2, 5, 4 ]
console.log(shuffle(arr)) // [ 2, 3, 5, 1, 4 ]
console.log(shuffle(arr)) // [ 4, 2, 3, 1, 5 ]
console.log(shuffle(arr)) // [ 5, 4, 2, 3, 1 ]
19.a == 1 && a == 2 && a == 3
如何让 a == 1 && a == 2 && a == 3
返回 true 呢
方案一
利用隐式转换会调用 valueOf
当遇到需要预期的原始值的对象时,js会自动调用它
const a = {
value: 1,
valueOf() {
return this.value++
}
}
console.log(a == 1 && a == 2 && a == 3) // true
方案二
在对象 valueOf 函数不存在的情况下会调用 toString 方法
const a = {
value: 1,
toString() {
return this.value++
}
}
console.log(a == 1 && a == 2 && a == 3) // true
方案三
利用Object.defineProperty
在全局 window 上挂载一个 a 属性
let _a = 1
Object.defineProperty(window, 'a', {
get() {
return _a++
}
})
console.log(a == 1 && a == 2 && a == 3)
20.手写LRU
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法[2],选择最近最久未使用的页面予以淘汰。该算法赋予每个页面[3]一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。
力扣地址[4]
/**
* @param {number} capacity
*/
var LRUCache = function(capacity) {
this.map = new Map()
this.capacity = capacity
};
/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function(key) {
if(this.map.has(key)){
const value = this.map.get(key)
// 更新存储位置
this.map.delete(key)
this.map.set(key,value)
return value
}
return - 1
};
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function(key, value) {
if(this.map.has(key)){
this.map.delete(key)
}
this.map.set(key,value)
// 如果此时超过了最长可存储范围
if(this.map.size > this.capacity){
// 删除 map 中最久未使用的元素
this.map.delete(this.map.keys().next().value)
}
};
21.优化一下
Generator
先看看下面输出的内容
async function getResult() {
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
console.log(1);
}, 1000);
})
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
console.log(2);
}, 500);
})
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
console.log(3);
}, 100);
})
}
getResult()
// 1 2 3
那如何使用 Es6 中的 generator 实现类似的效果呢 ?
function* getResult(params) {
yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
console.log(1);
}, 1000);
})
yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
console.log(2);
}, 500);
})
yield new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
console.log(3);
}, 100);
})
}
const gen = getResult()
// gen.next().value 就是每一次 yield 之后返回的 Promise
// gen.next() = {value: yeild 返回的数据,done: 迭代器是否走完}
gen.next().value.then(() => {
gen.next().value.then(() => {
gen.next();
});
});// 依次打印 1 2 3
将 gen.next() 封装一层,让其自己能够实现递归调用
const gen = getResult()
function co(g) {
const nextObj = g.next();
// 递归停止条件:当迭代器迭代到最后一个 yeild
if (nextObj.done) {
return;
}
nextObj.value.then(()=>{
co(g)
})
}
co(gen)