实现call办法

call做了什么:

  • 将函数设为对象的属性
  • 执行和删除这个函数
  • 指定this到函数并传入给定参数执行函数
  • 如果不传入参数,默认指向为 window
// 模仿 call bar.mycall(null);//实现一个call办法:// 原理:利用 context.xxx = self obj.xx = func-->obj.xx()Function.prototype.myCall = function(context = window, ...args) {  if (typeof this !== "function") {    throw new Error('type error')  }  // this-->func  context--> obj  args--> 传递过去的参数  // 在context上加一个惟一值不影响context上的属性  let key = Symbol('key')  context[key] = this; // context为调用的上下文,this此处为函数,将这个函数作为context的办法  // let args = [...arguments].slice(1)   //第一个参数为obj所以删除,伪数组转为数组  // 绑定参数 并执行函数  let result = context[key](...args);  // 革除定义的this 不删除会导致context属性越来越多  delete context[key];  // 返回后果   return result;};
//用法:f.call(obj,arg1)function f(a,b){ console.log(a+b) console.log(this.name)}let obj={ name:1}f.myCall(obj,1,2) //否则this指向window

实现双向数据绑定

let obj = {}let input = document.getElementById('input')let span = document.getElementById('span')// 数据劫持Object.defineProperty(obj, 'text', {  configurable: true,  enumerable: true,  get() {    console.log('获取数据了')  },  set(newVal) {    console.log('数据更新了')    input.value = newVal    span.innerHTML = newVal  }})// 输出监听input.addEventListener('keyup', function(e) {  obj.text = e.target.value})

实现数组的flat办法

function _flat(arr, depth) {  if(!Array.isArray(arr) || depth <= 0) {    return arr;  }  return arr.reduce((prev, cur) => {    if (Array.isArray(cur)) {      return prev.concat(_flat(cur, depth - 1))    } else {      return prev.concat(cur);    }  }, []);}

手写 Object.create

思路:将传入的对象作为原型

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

模仿Object.create

Object.create()办法创立一个新对象,应用现有的对象来提供新创建的对象的__proto__。

// 模仿 Object.createfunction create(proto) {  function F() {}  F.prototype = proto;  return new F();}

替换a,b的值,不能用长期变量

奇妙的利用两个数的和、差:

a = a + bb = a - ba = a - b

参考 前端进阶面试题具体解答

实现数组的map办法

Array.prototype._map = function(fn) {   if (typeof fn !== "function") {        throw Error('参数必须是一个函数');    }    const res = [];    for (let i = 0, len = this.length; i < len; i++) {        res.push(fn(this[i]));    }    return res;}

实现instanceOf

// 模仿 instanceoffunction instance_of(L, R) {  //L 示意左表达式,R 示意右表达式  var O = R.prototype; // 取 R 的显示原型  L = L.__proto__; // 取 L 的隐式原型  while (true) {    if (L === null) return false;    if (O === L)      // 这里重点:当 O 严格等于 L 时,返回 true      return true;    L = L.__proto__;  }}

实现数组去重

给定某无序数组,要求去除数组中的反复数字并且返回新的无反复数组。

ES6办法(应用数据结构汇合):

const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]

ES5办法:应用map存储不反复的数字

const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];uniqueArray(array); // [1, 2, 3, 5, 9, 8]function uniqueArray(array) {  let map = {};  let res = [];  for(var i = 0; i < array.length; i++) {    if(!map.hasOwnProperty([array[i]])) {      map[array[i]] = 1;      res.push(array[i]);    }  }  return res;}

手写节流函数

函数节流是指规定一个单位工夫,在这个单位工夫内,只能有一次触发事件的回调函数执行,如果在同一个单位工夫内某事件被触发屡次,只有一次能失效。节流能够应用在 scroll 函数的事件监听上,通过事件节流来升高事件调用的频率。

// 函数节流的实现;function throttle(fn, delay) {  let curTime = Date.now();  return function() {    let context = this,        args = arguments,        nowTime = Date.now();    // 如果两次工夫距离超过了指定工夫,则执行函数。    if (nowTime - curTime >= delay) {      curTime = Date.now();      return fn.apply(context, args);    }  };}

实现Event(event bus)

event bus既是node中各个模块的基石,又是前端组件通信的依赖伎俩之一,同时波及了订阅-公布设计模式,是十分重要的根底。

简略版:

class EventEmeitter {  constructor() {    this._events = this._events || new Map(); // 贮存事件/回调键值对    this._maxListeners = this._maxListeners || 10; // 设立监听下限  }}// 触发名为type的事件EventEmeitter.prototype.emit = function(type, ...args) {  let handler;  // 从贮存事件键值对的this._events中获取对应事件回调函数  handler = this._events.get(type);  if (args.length > 0) {    handler.apply(this, args);  } else {    handler.call(this);  }  return true;};// 监听名为type的事件EventEmeitter.prototype.addListener = function(type, fn) {  // 将type事件以及对应的fn函数放入this._events中贮存  if (!this._events.get(type)) {    this._events.set(type, fn);  }};

面试版:

class EventEmeitter {  constructor() {    this._events = this._events || new Map(); // 贮存事件/回调键值对    this._maxListeners = this._maxListeners || 10; // 设立监听下限  }}// 触发名为type的事件EventEmeitter.prototype.emit = function(type, ...args) {  let handler;  // 从贮存事件键值对的this._events中获取对应事件回调函数  handler = this._events.get(type);  if (args.length > 0) {    handler.apply(this, args);  } else {    handler.call(this);  }  return true;};// 监听名为type的事件EventEmeitter.prototype.addListener = function(type, fn) {  // 将type事件以及对应的fn函数放入this._events中贮存  if (!this._events.get(type)) {    this._events.set(type, fn);  }};// 触发名为type的事件EventEmeitter.prototype.emit = function(type, ...args) {  let handler;  handler = this._events.get(type);  if (Array.isArray(handler)) {    // 如果是一个数组阐明有多个监听者,须要顺次此触发外面的函数    for (let i = 0; i < handler.length; i++) {      if (args.length > 0) {        handler[i].apply(this, args);      } else {        handler[i].call(this);      }    }  } else {    // 单个函数的状况咱们间接触发即可    if (args.length > 0) {      handler.apply(this, args);    } else {      handler.call(this);    }  }  return true;};// 监听名为type的事件EventEmeitter.prototype.addListener = function(type, fn) {  const handler = this._events.get(type); // 获取对应事件名称的函数清单  if (!handler) {    this._events.set(type, fn);  } else if (handler && typeof handler === "function") {    // 如果handler是函数阐明只有一个监听者    this._events.set(type, [handler, fn]); // 多个监听者咱们须要用数组贮存  } else {    handler.push(fn); // 曾经有多个监听者,那么间接往数组里push函数即可  }};EventEmeitter.prototype.removeListener = function(type, fn) {  const handler = this._events.get(type); // 获取对应事件名称的函数清单  // 如果是函数,阐明只被监听了一次  if (handler && typeof handler === "function") {    this._events.delete(type, fn);  } else {    let postion;    // 如果handler是数组,阐明被监听屡次要找到对应的函数    for (let i = 0; i < handler.length; i++) {      if (handler[i] === fn) {        postion = i;      } else {        postion = -1;      }    }    // 如果找到匹配的函数,从数组中革除    if (postion !== -1) {      // 找到数组对应的地位,间接革除此回调      handler.splice(postion, 1);      // 如果革除后只有一个函数,那么勾销数组,以函数模式保留      if (handler.length === 1) {        this._events.set(type, handler[0]);      }    } else {      return this;    }  }};
实现具体过程和思路见实现event

手写 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),      result = null;  // 判断 context 是否传入,如果未传入则设置为 window  context = context || window;  // 将调用函数设为对象的办法  context.fn = this;  // 调用函数  result = context.fn(...args);  // 将属性删除  delete context.fn;  return result;};

实现非负大整数相加

JavaScript对数值有范畴的限度,限度如下:

Number.MAX_VALUE // 1.7976931348623157e+308Number.MAX_SAFE_INTEGER // 9007199254740991Number.MIN_VALUE // 5e-324Number.MIN_SAFE_INTEGER // -9007199254740991

如果想要对一个超大的整数(> Number.MAX_SAFE_INTEGER)进行加法运算,然而又想输入个别模式,那么应用 + 是无奈达到的,一旦数字超过 Number.MAX_SAFE_INTEGER 数字会被立刻转换为迷信计数法,并且数字精度相比以前将会有误差。

实现一个算法进行大数的相加:

function sumBigNumber(a, b) {  let res = '';  let temp = 0;  a = a.split('');  b = b.split('');  while (a.length || b.length || temp) {    temp += ~~a.pop() + ~~b.pop();    res = (temp % 10) + res;    temp  = temp > 9  }  return res.replace(/^0+/, '');}

其次要的思路如下:

  • 首先用字符串的形式来保留大数,这样数字在数学示意上就不会发生变化
  • 初始化res,temp来保留两头的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
  • 将两个数组的对应的位进行相加,两个数相加的后果可能大于10,所以可能要仅为,对10进行取余操作,将后果保留在以后位
  • 判断以后位是否大于9,也就是是否会进位,若是则将temp赋值为true,因为在加法运算中,true会主动隐式转化为1,以便于下一次相加
  • 反复上述操作,直至计算完结

实现 add(1)(2)(3)

函数柯里化概念: 柯里化(Currying)是把承受多个参数的函数转变为承受一个繁多参数的函数,并且返回承受余下的参数且返回后果的新函数的技术。

1)粗犷版

function add (a) {return function (b) {     return function (c) {      return a + b + c;     }}}console.log(add(1)(2)(3)); // 6

2)柯里化解决方案

  • 参数长度固定
var add = function (m) {  var temp = function (n) {    return add(m + n);  }  temp.toString = function () {    return m;  }  return temp;};console.log(add(3)(4)(5)); // 12console.log(add(3)(6)(9)(25)); // 43

对于add(3)(4)(5),其执行过程如下:

  1. 先执行add(3),此时m=3,并且返回temp函数;
  2. 执行temp(4),这个函数内执行add(m+n),n是此次传进来的数值4,m值还是上一步中的3,所以add(m+n)=add(3+4)=add(7),此时m=7,并且返回temp函数
  3. 执行temp(5),这个函数内执行add(m+n),n是此次传进来的数值5,m值还是上一步中的7,所以add(m+n)=add(7+5)=add(12),此时m=12,并且返回temp函数
  4. 因为前面没有传入参数,等于返回的temp函数不被执行而是打印,理解JS的敌人都晓得对象的toString是批改对象转换字符串的办法,因而代码中temp函数的toString函数return m值,而m值是最初一步执行函数时的值m=12,所以返回值是12。
  5. 参数长度不固定
function add (...args) {    //求和    return args.reduce((a, b) => a + b)}function currying (fn) {    let args = []    return function temp (...newArgs) {        if (newArgs.length) {            args = [                ...args,                ...newArgs            ]            return temp        } else {            let val = fn.apply(this, args)            args = [] //保障再次调用时清空            return val        }    }}let addCurry = currying(add)console.log(addCurry(1)(2)(3)(4, 5)())  //15console.log(addCurry(1)(2)(3, 4, 5)())  //15console.log(addCurry(1)(2, 3, 4, 5)())  //15

实现日期格式化函数

输出:

dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日
const dateFormat = (dateInput, format)=>{    var day = dateInput.getDate()     var month = dateInput.getMonth() + 1      var year = dateInput.getFullYear()       format = format.replace(/yyyy/, year)    format = format.replace(/MM/,month)    format = format.replace(/dd/,day)    return format}

字符串查找

请应用最根本的遍从来实现判断字符串 a 是否被蕴含在字符串 b 中,并返回第一次呈现的地位(找不到返回 -1)。

a='34';b='1234567'; // 返回 2a='35';b='1234567'; // 返回 -1a='355';b='12354355'; // 返回 5isContain(a,b);
function isContain(a, b) {  for (let i in b) {    if (a[0] === b[i]) {      let tmp = true;      for (let j in a) {        if (a[j] !== b[~~i + ~~j]) {          tmp = false;        }      }      if (tmp) {        return i;      }    }  }  return -1;}

Function.prototype.call

call惟一不同的是,call()办法承受的是一个参数列表

Function.prototype.call = function(context = window, ...args) {  if (typeof this !== 'function') {    throw new TypeError('Type Error');  }  const fn = Symbol('fn');  context[fn] = this;  const res = context[fn](...args);  delete context[fn];  return res;}

模板引擎实现

let template = '我是{{name}},年龄{{age}},性别{{sex}}';let data = {  name: '姓名',  age: 18}render(template, data); // 我是姓名,年龄18,性别undefined
function render(template, data) {  const reg = /\{\{(\w+)\}\}/; // 模板字符串正则  if (reg.test(template)) { // 判断模板里是否有模板字符串    const name = reg.exec(template)[1]; // 查找以后模板里第一个模板字符串的字段    template = template.replace(reg, data[name]); // 将第一个模板字符串渲染    return render(template, data); // 递归的渲染并返回渲染后的构造  }  return template; // 如果模板没有模板字符串间接返回}

封装异步的fetch,应用async await形式来应用

(async () => {    class HttpRequestUtil {        async get(url) {            const res = await fetch(url);            const data = await res.json();            return data;        }        async post(url, data) {            const res = await fetch(url, {                method: 'POST',                headers: {                    'Content-Type': 'application/json'                },                body: JSON.stringify(data)            });            const result = await res.json();            return result;        }        async put(url, data) {            const res = await fetch(url, {                method: 'PUT',                headers: {                    'Content-Type': 'application/json'                },                data: JSON.stringify(data)            });            const result = await res.json();            return result;        }        async delete(url, data) {            const res = await fetch(url, {                method: 'DELETE',                headers: {                    'Content-Type': 'application/json'                },                data: JSON.stringify(data)            });            const result = await res.json();            return result;        }    }    const httpRequestUtil = new HttpRequestUtil();    const res = await httpRequestUtil.get('http://golderbrother.cn/');    console.log(res);})();

AJAX

const getJSON = function(url) {  return new Promise((resolve, reject) => {    const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Mscrosoft.XMLHttp');    xhr.open('GET', url, false);    xhr.setRequestHeader('Accept', 'application/json');    xhr.onreadystatechange = function() {      if (xhr.readyState !== 4) return;      if (xhr.status === 200 || xhr.status === 304) {        resolve(xhr.responseText);      } else {        reject(new Error(xhr.responseText));      }    }    xhr.send();  })}