1. 能说出来两种对于 JavaScript 工程师很重要的编程范式么?前端
JavaScript 是一门多范式(multi-paradigm)的编程语言,它既支持命令式(imperative)/面向过程(procedural)编程,也支持面向对象编程(OOP,Object-Oriented Programming),还支持函数式编程(functional programming)。JavaScript 所支持的面向对象编程包括原型继承(prototypal inheritance)。面试
面试加分项算法
原型继承(即:原型,OLOO——连接到其它对象的对象);
函数式编程(即:闭包(closure),一类函数(first class functions),lambda 函数:箭头函数)。
面试减分项编程
连范式都不知道,更别提什么原型 OO(prototypal oo)或者函数式编程了。设计模式
深刻了解数组
The Two Pillars of JavaScript Part 1:JS 两大支柱之一:原型 OO
The Two Pillars of JavaScript Part 2:JS 两大支柱之二:函数式编程安全
2. 什么是函数式编程?网络
函数式编程,是将数学函数组合起来,而且避免了状态共享(shared state)及可变数据(mutable data),由此而产生的编程语言。发明于 1958 年的 Lisp 就是首批支持函数式编程的语言之一,而 λ 演算(lambda calculus)则能够说是孕育了这门语言。即便在今天,Lisp 这个家族的编程语言应用范围依然很广。闭包
函数式编程但是 JavaScript 语言中很是重要的一个概念(它但是 JavaScript 的两大支柱之一)。ES5 规范中就增长了不少经常使用的函数式工具。架构
面试加分项
纯函数(pure functions)/函数的纯粹性(function purity)
知道如何避免反作用(side-effects)
简单函数的组合
函数式编程语言:Lisp,ML,Haskell,Erlang,Clojure,Elm,F#,OCaml,等等
提到了 JavaScript 语言中支持函数式编程(FP)的特性:一类函数,高阶函数(higher order functions),做为参数(arguments)/值(values)的函数
面试减分项
没有提到纯函数,以及如何避免反作用
没有提供函数式编程语言的例子
没有说是 JavaScript 中的哪些特性使得函数式编程得以实现
深刻了解
The Two Pillars of JavaScript Part 2:JS 两大支柱之二:函数式编程
The Dao of Immutability
Composing Software
The Haskell School of Music
3. 类继承和原型继承有什么区别?
类继承(Class Inheritance):实例(instances)由类继承而来(类和实例的关系,能够类比为建筑图纸和实际建筑 🏠 的关系),同时还会建立父类—子类这样一种关系,也叫作类的分层分类(hierarchical class taxonomies)。一般是用 new 关键字调用类的构造函数(constructor functions)来建立实例的。不过在 ES6 中,要继承一个类,不用 class 关键字也能够。
原型继承(Prototypal Inheritance):实例/对象直接从其它对象继承而来,建立实例的话,每每用工厂函数(factory functions)或者 Object.create() 方法。实例能够从多个不一样的对象组合而来,这样就能选择性地继承了。
在 JavaScript 中,原型继承比类继承更简单,也更灵活。
面试加分项
类:会建立紧密的耦合,或者说层级结构(hierarchies)/分类(taxonomies)。
原型:提到了衔接继承(concatenative inheritance)、原型委托( prototype delegation)、函数继承(functional inheritance),以及对象组合(object composition)。
面试减分项
原型继承和组合,与类继承相比,不知道哪一个更好。
深刻了解
The Two Pillars of JavaScript Part 1:JS 两大支柱之一:原型 OO
Common Misconceptions About Inheritance in JavaScript:对于 JavaScript 中继承这个概念,所广泛存在的误解
4. 函数式编程和面向对象编程,各有什么优势和不足呢?
面向对象编程的优势:关于“对象”的一些基础概念理解起来比较容易,方法调用的含义也好解释。面向对象编程一般使用命令式的编码风格,声明式(declarative style)的用得比较少。这样的代码读起来,像是一组直接的、计算机很容易就能遵循的指令。
面向对象编程的不足:面向对象编程每每须要共享状态。对象及其行为经常会添加到同一个实体上,这样一来,若是一堆函数都要访问这个实体,并且这些函数的执行顺序不肯定的话,极可能就会出乱子了,好比竞争条件(race conditions)这种现象(函数 A 依赖于实体的某个属性,可是在 A 访问属性以前,属性已经被函数 B 修改了,那么函数 A 在使用属性的时候,极可能就得不到预期的结果)。
函数式编程的优势:用函数式范式来编程,就不须要担忧共享状态或者反作用了。这样就避免了几个函数在调用同一批资源时可能产生的 bug 了。拥有了“无参风格”(point-free style,也叫隐式编程)之类的特性以后,函数式编程就大大简化了,咱们也能够用函数式编程的方式来把代码组合成复用性更强的代码了,面向对象编程可作不到这一点。
函数式编程更偏心声明式、符号式(denotational style)的编码风格,这样的代码,并非那种为了实现某种目的而须要循序渐进地执行的一大堆指令,而是关注宏观上要作什么。至于具体应该怎么作,就都隐藏在函数内部了。这样一来,要是想重构代码、优化性能,那就大有可为了。(译者注:以作一道菜为例,就是由 买菜 -> 洗菜 -> 炒菜 这三步组成,每一步都是函数式编程的一个函数,无论作什么菜,这个流程都是不会变的。而想要优化这个过程,天然就是要深刻每一步之中了。这样无论内部如何重构、优化,总体的流程并不会变,这就是函数式编程的好处。)甚至能够把一种算法换成另外一种更高效的算法,同时还基本不须要修改代码(好比把及早求值策略(eager evaluation)替换为惰性求值策略(lazy evaluation))。
利用纯函数进行的计算,能够很方便地扩展到多处理器环境下,或者应用到分布式计算集群上,同时还不用担忧线程资源冲突、竞争条件之类的问题。
函数式编程的不足:代码若是过分利用了函数式的编程特性(如无参风格、大量方法的组合),就会影响其可读性,从而简洁度有余、易读性不足。
大部分工程师仍是更熟悉面向对象编程、命令式编程,对于刚接触函数式编程的人来讲,即便只是这个领域的一些的简单术语,均可能让他怀疑人生。
函数式编程的学习曲线更陡峭,由于面向对象编程太普及了,学习资料太多了。相比而言,函数式编程在学术领域的应用更普遍一些,在工业界的应用稍逊一筹,天然也就不那么“平易近人”了。在探讨函数式编程时,人们每每用 λ 演算、代数、范畴学等学科的专业术语和专业符号来描述相关的概念,那么其余人想要入门函数式编程的话,就得先把这些领域的基础知识搞明白,能不让人头大么。
面试加分项
共享状态的缺点、资源竞争、等等(面向对象编程)
函数式编程可以极大地简化应用开发
面向对象编程和函数式编程学习曲线的不一样
两种编程方式各自的不足之处,以及对代码后期维护带来的影响
函数式风格的代码库,学习曲线会很陡峭
面向对象编程风格的代码库,修改起来很难,很容易出问题(和水平至关的函数式风格的代码相比)
不可变性(immutability),可以极大地提高程序历史状态(program state history)的可见性(accessible)和扩展性(malleable),这样一来,想要添加诸如无限撤销/重作、倒带/回放、可后退的调试之类的功能的话,就简单多了。无论是面向对象编程仍是函数式编程,这两种范式都能实现不可变性,可是要用面向对象来实现的话,共享状态对象的数量就会剧增,代码也会变得复杂不少。
面试减分项
没有讲这两种编程范式的缺点——若是熟悉至少其中一种范式的话,应该可以说出不少这种范式的缺点吧。
深刻了解
老是你俩,看来你俩真是很是重要啊。
The Two Pillars of JavaScript Part 1:JS 两大支柱之一:原型 OO
The Two Pillars of JavaScript Part 2:JS 两大支柱之二:函数式编程
5. 何时该用类继承?
千万别用类继承!或者说尽可能别用。若是非要用,就只用它继承一级(one level)就行了,多级的类继承简直就是反模式的。这个话题(不太明白是关于什么的……)我也参与讨论过好些年了,仅有的一些回答最终也沦为 常见的误解 之一。更多的时候,这个话题讨论着讨论着就没动静了。
若是一个特性有时候颇有用 但有时候又很危险 而且还有另外一种更好的特性能够用 那务必要用另外一种更好的特性~ Douglas Crockford
面试加分项
尽可能别用,甚至是完全不用类继承。
有时候只继承一级的话也仍是 OK 的,好比从框架的基类继承,例如 React.Component。
相比类继承,对象组合(object composition)更好一些。
深刻了解
The Two Pillars of JavaScript Part 1:JS 两大支柱之一:原型 OO
JS Objects — Inherited a Mess:JS 对象(继承)——只是继承了混乱(mess)而已
6. 何时该用原型继承?
原型继承能够分为下面几类:
委托(delegation,也就是原型链)
组合(concatenative,好比混用(mixins)、 Object.assign())
函数式(functional,这个函数式原型继承不是函数式编程。这里的函数是用来建立一个闭包,以实现私有状态(private state)或者封装(encapsulation))
上面这三种原型继承都有各自的适用场景,不过它们都颇有用,由于都能实现组合继承(composition),也就是创建了 A 拥有特性 B(has-a)、A 用到了特性 B(uses-a) 或者 A 能够实现特性 B(can-do) 的这样一种关系。相比而言,类继承创建的是 A 就是 B 这样一种关系。
面试加分项
知道在什么状况下不适合用模块化(modules)或者函数式编程。
知道须要组合多个不一样来源的对象时,应该怎么作。
知道何时该用继承。
面试减分项
不知道何时应该用原型。
不知道混用和 Object.assign()。
深刻了解
Programming JavaScript Applications:文章中的“原型”这一节
7. 为何说“对象组合比类继承更好”?
这句话引用的是《设计花纹》(Design Patterns,设计模式)这本书的内容。意思是要想实现代码重用,就应该把一堆小的功能单元组合成知足需求的各类对象,而不是经过类继承弄出来一层一层的对象。
换句话说,就是尽可能编程实现 can-do、has-a 或者 uses-a 这种关系,而不是 is-a 这种关系。
面试加分项
避免使用类继承。
避免使用问题多多的基类。
避免紧耦合。
避免极其不灵活的层次分类(taxonomy)(类继承所产生的 is-a 关系可能会致使不少误用的状况)
避免大猩猩香蕉问题(“你只是想要一根香蕉,结果最后却整出来一只拿着香蕉的大猩猩,还有整个丛林”)。
要让代码更具扩展性。
面试减分项
没有提到上面任何一种问题。
没有表达清楚对象组合与类继承有什么区别,也没有提到对象组合的优势。
深刻了解
Composition over Inheritance
Introducing the Stamp Specification
8. 双向数据绑定/单向数据流的含义和区别
双向数据绑定(two-way data binding),意味着 UI 层所呈现的内容和 Model 层的数据动态地绑定在一块儿了,其中一个发生了变化,就会马上反映在另外一个上。好比用户在前端页面的表单控件中输入了一个值,Model 层对应该控件的变量就会马上更新为用户所输入的值;反之亦然,若是 Modal 层的数据有变化,变化后的数据也会马上反映至 UI 层。
单向数据流(one-way data flow), 意味着只有 Model 层才是单一数据源(single source of truth)。UI 层的变化会触发对应的消息机制,告知 Model 层用户的目的(对应 React 的 store)。只有 Model 层才有更改应用状态的权限,这样一来,数据永远都是单向流动的,也就更容易了解应用的状态是如何变化的。
采用单向数据流的应用,其状态的变化是很容易跟踪的,采用双向数据绑定的应用,就很难跟踪并理解状态的变化了。
面试加分项
React 是单向数据流的典型,面试时提到这个框架的话会加分。Cycle.js 则是另外一个很流行的单向数据流的库。
Angular 则是双向数据绑定的典型。
面试减分项
不理解单向数据流/双向数据绑定的含义,也说不清楚二者之间的区别。
深刻了解
Introduction to React.js
9. 单体架构和微服务架构各有何优劣?
采用单体架构(monolithic architecture)的应用,各组件的代码是做为一个总体存在的,组件之间互相合做,共享内存和资源。
而微服务架构(microservice architecture)则是由许许多多个互相独立的小应用组成,每一个应用都有本身的内存空间,应用在扩容时也是独立于其它应用进行的。
单体架构的优点:大部分应用都有至关数量的横切关注点(cross-cutting concerns),好比日志记录,流量限制,还有审计跟踪和 DOS 防御等安全方面的需求,单体架构在这方面就颇有优点。
当全部功能都运行在一个应用里的时候,就能够很方便地将组件与横切关注点相关联。
单体架构也有性能上的优点,毕竟访问共享内存仍是比进程间通讯(inter-process communication,IPC)要快的。
单体架构的劣势:随着单体架构应用功能的不断开发,各项服务之间的耦合程度也会不断增长,这样一来就很难把各项服务分离开来了,要作独立扩容或者代码维护也就更不方便了。
微服务的优点:微服务架构通常都有更好的组织结构,由于每项服务都有本身特定的分工,并且也不会干涉其它组件所负责的部分。服务解耦以后,想要从新组合、配置来为各个不一样的应用提供服务的话,也更方便了(好比同时为 Web 客户端和公共 API 提供服务)。
若是用合理的架构来部署微服务的话,它在性能上也是颇有优点的,由于这样一来,就能够很轻松地分离热门服务,对其进行扩容,同时还不会影响到应用中的其它部分。
微服务的劣势:在实际构建一个新的微服务架构的时候,会遇到不少在设计阶段没有预料到的横切关注点。若是是单体架构应用的话就很简单,新建一个中间件(shared magic helpers 不知道怎么翻译……)来解决这样的问题就好了,没什么麻烦的。
可是在微服务架构中就不同了,要解决这个问题,要么为每一个横切关注点都引入一个独立的模块,要么就把全部横切关注点的解决方案封装到一个服务层中,让全部流量都从这里走一遍就好了。
为了解决横切关注点的问题,虽然单体架构也趋向于把全部的路由流量都从一个外部服务层走一遍,可是在这种架构中,能够等到项目很是成熟以后再进行这种改造,这样就能够把还这笔技术债的时间尽可能日后拖一拖。
微服务通常都是部署在虚拟机或容器上的,随着应用规模的不断增长,虚拟机抢工做(VM wrangling work)的状况也会迅速增长。任务的分配通常都是经过容器群(container fleet)管理工具来自动实现的。
面试加分项
对于微服务的积极态度,虽然初始成本会比单体架构要高一些。知道微服务的性能和扩容在长期看来表现更佳。
在微服务架构和单体架构应用上都有实战经验。可以使应用中的各项服务在代码层面互相独立,可是又能够在开发初期迅速地将各项服务打包成一整个的单体架构应用。微服务化的改造能够在应用至关成熟以后,改形成本在可承受范围内的时候再进行。
面试减分项
不知道单体架构和微服务架构的区别。
不知道微服务架构额外的开销,或者没有实际经验。
不知道微服务架构中,IPC 和网络通讯所致使的额外的性能开销。
过度贬低微服务。说不清楚何时应该把单体架构应用解耦成微服务。
低估了可独立扩容的微服务的优点。
10. 异步编程是什么?又为何在 JavaScript 中这么重要?
在同步编程中,代码会按顺序自顶向下依次执行(条件语句和函数调用除外),若是遇到网络请求或者磁盘读/写(I/O)这类耗时的任务,就会堵塞在这样的地方。
在异步编程中,JS 运行在事件循环(event loop)中。当须要执行一个阻塞操做(blocking operation)时,主线程发起一个(异步)请求,(工做线程就会去执行这个异步操做,)同时主线程继续执行后面的代码。(工做线程执行完毕以后,)就会发起响应,触发中断(interrupt),执行事件处理程序(event handler),执行完后主线程继续日后走。这样一来,一个程序线程就能够处理大量的并发操做了。
用户界面(user interface,UI)自然就是异步的,大部分时间它都在等待用户输入,从而中断事件循环,触发事件处理程序。
Node.js 默认是异步的,采用它构建的服务端和用户界面的执行机制差很少,在事件循环中等待网络请求,而后一个接一个地处理这些请求。
异步在 JavaScript 中很是重要,由于它既适合编写 UI,在服务端也有上佳的性能表现。
面试加分项
理解阻塞的含义,以及对性能带来的影响。
理解事件处理程序,以及它为何对 UI 部分的代码很重要。
面试减分项
不熟悉同步、异步的概念。
讲不清楚异步代码和 UI 代码的性能影响,也说不明白它俩之间的关系。
总结
多问问应聘者高层次的知识点,若是能讲清楚这些概念,就说明即便应聘者没怎么接触过 JavaScript,也可以在短短几个星期以内就把语言细节和语法之类的东西弄清楚。
不要由于应聘者在一些简单的知识上表现不佳就把对方 pass 掉,好比经典的 CS-101 算法课,或者一些解谜类的题目。
面试官真正应该关注的,是应聘者是否知道如何把一堆功能组织在一块儿,造成一个完整的应用。
电话面试的注意点就这些了,在线下的面试中,我更加关注应聘者实际编写代码的能力,我会观察他如何写代码。在个人《精通 JavaScript 面试》这个系列文章中,会有更深刻的描述。