js高频手撕面试题(简单好记)

目录
文章目录隐藏
  1. 1. 原生 Ajax 请求
  2. 2. Apply
  3. 3. Call
  4. 4. Create
  5. 5. Currying
  6. 6. Debounce
  7. 7. Deduplication
  8. 8. Deepclone
  9. 9. Eventmitter
  10. 10. Extend
  11. 11. Flatten
  12. 12. Insatnceof
  13. 13. JSONP
  14. 14. New
  15. 15. Promise
  16. 16. Reduce
  17. 17. Throttle

1. 原生 Ajax 请求

const getQueriesByObj = (object = {}, prefix = '') => Object.entries(object)
  .reduce((prev, [key, value]) => prev += `${key}=${value}&`, prefix)
  .slice(0, -1);

const ajax = (type = 'get') => (url = '', data, extraProps = {}) =>
  new Promise((resolve, reject) => {
    const defaultProps = { async: true, 'Content-type': 'application/x-www-form-urlencoded' };
    const props = { ...defaultProps, ...extraProps };
    const { async, baseUrl, queries = {} } = props;
    const xhr = new XMLHttpRequest();
    // 获取完整的请求 url
    if (!url.includes('http')) {
      url = `${baseUrl || window.location.origin}${url}`;
    }
    if (type === 'get') {
      url = getQueriesByObj(queries, `${url}?`)
    }
    xhr.open(type, url, async);
    Object.entries(extraProps)
      .forEach(([key, value]) => xhr.setRequestHeader(key, value.toString()));
    // get or post
    data
      ? xhr.send(data)
      : xhr.send();
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          return resolve(xhr.response);
        }
        return reject(xhr.response);
      }
    }
  });

const testUrl = 'https://www.fastmock.site/mock/3410de68d1e03849d328e0b0651c4f1f/api/api/services/app/TransactionBill/GetTransactionBillInfo';
// const ajaxGet = ajax('get');
const ajaxPost = ajax('post');

ajaxPost(testUrl).then(console.log)
ajax('post')(testUrl).then(console.log)

2. Apply

Function.prototype.myApply = function (...args) {
  if (typeof this !== 'function') {
    throw new TypeError('Error');
  }
  let [context, params] = args;
  context = context || window;
  context.fn = this;
  const result = params ? context.fn(...params) : context.fn();
  delete context.fn;
  return result;
}

3. Call

Function.prototype.myCall = function (...args) {
  if (typeof this !== 'function') {
    throw new TypeError('Error');
  }
  let [context, ...params] = args;
  context = context || window;
  context.fn = this;
  const result = context.fn(...params);
  delete context.fn;
  return result;
}

关于 apply 与 call 详细知识点,推荐阅读《实例讲解 js 中的 call() apply() bind()的用法和区别

4. Create

const create = (proto, propertiesObject) => {
  // 小写驼峰
  const headToLowerCase = v => v.replace(/(^[A-Z])/g, (m, p1) => p1.toLowerCase());
  // 类型
  const is = (val, compareType) => {
    const type = Object.prototype.toString.call(val)
      .replace(/^(\[object+\s)([\S]+)(\]$)/g, (m, p1, p2) => headToLowerCase(p2));
    // [object, 'Array'] => array
    return compareType
      ? (headToLowerCase(compareType) === type)
      : type;
  };
  const generateObj = prototype => {
    const obj = {};
    obj.__proto__ = prototype;
    // setProperty to obj
    Object.entries(propertiesObject).forEach(([key, value]) => is(value, 'object') && Object.defineProperty(obj, key, value));
    return obj;
  }
  const type = is(proto);
  const actions = {
    object: () => generateObj(proto),
    function: () => generateObj(proto),
    null: () => new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument."),
    default: () => new TypeError('Object prototype may only be an Object: ' + proto),
  }
  return actions[type]();
}

const testObj = create({ a: 1, b: 2 }, {
  c: {
    value: 3,
    enumerable: true,
    configurable: true,
    writable: true,
  }
});
console.log('testObj', testObj);

5. Currying

// 连续求和
const add = (...arg) => arg.reduce((prev, curr) => curr += prev, 0);
const currying = (fn, ...prevArgs) => (...currArgs) => currArgs.length
  ? currying.call(null, fn, ...prevArgs, ...currArgs)
  : fn(...prevArgs);
const curryingAdd = currying(add);
curryingAdd(1, 2)(3)(4)();
curryingAdd(1, 2)(3, 4)();
curryingAdd(1)(2)(3)(4)();

// 个性化 log
const coloring = fn => ({ background, color = 'white' }) => (...text) => fn(`%c${text.join('')}`, `color:${color};background:${background}`);
const colors = {
  primary: '#007bff',
  success: '#28a745',
  warning: '#ffc107',
  danger: '#dc3545',
  info: '#17a2b8',
};
const dir = (key = '', value = {}) => {
  logs.primary(`++++++++++++start:${key}++++++++++++++`);
  console.dir(value);
  logs.primary(`++++++++++++end:${key}++++++++++++++`);
};
const logs = Object.keys(colors)
  .reduce((prev, curr) => ({
    ...prev,
    [curr]: coloring(console.log)({ background: colors[curr] })
  }), { dir });

logs.warning('warning');

6. Debounce

HTML 代码:

<h3>Try entering something...</h3>

<input />
<p class="normal">
  Normal input:
  <span class="text"></span>
</p>
<p class="debounced">
  Debounced input:
  <span class="text"></span>
</p>

css 代码:

.normal {
  .text {
    color: pink;
  }
}

.debounced {
  .text {
    color: green;
  }
}

JS 代码:

const $inupt = $('input');
const $normalText = $('.normal .text');
const $debouncedText = $('.debounced .text');
const debounce = (fn, delay = 300) => {
  let timer = null;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  }
}

$inupt.on('keyup', function(e) {
  $normalText.text(this.value)
})

$inupt.on('keyup', debounce(function(e) {
  $debouncedText.text($inupt.val())
}))

7. Deduplication

// 1. 使用 set
const deduplication = arr => [...new Set(arr)];
const testArr = [1, 1, 2, 3, 4, 5];
deduplication(testArr);// [1,2,3,4,5]
const testArr2 = [1, 1, 2, 3, 4, 5].map(item => ({ id: item })); // [{"id":1},{"id":1},{"id":2},{"id":3},{"id":4},{"id":5}]

const deduplication02 = (array, key = '') => {
  const actions = {
    object: (arr, k) => arr.reduce((prev, curr) => prev.filter(item => item[k] === curr[k]).length ? prev : [...prev, curr], []),
    plain: arr => [...new Set(arr)]
  };
  return key ? actions.object(array, key) : actions.plain(array);
};

deduplication02(testArr2, 'id'); // [{"id":1},{"id":2},{"id":3},{"id":4},{"id":5}]

8. Deepclone

// deepClone 的关键在于切断引用数据类型的指向

// 低嵌套的 copy
const copiedArr = arr => arr.slice();

const deepClone = value => {
  // 小写驼峰
  const headToLowerCase = v => v.replace(/(^[A-Z])/g, (m, p1) => p1.toLowerCase());
  // 类型
  const is = (val, compareType) => {
    const type = Object.prototype.toString.call(val)
      .replace(/^(\[object+\s)([\S]+)(\]$)/g, (m, p1, p2) => headToLowerCase(p2));
    // [object, 'Array'] => array
    return compareType
      ? (headToLowerCase(compareType) === type)
      : type;
  };
  const type = is(value);
  // 开始 copy
  const actions = {
    // 基本数据类型
    string: val => new String(val).valueOf(),
    number: val => new Number(val).valueOf(),
    boolean: val => new Boolean(val).valueOf(),
    null: val => null,
    undefined: val => undefined,
    // 引用数据类型
    array: val => val.map(item => deepClone(item)),
    object: val => Object.entries(val).reduce((pre, [k, v]) => ({
      ...pre,
      [k]: deepClone(v),
    }), {}),
    // 其它数据类型
    regexp: (val) => new RegExp(val).valueOf(),
    date: (val) => new Date(val).valueOf(),
    htmlBodyElement: val => val.cloneNode(),
    // todo 数据类型没有完全
    default: val => val,
  };
  const action = actions[type] || actions.default;
  return action(value);
};

9. Eventmitter

class EventEmitter {
  constructor() {
    this.events = {};
  }

  sub(type, event) {
    this.events[type] = this.events[type] || [];
    this.events[type].push(event);
  }

  pub(type) {
    const events = this.events[type] || [];
    events.forEach(event => event && event());
  }

  unsub(type, event) {
    this.events[type] = this.events[type] || [];
    this.events[type] = this.events[type].filter(evt => evt !== event);
  }
}

10. Extend

const extend = (...args) => {
  const [target = {}, ...objs] = args;
  const headToLowerCase = v => v.replace(/(^[A-Z])/g, (m, p1) => p1.toLowerCase());
  // 类型
  const is = (val, compareType) => {
    const type = Object.prototype.toString.call(val)
      .replace(/^(\[object+\s)([\S]+)(\]$)/g, (m, p1, p2) => headToLowerCase(p2));
    // [object, 'Array'] => array
    return compareType
      ? (headToLowerCase(compareType) === type)
      : type;
  };
  const deepClone = value => {
    // 小写驼峰
    const type = is(value);
    // 开始 copy
    const actions = {
      // 基本数据类型
      string: val => new String(val).valueOf(),
      number: val => new Number(val).valueOf(),
      boolean: val => new Boolean(val).valueOf(),
      null: val => null,
      undefined: val => undefined,
      // 引用数据类型
      array: val => val.map(item => deepClone(item)),
      object: val => Object.entries(val).reduce((pre, [k, v]) => ({
        ...pre,
        [k]: deepClone(v),
      }), {}),
      // 其它数据类型
      regexp: (val) => new RegExp(val).valueOf(),
      date: (val) => new Date(val).valueOf(),
      htmlBodyElement: val => val.cloneNode(),
      // todo 数据类型没有完全
      default: val => val,
    };
    const action = actions[type] || actions.default;
    return action(value);
  };
  return objs.length
    ? objs.reduce((prev, curr) => is(curr, 'object') ? ({ ...prev, ...deepClone(curr) }) : prev, target)
    : target
}
// test
extend({ a: 1 }, { b: 2, c: { a: 1 } });

11. Flatten

const flatten = arr => arr.reduce((prev, curr) => Array.isArray(curr)
  ? [...prev, ...flatten(curr)]
  : [...prev, curr],
  []);

12. Insatnceof

const my_instanceof = (left, right) => {
  const rightPrototype = right.prototype;
  let leftProto = left.__proto__;
  if (leftProto === null) {
    return false;
  }
  if (leftProto === rightPrototype) {
    return true;
  }
  return my_instanceof(leftProto, right);
}

// test
function Foo() {}
my_instanceof(Object, Object); // true
my_instanceof(Function, Function); // true
my_instanceof(Function, Object); // true
my_instanceof(Foo, Foo); // false
my_instanceof(Foo, Object);// true
my_instanceof(Foo, Function);// true

// instanceof:利用原型链判断“父级”的原型(prototype)对象是否在“实例”的原型链上;
// typeof:直接根据变量值得内存标识符进行判断;
// typeof 一般用来判断 number、string、boolean、undefined、object、function、symbol 这七中类型。
// js 在底层存储变量的时候,会在变量的机器码的低位 1-3 位存储其类型信息:
//
// 000:对象
//
// 010:浮点数
//
// 100:字符串
//
// 110:布尔
//
// 1:整数

// 参考链接  https://juejin.im/post/6844903613584654344

13. JSONP

const getQueriesByObj = (object, prefix = "") => Object.keys(object)
  .reduce((prev, curr) => (prev += `${curr}=${encodeURIComponent(object[curr])}&`), prefix)
  .slice(0, -1);

const jsonp = ({ url, params = {} }) => new Promise((resolve, reject) => {
  const script = document.createElement('script');
  const cbKey = `_jsonp${Math.random().toString().substring(2)}`;
  window[cbKey] = result => {
    delete window[cbKey];
    script.remove(); // 将添加到页面中的 script 标签移除
    return resolve(result);
  };
  script.type = 'text/javascript';
  script.id = cbKey;
  script.src = getQueriesByObj({ ...params, callback: cbKey }, url.includes('?') ? url : `${url}?`);
  document.body.append(script);
});

jsonp({
  url: "http://localhost:3000/test",
  params: {},
}).then(console.log);

14. New

const create = (...args) => {
  const obj = {};
  const [constructor, ...params] = args;
  obj.__proto__ = constructor.prototype;
  const result = constructor.apply(obj, params);
  return result instanceof Object ? result : obj;
}

function Person(name, age) {
  this.name = name;
  this.age = age;
  return this;
}

const testPerson = create(Person, 'neo', '23');

console.log('testPerson', testPerson);

15. Promise

class MyPromise {
  constructor(props) {
    this.STATE = {
      PENDING: 'pending',
      RESOLVED: 'resolved',
      REJECTED: 'rejected',
    }
    this.status = this.STATE.PENDING;
    this.data = undefined;
    this.resolvedCallbacks = [];
    this.rejectedCallbacks = [];
    this.fn = props;
    this.doTask();
  }

  doTask = () => {
    const { resolve, reject } = this;
    try {
      this.fn(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  resolve = (data) => {
    if (this.status === this.STATE.PENDING) {
      this.status = this.STATE.RESOLVED;
      this.data = data;
      this.resolvedCallbacks.forEach(cb => cb(data));
    }
  }

  reject = (data) => {
    if (this.status === this.STATE.PENDING) {
      this.status = this.STATE.REJECTED;
      this.data = data;
      this.rejectedCallbacks.forEach(cb => cb(data));
    }
  }

  handleResolve = (resolve, reject, handler) => {
    try {
      const ret = handler(this.data);
      if (ret instanceof MyPromise) {
        ret.then(resolve, reject);
      } else {
        resolve(ret);
      }
    } catch (e) {
      reject(e);
    }
  }

  handleReject = (resolve, reject, handler) => {
    try {
      const ret = handler(this.data);
      if (ret instanceof MyPromise) {
        ret.then(resolve, reject);
      } else {
        reject(ret);
      }
    } catch (e) {
      reject(e);
    }
  }

  then = (onFulfilled, onRejected) => {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
    onRejected = typeof onRejected === 'function' ? onRejected : e => {throw e};
    const actions = {
      resolved: () => new MyPromise((resolve, reject) => setTimeout(() =>
        this.handleResolve(resolve, reject, onFulfilled)
      )),
      rejected: () => new MyPromise((resolve, reject) => setTimeout(() =>
        this.handleReject(resolve, reject, onRejected)
      )),
      pending: () => new MyPromise((resolve, reject) => {
        this.resolvedCallbacks.push(() => setTimeout(() =>
          this.handleResolve(resolve, reject, onFulfilled)
        ));
        this.rejectedCallbacks.push(() => setTimeout(() =>
          this.handleReject(resolve, reject, onRejected)
        ));
      }),
      default: () => {
        throw new Error('sth may happened ~')
      },
    };
    return actions[this.status]
      ? actions[this.status]()
      : actions.default();
  }

  catch = (onRejected) => {
    return this.then(null, onRejected);
  }

  static resolve = (res) => new MyPromise(resolve => resolve(res));
  static reject = (reason) => new MyPromise((reject, resolve) => reject(reason));

  static all = promises => new MyPromise((resolve, reject) => {
    const result = [];
    const { length } = promises;
    promises.forEach((promise, idx) => MyPromise.resolve(promise).then(value => {
      result[idx] = value;
      if (result.length === length) {
        return resolve(result);
      }
    }, reject));
  });

  static allSettled = promises => new MyPromise(resolve => {
    const result = [];
    const { length } = promises;
    promises.forEach((promise, idx) => MyPromise.resolve(promise).then(
      value => {
        result[idx] = {
          status: 'fulfilled',
          value
        }
        if (result.length === length) {
          return resolve(result);
        }
      },
      reason => {
        result[idx] = { status: 'rejected', reason }
        if (result.length === length) {
          return resolve(result);
        }
      }));
  });

  static race = promises => new MyPromise((resolve, reject) => promises.forEach(promise => MyPromise.resolve(promise).then(resolve, reject)));
}


new MyPromise((resolve, reject) => {
  reject('失败');
}).then().then().then(data => {
  console.log(data);
}, err => {
  console.log('err', err);
})

MyPromise.resolve(1).then(console.log)
MyPromise.reject(1).then(console.log)


// Promise.race
Promise.myRace = promises => new Promise((resolve, reject) => promises.forEach(promise => Promise.resolve(promise).then(resolve, reject)));

const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 200, 'promise1');
});
const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, 'promise2');
});
Promise.myRace([promise1, promise2]).then(value => {
  console.log(value);
});

// Promise.myAllSettled
Promise.myAllSettled = promises => new Promise(resolve => {
  const result = [];
  const { length } = promises;
  promises.forEach((promise, idx) => Promise.resolve(promise).then(
    value => {
      result[idx] = {
        status: 'fulfilled',
        value
      }
      if (result.length === length) {
        return resolve(result);
      }
    },
    reason => {
      result[idx] = { status: 'rejected', reason }
      if (result.length === length) {
        return resolve(result);
      }
    }));
});

// Promise.all
Promise.myAll = promises => new Promise((resolve, reject) => {
  const result = [];
  const { length } = promises;
  promises.forEach((promise, idx) => Promise.resolve(promise).then(value => {
    result[idx] = value;
    if (result.length === length) {
      return resolve(result);
    }
  }, reject));
});

16. Reduce

// for 版
Array.prototype.myReduce = function (fn, initialValue) {
  const arr = this;
  const [begin, ...remain] = arr;
  const hasInitialValue = initialValue !== 'undefined';
  let prev = hasInitialValue ? begin : initialValue;
  const array = hasInitialValue ? remain : arr;
  for (let i = 0; i < array.length; i++) { prev = fn(prev, array[i], arr.indexOf(array[i]), arr); } return prev; } const sum1 = [1, 2, 3, 4].myReduce((prev, curr) => prev += curr);
const sum2 = [1, 2, 3, 4].myReduce((prev, curr) => prev += curr, 0);
console.log('**test**', sum1, sum2);

// foreach 版
Array.prototype.myReduce = function (fn, initialValue) {
  const arr = this;
  let prev = initialValue;
  arr.forEach((item, idx) => {
    prev = typeof prev === 'undefined' && idx === 0
      ? arr[idx]
      : fn(prev, arr[idx], idx, arr);
  });
  return prev;
}
const sum3 = [1, 2, 3, 4].myReduce((prev, curr) => prev += curr);
const sum4 = [1, 2, 3, 4].myReduce((prev, curr) => prev += curr, 0);
console.log('**test**', sum3, sum4);

// 递归 版
Array.prototype.myReduce = function (fn, initialValue) {
  const arr = this;
  const run = (array, prev, idx = 0) => {
    if (!array.length) return prev;
    const [current, ...remain] = array;
    idx = arr.indexOf(current);
    return typeof prev === 'undefined' && idx === 0
      ? run(remain, current, idx)
      : run(remain, fn(prev, current, idx, arr), idx)
  };
  return run(arr, initialValue);
}
const sum5 = [1, 2, 3, 4].myReduce((prev, curr) => prev += curr);
const sum6 = [1, 2, 3, 4].myReduce((prev, curr) => prev += curr, 0);
console.log('**test**', sum5, sum6);

17. Throttle

const throttle = (fn, delay = 1000) => {
  let timer = null;
  return (...args) => {
    if (timer) return;
    timer = setTimeout(() => {
      fn(...args);
      clearTimeout(timer);
      timer = null;
    }, delay);
  };
}

持续更新和纠正,欢迎大家拍砖!

「点点赞赏,手留余香」

0

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
码云笔记 » js高频手撕面试题(简单好记)

发表回复