Skip to content

二本应届生杭州小厂前端面试总结

1.现状和背景

1.1.本文目的

首先是对自己求职阶段性的总结,其次是虽然网上有很多的大厂面经,但毕竟能进大厂的少数人,更多的应届生最终还是会进入中小厂。我在经过一个月的小厂求职后,有一定的发言权。希望我的经验能给那些现阶段技术普通且缺少背景的同学提供一些帮助,找到令自己满意的正式工作。

1.2.个人背景

我是21应届生,大专的时候读的是电子商务,在这期间接触到编程并产生兴趣,自学了一定内容(回过头来看其实学习的效率很低)。后来统招专升本读的是计算机科学与技术,主学移动方向的软件开发。学习期间主要以实践为主,学习了一年半,最后一学期在温州实习了两个月,主要做服务医院的成本分析系统,之后的时间主要是修改毕业论文,吐槽下学校前期不说明代码不算入字数,导致一辩的时候被 认为工作量不足,我那整组基本都挂了

在这样的背景下,我从四月底开始投简历,期间也是咨询了一些从业者,了解到一些经验。刚开始在BOSS上投递杭州的工作,但可能由于是人在温州的缘故,基本都是石沉大海。于是改变策略,在温州找工作(我是温州人),可惜的是很少遇见心怡的工作。找了几天后,唯一去面的一家公司是温州中津研究院,公司的整体我挺满意的,也给我发了offer。但是仔细思考后还是觉得杭州我能得到更好的发展。于是在5月17日来到了杭州,在东站旁的一个青旅住了两晚后,由于房间实在太小来到了西湖区的一家叫汪星球的青年旅舍。

本文写作于2021年6月17日,在找了一个月终于确定了工作。期间面了杭州近10家中小公司,也算是积累了一些经验。

1.3.面试情况

  • 温州中津研究院(温州公司)- offer
  • 杭州广电云 - offer
  • 爱特电子 - 一面挂
  • 浙大万朋 - 一面挂
  • 微宏科技 - 一面挂
  • 博润空间科技 - 一面(996且工资极低,简历也不给打印,没有谈下去)
  • 浙江托普云农 - 一面挂
  • 数新网络 - 电话面(被放鸽子,后面没关注)
  • 金角科技 - 电话面
  • 天渺科技 - offer
  • 泰源科技 - 确定入职
  • 还有一些简历就直接挂的(阿里、字节、涂鸦智能,我还是有自知之明的,不过我是希望能感受下大厂的面试)

可以感受到我的面试经历还是比较曲折的,由于走的基本是社招,刚开始做面试题感觉是真的很难,后面在有所准备后好了很多(具体的公司的面试经验可以去我的GitHub上看)。

2.面试前准备


2.1.基础知识储备

  • 网络基础(osi 七层模型、http、tcp、dns、跨域及解决方案)
  • 浏览器运行机制(运行机制、前端性能优化)
  • HTML(盒模型、元素区别、语义化、meta 标签)
  • CSS(CSS3 新特性、实现三角形、隐藏元素几种方式、CSS 预处理器)
  • JS(ES6+)
  • jQuery(简单过一下,了解选择器及 jQuery 本质)
  • Vue 或者 React(生命周期、虚拟DOM 与 diff 算法、双向绑定实现)
  • Git(常用命令,协同开发时遇到代码冲突如何解决)
  • 算法(翻转字符串、数组去重)
  • Node.js、Typescript(加分项)

上面总结的都是我在面试中实际碰到的问题,面试官或笔试或口答来考察你,也许你的基础一般不能全部详细回答,但一定要能够简单地说出一些来,后面我会列出更加详细的题目。

2.2.项目准备

如果你像我一样背景普通,那么准备一到两个项目会起到很大帮助。一方面主动去展示项目能够获取面试官的好感,另一方面能够通过项目来介绍自己的技术栈。我展示的是一个基于 springboot 的用户音乐平台,当然能够将项目部署到线上是最好的了。

2.3.自我介绍

3.心态


作为应届生不要好高骛远,但也不要妄自菲薄。记住面试是一个双向选择的过程, 即是公司考察你,也是你深入了解公司的一个机会。

自信大方地去表达很重要,尽可能地扬长避短。真遇到不会的知识点,坦白说不会,不要不懂装懂。讲讲你了解的相关知识点,也可以谈谈对于问题大致的解决思路。

就我个人而言,一开始锁定的目标就是百人以上规模的公司,在连续碰壁后中间甚至考虑过要不要去外包,但是我自己找第一份工作目的主要还是做技术积累

最终选定入职这家公司,主要还是看重前端团队规模在中小厂中算大的了,另外在技术面的时候和两位面试官比较聊的来,可以感受到他们对于技术的热爱。

4.简历的准备


我自己作为求职新人关于这一点能谈的也不多,我的简历主要包括:个人信息、技术栈、实习经历、项目实践、教育背景这几部分,我主要挑了两个项目进行重点介绍。另外,如果你的 GitHub 或者技术博客经常更新的话别忘了放上去,这也是个加分项。

实习经历的话并非越多越好,要与应聘的岗位相匹配。像我之前做过:保险话语员、代理经纪人、内勤文员、售前客服、电商社群运营专员。最后的话只在简历上保留了前端工程师与电商社群运营,代表我的两段教育经历。

5.小厂的面试风格


基本上大致流程是:填表——技术一面(笔试 + 同事面)——技术二面(CTO 面)——HR面(谈薪)

笔试的题目绝大部分都是八股文,网上能找到或是网上题目修改一两个参数,都是一些老生常谈的。比较变态的会把字节阿里的编程题拿过来问你输出结果(第一次看到这种题目的时候我的内心是崩溃的),算法题比较少遇到(我在学校没有系统学习过算法,最近的话在看政采云团队的《算法101》)。

之后面试官会问就笔试和简历来提问,有一些公司是没有笔试直接提问的。

我遇到的 CTO 都不会跟你谈具体的前端技术,主要是考察你的整个知识结构以及认知。如果对你感兴趣的话会向你介绍一些行业发展情况,公司内部运作模式。

HR 面主要就是谈薪酬了,另外看看你对于 offer 的实际意愿怎么样。

6.面试题


6.1.计算机网络基础

OSI七层模型有哪些层级?TCP/IP有哪些层级?

OSI:物理层——数据链路层——网络层——传输层——会话层——表示层——应用层

OSI七层模型

TCP/IP:应用层——TCP传输控制层——IP网络层——链路层

HTTP状态码有哪些

分类分类描述
1**信息,服务器收到请求,需要请求者继续执行操作
2**成功,操作被成功接收并处理
3**重定向,需要进一步的操作以完成请求
4**客户端错误,请求包含语法错误或无法完成请求
5**服务器错误,服务器在处理请求的过程中发生了错误

cookies、sessionStorage 和 localStorage区别?服务器和浏览器之间的 cookies 是怎么传的?

JS 怎么实现一个类?

工厂模式、构建函数模式、原型模式

什么是跨域?跨域有哪些解决方案?

6.2.HTML

HTML中行内元素和块级元素有哪些?两者有什么区别?

行内元素:<span>、<a>、 <img>、 <input>、<textarea>、<select>、<label>

块级元素:<div>、<table>、<form>、<p>、<ul>、<h1>......<h6>、<hr> 、<pre>、<address>、<center>、<marquee> 、<blockquote>

二者区别:

  • 块级元素:
    1. 总是从新的一行开始,即各个块级元素独占一行,默认垂直向下排列
    2. 高度、宽度、margin及padding都是可控的,设置有效,有边距效果
    3. 宽度没有设置时,默认为100%
    4. 块级元素中可以包含块级元素和行内元素
  • 行内元素:
    1. 和其他元素都在一行,即行内元素和其他行内元素都会在一条水平线上排列
    2. 高度、宽度是不可控的,设置无效,由内容决定

header、nav、section、aside、article、footer这些标签有什么含义?

这些都是H5中的语义标签,具体理解可以看下图

语义标签

  • <section> 表示区块
  • <article> 表示文章。如文章、评论、帖子、博客
  • <header> 表示页眉
  • <footer> 表示页脚
  • <nav> 表示导航
  • <aside> 表示侧边栏。如文章的侧栏
  • <figure> 表示媒介内容分组。
  • <mark> 表示标记 (用得少)
  • <progress> 表示进度 (用得少)
  • <time> 表示日期

本质上新语义标签与<div><span>没有区别,只是其具有表意性,使用时除了在HTML结构上需要注意外,其它和普通标签的使用无任何差别,可以理解成<div class="nav"> 相当于<nav>

6.3.CSS

CSS 有哪些选择器

如何让一个块级元素水平垂直居中

有多种写法,展示如下

在 CSS 中对元素进行水平居中是非常简单的:如果它是一个行内元素,就对它的父容器应用 text-align: center;如果它是一个块级元素,就对它自身应用 margin: auto或者 margin: 0 auto

但是,如果要对一个元素垂直居中,margin: auto就行不通了。

方式一:绝对定位 + margin(需要指定子元素的宽高,不推荐)

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        .father{
            position: relative;
            min-height: 500px;
            background: pink;
        }
        .son {
            position: absolute;
            width: 200px;
            height: 100px;
            background: red;
            top: 50%;
            left: 50%;
            margin-top: -50px;
            margin-left: -100px;
        }
    </style>
</head>
<body>
    <div class="father">
        <div class="son">子元素的内容</div>
    </div>
    <script></script>
</body>
</html>

代码解释:我们先让子元素的左上角居中,然后向上移动宽度的一半(50px),就达到了垂直居中的效果;水平居中的原理类似。

不足之处:要求指定子元素的宽高,才能写出 margin-topmargin-left 的属性值。

但是,在通常情况下,对那些需要居中的元素来说,其宽高往往是由其内容来决定的,不建议固定宽高。

方式二:绝对定位 + translate(无需指定子元素的宽高,推荐)

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        .father{
            position: relative;
            min-height: 500px;
            background: pink;
        }
        .son {
            position: absolute;
            background: red;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }
    </style>
</head>
<body>
    <div class="father">
        <div class="son">子元素的内容</div>
    </div>
    <script></script>
</body>
</html>

这种写法,在没有指定子元素宽高的情况下,也能让其在父容器中垂直居中。因为 translate() 函数中使用百分比值时,是以这个元素自身的宽度和高度为基准进行换算和移动的(动态计算宽高)。

方式3:flex 布局(待改进)

将父容器设置为 Flex 布局,再给父容器加个属性justify-content: center,这样的话,子元素就能水平居中了;再给父容器加个属性 align-items: center,这样的话,子元素就能垂直居中了。

代码举例:

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        .father{
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background: pink;
        }
        .son {
            background: red;
        }
    </style>
</head>
<body>
    <div class="father">
        <div class="son">子元素的内容</div>
    </div>
    <script></script>
</body>
</html>

上面这种写法,不足之处在于:给父容器设置属性justify-content: centeralign-items: center之后,导致父容器里的所有子元素们都垂直居中了(如果父容器里有多个子元素的话)。可我明明想让指定的某个子元素居中,要怎么改进呢?

方式4: flex 布局 + margin: auto(推荐)

我们只需写两行声明即可:先给父容器设置 display: flex,再给指定的子元素设置我们再熟悉不过的 margin: auto,即可让这个指定的子元素在剩余空间里,水平垂直居中。大功告成。

代码举例:

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        .father{
            display: flex;
            min-height: 100vh;
            background: pink;
        }
        .son {
            margin: auto;
            background: red;
        }
    </style>
</head>
<body>
    <div class="father">
        <div class="son">子元素的内容,想水平垂直居中</div>
        <div class="son2">这个元素不想水平垂直居中</div>
    </div>
    <script></script>
</body>
</html>

请注意,当我们给父容器使用 Flex 布局 时,子元素的margin: auto不仅能让其在水平方向上居中,垂直方向上也是居中的

box-sizing属性是什么?

box-sizing就是用来改变盒子模型的,常用属性:content-box(默认盒子模型)、border-box(IE盒子模型或者叫怪异盒子)、inherit(继承父级元素的盒子模型)

默认盒子模型可见宽度 = width+内边距+边框(width就是内容宽度)

ie盒子模型可见宽度 = width(自动修改内容区大小,保持可见宽度与width值一致)

ie盒子模型适用于移动端弹性盒子布局。

em 和 rem 的区别

  • em:em是一种相对长度单位,相对于自身元素的字号大小,如果没有设置即参照父容器的字号大小或浏览器默认字号大小。
  • rem:rem是css3的新标准也是一种相对长度单位,其相对于HTML根标签的字号大小。

position 有哪几种

absolute/fixed/relative/static/inherit

CSS下划线怎么实现

第一反应想起来text-decoration,奈何英语差,就是想不起来这个单词。然后想到的是border-bottom(还嘴瓢说成了border-radius)。

  1. text-decoration:简单来说就是这个属性可以给文字设置一下装饰效果,比如删除线,下划线啥的。最常用的就是去掉a标签的默认下划线样式。

  2. border-bottom:通过设置盒子的下边框,可以起到模拟下划线的作用。

  3. linear-gradient:linear-gradient函数用于创建一个线性渐变的 "图像"。 为了创建一个线性渐变,需要设置一个起始点和一个方向(指定为一个角度)的渐变效果。你还要定义终止色。终止色就是你想让Gecko去平滑的过渡,并且你必须指定至少两种,当然也会可以指定更多的颜色去创建更复杂的渐变效果。

    这个css的函数不算常见,它的作用其实说白了就是创造一张图片。用渐变函数来模拟下划线,其实是设置背景图片,然后设置宽高,让它看起来像是一个下划线。

6.4.JS

JS中有那些基本数据类型?有那些引用数据类型?它们之间的区别是?

JS中一共有六种数据类型。

  • 基本数据类型(值类型):String 字符串、Number 数值、Boolean 布尔值、Null 空值、Undefined 未定义、Symbol(ES6)。
  • 引用数据类型(引用类型):Object 对象。

注意:内置对象 Function、Array、Date、RegExp、Error等都是属于 Object 类型。也就是说,除了那五种基本数据类型之外,其他的,都称之为 Object类型。

问:引用数据类型有几种?

答:只有一种,即 Object 类型。

数据类型之间最大的区别

  • 基本数据类型:参数赋值的时候,传数值。
  • 引用数据类型:参数赋值的时候,传地址(修改的同一片内存空间)。

map、filter、find、findIndex的使用场景是哪些?

都是遍历方法,最大区别在于应用场景不同。

这篇文章描述得非常形象

  • map:可以简单地理解为映射

    js
    var arr=[1,2,3,4];
    console.log( arr.map((n)=>n*n) );//[1, 4, 9, 16]
    console.log( arr.map((n)=>n-1) );//[0, 1, 2, 3]
  • filter:筛选过滤。

    js
    var users = [
      {name: "张含韵", "email": "zhang@email.com"},
      {name: "江一燕",   "email": "jiang@email.com"},
      {name: "李小璐",  "email": "li@email.com"}
    ];
    //获取所有人的email
    var emails=users.map(user=>user.email) 
    console.log(emails.join(',')) //"zhang@email.com", "jiang@email.com", "li@email.com"
    //获取指定人的email
    var liEmail=emails.filter(email=>/^li/.test(email))
    console.log(liEmail.join('')) //li@email.com
  • find:返回第一个符合条件的对象。

    js
    [1, 4, -5, 10].find((n) => n < 0)    // -5
    html
    <script type="text/javascript">
    $(function(){             //  等待DOM加载完毕.
     $("#a1").find("#b1").css("background","red");
     $("#a1 div").filter(":eq(1)").css("background","blue");
    })
    </script>
    <div id="a1" style="width:50px;height:50px;clear:both;background:#FC3">
        <div id="b1" style="width:10px;height:20px;">b1</div>
        <div id="b2" style="width:10px;height:20px;">b2</div>
        <div id="b3" style="width:10px;height:20px;">b3</div>
    </div>
  • findIndex:返回第一个符合条件的索引号。

交换a和b变量值有哪些方法?

方法一:使用临时变量

js
let temp = a
a = b
b = temp

下面的方案都不会有临时变量,其实不使用临时变量的思路都是让其中一个变量变成一个a和b都有关系的值,这样可以先改变另一个变量值, 最后改变原修改的变量值

方式二:让a先变成a与b的‘和’(也可以换成a和b的差,一样的) ,’和‘减去b巧妙的得到了a的变量值赋予b ,再通过‘和'减去a的值得到了b的值赋予a

js
a += b // 此时a的值为 原本的a与b之和
b = a - b // b = 和 - 原本b = 原本a
a -= b // a = 和 - 原本a = 原本b

还有变式,改为差的形式

js
a -= b // a = a与b之差
b = a + b // b = 差 + b = 原本a
a = b - a // a = 原本a - 差 = 原本b

方案三:使用异或运算

js
a ^= b
b ^= a
a ^= b

不了解的可以看这篇:异或运算实现两个数的交换

甚至可以这样

js
a = (b^=a^=b)^a;

方案四:把a先变成了一个对象,这个对象保存着应该交换后的键值对,最后赋值搞定

js
a = {a:b,b:a};
b = a.b;
a = a.a;

方案五:和上面的方法很像,只不过对象换成了数组

js
a = [a,b];
b = a[0];
a = a[1];

方案六:根据运算符优先级,首先执行b=a,此时的b直接得到了a的变量值,然后一步数组索引让a得到了b的值

js
a = [b,b=a][0]

方案七:利用了ES6的解构赋值语法,它允许我们提取数组和对象的值,对变量进行赋值

js
[a,b] = [b,a]

Promise的三种状态

pengding(进行中)/fulfilled(已成功)/rejected(已拒绝)

promise+setTimeout()+函数,判断输出

调用setTimeout时,把函数参数,放到事件队列中,等主程序运行完,再调用。即便是时间值为0,它也会等主程序执行完再执行,如果主程序队列为空,就会直接调用。

例1:

js
fun(){
   for(var i = 0;i < 10; i++){
     setTimeout(function (i) {//同步注册回调函数到异步的宏任务队列
       console.log(i);//执行此代码时,for循环已经执行完毕。
     },0);
   }
 },
 //输出结果:10个10
 //涉及知识点:JS循环机制、setTimeOut执行机制

Promise.resolve()用于将现有对象转换为Promise对象,从而控制异步流程。 而立即resolvePromise对象是在本轮“事件循环”(Event loop)的结束时,而不是在下一轮“事件循环”的开始时。

例2:

js
setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three

根据下列代码写出输出结果并给出解决方案

js
for (var i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i);
    },0);
}

输出结果为五个5。

setTimeout是异步执行,每一次循环setTimeout都被进任务队列里,等待执行。只有调用栈清空后才会执行任务队列里的任务。

也就是说它会等到for循环全部运行完毕后,才会执行setTimeout,但是当for循环结束后此时i的值已经变成了5。

方案一:闭包

通过闭包,将i的变量驻留在内存中,当输出j时,引用的是外部函数的变量值i,i的值是根据循环来的,执行setTimeout时已经确定了里面的的输出了。

js
for (var i=0; i<5; i++) {
    (j => {
        setTimeout(() => {
            console.log( j );
        }, 0);
    })(i);
}

方案二:拆分结构

将setTimeout的定义和调用分别放到不同部分:

js
function timer(i) {
    setTimeout( console.log(i), 0);
}
for (var i=0; i<5;i++) {
    timer(i);
}

方案三:let

使用es6中的let

js
for (let i=0; i<5; i++) {
    setTimeout( function timer() {
        console.log(i);
     }, 0 );
}

这个例子与第一个相比,只是把var更改成了let,可却得到了正确的结果。

因为for循环头部的let不仅将i绑定到for循环中,事实上它将其重新绑定到循环体的每一次迭代中,确保上一次迭代结束的值重新被赋值。setTimeout里面的function()属于一个新的域,通过var定义的变量是无法传入到这个函数执行域中的,通过使用let来声明块变量能作用于这个块,所以function就能使用i这个变量了;这个匿名函数的参数作用域和for参数的作用域不一样,是利用了这一点来完成的。这个匿名函数的作用域有点类似类的属性,是可以被内层方法使用的。

方案四:setTimeout第三个参数

js
for (let i=0; i<5; i++) {
    setTimeout(() => {
        console.log( i );
     }, 0, i );
}

查阅MDN之后发现,setTimeout函数不止有第三个参数,甚至后面可以紧跟无数个参数。

其原文函数形式如下:

js
var timeoutID = scope.setTimeout(function[, delay, param1, param2, ...]);
var timeoutID = scope.setTimeout(function[, delay]);
var timeoutID = scope.setTimeout(code[, delay]);

Additional parameters which are passed through to the function specified by functionor code once the timer expires.

param1,param2...paramN作为前面的回调函数的附加参数.

可以通过简单的代码来理解。

js
setTimeout((a,b,c) => {
   console.log(a,b,c)
}, 2000, "my", "name", "is starsion");
js
my name is starsion

给定一个整数数组 nums 和一个目标值 target,请在该数组中找出和为目标值的那两个整数,并返回他们的数组下标(JS实现)。另外写出时间复杂度

算法学习的还是太少,这里贴一下别人的解决方案

方案一:暴力双循环法 (不推荐使用,时间复杂度高)

关键点在于第二层循环从哪开始,思路就是当前这个整数与之后的所有的数依次进行判断即相加是否等于target,而且比较过的不要再次比较,还要排除判断自身相加的情况。

js
 var twoSum = function(nums, target) {
                     let arr = nums;
                     let arrs = new Array()
                     for(let i =  0; i < arr.length - 1; i++){
                         for(let j = i + 1; j < arr.length; j++){
                             if ( arr[i] + arr[j] === target) {
                                 arrs.push(i, j)
                                 return arrs
                             }
                         }
                     }
                 }
  console.log(twoSum([11,11,42,5,3,5],10))

方案二:利用哈希表

利用哈希表将每一个整数与target目标数的差值和该证书的索引值,以键值对(差值,索引值)的方式存入哈希表,循环判断每一个整数在哈希表中是否存在与该整数相等的差值,若不存在将该整数对应的差值以及对应的索引值存入哈希表,若存在即说明在之前的循环遍历中已经出现了与当前循环整数相加等于target的整数,这就已经找到了和为目标值的两个整数。 说直白点就是将数组中整数的差值一个一个放入哈希表,当出现了当前整数等于之前循环遍历的整数差值的情况,就说明这两个数已经找到了。当前整数 = 之前整数的差值 , 之前整数的差值 = target - 之前的整数 ,当前整数 + 之前的整数 = target。

js
const twoSum = (nums, target) => {
  // 1. 构造哈希表
  const map = new Map(); // 存储方式 {need, index}

  // 2. 遍历数组
  for (let i = 0; i < nums.length; i++) {
    // 2.1 如果找到 target - nums[i] 的值
    if (map.has(nums[i])) {
      return [map.get(nums[i]), i];
    } else {
      // 2.2 如果没找到则进行设置
      map.set(target - nums[i], i);
    }
  }
};

console.log(twoSum([3,2,5,7], 9)); // [0, 1]

JS如何实现监听

问的时候我只考虑到事件监听,但是其实还需要考虑数据监听(也可以理解为如何实现MVVM库/框架的数据绑定

先说事件监听

  1. 行内绑定:在HTML的标签中通过onclick属性进行绑定,绑定方式:on+事件名,在将所触发的事件赋值给该属性,如下:

    js
    <button onclick="alert('123');">点击</button>
  2. 使用element.onclick进行事件绑定:通过操作DOM元素,为DOM绑定事件(使用形式同行内绑定)。

    html
    <!DOCTYPE html>
    <html>
     <head>
      <meta charset="utf-8">
      <title></title>
      <script>
       window.onload = function(){
        var btn = document.getElementById('btn');
        btn.onclick = function(){
         alert("hello world");
        }
       }
      </script>
     </head>
     <body>
      <button id="btn">点击</button>
     </body>
    </html>
  3. 使用 addEventListener() 方法:接受3个参数(要处理的事件名、作为事件处理程序的函数、一个布尔值)。有些浏览器不支持事件捕获(如 IE8 及更低版本),所以谨慎绑定捕获阶段的事件监听器。

    html
    <!DOCTYPE html>
    <html>
     <head>
      <meta charset="utf-8">
      <title></title>
      <script>
       window.onload = function(){
        var btn = document.getElementById('btn');
        btn.addEventListener('click',function(){
         alert("123");
        },false);
       }
      </script>
     </head>
     <body>
      <button id="btn">点击</button>
     </body>
    </html>

数据监听

这块知识还没有深入学习过,以后学习Vue的源码再深入了解。

简单地说可以有:

  • 脏值检测(也是Angular使用的方法)
  • ES5的getter和setter
  • 已经被废弃的Object.observe
  • ES6的Proxy

内容参考

在input中输入时的状态变化

实现字符串反转

合并两个数组

浅拷贝和深拷贝区别

判断函数输出

js
(function () {
    console.log("app")
})()
console.log(b) // 5
console.log(a) // undefined

'-'连接字符串变成驼峰写法

函数声明式与函数表达式的区别

掘金的详解

判断输出

js
var a = [1,2,3,4]
var b = [1,2,3,4]
delete a[0]
console.log(a); // [empty, 2, 3, 4]
console.log(b); // [1,2,3,4]

答错了,回答时只答到了 JS 中不同数据类型的存储。

平时操作数组用的都是slice()、splice(),并不熟悉delete操作符。下面放上 MDN 的阐述。

delete 操作符用于删除对象的某个属性;如果没有指向这个属性的引用,那它最终会被释放。

js
const Employee = {
  firstname: 'John',
  lastname: 'Doe'
};

console.log(Employee.firstname); // "John"

delete Employee.firstname;

console.log(Employee.firstname); // undefined

postMessage()

以下是 MDN 的阐述:

**window.postMessage()**方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。**window.postMessage()**方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

从广义上讲,一个窗口可以获得对另一个窗口的引用(比如 targetWindow = window.opener),然后在窗口上调用 targetWindow.postMessage() 方法分发一个 MessageEvent 消息。接收消息的窗口可以根据需要自由处理此事件。传递给 window.postMessage() 的参数(比如 message )将通过消息事件对象暴露给接收消息的窗口

语法

js
otherWindow.postMessage(message, targetOrigin, [transfer]);

6.5.Vue

Vue中有哪些生命周期?数据初始化发生在哪个生命周期?

一张图方便理解(PS:图片来自网络)。

Vue生命周期

beforeCreate/created/beforeMount/mounted

生命周期函数主要分为三类

  1. 创建期间的生命周期函数
    • beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性

    • created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板。我们可以在这里进行Ajax请求。

    • beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中

    • mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示。(mounted之后,表示真实DOM渲染完了,可以操作DOM了

  2. 运行期间的生命周期函数
    • beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点

    • updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了。

  3. 销毁期间的生命周期函数
    • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
    • destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

给一段代码,判断在哪里出问题

vue
<ul>
    <li
        v-for="user in users"
        v-if="user.isActive"
        :key="user.id"
    >
        {{ user.name }}
    </li>
</ul>

考察vue的注意规范之v-if 与 v-for 一起使用

v-ifv-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中

所以,不推荐v-if和v-for同时使用,Vue2中会直接报错。

推荐使用方式:

vue
<ul v-if="shouldShowUsers">
    <li
        v-for="user in users"
        :key="user.id"
    >
        {{ user.name }}
    </li>
</ul>

或者放在计算属性遍历

js
computed: {
    activeUsers: function () {
        return this.users.filter(function (user) {
            return user.isActive
        })
    }
}
vue
<ul>
    <li
        v-for="user in activeUsers"
        :key="user.id"
    >
        {{ user.name }}
    </li>
</ul>

父子组件传值、兄弟组件传值、隔代传值如何实现?

内容太多,这篇文章总结地非常完善。

Vue双向绑定原理

$route 和 $router 的区别

  • $route对象表示当前的路由信息,包含了当前 URL 解析得到的信息。包含当前的路径,参数,query对象等。
  • $router对象是全局路由的实例,是router构造方法的实例。

6.6.浏览器运行机制及性能优化

6.7.Git

常用的命令有哪些?如何实现回滚指定版本?

回退命令:

  • git reset --hard HEAD^ 回退到上个版本
  • git reset --hard HEAD~3 回退到前3次提交之前,以此类推,回退到n次提交之前
  • git reset --hard commit_id 退到/进到,指定commit的哈希码(这次提交之前或之后的提交都会回滚)

Vue组件传值的几种方式

服务对象方式
父向子子组件通过props接受
子向父emit
兄弟组件emit和props结合、EventBus
跨越多层级provide/inject
通用Vuex

6.8.jQuery

jQuery 有哪些选择器?

判断输出

js
<body>
    <p id = "demo"></p>
</body>
<script>
    var a = $('#demo')
    var b = document.getElementById('demo')
    console.log(a===b) // false
    console.log(a) // jQuery.fn.init [p#demo]
    console.log(b) // <p id="demo"></p>
</script>

这道题错的很难受,回答时下意识感觉是不一样的,但还是回答了是一样的,事后想想应该是没有记住 jQuery 对象的本质,导致没法自信说出答案。

jQuery 对象本质是通过$对DOM元素进行了包装后产生的对象(伪数组形式存储)。jQuery对象不能使用原生js的属性和方法。

7.总结


五六月份的校招相对金三银四还是比较少的,想来杭州工作的小伙伴最好还是早点来杭州,不然容易错过面试机会。一些公司不想招人或者只需要一两个,这个需要自己判断。不要因为面试失败而灰心,面试本身也是一个提高个人水平的过程,我个人来说经过这一个月求职在 JS 和网络基础方面有了一个质的飞跃。

最重要的一点是认清自己想在第一份正式工作中获得什么,这决定了你会从事什么行业以及进入什么公司。

最后祝大家都拿到心仪的 offer,我们都有光明的未来。

本文参考


  1. 非科班二本前端大厂面试的心路历程和总结(腾讯、头条、阿里、京东) | 掘金技术征文