美文网首页让前端飞
前端笔试题(工程进阶)

前端笔试题(工程进阶)

作者: 前端辉羽 | 来源:发表于2020-11-30 11:10 被阅读0次

    1.什么是闭包, 如何使用它, 为何要使用它?

    闭包就是能够读取其他函数内部变量的函数。由于在Javascript语言中,
    只有函数内部的子函数才能读取局部变量,
    因此可以把闭包简单理解成“定义在一个函数内部的函数”。

    所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。

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

    2.给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

    如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
    注意你不能在买入股票前卖出股票。
    示例
    输入: [7,1,5,3,6,4]
    输出: 5
    解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
    注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

      function getMaxProfit(prices) {
                // 最小的价格
                let minPrice = Number.MAX_SAFE_INTEGER
                // 最大的利益
                let maxProfile = 0;
                for (let i = 0; i < prices.length; i++) {
                    if (prices[i] <= minPrice) {
                        minPrice = Math.min(minPrice, prices[i])//更新最小价格
                    } else {
                        // 更新最大收益
                        maxProfile = Math.max(prices[i] - minPrice, maxProfile)
                    }
                }
                return maxProfile;
    }
    

    3.JavaScript不支持函数重载,但TypeScript是否支持函数重载?

    是的,TypeScript支持函数重载。但是它的实现很奇怪,当我们在TypeScript中执行函数重载时,我们只能实现一个带有多个签名的函数。

    //带有字符串类型参数的函数  
    function add(a:string, b:string): string;    
    //带有数字类型参数的函数
    function add(a:number, b:number): number;    
    //函数定义
    function add(a: any, b:any): any {    
        return a + b;    
    }  
    

    在上面的例子中,前两行是函数重载声明。它有两次重载,第一个签名的参数类型为string,而第二个签名的参数类型为number。第三个函数包含实际实现并具有any类型的参数。任何数据类型都可以接受任何类型的数据。然后,实现检查所提供参数的类型,并根据供应商参数类型执行不同的代码段。

    4.在前端工程化涌现出众多工具, 试说明webpack与grunt、gulp的不同?

    三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。
    gruntgulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。
    webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。
    所以总结一下:
    从构建思路来说
    gulp和grunt需要开发者将整个前端构建过程拆分成多个Task,并合理控制所有Task的调用关系 webpack需要开发者找到入口,并需要清楚对于不同的资源应该使用什么Loader做何种解析和加工复制代码
    对于知识背景来说
    gulp更像后端开发者的思路,需要对于整个流程了如指掌 webpack更倾向于前端开发者的思路

    5.npm打包时需要注意哪些?如何利用webpack来更好的构建?

    Npm是目前最大的 JavaScript 模块仓库,里面有来自全世界开发者上传的可复用模块。你可能只是JS模块的使用者,但是有些情况你也会去选择上传自己开发的模块。 关于NPM模块上传的方法可以去官网上进行学习,这里只讲解如何利用webpack来构建。

    NPM模块需要注意以下问题:

    1. 要支持CommonJS模块化规范,所以要求打包后的最后结果也遵守该规则。
    2. Npm模块使用者的环境是不确定的,很有可能并不支持ES6,所以打包的最后结果应该是采用ES5编写的。并且如果ES5是经过转换的,请最好连同SourceMap一同上传。
    3. Npm包大小应该是尽量小(有些仓库会限制包大小)
    4. 发布的模块不能将依赖的模块也一同打包,应该让用户选择性的去自行安装。这样可以避免模块应用者再次打包时出现底层模块被重复打包的情况。
    5. UI组件类的模块应该将依赖的其它资源文件,例如.css文件也需要包含在发布的模块里。

    基于以上需要注意的问题,我们可以对于webpack配置做以下扩展和优化:

    1.CommonJS模块化规范的解决方案: 设置output.libraryTarget='commonjs2'使输出的代码符合CommonJS2 模块化规范,以供给其它模块导入使用
    输出ES5代码的解决方案:使用babel-loader把 ES6 代码转换成 2.ES5 的代码。再通过开启devtool: 'source-map'输出SourceMap以发布调试。
    3.Npm包大小尽量小的解决方案:Babel 在把 ES6 代码转换成 ES5 代码时会注入一些辅助函数,最终导致每个输出的文件中都包含这段辅助函数的代码,造成了代码的冗余。解决方法是修改.babelrc文件,为其加入transform-runtime插件
    4.不能将依赖模块打包到NPM模块中的解决方案:使用externals配置项来告诉webpack哪些模块不需要打包。
    5.对于依赖的资源文件打包的解决方案:通过css-loader和extract-text-webpack-plugin来实现,配置如下:

    const ExtractTextPlugin = require('extract-text-webpack-plugin');
    module.exports = {
      module: {
        rules: [
          {
            // 增加对 CSS 文件的支持
            test: /\.css/,
            // 提取出 Chunk 中的 CSS 代码到单独的文件中
            use: ExtractTextPlugin.extract({
              use: ['css-loader']
            }),
          },
        ]
      },
      plugins: [
        new ExtractTextPlugin({
          // 输出的 CSS 文件名称
          filename: 'index.css',
        }),
      ],
    };
    

    6.说一下在webpack中的loader和plugin的不同

    不同的作用

    • Loader直译为"加载器"。Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。
    • Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

    不同的用法

    • Loader在module.rules中配置,也就是说他作为模块的解析规则而存在。 类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),使用什么加载(loader)和使用的参数(options)
    • Plugin在plugins中单独配置。 类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入。

    7.webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全

    Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:

    初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
    开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
    确定入口:根据配置中的 entry 找出所有的入口文件;
    编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
    完成模块编译:在经过第4步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
    输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
    输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
    在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果

    8.Vue中的双向数据绑定是如何实现的

    Vue的双向数据绑定是通过数据劫持结合发布者订阅者模式来实现的 要实现这种双向数据绑定,必要的条件有:

    1、实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者 2、实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数 3、实现一个Watcher,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图 4、MVVM入口函数,整合以上三者

    个人理解:
    在new Vue的时候,在Observer中通过Object.defineProperty()达到数据劫持,代理所有数据的getter和setter属性,在每次触发setter的时候,都会通过Dep来通知Watcher,Watcher作为Observer数据监听器与Compile模板解析器之间的桥梁,当Observer监听到数据发生改变的时候,通过Updater来通知Compile更新视图** **而Compile通过Watcher订阅对应数据,绑定更新函数,通过Dep来添加订阅者,达到双向绑定。

    9.写 React/Vue 项目时为什么要在组件中写 key,其作用是什么?

    key 的作用是为了在 diff 算法执行时更快的找到对应的节点,提高 diff 速度。
    vue 和 react 都是采用 diff 算法来对比新旧虚拟节点,从而更新节点。在 vue 的 diff 函数中。可以先了解一下 diff 算法。
    在交叉对比的时候,当新节点跟旧节点头尾交叉对比没有结果的时候,会根据新节点的 key 去对比旧节点数组中的 key,从而找到相应旧节点(这里对应的是一个 key => index 的 map 映射)。如果没找到就认为是一个新增节点。而如果没有 key,那么就会采用一种遍历查找的方式去找到对应的旧节点。一种一个 map 映射,另一种是遍历查找。相比而言。map 映射的速度更快。

    相关文章

      网友评论

        本文标题:前端笔试题(工程进阶)

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