近日的一些前端笔试题【02】

229 阅读18分钟

1. em和rem的区别?

单位em是相对于父元素的,如果父元素没有设置字体大小,那就会追溯到body(是body,不是html)

注意:任意浏览器的默认字体高都是16px。所有未经调整的浏览器都符合: 1em=16px

rem是相对于跟元素( html),所以如果用rembody里面写任何字体大小都没效果,只能写html在中,

 设置了rem之后,字体大小只看html{}中设置的。改HTML的大小就能改整个页面字体的大小。

2. CSS的四种引入方式以及优先级?

  • 第一种:内联(行内)引入( 不推荐用 样式结构没分离)
<div style="">我是div</div>
  • 第二种:内嵌引入  (前期学习的时候用)
<head>
    <style>
           /* 写所有的样式 */
    </style>

 </head>
  • 第三种:外联(链接)引入  (以后项目或者工作中都会分开)
外部引入,单独建立一个后缀名为.css的文件,然后引入到html文件里面。
<link rel="stylesheet" href="./mycss.css">

<link>标签:用来引入外部文件的标签
 herf属性:链接css的地址

优缺点:实现了页面框架代码与表现CSS代码的完全分离,使得前期制作和后期维护都十分方便

  • 第四种:导入样式@import引入(不建议使用) 导入样式和外联样式比较相似,采用@import样式导入CSS样式表,在HTML初始化时,会被导入到HTML或者CSS文件中,成为文件的一部分,类似第二种内嵌样式。
1@import在html中使用,如下:

<head>
   <style>
           @import url()
   </style>

 </head>

(2)@import在CSS中使用,如下:

@import url(style.css);

优先级

理论上:行内>内嵌>链接>导入,实际上:内嵌、链接、导入在同一个文件头部,谁离相应的代码近,谁的优先级高。

3. XSS和CSRF的区别?

CSRF 跨站请求伪造

利用网站对用户的信任,攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生。攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据。整个过程攻击者并不能获取到受害者的登录凭证,仅仅是"冒用"。

跨站请求可以用各种方式:图片URL超链接CORSForm提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪。

  • 尽量使用POST,限制GET

GET接口太容易被拿来做CSRFI攻击,只要构造一个img标签,而img标签又是不能过滤的数据。接口最好限制为POST使用,GET则无效,降低攻击风险。当然POST并不是万无一失,攻击者只要构造一个form就可以,但需要在第三方页面做,这样就增加暴露的可能性。

  • 浏览器Cookie策略

IE6、7、8、Safari会默认拦截第三方本地Cookie (Third-party Cookie)的发送。但是Firefox2、3、Opera、Chrome、Android等不会拦截,所以通过浏览器Cookie策略来防御CSRF攻击不靠谱,只能说是降低了风险。 PS: Cookie分为两种,Sessin Cokie(在浏览器关闭后,就会关效,保存到内存里),Third-party Cookie(即只有到了Exprie时间后才会失效的Cooke,这种Cokie会保存到本地)。

PS:另外如果网站返回HTTP头包含P3P Header,那么将允许浏览器发送第三方Cookie。

  • 加验证码

欤证码,强制用户必须与应用进行交互,才能完成最终请求。在通常情况下,验证码能很好遏制CSRE攻击。但是出于用户体验考虑,网站不能给所有的操作都上验证码。因此验证码只能作为一种辅助手段,不能作为主要解决方案。

  • Referer Check

Referer Check在Web的最常见的应用就是防止图片盗链。同理、也可以被用于检查清俅是否来自合法的源(Rerer值是否是指定页面,或者网站的城),如果都不是,那么就极可能是CSRF攻击。但是因为服务器并不是什么时候都能取到Referer,所以也无法作为CSRF防御的 主要手段。但是用Referer Check来监控CSRF攻击的发生,倒是一种可行的方法。

  • Anti CSRF Token

现在业界对CSRF的防御,一致的做法是使用一个Token (Anti CSRF Token)。

XSS 跨站脚本

是发生在目标用户的浏览器层面上的,当渲染DOM树的过程成发生了不在预期内执行的JS代码时,就发生了XSS攻击。

  • 反射型XSS是在将XSS代码放在URL中,将参数提交到服务器。服务器解析后响应,在响应结果中存在XSS代码,最终通过浏览器解析执行。

  • 存储型XSS是将XSS代码存储到服务端(数据库、内存、文件系统等),在下次请求同一个页面时就不需要带上XSS代码了,而是从服务器读取。

  • DOM XSS的发生主要是在JS中使用eval造成的,所以应当避免使用eval语句。

  • 危害有盗取用户cookie,通过JS或CSS改变样式,DDos造成正常用户无法得到服务器响应。

  • XSS代码的预防主要通过对数据解码,再过滤掉危险标签属性事件

4. 单行或者多行文字溢出?

单行

white-space: nowrap;
/* 溢出的部分隐藏起来 */
overflow:hidden;
text-overflow: ellipsis;
  • white-space:正常情况下是 white-space =normal; 此时表示的是如果文字显示不开,则会自动换行。但我们需要不换行 ,则需要:white-space= nowrap; 此式表达的是不换行。
  • overflow:hidden: 因为不换行,文字超过了容器盒子,会在盒子外部显示,则需要把超出的部分隐藏掉,则使用此式。
  • text-overflow:ellipsis: 此式表达的是超出文本的地方用 省略号表示,ellipse 的意思就是 省略号的意思,此式极为重要,是转化为省略号的关键。

image.png

多行

overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;

image.png

一定要设置好我们盒子的高度和宽度,使得多的文字不会显示出来!

5. JavaScript弹出对话框的三种方式?

  • alert警告(确定) alert()方法是显示一条弹出提示消息和确认按钮的警告框。
    需要注意的是 :
    alert()是一个阻塞的函数,如果不点确认按钮,后面的内容就不会加载出来。
    使用方式:
    alert(“想要提示的文本内容”)

  • confirm()弹出个确认框 (确定,取消) confirm()方法是显示一个含有指定消息和确认和取消按钮的确认框。 如果点击"确定"返回true,否则返回false。

使用方式:

1)不接收返回值:
confirm(“这样写可以直接显示,不接收返回值。”)
(2)接收返回值:
        var x;
        var r=confirm(“请按下按钮!”);
        if (r==true){
        x=“你按下的是"确定"按钮。”;
        }
        else{
        x=“你按下的是"取消"按钮。”;
        }
        document.write(x)
  • prompt()提示框(弹出个输入框 让你输入东西) prompt()方法是显示提示用户进行输入的对话框。 这个方法返回的是用户输入的字符串。

使用方式:

1)不显示默认文本:
prompt(“开心吗?”); // 这个显示内容也可以不写,但就没有交互的意义了。2)显示默认文本:
        var x;
        var name=prompt(“请输入你的名字”,“Keafmd”);
        if (name!=null && name!=""){
        x="你好! " + name + “。”;
        document.write(x)

6. 重排和重绘?原文链接

重排也叫回流,触发重排一定会重绘,反之不一定。

浏览器页面生成过程:

  • 获取 HTML 文件并进行解析,生成 DOM 树;
  • 解析 HTML 的同时也会解析 CSS,生成 CSSOM 树;
  • 将 DOM 树和 CSSOM 树结合,生成渲染树 (Render Tree);
  • 根据生成的渲染树,进行布局 (Layout) (重排),得到节点的几何信息(位置,大小);
  • 根据渲染树以及重排得到的几何信息,进行绘制 (Painting) (重绘),调用 GPU (图形处理器) 将元素呈现出来。

重排: 重排也叫回流,当 DOM 的变化影响了元素的几何信息(位置、尺寸大小等),浏览器需要重新计算元素的几何属性,将其安放在界面的正确位置,这个过程叫做重排。

重排触发时机:

  • 页面初始渲染,这是开销最大的一次重排,并且避免不了;
  • 添加或删除可见的 DOM 元素;
  • 元素的位置发生变化;
  • 元素的尺寸发生变化(包括外边距、内边距、边框大小、高度和宽度等);
  • 元素内容发生变化(例如文字数量、图片大小等);
  • 元素字体大小改变;
  • 改变浏览器窗口尺寸(例如 resize 事件发生时);
  • 激活 CSS 伪类(例如 :hover);
  • 设置 style 属性的值,因为通过设置 style 属性改变节点样式的话,每一次设置都会触发一次重排;
  • 查询某些属性或调用某些计算方法:offsetWidth、offsetHeight 等,除此之外,当我们调用 getComputedStyle 方法,或者 IE 里的 currentStyle 时,也会触发重排,原理是一样的,都为求一个 “即时性” 和 “准确性” 。

常见引起重排的属性和方法

image.png 重绘:

当一个元素的外观发生变化,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。

重绘触发时机:

  • 颜色的改变;
  • 文本方向的改变;
  • 阴影的改变等。

如何减少重绘重排?

1)浏览器对于重排和重绘的优化

浏览器会维护一个队列,把所有会引起重排、重绘的操作放入这个队列,等队列中的操作到了一定数量或者到了一定的时间间隔,浏览器就会 flush 队列,进行一个批处理。这样就会让多次的重排、重绘变成一次重排重绘。

但当你获取布局信息的操作的时候,例如 offsetTop 等方法,为了保证获取结果的准确性,就会打破浏览器的这种优化策略,强制浏览器提前 flush 队列。

2)css 中避免重排和重绘

  • 减少重排范围,尽量将需要重排的内容固定在局部范围。
  • 不要使用 table 布局,可能很小的一个改动会造成整个 table 的重新布局。不得已使用 table 的场景,可以设置 table-layout: auto; 或者 table-layout: fixed; 这样可以让 table 一行一行的渲染,同时可可以限制重排的影响范围。
  • 集中修改样式。这样可以尽可能利用浏览器的优化机制,一次重排重绘就完成渲染。、
  • 避免设置多项内联样式。
  • 如果想设定元素的样式,可以通过改变元素的 class 类名(尽可能在 DOM 树的最里层)
  • 将 DOM 离线。通过设置元素属性 display: none; 将其从页面上去掉,然后再进行后续操作,这些后续操作将不会出发重排重绘,最后通过 display 属性显示。另外,visibility: hidden 的元素只对重绘有影响,不影响重排。
  • 使用 position: absolute / fixed; 脱离文档流。例如那些复杂的动画,对其设置 position: absolute / fixed; 尽可能地使元素脱离文档流,从而减少对其他元素的影响
  • 利用 transform translate 去代替 left top 的变换。
  • 使用 css3 硬件加速,可以让 transform、opacity、filters 这些动画不会引起重排重绘。
  • 避免使用 css 的 JavaScript 表达式。
  • 将频繁重排或重绘的节点设置为图层。将节点设置为 video 或 iframe;为节点添加 will-change 属性。

3)js 中避免重排和重绘

  • 减少直接操作 DOM 元素。不要一条一条地修改 DOM 的样式,改用 className 来控制。
  • 分离读写操作。当需要 js 操作元素样式时,即将获取样式属性的操作集中执行,并缓存值,在需要设置样式属性时也集中处理,避免获取和设置的操作互相夹杂。因为获取、设置的操作都会引起重排。
  • 动态插入多个节点时,可以使用文档碎片(DocumnetFragment),创建后一次插入,避免多次的渲染性能。DocumnetFragment 是一个保存多个元素的容器对象(保存在内存),当更新其中的一个或者多个元素时,页面不会更新。
  • 不要把 DOM 节点的 offsetLeft 等属性值放在一个循环里当成循环里的变量。
  • 使用 resize 事件时,做防抖和节流处理。

7. async和await的习题?

第一题:

console.log(0);  
setTimeout(() => {
    console.log(1);
    Promise.resolve().then(() => {
      console.log(2);
    });
    console.log(3);
});
async function func() {
    await console.log(4);
    console.log(5);
}
setTimeout(() => {
    console.log(6);
    func();
    console.log(7);
}, 0);

首先 输出同步任务0,遇到setTimeout放在宏任务队列里面,但是下面貌似没有可以执行的代码了,就开始执行宏任务队列的第一个任务,输出1,然后遇到promise,放在微任务队列,先输出3,再执行then,输出2,然后执行第二个setTImeout,打印6,调用func,遇到await,await返回的是一个promise,后面是一个console.log,所以直接打印4,打印之后跳出当前async函数,执行下面的代码,输出7,最后输出5

所以输出顺序是 0 1 3 2 6 4 7 5

第二题: 原文链接

// 说出下面代码的输出
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
}
console.log('script start')
async1();
new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {
    console.log('promise2');
});
console.log('script end')

解析

首先公布答案:script start、async1 start、async2、promise1、script end、async1 end、promise2

答案解析:

浏览器首先执行主线程上的代码,前面两段代码是两个异步函数async1和async2的定义(不执行),接着遇到了console.log('script start'),所以浏览器打印输出script start

接下来浏览器遇到async1()函数的执行,所以浏览器线程进入到async1函数内并创建相应上下文。先是执行console.log('async1 start')并输出async1 start。

然后遇到了await 关键字,此时浏览器暂停执行await 下面的代码

浏览器线程进入到async2函数内部,打印输出async2。

由于await后面返回是一个promise,所以可以理解为该promise会把await下面的代码console.log('async1 end')通过promise.then()推入到微任务队列中。注意这里是微任务队列中的第一个任务

执行完await后面的语句async2之后,async1函数会交出代码执行权,所以浏览器会去执行主线程上面的代码。此时就会去执行new Promise() 并输出promise1。同时resolve()并将该promise的回调函数then()推入微任务队列中。注意,这里console.log('promise2');是微任务队列中的第二个任务 然后继续执行主线程代码console.log('script end')并输出script end

此时主线程上的代码执行完毕,开始执行微任务队列中的任务。此时你还记得微任务队列中有几个任务,他们的顺序是什么吗?没错,微任务队列中的第一个任务是async1函数中await后面的代码console.log('async1 end');。所以此时会打印输出async1 end。

第一个微任务执行完后,执行第二个微任务console.log('promise2');,所以浏览器打印输出promise2

 // 说出下面代码的输出
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2');
    return new Promise((resolve, reiejct) => {
        resolve()
    })
}
console.log('script start')
async1();
new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {
    console.log('promise2');
});
console.log('script end')

解析

首先公布答案:script start、async1 start、async2、promise1、script end、promise2、async1 end

欸?好像哪里不一样?怎么和例子一种有点不一样。是不是发现,怎么这个例子中promise2、和 async1 end的输出顺序与上个例子刚好相反呢?

答案解析:

前面的执行输出与例子1一样,不过多讲述。主要区别就是最后promise2、和 async1 end的输出差别,所以这里我主要讲在这个例子中为什么promise2要先于async1 end输出

async2()是异步函数,所以它本身返回一个promise1,而async2中有return关键字。且该关键字返回一个promise2且将作为promise1的参数。形象地可以表述为下面这段代码

image.png

所以浏览器会把上面的这段嵌套的promise1和promise2代码推入微任务队列。注意,这是微任务中的第一个任务。

执行完await后面的语句async2之后,async1函数会交出代码执行权,所以浏览器会去执行主线程上面的代码。此时就会去执行new Promise() 并输出promise1。

同时resolve()并将该promise的回调函数then()推入微任务队列中。注意,这里console.log('promise2');是微任务队列中的第二个任务此时主线程上的代码执行完毕,开始执行微任务队列中的任务。此时你还记得微任务队列中有几个任务,他们的顺序是什么吗?没错,微任务队列中的第一个任务是async1函数中await后面那段嵌套了promise1和promise2的代码。所以浏览器执行promise1.then(),在执行promise1.then()时又遇到promise2.then(),所以浏览器将promise2.then()放入微任务队列末尾成为第三个微任务。

第一个微任务执行完后,执行第二个微任务console.log('promise2'),所以浏览器打印输出promise2。

第二个微任务执行完成后,开始执行第三个微任务promise2.then(() => { console.log('async end') }),打印输出async end 打完收工!!!

总结

  • await关键字后面是promise,如果是一个数值a,浏览器也会把该数值包装成promise.reslove(a)。
  • await关键后面的语句执行完后会交出代码执行权给主线程上的代码。
  • 遇到await就会暂停执行await下面的代码,你可以理解为await后面的promise将其后面的代码通过promise.then()推入到微任务队列中。

8. DOMContentLoaded 与 load 事件

DOMContentLoaded 意思就是:当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。

load 意思就是:当一个资源及其依赖资源已完成加载时,将触发 load 事件。

区别小结
简而言之,二者触发时间的区别在于:DOMContentLoaded 在 HTML 文档本解析之后触发,而 load 是在 HTML 所有相关资源被加载完成后触发。
为了感受这两个事件,可以使用 Chrome 打开一个任意网页。打开控制台的 Network 面板。

image.png 可以看到图上有两条线:一条蓝线,代表 DOMContentLoaded 事件,触发时间为 1.50s;一条红线,代表 load 事件,触发时间为 5.54s。
如果想要更直观地感受二者的区别,还可以 点击这里 查看效果。

我们已经知道 DOMContentLoaded 的触发时间为:当 HTML 文档被加载和解析完成。那么我们还需要理解 HTML 的解析过程。

  • 在既没有 CSS 也没有 JS 的情况下,HTML 文档的解析过程为:

    DOMContentLoaded 事件的触发时机为:HTML 解析为 DOM 之后。

  • 有 CSS 无 JS 的情况下,HTML 文档解析过程为:

    这里与 1. 不同的地方在于,渲染树的生成是基于 DOM 和 CSSOM 的。但是触发 DOMContentLoaded 的时间依然是在 HTML 解析为 DOM 后,无论此时 CSS 解析为 CSSOM 的过程是否完成。

  • 当有 JS 时,HTML 文档解析过程为:


    有一个问题:关于首屏时间?(www.cnblogs.com/caizhenbo/p…)
    “计算这个网页从空白到出现内容所发费的时间”。那怎么计算这段时间?这段时间其实就是 HTML 文档加载和解析的时间。也就是 DOMContentLoaded 事件触发之前所经历的时间。

9. dom渲染和事件循环?

JS是单线程的,也就是只有前一个任务执行完成,才会执行下一个任务。如果前一个任务耗时很长,那么下一个任务就只能干等着。显然,这样是非常浪费资源的。那么就要解决这个问题啦,先来了解一下「Event Loop」事件循环。

  • 所有的「同步任务」都在主线程进行
  • 异步任务进入任务队列,任务队列会通知主线程,哪个异步任务可以执行,这个异步任务就会进入主线程。异步任务必须指定回调函数,当主线程开始执行异步任务,其实就是在执行对应的回调函数。
  • 如果主线程的所有同步任务都执行完,系统就会去读取「任务队列」上的异步任务,如果有可以执行的,就会结束等待状态,进入主线程,开始执行。
  • 主线程不断的执行第3步

异步任务分类

「宏任务」macrotasks:script(整体代码)、setTimeout、setInterval、setImmediate、I/O、UI rendering

「微任务」microtasks:process.nextTick(NodeJS)、Promise、Object.observe、MutationObserver

微任务在dom渲染之前执行,宏任务在dom渲染之后执行。