防抖和节流的实现
function debounce(fn, delay) {
var timer;
return function () {
var _this = this;
var args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(function () {
fn.apply(_this, args);
}, delay);
};
}
function throttle(fn, delay) {
var timer;
return function () {
var _this = this;
var args = arguments;
if (timer) {
return;
}
timer = setTimeout(function() {
fn.apply(_this, args); // 用apply指向调用debounce的对象,相当于_this.fn(args);
timer = null;
}, delay);
};
}
JSONP的实现
// jsonp原理:因为jsonp发送的并不是ajax请求,其实是动态创建script标签
// script标签是没有同源限制的,把script标签的src指向请求的服务端地址。
function jsonp(url, data = {
}, callback = 'callback') {
// 处理json对象,拼接url
data.callback = callbak
let params = []
for (let key in data) {
params.push(key + '=' + data[key])
}
let script = document.creatElement('script')
script.src = url + '?' + params.join('&')
document.body.appendChild(script)
// 返回Promise
return new Promise((resolve, reject) => {
window[callback] = (data) => {
try {
resolve(data)
} catch (e) {
reject(e)
} finally {
// 移除script元素
script.parentNode.removeChild(script)
console.log(script)
}
}
})
}
// 请求数据
jsonp('http://photo.sina.cn/aj/index', {
page: 1,
cate: 'recommend',
}, 'jsonCallback').then(data => {
console.log(data)
})
柯里化:实现 sum 支持sum(1)、sum(1)(2,3)调用
函数的柯里化,是 Javascript 中函数式编程的一个重要概念。它返回的,是一个函数的函数。其实现方式,需要依赖参数以及递归,通过拆分参数的方式,来调用一个多参数的函数方法,以达到减少代码冗余,增加可读性的目的。
function curry(fn, currArgs) {
return function () {
let args = [].slice.call(arguments);
// 首次调用时,若未提供最后一个参数currArgs,则不用进行args的拼接
if (currArgs !== undefined) {
args = args.concat(currArgs);
}
// 递归调用
if (args.length < fn.length) {
return curry(fn, args);
}
// 递归出口
return fn.apply(null, args);
}
}
解析一下 curry 函数的写法:
首先,它有 2 个参数,fn 指的就是本文一开始的源处理函数 sum。currArgs 是调用 curry 时传入的参数列表,比如 (1, 2)(3) 这样的。
再看到 curry 函数内部,它会整个返回一个匿名函数。
再接下来的 let args = [].slice.call(arguments);,意思是将 arguments 数组化。arguments 是一个类数组的结构,它并不是一个真的数组,所以没法使用数组的方法。我们用了 call 的方法,就能愉快地对 args 使用数组的原生方法了。
currArgs !== undefined 的判断,是为了解决递归调用时的参数拼接。
最后,判断 args 的个数,是否与 fn (也就是 sum )的参数个数相等,相等了就可以把参数都传给 fn,进行输出;否则,继续递归调用,直到两者相等。
实现两个字符串数字相加
function add(a, b) {
// 小数前面补0
const absLen = Math.abs(a.length - b.length)
if (a.length > b.length) {
b = Array(absLen).fill(0).join('') + b
} else if (a.length < b.length) {
a = Array(absLen).fill(0).join('') + a
}
const arrA = a.split('').reverse()
const arrB = b.split('').reverse()
let result = []
let carry = 0 // 进位
for (let i = 0; i < a.length; ++i) {
let sum = parseInt(arrA[i]) + parseInt(arrB[i]) + carry
result[i] = sum % 10
carry = sum > 9 ? 1 : 0
}
if (carry === 1) {
result[a.length] = 1
}
return result.reverse().join('')
}
JS 扁平化(flatten) 数组
var array = [[1, 2, 3], 4, 5, 6, [[7]], []]
var result = flatten(array)
console.log(result)
// 递归
function flatten(arr, result = []) {
for (let item of arr) {
if (Array.isArray(item))
flatten(item, result)
else
result.push(item)
}
return result
}
// 迭代器实现
// 这里我们给数组的迭代器函数重新定义了 next 方法,
// 实现了一个 getFirst 用来递归取真正的第一个数组元素(无论嵌套多少层),
// 在对数组进行迭代操作的时候,会自动调用迭代器的 next 方法,获得我们一个个基本元素。
Array.prototype[Symbol.iterator] = function () {
let arr = [].concat(this)
const getFirst = function (array) {
let first = array[0]
// 去掉为 [] 的元素
while (Array.isArray(array[0]) && array.length === 0) {
array.shift()
}
if (Array.isArray(first)) {
// 即将是 []
if (first.length === 1) array.shift()
return getFirst(first)
} else {
array.shift()
return first
}
}
return {
next: function () {
let item = getFirst(arr)
if (item) {
return {
value: item,
done: false,
}
} else {
return {
done: true,
}
}
},
}
}
// 生成器实现
function* flat(arr) {
for (let item of arr) {
if (Array.isArray(item))
yield* flat(item)
else
yield item
}
}
function flatten(arr) {
let result = []
for (let val of flat(arr)) {
result.push(val)
}
return result
}
// reduce
function flatten(arr) {
return arr.reduce((flat, toFlatten) => {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}
一个字符串不重复的最长子序列
// 12343210 => 43210
var lengthOfLongestSubstring = function (s) {
let arr = [], max = 0
for (let i = 0; i < s.length; i++) {
let index = arr.indexOf(s[i])
if (index !== -1) {
arr.splice(0, index + 1);
}
arr.push(s.charAt(i))
max = Math.max(arr.length, max)
}
return max
};
// chain = new Chain, chain.eat().sleep(5).eat().sleep(6).work()
Class Chain{
Task = Promise.resolve();
Eat() {
this.Task = this.Task.then(_ => {
console.log(eat) })
Return this;
}
Sleep(t) {
this.task = this.Task.then(_ => new Promise(r => {
Console.log()
setTimeout(r, t)
}))
Return this
}
}
Generator的最小实现
function generator(cb) {
return (function () {
var object = {
next: 0,
stop: function () {
}
};
return {
next: function () {
var ret = cb(object);
if (ret === undefined) return {
value: undefined, done: true };
return {
value: ret,
done: false
};
}
};
})();
}
Promise的最小实现核心功能:
- new Promise(fn) 其中 fn 只能为函数,且要立即执行
- promise.then(success, fail)中的 success 是函数,且会在 resolve 被调用的时候执行,fail
同理
实现思路:
- then(succeed, fail) 先把成功失败回调放到一个回调数组 callbacks[] 上
- resolve() 和 reject() 遍历 callbacks
- resolve() 读取成功回调 / reject() 读取失败回调,并异步执行 callbacks
里面的成功和失败回调(放到本轮的微任务队列中)
class Promise2 {
state = "pending";
callbacks = [];
constructor(fn) {
if (typeof fn !== "function") {
throw new Error("must pass function");
}
fn(this.resolve.bind(this), this.reject.bind(this));
}
resolve(result) {
if (this.state !== "pending") return;
this.state = "fulfilled";
nextTick(() => {
this.callbacks.forEach((handle) => {
if (typeof handle[0] === "function") {
handle[0].call(undefined, result);
}
});
});
}
reject(reason) {
if (this.state !== "pending") return;
this.state = "rejected";
nextTick(() => {
this.callbacks.forEach((handle) => {
if (typeof handle[1] === "function") {
handle[1].call(undefined, reason);
}
});
});
}
then(succeed, fail) {
const handle = [];
if (typeof succeed === "function") {
handle[0] = succeed;
}
if (typeof fail === "function") {
handle[1] = fail;
}
this.callbacks.push(handle);
}
}
function nextTick(fn) {
if (process !== undefined && typeof process.nextTick === "function") {
return process.nextTick(fn);
} else {
// 用MutationObserver实现浏览器上的nextTick
var counter = 1;
var observer = new MutationObserver(fn);
var textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true,
});
counter += 1;
textNode.data = String(counter);
}
}
这是个观察者模式,这种收集依赖 -> 触发通知 -> 取出依赖执行 的方式,被广泛运用于观察者模式的实现,在Promise里,执行顺序是then收集依赖 -> 异步触发resolve -> resolve执行依赖。
Promise A+规范
Promise本质是一个状态机,且状态只能为以下三种:Pending(等待态)、Fulfilled(执行态)、Rejected(拒绝态),状态的变更是单向的,只能从Pending -> Fulfilled 或 Pending -> Rejected,状态变更不可逆
then方法接收两个可选参数,分别对应状态改变时触发的回调。then方法返回一个promise。then 方法可以被同一个 promise 调用多次。
我们思考一下如何实现这种链式调用:
- 显然.then()需要返回一个Promise,这样才能找到then方法,所以我们会把then方法的返回值包装成Promise。
- .then()的回调需要拿到上一个.then()的返回值
- .then()的回调需要顺序执行,以上面这段代码为例,虽然中间return了一个Promise,但执行顺序仍要保证是1->2->3。我们要等待当前Promise状态变更后,再执行下一个then收集的回调,这就要求我们对then的返回值分类讨论
- 值穿透:根据规范,如果 then()
接收的参数不是function,那么我们应该忽略它。如果没有忽略,当then()回调不为function时将会抛出异常,导致链式调用中断 - 处理状态为resolve/reject的情况:其实我们上边 then()
的写法是对应状态为padding的情况,但是有些时候,resolve/reject 在 then()
之前就被执行(比如Promise.resolve().then()),如果这个时候还把then()回调push进resolve/reject的执行队列里,那么回调将不会被执行,因此对于状态已经变为fulfilled或rejected的情况。
// Promise/A+规范的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
// 构造方法接收一个回调
constructor(executor) {
this._status = PENDING // Promise状态
this._value = undefined // 储存then回调return的值
this._resolveQueue = [] // 成功队列, resolve时触发
this._rejectQueue = [] // 失败队列, reject时触发
// 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
let _resolve = (val) => {
// 把resolve执行回调的操作封装成一个函数,放进setTimeout里,以兼容executor是同步代码的情况
const run = () => {
if (this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = FULFILLED // 变更状态
this._value = val // 储存当前value
// 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
// 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
while (this._resolveQueue.length) {
const callback = this._resolveQueue.shift()
callback(val)
}
}
setTimeout(run)
}
// 实现同resolve
let _reject = (val) => {
const run = () => {
if (this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
this._status = REJECTED // 变更状态
this._value = val // 储存当前value
while (this._rejectQueue.length) {
const callback = this._rejectQueue.shift()
callback(val)
}
}
setTimeout(run)
}
// new Promise()时立即执行executor,并传入resolve和reject
executor(_resolve, _reject)
}
// then方法,接收一个成功的回调和一个失败的回调
then(resolveFn, rejectFn) {
// 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
typeof resolveFn !== 'function' ? resolveFn = value => value : null
typeof rejectFn !== 'function' ? rejectFn = reason => {
throw new Error(reason instanceof Error ? reason.message : reason);
} : null
// return一个新的promise
return new MyPromise((resolve, reject) => {
// 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
const fulfilledFn = value => {
try {
// 执行第一个(当前的)Promise的成功回调,并获取返回值
let x = resolveFn(value)
// 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
// 这里resolve之后,就能被下一个.then()的回调获取到返回值,从而实现链式调用
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
// reject同理
const rejectedFn = error => {
try {
let x = rejectFn(error)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
} catch (error) {
reject(error)
}
}
switch (this._status) {
// 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
case PENDING:
this._resolveQueue.push(fulfilledFn)
this._rejectQueue.push(rejectedFn)
break;
// 当状态已经变为resolve/reject时,直接执行then回调
case FULFILLED:
fulfilledFn(this._value) // this._value是上一个then回调return的值(见完整版代码)
break;
case REJECTED:
rejectedFn(this._value)
break;
}
});
};
}
Promise.Finally()
//finally()方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。在finally之后,还可以继续then。并且会将值原封不动的传递给后面的then
//finally方法
finally(callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value), // MyPromise.resolve执行回调,并在then中return结果传递给后面的Promise
reason => MyPromise.resolve(callback()).then(() => {
throw reason }
) // reject同理
)}
// MyPromise.resolve(callback())的意义,这里补充解释一下:这个写法其实涉及到一个finally()的使用细节,finally()如果return了一个reject状态的Promise,将会改变当前Promise的状态,这个MyPromise.resolve就用于改变Promise状态,在finally()没有返回reject态Promise或throw错误的情况下,去掉MyPromise.resolve也是一样的
Promise.all()
// Promise.all(iterable)方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve)
// 如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。
//静态的all方法
static all(promiseArr) {
let index = 0
let result = []
return new MyPromise((resolve, reject) => {
promiseArr.forEach((p, i) => {
//Promise.resolve(p)用于处理传入值不为Promise的情况
MyPromise.resolve(p).then(
val => {
index++
result[i] = val
//所有then执行后, resolve结果
if (index === promiseArr.length) {
resolve(result)
}
},
err => {
//有一个Promise被reject时,MyPromise的状态变为reject
reject(err)
}
)
})
})
}
Promise.race()
// Promise.race(iterable)方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
//同时执行Promise,如果有一个Promise的状态发生改变,就变更新MyPromise的状态
for (let p of promiseArr) {
MyPromise.resolve(p).then( //Promise.resolve(p)用于处理传入值不为Promise的情况
value => {
resolve(value) //注意这个resolve是上边new MyPromise的
},
err => {
reject(err)
}
)
}
})
}
Promise 和 async await实现
function asyncToGenerator(generatorFn) {
return function () {
var gen = generatorFn.apply(this, arguments);
return new Promise(function (resolve, reject) {
function step(key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
return Promise.resolve(value).then(function (value) {
step('next', value);
}, function (err) {
step('throw', err);
});
}
}
return step('next');
});
}
}
手写bind的实现
// 高级:支持 new,例如 new (funcA.bind(thisArg, args))
function bind_3(asThis) {
var slice = Array.prototype.slice;
var args1 = slice.call(arguments, 1);
var fn = this;
if (typeof fn !== "function") {
throw new Error("Must accept function");
}
function resultFn() {
var args2 = slice.call(arguments, 0);
return fn.apply(
resultFn.prototype.isPrototypeOf(this) ? this : asThis, // 用来绑定 this
args1.concat(args2)
);
}
resultFn.prototype = fn.prototype;
return resultFn;
}
手写深拷贝
class DeepClone {
constructor() {
this.cacheList = [];
}
clone(source) {
if (source instanceof Object) {
const cache = this.findCache(source);
if (cache) return cache; // 如果找到缓存,直接返回
else {
let target;
if (source instanceof Array) {
target = new Array();
} else if (source instanceof Function) {
target = function () {
return source.apply(this, arguments);
};
} else if (source instanceof Date) {
target = new Date(source);
} else if (source instanceof RegExp) {
target = new RegExp(source.source, source.flags);
}
this.cacheList.push([source, target]); // 把源对象和新对象放进缓存列表
for (let key in source) {
if (source.hasOwnProperty(key)) {
// 不拷贝原型上的属性,太浪费内存
target[key] = this.clone(source[key]); // 递归克隆
}
}
return target;
}
}
return source;
}
findCache(source) {
for (let i = 0; i < this.cacheList.length; ++i) {
if (this.cacheList[i][0] === source) {
return this.cacheList[i][1]; // 如果有环,返回对应的新对象
}
}
return undefined;
}
}
手写New
function _new(obj, ...rest) {
// 基于Obj的原型创建一个新的对象
const newObj = Object.create(obj.prototype)
// 添加属性到新创建的newObj上, 并获取obj函数执行的结果.
const result = obj.apply(newObj, rest);
// 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象
return typeof result === 'object' ? result : newObj;
}
手写 EventHub(发布-订阅)
核心思路是:
- 使用一个对象作为缓存
- on 负责把方法发布到缓存的 EventName 对应的数组
- emit 负责遍历触发(订阅) EventName 下的方法数组
- off 找方法的索引,并删除
class EventHub {
cache = {
};
on(eventName, fn) {
this.cache[eventName] = this.cache[eventName] || [];
this.cache[eventName].push(fn);
}
emit(eventName) {
this.cache[eventName].forEach((fn) => fn());
}
off(eventName, fn) {
const index = indexOf(this.cache[eventName], fn); // 这里用this.cache[eventName].indexOf(fn) 完全可以,封装成函数是为了向下兼容
if (index === -1) return;
this.cache[eventName].splice(index, 1);
}
}
// 兼容 IE 8 的 indexOf
function indexOf(arr, item) {
if (arr === undefined) return -1;
let index = -1;
for (let i = 0; i < arr.length; ++i) {
if (arr[i] === item) {
index = i;
break;
}
}
return index;
}
手写Promise.any
Promise.any = promises => {
return new Promise((resolve, reject) => {
let hasOneResolve = false;
let remaining = promise.length;
const errors = [];
for (let index in Promises) {
Promises[index].then(data => {
if (hasOneResolved) return
hasOneResolved = true
resolve(data)
}, err => {
if (hasOneResolved) return;
remaining--;
errors[index] = err;
remaining || reject(errors)
})
}
})
}
如何中断Promise
promise.then(fn).then()
// 补充fn
//fn
return new Promise((_, _) => {
})
BFS和DFS
function deepTraversal(node) {
let nodes = [];
if (node != null) {
nodes.push[node];
let childrens = node.children
for (let i = 0; i < childrens.length; i++) {
deepTraversal(childrens[i]);
}
}
return nodes;
}
function deepTraversal(node) {
let nodes = []
if (node != null) {
//存放将来要访问的节点
const stack = [];
stack.push(node);
while (stack.length != 0) {
let item = stack.pop()
nodes.push(item);
let childrens = item.children;
for (let i = children.length - 1; i >= 0; i--) {
stack.push(childrens[i])
}
}
}
return nodes;
}
function wideTraversal(node) {
let nodes = [];
let i = 0;
if (node != null) {
nodes.push(node.nextElementSibling)
node = nodes[i++]
wideTraversal(node.firstElementChild)
}
return nodes;
}
function wideTraversal(node) {
let nodes = [];
let i = 0;
while (node != null) {
nodes.push(node)
node = nodes[i++]
let childrens = node.children;
for (let i = 0; i < childrens.length; i++) {
nodes.push(childrens[i])
}
}
return nodes;
}