实现apply办法

思路: 利用this的上下文个性。apply其实就是改一下参数的问题
Function.prototype.myApply = function(context = window, args) {  // 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); // 这里和call传参不一样  // 革除定义的this 不删除会导致context属性越来越多  delete context[key];   // 返回后果  return result;}
// 应用function f(a,b){ console.log(a,b) console.log(this.name)}let obj={ name:'张三'}f.myApply(obj,[1,2])  //arguments[1]

转化为驼峰命名

var s1 = "get-element-by-id"// 转化为 getElementById
var f = function(s) {    return s.replace(/-\w/g, function(x) {        return x.slice(1).toUpperCase();    })}

前端手写面试题具体解答

将数字每千分位用逗号隔开

数字有小数版本:

let format = n => {    let num = n.toString() // 转成字符串    let decimals = ''        // 判断是否有小数    num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals    let len = num.length    if (len <= 3) {        return num    } else {        let temp = ''        let remainder = len % 3        decimals ? temp = '.' + decimals : temp        if (remainder > 0) { // 不是3的整数倍            return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp        } else { // 是3的整数倍            return num.slice(0, len).match(/\d{3}/g).join(',') + temp         }    }}format(12323.33)  // '12,323.33'

数字无小数版本:

let format = n => {    let num = n.toString()     let len = num.length    if (len <= 3) {        return num    } else {        let remainder = len % 3        if (remainder > 0) { // 不是3的整数倍            return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',')         } else { // 是3的整数倍            return num.slice(0, len).match(/\d{3}/g).join(',')         }    }}format(1232323)  // '1,232,323'

实现AJAX申请

AJAX是 Asynchronous JavaScript and XML 的缩写,指的是通过 JavaScript 的 异步通信,从服务器获取 XML 文档从中提取数据,再更新以后网页的对应局部,而不必刷新整个网页。

创立AJAX申请的步骤:

  • 创立一个 XMLHttpRequest 对象。
  • 在这个对象上应用 open 办法创立一个 HTTP 申请,open 办法所须要的参数是申请的办法、申请的地址、是否异步和用户的认证信息。
  • 在发动申请前,能够为这个对象增加一些信息和监听函数。比如说能够通过 setRequestHeader 办法来为申请增加头信息。还能够为这个对象增加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变动时会触发onreadystatechange 事件,能够通过设置监听函数,来解决申请胜利后的后果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接管实现,这个时候能够通过判断申请的状态,如果状态是 2xx 或者 304 的话则代表返回失常。这个时候就能够通过 response 中的数据来对页面进行更新了。
  • 当对象的属性和监听函数设置实现后,最初调用 sent 办法来向服务器发动申请,能够传入参数作为发送的数据体。
const SERVER_URL = "/server";let xhr = new XMLHttpRequest();// 创立 Http 申请xhr.open("GET", SERVER_URL, true);// 设置状态监听函数xhr.onreadystatechange = function() {  if (this.readyState !== 4) return;  // 当申请胜利时  if (this.status === 200) {    handle(this.response);  } else {    console.error(this.statusText);  }};// 设置申请失败时的监听函数xhr.onerror = function() {  console.error(this.statusText);};// 设置申请头信息xhr.responseType = "json";xhr.setRequestHeader("Accept", "application/json");// 发送 Http 申请xhr.send(null);

手写 Promise.then

then 办法返回一个新的 promise 实例,为了在 promise 状态发生变化时(resolve / reject 被调用时)再执行 then 里的函数,咱们应用一个 callbacks 数组先把传给then的函数暂存起来,等状态扭转时再调用。

那么,怎么保障后一个 **then** 里的办法在前一个 **then**(可能是异步)完结之后再执行呢? 咱们能够将传给 then 的函数和新 promiseresolve 一起 push 到前一个 promisecallbacks 数组中,达到承前启后的成果:

  • 承前:以后一个 promise 实现后,调用其 resolve 变更状态,在这个 resolve 里会顺次调用 callbacks 里的回调,这样就执行了 then 里的办法了
  • 启后:上一步中,当 then 里的办法执行实现后,返回一个后果,如果这个后果是个简略的值,就间接调用新 promiseresolve,让其状态变更,这又会顺次调用新 promisecallbacks 数组里的办法,周而复始。。如果返回的后果是个 promise,则须要等它实现之后再触发新 promiseresolve,所以能够在其后果的 then 里调用新 promiseresolve
then(onFulfilled, onReject){    // 保留前一个promise的this    const self = this;     return new MyPromise((resolve, reject) => {      // 封装前一个promise胜利时执行的函数      let fulfilled = () => {        try{          const result = onFulfilled(self.value); // 承前          return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //启后        }catch(err){          reject(err)        }      }      // 封装前一个promise失败时执行的函数      let rejected = () => {        try{          const result = onReject(self.reason);          return result instanceof MyPromise? result.then(resolve, reject) : reject(result);        }catch(err){          reject(err)        }      }      switch(self.status){        case PENDING:           self.onFulfilledCallbacks.push(fulfilled);          self.onRejectedCallbacks.push(rejected);          break;        case FULFILLED:          fulfilled();          break;        case REJECT:          rejected();          break;      }    })   }

留神:

  • 间断多个 then 里的回调办法是同步注册的,但注册到了不同的 callbacks 数组中,因为每次 then 都返回新的 promise 实例(参考下面的例子和图)
  • 注册实现后开始执行构造函数中的异步事件,异步实现之后顺次调用 callbacks 数组中提前注册的回调

实现一个call

call做了什么:

  • 将函数设为对象的属性
  • 执行&删除这个函数
  • 指定this到函数并传入给定参数执行函数
  • 如果不传入参数,默认指向为 window
// 模仿 call bar.mycall(null);//实现一个call办法:Function.prototype.myCall = function(context) {  //此处没有思考context非object状况  context.fn = this;  let args = [];  for (let i = 1, len = arguments.length; i < len; i++) {    args.push(arguments[i]);  }  context.fn(...args);  let result = context.fn(...args);  delete context.fn;  return result;};

实现千位分隔符

// 保留三位小数parseToMoney(1234.56); // return '1,234.56'parseToMoney(123456789); // return '123,456,789'parseToMoney(1087654.321); // return '1,087,654.321'
function parseToMoney(num) {  num = parseFloat(num.toFixed(3));  let [integer, decimal] = String.prototype.split.call(num, '.');  integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');  return integer + '.' + (decimal ? decimal : '');}

正则表达式(使用了正则的前向申明和反前向申明):

function parseToMoney(str){    // 仅仅对地位进行匹配    let re = /(?=(?!\b)(\d{3})+$)/g;    return str.replace(re,','); }

实现斐波那契数列

// 递归function fn (n){    if(n==0) return 0    if(n==1) return 1    return fn(n-2)+fn(n-1)}// 优化function fibonacci2(n) {    const arr = [1, 1, 2];    const arrLen = arr.length;    if (n <= arrLen) {        return arr[n];    }    for (let i = arrLen; i < n; i++) {        arr.push(arr[i - 1] + arr[ i - 2]);    }    return arr[arr.length - 1];}// 非递归function fn(n) {    let pre1 = 1;    let pre2 = 1;    let current = 2;    if (n <= 2) {        return current;    }    for (let i = 2; i < n; i++) {        pre1 = pre2;        pre2 = current;        current = pre1 + pre2;    }    return current;}

Function.prototype.bind

Function.prototype.bind = function(context, ...args) {  if (typeof this !== 'function') {    throw new Error("Type Error");  }  // 保留this的值  var self = this;  return function F() {    // 思考new的状况    if(this instanceof F) {      return new self(...args, ...arguments)    }    return self.apply(context, [...args, ...arguments])  }}

判断对象是否存在循环援用

循环援用对象原本没有什么问题,然而序列化的时候就会产生问题,比方调用JSON.stringify()对该类对象进行序列化,就会报错: Converting circular structure to JSON.

上面办法能够用来判断一个对象中是否已存在循环援用:

const isCycleObject = (obj,parent) => {    const parentArr = parent || [obj];    for(let i in obj) {        if(typeof obj[i] === 'object') {            let flag = false;            parentArr.forEach((pObj) => {                if(pObj === obj[i]){                    flag = true;                }            })            if(flag) return true;            flag = isCycleObject(obj[i],[...parentArr,obj[i]]);            if(flag) return true;        }    }    return false;}const a = 1;const b = {a};const c = {b};const o = {d:{a:3},c}o.c.b.aa = a;console.log(isCycleObject(o)

查找有序二维数组的目标值:

var findNumberIn2DArray = function(matrix, target) {    if (matrix == null || matrix.length == 0) {        return false;    }    let row = 0;    let column = matrix[0].length - 1;    while (row < matrix.length && column >= 0) {        if (matrix[row][column] == target) {            return true;        } else if (matrix[row][column] > target) {            column--;        } else {            row++;        }    }    return false;};

二维数组斜向打印:

function printMatrix(arr){  let m = arr.length, n = arr[0].length    let res = []  // 左上角,从0 到 n - 1 列进行打印  for (let k = 0; k < n; k++) {    for (let i = 0, j = k; i < m && j >= 0; i++, j--) {      res.push(arr[i][j]);    }  }  // 右下角,从1 到 n - 1 行进行打印  for (let k = 1; k < m; k++) {    for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) {      res.push(arr[i][j]);    }  }  return res}

Object.is

Object.is解决的次要是这两个问题:

+0 === -0  // trueNaN === NaN // false
const is= (x, y) => {  if (x === y) {    // +0和-0应该不相等    return x !== 0 || y !== 0 || 1/x === 1/y;  } else {    return x !== x && y !== y;  }}

模板引擎实现

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; // 如果模板没有模板字符串间接返回}

手写 bind 函数

bind 函数的实现步骤:

  1. 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
  2. 保留以后函数的援用,获取其余传入参数值。
  3. 创立一个函数返回
  4. 函数外部应用 apply 来绑定函数调用,须要判断函数作为构造函数的状况,这个时候须要传入以后函数的 this 给 apply 调用,其余状况都传入指定的上下文对象。
// bind 函数实现Function.prototype.myBind = function(context) {  // 判断调用对象是否为函数  if (typeof this !== "function") {    throw new TypeError("Error");  }  // 获取参数  var args = [...arguments].slice(1),      fn = this;  return function Fn() {    // 依据调用形式,传入不同绑定值    return fn.apply(      this instanceof Fn ? this : context,      args.concat(...arguments)    );  };};

实现字符串翻转

在字符串的原型链上增加一个办法,实现字符串翻转:

String.prototype._reverse = function(a){    return a.split("").reverse().join("");}var obj = new String();var res = obj._reverse ('hello');console.log(res);    // olleh

须要留神的是,必须通过实例化对象之后再去调用定义的办法,不然找不到该办法。

实现防抖函数(debounce)

防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则从新计时。

那么与节流函数的区别间接看这个动画实现即可。

手写简化版:

// 防抖函数const debounce = (fn, delay) => {  let timer = null;  return (...args) => {    clearTimeout(timer);    timer = setTimeout(() => {      fn.apply(this, args);    }, delay);  };};

实用场景:

  • 按钮提交场景:避免屡次提交按钮,只执行最初提交的一次
  • 服务端验证场景:表单验证须要服务端配合,只执行一段间断的输出事件的最初一次,还有搜寻联想词性能相似

生存环境请用lodash.debounce

实现prototype继承

所谓的原型链继承就是让新实例的原型等于父类的实例:

//父办法function SupperFunction(flag1){    this.flag1 = flag1;}//子办法function SubFunction(flag2){    this.flag2 = flag2;}//父实例var superInstance = new SupperFunction(true);//子继承父SubFunction.prototype = superInstance;//子实例var subInstance = new SubFunction(false);//子调用本人和父的属性subInstance.flag1;   // truesubInstance.flag2;   // false

函数珂里化

指的是将一个承受多个参数的函数 变为 承受一个参数返回一个函数的固定模式,这样便于再次调用,例如f(1)(2)

经典面试题:实现add(1)(2)(3)(4)=10;add(1)(1,2,3)(2)=9;

function add() {  const _args = [...arguments];  function fn() {    _args.push(...arguments);    return fn;  }  fn.toString = function() {    return _args.reduce((sum, cur) => sum + cur);  }  return fn;}

实现类的继承

类的继承在几年前是重点内容,有n种继承形式各有优劣,es6遍及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深刻了解的去看红宝书即可,咱们目前只实现一种最现实的继承形式。

function Parent(name) {    this.parent = name}Parent.prototype.say = function() {    console.log(`${this.parent}: 你打篮球的样子像kunkun`)}function Child(name, parent) {    // 将父类的构造函数绑定在子类上    Parent.call(this, parent)    this.child = name}/**  1. 这一步不必Child.prototype =Parent.prototype的起因是怕共享内存,批改父类原型对象就会影响子类 2. 不必Child.prototype = new Parent()的起因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性3. Object.create是创立了父类原型的正本,与父类原型齐全隔离*/Child.prototype = Object.create(Parent.prototype);Child.prototype.say = function() {    console.log(`${this.parent}好,我是练习时长两年半的${this.child}`);}// 留神记得把子类的结构指向子类自身Child.prototype.constructor = Child;var parent = new Parent('father');parent.say() // father: 你打篮球的样子像kunkunvar child = new Child('cxk', 'father');child.say() // father好,我是练习时长两年半的cxk

查找字符串中呈现最多的字符和个数

例: abbcccddddd -> 字符最多的是d,呈现了5次

let str = "abcabcabcbbccccc";let num = 0;let char = ''; // 使其依照肯定的秩序排列str = str.split('').sort().join('');// "aaabbbbbcccccccc"// 定义正则表达式let re = /(\w)\1+/g;str.replace(re,($0,$1) => {    if(num < $0.length){        num = $0.length;        char = $1;            }});console.log(`字符最多的是${char},呈现了${num}次`);

深克隆(deepclone)

简略版:

const newObj = JSON.parse(JSON.stringify(oldObj));

局限性:

  1. 他无奈实现对函数 、RegExp等非凡对象的克隆
  2. 会摈弃对象的constructor,所有的构造函数会指向Object
  3. 对象有循环援用,会报错

面试版:

/** * deep clone * @param  {[type]} parent object 须要进行克隆的对象 * @return {[type]}        深克隆后的对象 */const clone = parent => {  // 判断类型  const isType = (obj, type) => {    if (typeof obj !== "object") return false;    const typeString = Object.prototype.toString.call(obj);    let flag;    switch (type) {      case "Array":        flag = typeString === "[object Array]";        break;      case "Date":        flag = typeString === "[object Date]";        break;      case "RegExp":        flag = typeString === "[object RegExp]";        break;      default:        flag = false;    }    return flag;  };  // 解决正则  const getRegExp = re => {    var flags = "";    if (re.global) flags += "g";    if (re.ignoreCase) flags += "i";    if (re.multiline) flags += "m";    return flags;  };  // 保护两个贮存循环援用的数组  const parents = [];  const children = [];  const _clone = parent => {    if (parent === null) return null;    if (typeof parent !== "object") return parent;    let child, proto;    if (isType(parent, "Array")) {      // 对数组做非凡解决      child = [];    } else if (isType(parent, "RegExp")) {      // 对正则对象做非凡解决      child = new RegExp(parent.source, getRegExp(parent));      if (parent.lastIndex) child.lastIndex = parent.lastIndex;    } else if (isType(parent, "Date")) {      // 对Date对象做非凡解决      child = new Date(parent.getTime());    } else {      // 解决对象原型      proto = Object.getPrototypeOf(parent);      // 利用Object.create切断原型链      child = Object.create(proto);    }    // 解决循环援用    const index = parents.indexOf(parent);    if (index != -1) {      // 如果父数组存在本对象,阐明之前曾经被援用过,间接返回此对象      return children[index];    }    parents.push(parent);    children.push(child);    for (let i in parent) {      // 递归      child[i] = _clone(parent[i]);    }    return child;  };  return _clone(parent);};

局限性:

  1. 一些非凡状况没有解决: 例如Buffer对象、Promise、Set、Map
  2. 另外对于确保没有循环援用的对象,咱们能够省去对循环援用的非凡解决,因为这很耗费工夫
原理详解实现深克隆