当前位置 博文首页 > 文章内容

    前端面试题整理

    作者:向日葵的花语 栏目:未分类 时间:2020-10-17 18:00:39

    1.创建代理对象,通过代理对象访问属性时抛出错误 Property "${key}" does not exist


    const man = {
    	name: 'jscoder',
    	age: 22
    }
    
    const pMan = new Proxy(man, {
    	get(target, key){
    		if (key in target) {
    			return target[key]
    		} else {
    			throw new Error(`Property "${key}" does not exist`)
    		}
    	}
    })

    2.红灯3秒亮一次,绿灯1秒亮一次,黄灯2秒亮一次, 如何让三个灯不断交替重复亮灯? (用Promise实现)三个亮灯函数已经存在:


    function red(){
    	console.log('red');
    }
    function green(){
    	console.log('green');
    }
    function yellow(){
    	console.log('yellow');
    }
    
    var light = function(timmer, cb){
    	return new Promise(function(resolve, reject) {
    		setTimeout(function() {
    			cb();
    			resolve();
    		}, timmer);
    	});
    };
    
    var step = function() {
    	Promise.resolve().then(function(){
    		return light(3000, red);
    	}).then(function(){
    		return light(2000, yellow);
    	}).then(function(){
    		return light(1000, green);
    	}).then(function(){
    		step();
    	});
    }
    
    step();

    3.答案解析


    在浏览器环境下打印结果是

    result 2 undefined result 1 undefined

    result 2 中执行的函数getCount()没有执行主体,里面函数的 this 是window,所以打印undefined

    result 1 中执行的方法getCount()前面的执行者是action,而action中没有count熟悉,所以打印结果是 undefined

    4.你觉得 TypeScript 和 JavaScript 有什么区别


    1)TS是JS的超集 TS在JS的基础上添加类型系统以及完全的支持ES6+语法 Angular, Vue.js3.0将直接支持TS TS需要编译,JS基本直接被浏览器解析执行

    2)TypeScript 你都用过哪些类型 基本类型,数组类型,函数类型 元组类型 枚举类型

    3)TypeScript 中type和interface的区别 type 可以声明基本类型别名,联合类型,元组等类型 type 语句中还可以使用 typeof 获取实例的 类型进行赋值 interface 能够声明合并

    5.对 async/await 的理解,分析内部原理


    答:Promise解决了回调地狱的问题,但是如果遇到复杂的业务,代码里面会包含大量的 then 函数,使得代码依然不是太容易阅读。基于这个原因,ES7 引入了 async/await,这是 JavaScript 异步编程的一个重大改进,提供了在不阻塞主线程的情况下使用同步代码实现异步访问资源的能力,并且使得代码逻辑更加清晰,而且还支持 try-catch 来捕获异常,非常符合人的线性思维。

    async/await,这种方式能够彻底告别执行器和生成器,实现更加直观简洁的代码。根据 MDN 定义,async 是一个通过异步执行并隐式返回 Promise 作为结果的函数。可以说async 是Generator函数的语法糖,并对Generator函数进行了改进。

    它的重点是自带了执行器,相当于把我们要额外做的(写执行器/依赖co模块)都封装了在内部。

    6.async/await如果右边方法执行出错该怎么解决


    答:

    方式1:
    async function test() {
    	let res = await 异步()
    }
    
    test().catch()
    方式2:
    async function test() {
    	let res = await 异步().then().catch()
    }

    7.说一下Event Loop的过程?promise 定义时传入的函数什么时候执行?(小米 三面)


    答:

    1)主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。 用两个队列来处理异步任务。 以setTimeout为代表的任务放到被称为macrotask,放到Macrotask queue中, 而以Promise 为代表的任务放到Microtask queue中。 eventloop对这两个队列的处理逻辑也不一样。 执行过程如下: JavaScript引擎首先从macrotask queue中取出第一个任务, 执行完毕后,将microtask queue中的所有任务取出,按顺序全部执行(全部执行不仅指开始执行时队列里的microtask,在这一步执行过程中产生的新的microtask,也要在这里执行) 然后再从macrotask queue中取下一个, 执行完毕后,再次将microtask queue中的全部取出; 循环往复,直到两个queue中的任务都取完。 换句话说,一次eventloop循环会处理一个macrotask和所有这次循环中产生的microtask

    2)Promise定义时传入的函数什么时候执行的? 定义时的函数称为 执行器函数, 它是同步的,会立即执行

    8.说一下防抖函数的应用场景,并简单说下实现方式 (滴滴)


    答: 应用场景:输入框搜索自动补全事件,频繁操作点赞和取消点赞等等 实现方式:

    var timer = null;
    function click(){
        clearTimeout(timer);
        timer = setTimeout(()=>{
            ajax(...);
        },500)
    }

    实现原理:如果在500ms内频繁操作,则每次都会清除一次定时器然后重新创建一个。直到最后一次操作,然后等待500ms后发送ajax

    9.说一下V8的垃圾回收机制 (小米)


    主要思路:

    1. 新生代内存区分为两个等大小空间,使用空间为From,空闲空间为To
    2. 将所有对象存储于From空间(包括活动对象和非活动对象)
    3. 当From空间应用到一定程度后会触发GC机制,标记整理后将活动对象拷贝至To
    4. From完成释放(From和To交换空间)

    10.performance API 中什么指标可以衡量首屏时间


    答:参考地址 https://www.cnblogs.com/longm/p/7382163.html

     https://juejin.im/post/6844904020482457613

    11.说下暂时性死区


    答:暂时性死区是ECMAScript与作用域相关的一个新语义模块, 在ES2015(又叫ES6)中引入

    借鉴:https://sinaad.github.io/xfe/2016/02/26/temporal-dead-zone-tdz-demystified/

    12.观察者和发布订阅的区别


    在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。

    在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。

    观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。

    观察者 模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式

    13.gulp自己写过任务吗?说一下它的构建流程(阿里2018)


    主要思路:

    构建流程指的是gulp是怎样工作的,说白了就是gulp在构建的时候都做了哪些事情

    gulp是基于Node开发环境运行的,所以要先确认好是否有Node开发环境 安装好Node以后,运行npm init创建package.json文件 安装gulp以及你的任务中要使用的依赖 创建并编写gulpfile.js文件 运行程序及打包(这里只是让同学们回顾一下大致过程,面试时可以简单带过,主要是构建流程) gulp的构建有三个核心概念,分别是读取流、转换流和写入流,我们通过读取流把需要转换的文件读取出来,然后通过转换流的转换逻辑,转换成我们想要的结果,再通过写入流去写入到指定的文件位置。

    这样的一个过程就完成了我们日常在构建当中所需要的工作。gulp的官方定义就是基于流的构建系统。gulp希望实现一个构建管道的概念,这样的话,我们在后续去做一些扩展插件的时候就可以有一个很统一的方式。

    14.package-lock.json 有什么作用,如果项目中没有它会怎么样,举例说明


    作用:package-lock.json 是在 npm install时候生成一份文件,用来记录当前状态下实际安装的各个npm package的具体来源和版本号。需要上传到git时,保证大家的依赖包一致。

    解决了package.json缺点:原来package.json文件只能锁定大版本,也就是版本号的第一位,并不能锁定后面的小版本,你每次npm install都是拉取的该大版本下的最新的版本,为了稳定性考虑我们几乎是不敢随意升级依赖包的,这将导致多出来很多工作量,测试/适配等,所以package-lock.json文件出来了,当你每次安装一个依赖的时候就锁定在你安装的这个版本。

    15.webpack 常用配置项,并说明用途 (跟谁学 2020)


    entry:打包的入口文件,一个字符串或者一个对象 output:配置打包的结果,一个对象 fileName:定义输出文件名,一个字符串 path:定义输出文件路径,一个字符串 module:定义对模块的处理逻辑,一个对象 loaders:定义一系列的加载器,一个数组 test:正则表达式,用于匹配到的文件 loader/loaders:字符串或者数组,处理匹配到的文件。如果只需要用到一个模块加载器则使用                  loader:string,如果要使用多个模块加载器,则使用loaders:array include:字符串或者数组,指包含的文件夹 exclude:字符串或者数组,指排除的文件夹 resolve:影响对模块的解析,一个对象 extensions:自动补全识别后缀,是一个数组 plugins:定义插件,一个数组

    16.阐述 webpack css-loader 作用 和 原理? (跟谁学)


     {
          test: /.css$/,
          loader: 'css-loader',
          exclude: /(node_modules|bower_components)/
    }

    css-loader只是帮我们解析了css文件里面的css代码, 默认webpack是只解析js代码的,所以想要应用样式我们要把解析完的css代码拿出来加入到 style标签中。

    实现原理:

    const postcss = require('postcss');
    const Tokenizer = require('css-selector-tokenizer');
    const loaderUtils = require('loader-utils');
    // 插件,用来提取url
    function createPlugin(options) {
      return function(css) {
        const { importItems, urlItems } = options;
        // 捕获导入,如果多个就执行多次
        css.walkAtRules(/^import$/, function(rule) {
          // 拿到每个导入
          const values = Tokenizer.parseValues(rule.params);
          // console.log(JSON.stringify(values));
          // {"type":"values","nodes":[{"type":"value","nodes":[{"type":"string","value":"./base.css","stringType":"'"}]}]}
          // 找到url
          const url = values.nodes[0].nodes[0]; // 第一层的第一个的第一个
          importItems.push(url.value);
        });
        // 遍历规则,拿到图片地址
        css.walkDecls(decl => {
          // 把value 就是 值 7.5px solid red
          // 通过Tokenizer.parseValues,把值变成了树结构
          const values = Tokenizer.parseValues(decl.value);
          values.nodes.forEach(value => {
            value.nodes.forEach(item => {
              /*
                { type: 'url', stringType: "'", url: './bg.jpg', after: ' ' }
                { type: 'item', name: 'center', after: ' ' }
                { type: 'item', name: 'no-repeat' }
              */
              if (item.type === 'url') {
                const url = item.url;
                item.url = `_CSS_URL_${urlItems.length}_`;
                urlItems.push(url); // ['./bg.jpg']
              }
            });
          });
          decl.value = Tokenizer.stringifyValues(values); // 转回字符串
        });
        return css;
      };
    }
    
    // css-loader是用来处理,解析@import "base.css"; url('./assets/logo.jpg')
    module.exports = function loader(source) {
      const callback = this.async();
      // 开始处理
      const options = {
        importItems: [],
        urlItems: []
      };
      // 插件转化,然后把url路径都转化成require('./bg.jpg'); // ...
      const pipeline = postcss([createPlugin(options)]);
      // 1rem 75px
      pipeline
        //   .process("background: url('./bg.jpg') center no-repeat;")
        .process(source)
        .then(result => {
          // 拿到导入路径,拼接
          const importCss = options.importItems
            .map(imp => {
              // stringifyRequest 可以把绝对路径转化成相对路径
              return `require(${loaderUtils.stringifyRequest(this, imp)})`; // 拼接
            })
            .join('\n'); // 拿到一个个import
          let cssString = JSON.stringify(result.css); // 包裹后就是"xxx" 双引号
          cssString = cssString.replace(/@import\s+?["'][^'"]+?["'];/g, '');
          cssString = cssString.replace(/_CSS_URL_(\d+?)_/g, function(
            matched,
            group1
          ) {
            // 索引拿到,然后拿到这个,替换掉原来的_CSS_URL_0_哪些
            const imgURL = options.urlItems[+group1];
            // console.log('图片路径', imgURL);
            // "background: url('"+require('./bg.jpg')+"') center no-repeat;"
            return `"+require('${imgURL}').default+"`;
          }); // url('_CSS_URL_1_')
          // console.log(JSON.stringify(options));
          // console.log(result.css);
          callback(
            null,
            `
            ${importCss}
            module.exports = ${cssString}
          `
          );
        });
    };

    17.webpack中loader和plugin的区别 (字节跳动 搜狐)


    什么是loader

    loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中

    1. 处理一个文件可以使用多个loader,loader的执行顺序和配置中的顺序是相反的,即最后一个loader最先执行,第一个loader最后执行
    2. 第一个执行的loader接收源文件内容作为参数,其它loader接收前一个执行的loader的返回值作为参数,最后执行的loader会返回此模块的JavaScript源码

    什么是plugin

    在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果。

    loader和plugin的区别

    对于loader,它是一个转换器,将A文件进行编译形成B文件,这里操作的是文件,比如将A.scss转换为A.css,单纯的文件转换过程

    plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行广泛的任务

    18.webpack、rollup、parcel优劣?


    webpack

    为处理资源管理和分割代码而生,可以包含任何类型的文件。灵活,插件多。

    rollup

    用标准化的格式(es6)来写代码,通过减少死代码尽可能地缩小包体积。

    parcel

    超快的打包速度,多线程在多核上并发编译,不用任何配置。

    对比

    配置:

    webpack和rollup都需要配config文件,指明entry, output, plugin,transformations。二者的细微区别在于: rollup 有对import/export所做的node polyfills,webpack没有 rollup支持相对路径,而webpack没有,所以得使用path.resolve/path.join。

    parcel则是完全开箱可用的,不用配置。

    入口文件:

    webpack只支持js文件作为入口文件,如果要以其他格式的文件作为入口,比如html文件为入口,如要加第三方Plugin。

    rollup可以用html作为入口文件,但也需要plugin,比如rollup-plugin-html-entry。

    parcel可以用index.html作为入口文件,而且它会通过看index.html的script tag里包含的什么自己找到要打包生成哪些js文件。

    transformations:

    transformations指的是把其他文件转化成js文件的过程,需要经过transformation才能够被打包。

    webpack使用Loaders来处理。

    rollup使用plugins来处理。

    parcel会自动去转换,当找到配置文件比如.babelrc, .postcssrc后就会自动转。

    摇树优化:

    摇树优化是webpack的一大特性。需要1,用import/export语法,2,在package.json中加副作用的入口,3,加上支持去除死代码的缩小器(uglifyjsplugin)。

    rollup会统计引入的代码并排除掉那些没有被用到的。这使您可以在现有工具和模块的基础上构建,而无需添加额外的依赖项或膨胀项目的大小。

    parcel不支持摇树优化。

    dev server:

    webpack用webpack-dev-server。

    rollup用rollup-plugin-serve和rollup-plugin-livereload共同作用。

    parcel内置的有dev server。

    热更新:

    webpack的 wepack-dev-server支持hot模式。

    rollup不支持hmr。

    parcel有内置的hmr。

    代码分割:

    webpack通过在entry中手动设置,使用CommonsChunkPlugin,和模块内的内联函数动态引入来做代码分割。

    rollup有实验性的代码分割特性。它是用es模块在浏览器中的模块加载机制本身来分割代码的。需要把experimentalCodeSplitting 和 experimentalDynamicImport 设为true。

    parcel支持0配置的代码分割。主要是通过动态improt。

    19.babel.config.js 和 .babelrc 有什么区别


    它们是Babel 有两种并行的配置文件格式,可以一起使用,也可以分开使用。

    • babel.config.js 文件是项目范围的配置,加载规则是按目录加载的,是只针对自己的代码。按照 commonjs 导出对象,可以写js的逻辑。具有不同的拓展名(json、js、html)。
    • .babelrc 文件是相对文件的配置,config的配置针对了第三方的组件和自己的代码内容。一般有了babel.config.js 就不会在去执行.babelrc的设置。

    20.webpack 中 tree shaking 的用途和原理是什么?


    答: tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。这个术语和概念实际上是兴起于 ES2015 模块打包工具 rollup。 新的 webpack 4 正式版本,扩展了这个检测能力,通过 package.json 的 "sideEffects" 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 "pure(纯的 ES2015 模块)",由此可以安全地删除文件中未使用的部分。 tree shaking的概念在1990年就提出了,但是直到ES6的ES6-style模块出现以后才真正被利用起来。这是因为tree shaking只能在静态modules下工作。ECMAScript 6 模块加载是静态的,因此整个依赖树可以被静态地推导出解析语法树。所以在ES6中使用tree shaking是非常容易的。而且,tree shaking不仅支持import/export级别,还支持statement(声明)级别。

    21.阐述一下 VUE中 eventbus 的原理 (猿辅导)


    EventBus是消息传递的一种方式,基于一个消息中心,订阅和发布消息的模式,称为发布订阅者模式。

    1. on('name', fn)订阅消息,name:订阅的消息名称, fn: 订阅的消息
    2. emit('name', args)发布消息, name:发布的消息名称 , args:发布的消息
    代码实现
    class Bus {
      constructor () {
        this.callbacks = {}
      }
      $on(name,fn) {
        this.callbacks[name] = this.callbacks[name] || []
        this.callbacks[name].push(fn)
      }
      $emit(name,args) {
        if(this.callbacks[name]){
           //存在遍历所有callback
           this.callbacks[name].forEach(cb => cb(args))
        }
      }
    }

    使用

    const EventBus = new EventBusClass()
    EventBus.on('fn1', function(msg) {
        alert(`订阅的消息是:${msg}`);
    });
    EventBus.emit('fn1', '你好,世界!');

    22.vue-loader 的实现原理是什么?


    答:vue-loader就是将 *.vue 文件变成 *.bundle.js,然后放入浏览器运行。而在这个过程当中,其实调用了三个内部loader(lib/style-compiler、lib/template-compiler和lib/selector)和多个外部loader(babel-loader、vue-style-loader、css-loader等等)。 JS部分:selector(参数type=script) 的处理结果是将 *.vue 中的 script 抽出来之后交给babel-loader去处理,最后生成可用的 JavaScript。 HTML部分:selector (参数type=template) 的处理结果是将 *.vue 中的 template 抽出来之后交给 template-compiler 处理,最终输出成可用的 HTML。 Style部分:selector (参数type=style) 的处理结果是将 *.vue 中的 style 抽出来之后交给 style-compiler 处理成设置好的样式(less、sass、css), 然后交给对应的 loader 处理生成 module, 之后通过 vue-style-loader或者style-loader 将 css 放在 <style> 里面,最后注入到 HTML 中。