• 2022我的前端面试总结


    Webpack Proxy工作原理?为什么能解决跨域

    1. 是什么

    webpack proxy,即webpack提供的代理服务

    基本行为就是接收客户端发送的请求后转发给其他服务器

    其目的是为了便于开发者在开发模式下解决跨域问题(浏览器安全策略限制)

    想要实现代理首先需要一个中间服务器,webpack中提供服务器的工具为webpack-dev-server

    2. webpack-dev-server

    webpack-dev-serverwebpack 官方推出的一款开发工具,将自动编译和自动刷新浏览器等一系列对开发友好的功能全部集成在了一起

    目的是为了提高开发者日常的开发效率,「只适用在开发阶段」

    关于配置方面,在webpack配置对象属性中通过devServer属性提供,如下:

    // ./webpack.config.js
    const path = require('path')
    
    module.exports = {
        // ...
        devServer: {
            contentBase: path.join(__dirname, 'dist'),
            compress: true,
            port: 9000,
            proxy: {
                '/api': {
                    target: 'https://api.github.com'
                }
            }
            // ...
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    devServetr里面proxy则是关于代理的配置,该属性为对象的形式,对象中每一个属性就是一个代理的规则匹配

    属性的名称是需要被代理的请求路径前缀,一般为了辨别都会设置前缀为/api,值为对应的代理匹配规则,对应如下:

    • target:表示的是代理到的目标地址
    • pathRewrite:默认情况下,我们的 /api-hy 也会被写入到URL中,如果希望删除,可以使用pathRewrite
    • secure:默认情况下不接收转发到https的服务器上,如果希望支持,可以设置为false
    • changeOrigin:它表示是否更新代理后请求的 headershost地址

    2. 工作原理

    proxy工作原理实质上是利用http-proxy-middleware 这个http代理中间件,实现请求转发给其他服务器

    举个例子:

    在开发阶段,本地地址为http://localhost:3000,该浏览器发送一个前缀带有/api标识的请求到服务端获取数据,但响应这个请求的服务器只是将请求转发到另一台服务器中

    const express = require('express');
    const proxy = require('http-proxy-middleware');
    
    const app = express();
    
    app.use('/api', proxy({target: 'http://www.example.org', changeOrigin: true}));
    app.listen(3000);
    
    // http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3. 跨域

    在开发阶段, webpack-dev-server 会启动一个本地开发服务器,所以我们的应用在开发阶段是独立运行在 localhost的一个端口上,而后端服务又是运行在另外一个地址上

    所以在开发阶段中,由于浏览器同源策略的原因,当本地访问后端就会出现跨域请求的问题

    通过设置webpack proxy实现代理请求后,相当于浏览器与服务端中添加一个代理者

    当本地发送请求的时候,代理服务器响应该请求,并将请求转发到目标服务器,目标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地

    在代理服务器传递数据给本地浏览器的过程中,两者同源,并不存在跨域行为,这时候浏览器就能正常接收数据

    注意:「服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器安全策略限制」

    代码输出结果

    var obj = {
       say: function() {
         var f1 = () =>  {
           console.log("1111", this);
         }
         f1();
       },
       pro: {
         getPro:() =>  {
            console.log(this);
         }
       }
    }
    var o = obj.say;
    o();
    obj.say();
    obj.pro.getPro();
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    输出结果:

    1111 window对象
    1111 obj对象
    window对象
    
    
    • 1
    • 2
    • 3
    • 4

    解析:

    1. o(),o是在全局执行的,而f1是箭头函数,它是没有绑定this的,它的this指向其父级的this,其父级say方法的this指向的是全局作用域,所以会打印出window;
    2. obj.say(),谁调用say,say 的this就指向谁,所以此时this指向的是obj对象;
    3. obj.pro.getPro(),我们知道,箭头函数时不绑定this的,getPro处于pro中,而对象不构成单独的作用域,所以箭头的函数的this就指向了全局作用域window。

    闭包的应用场景

    • 柯里化 bind
    • 模块

    网络劫持有哪几种,如何防范?

    ⽹络劫持分为两种:

    (1)DNS劫持: (输⼊京东被强制跳转到淘宝这就属于dns劫持)

    • DNS强制解析: 通过修改运营商的本地DNS记录,来引导⽤户流量到缓存服务器
    • 302跳转的⽅式: 通过监控⽹络出⼝的流量,分析判断哪些内容是可以进⾏劫持处理的,再对劫持的内存发起302跳转的回复,引导⽤户获取内容

    (2)HTTP劫持: (访问⾕歌但是⼀直有贪玩蓝⽉的⼴告),由于http明⽂传输,运营商会修改你的http响应内容(即加⼴告)

    DNS劫持由于涉嫌违法,已经被监管起来,现在很少会有DNS劫持,⽽http劫持依然⾮常盛⾏,最有效的办法就是全站HTTPS,将HTTP加密,这使得运营商⽆法获取明⽂,就⽆法劫持你的响应内容。

    实现一个 add 方法

    题目描述:实现一个 add 方法 使计算结果能够满足如下预期:
    add(1)(2)(3)()=6
    add(1,2,3)(4)()=10

    其实就是考函数柯里化

    实现代码如下:

    function add(...args) {
      let allArgs = [...args];
      function fn(...newArgs) {
        allArgs = [...allArgs, ...newArgs];
        return fn;
      }
      fn.toString = function () {
        if (!allArgs.length) {
          return;
        }
        return allArgs.reduce((sum, cur) => sum + cur);
      };
      return fn;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    浅拷贝

    // 这里只考虑对象类型
    function shallowClone(obj) {
        if(!isObject(obj)) return obj;
        let newObj = Array.isArray(obj) ? [] : {};
        // for...in 只会遍历对象自身的和继承的可枚举的属性(不含 Symbol 属性)
        for(let key in obj) {
            // obj.hasOwnProperty() 方法只考虑对象自身的属性
            if(obj.hasOwnProperty(key)) {
                newObj[key] = obj[key];
            }
        }
        return newObj;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    参考:前端进阶面试题详细解答

    AJAX

    const getJSON = function(url) {
        return new Promise((resolve, reject) => {
            const xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
            xhr.open('GET', url, false);
            xhr.setRequestHeader('Accept', 'application/json');
            xhr.onreadystatechange = function() {
                if (xhr.readyState !== 4) return;
                if (xhr.status === 200 || xhr.status === 304) {
                    resolve(xhr.responseText);
                } else {
                    reject(new Error(xhr.responseText));
                }
            }
            xhr.send();
        })
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    实现数组原型方法

    forEach

    Array.prototype.forEach2 = function(callback, thisArg) {
        if (this == null) {
            throw new TypeError('this is null or not defined')
        }
        if (typeof callback !== "function") {
            throw new TypeError(callback + ' is not a function')
        }
        const O = Object(this)  // this 就是当前的数组
        const len = O.length >>> 0  // 后面有解释
        let k = 0
        while (k < len) {
            if (k in O) {
                callback.call(thisArg, O[k], k, O);
            }
            k++;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    O.length >>> 0 是什么操作?就是无符号右移 0 位,那有什么意义嘛?就是为了保证转换后的值为正整数。其实底层做了 2 层转换,第一是非 number 转成 number 类型,第二是将 number 转成 Uint32 类型

    map

    基于 forEach 的实现能够很容易写出 map 的实现:

    - Array.prototype.forEach2 = function(callback, thisArg) {
    + Array.prototype.map2 = function(callback, thisArg) {
        if (this == null) {
            throw new TypeError('this is null or not defined')
        }
        if (typeof callback !== "function") {
            throw new TypeError(callback + ' is not a function')
        }
        const O = Object(this)
        const len = O.length >>> 0
    -   let k = 0
    +   let k = 0, res = []
        while (k < len) {
            if (k in O) {
    -           callback.call(thisArg, O[k], k, O);
    +           res[k] = callback.call(thisArg, O[k], k, O);
            }
            k++;
        }
    +   return res
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    filter

    同样,基于 forEach 的实现能够很容易写出 filter 的实现:

    - Array.prototype.forEach2 = function(callback, thisArg) {
    + Array.prototype.filter2 = function(callback, thisArg) {
        if (this == null) {
            throw new TypeError('this is null or not defined')
        }
        if (typeof callback !== "function") {
            throw new TypeError(callback + ' is not a function')
        }
        const O = Object(this)
        const len = O.length >>> 0
    -   let k = 0
    +   let k = 0, res = []
        while (k < len) {
            if (k in O) {
    -           callback.call(thisArg, O[k], k, O);
    +           if (callback.call(thisArg, O[k], k, O)) {
    +               res.push(O[k])                
    +           }
            }
            k++;
        }
    +   return res
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    some

    同样,基于 forEach 的实现能够很容易写出 some 的实现:

    - Array.prototype.forEach2 = function(callback, thisArg) {
    + Array.prototype.some2 = function(callback, thisArg) {
        if (this == null) {
            throw new TypeError('this is null or not defined')
        }
        if (typeof callback !== "function") {
            throw new TypeError(callback + ' is not a function')
        }
        const O = Object(this)
        const len = O.length >>> 0
        let k = 0
        while (k < len) {
            if (k in O) {
    -           callback.call(thisArg, O[k], k, O);
    +           if (callback.call(thisArg, O[k], k, O)) {
    +               return true
    +           }
            }
            k++;
        }
    +   return false
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    reduce

    Array.prototype.reduce2 = function(callback, initialValue) {
        if (this == null) {
            throw new TypeError('this is null or not defined')
        }
        if (typeof callback !== "function") {
            throw new TypeError(callback + ' is not a function')
        }
        const O = Object(this)
        const len = O.length >>> 0
        let k = 0, acc
    
        if (arguments.length > 1) {
            acc = initialValue
        } else {
            // 没传入初始值的时候,取数组中第一个非 empty 的值为初始值
            while (k < len && !(k in O)) {
                k++
            }
            if (k > len) {
                throw new TypeError( 'Reduce of empty array with no initial value' );
            }
            acc = O[k++]
        }
        while (k < len) {
            if (k in O) {
                acc = callback(acc, O[k], k, O)
            }
            k++
        }
        return acc
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    浏览器渲染优化

    (1)针对JavaScript: JavaScript既会阻塞HTML的解析,也会阻塞CSS的解析。因此我们可以对JavaScript的加载方式进行改变,来进行优化:

    (1)尽量将JavaScript文件放在body的最后

    (2) body中间尽量不要写