阅读 120

前端面试题总结1(持续更新)

前端技术总结

预编译

function fn(a,c){
            console.log(a) //f a(){}
            var a = 123
            console.log(a) //123
            console.log(c) //f c(){}
            function a(){}
            if(false){
                var d=678
            }
            console.log(d)//undefined
            console.log(b)//undefined
            var b =function() {}
            console.log(b)//f(){}
            function c(){}
            console.log(c)//f c(){}
        }
        fn(1,2)
        //预编译
        //作用域的创建阶段 预编译阶段
        //预编译的时候做了哪些事情
        //js的变量对象 AO对象 供js引擎自己去访问的
        //1 创建了ao对象 2 找形参和变量的声明 作为ao对象的属性名 值为undefined 3 实参和形参相统一 4找函数		  声明 会覆盖变量的声明
        AO:{
        a:undefined 1 function a(){}
        c:undefined 2 function c(){}
        d:undefined
        b:undefined
        }


复制代码

this指向问题

在函数中直接使用

   function get(content){
         console.log(content)
     }
     get('您好')//可以看作下列语句的语法糖
     get.call(window,'您好')
复制代码

函数作为对象的方法被调用(谁调用 就指向谁)

  var person = {
        name:'张三',
        run:function(time){
            console.log(`${this.name}在跑步 最多${time}min就不行了`)
        }
    }
    person.run(30)//可以看作下列语句的语法糖
    person.run.call(person,30)
复制代码

列题

    var name=222
       var a ={
           name:111,
           say:function(){
               console.log(this.name)
           }
       }
       var fun = a.say
       fun() //fun,call(window) 222
       a.say()//a.say.call(a) 111

       var b={
           name:333,
           say:function(fun){
               fun()
           }
       }
       b.say(a.say)//传入之后等价于  fun()=a.say 等价于第一种 222
       b.say = a.say
       b.say()//b调用 等于console.log(this.name)复制到b的say方法中 即333

复制代码

箭头函数中的this

  • 箭头函数中的this是在定义函数的时候绑定的,而不是在执行函数的时候绑定

  • 箭头函数中,this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为他没有this,所以也就不能用作构造函数

         var x =11
            var obj={
                x:22,
                say:()=>{
                    console.log(this.x)
                }
            }
            obj.say()   //输出是11
    复制代码
         var obj={
                   birth:1990,
                   getAge:function(){
                       var b=this.birth
                       var fn=()=>new Date().getFullYear()-this.birth
                       return fn()
                   }
               }
               console.log(obj.getAge())//输出是2021-1990=31
    复制代码

    因为箭头函数在getAge()中定义的,其父级为obj内部作用域,所以其箭头函数中的this指向的是父级obj对象,this.birth即1990

js中的深浅拷贝

赋值 :当我们把一个对象赋值给一个新的变量时候,赋的其实是该对象在栈中的地址,而不是堆中的数据,也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此两个对象是联动的

浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响

深拷贝:从堆中开辟一块新的区域存放对象,原始对象和新对象之前互不影响

操作和原数据是否指向同一对象第一层数据为一般数据类型第一层数据不是一般数据类型
赋值改变会使得原始数据改变改变会改变原始数据
浅拷贝改变不会改变原始数据改变会改变原始数据
深拷贝改变不会改变原始数据改变不会改变原始数据
  • 赋值代码

    var person = {
               name:"张三",
               hobby:['学习','敲代码','吃瓜']
           }
    
           var person1 = person
           person1.name = "李四"
           person1.hobby[0]='玩耍'
           console.log(person)//都是name:"李四" hobby:['玩耍','敲代码','吃瓜']
           console.log(person1)
    复制代码
  • 浅拷贝代码(基本数据类型不受影响,引用数据类型被同时改变)

    var person = {
               name:"张三",
               hobby:['学习','敲代码','吃瓜']
           }
    function shallowCopy(obj){
               var target = {}
               for(var i in obj){
                   if(obj.hasOwnProperty(i)){
                       target[i]=obj[i]
                   }
               }
               return target
           }
    
    var person1 = shallowCopy(person)
    person1.name='王二'
    person1.hobby[0]="玩耍"
    console.log(person)//张三       玩耍 敲代码  吃瓜
    console.log(person1)//王二      玩耍 敲代码  吃瓜
    复制代码
  • 深拷贝代码(拷贝前后数据互不影响)

    var person = {
               name:"张三",
               hobby:['学习','敲代码','吃瓜']
           }
    function deepClone(obj){
            var cloneObj = new obj.constructor()
            if(obj===null) return obj
            if(obj instanceof Date) return new Datr(obj)
            if(obj instanceof RegExp) return new RegExp(obj)
            if(typeof obj !== 'object') return obj
            for(var i in obj){
                if(obj.hasOwnProperty(i)){
                    cloneObj[i]=deepClone(obj[i])
                }
            }
            return cloneObj
        }
        // var person1 = JSON.parse(JSON.stringify(person))//大多数时候可以,但是如果对象中有日期、正	则表达式、函数就不行
        var person1 = deepClone(person)
        person1.name="胡图图"
        person1.hobby[0]="打麻将"
        console.log(person1)//二者完全不影响
        console.log(person)
    复制代码

    浅拷贝的实现方式

    • lodash里面的_.cloneObj
    • ...展开运算符
    • Array.prototype.concat()
    • Array.prototype.slice()

    深拷贝的实现方式

    • JSON.parse(JSON.stringify()) 在对象中有正则表达式、Date对象、正则对象、Promise时会发生异常
    • 递归实现(如上图中代码)
    • cloneDeep()
    • jquery.extend()

防抖

可以采用闭包的方式

 var input = document.querySelector('input')
      function debounce(delay){
          let timer
          return function(value){
              clearTimeout(timer)
              timer=setTimeout(function(){
                  console.log(value)
                  
              },delay)
          }
      }
      var debounceFunc = debounce(1000)
      input.addEventListener('keyup',function(e){
          debounceFunc(e.target.value)
      })
复制代码

采用闭包的方式,return的函数中既可以使用timer,timer只会在调用debounce的时候生成一次

节流

一段时间内只做一件事情,无论点击多少次,等到执行完了之后再执行第二个事情,闭包解决节流

document.querySelector('button').addEventListener('click',thro(handle,2000))
        function thro(func,wait){
            let timeout
            return function(){
                if(!timeout){
                    timeout = setTimeout(function(){
                        func()
                        timeout=null
                    },wait)
                }
            }
        }
        function handle(){
            console.log(Math.random())
        }
复制代码

闭包的底层原理

image-20210713133311411

 function a(){
         var aa=123
         function b(){
             var bb = 234
             console.log(aa)
         }
         return b
     }
     var res = a()
     res()//能够输出123
复制代码

因为a()执行完毕之后a对应的作用域链会断开,但是b在定义的时候就可以访问到a作用域内的作用域链,这个作用域链并不会断开,所以res()依然可以访问到aa=123,即可以输出123,闭包可以让变量一直保存在内存中。

闭包实现单例模式

var createLogin = function () {
        var div = document.createElement('div')
        div.innerHTML = "我是弹出的div"
        div.style.display = 'none'
        document.body.appendChild(div)
        return div
    }

    var getSingle = function(fn){
        var result
        return function(){
            return result || (result=fn.apply(this,arguments))
        }
    }
    
    var create = getSingle(createLogin)
    document.querySelector('button').onclick=function(){
        var loginLay = create()
        loginLay.style.display = "block"
    }
复制代码

无论点击多少次,只创建一个div

js运行机制

js是单线程,因为js离不开和用户的操作,假定js为多线程,在操作dom的时候,同时创建删除同一个dom,是无法执行的

arguments

function get(){
    console.log(arguments)
}
get(1,2,3)
//arguments是一个类数组对象
//可以通过Array.prototype.slice.call(arguments)来将其转化为数组
//也可以通过es6中的展开运算符来转化为数组
复制代码

为什么在调用这个函数的时候,代码中的b会变成全局变量

 function func(){
            let a=b=3
        }
        func()
        console.log(b)//3
        console.log(a)//error
复制代码

因为let a=b=3等价于let a=(b=3) b并没有声明,所以在创建的时候会变成全局变量,在func()执行之后,因为b为全局变量,所以可以访问,但是a在func()的作用域内,外部并不能访问

哪些操作会造成内存泄漏

  • 闭包
  • 意外的全局变量
  • 被遗忘的定时器
  • 脱离dom的引用(比如var div=document.querySelector('div')之后将其删除,但是并未对消除div这个引用,在内存中依然保留了对于div的引用)

高阶函数

将函数作为参数或者返回值的函数

function highOrder(params,callback){
    return callback(params)
}
复制代码

手写map

 var arr=[1,2,3]
        function map(arr,mapCallback){
            //检查参数是否正确
            if(!Array.isArray(arr)||!arr.length||typeof mapCallback!=="function"){return []}
            else{
                let result = []
                for(let i=0;i<arr.length;i++){
                    result.push(mapCallback(arr[i],i,arr))//1每一项 2索引 3传入数组
                }
                return result
            }
        }

        var res = map(arr,(item)=>{
            return item*2
        })

        console.log(res)//2,4,6
复制代码

js的事件循环机制

  • js中的异步操作比如fetch setTimeout setInterval压入到调用栈中的时候里面的消息会进入到消息队列中去,消息队列会等到调用栈清空之后再执行

  • promise async await的异步操作的时候会加入到微任务中去,会在调用栈清空的时候立即执行,调用栈中加入的微任务会立马执行

js单例模式

  • 定义 1.只有一个实例 2.可以全局访问
  • 主要解决的问题:一个全局使用的类 频繁的创建和销毁
  • 何时使用:当你想控制实例的数目 节省系统化资源的时候
  • 如何实现:判断系统是否有这个单例,如果有则返回,没有则创建
  • 单例模式的优点:内存中只有一个实例 减少了内存的开销 尤其是频繁的创建和销毁实例(比如首页页面的缓存)

es6创建单例

class Person{
         constructor(name,sex,hobby){
             this.name = name
             this.sex = sex
             this.hobby = hobby
         }
         static getInstance(name,sex,hobby){
             if(!this.instance){
                 this.instance = new Person(name,sex,hobby)
             }
             return this.instance
         }
     }

     let person1 = Person.getInstance('胡图图','男','吃瓜')
     let person2 = Person.getInstance('胡英俊','女','打豆豆')
     console.log(person1==person2)//true
     console.log(person1)//'胡图图','男','吃瓜'
     console.log(person2)
复制代码

策略模式

  • 策略模式的定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换
  • 策略模式指的是定义一系列的算法,把它们一个个封装起来。将不变的部分和变化的部分分隔是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将算法的使用与算法的实现分离开来
	var registerForm = document.querySelector('.registerForm')

     var strategies = {
         isNonEmpty: function(value,errorMsg){
             if(value == ''){return errorMsg}
         },
         minLength:function(value,length,errorMsg){
             if(value.length<6){return errorMsg}   
         },
         isMobile:function(value,errorMsg){
             if(!/^1[3|5|5][0-9]{9}$/.test(value)){return errorMsg}
         }
     }

        //假设有一个验证类,Validator  new Validator()
       var validataFun = function(){
           var validator = new Validator()
           //添加验证规则
           validator.add(registerForm.username,'isNonEmpty','用户名不能为空')
           validator.add(registerForm.password,'minLength:6','密码长度不能小于6位')
           validator.add(registerForm.username,'isMobile','手机号格式不正确')
           //开启验证
           var errorMsg = validator.start()
           return errorMsg
       }

       registerForm.onsubmit = function(){
           var errorMsg = validataFun()
           if(errorMsg){
               alert(errorMsg)
               return false
           }
       }

       //封装策略类 构造函数class
       var Validator = function(){
           //保存验证规则的数组
           this.cache=[]
       }

       Validator.prototype.add = function(dom,rule,errorMsg){
           var arr = rule.split(':')
           this.cache.push(function(){
               var strategy = arr.shift()
               arr.unshift(dom.value)
               arr.push(errorMsg)
               return strategies[strategy](...arr)
           })
       }

       Validator.prototype.start = function(){
           for(var i=0,vaFunc;vaFunc = this.cache[i++];){
               var msg = vaFunc()
               if(msg){
                   return msg
               }
           }
       }
复制代码

BFC

  • 定义:块级格式化上下文,它是指一个独立的块级渲染区域,只有Block-level BOX参与,该区域拥有一套渲染规则来约束块级盒子的布局,且与区域外部无关

    从一个现象说起

    一个盒子father没有设置height,其内容子元素son都为浮动的时候,无法撑起自身,即father的height一直为0,这个盒子没有形成BFC

  • 如何创建BFC

    • float的值不为none
    • position的值不是static或者relative
    • display的值是inline-block、flex或者inline-flex
    • overflow:hidden(比较好的方式,不会影响到外部的布局)
  • BFC的其他作用

    • 可以取消margin塌陷的问题
    • 可以阻止元素被浮动元素覆盖

数组扁平化

  • 数组自带的扁平化方法

    const arr = [1,[2,[3,[4,5]]],6]
    console.log(arr.flat(Infinity))
    复制代码
  • 正则加JSON.parse(JSON.stringify(arr))

  • 递归

文章分类
前端
文章标签