前端面试题精选

1.用es5实现es6中类的继承
 

function Animal(){
  this.name="动物";
  this.sex="男孩";
}

Animal.prototype.eat = ()=>{console.log("吃食物")}

function Dog(){
  Animal.call(this);
  this.name="狗狗";
}

Dog.prototype = Object.create(Animal.prototype);

Dog.prototype.constructor = Dog;

Dog.prototype.bark = ()=>{console.log("狗在叫")}

/*
        Dog.prototype = Object.create(Animal.prototype);Dog.prototype.constructor = Dog;这两句代码其实可以使用 Dog.prototype.__proto__ = Animal.prototype代替.
*/

结果:

                                                          

2.你知道多少种数组去重的方法?

方法1:
  
      function listUnique(list){
    
        let arr = [];

        list.forEach((item)=>{
            if(!arr.includes(item)){
                arr.push(item);
           }
        })

        return arr;

     }

方法2:

     function listUnique(list){
    
        let obj = {},arr = [];

        list.forEach((item)=>{
            if(!obj[item]){
               obj[item] = true;
               arr.push(item);   
           }
        })

        return arr;

     }

方法3:

     function listUnique(list){
    
       let set = new Set(list); //Set与数组类似,但是它存储的值是唯一的

       return [...set]; 

     }

方法4:(如果题目设定数组参数全是数字,从低到高按序排列没有空值,可以采用双指针去重)

    function deDuplecate(list){
     let slow = 0;
     let fast = 1;
     const result = [];
     while(list[fast] != null){
        if(list[fast] > list[slow]){
            result.push(list[slow]);
            slow = fast;
        }
        fast++; 
     }
     result.push(list[slow]);
     return result;
   }








3.实现script的延迟加载有多少种方法

1.使用动态创建script标签的方式实现延迟加载

  
   function lazyLoad(src){ //需要延迟加载的时候传入要加载的js路径

     let script = document.createElement("SCRIPT");
     script.src = src;
     document.body.appendChild(script);

   }


2.操作dom的方式实现延迟加载
  
   <script src="" id="sc"></script>
   <script>
          setTimeout(()=>{
             document.getElementById("sc").src="xxx.js";   
          },3000)  
   </script>

3.async属性

   HTML5为<script>标签定义了async属性,不让页面等待脚本下载和执行,从而异步加载页面其他内容.

   <script src="test1.js" async></script>

4.call函数实现

Function.prototype._call = function (obj, ...rest) {

  if (typeof obj === "object" && obj !== null) {

    obj.fun = this;

    obj.fun(...rest);

    delete obj.fun;

  } else {
    this(...rest);
  }

}

function test(a, b, c) {
  console.log(a, b, c, this.name);
}

test._call({ name: "kay" }, 1, 2, 3);

5.apply函数实现

Function.prototype._apply = function (obj, arr = []) {

  if (typeof obj === "object" && obj !== null) {

    obj.fun = this;

    obj.fun(...arr);

    delete obj.fun;

  } else {
    this(...arr);
  }

}

function test(a, b, c) {
  console.log(a, b, c, this.name);
}

test._apply({ name: "kay" }, [1, 3, 5]);

6.bind函数实现

Function.prototype._bind = function (obj, ...rest) {

  if (typeof obj === "object" && obj !== null) {

    const obj_copy = { ...obj, fun: this };

    return function (...rest2) {

      return obj_copy.fun(...rest, ...rest2);

    }

  } else {

    const fun = this;

    return function (...rest2) {
      return fun(...rest, ...rest2);
    }

  }

}

function fun1(a, b) {
  return this.x + a + b;
}

const fun2 = fun1._bind({ x: 1 }, 2);

console.log(fun2(3));

7.你能写出几种节流函数

//时间戳版本(立即执行)
function throttle(fun, delay = 250) {

  let old_time = 0;

  return function (...args) {

    const now = new Date().getTime();

    if (now - old_time >= delay) {

      fun(...args);

      old_time = now;

    }


  }

}

//定时器版本(延迟执行)
function throttle(fun, delay = 250) {

  let running = false;

  return function (...args) {

    if (running) {
      return false;
    }

    running = true;

    let timer = setTimeout(() => {
      clearTimeout(timer);
      fun(...args);
      running = false;
    }, delay)

  }

}

验证方法:

  window.onload = function () {
    window.onresize = throttle(function () {
      console.log(123);
    }, 1000)
  }

8.实现防抖函数

//延迟执行
function debounce(fn, delay = 300) {

  let timer = null;

  return function (...args) {

    if (timer) {
      clearTimeout(timer);
    }

    timer = setTimeout(() => {

      clearTimeout(timer);
      timer = null;
      fn(...args);

    }, delay)

  }

}


//和上面函数相比多了一个初次进入函数时会立即执行
function debounce(fn, delay = 300) {

  let timer = null;

  return function (...args) {

    if (timer) {
      clearTimeout(timer);
    }

    if (timer === null) { //第一次执行
      fn(...args);
    }

    timer = setTimeout(() => {

      clearTimeout(timer);
      timer = null;
      fn(...args);

    }, delay)

  }

}

验证方法:

9.模拟实现new(创建实例)

/**
 * new主要做三件事情
 * 
 * 1.创建一个空对象
 * 2.构造函数的this指向该空对象
 * 3.执行构造函数
 * 4.返回该对象
 */
function _new(constructor) {

  let obj = {};

  return function (...args) {

    constructor.apply(obj, args);

    obj.__proto__ = constructor.prototype;

    return obj;

  }

}

验证方法:

function test(a, b) {
  this.name = `测试:${a},${b}`;
}

test.prototype.init = function () { console.log("测试原型对象上面的方法") }

const obj = _new(test)(1, 2);//创建的新对象

console.log(obj);

obj.init();

10.你知道多少种浅拷贝和深拷贝的方法

/**
 * 使用函数的嵌套实现深拷贝
 */
function deepCopy(data) {

  if (Array.isArray(data)) { // 对数组的处理

    let arr = [];

    data.forEach((item) => {
      arr.push(deepCopy(item));
    })

    return arr;

  } else if (Object.prototype.toString.call(data) === "[object Object]" && data !== null) { //对对象的处理

    let obj = Object.create(null);

    for (let key in data) {

      obj[key] = deepCopy(data[key]);

    }

    return obj;

  } else { //其他类型数据处理

    return data;

  }

}

/*
* 利用json方法实现深拷贝,但是缺点是不能拷贝函数
*/
function deepCopy(data) {
  try {
    return JSON.parse(JSON.stringify(data));
  } catch (error) {
    return data;
  }
}

/**
 * 使用es6解析结构实现浅拷贝 
 */
function lightCopy(data) {

  if (Array.isArray(data)) {
    return [...data];
  } else if (Object.prototype.toString === "[object Object]" && data !== null) {
    return { ...data };
  } else {
    return data;
  }

}

/**
 *  使用Object.assign实现浅拷贝
 */
function lightCopy(data) {

  if (typeof data !== "object" || typeof data === null) {
    return data;
  }

  return Object.assign(data);

}

11.实现instanceof

/**
 * @param {*对象} left 
 * @param {*构造函数} right 
 */
function _instanceof(left, right) {

  let _left = left.__proto__;

  const _right = right.prototype;

  while (true) {

    if (_left === _right) {
      return true;
    } else if (_left === null || _left === undefined) {
      return false;
    } else {
      _left = _left.__proto__;
    }

  }

}

console.log(_instanceof({}, Object));
console.log(_instanceof([], Array));
console.log(_instanceof(new Number(123), Number));
console.log(_instanceof(new String("String"), String)); //instanceof只能验证对象是否是特定类的一个实例.例如 var test = "string";_instanceof(test,String)结果为false
console.log(_instanceof([], Object));

12.实现Object.create

Object._create = function (obj) {
  function F() { }
  F.prototype = obj;
  return new F();
}

const obj = Object._create(null);

console.log(obj);

13.实现Array.isArray

Array._isArray = function (origin) {

  return Object.prototype.toString.call(origin) === "[object Array]";

}

console.log(Array._isArray({}));

14.实现reduce

Array.prototype._reduce = function(fn,data){

    const array = this;

    let start_index = 0;

    if(data === null || data === undefined){

        data = array[0];

        start_index = 1;

    }

    for(let i = start_index;i<array.length;i++){
        data = fn(data,array[i],i,array);
    }

    return data;

}


const arr = [1,2,3,4,5,6];

let obj = {};

const result = arr._reduce((prev,current,index,arr)=>{

    prev[index] = current;

    return prev;

},obj)

console.log(result);

15.获取对象上所有的可枚举的属性(不包括原型链上的属性)

Object._getOwnPropertyNames = function(data){

    if(Object.prototype.toString.call(data)!== "[object Object]"){
        return null;
    }
    
    let arr = [];

    for(let key in data){
         if(data.hasOwnProperty(key)){
            arr.push(key);
         }
    }

    return arr;

}

let obj = {
    a:1,
    b:2,
    c:function(){}
}

console.log(Object._getOwnPropertyNames(obj));

16.请分别使用Object.defineProperty和Proxy实现双向数据绑定

    <!--html部分-->
    <input type="text" id="inputText"/>
    <span id="showText"></span>
//使用Object.defineProperty实现
const data = {};
const data_bak = {
    text:""
}

const inputText = document.getElementById("inputText");
const showText = document.getElementById("showText");

Object.defineProperty(data,"text",{
    set(value){
        inputText.value = value;
        showText.innerText = value;
        data_bak.text = value;
    },
    get(){
        return data_bak.text;
    }
})

inputText.oninput = function(e){
  data.text = e.target.value;
}


//使用Proxy实现
const data = {
    text:""
};

const inputText = document.getElementById("inputText");
const showText = document.getElementById("showText");

const proxyItem = new Proxy(data,{
  set(target,key,value){
    inputText.value = value;
    showText.innerText = value;  
    target[key] = value;
  },
  get(target,key){
     return target[key];
  }
})

inputText.oninput = function(e){
  proxyItem.text = e.target.value;
}

17.实现forEach

Array.prototype._forEach = function(fn){
    const array = this;
    for(let i=0;i<array.length;i++){
        fn(array[i],i,array);
    }
}


const arr = [{value:1},{value:2},{value:3},{value:4}];

arr._forEach((item,index)=>{
   item.value++;
})

console.log(arr);

18.模拟实现async await

function async(fn){

    return new Promise((resolve,reject)=>{
        const gennerator = fn();    
        function next(data){
            const result = gennerator.next(data);
            if(result.done){ //运行完成了
                resolve(result.value);
            }else{
                if(result.value instanceof Promise){
                    result.value.then((value)=>{
                        next(value)
                    }).catch((e)=>{
                        reject(e);
                    })
                }else{
                    next(result.value);
                }         
            }
        } 
        next();
    })

}

验证方法

function delay(){
    return new Promise((resolve)=>{
        setTimeout(()=>{
            resolve(100);
        },1000)
    })
}

function* getData(){
   
  let data = yield delay();

  data++;

  data = data + (yield 100); 

  data = data + (yield delay());

  return data;

}

async(getData).then((value)=>{
  console.log(value); //结果为301
})

19.题目如下

题目:有如下的求和函数

    function sum(a,b,c,d,e){
      return a+b+c+d+e;
    }

请用函数柯里化的方式编写一个增强函数currying,如果当传入增强后的函数的参数个数等于sum的参数个数时才执行sum函数

例如:
const newFn = currying(sum);
console.log(newFn(1,2,3,4)); //结果为[Function]
console.log(newFn(1)(2)(3,4,5)); //结果为15
console.log(newFn(1)(2)(3)(4)(5)); //结果为15
/**
 * 函数珂里化
 */
function currying(fn){

  const length = fn.length;//调用函数的length属性可以得到该函数拥有几个参数

  return function child(...args){

    if(args.length>=length){
        return fn(...args);
    }else{
        return (...new_args)=>{
            return child(...args,...new_args);
        }
    }

  }

}

20.箭头函数能new吗?为什么?

箭头函数不能使用new创建实例.new操作符其实做了四件事.1.创建一个空对象,将this指向该对象2.执行构造函数3.将空对象的__proto__指向构造函数的原型对象4.最后返回该对象.箭头函数为什么不能new呢?首先它没有自己的this指向,其次箭头函数没有原型对象.

21.有形似如下树形结构任意层级的数据static,请编写一个函数findName通过传入数据源static和type_id返回相应的type_name.例如:

const static = {
    type_id:1,
    children:[
        {
            type_id:2,
            type_name:"2号",
            children:[{
                type_id:5,
                type_name:"5号",
                children:[
                    {type_id:6, type_name:"6号",},
                    {type_id:88, type_name:"88号",}
                ]
            }]
        },
        {
            type_id:3,
            type_name:"3号",
        }, {
            type_id:4,
            type_name:"4号",
            children:[{
                type_id:7,
                type_name:"7号",
            },{
                type_id:8,
                type_name:"8号",
            }]
        }
    ]
}

console.log(findName(static,88)); //返回88号
console.log(findName(static,6)); //返回6号
console.log(findName(static,5)); //返回5号
console.log(findName(static,2)); //返回2号
console.log(findName(static,100)); //返回null

参考编码:


const toString = Object.prototype.toString;

const static = {
    type_id:1,
    children:[
        {
            type_id:2,
            type_name:"2号",
            children:[{
                type_id:5,
                type_name:"5号",
                children:[
                    {type_id:6, type_name:"6号",},
                    {type_id:88, type_name:"88号",}
                ]
            }]
        },
        {
            type_id:3,
            type_name:"3号",
        }, {
            type_id:4,
            type_name:"4号",
            children:[{
                type_id:7,
                type_name:"7号",
            },{
                type_id:8,
                type_name:"8号",
            }]
        }
    ]
}


function findName(data,id){
  if(typeof data !== "object" || data === null){
    return false;
  }
  if(toString.call(data) === "[object Object]"){
     data = [data];
  }
  const result = clac(data,id,[]);
  if(result){
    let value;
    Array.from(Array(result.length)).forEach((v,index)=>{
        if(index == 0){
            value = data[result[index]];
        }else{
            value = value.children[result[index]];
        }
    })
    if(value){
        return value.type_name;
    }
  }else{
    return null;
  }
}

function clac(data,id,array){
   for(let i = 0;i<data.length;i++){
        if(data[i].type_id === id){
            array.push(i);
            return array;    
        }else if(toString.call(data[i].children) === "[object Array]"){//还有下一级
            const result = clac(data[i].children,id,[...array,i]);
            if(result !== undefined){
                return result;
            }
        }
   }
}

console.log(findName(static,88));
console.log(findName(static,6));
console.log(findName(static,5));
console.log(findName(static,2));
console.log(findName(static,4));
console.log(findName(static,3));
console.log(findName(static,100));

运行结果:

22.请实现迭代器函数Iterator,如下所示:

const it = Iterator([1,2,3,4]);
console.log(it.next()); //输出{ done: false, value: 1 }
console.log(it.next()); //输出{ done: false, value: 2 }
console.log(it.next()); //输出{ done: false, value: 3 }
console.log(it.next()); //输出{ done: false, value: 4 }
console.log(it.next()); //输出{ done: true,  value: undefined }

  Iterator函数代码实现:

function Iterator(array){
    let i = 0;
    return {
        next:function(){
            return {
                done:i<array.length?false:true,
                value:array[i++]
            }
        }
    }
}

23.请实现一个函数getDays,通过传入年份和月份作为函数的参数,返回该月的天数.

(提示:闰年的二月有29天,平年的二月只有28天.如果一个年份能被4整除但不能被100整除或者能被400整除则该年份为闰年)

例如:

实现代码:

function getDays(year, month) {
      //根据年和月得到当月的天数
      month = parseInt(month);
      if (!month || month <= 0 || month > 12) {
        return null;
      }
      if (month === 2) {
        // 2月份的时候根据闰年和平年计算
        if ((year % 4 === 0 && year % 100 !== 0) || year % 400 == 0) {
          //闰年
          return 29;
        } else {
          return 28;
        }
      } else {
        const result = month % 2;
        if (month <= 7) {
          return result > 0 ? 31 : 30;
        } else {
          return result > 0 ? 30 : 31;
        }
      }
}

24.请编写函数moneyFilter,传入金额,将其转化为千分位格式.

例如: moneyFilter("1234567899") ,运行结果为  "1,234,567,899"

 const money = '1234567899';

 function moneyFilter() {
      const reg = /\d{1,3}(?=(\d{3})+$)/g;
      return money.replace(reg, (v1) => {
        return `${v1},`;
   });
 }

 console.log(moneyFilter(money));

25.模拟实现es6中的rest: fun(a,b,...rest)

例如代码如下,对于任意函数test,请编写高阶函数wrapRest实现test函数中通过rest获取剩余参数.

    function test(a,b,rest){
      console.log(a,b,rest);
    }

    function wrapRest(fn){
     ...
    }

    const restFn = wrapRest(test);

    restFn(1,2,3,4,5); //输出结果: 1 2 [3, 4, 5]
    restFn(1); //输出结果: 1 undefined []
    restFn(1,2); //输出结果: 1 2 []

实现代码:

  function wrapRest(fn){
      const len = fn.length; //获取参数的个数
      const start_index = len - 1;
      return function(){
        const prev_array = [];
        for(let i = 0;i < start_index; i++){
          prev_array.push(arguments[i]);
        }
        const next_array = Array.prototype.slice.call(arguments,start_index);
        prev_array.push(next_array);
        return fn.apply(this,prev_array);
      }
 }

26.洗牌算法.请编写一个函数sample,传入一个任意数组和个数n,每次返回数组中n个随机元素.

例如:sample([1,2,3,4,5,6,7,8,9], 5) //每次运行函数返回数组中5个随机的数字,比如[7, 8, 5, 1, 9]

实现代码:

  //获取随机数
  function randomValue(max, min) {
      if (min == null) {
        min = 0;
      }
      return parseInt(Math.random() * (max - min + 1)) + min;
  }
    
    //获取随机数组
    function sample(array, n) {
      if (n == null) {
        return array(randomValue(array.length));
      }
      const new_array = Array.isArray(array)
        ? array.slice()
        : [];
      const len = new_array.length;
      const count = Math.max(Math.min(n, len),0);
      for (i = 0; i < count; i++) {
        const idx = randomValue(i, len - 1);
        let tmp = new_array[i];
        new_array[i] = new_array[idx];
        new_array[idx] = tmp;
      }
      return new_array.slice(0,n);
    }

   console.log(sample([1,2,3,4,5,6,7,8,9], 5)); //测试代码

27.数组平摊.请编写函数flatten,传入任意数组array和是否浅度展开标识shallow,将数组平摊输出.

例如: 

        flatten([1, [2, 3], [4, 5, [6, [[7], 8, [9, [10, 11]]]]]])          //输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

        flatten([1, [2, 3], [4, 5, [6, [[7], 8, [9, [10, 11]]]]]],true)   //输出: [1, 2, 3, 4, 5, Array(2)]

实现代码:

   //shallow为true,浅展开.false,深度展开
   function flatten(array, shallow) {
      function circulate(data, flag) {
        let arr = [];
        data.forEach((item) => {
          if (Array.isArray(item) && !flag) {
            const result = circulate(item, shallow);
            arr = arr.concat(result);
          } else {
            arr.push(item);
          }
        });
        return arr;
      }
      return circulate(array, false);
    }

    console.log(flatten([1, [2, 3], [4, 5, [6, [[7], 8, [9, [10, 11]]]]]])); //测试代码

28.函数组合.题目如下,存在任意有返回值的函数A,B,C.请编写函数compose,生成的新函数fun传入参数时,函数将按照由右往左的顺序依次运行.传入的参数将作为A函数的入参运行,A函数的返回值作为B函数的参数,B函数的返回值作为C函数的参数.最后返回计算结果.

 function A(v) {
      return v + 2;
 }

 function B(v) {
   return v * 4;
 }

 function C(v) {
   return v - 6;
 }

 const fun = compose(C, B, A);

 console.log(fun(5)); //结果为22

代码实现:

 function compose() {
      const args = arguments;

      const start = args.length - 1;

      return function () {
        const params = Array.prototype.slice.call(arguments, 0);

        let idx = start;

        let result = args[idx].apply(null, params);

        idx--;

        while (idx >= 0) {
          result = args[idx].call(null, result);
          idx--;
        }

        return result;
      };
}

29.编写函数range生成数字队列如下,三个参数分别对应起始值,结束值和步长.(计算结果包含起始值,不包含结束值)

    console.log(range(10)); //[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    console.log(range(1, 11)); //[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    console.log(range(0, 30, 5)); //[0, 5, 10, 15, 20, 25]

实现代码:

    function range(start, end, step) {
      if (end == null) {
        end = start;
        start = 0;
      }

      if (step == null) {
        step = 1;
      }

      const array = [];

      const len = Math.floor((end - start) / step);

      let tmp = start;

      for (let i = 0; i < len; i++) {
        array.push(tmp);
        tmp += step;
      }

      return array;

    }

30.字符串逃逸.为了预防xss攻击,请编写函数createEsacper将含有特殊含义的字符串进行转译.

 const escapeMap = { //需要转译的队列
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&hx27;',
      '`': '&#x660;',
 };

 const escape = createEsacper(escapeMap);

 console.log(escape(`<script>alert(123)<\/script>`)); //输出 &lt;script&gt;alert(123)&lt;/script&gt;

代码实现:

  function createEsacper(keyMap) {
      const keys = Object.keys(keyMap);

      const reg = new RegExp(`(?:${keys.join('|')})`, 'g');

      const replace = (value) => {
        return keyMap[value];
      };

      return function (str) {
        return reg.test(str) ? str.replace(reg, replace) : str;
      };
  }

31.存在斐波那契数列求值函数fn.为了提升计算效率,请编写缓存函数memoize生成新函数,将每次的输入参数和值配对缓存起来,方便下次快速计算.

    //键名的设置
    const hasher = function () {
      var n = arguments[0];
      return `键_${n}`;
    };

    const fn = function (n) {
      return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
    };

    var fibonacci = memoize(fn, hasher);//斐波那契数列求值

   
    console.log(fibonacci(10)); // 输出55

    console.log(fibonacci.cache); //输出 {"键_1":1,"键_0":0,"键_2":1,"键_3":2,"键_4":3,"键_5":5,"键_6":8,"键_7":13,"键_8":21,"键_9":34,"键_10":55}

实现代码:

  function memoize(fn, hasher) {
      const middle = function () {
        const params = arguments[0];

        const cache = middle.cache;

        const key = hasher ? hasher.call(null, params) : params;

        if (!cache[key]) {
          cache[key] = fn.apply(null, arguments);
        }

        return cache[key];
      };

      middle.cache = {};

      return middle;
    }

32.请编写createIndexFinder函数生成indexOf和lastIndexOf,通过传入数组和值找出值的索引.其中indexOf第三个参数为true时采用二分查找法.indexOf从正向找值的索引,lastIndexOf从反方向找.找不到时返回-1.如下:

    const indexOf = createIndexFinder(1, true);

    const lastIndexOf = createIndexFinder(-1);

    const arr = [1, 3, 5, 7, 9];

    console.log(indexOf(arr, 9, true)); //输出4

    console.log(lastIndexOf(arr, 9)); //输出4

实现代码:

function createIndexFinder(dir, sorter) {
      return function (array, item, flag) {
        const start_index = dir > 0 ? 0 : array.length - 1; //获取起始索引

        if (!flag || !sorter) {
          flag = false;
        }

        if (flag) {
          //启用二分查找法
          let low = 0,
            high = array.length - 1;
          let mid;
          while (low < high) {
            mid = Math.floor((high + low) / 2);
            if (array[mid] < item) {
              low = mid + 1;
            } else {
              high = mid;
            }
          }
          return low;
        }

        for (let i = start_index; i < array.length && i >= 0; i += dir) {
          if (isNaN(item) && isNaN(array[i])) {
            //因为 NaN === NaN 为false,所以要对NaN做特殊处理
            return i;
          }
          if (array[i] === item) {
            return i;
          }
        }

        return -1;
      };
    }

33. 一段文本不知道内容有多少,请用css的方式实现,当内容只有一行时居中显示,多行时居左显示

 .div{
       display: flex;
       flex-direction: row;
       justify-content: center;
}

这道题考察flex布局.flex-direction定义了主轴的方向,justify-content定义了主轴的对齐方式.文本是单行的时候居中显示,文本多行会默认居左.

34.Promise能实现异步操作的原理是什么?为什么resolve()执行后,它下一行代码仍然继续执行

如果面试官抛出这样的问题,自信的同学直接手写一个30行简版的Promise甩给他再作答.

function _Promise(fn) {
  let array = [],
    index = 0;
  const { cache_array, cache_index } = __Promise.cache || {};
  if (cache_array != null) {
    array = cache_array;
  }
  if (cache_index != null) {
    index = cache_index + 1;
  }
  return new __Promise(array, fn, index);
}
function __Promise(array, fn, index) {
  this.then = (hanlder) => {
    array.push(hanlder);
    return this;
  };
  const resolve = (...args) => {
    setTimeout(() => {
      __Promise.cache = {
        cache_array: array,
        cache_index: index,
      };
      array[index].apply(null, args);
      __Promise.cache = null;
    }, 0);
  };
  fn.call(null, resolve);
  return this;
}

测试代码:

new _Promise((resolve)=>{
   resolve(3);
   console.log(2);
}).then((v)=>{
   return new _Promise((resolve)=>{
        v++;
        console.log(v); 
        resolve(v);
    })
}).then((v)=>{
   return new _Promise((resolve)=>{
        setTimeout(()=>{
            resolve(v+10)
        },3000)
    })
}).then((v)=>{
   console.log(v);
})

执行结果:

解答:

Promise实现异步的原理是采用观察者的模式将回调函数先存储起来,等到运行resolve()时就取出回调函数执行从而达到异步调用的目的.resolve()执行完它的下一行代码还会继续执行的原因是resolve本身是一个异步调用的机制,resolve()运行完并不会立即跳到.then的回调函数中,而是会先运行resolve后面的代码再执行回调函数.

35.请设计一个请求方法request,接口访问失败后重试访问,最多重试三次.如果都失败了退出操作,但请求成功了请将结果返回.

调用如下.

//页面上调用
request({
  url:"/api/getList",
  data:{
     id:1
  }
}).then((data)=>{
  console.log(data);//获得接口数据
})

测试代码:

function request(params){
   let count = 3;
   //控制返回结果
   function execute(rResolve){
      if(rResolve == null){ //第一次请求 rResolve为空
         return new Promise((resolve)=>{
            Post(resolve)
         })
      }else{
         Post(rResolve);
      }      
   }
   //请求数据
   function Post(resolve){
      axios.post(params.url,params.data).then((res)=>{
         resolve(res);
       }).catch(()=>{
          if(count > 0){
            count--;
            execute(resolve);
          }
       })
   }
   return execute();
}

36.计算多个区间的交集.每个区间用长度为2的数字数组表示,如[2,5]表示区间2到5(包括2和5).区间不限定防线.如[5,2]等同于[2,5].

请实现getIntersection函数,可接受多个区间并返回所有区间的交集(用区间表示),空集用null表示.调用如下.

getIntersection([5, 2], [1, 9], [3, 6]); // [3,5]

getIntersection([1, 7], [8, 9], [1, 2, 3]); // null

测试代码:

function getIntersection(...args) {
  function clac(array1, array2) {
    if (array1[0] > array1[1]) {
      array1 = [array1[1], array1[0]];
    }

    if (array2[0] > array2[1]) {
      array2 = [array2[1], array2[0]];
    }

    if (array1[1] < array2[0] || array1[0] > array2[1]) {
      return [];
    }

    let min_arr, max_arr;

    if (array1[1] - array1[0] <= array2[1] - array2[0]) {
      min_arr = array1;
      max_arr = array2;
    } else {
      min_arr = array2;
      max_arr = array1;
    }

    const result = [];

    for (let i = min_arr[0]; i <= min_arr[1]; i++) {
      if (i >= max_arr[0] && i <= max_arr[1]) {
        result.push(i);
      } else if (i > max_arr[1]) {
        break;
      }
    }

    if (result.length > 0) {
      return [result[0], result[result.length - 1]];
    }

    return result;
  }

  let data = args.reduce((value, cur) => {
    return clac(value, cur);
  });

  if (data.length == 0) {
    return null;
  } else {
    return data;
  }
}

37.请编写一个统计函数stat.获取当前页面中元素节点的数量总和、元素节点的最大嵌套深度以及最大子元素个数,请用 JS 配合原生 DOM API 实现该需求(不用考虑陈旧浏览器以及在现代浏览器中的兼容性,可以使用任意浏览器的最新特性;不用考虑 shadow DOM)。比如在如下页面中运行后:

<html>
  <head></head>
  <body>
    <div>
      <span>f</span>
      <span>o</span>
      <span>o</span>
    </div>
  </body>
</html>

// 会输出:
{
  totalElementsCount: 7,
  maxDOMTreeDepth: 4,
  maxChildrenCount: 3
}

测试代码:

function stat() {
  const htmlEle = document.querySelector('html');

  let count = 1;

  let max_child = 0;

  let deep = 1;

  function executa(ele, layer) {
    layer++;

    const nodeList = Array.prototype.slice.call(ele.children, 0);

    count += nodeList.length;

    max_child = nodeList.length > max_child ? nodeList.length : max_child;

    deep = layer > deep ? layer : deep;

    nodeList.forEach((node) => {
      if (node.children.length > 0) {
        executa(node, layer);
      }
    });
  }

  executa(htmlEle, 1);

  return {
    totalElementsCount: count,
    maxDOMTreeDepth: deep,
    maxChildrenCount: max_child,
  };
}

38.请使用原生代码实现一个Events模块,可以实现自定义事件的订阅、触发、移除功能

调用如下:

const fn1 = (...args) => console.log('I want sleep1', ...args);
const fn2 = (...args) => console.log('I want sleep2', ...args);
const event = new Events();
event.on('sleep', fn1, 1, 2, 3);
event.on('sleep', fn2, 1, 2, 3);
event.fire('sleep', 4, 5, 6);
// I want sleep1 1 2 3 4 5 6
// I want sleep2 1 2 3 4 5 6
event.off('sleep', fn1);
event.once('sleep', () => console.log('I want sleep'));
event.fire('sleep');
//I want sleep2 1 2 3
//I want sleep
event.fire('sleep', 4, 5, 6);
//I want sleep2 1 2 3 4 5 6

测试代码:

class Events {
  loop = {};
  constructor() {}

  on(...args) {
    const [event_name, fn, ...rest] = args;

    if (!this.loop[event_name]) {
      this.loop[event_name] = [];
    }

    const nFun = fn.bind(null, ...rest);

    nFun.old = fn;

    this.loop[event_name].push(nFun);
  }

  off(event_name, fn) {
    const list = this.loop[event_name];

    const index = list.findIndex((v) => {
      return v.old === fn;
    });

    if (index != -1) {
      list.splice(index, 1);
    }
  }

  once(...args) {
    this.on(...args);
    const event_name = args[0];
    const list = this.loop[event_name];
    list[list.length - 1].once = true;
  }

  fire(event_name, ...args) {
    const list = this.loop[event_name] || [];

    const once_list = [];

    list.forEach((fn, index) => {
      fn(...args);
      if (fn.once) {
        once_list.push(index);
      }
    });

    if (once_list.length > 0) {
      const result = [];
      list.forEach((item, index) => {
        if (!once_list.includes(index)) {
          result.push(item);
        }
      });
      this.loop[event_name] = result;
    }
  }
}

39.请编写函数requestHandler对请求方法进行封装,请求成功直接返回结果.10s内最多发起3次请求失败重试,如果3次都请求失败或者请求总时长超过10s抛出异常.

调用方式如下:

   // 模拟请求的方法
   function request(){
     return new Promise((resolve,reject)=>{
      setTimeout(()=>{
        const num = parseInt(Math.random() * (10 - 1 +1)) + 1;
        const flag = num > 7 ? false:true;
        if(!flag){
          resolve(null);
        }else{
          reject(null)
        }
      },500) 
     })
   }

  
   function requestHandler(){
       

   }

   requestHandler().then(()=>{
       console.log('请求成功');
   }).catch((err)=>{
    console.log(err);
   });

测试代码:

function requestHandler(){

      let count = 0;
     
      function execuate(){
        return new Promise((Rresolve,Rreject)=>{
          (function req(){
            count++;
            if(count > 3){
              Rreject("请求三次失败");
              return;
            }
            request().then(()=>{
              Rresolve();
            }).catch(()=>{
              req();
            })
          })();
        })
      }

      return Promise.race([execuate(),new Promise((r,reject)=>{
        setTimeout(()=>{ 
          reject("TIME_OUT") 
        },10000)
      })])
    
   }

40.请模拟实现Promise.race

调用方式如下:

  Promise._race([
    new Promise((resolve)=>{
       setTimeout(()=>{
        resolve(1000)
       },1000)
    })
    ,new Promise((resolve)=>{
       setTimeout(()=>{
        resolve(500)
       },500)
    })
  ]).then((msg)=>{
    console.log(msg); // 输出500
  }).catch((err)=>{
    console.log(err);
  })

测试代码:

  Promise._race = function(list){
    return new Promise((resolve,reject)=>{
         list.forEach((p)=>{
            p.then((data)=>{
               resolve(data);
            }).catch((error)=>{
                reject(error);
            })
         })
    })
  }

41.请编写函数compose依次执行完所有异步任务队列,要求前一个异步任务结束后才开始执行下一个异步任务.

调用方式如下:

    const tasks = [
      ()=>(
        new Promise((resolve)=>{
          setTimeout(()=>{
            console.log(1);
            resolve(1);
          },3000)
        })
      ),
      ()=>(
        new Promise((resolve)=>{
          setTimeout(()=>{
            console.log(2);
            resolve(2);
          },2000)
        })
        ),
      ()=>(
        new Promise((resolve)=>{
          setTimeout(()=>{
            console.log(3);
            resolve(3);
          },1000)
        })
      ),
    ]

    function compose(list){
    
    }
    
    compose(tasks).then((v)=>{
      console.log(v)
    }); // 依次打印 1 2 3 3 

测试代码:

  function compose(list){
      return list.reduce((cur,next)=>{
        return cur.then(()=>{return next()})
      },Promise.resolve())
  }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值