前端手写
2021-07-21 本文已影响0人
Grandperhaps
- 节流 防抖
- 用xhr手写axios
- 函数柯里化
- 手写promise
- 手写reduce
- new
- 深拷贝
- string.indexOf()
9.reduce实现map - 深拷贝
- 柯里化
- 手写repeat函数
- 手写promise.all
- 手写apply bind
- 实现一个eventbus
- 发布订阅
1. 节流 防抖
//节流:一个函数执行后,只有大于设定的执行周期后,才会执行第二次,
//有个需要频繁出发的函数,出于性能优化,在规定的时间内,只让出发的第一次生效,后边的不生效
/* fn被节流的函数 deley 规定时间*/
function throttle(fn, delay) {
//记录上一次函数出发时间
let lastTime = 0
return function() {
//记录当前函数触法的时间
let nowTime = Date.now()
if (nowTime - lastTime > delay) {
fn.call(this)
lastTime = nowTime
}
}
}
document.onscroll = throttle(() => {
console.log('触发成功!' + Date.now())
}, 1000)
防抖
//防抖函数:一个频繁出发的函数,在规定的某个时间内,只让最后一次生效,前边的不生效如:频繁点击按钮
function debounce(fn, delay) {
let timer = null
return function() {
//清楚上一次延时器
clearTimeout(timer)
//重新设置新的延时器,
timer = setTimeout(() => {
fn.call(this)
}, delay)
}
}
document.getElementById('btn').onclick = debounce(() => {
console.log("触发了")
}, 2000)
2. 用xhr手写axios(将原生的ajax封装成promise)
var myNewAjax=function(url){
return new Promise(function(resolve,reject){
var xhr = new XMLHttpRequest();
xhr.open('get',url);
xhr.send(data);
xhr.onreadystatechange=function(){
if(xhr.status==200&&readyState==4){
var json=JSON.parse(xhr.responseText);
resolve(json)
}else if(xhr.readyState==4&&xhr.status!=200){
reject('error');
}
}
})
}
3. 函数柯里化
function curry(fn,val){
return function(){
//转化为数组
var args = Array.from(arguments)
if(val){
//参数拼接
args = args.concat(val)
}
//fn.length 表示函数需要传入多少参数
//递归调用 当参数相等停止递归
if(fn.length > args.length){
return curry(fn,args)
}
return fn.apply(null,args)
}
}
function sum(a, b, c) {
console.log(a + b + c);
}
const fn = curry(sum);
fn(1,2,3)
fn(1)(2)(3)
4. 手写promise
5. 手写reduce
Array.prototype.reduce = function(fn, init) {
var arr = this // this就是调用reduce方法的数组
var total = init || arr[0] // 有初始值使用初始值
// 有初始值的话从0遍历, 否则从1遍历
for (var i = init ? 0 : 1; i < arr.length; i++) {
total = fn(total, arr[i], i , arr)
}
return total
}
var arr = [1,2,3]
console.log(arr.reduce((prev, item) => prev + item, 10))
6. new
- new的具体步骤
- 创建一个空对象 var obj = {}
- 修改obj.proto=Dog.prototype
- 只改this指向并且把参数传递过去,call和apply都可以
根据规范,返回 null 和 undefined 不处理,依然返回obj
function _new(fn,...rest){
//基于fn的prototype构建对象的原型
const thisObj = Object.create(fn.prototype);
//将thisObj作为fn的this,继承其属性,并获取返回结果为result
const result = fn.apply(thisObj,rest);
//根据result对象的类型决定返回结果
return typeof result === "object" ? result : thisObj;
}
7. 深拷贝
function deepClone(obj){
let objClone = Array.isArray(obj)?[]:{};
if(obj && typeof obj==="object"){
for(key in obj){
//排除forin继承父属性
if(obj.hasOwnProperty(key)){
//判断ojb子元素是否为对象,如果是,递归复制
if(obj[key]&&typeof obj[key] ==="object"){
objClone[key] = deepClone(obj[key]);
}else{
//如果不是,简单复制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
let a=[1,2,3,4],
b=deepClone(a);
a[0]=2;
console.log(a,b);
8. string.indexOf()
String.prototype.indexO = function(st){
// console.log(this.length);
let str = this;
var j = 0;
let reflag;
for(let i = 0;i< str.length;i++){
if (str.charAt(i) == st.charAt(0)){
// console.log(str.charAt(i))
// console.log(st.charAt(0))
let re_selft = i;
let _self = i;
while(j<st.length){
if(str.charAt(_self)!= st.charAt(j)){
reflag = -1;
return reflag;
}
else{
reflag = re_selft
}
_self++;
j++;
}
}
}
return reflag
}
9.reduce实现map
Array.prototype.mymap = function(fn,mapthis){
var res = []
var redthis = mapthis||null
this.reduce(function(sum,val,index,arr){
res.push(fn.call(redthis,val,index,arr))
},null)
return res
}
var arr = [1,2,3,5,1]
console.log(arr.mymap(val=>val*2));
10. 深拷贝
function deepClone(obj){
let objClone = Array.isArray(obj)?[]:{};
if(obj && typeof obj==="object"){
for(key in obj){
//排除forin继承父属性
if(obj.hasOwnProperty(key)){
//判断ojb子元素是否为对象,如果是,递归复制
if(obj[key]&&typeof obj[key] ==="object"){
objClone[key] = deepClone(obj[key]);
}else{
//如果不是,简单复制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
let a=[1,2,3,4],
b=deepClone(a);
a[0]=2;
console.log(a,b);
11. 柯里化
function curry(fn,val){
return function(){
//转化为数组
var args = Array.from(arguments)
if(val){
//参数拼接
args = args.concat(val)
}
//fn.length 表示函数需要传入多少参数
//递归调用 当参数相等停止递归
if(fn.length > args.length){
return curry(fn,args)
}
return fn.apply(null,args)
}
}
function sum(a, b, c) {
console.log(a + b + c);
}
const fn = curry(sum);
fn(1,2,3)//6
fn(1)(2)(3)//6
fn(1,2)(3)//6
12. 手写repeat函数
function sleep (func,wait,args) {
return new Promise((resolve)=>{
setTimeout(()=>{
func.apply(this,args);
resolve()
},wait)
})
}
function repeat(func, times, wait,args) {
return async function () {
for(i = 0;i<times;i++){
await sleep(func,wait,args)
}
}
}
function alert(){
console.log('hellowWord');
}
const repeatFunc = repeat(alert, 4, 3000);
repeatFunc()
// 调用这个 repeatFunc ("hellworld"),会alert4次 helloworld, 每次间隔3秒
13. 手写promise.all
function myPromiseAll(arr) { // 参数是一个iterable对象,一般是数组
// 返回一个Promise实例
return new Promise((resolve, reject) => {
var index = 0
var result = []
function newData(i,data){
result[i] = data
if(++index === arr.length){
resolve(result)
}
}
if (arr.lenght == 0) {
resolve()
} else {
for(let i = 0; i < arr.length ; i++){
if(arr[i].then){
arr[i].then(data=>{
newData(i,data)
},(err)=>{
reject(err)
console.log(arr);
// 输出传入的数组中每一个 promise 执行后的状态和值
return
})
} else {
newData(i,arr[i])
}
}
}
});
}
let p1 = new Promise((resolve, reject)=> {
setTimeout(resolve, 2000, "P1 resolved");
})
let p2 = new Promise((resolve, reject)=> {
setTimeout(reject, 3000, "P2 resolved");
})
let p3 = new Promise((resolve, reject)=> {
setTimeout(resolve, 4000, "P3 resolved");
})
let pResult = myPromiseAll([p1,p2,p3]);
pResult.then(value=>{
console.log(pResult);
console.log(value);
},err=> {
console.log(pResult);
console.log(err);
})
// 输入不仅仅只有Array
function promiseAll (args) {
return new Promise((resolve, reject) => {
const promiseResults = [];
let iteratorIndex = 0;
// 已完成的数量,用于最终的返回,不能直接用完成数量作为iteratorIndex
// 输出顺序和完成顺序是两码事
let fullCount = 0;
// 用于迭代iterator数据
for (const item of args) {
// for of 遍历顺序,用于返回正确顺序的结果
// 因iterator用forEach遍历后的key和value一样,所以必须存一份for of的 iteratorIndex
let resultIndex = iteratorIndex;
iteratorIndex += 1;
// 包一层,以兼容非promise的情况
Promise.resolve(item).then(res => {
promiseResults[resultIndex] = res;
fullCount += 1;
// Iterator 接口的数据无法单纯的用length和size判断长度,不能局限于Array和 Map类型中
if (fullCount === iteratorIndex) {
resolve(promiseResults)
}
}).catch(err => {
reject(err)
})
}
// 处理空 iterator 的情况
if(iteratorIndex===0){
resolve(promiseResults)
}
}
)
}
Race
function myPromiseRace(arr){
return new Promise((resolve,reject)=>{
arr.forEach(p=>{
Promise.resolve(p).then(data=>{
resolve(data)
},err=>{
reject(err)
})
})
})
}
let p1 = new Promise((resolve, reject)=> {
setTimeout(resolve, 2000, "P1 resolved");
})
let p2 = new Promise((resolve, reject)=> {
setTimeout(resolve, 1000, "P3 resolved");
})
let pResult = myPromiseRace([p1,p2]);
pResult.then(value=>{
console.log(pResult);
console.log(value);
},err=> {
console.log(pResult);
console.log(err);
})
14. 手写apply bind
bind
Function.prototype.mybind = function () {
let args = Array.from(arguments);
let thisArg = args.shift();
let thisFunc = this;
return function F() {
newArgs = args.concat(Array.from(arguments));
if (this instanceof F){
return thisFunc.apply(this.newArgs)
}
return thisFunc.apply(thisArg, newArgs);
}
}
apply
Function.prototype.myApply = function(context) {
if (typeof context === 'undefined' || context === null) {
context = window
}
context.fn = this
let args = arguments[1]
let result
if (args) {
result = context.fn(...args)
} else {
result = context.fn()
}
delete context.fn
return result
}
call
Function.prototype.myCall = function(context) {
// 判断是否是undefined和null
if (typeof context === 'undefined' || context === null) {
context = window
}
context.fn = this
let args = [...arguments].slice(1)
let result = context.fn(...args)
delete context.fn
return result
}
15. 实现一个eventbus
function EventBus() {}
EventBus.prototype.on = function (name, callback) {
//如果没有事件对象,新增一个
if(!this._events){
//创建一个干净的没有原型链的对象
this._events = Object.create(null);
}
//如果没有这个事件的订阅,新增一个,如果有,push进去
if(!this._events[name]){
this._events[name] = [callback];
}else{
this._events[name].push(callback);
}
}
EventBus.prototype.emit = function (name, ...args) {
//发布的时候,如果有这个事件,循环执行所有这个订阅的方法
if(this._events[name]){
this._events[name].forEach(callback => {
callback(...args);
})
}
}
EventBus.prototype.off = function (name) {
//如果有这个事件的订阅,清除所有订阅
if(this._events[name]){
delete this._events[name];
}
}
EventBus.prototype.once = function (name, callback) {
let once = (...args) => {
callback(...args);
this.off(name);
};
this.on(name, once);
}
let eventBus = new EventBus();
eventBus.on('on', function (msg) {
console.log(msg);
})
eventBus.once('once', function (msg) {
console.log(msg);
})
eventBus.on('off', function (msg) {
console.log(msg);
})
eventBus.emit('on', '发布on1')//发布on1
eventBus.emit('on', '发布on2')//发布on2
eventBus.emit('once', '发布once')//发布once
eventBus.emit('once', '发布once')
eventBus.emit('off', '发布off')//发布off
eventBus.off('off')
eventBus.emit('off', '发布off')
16. 发布订阅
class Event {
// Events<String, Function[]>
events = {};
emit(type, ...args) {
// 发布事件
// 可以传递多个参数,每个事件处理函数都会被执行一次
const listeners = this.events[type];
for (const listener of listeners) {
listener(...args);
}
}
on(type, listener) {
// 注册事件
// 一个事件可以绑定多个事件处理函数
this.events[type] = this.events[type] || [];
this.events[type].push(listener);
}
}
const e = new Event();
e.on("click", x => console.log(x.id));
e.emit("click", { id: 3 }); // 3
e.emit("click", { id: 4 }); // 4
17. 数组扁平化
1. reduce
function flatten(arr) {
return arr.reduce((result, item)=> {
return result.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
2. toString & split
function flatten(arr) {
return arr.toString().split(',').map(function(item) {
return Number(item);
})
}
3. join & split
和上面的toString一样,join也可以将数组转换为字符串
function flatten(arr) {
return arr.join(',').split(',').map(function(item) {
return parseInt(item);
})
}
4. 递归
递归的遍历每一项,若为数组则继续遍历,否则concat
function flatten(arr) {
var res = [];
arr.map(item => {
if(Array.isArray(item)) {
res = res.concat(flatten(item));
} else {
res.push(item);
}
});
return res;
}
5. 扩展运算符
es6的扩展运算符能将二维数组变为一维
[].concat(...[1, 2, 3, [4, 5]]); // [1, 2, 3, 4, 5]
function flatten(arr) {
while(arr.some(item=>Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
18. 扁平数组转换为数
function getTree(arr, parent) {
let temp = arr.slice()
let newArr = []
temp.forEach(item => {
if (item.parentId === parent) {
let obj = {}
obj.id = item.id
obj.parentId = item.parentId // 这一块可以写一个深拷贝
obj.children = getTree(temp, item.id)
newArr = newArr.concat(obj)
}
})
return newArr
}