手写代码合集

371 阅读8分钟

1、防抖

<button id="debounce">点我防抖!</button>;
// 防抖
var myDebounce = document.getElementById("debounce");
myDebounce.addEventListener("click", debounce(sayDebounce));
function debounce(fn) {
  let timer = null;
  return function () {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      fn.call(this, arguments);
    }, 1000);
  };
}
function sayDebounce() {
  console.log("防抖成功!");
}

2、节流

<button id="throttle">点我节流!</button>;
// 节流
var myThrottle = document.getElementById("throttle");
myThrottle.addEventListener("click", throttle(sayThrottle));
function throttle(fn) {
  let timer = true;
  return function () {
    if (!timer) {
      return;
    }
    timer = false;
    setTimeout(() => {
      fn.call(this, arguments);
      timer = true;
    }, 500);
  };
}
function sayThrottle() {
  console.log("节流成功!");
}

3、柯里化函数

    function add() {
      const args = [...arguments]
      function fn() {
        args.push(...arguments)
        return fn
      }
      fn.toString = function () {
        return args.reduce((sun, i) => {
          return sun + i
        })
      }
      return fn
    }

    let a = Number(add(1)(1, 1, 1,)(1)(1)(1))
    console.log(a); // 7

4、Promise.all()

  • promise.all 方法:哪个先执行就先显示在数组里面,然后全部返回成功了再全部展示。
  • 如果有一个失败就返回第一个失败的信息。全部不进行了

let promise1 = new Promise((resolve, rejected) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});
let promise2 = new Promise((resolve, rejected) => {
  setTimeout(() => {
    resolve(2);
  }, 2000);
});
let promise3 = new Promise((resolve, rejected) => {
  setTimeout(() => {
    resolve(3);
  }, 3000);
});
Promsie.all([promise1, promise2, promise3]).then((res) => {
  console.log(res);
});

5、Promise.race()

  • promise.race 方法:哪个先执行先展示哪个的信息,只展示一个。
let promise1 = new Promise((resolve, rejected) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});
let promise2 = new Promise((resolve, rejected) => {
  setTimeout(() => {
    resolve(2);
  }, 2000);
});
let promise3 = new Promise((resolve, rejected) => {
  setTimeout(() => {
    resolve(3);
  }, 3000);
});
Promsie.race([promise1, promise2, promise3]).then((res) => {
  console.log(res);
});

6、浅拷贝

浅拷贝:以赋值的方式进行拷贝对象,仍指向同一个地址,修改时,原对象也会变化。

  1. 赋值的方式 =
  2. Object.assign()
  3. 解构赋值 ...
let obj = {
  name: "小三",
  arr: [1, 2],
  person: {
    a: 1,
    b: 2,
  },
  fun: function () {},
};
//
// 1. 赋值的方式 =
function deep(obj) {
  let newobj = {};
  for (let key in obj) {
    if (obj.hasOwnProperty(obj)) {
      newobj[key] = obj[key];
    }
  }
  return newobj;
}
let obj1 = deep(obj);
//
// 2. Object.assign()
let obj1 = Object.assign({}, obj);
// 3. 解构赋值的方式
let obj1 = [...obj];

7、深拷贝

深拷贝:新拷贝一个对象,修改时原对象不会改变

  1. JSON.parse(JSON.stringify(Obj))
  2. 递归循环拷贝
let obj = {
  name: "小三",
  unfind: undefined,
  fun: function () {},
  symbol: Symbol("唯一值"),
};
//
let obj1 = JSON.parse(JSON.stringify(obj));
// 缺点就是 undfined,Symbol,function不会进行拷贝,所以要用递归循环
//
function deepOjb(obj, hash = new WeakMap()) {
  if (obj === null) return obj; //如果是 null undefined 的话就不拷贝
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  if (typeof obj !== "object") return obj; //如果是普通类型的值的话就不进行拷贝
  if (hash.get(obj)) return hash.get(obj);
  let newobj = new obj.constructor(); //把拷贝对象的构造函数指向此创建的对象
  hash.set(obj, newobj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 进行递归拷贝
      newobj[key] = deepObj(obj[key], hash);
    }
  }
  return newobj
}

8、动态路由

<!-- 动态路由定义,怎么获取参数 -->
<!-- 一、路由定义法 -->
<!-- App.vue -->
<router-link :to=" '.user' + userID" replace>xxxxx</router-llink>
<!-- 路由定义 -->
{
  path: '/user/:userID'
  component:user
}


<!-- 1. -->
<router-link :to="name:'/user', param:{uname:user}"></router-link>
<!-- 2. -->
this.$router.push({name:'/user', param:{uname:userID}})
<!-- 3. -->
this.$router.push('/user' + userID)


<!-- 获取参数的方法 -->
this.$router.userID

9、async 和 catch 可以捕获异常

async function fn() {
  try {
    let a = await Promise.reject("异常");
  } catch (error) {
    consolo.log(error);
  }
// }
fn() //异常

10、原型和原型链

console.log(Object.__proto__ === Function.prototype); //true
console.log(Function.__proto__.__proto__ === Object.prototype); //true
console.log(Function.prototype.__proto__ === Object.prototype); //true
console.log(Function.__proto__ === Function.prototype); //true
console.log(Object.prototype.__proto__); //null 原型链的尽头
// 原型链继承
function Person() {
  this.name = "Zaxlct"; //Person.prototype.name = "Zaxlct";
}
// 一样的写法
Person.prototype.sayName = function () {
  alert(this.name);
};
let person1 = new Person();
let person2 = new Person();
person1.name = 666;
console.log(person1.name); //666
console.log(person2.name); //Zaxlct
// 原型继承:将子类的原型对象指向父类的实例
// 优点:继承了父类的模板和父类的原型对象
// 缺点:无法实现多继承,无法传参。
function Parent() {
  this.name = "小三";
}
function Child() {
  this.age = 21;
}
Child.prototype = new Parent();
let obj1 = new Child();
let obj2 = new Parent();

obj1.name = "小舞";

console.log(obj1); // Child {age: 21, name: '小舞'}
console.log(obj2); // Parent {name: '小三'}
// 构造函数继承:在子类构造函数内用 apply、call 来改变父类构造函数的 this 指向。
// 优点:可以实现多继承,可以继承了父类的实例的属性和方法,也可以传参。
// 缺点: 不能继承父类的原型属性和方法。
function Parent(name, age) {
  this.name = name;
  this.age = age;
  this.aaa = 666;
}
function Child(name, age) {
  Parent.call(this, name, age);
}

let obj = new Child("小三", 21);
console.log(obj);
console.log(obj.name, obj.age, obj.aaa); // 小三  21  666
// 组合继承:将原型链继承和构造函数函数继承组合在一起。
// 原型链继承是为了保证子类能够继承父类的原型属性和方法
// 构造函数继承是为了保证子类能够继承父类的实例实行和方法
function Parent(name) {
  this.name = name;
  this.color = [1, 2, 3];
}

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}

Child.prototype = new Parent();

var child1 = new Child("小三", 21);
var child2 = new Child("小舞", 22);

child1.color.push(4);

console.log(child1); // Child {name: '小三', color: Array(4), age: 21}
console.log(child2); // Child {name: '小三', color: Array(3), age: 21}


// 寄生组合继承
// 使用超类型的原型副本作为子类型的原型,避免了创建过多的属性
// 手写 寄生组合继承
    function Parent(name) {
      this.name = name
      this.say = function () {
        console.log('say');
      }
    }
    Parent.prototype.play = function () {
      console.log('play');
    }
    function Children(name) {
      Parent.call(this)
      this.name = name
    }
    Children.prototype = Object.create(Parent.prototype)
    Children.prototype.constructor = Children
    let child = new Children('小王')
    console.log(child.name); // 小王
    child.say() // say
    child.play() // palay

11、手写 new

    // 手写 new
    function mynew(fn, ...args) {
      let obj = Object.create(fn.prototype)
      let res = fn.call(obj, ...args)
      if (res && (typeof res === 'object' || typeof res === 'function')) {
        return res
      }
      return obj
    }
    // 使用
    function Person(name, age) {
      this.name = name
      this.age = age
    }
    Person.prototype.play = function () {
      console.log(this.age);
    }
    let p1 = mynew(Person, '小王', 21)
    console.log(p1); // {name: "小王", age: 21}
    console.log(p1.name); //小王
    p1.play() // 21

12、实现一个简单的 Promise

    // 实现一个简单的 Promise
    let promise = new Promise((resolve, reject) => {
      resolve('小三')
    })
    promise.then(res => {
      console.log(res);  // 小三
    })

13、手写 柯里化函数

    手写 柯里化函数
    function add() {
      const args = [...arguments]
      function fn() {
        args.push(...arguments)
        return fn
      }
      fn.toString = function () {
        return args.reduce((sum, i) => {
          return sum + i
        })
      }
      return fn
    }
    console.log(add(1)(2)(3)(4)) // 10

14、手写 Call

call 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
  3. 处理传入的参数,截取第一个参数后的所有参数。
  4. 将函数作为上下文对象的一个属性。
  5. 使用上下文对象来调用这个方法,并保存返回结果。
  6. 删除刚才新增的属性。
  7. 返回结果。
// call函数实现
Function.prototype.myCall = function(context) {
  // 判断调用对象
  if (typeof this !== "function") {
    console.error("type error");
  }
  // 获取参数
  let args = [...arguments].slice(1),
  let result = null;
  // 判断 context 是否传入,如果未传入则设置为 window
  context = context || window;
  // 将调用函数设为对象的方法
  context.fn = this;
  // 调用函数
  result = context.fn(...args);
  // 将属性删除
  delete context.fn;
  return result;
};

15、手写 Apply

apply 函数的实现步骤:

  1. 判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
  3. 将函数作为上下文对象的一个属性。
  4. 判断参数值是否传入
  5. 使用上下文对象来调用这个方法,并保存返回结果。
  6. 删除刚才新增的属性
  7. 返回结果
// apply 函数实现
Function.prototype.myApply = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  let result = null;
  // 判断 context 是否存在,如果未传入则为 window
  context = context || window;
  // 将函数设为对象的方法
  context.fn = this;
  // 调用方法
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }
  // 将属性删除
  delete context.fn;
  return result;
};
    function parent(name, age) {
      this.name = name
      this.age = age
      this.args = arguments
    }
    let p1 = new parent('小三', 21)
    // console.log(p1);
    let arr = p1.args
    console.log(arr);
    console.log(arr[1]);  // 21
    console.log([...arr].slice(1)); // [21]

JS的arguments

寄生组合继承

    // 手写 寄生组合继承
    function parent(name) {
      this.name = name
      this.say = function () {
        console.log(this.name);
      }
    }
    parent.prototype.play = function () {
      console.log(this.name);
    }
    function Children(name) {
      parent.call(this)
      this.name = name
    }
    Children.prototype = Object.create(parent.prototype)
    Children.prototype.constructor = Children
    let child = new Children('小三')
    console.log(child.name); // 小三
    child.say() //小三
    child.play() //小三

16、 class 继承

    // class 继承
    class Parent {
      constructor(name, age) {
        this.name = name
        this.age = age
      }
      say() {
        console.log(this.name);
      }
    }
    let p1 = new Parent('小三', 21)
    console.log(p1) // Parent {name: "小三", age: 21}

    class Children extends Parent {
      constructor(name, age, sex) {
        super(name, age)
        this.sex = sex
      }
      play() {
        super.say()
      }
    }


    let p2 = new Children('小王', 22, '男')
    console.log(p2) // Children {name: "小王", age: 22, sex: "男"}
    p2.play() // 小王

手写 bind

  //bind实现要复杂一点  因为他考虑的情况比较多 还要涉及到参数合并(类似函数柯里化)
  Function.prototype.myBind = function (context, ...args) {
    if (!context || context === null) {
      context = window;
    }
    // 创造唯一的key值  作为我们构造的context内部方法名
    let fn = Symbol();
    context[fn] = this;
    let _this = this;
    //  bind情况要复杂一点
    const result = function (...innerArgs) {
      // 第一种情况 :若是将 bind 绑定之后的函数当作构造函数,通过 new 操作符使用,则不绑定传入的 this,而是将 this 指向实例化出来的对象
      // 此时由于new操作符作用  this指向result实例对象  而result又继承自传入的_this 根据原型链知识可得出以下结论
      // this.__proto__ === result.prototype   //this instanceof result =>true
      // this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true
      if (this instanceof _this === true) {
        // 此时this指向指向result的实例  这时候不需要改变this指向
        this[fn] = _this;
        this[fn](...[...args, ...innerArgs]); //这里使用es6的方法让bind支持参数合并
        delete this[fn];
      } else {
        // 如果只是作为普通函数调用  那就很简单了 直接改变this指向为传入的context
        context[fn](...[...args, ...innerArgs]);
        delete context[fn];
      }
    };
    // 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法
    // 实现继承的方式: 使用Object.create
    result.prototype = Object.create(this.prototype);
    return result;
  };

  //用法如下

  // function Person(name, age) {
  //   console.log(name); //'我是参数传进来的name'
  //   console.log(age); //'我是参数传进来的age'
  //   console.log(this); //构造函数this指向实例对象
  // }
  // // 构造函数原型的方法
  // Person.prototype.say = function() {
  //   console.log(123);
  // }
  // let obj = {
  //   objName: '我是obj传进来的name',
  //   objAge: '我是obj传进来的age'
  // }
  // // 普通函数
  // function normalFun(name, age) {
  //   console.log(name);   //'我是参数传进来的name'
  //   console.log(age);   //'我是参数传进来的age'
  //   console.log(this); //普通函数this指向绑定bind的第一个参数 也就是例子中的obj
  //   console.log(this.objName); //'我是obj传进来的name'
  //   console.log(this.objAge); //'我是obj传进来的age'
  // }

  // 先测试作为构造函数调用
  // let bindFun = Person.myBind(obj, '我是参数传进来的name')
  // let a = new bindFun('我是参数传进来的age')
  // a.say() //123

  // 再测试作为普通函数调用
  // let bindFun = normalFun.myBind(obj, '我是参数传进来的name')
  //  bindFun('我是参数传进来的age')

手写-发布订阅模式(字节)

class EventEmitter {
  constructor() {
    this.events = {};
  }
  // 实现订阅
  on(type, callBack) {
    if (!this.events[type]) {
      this.events[type] = [callBack];
    } else {
      this.events[type].push(callBack);
    }
  }
  // 删除订阅
  off(type, callBack) {
    if (!this.events[type]) return;
    this.events[type] = this.events[type].filter((item) => {
      return item !== callBack;
    });
  }
  // 只执行一次订阅事件
  once(type, callBack) {
    function fn() {
      callBack();
      this.off(type, fn);
    }
    this.on(type, fn);
  }
  // 触发事件
  emit(type, ...rest) {
    this.events[type] &&
      this.events[type].forEach((fn) => fn.apply(this, rest));
  }
}
// 使用如下
// const event = new EventEmitter();

// const handle = (...rest) => {
//   console.log(rest);
// };

// event.on("click", handle);

// event.emit("click", 1, 2, 3, 4);

// event.off("click", handle);

// event.emit("click", 1, 2);

// event.once("dbClick", () => {
//   console.log(123456);
// });
// event.emit("dbClick");
// event.emit("dbClick");