手写代码

156 阅读5分钟

实现一个new操作符

new操作符做了这些事:

  • 新建一个对象obj
  • 把新对象obj和构造函数通过原型链(proto)连接起来
  • 将构造函数的this指向新对象obj
  • 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象。
    function myNew(func, ...args) {
      // Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
      const obj = Object.create(func.prototype);
      let result = func.apply(obj, args);
      return result instanceof Object ? result : obj;
    }

    function Person() {
      return {
        name: '张三',
        age: 12
      }
    }
    let instance = myNew(Person, '李四', 123);
    console.log(instance);// {name: "张三", age: 12}

实现一个call

call语法:fun.call(thisArg, arg1, arg2, ...), 调用一个函数,其具有一个指定的this值和提供参数的列表

call核心:

  • 将调用函数设为传入对象的属性;
  • 执行并且删除这个函数;
  • 指定this到函数并传入给定参数执行函数
  • 如果不传入参数,默认指向为window。
    Function.prototype.call2 = function(context = window) {
      context.fn = this;// this为调用函数
      let args = [...arguments].slice(1);
      let result = context.fn(...args);
      delete context.fn;
      return result;
    }

实现一个apply

  • apply语法:fun.appy(thisArg, [argsArray]), 调用一个函数,以及作为一个数组提供的参数;
  • apply()的实现和call()类似,只是参数形式不同;
    Function.prototype.apply2 = function(context = window) {
      context.fn = this;
      let result;
      // 判断是否有第二个数组参数传入
      result = arguments[1] ? context.fn(...arguments[1]) : context.fn();
      delete context.fn;
      return result;
    } 

实现一个bind()

bind语法:会创建一个新函数,当这个新函被数调用时,bind()的第一个参数将作为它运行时的this,之后的一序列参数将会在传递的实参前作为它的参数;

    Function.prototype.bind2 = function(context = window) {
      if (typeof this != 'function') {
        throw Error(`${this} not a Function!`);
      }
      let fn = this;
      // 也可使用Array.prototype.slice.call(arguments, 1)方法获取
      let args = [...arguments].slice(1);
      let resFn = function() {
        // instanceof: 判断对象是否继承于当前对象
        // true: 返回的函数resFn作为了一个构造函数使用,那么this指向新的实例,而不是context
        // false: 返回的函数resFn作为了一个普通函数使用,那么this指向context或window
        return fn.apply(this instanceof resFn ? this : resFn, args.concat(...arguments));
      }
      // 给返回函数的prototype添加属性和方法会修改原型对象prototype
      // 创建一个空函数实例,然后给resFn原型继承,resFn创建的实例可以继承tmp方法
      // 这样修改了resFn原型方法,只会影响tmp函数,而不会影响到原函数bind2原型对象
      function tmp() {};
      tmp.prototype = this.prototype;
      resFn.prototype = new tmp();
      return resFn;
    }

for循环中的作用域问题

写出以下代码输出值,尝试用es5和es6的方式进行改进输出循环中的值

    for (var i = 1; i<= 5; i++) {
      setTimeout(function timer() {
        console.log(i);
      }, i*1000);
    }

    // 输出5个6,因为回调函数在for循环之后执行,所有函数共享一个i的引用

    // es5
    for (var i = 1; i<= 5; i++) {
      (function (j) {
        setTimeout(function timer() {
          console.log(j);
        }, j*1000);
      })(i);
    }

    // es6
    for (let i = 1; i<= 5; i++) {
      setTimeout(function timer() {
        console.log(i);
      }, i*1000);
    }

实现一个深拷贝

const deepCopy = obj => {
  if (typeof obj !== 'object') {
    return obj;
  }
  let result = obj.constructor === Array ? [] : {};
  for (let i in obj) {
    result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];
  }
  return result;
}

实现一个继承

  • 寄生组合式继承 一般只建议写这种,其他方式的继承会在一次实例中调用两次父类的构造函数或其它缺点;
    核心实现是: 用一个F空函数去取代执行力Parent这个构造函数
    function Parent(name) {
      this.name = name;
    }
    Parent.prototype.sayName = function() {
      console.log(this.name);
    }

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

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

    Child.prototype = create(Parent.prototype);
    Child.prototype.constructor = Child;

    Child.prototype.say = function() {
      console.log(`名字: ${this.name},年龄:${this.age}`);
    }

    let instance = new Child('张三', 18);
    instance.say();// 名字: 张三,年龄:18
  • 使用es6实现继承
    class Parent {
      constructor(name) {
        this.name = name;
      }
      sayName() {
        console.log(`名字:${this.name}`);
      }
    }
    class Child extends Parent {
      constructor(name, age) {
        super(name);
        this.age = age;
      } 
      say() {
        console.log(`名字:${this.name}, 年龄: ${this.age}`)
      }
    }

    let instance1 = new Child('张三',  14);
    instance1.say(); // 名字:张三, 年龄: 14}

手写一个Promise

Promise规范:

  • 三种状态: pending、fulfild(resolved)、rejected
  • 当处于pending状态的时候,可以转移到fulfild(resolve)状态或者rejected状态;
  • 当处于fulfild(resolve)和rejected状态的时候,就不可变。
    function myPromise(constructor) {
      let self = this;
      self.status = 'pending'; // 定义状态改变前的状态
      self.value = undefined; // 定义状态为resolved时候状态
      self.reason = undefined;// 定义状态为rejected时候的状态
      function resolve(value) {
        if (self.status === 'pending') {
          self.value = value;
          self.status = 'resolved';
        }
      }
      function reject(reason) {
        if (self.status === 'pending') {
          self.reason = reason;
          self.status = 'rejected';
        }
      } 
      // 捕获异常
      try {
        constructor(resolve, reject);
      } catch(e) {
        reject(e);
      }
    }

    // 同时需要在没有Promise的原型上定义链式调用的then方法:
    myPromise.prototype.then = function(onFulfilled, onRejected) {
      let self = this;
      switch(self.status) {
        case 'resolved':
          onFulfilled(self.value);
          break;
        case 'rejected':
          onRejected(self.reason);
          break;
        default:
      }
    }

    let instance = new myPromise((resolve, reject) => resolve(1));
    instance.then((res) => {
      console.log(res);
    });

实现防抖(Debouncing)实现

  • 典型例子: 限制鼠标链接触发
  • 一个比较好的解释: 当一次事件发生后,事件处理器要等一定阈值的时间,如果这段时间过去后,再也没有事件发生,就处理最后一次发生的事件。假设还差0.01秒就达到指定的时间,这时候又来了一个事件,那么之前的等待作废,需要重新再等待指定时间。
    function debounce(fn, wait) {
      let timer = null;
      return function() {
        if (timer) {
          clearTimeout(timer);
        }
        timer = setTimeout(() => {
          fn.apply(this, arguments);
        }, wait);
      }
    }

实现节流(Throttling)

可以理解为事件在一个管道中传输,加上这个节流阈以后,事件的流速就会减慢。实际上这个函数的作用就是如此,它可以将一个函数的调用频次限制在一定的阈值内,例如1s,那么1s内这个函数一定不会被调用两次

    function throttle(fn, wait) {
      let prev = new Date();
      return function() {
        const now = new Date();
        if (now - prev > wait) {
          fn.apply(this, arguments);
          prev = new Date();
        }
      }
    }

实现instanceof

  • 用处: 检测一个对象(left)是不是另一个对象(right)的实例
  • 原理:查看对象(right)的prototype指向是否在对象(left)的__proto__链上。如果在,则返回true,如果不在,则返回false。不过有一个特殊情况,就是当对象B的prototype为null的时候会报错;
    function _instanceof(left, right) {
      let proto = left.__proto__;
      let prototype = right.prototype;
      while(true) {
        if (proto === null) return false;
        if (proto === prototype) return true;
        proto = proto.__proto__;  
      }
    }

判定括号字符串是否有效

let s = "(){]{[(68)]}"
const isValidStr = (str = "") => {
  if (!str.length) { return true }
  let mapStr = {
    "{": "}",
    "[": "]",
    "(": ")"
  },
  right = ["}", "]", ")"],
  stack = [];
  for (let i = 0; i < str.length; i++) {
    let ch = str[i];
    if (mapStr[ch]) {
      stack.push(ch);
    } else {
      if (!stack.length || (right.includes(ch) && mapStr[stack.pop()] !== ch)) {
        return false;
      }
    }
  }
  return !stack.length;
}
// console.log(isValidStr(s));

冒泡排序

let sortArr = [5, 2, 7, 8, 34, 7, 39, 12, 56, 1];
const bubbleSort = (arr) => {
  let len = arr.length;
  if (len <= 1 ) { return arr; }
  let flag = true;
  for (let i = 0; i < len - 1 && flag; i++) {
    flag = false;
    for (let j = 0; j < len - i; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
        flag = true;
      }
    }
  }
  return arr;
}
// console.log(bubbleSort(sortArr));

插入排序

const insertSort = (arr) => {
  let len = arr.length;
  if (len <= 1) { return arr; }
  for (let i = 1; i < len; i ++) {
    for (let j = i; j > 0; j--) {
      if (arr[j] < arr[j - 1]) {
        [arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
      }
    }
  }
  return arr;
}
// console.log(insertSort(sortArr));

快速排序

const quickSort = (arr) => {
  let len = arr.length;
  if (len <= 1) { return arr; }
  let middleIndex = Math.floor(len / 2),
    middle = arr.splice(middleIndex, 1)[0],
    leftArr = [],
    rightArr = [];
  for (let i = 0; i < len - 1; i++) {
    arr[i] < middle ? leftArr.push(arr[i]) : rightArr.push(arr[i]);
  }
  return quickSort(leftArr).concat(middle, quickSort(rightArr));
}

// console.log(quickSort(sortArr));

快速排序-原地快排

const swap = (arr, i, j) => {
  [arr[i], arr[j]] = [arr[j], arr[i]];
}

const partition = (arr, start, end) => {
  let j = start;
  let povit = arr[end];
  for (let i = start; i <= end; i++) {
    if (arr[i] <= povit) {
      swap(arr, i, j++);
    }
  }
  return j - 1;
}

const quickSort2 = (arr, start = 0, end = arr.length - 1) => {
  if (end - start < 1) {
    return arr;
  }
  let povitIndex = partition(arr, start, end);
  quickSort2(arr, start, povitIndex - 1);
  quickSort2(arr, povitIndex + 1, end);
  return arr;
}

执行上下文

console.log("start");

setTimeout(() => {
console.log("setTimeout1");
}, 0);

(async function foo() {
console.log("async 1");

await asyncFunction();

console.log("async2");

})().then(console.log("foo.then"));

async function asyncFunction() {
console.log("asyncFunction");

setTimeout(() => {
  console.log("setTimeout2");
}, 0);

new Promise((res) => {
  console.log("promise1");

  res("promise2");
}).then(console.log);
  • 最开始碰到 console.log("start"); 直接执行并打印出 start
  • 往下走,遇到一个 setTimeout1 就放到宏任务队列
  • 碰到立即执行函数 foo, 打印出 async 1
  • 遇到 await 堵塞队列,先 执行await的函数
  • 执行 asyncFunction 函数, 打印出 asyncFunction
  • 遇到第二个 setTimeout2, 放到宏任务队列
  • new Promise 立即执行,打印出 promise1
  • 执行到 res("promise2") 函数调用,就是Promise.then。放到微任务队列
  • asyncFunction函数就执行完毕, 把后面的打印 async2 会放到微任务队列
  • 然后打印出立即执行函数的then方法 foo.then
  • 最后执行打印 end
  • 开始执行微任务的队列 打印出第一个 promise2
  • 然后打印第二个 async2
  • 微任务执行完毕,执行宏任务 打印第一个 setTimeout1
  • 执行第二个宏任务 打印 setTimeout2

使用Promise实现红绿灯交替重复亮

红灯3秒亮一次,黄灯2秒亮一次,绿灯1秒亮一次;如何让三个灯不断交替重复亮灯?(用Promise实现)三个亮灯函数已经存在:

function red() {
  console.log("red");
}
function green() {
  console.log("green");
}
function yellow() {
  console.log("yellow");
}
const light = (cb, timer) => {
  return new Promise(resolve => {
    setTimeout(() => {
      cb();
      resolve();
    }, timer);
  })
}
async function setup() {
  await light(red, 3000);
  await light(green, 2000);
  await light(yellow, 1000);
  setup();
}
setup();

数组去重

let arr = [5, 2, 7, 8, 34, 7, 39, 12, 56, 1];

const removeRepeat = (arr = []) => {
  let newArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (newArr.indexOf(arr[i]) === -1) {
      newArr.push(arr[i]);
    }
  }
  return newArr;
}

const removeRepeat2 = (arr = []) => {
  return [...new Set(arr)]
}

const removeRepeat3 = (arr = []) => {
  return arr.filter((item, index, arr) => {
    // 下标相等的返回,不相等的说明前边有相同数据了
    return arr.indexOf(item) == index;
  });
}
console.log(removeRepeat4(arr));