简述浏览器的渲染过程,回流(重排)和重绘(reflow和repaint)在渲染过程中的哪一部分?
html文档解析成dom树,css解析成css对象模型(CSS Object Model),两者合并生成渲染树(rendering tree),渲染树进行布局(layer)即根据渲染树带有的位置信息计算位置,最后绘制(painting)屏幕就显示了页面。

回流:布局(layer)发生变化需要倒回去重新渲染,这个回退就是回流(reflow)。因为绘制发生在布局的下一步,所以回流必定触发重绘
重绘:绘制时背景色,文字颜色等变动,不影响位置,即重绘(repaint)
补充:渲染引擎为两种,firefox的geoko和chrome和safari的webkit
文档参考:https://www.jianshu.com/p/e6252dc9be32
简述什么是 XSS 攻击以及 CSRF 攻击?
CSRF(Cross-site request forgery):跨站请求伪造。

cookie在同一个浏览器是共享的,网站a的登录凭证放在cookie内,同时在恶意网站b点操作,触发事件,网站b拿着网站a的cooike凭证鉴权,成功后访问网站a的后台进行作恶
措施:使用token的方式,比如JWT
XSS(Cross Site Scripting):跨域脚本攻击。
核心原理是:不需要你做任何的登录认证,通过合法的操作(比如在url中输入、在评论框中输入),向你的页面注入脚本(可能是js、hmtl代码块等),造成D-doss攻击(比如弹窗广告,频繁请求后台)
措施:参考:https://www.freebuf.com/articles/web/185654.html
简述 CSS 有哪些上下文类型?
- BFC(Block Formatting Context),块级格式上下文
- IFC(Inline formatting context),内联格式上下文
- FFC,弹性盒格式上下文(CSS3新布局属性
- GFC,网格格式上下文(CSS3新布局属性
参考:https://blog.youkuaiyun.com/web_only_/article/details/100709188
简述 ES6 的新特性
常用的有:
1、变量声明:const和let
2、模板字符串:${param}拼接
3、箭头函数(Arrow Functions):()=>{}
4、对象和数组解构:let {a,b}=object1
参考:https://www.jianshu.com/p/ac1787f6c50f
了解过 Gulp Grunt 吗?简述他们的优势以及劣势
参考:https://www.jianshu.com/p/1a255e740710
Javascript 可以保存的最大数值是多少?
2的53次方,Math.pow(2,53) 9007199254740992
优化首屏渲染的方式有哪几种?
- Vue-Router路由懒加载(利用Webpack的代码切割)
- 使用CDN加速,将通用的库从vendor进行抽离
- Nginx的gzip压缩
- Vue异步组件
- 服务端渲染SSR
- 如果使用了一些UI库,采用按需加载
- Webpack开启gzip压缩
- 如果首屏为登录页,可以做成多入口
- Service Worker缓存文件处理
- 使用link标签的rel属性设置 prefetch(这段资源将会在未来某个导航或者功能要用到,但是本资源的下载顺序权重比较低,prefetch通常用于加速下一次导航)、preload(preload将会把资源得下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度)
JavaScript 中的严格模式是什么,有什么作用?
参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode
简述输入 URL 到浏览器显示的流程
以下是有数据请求情况下,若是单页面无数据,只是路由跳转则直接最后一步渲染,即只是回流和重绘即可
客户端:
浏览器输入url,一般就是域名,部分直接是IP,如果是域名则需要解析成IP
判断本地是否存有对应域名解析成IP的关系设置,比如host文件内的配置,没有则请求DNS服务器,DNS怎么路由域名得到IP可简单理解为请求-匹配-无则向上级DNS-请求-匹配……向根DNS服务器请求,最后拿到对应IP
IP独一无二,通过IP就明确了需要请求的服务器,得到请求目标IP后,发起HTTP请求
请求传输,就是传输层的约定,使用TCP协议,使用字节流服务,并且将大块的数据分割成以报文段为单位的数据包进行管理,并为它们编号。客户端和管理端通过三次确实是否发送完成,若三次握手失败,则再次发送请求。
网络传输,网络层的约定,通过IP约定将上面分割的数据包传输给目标服务器,为了保证目标真实准确,传输前会再确定目标服务器的MAC地址。ip可更换,mac地址一般固定,且可通过ARP协议得到mac地址
有MAC地址,则到了数据链路层,向目标服务器进行数据传输,客户端流程结束
服务端:
在数据链路层接到数据包,向上层转发,经过传输层时,反向将数据包组装成原来的http请求内容
服务器收到请求,ip决定了是哪个服务器,端口则决定了向服务器上哪个应用作请求。数据请求类接口在对应应用内,请求数据,正常则响应状态码200,异常则响应异常码
服务器返回数据,客户端拿到数据,页面渲染
渲染:
html文档解析成dom树,css解析成css对象模型(CSS Object Model),两者合并生成渲染树(rendering tree),渲染树进行布局(layer)即根据渲染树带有的位置信息计算位置,最后绘制(painting)屏幕就显示了页面。
简述 Javascript 中 this 的指向有哪些
1、普通函数调用
没特殊意外,就是指向全局对象-window
其中,let
允许把变量的作用域限制在块级域中;var
申明变量要么是全局的,要么是函数级的,而无法是块级的。
// 以下都在浏览器输出
// let
let username = "老大";
!function fn(){
console.log(this.username); // undefined,let 申明在块内,this调用是全局的
}() // 立即执行函数
// var
var name = "老大";
!function fn(){
console.log(this.name); // 老大,var 申明在全局,this调用是全局,可以拿到
}()
// window
// 所有浏览器都支持 window 对象。它代表浏览器的窗口。
// 所有全局 JavaScript 对象,函数和变量自动成为 window 对象的成员。
// 全局变量是 window 对象的属性。
// 全局函数是 window 对象的方法。
window.name = "老大";
!function fn(){
console.log(this.name); // 老大,name申明在全局,this调用全局,可以拿到
}()
2、对象函数调用
指向对象
/*对象函数调用*/
//window.name='老大';
//var name='老大';
let name='老大';
let obj={
id:01,
fn:function(){
console.log(this.name); // undefined this代表这个obj对象,对象外怎么设值,内部都是undefined
console.log(this.id); //01
}
}
obj.fn();
// 例如:
let obj1={
a:111
}
let obj2={
a:222,
fn:function(){
console.log(this.a);
}
}
obj1.fn=obj2.fn;
obj1.fn(); //111 obj得到fn属性,然后this是代表obj1对象,所以this.a是obj对象的a属性值,打印就是111
3、构造函数调用
let structureClass=function(){
this.name='大哥';
}
let subClass1=new structureClass();
console.log(subClass1.name); // 大哥
let subClass=new structureClass();
subClass.name='大姐头';
console.log(subClass.name); // 大姐头
4、apply和call调用
let obj1={
name:'老大'
};
let obj2={
name:'saucxs',
fn:function(){
console.log(this.name);
}
}
obj2.fn.call(obj1); // 老大
// 虽然是 obj2 调用方法,但是使用了 call ,动态的把 this 指向到 obj1 。
// 相当于这个 obj2.fn 这个执行环境是 obj1
// call 和 apply 两个主要用途:
// 1.改变 this 的指向(把 this 从 obj2 指向到 obj1 )
// 2.方法借用( obj1 没有 fn ,只是借用 obj2 方法)
// call与apply区别
// call 和 apply 的作用,完全一样,唯一的区别就是在参数上面。
// call 接收的参数不固定,第一个参数是函数体内 this 的指向,第二个参数以下是依次传入的参数。
// apply 接收两个参数,第一个参数也是函数体内 this 的指向。第二个参数是一个集合对象(数组或者类数组)
5、箭头函数调用
箭头函数里面,没有 this
,箭头函数里面的 this
是继承外面的环境。
// 普通函数
let obj={
name:'老大',
fn:function(){
setTimeout(function(){console.log(this.name)})
}
}
obj.fn(); // undefined this在普通函数setTimeout里,普通函数this代表全局的,全局没有name,所以undefined
// 箭头函数
let obj={
name:"老大",
fn:function(){
setTimeout(()=>{console.log(this.name)});
}
}
obj.fn(); // 老大 this在箭头函数里,箭头函数本身没有this,向上级取,即取到对象的this,有name属性,输出`老大`
简述 jsonp 的工作原理
解决跨域调用问题
// 代码A
<script type="text/javascript">
//回调函数
function callback(data) {
alert(data.message);
}
</script>
<script type="text/javascript" src="http://localhost:20002/test.js"></script>
// 代码B
//调用callback函数,并以json数据形式作为阐述传递,完成回调
callback({message:"success"});
// 上面就是JSONP的简单实现模式,创建一个回调函数,然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递,完成回调
简述 Javascript 事件冒泡和事件捕获原理

body元素是整个网页的容器,它的内部包含了一个div元素,而div的内部又包含了两个元素:h1和p。假如现在在p的内部点击了一下,外部容器div,以及最外部的body也都会触发各自的click事件。
触发顺序分两类,从外层body到内层p,称为事件捕获,例如Netscape实现的浏览器(Netscape浏览器已消失)。从内层到外层,称为事件冒泡,由微软实现,例如IE。
实际使用,由用户控制哪个方式,如标准的事件绑定使用addEventListener函数,它接收两个必传参数和一个可选参数:必传的为event(事件名,如"cick")和function(回调函数),可选的为useCapture(是否使用捕获模型,默认为false,即默认是冒泡模式)
如何解决 CSS 类名重名?
1、常用的是约定命名规范
2、使用CSS Modules
// vue中使用
<template>
<p :class="$style.gray">
Im gray
</p>
</template>
<style module>
.gray {
color: gray;
}
</style>
// 会编译为
<p class="gray_3FI3s6uz">Im gray</p>
.gray_3FI3s6uz {
color: gray;
}
// $style.red就可以当做一个变量,并且可以在js中使用
<script>
export default {
created () {
console.log(this.$style.gray)
// -> "gray_3FI3s6uz"
// 一个基于文件名和类名生成的标识符
}
}
</script>
简述发布订阅模式的实现方式以及原理
发布订阅模式(观察者模式),定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己的状态。
① 发布者需要一个数组类型的属性subscribers,以存储所有的订阅者;
② 订阅subscribe()
:将新的订阅者加入到这个数组中去;
③ 退订unsubscribe()
:从订阅者数组中删除某个订阅者;
④ 发布publish()
:循环遍历subscribers数组中的每一个元素,并通知他们,即发送消息,意味着调用订阅者的某个方法。因此,当用户订阅信息时,该订阅者需要向subscribe()
提供它的其中一个方法。
subscribe()
、unsubscribe()
、publish()
三种方法都需要一个type参数,因为发布者可能触发多个事件,而用户可能仅选择订阅其中一种,而不是另外一种。
- ▲ 7 简述常见异步编程方案 (promise, generator, async) 的原理
简述 Javascript 的柯里化与逆柯里化
// 柯里化
// 普通的add函数
function add(x, y) {
return x + y
}
// Currying(柯里化)后
function curryingAdd(x) {
return function (y) {
return x + y
}
}
add(1, 2) // 3
curryingAdd(1)(2) // 3
就是把add函数的x,y两个参数变成了先用一个函数接收x然后返回一个函数去处理y参数。柯里化就是只传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
使用场景:
/ 正常正则验证字符串 reg.test(txt)
// 函数封装后
function check(reg, txt) {
return reg.test(txt)
}
check(/\d+/g, 'test') //false
check(/[a-z]+/g, 'test') //true
// Currying后
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt)
}
}
var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)
hasNumber('test1') // true
hasNumber('testtest') // false
hasLetter('21212') // false
// 这是一个正则的校验,正常来说直接调用check函数就可以了,但是如果我有很多地方都要校验是否有数字,其实就是需要将第一个参数reg进行复用,这样别的地方就能够直接调用hasNumber,hasLetter等函数,让参数能够复用,调用起来也更方便
拓展:
// 实现一个add方法,使计算结果能够满足如下预期:
add(1)(2)(3) = 6;
add(1, 2, 3)(4) = 10;
add(1)(2)(3)(4)(5) = 15;
function add() {
// 第一次执行时,定义一个数组专门用来存储所有的参数
var _args = Array.prototype.slice.call(arguments);
// 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
var _adder = function() {
_args.push(...arguments);
return _adder;
};
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
_adder.toString = function () {
return _args.reduce(function (a, b) {
return a + b;
});
}
return _adder;
}
add(1)(2)(3) // 6
add(1, 2, 3)(4) // 10
add(1)(2)(3)(4)(5) // 15
add(2, 6)(1) // 9
注1、Array.prototype.slice.call(arguments)
意思是把调用方法的参数截取出来。
js里Array是一个类 slice是此类里的一个方法 ,那么应该Array.prototype.slice这么去用,arrayObj.slice(start, [end])
是截取数组的一部分。call([thisObj[,arg1[arg2[[argN]]]]]) ,thisObj是一个对象的方法,arrg1~argN是参数。
所以这行意思就是说把调用方法的参数截取出来。
function test(a,b,c,d)
{
var arg = Array.prototype.slice.call(arguments,1);
alert(arg);
}
test("a","b","c","d"); //b,c,d