前端工程师常见面试题(前端进阶)前端工程化
Babel 的原理是什么?
参考回答:
babel 的转译过程也分为三个阶段, 这三步具体是:
o 解析 Parse: 将代码解析生成抽象语法树( 即 AST),即词法分析与语法分 析的过程
o 转换 Transform: 对于 AST 进行变换一系列的操作, babel 接受得到 AST 并通过 babel-traverse 对其进行遍历, 在此过程中进行添加 、更新 及移除等操作
o 生成 Generate: 将变换后的 AST 再转换为 JS 代码, 使用到的模块是 babel-generator
如何写一个 babel 插件?
参考回答:
Babel 解析成 AST, 然后插件更改 AST, 最后由 Babel 输出代码
那么 Babel 的插件模块需要你暴露一个 function, function 内返回 visitor module.export = function(babel){ return { visitor:{ } } }
visitor 是对各类型的 AST 节点做处理的地方,那么我们怎么知道 Babel 生成了的 AST 有哪些节点呢?
很简单, 你可以把 Babel 转换的结果打印出来, 或者这里有传送门: AST explorer
这里我们看到 const result = 1 + 2 中的 1 + 1 是一个 BinaryExpression 节点, 那么在 visitor 中, 我们就处理这个节点 var babel = require("babel-core"); var t = require("babel-types"); const visitor = { BinaryExpression(path) { const node = path.node; let result; // 判断表达式两边, 是否都是数字 if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) { // 根据不同的操作符作运算 switch (node.operator) { case "+": result = node.left.value + node.right.value; break case "-": result = node.left.value - node.right.value; break; case "*": result = node.left.value * node.right.value; break; case "/": result = node.left.value / node.right.value; break; case "**": let i = node.right.value; while (--i) { result = result || node.left.value; result = result * node.left.value; } break; default: } } // 如果上面的运算有结果的话 if (result !== undefined) { // 把表达式节点替换成 number 字面量 path.replaceWith(t.numericLiteral(result)); } } }; module.exports = function (babel) { return { visitor }; }
插件写好了, 我们运行下插件试试 const babel = require("babel-core"); const result = babel.transform("const result = 1 + 2;",{ plugins:[ require("./index") ] }); console.log(result.code); // const result = 3;
与预期一致, 那么转换 const result = 1 + 2 + 3 + 4 + 5;呢?
结果是: const result = 3 + 3 + 4 + 5;
这就奇怪了, 为什么只计算了 1 + 2 之后, 就没有继续往下运算了?
我们看一下这个表达式的 AST 树
你会发现 Babel 解析成表达式里面再嵌套表达式。
表达式( 表达式( 表达式( 表达式(1 + 2) + 3) + 4) + 5)
而我们的判断条件并不符合所有的, 只符合 1 + 2
// 判断表达式两边, 是否都是数字
if (t.isNumericLiteral(node.left) && t.isNumericLiteral(node.right)) {}
那么我们得改一改
第一次计算 1 + 2 之后, 我们会得到这样的表达式
表达式( 表达式( 表达式(3 + 3) + 4) + 5)
其中 3 + 3 又符合了我们的条件, 我们通过向上递归的方式遍历父级节点
又转换成这样:
表达式( 表达式(6 + 4) + 5)
表达式(10 + 5)
15
// 如果上面的运算有结果的话
if (result !== undefined) {
// 把表达式节点替换成 number 字面量
path.replaceWith(t.numericLiteral(result));
let parentPath = path.parentPath;
// 向上遍历父级节点
parentPath && visitor.BinaryExpression.call(this, parentPath);
}
到这里, 我们就得出了结果 const result = 15;
那么其他运算呢:
const result = 100 + 10 - 50 >>> const result = 60;
const result = (100 / 2) + 50 >>> const result = 100;
const result = (((100 / 2) + 50 * 2) / 50) ** 2 >>> const result = 9; 你的 git 工作流是怎样的?
参考回答:
GitFlow 是由 Vincent Driessen 提出的一个 git 操作流程标准 。包含如下几个关键分支: master 主分支 develop 主开发分支, 包含确定即将发布的代码
feature 新功能分支,一般一个新功能对应一个分支,对于功能的拆分需要比较合理, 以 避免一些后面不必要的代码冲突
release 发布分支, 发布时候用的分支, 一般测试时候发现的
bug 在这个分支进行修复hotfixhotfix 分支, 紧急修 bug 的时候用
GitFlow 的优势有如下几点:
并行开发: GitFlow 可以很方便的实现并行开发: 每个新功能都会建立一个新
的 feature 分支, 从而和已经完成的功能隔离开来, 而且只有在新功能完成开发 的情况下, 其对应的 feature 分支才会合并到主开发分支上 (也就是我们经常说 的 develop 分支) 。另外,如果你正在开发某个功能, 同时又有一个新的功能需 要开发,你只需要提交当前 feature 的代码,然后创建另外一个 feature 分支并完 成新功能开发 。然后再切回之前的 feature 分支即可继续完成之前功能的开发。
协作开发: GitFlow 还支持多人协同开发, 因为每个 feature 分支上改动的代码
都只是为了让某个新的 feature 可以独立运行 。 同时我们也很容易知道每个人都 在干啥。
发布阶段: 当一个新 feature 开发完成的时候, 它会被合并到 develop 分支, 这
个分支主要用来暂时保存那些还没有发布的内容, 所以如果需要再开发新 的 feature, 我们只需要从 develop 分支创建新分支, 即可包含所有已经完成 的 feature 。
支持紧急修复: GitFlow 还包含了 hotfix 分支 。这种类型的分支是从某个已经发
布的 tag 上创建出来并做一个紧急的修复, 而且这个紧急修复只影响这个已经
发布的 tag, 而不会影响到你正在开发的新 feature。
然后就是 GitFlow 最经典的几张流程图, 一定要理解:
feature 分支都是从 develop 分支创建, 完成后再合并到 develop 分支上, 等待发布。
当需要发布时, 我们从 develop 分支创建一个 release 分支
然后这个 release 分支会发布到测试环境进行测试, 如果发现问题就在这个分支直接进 行修复 。在所有问题修复之前, 我们会不停的重复发布->测试->修复->重新发布->重 新测试这个流程。
发布结束后, 这个 release 分支会合并到 develop 和 master 分支, 从而保证不会有代码 丢失。
master 分支只跟踪已经发布的代码, 合并到 master 上的 commit 只能来自release 分支 和 hotfix 分支。
hotfix 分支的作用是紧急修复一些 Bug。
它们都是从 master 分支上的某个 tag 建立, 修复结束后再合并到 develop 和 master 分 支上。 rebase 与 merge 的区别?
参考回答:
git rebase 和 gitmerge 一样都是用于从一个分支获取并且合并到当前分支.
假设一个场景,就是我们开发的[feature/todo]分支要合并到 master 主分支,那么用 rebase 或 者 merge 有什么不同呢?
o marge 特点: 自动创建一个新的commit 如果合并的时候遇到冲突,仅需 要修改后重新commit
o 优点: 记录了真实的 commit 情况, 包括每个分支的详情
o 缺点: 因为每次 merge 会自动产生一个 mergecommit, 所以在使用一些 git 的 GUI tools, 特别是 commit 比较频繁时, 看到分支很杂乱。
o rebase 特点: 会合并之前的 commit 历史
o 优点: 得到更简洁的项目历史, 去掉了merge commit
o 缺点: 如果合并出现代码问题不容易定位, 因为re-write 了 history
因此,当需要保留详细的合并信息的时候建议使用gitmerge,特别是需要将分支合并进入 master 分支时; 当发现自己修改某个功能时, 频繁进行了 git commit 提交时, 发现其实 过多的提交信息没有必要时, 可以尝试 git rebase. git reset 、git revert 和 git checkout 有什么区别
参考回答:
这个问题同样也需要先了解 git 仓库的三个组成部分: 工作区 ( Working Directory) 、 暂存区 (Stage) 和历史记录区 ( History) 。
o 工作区: 在 git 管理下的正常目录都算是工作区, 我们平时的编辑工作 都是在工作区完成
o 暂存区: 临时区域 。里面存放将要提交文件的快照
o 历史记录区: git commit 后的记录区
三个区的转换关系以及转换所使用的命令:
git reset 、git revert 和 git checkout 的共同点: 用来撤销代码仓库中的某些更改。 然后是不同点:
首先, 从 commit 层面来说:
o gitreset 可以将一个分支的末端指向之前的一个 commit。然后再下次 git 执行垃圾回收的时候,会把这个 commit 之后的 commit 都扔掉。gitreset 还支持三种标记, 用来标记 reset 指令影响的范围:
1 --mixed: 会影响到暂存区和历史记录区 。也是默认选项
1 --soft: 只影响历史记录区
1 --hard: 影响工作区 、暂存区和历史记录区
注意:因为 gitreset 是直接删除 commit 记录,从而会影响到其他开发人员的分 支, 所以不要在公共分支 (比如 develop) 做这个操作。
1 git checkout 可以将 HEAD 移到一个新的分支, 并更新工作目录。 因为可能会覆盖本地的修改, 所以执行这个指令之前, 你需要 stash 或者 commit 暂存区和工作区的更改。
o git revert 和 gitreset 的目的是一样的, 但是做法不同, 它会以创建新的 commit 的方式来撤销 commit,这样能保留之前的 commit 历史,比较安 全 。另外, 同样因为可能会覆盖本地的修改, 所以执行这个指令之前, 你需要 stash 或者 commit 暂存区和工作区的更改。
然后, 从文件层面来说:
o gitreset 只是把文件从历史记录区拿到暂存区,不影响工作区的内容,而 且不支持 --mixed 、--soft 和 --hard。
o git checkout 则是把文件从历史记录拿到工作区, 不影响暂存区的内容。
o git revert 不支持文件层面的操作。 webpack 和 gulp 区别 (模块化与流的区别)
参考回答:
gulp 强调的是前端开发的工作流程,我们可以通过配置一系列的task,定义 task 处理的 事务 (例如文件压缩合并 、雪碧图 、启动server 、版本控制等) , 然后定义执行顺序, 来让 gulp 执行这些task, 从而构建项目的整个前端开发流程。
webpack 是一个前端模块化方案, 更侧重模块打包, 我们可以把开发中的所有资源 (图 片、js 文件、css 文件等) 都看成模块,通过 loader (加载器) 和 plugins (插件) 对资源 进行处理, 打包成符合生产环境部署的前端资源。
人活一世,积好八德,非富即贵!1。口德得饶人处且饶人!直话可以转个弯说冷冰冰的话可以加热了说批评人的话一对一的说,要顾及别人的自尊。人前留一线,日后好相见。2。掌德赞美别人,学会鼓掌每个人都需要来自他人的掌声为
四五十岁的70后,不必合群,慢慢退出以下五个圈子时间如白驹过隙,70后的我们,变成了中老年人了。四十而不惑,五十知天命。别想多了,人生到底要怎样?是运,也是命。小时候以为,圈子里可以捡到宝,到头来,没有本事的人,在哪个圈子里,都
高敏感型人格的12种表现1。害怕冲突。宁可天下人负我,不可我负天下人,即使吵架吵赢了也会很痛苦,因为你会觉得伤害了别人。2。需要独处。给自己独处的时间,允许生命留白。很享受跟他们在一起的时光,但很容易疲倦
人都认为自己是对的人身上的错误,其实就如同屋子里空气中的灰尘,无处不在,平常人是看不见这些灰尘的。只有等阳光透过窗户的缝隙,照射到屋子里以后,人才看见了空气中无处不在的灰尘。灰尘弥漫在空气中,只是没
生活就像一场旅途坐在窗前,看着街上人来人往。这里是一个生活火热而不失出人意表的城市,却又有些诡异冷酷。孤独无奈,惯常流淌在这里的街道上。在这座城市里人与人之间如同陌生人,似乎没有任何关心及理解。然
剩米饭不要扔,跟我这样做,这九种炒饭都好吃!大家好!我是懒惰的小厨娘!剩米饭你们都是怎么处理的呢?我们家之前都是做成蛋炒饭,自从我学会做饭之后,那是变着花样的做各种炒饭,非常的好吃!而且这样就不用担心剩米饭会坏了,喜欢的朋友
这几款好看的机械键盘从外观上就已经赢了,简直就是颜值党的首选对于时常需要使用到键盘的人来说,更好看的键盘可以在使用之下减少长期使用的麻木感,让人心情更加愉悦,不过颜值党不只是看中颜值,其实用性和配置也是必要的,简而言之,我可以不用到这些功能
煮汤圆,冷水下锅还是热水下锅?怎样吃更健康?元宵节快乐!元宵节,是一年中的第一个月圆之夜,习俗之一就是吃点圆食,期盼团团圆圆,圆圆满满。所以,家家户户餐桌上必不可少的就是汤圆或者元宵。这圆滚滚的美食人人都爱吃。不过汤圆在烧煮
上了年纪,晚上起夜与不起夜,哪个更健康?医生说出实情62岁的周大爷最近白天总是没精神,犯困。为什么会如此呢?原来他最近总是多次起床夜尿,平均每晚都要起来35次。本来上了年纪,就容易睡不好,这样一折腾,周大爷睡觉的时间就更少了,连他的
皮蛋到底是人间美味还是健康杀手?听听医生咋说,了解下导语皮蛋的味道口感醇香,入口爽滑,而回味更是绵长状态,深受人们的喜爱与欢迎,不管是直接凉拌着吃,还是做成皮蛋瘦肉粥皮蛋鱼片汤,或者是皮蛋拌豆腐,都是日常生活里非常受人喜爱与欢迎的菜
国羽教父李永波执掌国家队24年,卸任教练后种榴莲,今身材发福头条创作挑战赛在竞技体育项目中,我国有多个项目一直在世界占据着优势地位。除了乒乓球和跳水,就应该是羽毛球了。说到羽毛球的强势崛起,李永波是无法忽略的中心人物。作为运动员,他6夺得世