官值:Web前端开发面试考点指南

Web 前端面试指南与高频考题解析指南

 

 

第一章 面试准备:简历编写和面试前准备

 

一般来说,跳槽找工作要经历投递简历、准备面试、面试和谈 offer 四个阶段。其中面试题目会因你的等级和职位而异,从入门级到专家级,广度和深度都会有所增加。不过,不管什么级别和职位,面试题目一般都可以分类为理论知识、算法、项目细节、技术视野、开放性题、工作案例等内容。接下来重点来说下简历和知识点梳理的方法。

 

准备一份合适的简历

首先,什么样子的简历更加容易拿到面试邀请?笔者作为一名在 BAT 中待过两家的面试官,见过各种各样的简历,先说一下一些比较不受欢迎的简历:

  • 招聘网站上的简历:有些简历是 HR 直接从某招聘网站直接下载下来的,格式统一,而且对于自己的技能还有自己打分,这类简历有可能是候选人根本就没自己精心准备简历,而是网站根据他填写的内容自动生成的,遇到这样的简历笔者一定会让 HR 或者候选人更新一份简历给我。
  • 太花俏的简历:有人觉得简历花俏一点会让人眼前一亮,但是公司招聘的是前端不是视觉设计,所以如果找的不是视觉设计工作,还是工工整整的简历会比较受欢迎,而且太花俏的简历有可能让人第一感觉是华而不实,并不是关注候选人的技能。
  • 造假或者描述太出格的简历:看到你简历的人可能是不懂技术的 HR,也可能是专业领域的大牛,如果数据造假或者夸大其实,这样很容易就让人给卡掉。
  • 那么,怎样的简历才是好的简历呢?

     

    技术型简历的重要组成部分

    一份合适的技术型简历最重要的三部分是:

  • 个人掌握的技能,是否有岗位需要用到的技能,及其技能掌握的熟练程度:熟悉、了解还是精通
  • 项目经历,项目经历是否对现在岗位有用或者有重叠,是否能够驾驭大型项目
  • 实习经历,对于没有经验的应届生来说,实习经历是很重要的部分,是否有大公司或者具体项目的实习经历是筛选简历的重要参考
  • 技术型简历一般不要太花俏,关键要语言表达通顺清楚,让语言准确和容易理解,在 HR 筛选简历的时候,可以瞬间抓住他的眼球。另外如果有一些特殊奖项,也可以在简历中突出出来,比如:季度之星、最佳个人之类的奖项,应届生会有优秀毕业生、全额奖学金等。

     

    推荐使用 PDF 版本的简历

    一般来说简历会有 Word、Markdown 等版本,这里笔者推荐使用 PDF 版本的简历,主要原因如下:

  • 内容丰富,布局调整方便
  • 字体等格式有保障,你不知道收到你简历的人用的是什么环境,PDF 版本不会因为不同操作系统等原因而受限
  • 便于携带和传播,始终存一份简历在手机或者邮箱内,随时发送
  • 不容易被涂改
  • 一般 Windows 系统的 Word、Mac 系统的 Pages 都支持导出 PDF 格式的文件,原稿可以保存到云端或者 iCloud,方便以后修改。

    虽然我们是 Web 前端工程师,笔者还是不推荐使用 HTML 格式的简历,HTML 版本的简历容易受浏览器等环境因素影响,而且接收方不一定是技术人员,你做的炫酷的效果也不一定会被看到。

     

    简历最好要有针对性地来写

    简历是「敲门砖」,笔者建议根据你想要找的公司、岗位和职位描述来有针对性地写简历。尤其是个人技能和项目(实习)经验部分,要根据岗位要求来写,这样才能增加受邀面试的机会。

    举个例子:好友给你推荐了百度地图部门的一个高级 Web 前端工程师工作,并且把职位描述(JD)发给你了,里面有要求哪些技能,用到哪些技术,还有加分项。那么你写简历就应该思考自己有没有这些技能。如果没有 JD,那么至少你应该知道:地图部门肯定做一些跟地图相关的工作,如果恰巧你之前研究过地图定位,了解 HTML5 Geolocation 定位接口,那么你可以在简历里提一下。

    很多时候我们并不知道简历会被谁看到,也不知道简历会被朋友/猎头投递到什么公司或者职位,那么这样的简历应该是一种「通用简历」。所谓通用简历,应该是与你找的职位和期望的级别相匹配的简历,比如想找大概 T4 水平的 Web 前端工作,那么你就应该在简历体现出来自己的技能能够达到 T4 的水平。不要拿着一两年前的简历去找工作,前端这两年发展速度很快,只靠一两年前简历上面「精通、熟悉」的库和框架,可能已经找不到工作了。

    所以,写简历也是个技术活,而且是一个辛苦活!不要用千篇一律的模板!

     

    简历是面试时「点菜」用的菜单

    简历除了是「敲门砖」之外,还是供面试官提问用的「菜单」。面试官会从你简历上面写的技能、项目进行提问。所以简历是候选人「反客为主」的重要工具,这也是笔者一直提到的:不要造假或者描述太出格,而应该实事求是地写简历。简历中的技能和项目都要做好知识点梳理,尽量多地梳理出面试官可能问到的问题,并且想出怎么回答应对,千万不要在简历上自己给自己挖坑

    案例:记得有一个候选人,写的工作时间段有问题,简历上写在 2015 年 3 月到 2017 年 4 月在 A 公司工作,但是面试自我介绍的时候说自己在 A 公司工作了一年,这就有可能让面试官认为个人工作经历存在造假可能。不要小看细节!

    另外简历中不要出现错误的单词拼写,注意单词的大小写,比如jQuery之类。

     

    拿到面试邀请之后做的准备工作

    当有公司邀请你去面试的时候,应该针对性地做一些功课。

    了解部门和团队

    了解部门做的事情,团队用的技术栈,前文提到这部分信息一般从 JD 当中就可以看到,如果 JD 并没有这些信息,那么可以根据面试的部门搜索下,总会找到一些零星的信息,如果实在没有任何信息,就准备岗位需要的通用技术。

    了解面试官

    通过邀请电话或者面试邀请邮件,可以找到面试官信息。通过这些信息查找面试官技术博客、GitHub 等,了解面试官最近关注的技术和擅长的技术,因为面试官往往会在面试的过程中问自己擅长的技术。

     

    面试中出现的常规问题

    对于面试中出现的常规问题要做好准备,比如:介绍下自己,为什么跳槽,面试最后一般会问有什么要问的。

    介绍自己

    介绍自己时,切忌从自己大学实习一直到最新公司全部毫无侧重地介绍,这些在简历当中都有,最好的方式是在介绍中铺垫自己的技术特长、做的项目,引导面试官问自己准备好的问题。

    为什么跳槽

    这个问题一定要慎重和认真思考,诚实回答。一般这个问题是想评估你入职能够待多长时间,是否能够融入团队。

    每个人跳槽前肯定想了很多原因,最终才走出这一步,不管现在工作怎样,切忌抱怨,不要吐槽,更不要说和现在领导不和睦之类的话。 多从自身发展找原因,可以表达寻找自己心目中的好的技术团队氛围和平台机会,比如:个人遇见了天花板,希望找个更好的发展机会。

     

    利用脑图来梳理知识点

    对于统一校招类的面试,要重点梳理前端的所有知识点,校招面试一般是为了做人才储备,所以看的是候选人的可塑性和学习能力;对于社招类面试,则看重的是业务能力和 JD 匹配程度,所以要针对性地整理前端知识点,针对性的内容包括:项目用到的技术细节、个人技能部分需要加强或提升的常考知识点。

    所以,不仅仅简历要针对性地来写,知识点也要根据自己的经历、准备的简历、公司和职位描述来针对性地梳理。每个读者的技术能力和工作经历不同,因而知识点梳理大纲也不同,本小册重点介绍如何梳理自己的面试知识点,并且对一些常考的题目进行解答,起到知识点巩固和讲解的作用。

    基础知识来自于自己平时的储备,一般对着一本系统的书籍或者自己平时的笔记过一遍即可,但是提到自己做到的项目是没有固定的复习套路的,而且围绕项目可以衍生出来各种问题,都需要了解,项目讲清楚对于候选人也特别重要。基础是固定的,任何人经过一段时间都可以学完的,但是项目经历是实打实的经验。

    对于项目的复习和准备,笔者建议是列思维导图(脑图),针对自己重点需要讲的项目,列出用到的技术点(知识点),介绍背景、项目上线后的收益以及后续优化点。这是第一层,第二层就是针对技术点(知识点)做各种发散的问题。

     

    小结

    本小节希望你得到下面的知识:

  • 找工作之前应该准备一份合适的工作简历
  • 工作简历可以针对性地来写
  • 收到面试邀请之后应该去了解下 JD 和涉及公司部门的基本情况
  • 利用思维导图来梳理知识点
  •  

    第二章:ES 基础知识点与高频考题解析

    JavaScript 是 ECMAScript 规范的一种实现,本小节重点梳理下 ECMAScript 中的常考知识点,然后就一些容易出现的题目进行解析。

     

    知识点梳理

    • 变量类型
      • JS 的数据类型分类和判断
      • 值类型和引用类型
    • 原型与原型链(继承)
      • 原型和原型链定义
      • 继承写法
    • 作用域和闭包
      • 执行上下文
      • this
      • 闭包是什么
    • 异步
      • 同步 vs 异步
      • 异步和单线程
      • 前端异步的场景
    • ES6/7 新标准的考查
      • 箭头函数
      • Module
      • Class
      • Set 和 Map
      • Promise

     

    变量类型

    JavaScript 是一种弱类型脚本语言,所谓弱类型指的是定义变量时,不需要什么类型,在程序运行过程中会自动判断类型。

    ECMAScript 中定义了 6 种原始类型:

    • Boolean
    • String
    • Number
    • Null
    • Undefined
    • Symbol(ES6 新定义)

    注意:原始类型不包含 Object。

    题目:类型判断用到哪些方法?

     

    typeof

    typeof xxx得到的值有以下几种类型:undefined boolean number string object function、symbol ,比较简单,不再一一演示了。这里需要注意的有三点:

    • typeof null结果是object ,实际这是typeof的一个bug,null是原始值,非引用类型
    • typeof [1, 2]结果是object,结果中没有array这一项,引用类型除了function其他的全部都是object
    • typeof Symbol() 用typeof获取symbol类型的值得到的是symbol,这是 ES6 新增的知识点

     

    instanceof

    用于实例和构造函数的对应。例如判断一个变量是否是数组,使用typeof无法判断,但可以使用[1, 2] instanceof Array来判断。因为,[1, 2]是数组,它的构造函数就是Array。同理:

    function Foo(name) { this.name = name}var foo = new Foo('bar')console.log(foo instanceof Foo) // true

    题目:值类型和引用类型的区别

     

    值类型 vs 引用类型

    除了原始类型,ES 还有引用类型,上文提到的typeof识别出来的类型中,只有object和function是引用类型,其他都是值类型。

    根据 JavaScript 中的变量类型传递方式,又分为值类型引用类型,值类型变量包括 Boolean、String、Number、Undefined、Null,引用类型包括了 Object 类的所有,如 Date、Array、Function 等。在参数传递方式上,值类型是按值传递,引用类型是按共享传递。

    下面通过一个小题目,来看下两者的主要区别,以及实际开发中需要注意的地方。

    // 值类型var a = 10var b = ab = 20console.log(a) // 10console.log(b) // 20

    上述代码中,a b都是值类型,两者分别修改赋值,相互之间没有任何影响。再看引用类型的例子:

    // 引用类型var a = {x: 10, y: 20}var b = ab.x = 100b.y = 200console.log(a) // {x: 100, y: 200}console.log(b) // {x: 100, y: 200}

    上述代码中,a b都是引用类型。在执行了b = a之后,修改b的属性值,a的也跟着变化。因为a和b都是引用类型,指向了同一个内存地址,即两者引用的是同一个值,因此b修改属性时,a的值随之改动。

    再借助题目进一步讲解一下。

    说出下面代码的执行结果,并分析其原因。

    function foo(a){ a = a * 10;}function bar(b){ b.value = 'new';}var a = 1;var b = {value: 'old'};foo(a);bar(b);console.log(a); // 1console.log(b); // value: new

    通过代码执行,会发现:

    • a的值没有发生改变
    • 而b的值发生了改变

    这就是因为Number类型的a是按值传递的,而Object类型的b是按共享传递的。

    JS 中这种设计的原因是:按值传递的类型,复制一份存入栈内存,这类类型一般不占用太多内存,而且按值传递保证了其访问速度。按共享传递的类型,是复制其引用,而不是整个复制其值(C 语言中的指针),保证过大的对象等不会因为不停复制内容而造成内存的浪费。

    引用类型经常会在代码中按照下面的写法使用,或者说容易不知不觉中造成错误

    var obj = { a: 1, b: [1,2,3]}var a = obj.avar b = obj.ba = 2b.push(4)console.log(obj, a, b)

    虽然obj本身是个引用类型的变量(对象),但是内部的a和b一个是值类型一个是引用类型,a的赋值不会改变obj.a,但是b的操作却会反映到obj对象上。


     

    原型和原型链

    JavaScript 是基于原型的语言,原型理解起来非常简单,但却特别重要,下面还是通过题目来理解下JavaScript 的原型概念。

    题目:如何理解 JavaScript 的原型

    对于这个问题,可以从下面这几个要点来理解和回答,下面几条必须记住并且理解

    • 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(null除外)
    • 所有的引用类型(数组、对象、函数),都有一个__proto__属性,属性值是一个普通的对象
    • 所有的函数,都有一个prototype属性,属性值也是一个普通的对象
    • 所有的引用类型(数组、对象、函数),__proto__属性值指向它的构造函数的prototype属性值

    通过代码解释一下,大家可自行运行以下代码,看结果。

    // 要点一:自由扩展属性var obj = {}; obj.a = 100;var arr = []; arr.a = 100;function fn () {}fn.a = 100;// 要点二:__proto__console.log(obj.__proto__);console.log(arr.__proto__);console.log(fn.__proto__);// 要点三:函数有 prototypeconsole.log(fn.prototype)// 要点四:引用类型的 __proto__ 属性值指向它的构造函数的 prototype 属性值console.log(obj.__proto__ === Object.prototype)

     

    原型

    先写一个简单的代码示例。

    // 构造函数function Foo(name, age) { this.name = name}Foo.prototype.alertName = function () { alert(this.name)}// 创建示例var f = new Foo('zhangsan')f.printName = function () { console.log(this.name)}// 测试f.printName()f.alertName()

    执行printName时很好理解,但是执行alertName时发生了什么?这里再记住一个重点 当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找,因此f.alertName就会找到Foo.prototype.alertName。

    那么如何判断这个属性是不是对象本身的属性呢?使用hasOwnProperty,常用的地方是遍历一个对象的时候。

    var itemfor (item in f) { // 高级浏览器已经在 for in 中屏蔽了来自原型的属性,但是这里建议大家还是加上这个判断,保证程序的健壮性 if (f.hasOwnProperty(item)) { console.log(item) }}

    题目:如何理解 JS 的原型链

     

    原型链

    还是接着上面的示例,如果执行f.toString()时,又发生了什么?

    // 省略 N 行// 测试f.printName()f.alertName()f.toString()

    因为f本身没有toString(),并且f.__proto__(即Foo.prototype)中也没有toString。这个问题还是得拿出刚才那句话——当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找

    如果在f.__proto__中没有找到toString,那么就继续去f.__proto__.__proto__中寻找,因为f.__proto__就是一个普通的对象而已嘛!

    • f.__proto__即Foo.prototype,没有找到toString,继续往上找
    • f.__proto__.__proto__即Foo.prototype.__proto__。Foo.prototype就是一个普通的对象,因此Foo.prototype.__proto__就是Object.prototype,在这里可以找到toString
    • 因此f.toString最终对应到了Object.prototype.toString

    这样一直往上找,你会发现是一个链式的结构,所以叫做“原型链”。如果一直找到最上层都没有找到,那么就宣告失败,返回undefined。最上层是什么 —— Object.prototype.__proto__ === null

     

    原型链中的this

    所有从原型或更高级原型中得到、执行的方法,其中的this在执行时,就指向了当前这个触发事件执行的对象。因此printName和alertName中的this都是f。


     

    作用域和闭包

    作用域和闭包是前端面试中,最可能考查的知识点。例如下面的题目:

    题目:现在有个 HTML 片段,要求编写代码,点击编号为几的链接就alert弹出其编号

    <ul> <li>编号1,点击我请弹出1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li></ul>

    一般不知道这个题目用闭包的话,会写出下面的代码:

    var list = document.getElementsByTagName('li');for (var i = 0; i < list.length; i++) { list[i].addEventListener('click', function(){ alert(i + 1) }, true)}

    实际上执行才会发现始终弹出的是6,这时候就应该通过闭包来解决:

    var list = document.getElementsByTagName('li');for (var i = 0; i < list.length; i++) { list[i].addEventListener('click', function(i){ return function(){ alert(i + 1) } }(i), true)}

    要理解闭包,就需要我们从「执行上下文」开始讲起。

     

    执行上下文

    先讲一个关于 变量提升 的知识点,面试中可能会遇见下面的问题,很多候选人都回答错误:

    题目:说出下面执行的结果(这里笔者直接注释输出了)

    console.log(a) // undefinedvar a = 100fn('zhangsan') // 'zhangsan' 20function fn(name) { age = 20 console.log(name, age) var age}console.log(b); // 这里报错// Uncaught ReferenceError: b is not definedb = 100;

    在一段 JS 脚本(即一个<script>标签中)执行之前,要先解析代码(所以说 JS 是解释执行的脚本语言),解析的时候会先创建一个 全局执行上下文 环境,先把代码中即将执行的(内部函数的不算,因为你不知道函数何时执行)变量、函数声明都拿出来。变量先暂时赋值为undefined,函数则先声明好可使用。这一步做完了,然后再开始正式执行程序。再次强调,这是在代码执行之前才开始的工作。

    我们来看下上面的面试小题目,为什么a是undefined,而b却报错了,实际 JS 在代码执行之前,要「全文解析」,发现var a,知道有个a的变量,存入了执行上下文,而b没有找到var关键字,这时候没有在执行上下文提前「占位」,所以代码执行的时候,提前报到的a是有记录的,只不过值暂时还没有赋值,即为undefined,而b在执行上下文没有找到,自然会报错(没有找到b的引用)。

    另外,一个函数在执行之前,也会创建一个 函数执行上下文 环境,跟 全局上下文 差不多,不过 函数执行上下文 中会多出this arguments和函数的参数。参数和arguments好理解,这里的this咱们需要专门讲解。

    总结一下:

    • 范围:一段<script>、js 文件或者一个函数
    • 全局上下文:变量定义,函数声明
    • 函数上下文:变量定义,函数声明,this,arguments

     

    this

    先搞明白一个很重要的概念 —— this的值是在执行的时候才能确认,定义的时候不能确认! 为什么呢 —— 因为this是执行上下文环境的一部分,而执行上下文需要在代码执行之前确定,而不是定义的时候。看如下例子

    var a = { name: 'A', fn: function () { console.log(this.name) }}a.fn() // this === aa.fn.call({name: 'B'}) // this === {name: 'B'}var fn1 = a.fnfn1() // this === window

    this执行会有不同,主要集中在这几个场景中

    • 作为构造函数执行,构造函数中
    • 作为对象属性执行,上述代码中a.fn()
    • 作为普通函数执行,上述代码中fn1()
    • 用于call apply bind,上述代码中a.fn.call({name: 'B'})

    下面再来讲解下什么是作用域和作用域链,作用域链和作用域也是常考的题目。

    题目:如何理解 JS 的作用域和作用域链

     

    作用域

    ES6 之前 JS 没有块级作用域。例如

    if (true) { var name = 'zhangsan'}console.log(name)

    从上面的例子可以体会到作用域的概念,作用域就是一个独立的地盘,让变量不会外泄、暴露出去。上面的name就被暴露出去了,因此,JS 没有块级作用域,只有全局作用域和函数作用域

    var a = 100function fn() { var a = 200 console.log('fn', a)}console.log('global', a)fn()

    全局作用域就是最外层的作用域,如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中。这样的坏处就是很容易撞车、冲突。

    // 张三写的代码中var data = {a: 100}// 李四写的代码中var data = {x: true}

    这就是为何 jQuery、Zepto 等库的源码,所有的代码都会放在(function(){....})()中。因为放在里面的所有变量,都不会被外泄和暴露,不会污染到外面,不会对其他的库或者 JS 脚本造成影响。这是函数作用域的一个体现。

    附:ES6 中开始加入了块级作用域,使用let定义变量即可,如下:

    if (true) { let name = 'zhangsan'}console.log(name) // 报错,因为let定义的name是在if这个块级作用域

     

    作用域链

    首先认识一下什么叫做 自由变量 。如下代码中,console.log(a)要得到a变量,但是在当前的作用域中没有定义a(可对比一下b)。当前作用域没有定义的变量,这成为 自由变量 。自由变量如何得到 —— 向父级作用域寻找。

    var a = 100function fn() { var b = 200 console.log(a) console.log(b)}fn()

    如果父级也没呢?再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是 作用域链 。

    var a = 100function F1() { var b = 200 function F2() { var c = 300 console.log(a) // 自由变量,顺作用域链向父作用域找 console.log(b) // 自由变量,顺作用域链向父作用域找 console.log(c) // 本作用域的变量 } F2()}F1()

     

    闭包

    讲完这些内容,我们再来看一个例子,通过例子来理解闭包。

    function F1() { var a = 100 return function () { console.log(a) }}var f1 = F1()var a = 200f1()

    自由变量将从作用域链中去寻找,但是 依据的是函数定义时的作用域链,而不是函数执行时,以上这个例子就是闭包。闭包主要有两个应用场景:

    • 函数作为返回值,上面的例子就是
    • 函数作为参数传递,看以下例子
    function F1() { var a = 100 return function () { console.log(a) }}function F2(f1) { var a = 200 console.log(f1())}var f1 = F1()F2(f1)

    至此,对应着「作用域和闭包」这部分一开始的点击弹出alert的代码再看闭包,就很好理解了。


     

    异步

    异步和同步也是面试中常考的内容,下面笔者来讲解下同步和异步的区别。

     

    同步 vs 异步

    先看下面的 demo,根据程序阅读起来表达的意思,应该是先打印100,1秒钟之后打印200,最后打印300。但是实际运行根本不是那么回事。

    console.log(100)setTimeout(function () { console.log(200)}, 1000)console.log(300)

    再对比以下程序。先打印100,再弹出200(等待用户确认),最后打印300。这个运行效果就符合预期要求。

    console.log(100)alert(200) // 1秒钟之后点击确认console.log(300)

    这俩到底有何区别?—— 第一个示例中间的步骤根本没有阻塞接下来程序的运行,而第二个示例却阻塞了后面程序的运行。前面这种表现就叫做 异步(后面这个叫做 同步 ),即不会阻塞后面程序的运行

     

    异步和单线程

    JS 需要异步的根本原因是 JS 是单线程运行的,即在同一时间只能做一件事,不能“一心二用”。

    一个 Ajax 请求由于网络比较慢,请求需要 5 秒钟。如果是同步,这 5 秒钟页面就卡死在这里啥也干不了了。异步的话,就好很多了,5 秒等待就等待了,其他事情不耽误做,至于那 5 秒钟等待是网速太慢,不是因为 JS 的原因。

    讲到单线程,我们再来看个真题:

    题目:讲解下面代码的执行过程和结果

    var a = true;setTimeout(function(){ a = false;}, 100)while(a){ console.log('while执行了')}

    这是一个很有迷惑性的题目,不少候选人认为100ms之后,由于a变成了false,所以while就中止了,实际不是这样,因为JS是单线程的,所以进入while循环之后,没有「时间」(线程)去跑定时器了,所以这个代码跑起来是个死循环!

     

    前端异步的场景

    • 定时 setTimeout setInterval
    • 网络请求,如 Ajax <img>加载

    Ajax 代码示例

    console.log('start')$.get('./data1.json', function (data1) { console.log(data1)})console.log('end')

    img 代码示例(常用于打点统计)

    console.log('start')var img = document.createElement('img')// 或者 img = new Image()img.onload = function () { console.log('loaded') img.onload = null}img.src = '/xxx.png'console.log('end')

     

    ES6/7 新标准的考查

    题目:ES6 箭头函数中的this和普通函数中的有什么不同

     

    箭头函数

    箭头函数是 ES6 中新的函数定义形式,function name(arg1, arg2) {...}可以使用(arg1, arg2) => {...}来定义。示例如下:

    // JS 普通函数var arr = [1, 2, 3]arr.map(function (item) { console.log(index) return item + 1})// ES6 箭头函数const arr = [1, 2, 3]arr.map((item, index) => { console.log(index) return item + 1})

    箭头函数存在的意义,第一写起来更加简洁,第二可以解决 ES6 之前函数执行中this是全局变量的问题,看如下代码

    function fn() { console.log('real', this) // {a: 100} ,该作用域下的 this 的真实的值 var arr = [1, 2, 3] // 普通 JS arr.map(function (item) { console.log('js', this) // window 。普通函数,这里打印出来的是全局变量,令人费解 return item + 1 }) // 箭头函数 arr.map(item => { console.log('es6', this) // {a: 100} 。箭头函数,这里打印的就是父作用域的 this return item + 1 })}fn.call({a: 100})

    题目:ES6 模块化如何使用?

     

    Module

    ES6 中模块化语法更加简洁,直接看示例。

    如果只是输出一个唯一的对象,使用export default即可,代码如下

    // 创建 util1.js 文件,内容如export default { a: 100}// 创建 index.js 文件,内容如import obj from './util1.js'console.log(obj)

    如果想要输出许多个对象,就不能用default了,且import时候要加{...},代码如下

    // 创建 util2.js 文件,内容如export function fn1() { alert('fn1')}export function fn2() { alert('fn2')}// 创建 index.js 文件,内容如import { fn1, fn2 } from './util2.js'fn1()fn2()

    题目:ES6 class 和普通构造函数的区别

     

    class

    class 其实一直是 JS 的关键字(保留字),但是一直没有正式使用,直到 ES6 。 ES6 的 class 就是取代之前构造函数初始化对象的形式,从语法上更加符合面向对象的写法。例如:

    JS 构造函数的写法

    function MathHandle(x, y) { this.x = x; this.y = y;}MathHandle.prototype.add = function () { return this.x + this.y;};var m = new MathHandle(1, 2);console.log(m.add())

    用 ES6 class 的写法

    class MathHandle { constructor(x, y) { this.x = x; this.y = y; } add() { return this.x + this.y; }}const m = new MathHandle(1, 2);console.log(m.add())

    注意以下几点,全都是关于 class 语法的:

    • class 是一种新的语法形式,是class Name {...}这种形式,和函数的写法完全不一样
    • 两者对比,构造函数函数体的内容要放在 class 中的constructor函数中,constructor即构造器,初始化实例时默认执行
    • class 中函数的写法是add() {...}这种形式,并没有function关键字

    使用 class 来实现继承就更加简单了,至少比构造函数实现继承简单很多。看下面例子

    JS 构造函数实现继承

    // 动物function Animal() { this.eat = function () { console.log('animal eat') }}// 狗function Dog() { this.bark = function () { console.log('dog bark') }}Dog.prototype = new Animal()// 哈士奇var hashiqi = new Dog()

    ES6 class 实现继承

    class Animal { constructor(name) { this.name = name } eat() { console.log(`${this.name} eat`) }}class Dog extends Animal { constructor(name) { super(name) this.name = name } say() { console.log(`${this.name} say`) }}const dog = new Dog('哈士奇')dog.say()dog.eat()

    注意以下两点:

    • 使用extends即可实现继承,更加符合经典面向对象语言的写法,如 Java
    • 子类的constructor一定要执行super(),以调用父类的constructor

    题目:ES6 中新增的数据类型有哪些?

     

    Set 和 Map

    Set 和 Map 都是 ES6 中新增的数据结构,是对当前 JS 数组和对象这两种重要数据结构的扩展。由于是新增的数据结构,目前尚未被大规模使用,但是作为前端程序员,提前了解是必须做到的。先总结一下两者最关键的地方:

    • Set 类似于数组,但数组可以允许元素重复,Set 不允许元素重复
    • Map 类似于对象,但普通对象的 key 必须是字符串或者数字,而 Map 的 key 可以是任何数据类型

     

    Set

    Set 实例不允许元素有重复,可以通过以下示例证明。可以通过一个数组初始化一个 Set 实例,或者通过add添加元素,元素不能重复,重复的会被忽略。

    // 例1const set = new Set([1, 2, 3, 4, 4]);console.log(set) // Set(4) {1, 2, 3, 4}// 例2const set = new Set();[2, 3, 5, 4, 5, 8, 8].forEach(item => set.add(item));for (let item of set) { console.log(item);}// 2 3 5 4 8

    Set 实例的属性和方法有

    • size:获取元素数量。
    • add(value):添加元素,返回 Set 实例本身。
    • delete(value):删除元素,返回一个布尔值,表示删除是否成功。
    • has(value):返回一个布尔值,表示该值是否是 Set 实例的元素。
    • clear():清除所有元素,没有返回值。
    const s = new Set();s.add(1).add(2).add(2); // 添加元素s.size // 2s.has(1) // trues.has(2) // trues.has(3) // falses.delete(2);s.has(2) // falses.clear();console.log(s); // Set(0) {}

    Set 实例的遍历,可使用如下方法

    • keys():返回键名的遍历器。
    • values():返回键值的遍历器。不过由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys()和values()返回结果一致。
    • entries():返回键值对的遍历器。
    • forEach():使用回调函数遍历每个成员。
    let set = new Set(['aaa', 'bbb', 'ccc']);for (let item of set.keys()) { console.log(item);}// aaa// bbb// cccfor (let item of set.values()) { console.log(item);}// aaa// bbb// cccfor (let item of set.entries()) { console.log(item);}// ["aaa", "aaa"]// ["bbb", "bbb"]// ["ccc", "ccc"]set.forEach((value, key) => console.log(key + ' : ' + value))// aaa : aaa// bbb : bbb// ccc : ccc

     

    Map

    Map 的用法和普通对象基本一致,先看一下它能用非字符串或者数字作为 key 的特性。

    const map = new Map();const obj = {p: 'Hello World'};map.set(obj, 'OK')map.get(obj) // "OK"map.has(obj) // truemap.delete(obj) // truemap.has(obj) // false

    需要使用new Map()初始化一个实例,下面代码中set get has delete顾名即可思义(下文也会演示)。其中,map.set(obj, 'OK')就是用对象作为的 key (不光可以是对象,任何数据类型都可以),并且后面通过map.get(obj)正确获取了。

    Map 实例的属性和方法如下:

    • size:获取成员的数量
    • set:设置成员 key 和 value
    • get:获取成员属性值
    • has:判断成员是否存在
    • delete:删除成员
    • clear:清空所有
    const map = new Map();map.set('aaa', 100);map.set('bbb', 200);map.size // 2map.get('aaa') // 100map.has('aaa') // truemap.delete('aaa')map.has('aaa') // falsemap.clear()

    Map 实例的遍历方法有:

    • keys():返回键名的遍历器。
    • values():返回键值的遍历器。
    • entries():返回所有成员的遍历器。
    • forEach():遍历 Map 的所有成员。
    const map = new Map();map.set('aaa', 100);map.set('bbb', 200);for (let key of map.keys()) { console.log(key);}// "aaa"// "bbb"for (let value of map.values()) { console.log(value);}// 100// 200for (let item of map.entries()) { console.log(item[0], item[1]);}// aaa 100// bbb 200// 或者for (let [key, value] of map.entries()) { console.log(key, value);}// aaa 100// bbb 200

     

    Promise

    Promise是 CommonJS 提出来的这一种规范,有多个版本,在 ES6 当中已经纳入规范,原生支持 Promise 对象,非 ES6 环境可以用类似 Bluebird、Q 这类库来支持。

    Promise 可以将回调变成链式调用写法,流程更加清晰,代码更加优雅。

    简单归纳下 Promise:三个状态、两个过程、一个方法,快速记忆方法:3-2-1

    三个状态:pending、fulfilled、rejected

    两个过程:

    • pending→fulfilled(resolve)
    • pending→rejected(reject)

    一个方法:then

    当然还有其他概念,如catch、 Promise.all/race,这里就不展开了。

    关于 ES6/7 的考查内容还有很多,本小节就不逐一介绍了,如果想继续深入学习,可以在线看《ES6入门》。

     

    小结

    本小节主要总结了 ES 基础语法中面试经常考查的知识点,包括之前就考查较多的原型、异步、作用域,以及 ES6 的一些新内容,这些知识点希望大家都要掌握。

     

    第三章:JS-Web-API 知识点与高频考题解析

    除 ES 基础之外,Web 前端经常会用到一些跟浏览器相关的 API,接下来我们一起梳理一下。

     

    知识点梳理

    • BOM 操作
    • DOM 操作
    • 事件绑定
    • Ajax
    • 存储

     

    BOM

    BOM(浏览器对象模型)是浏览器本身的一些信息的设置和获取,例如获取浏览器的宽度、高度,设置让浏览器跳转到哪个地址。

    • navigator
    • screen
    • location
    • history

    这些对象就是一堆非常简单粗暴的 API,没任何技术含量,讲起来一点意思都没有,大家去 MDN 或者 w3school 这种网站一查就都明白了。面试的时候,面试官基本不会出太多这方面的题目,因为只要基础知识过关了,这些 API 即便你记不住,上网一查也都知道了。下面列举一下常用功能的代码示例

    获取浏览器特性(即俗称的UA)然后识别客户端,例如判断是不是 Chrome 浏览器

    var ua = navigator.userAgentvar isChrome = ua.indexOf('Chrome')console.log(isChrome)

    获取屏幕的宽度和高度

    console.log(screen.width)console.log(screen.height)

    获取网址、协议、path、参数、hash 等

    // 例如当前网址是 https://juejin.im/timeline/frontend?a=10&b=10#someconsole.log(location.href) // https://juejin.im/timeline/frontend?a=10&b=10#someconsole.log(location.protocol) // https:console.log(location.pathname) // /timeline/frontendconsole.log(location.search) // ?a=10&b=10console.log(location.hash) // #some

    另外,还有调用浏览器的前进、后退功能等

    history.back()history.forward()

     

    DOM

    题目:DOM 和 HTML 区别和联系

    什么是 DOM

    讲 DOM 先从 HTML 讲起,讲 HTML 先从 XML 讲起。XML 是一种可扩展的标记语言,所谓可扩展就是它可以描述任何结构化的数据,它是一棵树!

    <?xml version="1.0" encoding="UTF-8"?><note> <to>Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>Don't forget me this weekend!</body> <other> <a></a> <b></b> </other></note>

    HTML 是一个有既定标签标准的 XML 格式,标签的名字、层级关系和属性,都被标准化(否则浏览器无法解析)。同样,它也是一棵树。

    <!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>Document</title></head><body> <div> <p>this is p</p> </div></body></html>

    我们开发完的 HTML 代码会保存到一个文档中(一般以.html或者.htm结尾),文档放在服务器上,浏览器请求服务器,这个文档被返回。因此,最终浏览器拿到的是一个文档而已,文档的内容就是 HTML 格式的代码。

    但是浏览器要把这个文档中的 HTML 按照标准渲染成一个页面,此时浏览器就需要将这堆代码处理成自己能理解的东西,也得处理成 JS 能理解的东西,因为还得允许 JS 修改页面内容呢。

    基于以上需求,浏览器就需要把 HTML 转变成 DOM,HTML 是一棵树,DOM 也是一棵树。对 DOM 的理解,可以暂时先抛开浏览器的内部因素,先从 JS 着手,即可以认为 DOM 就是 JS 能识别的 HTML 结构,一个普通的 JS 对象或者数组。

    但是,后来大家发现结合float + div可以实现之前通过table实现的网页布局,因此就被“误用”于网页布局了。

    题目:为何 float 会导致父元素塌陷?

     

    破坏性

     

    小结

    float 的设计初衷是解决文字环绕图片的问题,后来误打误撞用于做布局,因此有许多不合适或者需要注意的地方,上文基本都讲到了需要的知识点。如果是刚开始接触 float 的同学,学完上面的基础知识之后,还应该做一些练习实战一下 —— 经典的“圣杯布局”和“双飞翼布局”。这里就不再展开讲了,网上资料非常多,例如浅谈面试中常考的两种经典布局——圣杯与双飞翼(此文的最后两张图清晰地展示了这两种布局)。


     

    定位position

    position 用于网页元素的定位,可设置 static/relative/absolute/fixed 这些值,其中 static 是默认值,不用介绍。

    题目:relative 和 absolute 有何区别?

    relative

    相对定位 relative 可以用一个例子很轻松地演示出来。例如我们写 4 个<p>,出来的样子大家不用看也能知道。

    <p>第一段文字</p><p>第二段文字</p><p>第三段文字</p><p>第四段文字</p>

    <img src="preview.png" data-realsrc="abc.png"/>

    另外,这里为何要用data-开头的属性值?—— 所有 HTML 中自定义的属性,都应该用data-开头,因为data-开头的属性浏览器渲染的时候会忽略掉,提高渲染性能。

    DOM 查询做缓存

    两段代码做一下对比:

    var pList = document.getElementsByTagName('p') // 只查询一个 DOM ,缓存在 pList 中了var ifor (i = 0; i < pList.length; i++) {} var ifor (i = 0; i < document.getElementsByTagName('p').length; i++) { // 每次循环,都会查询 DOM ,耗费性能}

    总结:DOM 操作,无论查询还是修改,都是非常耗费性能的,应尽量减少。

    合并 DOM 插入

    DOM 操作是非常耗费性能的,因此插入多个标签时,先插入 Fragment 然后再统一插入 DOM。

    var listNode = document.getElementById('list')// 要插入 10 个 li 标签var frag = document.createDocumentFragment();var x, li;for(x = 0; x < 10; x++) { li = document.createElement("li"); li[xss_clean] = "List item " + x; frag.appendChild(li); // 先放在 frag 中,最后一次性插入到 DOM 结构中。}listNode.appendChild(frag);

    事件节流

    例如要在文字改变时触发一个 change 事件,通过 keyup 来监听。使用节流。

    var textarea = document.getElementById('text')var timeoutIdtextarea.addEventListener('keyup', function () { if (timeoutId) { clearTimeout(timeoutId) } timeoutId = setTimeout(function () { // 触发 change 事件 }, 100)})

    尽早执行操作

    window.addEventListener('load', function () { // 页面的全部资源加载完才会执行,包括图片、视频等})document.addEventListener('DOMContentLoaded', function () { // DOM 渲染完即可执行,此时图片、视频还可能没有加载完})

    性能优化怎么做

    上面提到的都是性能优化的单个点,性能优化项目具体实施起来,应该按照下面步骤推进:

  • 建立性能数据收集平台,摸底当前性能数据,通过性能打点,将上述整个页面打开过程消耗时间记录下来
  • 分析耗时较长时间段原因,寻找优化点,确定优化目标
  • 开始优化
  • 通过数据收集平台记录优化效果
  • 不断调整优化点和预期目标,循环2~4步骤
  • 性能优化是个长期的事情,不是一蹴而就的,应该本着先摸底、再分析、后优化的原则逐步来做。


     

    Web 安全

    题目:前端常见的安全问题有哪些?

    Web 前端的安全问题,能回答出下文的两个问题,这个题目就能基本过关了。开始之前,先说一个最简单的攻击方式 —— SQL 注入。

    上学的时候就知道有一个「SQL注入」的攻击方式。例如做一个系统的登录界面,输入用户名和密码,提交之后,后端直接拿到数据就拼接 SQL 语句去查询数据库。如果在输入时进行了恶意的 SQL 拼装,那么最后生成的 SQL 就会有问题。但是现在稍微大型一点的系统,都不会这么做,从提交登录信息到最后拿到授权,要经过层层的验证。因此,SQL 注入都只出现在比较低端小型的系统上。

    XSS(Cross Site Scripting,跨站脚本攻击)

    这是前端最常见的攻击方式,很多大型网站(如 Facebook)都被 XSS 攻击过。

    举一个例子,我在一个博客网站正常发表一篇文章,输入汉字、英文和图片,完全没有问题。但是如果我写的是恶意的 JS 脚本,例如获取到[xss_clean]然后传输到自己的服务器上,那我这篇博客的每一次浏览都会执行这个脚本,都会把访客 cookie 中的信息偷偷传递到我的服务器上来。

    其实原理上就是黑客通过某种方式(发布文章、发布评论等)将一段特定的 JS 代码隐蔽地输入进去。然后别人再看这篇文章或者评论时,之前注入的这段 JS 代码就执行了。JS 代码一旦执行,那可就不受控制了,因为它跟网页原有的 JS 有同样的权限,例如可以获取 server 端数据、可以获取 cookie 等。于是,攻击就这样发生了。

    XSS的危害

    XSS 的危害相当大,如果页面可以随意执行别人不安全的 JS 代码,轻则会让页面错乱、功能缺失,重则会造成用户的信息泄露。

    比如早些年社交网站经常爆出 XSS 蠕虫,通过发布的文章内插入 JS,用户访问了感染不安全 JS 注入的文章,会自动重新发布新的文章,这样的文章会通过推荐系统进入到每个用户的文章列表面前,很快就会造成大规模的感染。

    还有利用获取 cookie 的方式,将 cookie 传入入侵者的服务器上,入侵者就可以模拟 cookie 登录网站,对用户的信息进行篡改。

    XSS的预防

    那么如何预防 XSS 攻击呢?—— 最根本的方式,就是对用户输入的内容进行验证和替换,需要替换的字符有:

    & 替换为:&amp;< 替换为:&lt;> 替换为:&gt;” 替换为:&quot;‘ 替换为:&#x27;/ 替换为:&#x2f;

    替换了这些字符之后,黑客输入的攻击代码就会失效,XSS 攻击将不会轻易发生。

    除此之外,还可以通过对 cookie 进行较强的控制,比如对敏感的 cookie 增加http-only限制,让 JS 获取不到 cookie 的内容。

    CSRF(Cross-site request forgery,跨站请求伪造)

    CSRF 是借用了当前操作者的权限来偷偷地完成某个操作,而不是拿到用户的信息。

    例如,一个支付类网站,给他人转账的接口是http://buy.com/pay?touid=999&money=100,而这个接口在使用时没有任何密码或者 token 的验证,只要打开访问就直接给他人转账。一个用户已经登录了http://buy.com,在选择商品时,突然收到一封邮件,而这封邮件正文有这么一行代码<img src="http://buy.com/pay?touid=999&money=100"/>,他访问了邮件之后,其实就已经完成了购买。

    CSRF 的发生其实是借助了一个 cookie 的特性。我们知道,登录了http://buy.com之后,cookie 就会有登录过的标记了,此时请求http://buy.com/pay?touid=999&money=100是会带着 cookie 的,因此 server 端就知道已经登录了。而如果在http://buy.com去请求其他域名的 API 例如http://abc.com/api时,是不会带 cookie 的,这是浏览器的同源策略的限制。但是 —— 此时在其他域名的页面中,请求http://buy.com/pay?touid=999&money=100,会带着buy.com的 cookie ,这是发生 CSRF 攻击的理论基础。

    预防 CSRF 就是加入各个层级的权限验证,例如现在的购物网站,只要涉及现金交易,肯定要输入密码或者指纹才行。除此之外,敏感的接口使用POST请求而不是GET也是很重要的。


    小结

    本小节总结了前端运行环境(即浏览器)的一些常考查知识点,包括页面加载过程、如何性能优化以及需要注意的安全问题。

     

    第7章:开发环境相关知识点与高频考题解析

    工程师的开发环境决定其开发效率,常用的开发环境配置也是面试考查点之一。

     

    知识点梳理

    • IDE
    • Git
    • Linux 基础命令
    • 前端构建工具
    • 调试方法

    本小节会重点介绍 Git 的基本用法、代码部署和开发中常用的 Linux 命令,然后以 webpack 为例介绍下前端构建工具,最后介绍怎么抓包解决线上问题。这些都是日常开发和面试中常用到的知识。


     

    IDE

    题目:你平时都使用什么 IDE 编程?有何提高效率的方法?

    前端最常用的 IDE 有 Webstorm、Sublime、Atom 和 VSCode,我们可以分别去它们的官网看一下。

    Webstorm 是最强大的编辑器,因为它拥有各种强大的插件和功能,但是我没有用过,因为它收费。不是我舍不得花钱,而是因为我觉得免费的 Sublime 已经够我用了。跟面试官聊到 Webstorm 的时候,没用过没事儿,但一定要知道它:第一,强大;第二,收费。

    Sublime 是我日常用的编辑器,第一它免费,第二它轻量、高效,第三它插件非常多。用 Sublime 一定要安装各种插件配合使用,可以去网上搜一下“sublime”常用插件的安装以及用法,还有它的各种快捷键,并且亲自使用它。这里就不一一演示了,网上的教程也很傻瓜式。

    Atom 是 GitHub 出品的编辑器,跟 Sublime 差不多,免费并且插件丰富,而且跟 Sublime 相比风格上还有些小清新。但是我用过几次就不用了,因此它打开的时候会比较慢,卡一下才打开。当然总体来说也是很好用的,只是个人习惯问题。

    VSCode 是微软出品的轻量级(相对于 Visual Studio 来说)编辑器,微软做 IDE 那是出了名的好,出了名的大而全,因此 VSCode 也有上述 Sublime 和 Atom 的各种优点,但是我也是因为个人习惯问题(本人不愿意尝试没有新意的新东西),用过几次就不用了。

    总结一下:

    • 如果你要走大牛、大咖、逼格的路线,就用 Webstorm
    • 如果你走普通、屌丝、低调路线,就用 Sublime
    • 如果你走小清新、个性路线,就用 VSCode 或者 Atom
    • 如果你面试,最好有一个用的熟悉,其他都会一点

    最后注意:千万不要说你使用 Dreamweaver 或者 notepad++ 写前端代码,会被人鄙视的。如果你不做 .NET 也不要用 Visual Studio ,不做 Java 也不要用 Eclipse。


     

    Git

    你此前做过的项目一定要用过 Git,而且必须是命令行,如果没用过,你自己也得恶补一下。对 Git 的基本应用比较熟悉的同学,可以跳过这一部分了。macOS 自带 Git,Windows 需要安装 Git 客户端,去 Git 官网 下载即可。

    国内比较好的 Git 服务商有 coding.net,国外有大名鼎鼎的 GitHub,但是有时会有网络问题,因此建议大家注册一个 coding.net 账号然后创建项目,来练练手。

    题目:常用的 Git 命令有哪些?如何使用 Git 多人协作开发?

     

    常用的 Git 命令

    首先,通过git clone <项目远程地址>下载下来最新的代码,例如git clone git@git.coding.net:username/project-name.git,默认会下载master分支。

    然后修改代码,修改过程中可以通过git status看到自己的修改情况,通过git diff <文件名>可查阅单个文件的差异。

    最后,将修改的内容提交到远程服务器,做如下操作

    git add .git commit -m "xxx"git push origin master

    如果别人也提交了代码,你想同步别人提交的内容,执行git pull origin master即可。

     

    如何多人协作开发

    多人协作开发,就不能使用master分支了,而是要每个开发者单独拉一个分支,使用git checkout -b <branchname>,运行git branch可以看到本地所有的分支名称。

    自己的分支,如果想同步master分支的内容,可运行git merge master。切换分支可使用git checkout <branchname>。

    在自己的分支上修改了内容,可以将自己的分支提交到远程服务器

    git add .git commit -m "xxx"git push origin <branchname>

    最后,待代码测试没问题,再将自己分支的内容合并到master分支,然后提交到远程服务器。

    git checkout mastergit merge <branchname>git push origin master

     

    关于 SVN

    关于 SVN 笔者的态度和针对 IE 低版本浏览器的态度一样,你只需要查询资料简单了解一下。面试的时候可能会问到,但你只要熟悉了 Git 的操作,面试官不会因为你不熟悉 SVN 而难为你。前提是你要知道一点 SVN 的基本命令,自己上网一查就行。

    不过 SVN 和 Git 的区别你得了解。SVN 是每一步操作都离不开服务器,创建分支、提交代码都需要连接服务器。而 Git 就不一样了,你可以在本地创建分支、提交代码,最后再一起 push 到服务器上。因此,Git 拥有 SVN 的所有功能,但是却比 SVN 强大得多。(Git 是 Linux 的创始人 Linus 发明的东西,因此也倍得推崇。)


     

    Linux 基础命令

    目前互联网公司的线上服务器都使用 Linux 系统,测试环境为了保证和线上一致,肯定也是使用 Linux 系统,而且都是命令行的,没有桌面,不能用鼠标操作。因此,掌握基础的 Linux 命令是非常必要的。下面总结一些最常用的 Linux 命令,建议大家在真实的 Linux 系统下亲自试一下。

    关于如何得到 Linux 系统,有两种选择:第一,在自己电脑的虚拟机中安装一个 Linux 系统,例如 Ubuntu/CentOS 等,下载这些都不用花钱;第二,花钱去阿里云等云服务商租一个最便宜的 Linux 虚拟机。推荐第二种。一般正式入职之后,公司都会给你分配开发机或者测试机,给你账号和密码,你自己可以远程登录。

    题目:常见 linux 命令有哪些?

     

    登录

    入职之后,一般会有现有的用户名和密码给你,你拿来之后直接登录就行。运行 ssh name@server 然后输入密码即可登录。

     

    目录操作

    • 创建目录 mkdir <目录名称>
    • 删除目录 rm <目录名称>
    • 定位目录 cd <目录名称>
    • 查看目录文件 ls ll
    • 修改目录名 mv <目录名称> <新目录名称>
    • 拷贝目录 cp <目录名称> <新目录名称>

     

    文件操作

    • 创建文件 touch <文件名称> vi <文件名称>
    • 删除文件 rm <文件名称>
    • 修改文件名 mv <文件名称> <新文件名称>
    • 拷贝文件 cp <文件名称> <新文件名称>

     

    文件内容操作

    • 查看文件 cat <文件名称> head <文件名称> tail <文件名称>
    • 编辑文件内容 vi <文件名称>
    • 查找文件内容 grep '关键字' <文件名称>

     

    前端构建工具

    构建工具是前端工程化中不可缺少的一环,非常重要,而在面试中却有其特殊性 —— 面试官会通过询问构建工具的作用、目的来询问你对构建工具的了解,只要这些你都知道,不会再追问细节。因为,在实际工作中,真正能让你编写构建工具配置文件的机会非常少,一个项目就配置一次,后面就很少改动了。而且,如果是大众使用的框架(如 React、Vue 等),还会直接有现成的脚手架工具,一键创建开发环境,不用手动配置。

    题目:前端为何要使用构建工具?它解决了什么问题?

     

    何为构建工具

    “构建”也可理解为“编译”,就是将开发环境的代码转换成运行环境代码的过程。开发环境的代码是为了更好地阅读,而运行环境的代码是为了更快地执行,两者目的不一样,因此代码形式也不一样。例如,开发环境写的 JS 代码,要通过混淆压缩之后才能放在线上运行,因为这样代码体积更小,而且对代码执行不会有任何影响。总结一下需要构建工具处理的几种情况:

    • 处理模块化:CSS 和 JS 的模块化语法,目前都无法被浏览器兼容。因此,开发环境可以使用既定的模块化语法,但是需要构建工具将模块化语法编译为浏览器可识别形式。例如,使用 webpack、Rollup 等处理 JS 模块化。
    • 编译语法:编写 CSS 时使用 Less、Sass,编写 JS 时使用 ES6、TypeScript 等。这些标准目前也都无法被浏览器兼容,因此需要构建工具编译,例如使用 Babel 编译 ES6 语法。
    • 代码压缩:将 CSS、JS 代码混淆压缩,为了让代码体积更小,加载更快。

     

    构建工具介绍

    最早普及使用的构建工具是 Grunt ,不久又被 Gulp 给追赶上。Gulp 因其简单的配置以及高效的性能而被大家所接受,也是笔者个人比较推荐的构建工具之一。如果你做一些简单的 JS 开发,可以考虑使用。

    如果你的项目比较复杂,而且是多人开发,那么你就需要掌握目前构建工具届的神器 —— webpack 。不过神器也有一个缺点,就是学习成本比较高,需要拿出专门的时间来专心学习,而不是三言两语就能讲完的。我们下面就演示一下 webpack 最简单的使用,全面的学习还得靠大家去认真查阅相关文档,或者参考专门讲解 webpack 的教程。

     

    webpack 演示

    接下来我们演示一下 webpack 处理模块化和混淆压缩代码这两个基本功能。

    首先,你需要安装 Node.js,没有安装的可以去 Node.js 官网 下载并安装。安装完成后运行如下命令来验证是否安装成功。

    node -vnpm -v

    然后,新建一个目录,进入该目录,运行npm init,按照提示输入名称、版本、描述等信息。完成之后,该目录下出现了一个package.json文件,是一个 JSON 文件。

    接下来,安装 wepback,运行npm i --save-dev webpack,网络原因需要耐心等待几分钟。

    接下来,编写源代码,在该目录下创建src文件夹,并在其中创建app.js和dt.js两个文件,文件内容分别是:

    // dt.js 内容module.exports = { getDateNow: function () { return Date.now() }}// app.js 内容var dt = require('./dt.js')alert(dt.getDateNow())

    然后,再返回上一层目录,新建index.html文件(该文件和src属于同一层级),内容是

    <!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>test</title></head><body> <div>test</div> <script src='./dist/bundle.js'></script></body></html>

    然后,编写 webpack 配置文件,新建webpack.config.js,内容是

    const path = require('path');const webpack = require('webpack');module.exports = { context: path.resolve(__dirname, './src'), entry: { app: './app.js', }, output: { path: path.resolve(__dirname, './dist'), filename: 'bundle.js', }, plugins: [ new webpack.optimize.UglifyJsPlugin({ compress: { //supresses warnings, usually from module minification warnings: false } }), ]};

    总结一下,目前项目的文件目录是:

    src +-- app.js +-- dt.jsindex.htmlpackage.jsonwebpack.config.js

    接下来,打开package.json,然后修改其中scripts的内容为:

    "scripts": { "start": "webpack" }

    在命令行中运行npm start,即可看到编译的结果,最后在浏览器中打开index.html,即可弹出Date.now()的值。

     

    总结

    最后再次强调,深刻理解构建工具存在的价值,比你多会一些配置代码更加有意义,特别是对于应对面试来说


     

    调试方法

    调试方法这块被考查最多的就是如何进行抓包。

    题目:如何抓取数据?如何使用工具来配置代理?

    PC 端的网页,我们可以通过 Chrome、Firefox 等浏览器自带的开发者工具来查看网页的所有网络请求,以帮助排查 bug。这种监听、查看网络请求的操作称为抓包

    针对移动端的抓包工具,Mac 系统下推荐使用 Charles 这个工具,首先 下载 并安装,打开。Windows 系统推荐使用 Fiddler,下载安装打开。两者使用基本一致,下面以 Charles 为例介绍。

    接下来,将安装好 Charles 的电脑和要抓包的手机,连接到同一个网络(一般为公司统一提供的内网,由专业网络工程师搭建),保证 IP 段相同。然后,将手机设置网络代理(每种不同手机如何设置网络代理,网上都有傻瓜式教程),代理的 IP 为电脑的 IP,代理的端口为8888。然后,Charles 可能会有一个弹框提示是否允许连接代理,这里选择“允许”即可。这样,使用手机端访问的网页或者联网的请求,Charles 就能监听到了。

    在开发过程中,经常用到抓包工具来做代理,将线上的地址代理到测试环境,Charles 和 Fiddler 都可实现这个功能。以 Charles 为例,点击菜单栏中 Tools 菜单,然后二级菜单中点击 Map Remote,会弹出配置框。首先,选中 Enable Map Remote 复选框,然后点击 Add 按钮,添加一个代理项。例如,如果要将线上的https://www.aaa.com/api/getuser?name=xxx这个地址代理到测试地址http://168.1.1.100:8080/api/getuser?name=xxx,配置如下图

    小结

    本小节总结了前端开发环境常考查的知识,这些知识也是前端程序员必须掌握的,否则会影响开发效率。

     

    第8章:如何回答常见的软技能问题

    面试是个技术活,不仅仅是技术,各种软技能的面试技巧也是非常重要的,尤其是程序员一般对于自己的软技能不是很看重,其实软技能才是决定你职场能够走多远的关键

     

    程序员应该具备的软技能

    程序员除了业务技能外,应该具有下面的软技能:

  • 韧性:抗压能力,在一定项目压力下能够迎难而上,比如勇于主动承担和解决技术难题
  • 责任心:对于自己做过的项目,能够出现 bug 之类主动解决
  • 持续学习能力:IT 行业是个需要不断充电的行业,尤其 Web 前端这些年一直在巨变,所以持续学习能力很重要
  • 团队合作能力:做项目不能个人英雄主义,应该融入团队,跟团队一起打仗
  • 交流沟通能力:经常会遇见沟通需求和交互设计的工作,应该乐于沟通分享
  • 另外在《软技能:代码之外的生存指南》这本书里提到了下面一些软技能:

  • 职业
  • 自我营销
  • 学习能力
  • 提升工作效率
  • 理财
  • 健身
  • 积极的人生观
  •  

    常见的软技能问题和提升

    回答软技能类的问题,应该注意在回答过程中体现自己具备的软技能。下面列举几个常见的软技能类的问题。

     

    回想下你遇见过最难打交道的同事,你是如何跟他沟通的

    一般来说,工作中总会遇见一两个自己不喜欢的人,这种情况应该尽量避免冲突,从自己做起慢慢让对方感觉到自己的合作精神。

    所以,遇见难打交道的同事,不要急于上报领导,应该自己主动多做一些事情,比如规划好工作安排,让他选择自己做的事情,有了结论记得发邮件确认下来,这样你们的领导和其他成员都会了解到工作的安排,在鞭笞对方的同时,也做到了职责明确。在项目当中,多主动检查项目进展,提前发现逾期的问题。

    重点是突出:自己主动沟通解决问题的意识,而不是遇见问题就找领导。

     

    当你被分配一个几乎不可能完成的任务时,你会怎么做

    这种情况下,一般通过下面方式来解决:

  • 自己先查找资料,寻找解决方案,评估自己需要怎样的资源来完成,需要多长时间
  • 能不能借助周围同事来解决问题
  • 拿着分析结果跟上级反馈,寻求帮助或者资源
  • 突出的软技能:分析和解决问题,沟通寻求帮助。

     

    业余时间都做什么?除了写码之外还有什么爱好

    这类问题也是面试官的高频问题,「一个人的业余时间决定了他的未来」,如果回答周末都在追剧打游戏之类的,未免显得太不上进。

    一般来说,推荐下面的回答:

    周末一般会有三种状态:

  • 和朋友一起去做做运动,也会聚会聊天,探讨下新技术之类的;
  • 也会看一些书籍充充电,比如我最近看的 xx,有什么的想法;
  • 有时候会闷在家用最近比较火的技术做个小项目或者实现个小功能之类的。
  • 这样的回答,既能表现自己阳光善于社交沟通的一面,又能表现自己的上进心。

     

    小结

    本小节介绍了程序员除了业务技术能力之外应该日常修炼的软技能,在面试中,软技能会被以各种形式问起,候选人应该先了解有哪些软技能可以修炼,才能在回答软技能问题的时候,尽量提到自己具备的软技能。

     

     

    第九章:如何介绍项目及应对项目细节追问

    一个标准的面试流程中,肯定会在一面二面中问到你具体做过的项目,然后追问项目的细节。这类问题往往会通过下面形式来提问:

  • 发现你简历的一个项目,直接让你介绍下这个项目
  • 让你回忆下你做过的项目中,最值得分享(最大型/最困难/最能体现技术能力/最难忘)的
  • 如果让你设计 xx 系统/项目,你会怎么着手干
  • 这类跟项目相关的综合性问题,既能体现候选人的技术水平、业务水平和架构能力,也能够辨别候选人是不是真的做过项目,还能够发现候选人的一些软技能。

    下面分享下,遇见这类问题应该怎样回答。

     

    怎样介绍自己做过的一个项目

    按照第 1 小节说的,简历当中的项目,你要精挑细选,既要体现技术难度,又要想好细节。具体要介绍一个项目(包括梳理一个项目),可以按照下面几个阶段来做。

     

    1. 介绍项目背景

    这个项目为什么做,当初大的环境背景是什么?还是为了解决一个什么问题而设立的项目?背景是很重要的,如果不了解背景,一上来就听一个结论性的项目,面试官可能对于项目的技术选型、技术难度会有理解偏差,甚至怀疑是否真的有过这样的项目。

    比如一上来就说:我们的项目采用了「backbone」来做框架,然后。。。而「backbone」已经是三四年前比较新鲜的技术,现在会有更好的选择方案,如果不介绍项目的时间背景,面试官肯定一脸懵逼。

     

    2. 承担角色

    项目涉及的人员角色有哪些,自己在其中扮演的角色是什么?

    这里候选往往人会自己给自己挖坑,比如把自己在项目中起到的作用夸大等。一般来说,面试官细节追问的时候,如果候选人能够把细节或者技术方案等讲明白、讲清楚,不管他是真的做过还是跟别人做过,或者自己认真思考过,都能体现候选人的技术水平和技术视野。前提还是在你能够兜得住的可控范围之内做适当的「美化」。

     

    3. 最终的结果和收益

    项目介绍过程中,应该介绍项目最终的结果和收益,比如项目最后经过多久的开发上线了,上线后的数据是怎样的,是否达到预期,还是带来了新的问题,遇见了问题自己后续又是怎样补救的。

     

    4. 有始有终:项目总结和反思

    有总结和反思,才会有进步。 项目做完了往往会有一些心得和体会,这时候应该跟面试官说出来。在梳理项目的总结和反思时,可以按照下面的列表来梳理:

    • 收获有哪些?
    • 是否有做得不足的地方,怎么改进?
    • 是否具有可迁移性?

    比如,之前详细介绍了某个项目,这个项目当时看来没有什么问题,但是现在有更好的解决方案了,候选人就应该在这里提出来:现在看来,这个项目还有 xx 的问题,我可以通过 xx 的方式来解决。

    再比如:做这个项目的时候,你做得比较出彩的地方,可以迁移到其他项目中直接使用,小到代码片段,大到解决方案,总会有你值得总结和梳理的地方。

    介绍完项目总结这部分,也可以引导面试官往自己擅长的领域思考。比如上面提到项目中的问题,可以往你擅长的方面引导,即使面试官没有问到,你也介绍到了。

    按照上面的四段体介绍项目,会让面试官感觉候选人有清晰的思路,对整个项目也有理解和想法,还能够总结反思项目的收益和问题,可谓「一箭三雕」。

     

    没有做过大型项目怎么办

    对于刚刚找工作的应届生,或者面试官让你进行一个大型项目的设计,候选人可能没有类似的经验。这时候不要用「我不会、没做过」一句话就带过。

    如果是实在没有项目可以说,那么可以提自己日常做的练手项目,或者看到一个解决方案的文章/书,提到的某个项目,抒发下自己的想法。

    如果是对于面试官提出来需要你设计的项目/系统,可以按照下面几步思考:

  • 有没有遇见过类似的项目
  • 有没有读过类似解决方案的文章
  • 项目能不能拆解,拆解过程中能不能发现自己做过的项目可以用
  • 项目解决的问题是什么,这类问题有没有更好的解决方案
  • 总之,切记不要一句「不知道、没做过」就放弃,每一次提问都是自己表现的机会。

     

    项目细节和技术点的追问

    介绍项目的过程中,面试官可能会追问技术细节,所以我们在准备面试的时候,应该尽量把技术细节梳理清楚,技术细节包括:

  • 技术选型方案:当时做技术选型所面临的状况
  • 技术解决方案:最终确定某种技术方案的原因,比如:选择用 Vue 而没有用 React 是为什么?
  • 项目数据和收益
  • 项目中最难的地方
  • 遇见的坑:如使用某种框架遇见哪些坑
  • 一般来说,做技术选型的时候需要考虑下面几个因素:

  • 时代:现在比较火的技术是什么,为什么火起来,解决了什么问题,能否用到我的项目中?
  • 团队:个人或者团队对某种技术的熟悉程度是怎样的,学习成本又是怎样的?
  • 业务需求:需求是怎样的,能否套用现在的成熟解决方案/库来快速解决?
  • 维护成本:一个解决方案的是否再能够 cover 住的范围之内?
  • 在项目中遇见的数据和收益应该做好跟踪,保证数据的真实性和可信性。另外,遇见的坑可能是面试官问得比较多的,尤其现在比较火的一些技术(Vue、React、webpack),一般团队都在使用,所以一定要提前准备下。

     

    小结

    本小节介绍了面试中关于项目类问题的回答方法,介绍项目要使用四段体的方式,从背景、承担角色、收益效果和总结反思四个部分来介绍项目。

    准备这个面试环节的时候,利用笔者一直提倡的「思维导图」法,好好回顾和梳理自己的项目。

     

     

    第10章 HR 面:谈钱不伤感情

    当你顺利通过面试,最后 HR 面试主要有两大环节:

  • 了解候选人是否在岗位、团队、公司文化等方面能够跟要求匹配,并且能够长期服务
  • 谈薪资
  •  

    匹配度考查

    很多情况下 HR 并不懂技术,但是也会问你项目上的问题,这时候其实是考查候选人对自己所做项目和技术的掌握能力。「检验一个人是否掌握一个专业知识,看他能不能把专业知识通俗易懂地对一个外行讲明白」。在面对 HR 询问项目或者技术点的细节时,你应该尽量通俗易懂地将知识点讲明白。怎样做到通俗易懂?笔者建议多作类比,跟生活中常见的或者大家都明白的知识作对比。举个例子,讲解「减少 Cookie 对页面打开速度优化有效果」的时候,笔者会这样来介绍:

    你应该知道平时上传文件(比如头像)要比下载文件慢,这是因为网络上行带宽要比下行带宽窄,HTTP 请求的时候其实是双向的,先上传本地的信息,包括要访问的网址、本地携带的一些 Cookie 之类的数据,这些数据因为是上行(从用户手中发到服务器,给服务器上的代码使用),本来上行带宽小,所以对速度的影响更大。因此在 HTTP 上行请求中,减少 Cookie 的大小等方式可以有效提高打开速度,尤其是在带宽不大的网络环境中,比如手机的 2G 弱网络环境。

    如果他还是不太清楚,那么带宽、上行、下行这些概念都可以类比迅雷下载这个场景,一解释应该就明白了。

    HR 面试还会通过一些问题,判断你与公司文化是否契合,比如阿里的 HR 有政委体系,会严格考查候选人是否符合公司企业文化。针对这类问题应该在回答过程中体现出自己阳光正能量的一面,不要抱怨前公司,抱怨前领导,多从自身找原因和不足,谦虚谨慎。

     

    谈薪资

    谈 offer 的环节并不轻松,包括笔者在内往往在这个阶段「折了兵」。不会谈 offer 往往会遇见这样的情况:

  • 给你多少就要多少,不会议价
  • 谈一次被打击一次,最后越来越没有底气
  • 尤其是很多不专业的 HR 以压低工资待遇为自己的首要目标,把候选人打击得不行不行的。一个不够满意的 offer 往往导致入职后出现抱怨,本小节重点讲下如何谈到自己中意的 offer。

     

    准确定位和自我估值

    在准备跳槽时,每个人肯定会对自己有一个预估,做好足够的心理准备。下面谈下怎么对自己的薪酬做个评估。一般来说跳槽的薪水是根据现在薪酬的基础上浮 15~30%,具体看个人面试的情况。对于应届毕业生,大公司基本都有标准薪水,同期的应届生差别不会特别大。

    除了上面的方法,还应该按照公司的技术职级进行估值。每个公司都有对应的技术职级,不同的技术职级薪酬范围是固定的,如果是小公司,则可以参考大公司的职级范围来确定薪资范围。

    根据职级薪资范围和自己现在薪酬基础上浮后的薪酬,做个比较,取其较高的结果。

    当然如果面试结果很好,你可以适当地提高下薪酬预期。除了这种情况,应该针对不同的性质来对 offer先做好不同的估值。这里的预期估值只是心理预期,也就是自己的「底牌」。

    所谓不同性质的 offer 指的是:

  • 是否是自己真心喜欢的工作岗位: 如果是自己真心喜欢的工作岗位,比如对于个人成长有利,或者希望进入某个公司部门,从事某个专业方向的工作,而你自己对于薪酬又不是特别在意,这时候可以适当调低薪酬预期,以拿到这个工作机会为主。
  • 是否只是做 backup 的岗位:面试可能不止面试一家,对于不是特别喜欢的公司部门,那么可以把这个 offer 做为 backup,后面遇见喜欢的公司可以以此基础来谈薪水。
  • 这时候分两种情况:如果面试结果不是很好,这种情况应该优先拿到 offer,所以可以适当降低期望薪酬;如果面试结果很好,这种情况应该多要一些薪酬,增加的薪酬可以让你加入这家公司也心里很舒服。

    对于自己真正的目标职位,面试之前应该先找 backup 岗位练练手,一是为了找出面试的感觉,二是为了拿到几个 offer 做好 backup。

    关于如何客观评估自己的身价,有篇知乎的帖子比较专业,有时间可以读一下:
    如何在跳槽前客观地评估自己的身价?

     

    跟 HR 沟通的技巧

    跟 HR 沟通的时候,不要夸大现在的薪酬,HR 知道的信息往往会超出你的认知,尤其大公司还会有背景调查,所以不要撒谎,实事求是。

    跟 HR 沟通的技巧有以下几点:

    不要急于出价

    不要急于亮出自己的底牌,一旦你说出一个薪酬范围,自己就不能增加薪酬了,还给了对方砍价的空间。而且一个不合理的价格反而会让对方直接放弃。所以不要着急出价,先让对方出价。

    同时,对于公司级别也是,不要一开始就奔着某个目标去面试,这样会加大面试的难度,比如:

    目标是拿到阿里 P7 的职位,不要说不给 P7 我就不去面试之类的,这样的要求会让对方一开始就拿 P7 的标准来面试,可能会找 P8+ 的面试官来面试你,这样会大大提升面试难度。

    要有底气足够自信

    要有底气,自信,自己按照上面的估值盘算好了想要的薪酬,那么应该有底气地说出来,并且给出具体的原因,比如:

  • 我已经对贵公司的薪酬范围和级别有了大概的了解,我现在的水平大概范围是多少
  • 现在公司很快就有调薪机会,自己已经很久没有调薪,年前跳槽会损失年终奖等情况
  • 现在我已经有某个公司多少 K 的 offer
  • 如果 HR 表示你想要的薪酬不能满足,这时候你应该给出自己评估的依据,是根据行业职级标准还是自己现有薪酬范围,这样做到有理有据。

    谈好 offer 就要尽快落实

    对于已经谈拢的薪酬待遇,一定要 HR 以发邮件 offer 的形式来确认。

     

    小结

    本小节详细谈了 HR 轮面试的两大环节,重点介绍了谈 offer 的一些技巧,希望对你有所帮助和启发。

     

     

     

    其他

    除了前面小节中提到的一些面试中应该注意的问题,本小节再整理一些面试前和面试中应该注意的事项。

     

    面试注意事项

    1. 注意社交礼仪

    虽然说 IT 行业不怎么注重工作环境,上下级也没有繁文缛节,但是在面试中还是应该注意一些社交礼仪的。像进门敲门、出门关门、站着迎人这类基本礼仪还是要做的。

     

    舒适但不随意的着装

    首先着装方面,不要太随意,也不要太正式,太正式的衣服可能会使人紧张,所以建议穿自己平时喜欢的衣服,关键是干净整洁。

     

    约个双方都舒服的面试时间

    如果 HR 打电话预约面试时间,记得一定要约个双方都舒服的时间,宁愿请假也要安排好面试时间。

    有个case:前几天有个朋友说为了给公司招人,晚上住公司附近酒店,原因是候选人为了不耽误现在公司的工作,想在 10 点之前按时上班,预约的面试时间是早上 8 点。这样对于面试官来说增加了负担,心里肯定不会特别舒服,可能会影响候选人的面试结果。

    面试时间很重要,提前十分钟到面试地点,熟悉下环境,做个登记之类的,留下个守时的好印象。如果因为堵车之类的原因不能按时到达,则要在约定时间之前电话通知对方。

     

    2. 面试后的提问环节

    面试是一个双向选择的事情,所以面试后一般会有提问环节。在提问环节,候选人最好不要什么都不问,更不要只问薪水待遇、是否加班之类的问题。

    其实这个时候可以反问面试官了解团队情况、团队做的业务、本职位具体做的工作、工作的规划,甚至一些数据(可能有些问题不会直面回答)。

    还可以问一些关于公司培训机会和晋升机会之类的问题。如果是一些高端职位,则可以问一下:自己的 leader 想把这个职位安排给什么样的人,希望多久的时间内可以达到怎样的水平。

     

    3. 面试禁忌

    • 不要对老东家有太多埋怨和负面评价
    • 不要有太多负面情绪,多表现自己阳光的一面
    • 不要夸大其词,尤其是数据方面
    • 不要贬低任何人,包括自己之前的同事,比如有人喜欢说自己周围同事多么的差劲,来突出自己的优秀
    • 不要过多争辩。你是来展现自己胜任能力的,不是来证明面试官很蠢的

     

    4. 最好自己带电脑

    有些面试会让候选人直接笔试,或者直接去小黑板上面画图写代码,这种笔试的时候会非常痛苦,我经常见单词拼写错误的候选人,这种情况最好是自己带着电脑,直接在自己熟悉的 IDE 上面编写。

    这里应该注意,自己带电脑可能也有弊端。如果你对自己的开发环境和电脑足够熟悉,操作起来能够得心应手,那么可以带着;如果你本身电脑操作就慢,比如 Linux 命令不熟悉,打开命令行忘记了命令,这种情况下会被减分。带与不带自己根据自己情况权衡。

     

    5. 面试后的总结和思考

    • 面试完了多总结自己哪里做得不好,哪里做得好,都记录下来,后续扬长避短
    • 通过面试肯定亲身体会到了公司团队文化、面试官体现出来的技术能力、专业性以及职位将来所做的事情,跟自己预期是否有差距,多个 offer 的话多做对比

    每次面试应该都有所收获,毕竟花费了时间和精力。即使面不上也可以知道自己哪方面做得不好,继续加强。

     

    本文总结

     

    面试前准备阶段

    简历准备:

  • 简历要求尽量平实,不要太花俏
  • 格式推荐 PDF
  • 内容包含:个人技能、项目经验和实习经验
  • 简历应该针对性来写
  • 简历提到的项目、技能都要仔细回想细节,挖掘可能出现的面试题
  • 拿到面邀之后准备:

  • 开场问题:自我介绍、离职原因等
  • 了解面试官、了解公司和部门做的事情
  • 知识梳理推荐使用思维导图
  •  

    技术面部分

    集中梳理了 ECMAScript 基础、JS-Web-API、CSS 和 HTML、算法、浏览器和开发环境六大部分内容,并且就一些高频考题进行讲解。

     

    非技术面试部分

    主要从软技能和项目介绍两个部分来梳理。在软技能方面,介绍了工程师从业人员应该具有的软技能,并且通过几个面试真题介绍了怎么灵活应对面试官;在项目介绍小节,推荐按照项目背景、承担角色、项目收益和项目总结反思四步来介绍,并且继续推荐使用思维导图方式来梳理项目的细节。

     

    HR 面

    在小册最后,介绍了 HR 面试应该注意的问题,重点分享了作为一个 Web 前端工程师怎么对自己进行估值,然后跟 HR 进行沟通,拿到自己可以接受的 offer。

    最后还介绍了一些面试注意事项,在面试整个流程中,太多主观因素,细节虽小也可能决定候选人面试的结果。

     

    补充说明

    本着通用性和面试门槛考虑的设计,本小册对于一些前端进阶和框架类的问题没有进行梳理,没有涉及的内容主要有:

  • Node.js部分
  • 类库:Zepto、jQuery、React、Vue 和 Angular 等
  • 移动开发
  • 下面简单展开下上面的内容。

     

    Node.js部分

    Node.js 涉及的知识点比较多,而且比较偏后端和工具性,如果用 Node.js 来做 Server 服务,需要补充大量的后端知识和运维知识,这里帮助梳理下知识点:

    • Node 开发环境
      • npm 操作
      • package.json
    • Node 基础 API 考查
      • file system
      • Event
      • 网络
      • child process
    • Node 重点和难点
      • 事件和异步理解
      • Steam 相关概念
      • Buffer 相关概念
      • domain
      • vm
      • cluster
      • 异常调优
    • Server 相关
        • Koa
        • Express
      • 数据库
        • MongoDB
        • MySQL
        • Redis
      • 运维部署
        • Nginx
        • 进程守候
        • 日志

    Node 的出现让前端可以做的事情更多,除了做一些 Server 的工作以外,Node 在日常开发中可以做一些工具来提升效率,比如常见的前端构建工具目前都是 Node 来编写的,而我们在研发中,一些类似 Mock、本地 server、代码实时刷新之类的功能,都可以使用 Node 来自己实现。

     

    前端框架

    jQuery 和 Zepto 分别是应用在 PC 和移动上面的库,大大降低了前端开发人员的门槛,很多前端工程师都是从写 jQuery 代码开始的。jQuery 和 Zepto 这两个库对外的 API 都是相同的。在面试的时候可能会问到一些具体代码的实现,比如下面两个问题:

    题目:谈谈 jQuery 的 delegate 和 bind 有什么区别;_window.onload 和$().ready有什么区别

    这实际上都是 JS-Web-API 部分基础知识的实际应用:

    • delegate 是事件代理(委托),bind是直接绑定事件
    • onload 是浏览器部分的全部加载完成,包括页面的图片之类资源;ready 则是DOMContentLoaded事件,比 onload 提前一些

    下面再说下比较火的 Angular、React 和 Vue。

    为什么会出现 Angular、React 和 Vue 这种库?

    理解为什么会出现一种新技术,以及新技术解决了什么问题,才能够更好地选择和运用新技术,不至于落入「喜新厌旧」的怪圈。

    首先在互联网用户界面和交互越来越复杂的阶段,这些 MV* 库是极大提升了开发效率,比如在数据流为主的后台系统,每天打交道最多的就是数据的增删改查,这时候如果使用这些库,可以将注意力转移到数据本身来,而不再是页面交互,从而极大地提升开发效率和沟通成本。

    React 还有个很好的想法是 React Native,只需要写一套代码就可以实现 Web、安卓、iOS 三端相同的效果,但是在实际使用和开发中会有比较大的坑。而且就像 Node 一样,前端用 Node 写 Server 可能需要用到的后端知识要比前端知识多,想要写好 React Native,客户端的知识也是必不可少的。React Native 和Node 都是拓展了 Web 前端工程师可以走的路,既可以向后又可以向前,所谓「全栈」。

    Angular、React 和 Vue 各自的特点

    • AngularJS有着诸多特性,最为核心的是 MVVM、模块化、自动化双向数据绑定、语义化标签、依赖注入等
    • React 是一个为数据提供渲染为 HTML 视图的开源 JavaScript 库,最大特点是引入 Virtual DOM,极大提升数据修改后 DOM 树的更新速度,而且也有 React Native 来做客户端开发
    • Vue.js 作为后起前端框架,借鉴了 Angular 、React 等现代前端框架/库的诸多特点,并取得了相当不错的成绩。

    一定要用这些库吗?

    目前这些库的确解决了实际开发中很多问题,但是这种「三足鼎立」的状况不是最终态,会是阶段性产物。从长远来说,好的想法和点子终究会体现在语言本身特性上来,即通过这些库的想法来推动标准的改进,比如 jQuery 的很多选择器 API,最终都被 CSS3 和 HTML5 接纳和实现,也就就有了后来的 Zepto。

    另外,以展现交互为主的项目不太推荐使用这类库,本身库的性能和体积就对页面造成极大的负担,比如笔者使用 Vue 做纯展现为主的项目,性能要比页面直出 HTML 慢。纯展现页面指的是那些以展现为主、用户交互少的页面,如文章列表页、文章详情页等。

    如果是数据交互较多的页面,例如后台系统这类对性能要求不多而数据交互较多的页面,推荐使用

    另外,不管是什么库和框架,我们最终应该学习的是编程思维,比如分层、性能优化等,考虑视图层、组件化和工程效率问题。相信随着 ES 标准发展、摩尔定律(硬件)和浏览器的演进,目前这些问题和状况都会得到改善。

    关于三者的学习资料就不补充了,因为实在是太火了,随便搜索一下就会找到。

     

    移动开发

    这里说的移动开发指的是做的项目是面向移动端的,比如 HTML5 页面、小程序等。做移动开发用的也是前面几个小节梳理的基础知识,唯一不同的是工程师面向的浏览器是移动端的浏览器或者固定的 Webview,所以会跟普通的 PC 开发有所不同。除了最基础的 JSBridge 概念之外,这里笔者重点列出以下几点:

  • 移动端更加注重性能和体验,因为移动端设备和网络都比 PC 的差一些
  • 交互跟 PC 不同,比如 touch 事件
  • 浏览器和固定的 Webview 带来了更多兼容性的问题,如微信 webview、安卓浏览器和 iOS 浏览器
  • 调试技巧更多,在 Chrome 内开发完页面,放到真机需要再调试一遍,或者需要真机配合才能实现页面的完整功能
  •  

    后记

    博文梳理了很多知识点,但是限于笔者精力、篇幅和新知识的不断涌现,难免会有考虑不到的地方,还请大家按照我在第一节提到的思维导图的方式,自己列脑图进行梳理。

     

    最后,祝每个人都拿到满意的 offer!

     

     

     

    相关推荐

    相关文章