前端面试题整理
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的垃圾回收机制 (小米)
主要思路:
- 新生代内存区分为两个等大小空间,使用空间为From,空闲空间为To
- 将所有对象存储于From空间(包括活动对象和非活动对象)
- 当From空间应用到一定程度后会触发GC机制,标记整理后将活动对象拷贝至To
- 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')