前端面试基础(手写基础题)

总结高频手写题目




JS系列

一、实现函数节流、防抖方法

节流函数(throttle):规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只能有一次生效。

//func用户传入的防抖函数;wait是等待时间为1s
const throttle= (func,wait = 1000) => {
    //上一次执行该函数的时间
    let lastTimer = 0;
    return function () {
        let now = +new Date();
        let args = arguments;
        // 将当前时间和上一次执行时间做对比
        // 差值大于wait 执行函数
        if (now-lastTimer > wait) {
            lastTimer = now;
            func.apply(this, args)
        }
    }
};

setInterval(
    throttle(()=>{
        console.log(1);
    },500)
); 

适用场景:

  • 拖拽场景:固定时间内只执行一次,防止高频词触发位置变动。
  • 缩放场景:监控浏览器resize
  • 动画场景:避免短时间内多次触发动画引起性能为题。

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

//func需要传入的防抖函数
//wait是等待时间
const debounce = (func,wait=50)=>{
    let timer = 0;
    // 返回函数是用户每次实际调用的防抖函数
    return function (...args) {
        // 如果已经设定过定时器了就清空上一次的定时器
        if(timer) clearTimeout(timer);
        // 开启一个新的定时器,延迟执行用户传入的方法
        timer = setTimeout(()=>{
            func.apply(this,args) //通过this,args获取函数作用域和变量
        },wait)    
    }
};

上面实现了简单的防抖,但是有缺陷,这个防抖只能在最后调用。详细的防抖会有 immediate选项。表示是否立即调用。

适用场景

  • 表单验证:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次。
  • 按钮提交:只执行最后一次提交,防止多次提交按钮。

二、数组去重

给定一个数组:var arr = [1,1,‘true’,‘true’,true,true,0,0,undefined,undefined,null,null,NaN,NaN,‘NaN’,2,{},{}];

  • 方法一:双重for + splice
function arrayUnique_1(arr) {
    let res = [];
    for(let i=0;i<arr.length;i++) {
        for (let j=i+1;j<arr.length;j++) {
            //当第一个等于第二个,splice方法删除第二个
            if(arr[i]===arr[j]){
                arr.splice(j,1);
                j-- //第二个删除后,这个索引位需要重新判断
            }
        }
    }
    return arr
}
console.log(arrayUnique_1(arr)); //NaN和{}没有去重

  • 方法二: 利用ES6的 set 去重
function arrayUnique_2(arr) {
    return Array.from(new Set(arr))
}

function arrayUnique_3(arr) {
    return [...new Set(arr)]
}
console.log(arrayUnique_2(arr)); //{}没有去重
console.log(arrayUnique_3(arr)); //{}没有去重

  • 方法三:利用indexof去重
function arrayUnique_4(arr) {
    //判断是否是数组
    if (!Array.isArray(arr)) {
        console.log('type error!');
        return
    }
    var array = [];
    for (let i=0;i<arr.length;i++) {
        if (array.indexOf(arr[i]) === -1){ //利用indexof 检测元素在数组中第一次出现的位置是否和元素现在的位置相等
            array.push(arr[i])
        }
    }
    return array
}

console.log(arrayUnique_4(arr)); //NaN,{}没有去重

- 方法四:利用sort()排序后,判断相邻元素是否相等
function arrayUnique_5(){
    if (!Array.isArray(arr)) {
        console.log('type error!');
        return ;
    }
    arr = arr.sort();
    let array = [arr[0]]
    for (let i=1;i<arr.length;i++) {
        if(arr[i]!==arr[i-1]){
            array.push(arr[i])
        }
    }
    return array
}

console.log(arrayUnique_5(arr)); //NaN,{}没有去重

  • 方法五:includes检测数组中是否有某个值
function arrayUnique_6(arr) {
    if (!Array.isArray(arr)){
        console.log('type error!');
        return ;
    }
    let array = [];
    for (let i=0;i<arr.length;i++){
        if (!array.includes(arr[i])){ //includes:检测数组是否有某个值
            array.push(arr[i]);
        }
    }
    return array
}

console.log(arrayUnique_6(arr)); //{}没有去重

  • 方法六:利用filter
//利用filter
function arrayUnique_7(arr) {
    return arr.filter(function (item,index,arr) {
        // 当前元素,在原始数组中的第一个索引===当前索引值,则返回
        return arr.indexOf(item,0) === index; //每次都是从左边开始找
    })
}

console.log(arrayUnique_7(arr)); //{}没有去重,NaN直接没了

  • 方法七:利用Map

利用Map中不会出现相同key值的特点,创建一个空的Map数据结构,
遍历数组,把数组中的每一个元素作为key存到Map中。

function arrayUnique_8(arr) {
    let map = new Map();
        let array = new Array();
        for(let i=0;i<arr.length;i++){
            if(map.has(arr[i])) {
            map.set(arr[i],true)
        }else{
             map.set(arr[i],false);
             array.push(arr[i])
        }
    }
    return array;
}

console.log(arrayUnique_8(arr)); //{}没有去重

三、深克隆

  • 方法一
const newObj = JSON.parse(JSON.stringify(oldObj));

缺点:无法实现对函数,RegExp等特殊对象的克隆

  • 方法二
function deepCopy(obj) {
  // 判断是否是简单的数据类型
  if(typeof obj === "object"){
      let res = obj.constructor == Array ? [] : {};
      for(let i in obj){
          res[i] = typeof obj[i] == "object" ? deepCopy(obj[i]):obj[i];
      }
  }  else{
      // 如果是简单数据类型,则直接赋值
      let res = obj;
  }
  return res;
}

四、实现instance of

  • 核心是原型链的向上查找
function myInstanceof(left, right) {
  let proto = Object.getPrototypeOf(left);
  while(true) {
    if(proto == null) return false;
    if(proto === right.prototype) return true;
    ptoto = Object.getPrototypeOf(ptoto);
  }
}

五、嵌套数组指定层次展开flat扁平化

// 实现数组得扁平化
// 就是将多维数组转换为一维数组
let ary = [1,[2,[3,[4,5]]],6]
let str = JSON.stringify(ary)

// arr_flat = ary.flat(Infinity); //不知道要扁平的数组的具体深度,只想完全扁平这个嵌套数组里的成员话,可以使用 Infinity 这个值
// // flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回
// console.log(arr_flat);

// 递归
// 递归的遍历每一项,若为数组则继续遍历,否则concat
// let ary = [1,[2,[3,[4,5]]],6]
// let fn = function(ary) {
//   let result = [];
//   ary.map(item =>{
//     if(Array.isArray(item)){
//         result = result.concat(flatten(item));
//       }else{
//         result.push(item)
//       }
//   })
//   return result
// }
// console.log(fn());

function flatten(arr) {
    while(arr.some(item=>Array.isArray(item))) {
        arr = [].concat(...arr);
    }
    return arr;
}

console.log(flatten(ary));

// 和上面的toString一样,join也可以将数组转换为字符串
function flatten(arr) {
    return arr.join(',').split(',').map(function(item) {
        return parseInt(item);
    })
}
console.log(flatten(ary));

六、实现 reduce 数组方法

//reduce()函数接受两个参数,一个函数一个累积变量的初始值。
//函数有四个参数:累计变量初值(默认第一个成员),当前变量值(默认第二个成员),当前位置,数组自身。
//arr.reduce(function(prev, cur, index, arr){}, initialValue)
Array.prototype.myReduce=function(fn,base){
    if(typeof fn !== 'function'){
        throw new TypeError("arguments[0] is not a function");//TypeError是错误的类型之一:类型错误
    }

    var initialArr=this;//调用myReduce()函数的当前对象
    var arr=initialArr.concat();//目的是返回一个等于初始数组的新数组,后面的操作都基于arr,这样初始数组不会发生改动
    var index,newValue;
    
    if(arguments.length==2){
        arr.unshift(base);
        index = 0; //!!当前位置 指的是当前变量(第二个参数)针对调用该方法的数组位置即initialArr
    }else{
        index=1;
    }

    if(arr.length===1){//长度为1 直接返回
        newValue=arr[0];
    }

    while(arr.length>1){
        newValue=fn.call(null,arr[0],arr[1],index,initialArr);
        index++;
        arr.splice(0,2,newValue);//删除前两位 然后把累加值添加到第一位
    }
    return newValue;
};

七、实现数组map方法

// 模拟实现map
// arr.map(function (currentValue, index, arr) {
// })
// currentValue 必须。 当前元素的值
// index 可选。 当期元素的索引值
// arr 可选。 当期元素属于的数组对象

Array.prototype.newMap = function (fn) { //写法一
    var newArr = [];
    for (var i = 0; i < this.length; i++) {
        newArr.push(fn(this[i], i, this)) //this指向调用newMap方法的数组
    }
    return newArr;
}

// arr.reduce((previousValue, currentValue, currentIndex, array) => {}, initialValue?)
// reduce若不指定初始值, 那么第一次迭代的 previousValue 为 ar[[0], currentValue 为 arr[1], currentIndex 为 1,
// 若指定初始值, 那么第一次迭代的 previousValue 为 initialValue, currentValue为 arr[0], currentIndex 为0.

Array.prototype.newMap = function (fn, Arg) { 写法二:用数组的reduce方法实现数组的map
    var res = [];
    this.reduce((prev, curr, index, array) => {
        res.push(fn.call(Arg, curr, index, array));
    }, 0) //指定初始值initialValue=0,所以从currentIndex=0开始,即第一个开始  不这样会缺第一项,结果为[3,4]
    return res;
}

let arr = [1, 2, 3];
let res = arr.newMap((a) => a + 1);
console.log(res); //[2,3,4]

八、实现Array.fill()、Array.filter()

// array.fill(value, start, end)
// value 必需。填充的值。start 可选。开始填充位置。end 可选。停止填充位置 (默认为 array.length)

Array.prototype.myFill = function (value, start = 0, end = this.length) {
    for (let i = start; i < end; i++) {
        this[i] = value;
    }
}
//Array.filter()
Array.prototype.myFilter = function myFilter(fn, context) {
    if (typeof fn !== "function") {
        throw new TypeError(`${fn} is not a function`);
    }
    let arr = this;
    let temp = [];
    for (let i = 0; i < arr.length; i++) {
        let result = fn.call(context, arr[i], i, arr);
        if (result) temp.push(arr[i]);
    }
    return temp;
};

九、实现Array.find()、Array.finIndex()

Array.find()
用于找出第一个符合条件的数组成员,参数为一个回调函数

Array.prototype.myFind = function (fn, start = 0, end = this.length) {
    for (let i = start; i < end; i++) {
        if (fn.call(this, this[i], i, this)) {
            return this[i]
        }
    }
}

Array.findIndex()

Array.prototype.myFindIndex = function (fn, start = 0, end = this.length) {
    for (let i = start; i < end; i++) {
        if (fn.call(this, this[i], i, this)) {
            return i
        }
    }
    return -1
}

十、Promise简单实现

这题再缓缓

十一、Ajax请求的原生实现

// 请熟练掌握
var xhr = new XMLHttpRequest();// 创建XMLHttqRequest
var url = 'https://bbin.com';
xhr.onreadystatechange=function(){// 监听状态码的变化,每次变化 均执行
    if(xhr.readyState===4){

        if (xhr.status === 200) { // 服务端 状态码
            console.log(xhr.responseText); //服务器返回的响应文本
        }else{
            console.error(xhr.statusText); //状态码的文本描述,如200的statusText是ok
        }
    }
}

xhr.open('GET', url, true); // 初始化请求参数,还没发送请求   true表示异步
xhr.send(null); // 向服务器发送请求,但是不带有数据发送过去,一般在get方式发送时候多使用这个方式

十二、模拟new

// new操作符的作用:
// 1. 创建了一个全新的对象,这个对象的__proto__要指向构造函数的原型对象
// 2. 执行构造函数
// 3. 返回值为Object类型则作为new方法的返回值返回。否则返回上述全新对象
第一种:
function myNew(fn, ...args) {
  let instance = Object.create(fn.prototype);
  let res = fn.apply(instance, args);
  return typeof res === 'object' ? res: instance;
}

第二种:
function myNew(constructor,params){
  var args = [].slice.call(arguments);
  var constructor = args.shift();
  var obj = new Object();
  obj.__proto__ = constructor.prototype;
  var res = constructor.apply(obj,args);
  return (typeof res === 'object' && typeof res !== null) ? res : obj;
}

十三、实现Object.create方法

// 用于创建一个新对象,被创建的对象继承另一个对象(o)的原型
function createObj(o) {//传入的参数o为返回实例的__porto__,也就是实例构造函数的显示原型
    function F() {}//构造函数
    F.prototype = o;
    return new F();//返回实例
}

十四、ES5实现继承那些事

详细讲解请点击链接☝

十五、实现函数的call、apply、bind方法

详细讲解请点击链接☝,具体实现☟

/* 先将传入的指定执行环境的对象 context 取到
将需要执行的方法(调用call的对象) 作为 context 的一个属性方法fn
处理传入的参数, 并执行 context的属性方法fn, 传入处理好的参数
删除私自定义的 fn 属性
返回执行的结果 */
// 模拟 call 方法
Function.prototype.defineCall = function (context) {
    context = context || window;
    context.fn = this; //this指向 sayName函数实例
    let args = [];
    for (let i = 1; i < arguments.length; i++) { //i从1开始 
        args.push(arguments[i]);
    } //或者args = [...arguments].slice(1);
    let result = context.fn(args.join(','));
    delete context.fn;
    return result;
}
let sayName = function (age) {
    console.log('current name: ' + this.name, "current age: " + age);
}
let obj1 = {
    name: "obj's name"
}
sayName.defineCall(obj1, 22); //this指向 sayName函数实例
// current name: obj's name current age: 22


// 模拟 apply 方法
Function.prototype.defineApply = function (context, arr) {
    context = context || window;
    context.fn = this;
    let result;
    if (!arr) { // 第二个参数不传
        result = context.fn();
    } else { // 第二个参数是数组类型
        let args = [];
        for (let i = 0; i < arr.length; i++) { //i从0开始
            args.push(arr[i]);
        }
        result = context.fn(args.join(','));
    }
    delete context.fn;
    return result;
}
let obj2 = {
    name: ['Tom', 'Johy', 'Joe', 'David']
}
sayName.defineApply(obj2, [3, 4, 5, 6, 7]);
// current name: Tom,Johy,Joe,David current age: 3,4,5,6,7


//用call、apply模拟实现bind
Function.prototype.bind = function (context) {
    let self = this; // 保存函数的引用
    return function () { // 返回一个新的函数
        // console.log(arguments);
        // return self.apply(context, arguments);
        return self.call(context, arguments);
    }
};

let obj = {
    name: 'seven'
}

let func = function () {
    console.log(this.name)
}.bind(obj);
func('zhangsan', 20);

十六、实现一个双向绑定

十七、单例模式

十八、观察者模式

//ES6 实现观察者模式代码:(观察订阅模式)
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn); // 依赖收集  Vue中的dep
const observable = obj => new Proxy(obj, {set});// Proxy和Reflect一一对应
function set(target, key, value, receiver){
    const result = Reflect.set(target, key, value, receiver); // 执行set
    queuedObservers.forEach(observe => observe()); // 派发更新
    return result;
}

// 使用 观察者模式实例
const person = observable({
    name: 'Sun',
    age: 30
});

function print(){
    console.log(`name: ${person.name}, age: ${person.age}`);
}
observe(print);

person.age = 31;
// name: Sun, age: 31

十九、使用setTimeout实现setInterval方法

function mysetinterval(fn,time){
    console.log("利用steTimeout实现setInterval");
    function interval(){//执行该函数,异步被挂起time时间后在执行,一上来就执行fn
        setTimeout(interval,time);//异步
        //好,time时间过去,这个异步被执行,而内部执行的函数正是interval,就相当于进了一个循环,递归
        fn();//同步
    }
    setTimeout(interval,time);//interval被延迟time时间执行
}

二十、实现jsonp

var newscript=document.createElement('script');
newscript.src='https://sp0.baidu.com/su?wd=Java&cb=foo';
document.body.appendChild(newscript);
function foo(data) { //callback函数要绑定在window对象上
   console.log(data);
}

二十一、实现Promise All、Promise.race

 //promise.all()
  function myall(proArr) {
    return new Promise((resolve, reject) => {
      let ret = []
      let count = 0
      let done = (i, data) => {
        ret[i] = data
        if(++count === proArr.length) resolve(ret)
      }
      for (let i = 0; i < proArr.length; i++) {
        proArr[i].then(data => done(i,data) , reject)
      }
    })
  }
  
//promise.race();这么简单得益于promise的状态只能改变一次,即resolve和reject都只被能执行一次
 function myrace(proArr) {
    return new Promise(function (resolve, reject) {
      for(let i=0;i<proArr.length;i++){
        proArr[i].then(resolve,reject);
      }
    })
  }

CSS

一、CSS画各种图形

<div class="a"></div>
//等腰三角形
.a{
width: 0px;
height: 0px;
border-bottom: 50px solid red;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
}

//等腰梯形
.a{
width: 50px;
height: 0px;
border-bottom: 50px solid red;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
}

//扇形
.a{
width: 0px;
height: 0px;
border-bottom: 50px solid red;
border-left: 50px solid transparent;
border-right: 50px solid transparent;
border-radius: 50%;
}

//圆
.a{
width: 50px;
height: 50px;
background-color: blue;
border-radius: 25px;//50% 边框半径为宽高的50%
}
半圆
.a{
width: 100px;
height: 50px;
background-color: blue;
border-top-left-radius: 50px;
border-top-right-radius: 50px;
/* border-bottom-left-radius: 50px; */
}

二、三列布局

双飞翼布局是通过新建的div的外边距隔离各列,
而圣杯布局则是通过父容器的内边距来实现各列的间隙。

三、垂直水平居中

每日一更,持续练习!颤抖吧、愚蠢的面试题们。


参考资料

前端秋招面经

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值