美文网首页
前端常见问题总结

前端常见问题总结

作者: 饥人谷_折纸大师 | 来源:发表于2022-08-08 11:52 被阅读0次

    什么是闭包?闭包的用途是什么?闭包的缺点是什么?

    什么是闭包

    闭包:内部函数总是可以访问其所在的外部函数中声明的参数和变量。声明在一个函数中的函数,叫做闭包函数。
    存在不被释放或者互相引用的场景都可以叫做闭包。
    代码举例:

    function Counter(){
        let num = 0
        function add(){
            num++
            console.log(num)
        }
        return add
    }
    let addOne = Counter()
    addOne()//1
    addOne()//2
    

    在Counter函数中创建了一个add函数,且add函数中用到了Counter函数的num变量,这就使得num和add函数构成了一个闭包。这个变量num一直存在于内中,我们很难在外部访问num变量,但是我们可以通过操作内部的函数来控制它。

    闭包的用途
    1. 隐藏变量,只通过制定接口访问数据
    const cache = (() => {
     const store = {}
     return {
         get(key) {
             return store[key]
         },
         set(key, val) {
             store[key] = val
         },
         remove(key) {
             delete store[key]
         }
     }
    })()
    

    这个箭头函数我们会得到一个对象,我会得到它的get,set和remove方法,我们打印这个cache的时候只会看到get,set和remove三个属性,store就被我们隐藏起来了。但是我们可以通过get,set和remove来操作store。

    1. 存储临时变量
    function sum(a){
     return function(b){
         return a+b
     }
    }
    const sum1 = sum(1)
    const sum2 = sum(2)
    console.log(sum1(3))//4
    console.log(sum2(3))//5
    

    声明好的sum1和sum2就会隐藏在闭包里,当我们在调用sum1(3)的时候就自动加上了sum1,同理在调用sum2(3)的时候,也自动加上了sum2。sum1和sum2就存储了一个临时的数据给我们用。

    1. 让对象拥有私有属性
     constructor() {
         let name
         Object.assign(this,{
             setName(newName){
                 name = newName
             },
             getName(){
                 return name
             }
         })
     }
     sayHi(){
         console.log(`Hi,${this.getName()}`)
     }
    }
    let p1 = new People
    p1.setName('qyk')
    p1.getName()//'qyk'
    p1.sayHi()//"Hi,qyk"
    

    sayHi()调用了上层的name,是个闭包。同时也让p1有了私有属性。

    1. 调用一个函数返回另一个函数
    闭包的缺点
    1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
    2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变父函数内部变量的值。

    call、apply、bind 的用法分别是什么?

    call

    call 方法第一个参数是要绑定给this的值,后面传入的是一个参数列表。当第一个参数为null、
    undefined的时候,默认指向window。

    function add(a,b){
      return a+b;
    }
    function.call(add,1,2)//3 其中1和2是连续的参数
    
    apply

    apply接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组。当第一个参数为
    null、undefined的时候,默认指向window。

    function add(a,b){
      return a+b;
    }
    funciton.apply(add,[1,2]//3 其中1和2数参数数组
    

    当函数需要传递多个变量时, apply 可以接受一个数组作为参数输入, call 则是接受一系列的单独变量。

    bind

    第一个参数是this的指向,从第二个参数开始是接收的参数列表。bind 方法不会立即执行,而是返回一个改变了上下文 this 后的函数。

    fn.bind(x,y,z) 不会执行 fn,而是会返回一个新的函数
    新的函数执行时,会调用 fn,调用形式为 fn.call(x, y, z),其中 x 是 this,y 和z 是其他参数

    function add(a, b){
      return a+b;
    }
    var foo1 = add.bind(add, 1,2); 
    foo1(); //3 只有调用才会执行
    

    在 ES6 的箭头函数下, call 和 apply 将失效。


    请说出至少 10 个 HTTP 状态码,并描述各状态码的意义。

    1. 状态码100表示继续。客户端应继续其请求
    2. 状态码101表示切换协议。服务器根据客户端的请求切换协议
    3. 状态码200表示请求成功。一般用于GET与POST请求
    4. 状态码201表示已创建。成功请求并创建了新的资源
    5. 状态码202表示已接受。已经接受请求,但未处理完成
    6. 状态码203表示非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本
    7. 状态码300表示多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
    8. 状态码303表示查看其它地址
    9. 状态码305表示使用代理。所请求的资源必须通过代理访问
    10. 状态码306表示已经被废弃的HTTP状态码
    11. 状态码400表示客户端请求的语法错误,服务器无法理解
    12. 状态码403表示服务器理解请求客户端的请求,但是拒绝执行此请求
    13. 状态码404表示服务器无法根据客户端的请求找到资源(网页)

    如何实现数组去重?

    假设有数组 array = [1,5,2,3,4,2,3,1,3,4]
    你要写一个函数 unique,使得
    unique(array) 的值为 [1,5,2,3,4]
    也就是把重复的值都去掉,只保留不重复的值。

    使用indexOf

    思路:新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,
    如果有相同的值则跳过,不相同则push进数组。

    let arr = [1, 5, 2, 3, 4, 2, 3, 1, 3, 4]
    function unique(arr) {
     let array = [];
     for (let i = 0; i < arr.length; i++) {    // 首次遍历数组
         if (array.indexOf(arr[i]) === -1) {   // 判断索引有没有等于
             array.push(arr[i])
         }
     }
     return array
    }
    console.log(unique(arr));
    

    缺点:无法对NaN和对象去重

    使用set
    let arr = [1, 5, 2, 3, 4, 2, 3, 1, 3, 4]
    function unique (arr) {
     return Array.from(new Set(arr))
    }
    console.log(unique(arr))
    

    缺点:无法去重对象,且有兼容性问题。API太新,旧浏览器不支持

    使用map
    function unique(arr) {
        const map = new Map()
        const newArr = []
    
        arr.forEach(item => {
            if (!map.has(item)) { // has()用于判断map是否包为item的属性值
                map.set(item, true) // 使用set()将item设置到map中,并设置其属性值为true
                newArr.push(item)
            }
        })
    
        return newArr
    }
    
    console.log(unique(arr))
    

    缺点:API 太新,旧浏览器不支持。


    DOM 事件相关

    什么是事件委托

    由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件。这种方法叫做事件委托。 通俗点讲就是委托一个元素帮我监听我本该监听的元素。

    怎么阻止默认动作

    w3c的方法是e.preventDefault(),IE则是使用e.returnValue = false

    <a href="http://www.cnblogs.com/yycode/" id="testA" >caibaojian.com</a>
     var a = document.getElementById("testA");
     a.onclick =function(e){
         if(e.preventDefault){
             e.preventDefault();//W3C
         }else{
             window.event.returnValue = false;//IE
         }
    }
    
    怎么阻止事件冒泡

    w3c使用e.stopPropagation(),IE使用e.cancelBubble = true

     function stopBubble(e) { 
         if ( e && e.stopPropagation ){ 
             e.stopPropagation(); //W3C
         }else{ 
             window.event.cancelBubble = true; //IE
         }
    }
    

    如何理解 JS 的继承?

    基于原型的继承

    实例化一个新的函数,子类的原型指向了父类的实例,子类就可以调用其父类原型对象上的共有属性。

    function Parent() {
        this.parentName = '父类';
    }
    Parent.prototype.getParentName = function() {
        return this.parentName;
    };
    
    function Child() {
        this.childName = '子类';
    }
    Child.prototype = new Parent();//继承Parent
    Child.prototype.getChildName = function() {
        return this.childName
    };
    
    let c = new Child();
    console.log(c.getParentName()); // '父类'
    
    function Parent(name1){
      this.name1 = name1
    }
    Parent.prototype.pMethod = function(){
      console.log(this.name1)
    }
    
    function Child(name2, name1){
        Parent.call(this, name1) // 得分点
        this.name2 = name2
    }
    Child.prototype.__proto__ = Parent.prototype 
    Child.prototype.cMethod = function(){
        console.log(this.name2)
    }
    

    缺点:子类的实例可以访问父类的私有属性,子类的实例还可以更改该属性,不安全

    基于class的继承

    如需创建类继承,使用 extends 关键字。

    class Parent{
        constructor(name1){
            this.name1 = name1
        }
        pMethod(){
            console.log(this.name1)
        }
    }
    class Child extends Parent{
        constructor(name2, name1){
            super(name1) // 得分点
            this.name2 = name2
        }
        cMethod(){
            console.log(this.name2)
        }
    }
    

    数组排序

    给出正整数数组 array = [2,1,5,3,8,4,9,5]
    请写出一个函数 sort,使得 sort(array) 得到从小到大排好序的数组 [1,2,3,4,5,5,8,9]
    新的数组可以是在 array 自身上改的,也可以是完全新开辟的内存。

    let min = (numbers)=>{
        if(numbers.length>2){
            return min([numbers[0],min(numbers.slice(1))])
        }else {
            return Math.min.apply(null,numbers)
        }
    }//求出最小值的算法
    let minIndex = (numbers) =>
        numbers.indexOf(min(numbers))//标记最小值
    
    let sort = (numbers)=>{
        if(numbers.length>2){
            let index = minIndex(numbers)
            let min = numbers[index]
            numbers.splice(index,1)
            return [min].concat(sort(numbers))
        }else{
            return numbers[0]<numbers[1]? numbers :numbers.reverse()
        }
    }
    let array =[2,1,5,3,8,4,9,5]
    sort(array)
    

    对 Promise 的了解?

    promise的用途

    Promise 用于避免回调地域,让代码看起来更同步

    创建一个 new Promise
    function fn(){
        return new Promise((resolve, reject)=>{
            成功时调用 resolve(data)
            失败时调用 reject(reason)
        })
    }
    
    使用 Promise.prototype.then
    const promise1 = fn() // 得到 promise1 对象
    fn().then(success, fail).then(success2, fail2).catch(fail3)
    或者
    promise1.then(success, fail).then(success2, fail2).catch(fail3)
    均可
    
    使用Promise.all
    Promise.all([promise1, promise2]) 并行,等待所有 promise 成功。
    如果都成功了,则 all 对应的 promise 也成功;如果有一个失败了,则 all 对应的 promise 失败。
    
    使用 Promise.race
    Promise.race([promise1, promise2]),返回一个
    promise,一旦数组中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
    

    说说跨域

    什么是同源

    源:协议+域名+端口号
    Window.origin或者location.origin可以得到当前的源
    两个URL的协议、域名和端口号完全一致那么这两个URL就是同源
    同源策略就是,浏览器规定:如果JS运行在源A里,那么就只能获取源A的数据,不能获取源B的数据,
    即不允许跨域。这是浏览器的功能。浏览器为了主动预防偷数据的问题,设置了严格的同源策略

    什么是跨域

    跨域,是指浏览器不能执行其他网站的脚本。
    它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。
    当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。

    JSONP跨域

    我们在跨域的时候,由于当前浏览器不支持CORS,或者因为某些条件不支持CORS,我们必须使用一种方法来进行跨域。于是我们请求一个JS文件,这个JS文件回执行一个回调,回调里面有我们的数据。回调的名字可以通过随机数生成的,我们把这个随机数以callback的参数传给后台,后台会把函数返回给我们并且执行。
    缺点:由于它是一个script标签,所以读不到AJAX那么精确的status值,无法知道状态码是什么,也只能发送GET请求,JSONP不支持POST

    CORS跨域

    CORS(跨域资源共享)
    跨源资源共享 (CORS)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),使得浏览器允许这些 origin 访问加载自己的资源。浏览器默认不同源之间不能互相访问数据,但是我们想让两个网站互相访问。我们就用CORS,如果要共享数据就需要提前声明。例如,源B要访问源A,源A就要在响应头里声明源B可以访问:
    response.setHeader("Access-Control-Allow-Origin","http://foo.example")

    相关文章

      网友评论

          本文标题:前端常见问题总结

          本文链接:https://www.haomeiwen.com/subject/rzqnwrtx.html