前端常见手写大全

149 阅读7分钟
数组迭代器
  1. 创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
  2. 第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
  3. 第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
  4. 不断调用指针对象的next方法,直到它指向数据结构的结束位置。

\

function myIteration(arr){
    let index=0
    return {
        next:function(){
            
            return index<arr.length?
                    {value:arr[index++],done:false}:
                    {value:undefined,done:true}
        }
    }
}
对象
let obj = {
    data: ['name:lilei', 'age:18', 'sex:男'],
    [Symbol.iterator]() {
        const self = this
        let index = 0;
        return {
            next() {
                return index<self.data.length?
                        {value:self.data[index++],done:false}:
                        {value:undefined,done:true}
            }
        }
    }
}
for (let i of obj) {
    console.log(i)
    //"name:lilei" "age:18" "sex:男"
}
深克隆
function deepClone(obj) {
            let newObj = {}
            function diff(obj) {
                return Object.prototype.toString.call(obj)
            }
            let res = diff(obj)
            if (res === '[object RegExp]') return new RegExp(obj)
            if (res === '[object Array]') return new Array(obj)
            if (res === '[object Date]') return new Date(obj)
            if (res === '[object Function]') return new Function(obj)
            for (let key in obj) {
                if (obj.hasOwnProperty(key)) {
                    if (typeof obj[key] !== "object") {
                        newObj[key] = obj[key]
                    } else {
                        newObj[key] = deepClone(obj[key])
                    }
                }
            }
            return newObj
        }

\

  ffunction cloneDeep(obj, map = new WeakMap()) {
    if (typeof obj !== 'object' || obj === null) return obj
    const objFromMap = map.get(obj)
    if (objFromMap) return objFromMap
    let target = {}
    map.set(obj, target)
    //map
    if (obj instanceof Map) {
        target = new Map()
        obj.forEach((v, key) => {
            const v1 = cloneDeep(v, map)
            const k1 = cloneDeep(k, map)
            target.set(k1, v1)
        })
    }
    //set
    if (obj instanceof Set) {
        target = new Set()
        obj.forEach(v => {
            const v1 = cloneDeep(v, map)
            target.add(v1)
        })
    }
    //array
    if (obj instanceof Array) {
        target = obj.map(item => {
            cloneDeep(item, map)
        })
    }
    //obj
    for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] !== "object") {
                target[key] = obj[key]
            } else {
                target[key] = cloneDeep(obj[key])
            }
        }

    }
    return target
}
jsonp
class Jsonp {
  constructor(req) {
    this.url = req.url;
    this.callbackName = req.callbackName;
  }
  create() {
    const script = document.createElement("script");
    const url = `${this.url}?callback=${this.callbackName}`;
    script.src = url;
    document.getElementsByTagName("head")[0].appendChild(script);
  }
}
发布订阅模式
//创建一个类一个属性三个方法
        class Observe {
            constructor() {
                this.message = {}
            }

            //绑定
            $on(type, fn) {
                if (!this.message[type]) {
                    this.message[type] = []
                }
                if (this.message[type].includes(fn)) return
                this.message[type].push(fn)
            }

            // 解除
            $off(type, fn) {
                if (!type) {
                    this.message = {}
                    return
                }
                if (!fn) {
                    delete this.message[type]
                } else {
                    this.message[type] = this.message[type].filter(item => item !== fn)
                }
            }

            // 触发
            $emit(type) {
                if (!this.message[type]) return
                this.message[type].forEach(item => item())
            }
        }

\

单例模式

\

let Person = (function () {
    function Person() {
        this.name = 'lilei'
    }
    let instance = null
    return function () {
        return !instance ? instance = new Person : instance
    }
})()

\

class Axios {
    private static instance: Axios | null = null
    private constructor() { }

    static make() {
        if (Axios.instance === null) {
            Axios.instance = new Axios()
        }
        return Axios.instance
    }
}

\

  • 网站弹出广告是固定的
  • 每次弹出的都是设计好的

\

观察者模式
//观察者:身份+技能
class Observer {
    constructor(name, fn = () => { }) {
        this.name = name
        this.fn = fn
    }

}
//被观察者
class Subject{
    constructor(state){
        this.state=state//状态
        this.observers=[]//观察者列表
    }
    setState(val){
        this.state=val
        this.observers.forEach(item=>item.fn(this.state))
    }
    addObservers(obs){
        if(this.observers.includes(obs))return
        this.observers.push(obs)
    }
}

\

装饰者模式

\

Function.prototype.before = function( beforefn ){
    var __self = this; // 保存原函数的引用
    return function(){ // 返回包含了原函数和新函数的"代理"函数
    beforefn.apply( this, arguments ); // 执行新函数,且保证 this 不被劫持,新函数接受的参数
    // 也会被原封不动地传入原函数,新函数在原函数之前执行
    return __self.apply( this, arguments ); // 执行原函数并返回原函数的执行结果,
    // 并且保证 this 不被劫持
}
}
Function.prototype.after = function( afterfn ){
    var __self = this;
    return function(){
    var ret = __self.apply( this, arguments );
    afterfn.apply( this, arguments );
    return ret;
}
};

\

\


数组扁平化

\

//自带方法
let arr = [1, [2, 8, [3, 0, [4, 4, 7, [5, 6]]]]]
        let array = arr.flat(Infinity)//维度-1

\

//递归循环
function flatten(array) {
            let arr = []
            for (let i of array) {
                if (Array.isArray(i)) {
                    //arr = arr.concat(flatten(i))
                    arr=[...arr,...flatten(i)]

                } else {
                    arr.push(i)
                }
            }
            return arr
        }

\

//while
function flattenWhile(array){
            while(array.some(item=>Array.isArray(item))){
                array=[].concat(...array)
            }
            return array
        }

\

//reduce
function flattenReduce(array){
    return array.reduce((res,next)=>{
        return res.concat(Array.isArray(next)?flatten(next):next)
    },[])
}

\

///join,toString
function flattenJS(array){
    return array.toString().split(',').map(item=>+item)
}

\

//flatMap
array.flatMap(item=>item)

\

对象扁平化

\

function flatten(obj) {
    let newObj = {}
    function isObj(obj) {
        return typeof obj === 'object' && obj !== null
    }
    function dfs(obj, newKey) {
        let isArr = Array.isArray(obj)
        for (let key in obj) {
            let cur = newKey ? (isArr ? `${newKey}[${key}]` : `${newKey}.${key}`) : `${key}`
            if (isObj(obj[key])) {
                dfs(obj[key], cur)
            } else {
                newObj[cur] = obj[key]
            }
        }
    }
    dfs(obj, '')
    return newObj
}

\

函数柯里化

\

//累加
function sum(arr) {
            return arr.reduce((a, b) => {
                return a + b
            })
        }
function curring(fn) {
            let args = []//最终参数
            return function _curring(...rest) {
                if (rest.length === 0) {
                    return fn(args)
                } else {
                    args=[...args,...rest]
                    return _curring
                }
            }
        }

\

instanceof

\

function instanceOf(a, b) {
            let left = a.__proto__
            let right = b.prototype
            while (true) {
                if (left === right) {
                    return true
                }
                if (left === null) {
                    return false
                }
                left = left.__proto__
            }
        }

\

将数组转化为树
let list = [            { id: 1, title: '标题1', parent_id: 0 },            { id: 2, title: '标题2', parent_id: 0 },            { id: 3, title: '标题2-1', parent_id: 2 },            { id: 4, title: '标题3-1', parent_id: 3 },            { id: 5, title: '标题4-1', parent_id: 4 },            { id: 6, title: '标题2-2', parent_id: 2 },        ]
        function convert(list) {
            const result = []
            const map = list.reduce((pre, cur) => {
                pre[cur.id] = cur
                return pre
            }, {})
            for (let item of list) {
                if (item.parent_id === 0) {
                    result.push(item)
                    // continue
                }
                if (item.parent_id in map) {
                    const parent = map[item.parent_id]
                    parent.children = parent.children || []
                    parent.children.push(item)
                }
            }
            return result
        }
  function jsonToTree(data) {
            // 初始化结果数组,并判断输入数据的格式
            let result = []
            if (!Array.isArray(data)) {
                return result
            }
            // 使用map,将当前对象的id与当前对象对应存储起来
            let map = {};
            data.forEach(item => {
                map[item.id] = item;
            });
            // 
            data.forEach(item => {
                let parent = map[item.pid];
                if (parent) {
                    (parent.children || (parent.children = [])).push(item);
                } else {
                    result.push(item);
                }
            });
            return result;
        }

手写(空对象指向构造函数原型  this指向  返回值判断)
const myNew = function (fn, ...rest) {
            let obj = Object.create(fn.prototype)//指定原型
            let result = fn.apply(obj, rest)//this
            return result instanceof Object ? result : obj//判断传进来的函数有没有返回值
        }

\

创建对象的方式
  1. 工厂模式
function createPerson(name,age){
    let obj =new Object()
    obj.name=name
    obj.age=age
    obj.say=function(){
        console.log(obj.name,obj.age)
    }
}
  1. 构造函数
function Person(name,age){
    this.name=name
    this.age=age
    this.say=function(){
        console.log(this.name,this.age)
    }
}
  1. 构造函数加原型
function Person(name,age){
    this.name=name
    this.age=age
}
Person.prototype.say=function(){
    console.log(this.name)
}
继承的模式
  1. 原型链继承  (子构造函数原型为父子实例)
function Parent() {
            this.name = 'parent';
            this.play = [1, 2, 3]
        }
        function Child() {
            this.type = 'child';
        }
        Child.prototype = new Parent();        //此处导致
        let child1 = new Child
        let child2 = new Child
        let res = child1.__proto__ == child2.__proto__
        console.log(res);
//两个实例用的是同一个原型对象
  1. 构造函数继承   (call方法,换属性)
function Parent1() {
    this.name = 'parent1';
}

Parent1.prototype.getName = function () {
    return this.name;
}

function Child1() {
    Parent1.call(this);
    this.type = 'child1'
}

let child = new Child1();
console.log(child);  // 没问题
console.log(child.getName());  // 会报错
//父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法
  1. 组合继承
  function Parent3 () {
    this.name = 'parent3';
    this.play = [1, 2, 3];
  }
 
  Parent3.prototype.getName = function () {
    return this.name;
  }
  function Child3() {
    // 第二次调用 Parent3()
    Parent3.call(this);
    this.type = 'child3';
  }
 
  // 第一次调用 Parent3()
  Child3.prototype = new Parent3();
  // 手动挂上构造器,指向自己的构造函数
  Child3.prototype.constructor = Child3;
  var s3 = new Child3();
  var s4 = new Child3();
  s3.play.push(4);
  console.log(s3.play, s4.play);  // 不互相影响
  console.log(s3.getName()); // 正常输出'parent3'
  console.log(s4.getName()); // 正常输出'parent3'
///通过注释我们可以看到 Parent3 执行了两次,第一次是改变Child3 的 prototype 的时候,第二次是通过 call 方法调用 Parent3 的时候,那么 Parent3 多构造一次就多进行了一次性能开销
  1. 原型式继承
  let parent4 = {
    name: "parent4",
    friends: ["p1", "p2", "p3"],
    getName: function() {
      return this.name;
    }
  };
 
  let person4 = Object.create(parent4);
  person4.name = "tom";
  person4.friends.push("jerry");
  let person5 = Object.create(parent4);
  person5.friends.push("lucy");
 
  console.log(person4.name);
  console.log(person4.name === person4.getName());
  console.log(person5.name);
  console.log(person4.friends);
  console.log(person5.friends);
///那么关于这种继承方式的缺点也很明显,多个实例的引用类型属性指向相同的内存,存在篡改的可能,接下来我们看一下在这个继承基础上进行优化之后的另一种继承方式——寄生式继承。
  1. 寄生式继承
   let parent5 = {
    name: "parent5",
    friends: ["p1", "p2", "p3"],
    getName: function() {
      return this.name;
    }
  };
 
  function clone(original) {
    let clone = Object.create(original);
    clone.getFriends = function() {
      return this.friends
    };
    return clone;
  }
 
  let person5 = clone(parent5);
  console.log(person5.getName());
  console.log(person5.getFriends());
  1. 寄生组合
  function clone (parent, child) {
    // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
    child.prototype = Object.create(parent.prototype);
    child.prototype.constructor = child;
  }
 
  function Parent6() {
    this.name = 'parent6';
    this.play = [1, 2, 3];
  }
   Parent6.prototype.getName = function () {
    return this.name;
  }
  function Child6() {
    Parent6.call(this);
    this.friends = 'child5';
  }
 
  clone(Parent6, Child6);
 
  Child6.prototype.getFriends = function () {
    return this.friends;
  }
 
  let person6 = new Child6();
  console.log(person6);
  console.log(person6.getName());
  console.log(person6.getFriends());

\

  1. 原型链继承:简单的prototype=实例
  2. 构造函数继承:借用方法
  3. 组合继承: 1+2(更改constructor)
  4. 原型式继承:儿子= Object.create(fn.protorype
  5. 寄生式继承: 寄生函数,实例=Object.create(父.prototype)
  6. 寄生组合式继承:5+3
防抖节流
function debounce(fn, delay) {
    let timer = null
    return function () {
        if (timer) { clearTimeout(timer) }
        timer = setTimeout(() => {
            fn()
        }, delay);
    }
}
function throttle(fn, delay) {
    let timer = true

    return function () {
        if (timer) {
            setTimeout(() => {
                fn()
                timer = true
            }, delay);
        }
        timer = false
    }
}
call,apply,bind
Function.prototype._call = function (context = window, ...args) {
    let key = Symbol('key')
    context[key] = this;
    let result = context[key](...args);
    delete context[key]; 
    return result;
};
Function.prototype._apply = function (context = window, args) {
    let key = Symbol('key')
    context[key] = this;
    let result = context[key](...args);
    delete context[key]; 
    return result;
};
Function.prototype._bind = function (context, ...outerArgs) {
    // this->func context->obj outerArgs->[10,20]
    let self = this
    // 返回一个函数
    return function F(...innerArgs) {
        // 把func执行,并且改变this即可
        return self.apply(context, [...outerArgs, ...innerArgs]) //返回改变了this的函数,参数合并
    }
}
Object.create()
// 模拟 Object.create

function create(proto) {
  function F() {}
  F.prototype = proto;

  return new F();
}
url参数解析
let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';

/* 结果
        { user: 'anonymous',
          id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
          city: '北京', // 中文需解码
          enabled: true, // 未指定值得 key 约定为 true
        }
*/

function parseParam(url) {
    let index = url.indexOf("?")
    let urlArr = url.slice(index + 1)
    let paramArr = urlArr.split("&")
    let res = {}
    for (let item of paramArr) {
        let arr = item.split("=")
        let key = arr[0]
        let value = arr[1] || true
        if (res.hasOwnProperty(key)) {
            let temValue = res[value]
            if (temValue instanceof Array) {
                res[key] = [...temValue, value]
            } else {
                res[key] = [temValue, value]
            }

        } else {
            res[key] = value
        }
    }
    return res
}

parseParam(url)
转化为驼峰
var s1 = "get-element-by-id"

// 转化为 getElementById

var f = function(s) {
    return s.replace(/-\w/g, function(x) {
        return x.slice(1).toUpperCase();
    })
}
实现千分位
function parseToMoney(num) {
    num = parseFloat(num.toFixed(3));
    let [integer, decimal] = String.prototype.split.call(num, '.');

    let reg = /(?!^)(?=(\d{3})+$)/g
    integer = integer.replace(reg, ',');
    return integer + (decimal ? '.' + decimal : '');
}

let res = parseToMoney(2434352541522)
console.log(res)
实现map
Array.prototype.MyMap = function(fn, context){
  // 转换类数组
  var arr = Array.prototype.slice.call(this);//由于是ES5所以就不用...展开符了
  var mappedArr = [];
  for (var i = 0; i < arr.length; i++ ){
    // 把当前值、索引、当前数组返回去。调用的时候传到函数参数中 [1,2,3,4].map((curr,index,arr))
    mappedArr.push(fn.call(context, arr[i], i, this));
  }
  return mappedArr;
}
实现reduce
Array.prototype.myReduce = function(fn, initialValue) {
  var arr = Array.prototype.slice.call(this);
  var res, startIndex;
  res = initialValue ? initialValue : arr[0]; // 不传默认取数组第一项
  startIndex = initialValue ? 0 : 1;
  for(var i = startIndex; i < arr.length; i++) {
    // 把初始值、当前值、索引、当前数组返回去。调用的时候传到函数参数中 [1,2,3,4].reduce((initVal,curr,index,arr))
    res = fn.call(null, res, arr[i], i, this);
  }
  return res;
}
* 类似柯理化(未完成)
add(1)(2)(4)
睡眠函数
function sleep(delay) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve()
        }, delay);
    })
}


sleep(2000).then(() => {
    console.log(222)
})
xhr发送请求
function request(url, method) {
    return new Promise((resolve.reject)=> {
        const xhr = new XMLHttpRequest()
        xhr.open(url, method)
        xhr.send(data = {})
        xhr.onreadystatechange = function () {
            if (this.readyState === 4 && this.status >= 200 && this.status < 300) {
                resolve(this.response)
            } else {
                reject(new Error("失败"))
            }
        }
    })

}
异步并发限制
function request(url) {
    return new Promise((resolve, reject) => {
        setTimeout(() => { resolve(url) }, 1000)
    })
}

function doTask(url) {
    let req = request(url)
    pool.push(req)
    req.then((res) => {
        // console.log(res)
        pool.splice(pool.indexOf(req), 1)
        let url = urls.shift()
        if (url) {
            doTask(url)
        }
    })

}

const MAX = 3
let pool = []
let urls = ["aaa", "bbb", "ccc", 'ddd', 'eee', 'fff', 'ggg']

while (pool.length < MAX) {
    let url = urls.shift()
    doTask(url)
}
图片懒加载
// <img src="default.png" data-src="https://xxxx/real.png">
function isVisible(el) {
  const position = el.getBoundingClientRect()
  const windowHeight = document.documentElement.clientHeight
  // 顶部边缘可见
  const topVisible = position.top > 0 && position.top < windowHeight;
  // 底部边缘可见
  const bottomVisible = position.bottom < windowHeight && position.bottom > 0;
  return topVisible || bottomVisible;
}

function imageLazyLoad() {
  const images = document.querySelectorAll('img')
  for (let img of images) {
    const realSrc = img.dataset.src
    if (!realSrc) continue
    if (isVisible(img)) {
      img.src = realSrc
      img.dataset.src = ''
    }
  }
}

// 测试
window.addEventListener('load', imageLazyLoad)
window.addEventListener('scroll', imageLazyLoad)
// or
window.addEventListener('scroll', throttle(imageLazyLoad, 1000))
<script>
    // 获取所有的图片标签
    const imgs = document.getElementsByTagName('img')
    // 获取可视区域的高度
    const viewHeight = window.innerHeight || document.documentElement.clientHeight
    // num用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出
    let num = 0
    function lazyload(){
        for(let i=num; i<imgs.length; i++) {
            // 用可视区域高度减去元素顶部距离可视区域顶部的高度
            let distance = viewHeight - imgs[i].getBoundingClientRect().top
            // 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明元素露出
            if(distance >= 0 ){
                // 给元素写入真实的src,展示图片
                imgs[i].src = imgs[i].getAttribute('data-src')
                // 前i张图片已经加载完毕,下次从第i+1张开始检查是否露出
                num = i + 1
            }
        }
    }
    // 监听Scroll事件
    window.addEventListener('scroll', lazyload, false);
</script>
实现async,await(未完成)
设置storage
class Storage{
  constructor(name){
      this.name = 'storage';
  }
  //设置缓存
  setItem(params){
      let obj = {
          name:'', // 存入数据  属性
          value:'',// 属性值
          expires:"", // 过期时间
          startTime:new Date().getTime()//记录何时将值存入缓存,毫秒级
      }
      let options = {};
      //将obj和传进来的params合并
      Object.assign(options,obj,params);
      if(options.expires){
      //如果options.expires设置了的话
      //以options.name为key,options为值放进去
          localStorage.setItem(options.name,JSON.stringify(options));
      }else{
      //如果options.expires没有设置,就判断一下value的类型
          let type = Object.prototype.toString.call(options.value);
          //如果value是对象或者数组对象的类型,就先用JSON.stringify转一下,再存进去
          if(Object.prototype.toString.call(options.value) == '[object Object]'){
              options.value = JSON.stringify(options.value);
          }
          if(Object.prototype.toString.call(options.value) == '[object Array]'){
              options.value = JSON.stringify(options.value);
          }
          localStorage.setItem(options.name,options.value);
      }
  }
  //拿到缓存
  getItem(name){
      let item = localStorage.getItem(name);
      //先将拿到的试着进行json转为对象的形式
      try{
          item = JSON.parse(item);
      }catch(error){
      //如果不行就不是json的字符串,就直接返回
          item = item;
      }
      //如果有startTime的值,说明设置了失效时间
      if(item.startTime){
          let date = new Date().getTime();
          //何时将值取出减去刚存入的时间,与item.expires比较,如果大于就是过期了,如果小于或等于就还没过期
          if(date - item.startTime > item.expires){
          //缓存过期,清除缓存,返回false
              localStorage.removeItem(name);
              return false;
          }else{
          //缓存未过期,返回值
              return item.value;
          }
      }else{
      //如果没有设置失效时间,直接返回值
          return item;
      }
  }
  //移出缓存
  removeItem(name){
      localStorage.removeItem(name);
  }
  //移出全部缓存
  clear(){
      localStorage.clear();
  }
}

\

判断循环引用
function diff(obj) {
    try {
        JSON.parse(JSON.stringify(obj))
        return  false
    } catch (error) {
        return true
    }
}

\

二维数组斜向打印
function printMatrix(arr){
  let m = arr.length, n = arr[0].length
	let res = []
  
  // 左上角,从0 到 n - 1 列进行打印
  for (let k = 0; k < n; k++) {
    for (let i = 0, j = k; i < m && j >= 0; i++, j--) {
      res.push(arr[i][j]);
    }
  }

  // 右下角,从1 到 n - 1 行进行打印
  for (let k = 1; k < m; k++) {
    for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) {
      res.push(arr[i][j]);
    }
  }
  return res
}
快排
function quickSort(arr){
    if(arr.length<2)return arr
    let index=Math.floor(arr.length/2)
    let temNum=arr.splice(index,1)[0]
    let left=[]
    let right=[]
    
    for(let i of arr){
        if(i<temNum){
            left.push(i)
        }else{
            right.push(i)
        }
    }
    return [...quickSort(left),temNum,...quickSort(right)]
}