Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS基础手写代码 #20

Open
Godiswill opened this issue Mar 18, 2020 · 0 comments
Open

JS基础手写代码 #20

Godiswill opened this issue Mar 18, 2020 · 0 comments

Comments

@Godiswill
Copy link
Owner

Godiswill commented Mar 18, 2020

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 (typeof _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 instanceof 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 (typeof obj !== 'object' || obj === null) return false;
  var proto = obj;

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

  return Object.getPrototypeOf(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 typeof obj === 'object' || typeof obj === 'function' ?
    type[ Object.prototype.toString.call( obj ) ] || 'object' :
    typeof 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 = Date.now();
    const space = now - lastTime; // 间隔时间
    if( lastTime && space < delay ) { // 为了响应用户最后一次操作
      // 非首次,还未到时间,清除掉计时器,重新计时。
      timer && clearTimeout(timer);
      // 重新设置定时器
      timer = setTimeout(() => {
        lastTime = Date.now(); // 不要忘了记录时间
        fn.apply(this, arguments);
      }, delay - space);
      return;
    }
    // 首次或到时间了
    lastTime = now;
    fn.apply(this, arguments);
    timer && clearTimeout(timer);
  };
}

事件代理

function delegate(ele, selector, type, fn) {
  function callback(e) {
    e = e || window.event;
    const target = e.target || e.srcElement;
    let selectors = ele.querySelectorAll(selector);
    selectors = [].slice.call(selectors);
    if( selectors.includes(target) ) {
      fn.call(target, e);
    }
  }
  ele.addEventListener(type, callback, false);
}

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

限制并发数

requestLimit

function sendRequest(urls, max, callback) {
  const last = performance.now();
  const 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;
      const 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);
      const data = `resolve: ${url}`;
      resolve(data);
      console.log(data);
    }, Math.random() * time);

    bad = setTimeout(function () {
      clearTimeout(good);
      const 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 || typeof 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);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant