面试高频JS考查点手写实现

原文链接

考查 this

call、apply

Function.prototype._call = function(ctx = window, ...args) {
  const fnKey = 'tmp_' + Date.now();
  ctx[fnKey] = this;
  const result = ctx[fnKey](...args);
  delete ctx[fnKey];
  return result;
};

// 第二个参数是数组
Function.prototype._apply = function(ctx = window, args = []) {
  const fnKey = 'tmp_' + Date.now();
  ctx[fnKey] = this;
  const result = ctx[fnKey](...args);
  delete ctx[fnKey];
  return result;
};

let obj = { x: 1 };
function fn() {
  console.log(this.x, arguments);
}
fn._call(obj, 1, 2, 3);
fn._apply(obj, [1, 2, 3]);

bind

Function.prototype._bind = function() {
  const slice = [].slice;
  const _fn = this;
  const ctx = arguments[0];
  const args = slice.call(arguments, 1);
  if (typ _fn !== 'function') {
    throw new TypeError('Function.prototype.bind - ' +
      'what is trying to be bound is not callable');
  }
  return function() {
    const funcArgs = args.concat(slice.call(arguments));
    return _fn.apply(ctx, funcArgs);
  };
};

const obj = { x: 1 };
function A() {
  console.log(this.x);
}
const fn = A._bind(obj);
fn();

new

function newObject(fn) {
  const obj = {};
  const res = fn.apply(obj, [].slice.call(arguments, 1));
  return res instanc Object ? res : obj;
}

function Con(a) {
  this.a = a;
}
const obj = newObject(Con, 1);

链式调用

Number.prototype.add = function(num) {
  num = +num;
  if(num !== num) return Number(this);
  return Number(this + num);
};

let n = 1;
n.add(1).add(2).add(3);

考查原型链

instanceof

function instance_of(lhs, rhs) {
  while (lhs) {
    lhs = lhs.__proto__;
    if (lhs === rhs.prototype) return true;
  }
  return false;
}

组合寄生继承

function Super(){}
function Sub() {
    Super.call(this); // 继承自有属性
}

// 继承原型链上的属性和方法
Sub.prototype = Object.create(Super.prototype); // 继承原型链
Sub.prototype.constructor = Sub;

Object.create

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

纯对象

  • redux 版
function isPlainObject(obj) {
  if (typ obj !== 'object' || obj === null) return false;
  var proto = obj;

  while (Object.getPrototyp(proto) !== null) {
    proto = Object.getPrototyp(proto);
  }

  return Object.getPrototyp(obj) === proto;
}

function A() {}
isPlainObject( new A );      // false
isPlainObject( new Object ); // true
isPlainObject( {} );         // true

判断类型

  • jQuery 3.4.1版
const type = 'Boolean Number String Function Array Date RegExp Object Error Symbol'
  .split(' ')
  .reduce((pre, name) => {
    pre[`[object ${name}]`] = name.toLowerCase();
    return pre;
  }, {});

function toType( obj ) {
  if ( obj == null ) {
    return obj + '';
  }

  return typ obj === 'object' || typ obj === 'function' ?
    type[ Object.prototype.toString.call( obj ) ] || 'object' :
    typ obj;
}

toType(/xxx/) // regexp

考查闭包

柯里化

function curry(fn, ...args) {
  if(args.length >= fn.length) {
    return fn.apply(null, args);
  }

  return (...args2) => curry(fn, ...args, ...args2);
}

const add = curry(function(a, b, c) {
  return a + b + c;
});
add(1, 2, 3);
add(1)(2)(3);
add(1, 2)(3);
add(1)(2, 3);

考查性能意识

防抖

function debounce(fn, delay) {
  let timer = null;
  return function() {
    // 中间态一律清除掉
    timer && clearTimeout(timer);
    // 只需要最终的状态,执行
    timer = setTimeout(() => fn.apply(this, arguments), delay);
  };
}

节流

function throttle(fn, delay) {
  let timer, lastTime;
  return function() {
    const now = new Date().getTime();
    const space = now - lastTime; // 间隔时间
    if( lastTime && space < delay ) { // 为了响应用户最后一次操作
      // 非首次,还未到时间,清除掉计时器,重新计时。
      timer && clearTimeout(timer);
      // 重新设置定时器
      timer = setTimeout(() => {
        lastTime = now; // 不要忘了记录时间
        fn.apply(this, arguments);
      }, delay);
      return;
    }
    // 首次或到时间了
    lastTime = now;
    fn.apply(this, arguments);
  };
}

节流防抖详解

事件代理

function delegate(ele, selector, type, fn) {
  function callback(e) {
    e = e || window.event;
    const target = e.target || e.srcElement;
    if( ele.contains(target) ) {
      fn.call(target, e);
    }
  }
  ele.addEventListener(type, callback, false);
}

delegate(document.querySelector('body'), 'button', 'click', function () {
  console.log('bingo');
});

限制并发数

function sendRequest(urls, max, callback) {
  let last = performance.now();
  let len = urls.length;
  let limit = max; // 控制并发数
  let cnt = 0;     // 累积执行任务数
  let res = [];    // 有序存储执行结果
  const tasks = urls.map((url, index) => () => fetch(url)
    .then(data => {
      res[index] = data;
    })
    .catch(reason => {
      res[index] = reason;
    })
    .finally(() => {
      if( ++cnt === len ) return callback(res);
      ++limit;
      doTasks();
    }));

  doTasks();

  function doTasks() {
    while( limit && tasks.length ) {
      --limit;
      let task = tasks.shift();
      task();
      console.log(`执行间隔:${performance.now() - last}`);
    }
  }
}

// 模拟 fetch
function fetch(url) {
  return new Promise(function (resolve, reject) {
    let good, bad;
    const time = 3000;

    good = setTimeout(function () {
      clearTimeout(bad);
      let data = `resolve: ${url}`;
      resolve(data);
      console.log(data);
    }, Math.random() * time);

    bad = setTimeout(function () {
      clearTimeout(good);
      let reason = `reject: ${url}`;
      reject(reason);
      console.log(reason);
    }, Math.random() * time);
  });
}

// 测试
sendRequest([1,2,3,4,5,6,7,8,9,10], 5, (res) => console.log('all done:' + res));

考查跨域

JSONP

function fn({ip}) {
  console.log(ip); // 
}
function jsonp(cb, domain) {
  const script = document.createElement('script');
  script.src = `https://api.asilu.com/ip/?callback=${cb}&ip=${domain}`;
  document.querySelector('head').appendChild(script);
}

// 获取百度IP
jsonp('fn', 'www.baidu.com');

考查 ES6

数组去重

  • Map 版
function deleteDuplicate(arr) {
  const map = new Map();
  arr.forEach( value => map.set(value, value) );
  return Array.from( map.values() ); // return [ ...map.values() ];
}

const arr = [NaN, 1, [1], [1], 1, '1', 4, 1, 2, 4, 5, 5, NaN, NaN, null, null, undefined, undefined];
deleteDuplicate( arr );
// [NaN, 1, Array(1), Array(1), "1", 4, 2, 5, null, undefined]
// Map 的遍历顺序就是插入顺序
  • Set 版
function deleteDuplicate(arr) {
  const set = new Set( arr );
  return Array.from( set ); // return [ ...set ];
}
deleteDuplicate( arr );
//[NaN, 1, Array(1), Array(1), "1", 4, 2, 5, null, undefined]

Promise.all

Promise._all = function (promises) {
  return new Promise(function(resolve, reject) {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('arguments must be an array'));
    }
    const len = promises.length;
    let cnt = 0;
    let res = [];
    for(let i = 0; i < len; i++) {
      Promise
        .resolve(promises[i])
        .then(function(value) {
          cnt++;
          res[i] = value;
          if (cnt === len) {
            return resolve(res);
          }
        }, function(reason) {
          return reject(reason);
        });
    }
  });
};

Promise._all([1,2,3,4]);
// Promise {<resolved>: Array(4)}
Promise._all([1,2,3,4]).then(res => console.log(res));
// [1, 2, 3, 4]
Promise._all([1,2,3,Promise.reject('error')]);
// Promise {<rejected>: "error"}
Promise._all([1,2,3,Promise.reject('error')]).catch(reason => console.log(reason));
// error

考查设计模式

订阅发布模式

mitt:极简事件监听

function mitt(all/*: EventHandlerMap*/) {
  all = all || Object.create(null);

  return {
    on(type/*: string*/, handler/*: EventHandler*/) {
      (all[type] || (all[type] = [])).push(handler);
    },
    off(type/*: string*/, handler/*: EventHandler*/) {
      if (all[type]) {
        all[type].splice(all[type].indexOf(handler) >>> 0, 1);
      }
    },
    emit(type/*: string*/, evt/*: any*/) { // * 表示订阅所有事件消息
      (all[type] || []).slice().map((handler) => { handler(evt); });
      (all['*'] || []).slice().map((handler) => { handler(type, evt); });
    }
  };
}

const m = mitt();
m.on('hello', (name) => console.log('hello ' + name));
m.emit('hello', 'world');

考查算法

深拷贝

Vuex版

/**
 * Get the first item that pass the test
 * by second argument function
 *
 * @param {Array} list
 * @param {Function} f
 * @return {*}
 */
export function find (list, f) {
  return list.filter(f)[0]
}

/**
 * Deep copy the given object considering circular structure.
 * This function caches all nested objects and its copies.
 * If it detects circular structure, use cached copy to avoid infinite loop.
 *
 * @param {*} obj
 * @param {Array<Object>} cache
 * @return {*}
 */
export function deepCopy (obj, cache = []) {
  // just return if obj is immutable value
  if (obj === null || typ obj !== 'object') {
    return obj
  }

  // if obj is hit, it is in circular structure
  const hit = find(cache, c => c.original === obj)
  if (hit) {
    return hit.copy
  }

  const copy = Array.isArray(obj) ? [] : {}
  // put the copy into cache at first
  // because we want to refer it in recursive deepCopy
  cache.push({
    original: obj,
    copy
  })

  Object.keys(obj).forEach(key => {
    copy[key] = deepCopy(obj[key], cache)
  })

  return copy
}

DFS 全排列

输入 `abc`
输出
  abc
  acb
  bac
  bca
  cab
  cba
const str = 'abc';
const len = str.length;
const flag = [];
const res = [];

DFS(0);

// cur 表示第 cur 位取得字符
// 每一位有 len 种取法
function DFS(cur) {
  if(cur === len)
    return console.log(res.join(''));

  for(let i = 0; i < len; i++) {
    if(!flag[i]) {
      res[cur] = str[i];
      flag[i] = true;
      DFS(cur + 1);
      flag[i] = false;
    }
  }
}

快排

function swap(arr, i, j) {
  if(i === j) return;
  let tmp = arr[i];
  arr[i] = arr[j];
  arr[j] = tmp;
}

function quicksort(arr, s, e) {
  if( s >= e ) return;
  const r = s + Math.floor( (e - s) / 2 );

  // 选取中间位r(也可以随机)放在首位
  swap(arr, s, r);

  // 找选取的数应该在数组中的位置
  // 小于则表示位置挪后一位
  let m = s, j = s + 1;
  for(; m < e && j < e; j++) {
    if(arr[j] < arr[s]) {
      swap(arr, ++m, j);
    }
  }
  // 找到 r 的序位
  swap(arr, s, m);

  // 递归排序左左右两边
  quicksort(arr, s, m);
  quicksort(arr, m + 1, e);
}

let arr = [2, 7, 3, 4, 1, 8, 6];
quicksort(arr, 0, arr.length);

考查正则、replace 技巧

数钱格式化

'99999999'.replace(/\d{1,3}(?=(\d{3})+$)/g, '$&,');

// antd 的例子
'99999999'.replace(/\B(?=(\d{3})+(?!\d))/g, ',');

// 非正则版
function money(str) {
  const len = str.length;
  let start = len % 3 || 3;
  let arr = [str.slice(0, start)];
  while(start < len) {
    arr.push( str.slice(start, start + 3) );
    start += 3;
  }
  return arr.join(',')
}

首尾去空格

'   12 34 5 6   '.replace(/^\s+|\s+$/g, '');

相邻字符去重

'aaabbbcdfgghhjjkkk'.replace(/([A-Za-z]{1})(\1)+/g, '$1');

单词首字母大写

' hi man good  luck '.replace(/\w+/g, function(word) { 
    return word.substr(0,1).toUpperCase() + word.substr(1);
});
原文链接:juejin.im

上一篇:CSS使元素水平垂直居中
下一篇:vue 使用cropper.js 截取图片并进行图片压缩

相关推荐

  • 🙋Hanjst汉吉斯特改进+enSafeExpression安全表达式等

    Hanjst汉吉斯特模版语言及模版引擎,近期持续改进升级。 这次改进主要是增加了对安全输出表达式兼容,由于涉及到对软件开发过程中的效率和软件运行效率的平衡和取舍,所以多写了几句,以描述这个权衡利弊对...

    15 天前
  • 🙋Hanjst汉吉斯特升级:+showImageAsync及性能改进等

    自2019年元旦🙋Hanjst汉吉斯特 模板语言及其编译引擎发布,已经过去一年多了。 这期间随着 🙋Hanjst汉吉斯特 的推广应用,我们也陆续发布了如下一些更新内容: 🛠️Hanjst/汉吉...

    2 个月前
  • 😉我用 Nuxt.js 仿了个掘金

    前言 首先肯定是要夸夸掘金啦,最开始从 CSDN 到 博客园 再到 掘金,个人感觉掘金的技术氛围非常的nice,真是个宝藏社区👏。技术文章大多以前端为主,对前端开发者非常友好,质量也是歪瑞古的。

    1 个月前
  • 😀一个原生js弹幕库

    danmujs 😀一个原生js弹幕库,基于 CSS3 Animation 地址、核心代码 本项目基于 rcbullets,项目约70%的代码基于rcbullets,首先要感谢这个项目的作者,如...

    4 个月前
  • 🕵️‍♀️由原型到JS中的“模拟类”

    讲述了有关 JavaScript 中原型相关知识,又引出了 JavaScript 中的“类“究竟是什么?,以及一系列相关问题。 一、前置知识 1、JavaScript 的面向对象(OOP) ​ 面向...

    2 个月前
  • 🔥手写大厂前端知识点源码系列(上)

    如今前端攻城狮的要求越来越高,会使用常见的API已经不能满足现如今前端日益快速发展的脚步。现在大厂基本都会要求面试者手写前端常见API的原理,以此来证明你对该知识点的理解程度。

    2 个月前
  • 🔥前端面试大厂手写源码系列(上)

    如今前端攻城狮的要求越来越高,会使用常见的API已经不能满足现如今前端日益快速发展的脚步。现在大厂基本都会要求面试者手写前端常见API的原理,以此来证明你对该知识点的理解程度。

    2 个月前
  • 🔥《吊打面试官》系列 Node.js 必知必会必问!

    (/public/upload/f204a3b224d986128f1b4d9b8d06cd17) 前言 codeing 应当是一生的事业,而不仅仅是 30 岁的青春🍚 本文已收录 Git...

    3 个月前
  • 🔥0202年了,几个基础的手写函数总得会吧

    这几天看到一个大三大佬面试字节跳动的蚊子,突然觉得自己太辣鸡了···校招的题我一半多都不会啊···赶紧潜下心来学习学习提(an)高(wei)自己,边翻掘金边谷歌,简单实现了几个常用函数···(借鉴了太...

    2 个月前
  • 💖CSS + JS 送学妹满屏幕小爱心

    故事开始 午饭时间,暗恋已久的学妹拉着我的衣袖:“学长学长,你能不能让这些爱心变成五颜六色的吗~”。 我在旁边笑开了花~~~ image.png(/public/upload/04aaa24e...

    1 个月前

官方社区

扫码加入 JavaScript 社区