手撕代码
1.ajax原理
const ajax = {
get(url, fn) {
const xhr = XMLHttpRequest() // 第一步:创建XMLHttpRequest对象
xhr.open('get', url, true) // 第二步:通过xhr对象的open方法与服务器建立连接-get方法请求数据,true表示异步请求
xhr.onreadystatechange = function() {
if(xhr.readystate === 4) {
if(xhr.status >- 200 && xhr.status <300) {
fn(xhr.responseText) // 第四步,建立连接后,异步执行回调函数,即浏览器要实现的具体功能--将处理结果更新到HTML页面中
}else if(xhr.status >= 400) {
console.log(`错误信息:${xhr.status}`)
}
}
}
xhr.send() // 第三步,通过xhr对象的send方法发送给服务器端
},
post(url, fn, data) {
const xhr = new XMLHttpRequest()
xhr.open('post', url, true)
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
xhr.onreadystatechange = function() {
if(xhr.readystate === 4) {
if(xhr.status >- 200 && xhr.status <300) {
fn(xhr.responseText)
}else if(xhr.xhr.status >= 400) {
console.log(`错误信息:${xhr.status}`)
}
}
}
xhr.send(data)
}
}
2.new原理
function myNew(Fn, ...args) {
const obj = {} // 创建一个空对象
obj.__proto__ = Fn.prototype // 将对象的隐式原型和构造函数的显示原型连接起来
Fn.apply(obj, args) // 把把构造函数的this指向obj上
return obj
}
//验证
function Person(name, age) {
this.name = name
this.age = age
}
const p = new myNew(Person, 'fan', 23)
console.log(p)
3.打乱一个数组
// 方法一:洗牌算法
const shuffle1 = (arr) => {
let i = arr.length
while(i) {
let j = Math.floor(Math.random() * i--) // 随机一个位置j
[arr[j], arr[i]] = [arr[i], arr[j]] // 将当前位置i和随机的位置j进行交换
}
}
// 方法二:循环随机位置交换 和一一样
function shuffle2(arr) {
let len = arr.length
for( let i = 0 ; i < len ; i++) {
let j = parseInt(Math.random() * (len - 1))
let t = arr[i]
arr[i] = arr[j]
arr[j] = t
}
}
// 方法三:用sort方法随机一个数---sort():随机选两个值,a-b大于0就交换顺序,a-b小于0就不变
const shuffle3 = (arr) => {
return arr.sort(() => {
return Math.random() > 0.5 ? 1 : -1
})
}
4.防抖函数
规定时间内无论触发多少次,只有最后一次有效,每次触发都重新计时
function debounce(fn, delay = 200) {
let timer = null // 初始化计时器,作为计时清除的依据
return function(...args) {
if(timer) {
clearTimeout(timer) // 如果再次触发了定时器,先取消上一次的定时器
}
timer = setTimeout(() => {
fn.apply(this, args) // 获取上一个函数作用域的this
}, delay) // 200ms内不会再次触发
}
}
5.节流函数
规定时间内无论触发多少次,只要到时间必定执行一次。所以也是无论触发多少次,只有最后一次有效。
// 定时器写法
function throttle(fn, delay = 200) {
let flag = true // 设置一个开关
return function(...args) {
if(!flag) {
return // 如果开关关掉了就不用继续往下了
}
flag = false // 利用闭包,正式开始实现节流,关闭开关
let timer = setTimeout(() => {
fn.apply(this, args)
flag = true // 执行完再打开开关,清除定时器
clearTimeout(timer)
}, delay)
}
}
// 时间戳写法
function throttle2(fn, delay) {
let oldTime = Date.now()
return function(...args) {
let newTime = Date.now()
if(newTime - oldTime >= delay) {
fn.apply(null,args)
oldTime = Date.now()
}
}
}
6.数组去重
// 方法一
const unique1 = (arr) => {
return [...new Set(arr)]
}
// 方法2--两层嵌套法
function unique2(arr) {
if(!Array.isArray(arr)) {
console.log('type error')
return
}
for(let i=0; i<arr.length; i++) {
for(let j=i+1; j<arr.length; j++) {
if(arr[i] === arr[j]) {
arr.splice(j,1)
j--
}
}
}
return arr //缺陷NaN不会被去掉 NaN == NaN会返回false
}
// 方法3--利用自身元素不可重复性
function unique3(arr) {
if(!Array.isArray(arr)) {
console.log('type error')
return
}
let newArr = []
arr.forEach(index => {
if(!newArr.includes(index)) {
newArr.push(index)
}
})
return newArr
}
// 以上三种方法的缺陷:空对象不能被去重
// 方法4:利用自身元素不可重复性
function unique4(arr) {
if(!Array.isArray(arr)) {
console.log('type error')
return
}
let newArr = [];
let obj = {}
arr.forEach(index => {
if(typeof index !== 'object') {
if(newArr.indexOf(index) === -1) {
newArr.push(index)
}
} else {
let str = JSON.stringify(index)
if (!obj[str]) { // 判断str是否在obj里存在,若没有则放进去
newArr.push(index)
obj[str] = 1 // 放进去后设置为1?
}
}
})
return newArr
}
//同样也有缺陷 NaN又去不掉了
let arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique2(arr))
7.实现(a == 1 && a == 2 && a == 3)
// (a == 1 && a == 2 && a == 3)
// 方法一
let value = 0
Object.defineProperty(global, 'a', {
get() {
return ++value
}
})
console.log(a == 1 && a == 2 && a == 3)
// 方法二
const b = {
i : 1,
toString() {
return b.i++
}
}
console.log(b == 1 && b == 2 && b == 3)
// 方法三 ---a==1时,当a是一个数组,a会发生隐式转换,相当于调用了3次toString方法,toString本身就是调用join方法,shift删除第一个元素,并返回该元素
const c = [1, 2, 3]
c.join = c.shift // 不加括号,
// console.log(c.join()) // 1
// console.log(c.join()) // 2
// console.log(c.shift()) // 3
console.log(c == 1 && c == 2 && c == 3)
8.发布订阅者模式
function Event() {
// 里面的内容是个消息队列,
// 即'eventType1':[handle1, handle2, handle3],
// 'eventType2':[handle4, handle5]的形式
this.handlers = {}
}
Event.prototype = {
// 订阅事件 , eventType即订阅者--需要监听什么行为,,,handler即操作--行为发生后进行什么操作
on(eventType, handler) {
if(!(eventType in this.handlers)) {
this.handlers[eventType] = []; // 如果没有该订阅者,初始化创建一个该订阅者缓存列表-调度中心
}
this.handlers[eventType].push(handler); // 如果有该订阅者,就把订阅的消息添加进订阅者的缓存列表
},
emit(eventType) {
if(!(eventType in this.handlers)) {
return // 判断订阅者列表有没有该事件,如果没有,那还删什么,直接返回
}
this.handlers[eventType].forEach(handler => {
handler()
});
},
off(eventType, handler) {
if(!(eventType in this.handlers)) {
return // 判断订阅者列表有没有该事件,如果没有,那还删什么,直接返回
}
// if(!handler) { // 这样写不行
// if(!(handler in this.handlers[eventType])) { // in也不行
if(!(this.handlers[eventType].includes(handler))) { // includes可以
this.handlers[eventType] = [] // 如果确实订阅了该事件,检查有没有要删除的操作,如果没有就删掉整个事件?
return
}
this.handlers[eventType] = this.handlers[eventType].filter(item => item !== handler) // 如果有则过滤掉该操作,filter返回新数组
}
}
// 下面为验证环节
function handler1(){
console.log("监听到订阅者a变化之后要进行的操作");
}
function handler2(){
console.log("监听到订阅者b变化之后要进行的操作");
}
function handler3(){
console.log("监听到订阅者b变化之后要进行的操作");
}
// 创建一个订阅实例
let Publisher = new Event ();
//订阅事件a--向Publisher委托一些内容,帮我监听它
Publisher.on('a', handler1);
Publisher.on('b', handler2);
// Publisher.on('b', handler3);
// 这里事件b中没有handler3,整个事件设置为空
Publisher.off('b', handler3)
console.log(Publisher) // 显示Event->handlers->a,b
//触发事件a
Publisher.emit('a');
Publisher.emit('b');
9.观察者模式
// 发布者类--dependence依赖---订阅者需要依赖dep才能了解数据的变化--dep相当于杂志社,数据发生变化时,dep会通知watcher订阅者
class Dep {
constructor() {
this.subs = []; // 数组存储所有的观察者
}
addSub(sub) {
this.subs.push(sub); // 把观察者放到数组中
}
notify() {
this.subs.forEach(sub => {
sub.update() // 当事件发生时,循环所有在subs中的观察者
})
}
}
class Watcher {
constructor(name) {
this.name = name;
}
update() {
console.log(`${this.name}更新了`); // 当事件发生时,观察者具体要做的事情
}
}
let de1 = new Dep(); // 创建一个发布者对象
let o1 = new Watcher('卢本伟'); // 创建一个观察者(订阅者)对象
let o2 = new Watcher('马飞飞')
let o3 = new Watcher('yjj')
de1.addSub(o1) // 向杂志社订阅
de1.addSub(o2)
de1.addSub(o3)
de1.notify() // 杂志社通知数据变化
10.手写promise.all
function promiseAll(promises) { // 传入的参数可以任意一个iterator类型的参数:array、set、map、promise实例
return new Promise((resolve, reject) => {
if(!Array.isArray(promises)) throw new TypeError("传入参数必须为一个数组")
let result = [] // 存放合并后的promise实例
let count = 0 // 记录数组内合并的实例
for(let item of promises) {
Promise.resolve(item).then((data) => { // 把每一项转为promise对象后执行.then
result[count] = data
count++
if(count === promises.length) resolve(result)
}).catch(err => reject(err))
}
})
}
//验证
const promise1 = Promise.resolve(3)
const promise2 = 12
const promise3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'foo')
})
Promise.all([promise1, promise2, promise3]).then(value => console.log(value))
promiseAll([promise1, promise2, promise3]).then(value => console.log(value))
// [ 3, 12, 'foo' ]
11.手动添加iterator属性
使不可迭代对象可迭代以及使用for...of
const obj = {
a: 1,
b: 2
}
obj[Symbol.iterator] = function() {
let index = 0,
self = this,
keys = Object.keys(this)
return {
next() {
if(index < keys.length) {
return {
value: self[keys[index++]],
done: false
}
} else {
return {
value: undefined,
done: true
}
}
}
}
}
console.log([...obj]) // [1, 2]
for(let value of obj) {
console.log(value) // 1 2
}
12.promise
// ES5
function Promise(executor){
let self = this;
self.state = 'pending'; // 状态
self.value = undefined; // 成功的值
self.reason = undefined; // 失败的值-原因
// 用发布订阅模式来解决异步问题
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(value){
if(self.state === 'pending') {
self.state = 'fulfilled';
self.value = value;
// 当状态改变时,再调用回调函数
self.onFulfilledCallbacks.forEach( item => item(value))
}
};
function reject(reason){
if(self.state === 'pending') {
self.state = 'rejected';
self.reason = reason
}
};
// 如果executor执行报错,直接执行reject
try {
executor(resolve, reject);
} catch(err) {
reject(err);
}
}
Promise.prototype.then = function(onFulfilled, onRejected) {
// 确保传入的是一个函数
onFulfilled = onFulfilled === 'function' ? onFulfilled : function(data) { resolve(data) }
onRejected = onRejected === 'function' ? onRejected : function(reason) { throw reason }
let self = this;
// pending状态时,先把回调函数存起来
if(self.state === 'pending') {
self.onFulfilledCallbacks.push(onFulfilled);
self.onRejectedCallbacks.push(onRejected);
}
if(self.state === 'fulfilled') {
onFulfilled(self.value);
}
if(self.state === 'rejected') {
onRejected(self.reason);
}
}
modules.exports = Promise;
// ES6 ---完整版
class myPromise {
constructor(executor) {
let self = this;
self.state = 'pending'; // 状态
self.value = undefined; // 成功的值
self.reason = undefined; // 失败的值-原因
// 利用发布订阅模式解决回调问题,建立一个成功回调函数队列
self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = [];
// 定义resovle方法
resolve = value => {
if (self.state === 'pending') {
self.state = 'fulfilled';
self.value = value;
// 当成功回调队列中有函数的话,就删除第一个元素并执行该元素
while(self.onFulfilledCallbacks.length) self.onFulfilledCallbacks.shift()()
// 或者直接 self.onFulfilledCallbacks.forEach(fn => fn())
}
};
reject = reason => {
if (self.state === 'pending') {
self.state = 'rejected';
self.reason = reason;
// while(self.onRejectedCallbacks.length) self.onRejectedCallbacks.shift()()
self.onRejectedCallbacks.forEach(fn => fn())
}
};
// 如果executor执行报错,直接执行reject
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
onFulfilled = onFulfilled ? onFulfilledCallback : value => value
onRejected = onRejected ? onRejected : reason => { throw reason }
let self = this;
// if (self.state === 'fulfilled') {
// onFulfilled(self.value);
// } else if (self.state === 'rejected') {
// onRejected(self.reason);
// } else {
// self.onFulfilledCallbacks.push(() => {
// onFulfilled(self.value);
// });
// self.onRejectedCallbacks.push(() => {
// onRejected(self.reason);
// });
// }
//上面为简陋版,下面为具体版本
let promise2 = new myPromise((resolve, reject) => {
if(self.state === 'fulfilled') {
// 利用定时器把下面代码变成异步代码
setTimeout(() => {
try {
let x = onFulfilled(self.value);
// 判断x的值是普通值还是promise对象,如果是普通值,则直接调用resolve;如果是promise对象,则查看promise的执行结果,决定调用resolve还是reject
resolvePromise(x, resolve, reject);
} catch(e) {
reject(e);
}
}, 0);
} else if(self.state === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(x, resolve, reject);
} catch(e) {
reject(e);
}
}, 0);
} else {
// pending状态下,把成功和失败的回调函数收集起来---多个then调用
self.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(self.value);
resolvePromise(x, resolve, reject);
} catch(e) {
reject(e);
}
}, 0);
});
self.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(self.reason);
resolvePromise(x, resolve, reject);
} catch(e) {
reject(e);
}
}, 0);
});
}
})
// 返回一个promise对象
return promise2
}
// 链式调用
static resolvePromise(x, resolve, reject) {
// 如果x还是promise对象,那就还调用.then
if(x instanceof myPromise) {
x.then(resolve, reject);
} else {
// 如果是普通值,那么直接resolve
resolve(x)
}
}
}
modules.exports = myPromise;
13.数组扁平化
// 数组扁平化
// 降维过程,多维数组经过扁平化变成一维数组
// 方法一:循环嵌套
const arr1 = [1,2,[3,4,[5]]]
function flatten1(arr) {
let result = [];
for(let i=0; i<arr.length; i++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten1(arr[i])); // 循环递归,如果还是该元素还是数组,那么继续进行循环递归
} else {
result.push(arr[i]); // 如果该元素是数字的话就push到result里
}
}
return result;
}
// 方法二:用reduce方法实现循环迭代
function flatten2(arr) {
return arr.reduce(function(prev, next) {
return prev.concat(Array.isArray(next) ? flatten2(next) : next)
}, [])
}
// 方法三:
function flatten3(arr) {
return arr.toString().split(',') // [ '1', '2', '3', '4', '5' ] 不好
}
// 方法四:转成字符串后用正则过滤方括号,最后再转回对象
function flatten4(arr) {
let str = JSON.stringify(arr);
str = str.replace(/([|])/g, '');
str = '[' + str + ']';
return JSON.parse(str);
}
// 方法五:ES6 的 flat
function flatten5(arr) {
return arr.flat(Infinity) // Infinity表示不管多少层都展开
}
console.log(flatten5(arr1))
14.对象扁平化
let obj = {
a: 'a',
b: [1, {c: true}, [3]],
d: {e: undefined, f: 3},
g: null
}
// console.log(Object(obj))
/**
* 输出:
* obj = {
a: "a",
b[0]: 1,
b[1].c: true,
b[2][0]: 3,
d.f: 3
// null和undefined直接舍去????
}
*/
// 用递归思想来解决,如果是基本数据类型,直接输出,复杂类型就要继续递归。
function flatten(o) {
let result = {}
let recurse = (obj, preKey = '') => {
Object.entries(obj).forEach(([key, value]) => { // entires的结果中的key、value会以数组形式呈现
// preKey默认是'', 如果是递归入口 preKey有值 需要加 . 或者 [] 分割
let newKey = key // a b 0 1 c 2 0 d e f g
if (preKey) {
newKey = `${preKey}${ Array.isArray(obj) ? `[${newKey}]` : `.${newKey}` }` // b[0] b[1] b[1].c b[2] b[2][0] d.e d.f
}
// 引用类型继续递归拍平, 基本类型设置到结果对象上
if (value && typeof value === 'object') {
return recurse(value, newKey)
}
result[newKey] = value
})
}
recurse(o)
return result
}
console.log(flatten(obj))
15.call
// 思路:把要改变this指向的方法,挂到目标对象上执行并返回
Function.prototype.myCall = function(context) {
if(typeof this !== 'function') {
throw new TypeError('x')
}
context = context || window
// 定义this指向的当前的函数也就是Function实例----相当于给对象加一个属性fn 值为this
context.fn = this
// 第一项为this,call(this, arg1, arg2, ...),call传入的一堆参数,所以要用slice取索引1之后的所有元素
let arg = [...arguments].slice(1)
// fn的返回值就是mycall的返回值
let result = context.fn(...arg)
// 只改变一次,结束后把这个添加的属性再删掉
delete context.fn
return result
}
16.apply
Function.prototype.myApply = function(context) {
if(typeof this !== 'function') {
throw new TypeError('x')
}
context = context || window
context.fn = this
// apply传入的第二个参数是数组,所以直接取索引1
let result = context.fn(...arguments[1])
delete context.fn
return result
}
//测试用例:
obj={c:2}
function a(x,y){console.log(this,x,y)}
a.apply(obj,[1,2])//{c:2} 1 2
a.myApply(obj,[1,2])//{c:2,func:[function a]} 1 2
17.bind
Function.prototype.myBind = function(context, ...args1) {
//对context进行深拷贝,防止bind执行后返回函数未执行期间,context被修改
context = JSON.parse(JSON.stringify(context)) || context
context.fn = this
return function(...args2) {
let allArgs = [...args1, ...args2]
let result = context.fn(...allArgs)
delete context.fn
return result
}
}
obj = { c: 2 }
function a(x,y,z) { console.log(this, x, y, z) }
a.bind(obj,1,2)(3)//{c:2} 1 2 3
a.myBind(obj,1,2)(3)//{c:2,func:[function a]} 1 2 3
18.把url中的信息转为对象
// 将常规的url字符串的参数解析为对象的形式
let httpUrlStr = 'https://plus.wps.cn/?utm_campaign=WPS&utm_medium=navigation&utm_source=wps'
let target = { "utm_campaign": "WPS", "utm_medium": "navigation", "utm_source": "wps"};
function queryString(str) {
// 截取问号后的字符串
let params = str.split('?')[1];
let param = params.split('&');
let obj = {};
for(let i=0; i<param.length; i++) {
let paramsA = param[i].split('=');
let key = paramsA[0];
let value = paramsA[1];
obj[key] = value;
}
return obj
}
console.log(queryString(httpUrlStr))
19.反转数字
function myReverse(num) {
let res = 0;
let digit = 0;
while(num) {
digit = num % 10;
res = res * 10 + digit;
num /= 10
num = Math.floor(num)
}
return res
}
console.log(myReverse(13123123122))
20.找出数组中的重复数字
// 找出数组中重复的数字
// 1.set
function findRepeatNumber(arr) {
let set = new Set();
let res = [];
for(item of arr) {
let len = set.size;
set.add(item);
if(len === set.size) {
if(!res.includes(item)) {
res.push(item)
}
}
}
return res
}
// 2.map
function findRepeatNumber2(arr) {
let map = new Map();
let res = [];
for(item of arr) {
if(map.has(item)) {
if(!res.includes(item)) {
res.push(item);
}
} else {
map.set(item, item)
}
}
return res
}
//常规
function findRepeatNumber3(arr) {
// test装去重后的数组
let test = [];
let res = [];
for(item of arr) {
if(test.includes(item)) {
// 在去重数组里再挨个遍历是否有重复的数组
if(!res.includes(item)) {
res.push(item)
}
} else {
test.push(item)
}
}
return res
}
21.有效括号
const isValid = (s) => {
const n = s.length;
if(n % 2 === 1) {
return false;
}
const pairs = new Map([
[')', '('],
['}', '{'],
[']', '['],
]);
// 创建栈
const stk = [];
// 遍历该字符串,取其值,即左括号
for(let ch of s) {
// 如果找到任意右括号(键),并且栈中没有任何字符或者栈顶得元素和该项元素对应得键不对应则返回false,对应上了就删除栈里最后一项,然后接着往后找
if (pairs.has(ch)) {
if (!stk.length || stk[stk.length - 1] !== pairs.get(ch)) {
return false;
}
stk.pop();
}
else {
// 如果找到得是左括号(值),则放到栈中,以供找到右括号时进行对比寻找
stk.push(ch);
}
}
// 最后如果栈中没有元素了,就是被找到一对,删除了,找到一对删除了,最后删完了,就是能一一对应上
return !stk.length
}
22.统计字符串中包含某字母的数量
// 方法一:辅助变量
function findNum(arr) {
let newArr = arr.toLowerCase();
let res = 0;
let include = false;
for(let item of newArr) {
if (include && item == 'e') {
continue;
} else if (item == 'e') {
include = !include;
}
if (include && item == ' ') {
include = !include;
res++;
}
}
// 判断最后一个单词
if (include) {
res++;
}
return res
}
console.log(findNum('Nice to meet you'))
// 方法二:朴实无华-时间复杂度高
function findNum(arr) {
let newArr = arr.toLowerCase().split(' ');
let res = 0;
for(let item of newArr) {
if (item.split('').includes('e')) {
res++
}
}
return res
}
console.log(findNum('Nice to meet you'))
\