2019.8.13
1.什么是闭包
阮一峰博客
闭包就是连接内部函数和外部函数的桥梁,用函数嵌套实现内部函数请求到外部函数的变量,闭包的变量是一直存在内存中的,因为一直被引用,不会被垃圾回收机制回收,解决方法是,在退出函数之前,将不使用的局部变量全部删除。
- 函数嵌套
- 函数返回函数
应用场景:
1.定义私有方法
2.setTimeout传参
let callLater = function (paramA, paramB, paramC) {
return function(){
console.log(paramA)
}
}
let fn = callLater('box', "display", "none");
let hideMenu = setTimeout(fn, 5000);
2019.8.14
2.如何理解面向对象
参考博客链接
面向对象是一种编程思想,用构造函数和原型链封装各个独立的有自己的属性和方法的对象,对象可以接收数据,处理数据,传送数据给其他对象,对象可以从父级对象继承属性和方法
3. 前端性能优化的方法?
参考博客链接
- 减少请求数量
合并:js/css文件
图片处理:雪碧图,Base64格式
使用缓存 - 减少资源大小
压缩:css/js/图片压缩 - 优化网络连接
使用cdn
dns预解析 - 优化资源加载
资源加载位置:和样式相关文件放在head标签中,先外链后本页,js文件放在body底部引用
资源加载时机:资源懒加载、资源预加载、模块按需加载 - 减少重绘回流
减少dom操作、页面效果尽量使用css实现而不是js(因为css实现不会阻塞js进程) - webpack优化
https://juejin.im/post/5b0c3b53f265da09253cbed0
图片预加载:图片还不需要展示的时候就去加载
图片懒加载:大的图片在需要用到的时候才去加载
扩展问题:CSS和JS的位置会影响页面的效率,为什么?
css放在顶部head里面是因为这样才可以让dom树和render树并行渲染
js放在底部是因为html 页面渲染是从上往下的,需要先让页面加载完成,再处理js交互
详解:css加载过程中不会影响到DOM树的生成,但是会影响到Render树的生成,进而影响到layout
所以一般来说,style的link标签尽量放在head里面,因为在解析DOM树的时候是自上而下的,而css样式又是通过异步加载的,这样的话,
解析DOM树的body节点和加载CSS样式尽可能的并行,加快Render树生成的速度。
js脚本应该放在底部,原因在于js线程和GUI渲染是互斥的关系,如果js放在首部。
当下载js的时候,会影响渲染线程绘制页面,js的作用主要是处理交互,而交互必须得先让页面呈现才能进行,所以为了保证用户体验,尽量让页面先绘制出来。
4. 在地址栏输入url后网页的加载过程
参考博客链接
- 1.dns 解析 。将地址栏中的域名解析为对应的真实的IP地址
首先浏览器先检查本地hosts文件是否有这个网址映射关系,如果有就调用这个IP地址映射,完成域名解析。
如果没找到则会查找本地DNS解析器缓存,如果查找到则返回。
如果还是没有找到则会查找本地DNS服务器,如果查找到则返回。
最后迭代查询,按根域服务器 ->顶级域,.cn->第二层域,hb.cn ->子域,www.hb.cn的顺序找到IP地址。
问题1:oliver让我直接中在本地配置host, 这是为什么呢?是不是无法解析,才一定要在本地配置。
是的。
这中间有介绍到 cdn,cdn的原理就是加快dns的解析,将用量很高的静态的资源交给 cdn的厂商,他们配置了各地区的服务器,dns最快速度解析。
CDN网络是在用户和服务器之间增加Cache层,主要是通过接管DNS实现,将用户的请求引导到Cache上获得源服务器的数据
- 2.tcp连接
- 3.发送http请求
- 4.服务器接收请求后响应,返回数据。
- 5.解析html文件,渲染页面
在渲染页面之前,需要构建DOM树和CSSOM树。
衍生问题:DOM操作对性能的影响,以及如何优化
参考博客
在浏览器中,DOm操作和ECMAScript的实现是分离的。
操作DOM,就是通过js代码调用dom的接口。
浏览器渲染页面的过程中,浏览器会通过解析HTML文档来构建DOM树,解析CSS构建CSS规则树。而js代码在解析过程中,可能会修改生成的DOM树和CSSOM树,
这个过程叫做重绘回流;
重绘就是js操作dom时,只修改了样式,元素的位置和尺寸并没有变,这个时候页面只有部分需要重新绘制。
回流就是元素的位置和尺寸发生了改变,需要重新布局。
优化方法
1.合并多次的DOM操作为单次的DOM操作
element.style.cssText += "border: 1px solid #f00"
element.className += 'empty'
2.使用文档片段fragment
3.设置具有动画效果的DOM元素的position属性为fixed或absolute;
使得元素脱离页面布局流,从而避免了页面频繁的重排,只涉及动画元素自身的重排了。
2019.8.20
5.浏览器缓存:强缓存和协商缓存
参考博客链接
强缓存是利用http的返回头中的Expires/Cache-Control两个字段来控制的,用来表示资源的缓存时间;
Expires:表示资源的失效时间,在这个时间之前,资源是缓存的。Expires 受限于本地时间,如果修改了本地时间,可能会造成缓存失效。
Cache-Control:该字段的max-age是一个相对时间,代表资源的有效期。优先级比Expires高
协商缓存就是由服务器来确定缓存资源是否可用,所以客户端和服务端要通过某种标识来进行通信,从而让服务器判断请求资源是否可以访问。
- HTTP响应报文Last-Modify表示资源最后的修改时间,http请求头中会包含If-Modified-Since,该值就是缓存之前返回的Last-Modified,根据资源的最后修改时间判断是否缓存。但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag 。
- HTTP响应报文中Etag是一个校验码,资源有更新时etag会更新,响应头中会包含If-None-Match,根据这个值是否变化判断是否缓存(优先级比Last-Modify高)
缓存类型 | 获取资源方式 | 状态码 | 发送请求到服务器 | f5刷新是否有效 |
---|---|---|---|---|
强缓存 | 从缓存取 | 200 | 否,直接从缓存取 | 无效 |
协商缓存 | 从缓存取 | 304 | 是,通过服务器来告知缓存是否可用 | 有效 |
6.Javascript事件循环机制(event loop)
事件循环指的是计算机系统的一种运行机制,js用这种机制解决单线程运行带来的一些问题。
在程序中设置两个线程,一个负责程序本身的运作,称为主线程,另一个负责主程序与其他进程(异步函数)的通信,称为事件循环线程。
- i.什么是事件队列?
事件队列是一个存储着待执行任务的队列,其中的任务严格按照时间先后顺序执行,先进先出的顺序
- ii.Promise的含义和用法?
Promise保存这个某个未来才会结束的事件,这个事件是放在异步函数的回调函数里,它会加到事件队列里,当所有同步任务执行完才会返回结果到主线程
- iii.什么是macro task(宏任务)和micro task(微任务)?
macro task:setTimeout, setInterval, setImmediate, I/O, UI rendering
micro task:process.nextTick, Promise, MutationObserver
任务队列中,在每一次事件循环中,宏任务只会提取一个执行,而微任务会一直提取,直到微任务为空为止。所以事件循环的执行顺序是主线程-》微任务-》宏任务
ex:
setTimeout(()=>{
console.log('A');
},0);
var obj={
func:function () {
setTimeout(function () {
console.log('B')
},0);
return new Promise(function (resolve) {
console.log('C');//Promise新建后立即执行
resolve();
})
}
};
obj.func().then(function () {
console.log('D')
});
console.log('E');
/*
主线程:[c,e]
宏任务:[a,b]
微任务:[d]
打印顺序:c/e/d/a/b
事件队列是先进先出的顺序
*/
7.对web安全的理解
参考博客
- xss攻击:跨站脚本攻击是一种安全漏洞,攻击者利用这个漏洞在网站上注入恶意代码。
容易发生恶意攻击的情况:
i.点击不可靠的链接进入网站
ii.没有过滤掉恶意代码的内容发送到后台
xss攻击的3种类型
i.反射型:通过url参数注入
ii.存储型:调用目标接口,把恶意代码提交到数据库
iii.dom型:通过修改页面的DOM节点形成的xss
XSS 有哪些注入的方法:
在 HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。
i.在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。
ii.在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。
iii.在标签的 href、src 等属性中,包含 javascript: 等可执行代码。
vi.在 onload、onerror、onclick 等事件中,注入不受控制代码。
XSS 攻击的预防
i.设置httponly为true,脚本将无法请求到cookie
ii.过滤
输入检查,表单按照格式输入
对HTML做充分的转义
- csrf是利用用户的登录状态发送请求,从而达到修改用户自身个人资料、状态、日志等用户信息等目的
8.防抖
如果持续触发事件,事件触发的间隔大于指定时间的间隔,事件才会执行,这就在防抖
举例说明:鼠标移动给数字加1,
<div id="box" style="width:100px;height:100px;background:red"></div>
<body>
<script type="text/javascript">
var count = 0;
var iBox = document.getElementById('box');
var addCount = function (argument) {
iBox.innerHTML = count++;
}
iBox.onmousemove = addCount;
</script>
现在给这个例子做节流,这个函数实现的是,鼠标移动后必须过1s没有再次触发事件,这个事件才执行
iBox.onmousemove = debounce(addCount,1000);
function debounce(fn,time){
var timeout;
return function(){
clearTimeout(timeout);
timeout = setTimeout(fn,time);
}
}
给函数做优化,改变this指向,传参event对象
function debounce(fn,time){
var timeout;
return function(){
clearTimeout(timeout);
//修改this指向,并且把event对象传过去
timeout = setTimeout(fn.bind(this,arguments),time);
}
}
让函数是可以立即执行的,等待n s后才再次触发
iBox.onmousemove = debounce(addCount,1000,true);
function debounce(fn,time,immediate){
var timeout;
return function(){
if(timeout){
clearTimeout(timeout);
}
if(immediate && !timeout){
timeout = setTimeout(function(){
}, time);
fn.apply(this,arguments);
}else{
timeout = setTimeout(fn.bind(this,arguments),time);
}
}
}
9.节流
如果持续触发事件,指定时间间隔内只执行一次事件,这叫做节流
9.1使用时间戳实现节流
1s内只执行一次addCount,效果是会立即执行一次,但是结束触发后不再执行
iBox.onmousemove = throttle(addCount,1000);
function throttle(fn,time){
var pre = 0;
return function(){
var now = new Date();
if(now - pre > time){
fn.apply(this,arguments);
pre = now;
}
}
}
9.2使用定时器实现节流
1s内只执行一次addCount,效果是不会立即执行,会在n 秒内执行第一次,但是结束触发后还能再执行一次
function throttle(fn,time){
var timeout;
return function(){
if(!timeout){
timeout = setTimeout(function(){
timeout = null;
fn.apply(this,arguments)
},time);
}
}
}
9.3最终版
结合定时器和时间戳,既可以立即执行,在结束触发后还能再执行一次
function throttle(fn,time){
var timeout;
var pre = 0;
return function(){
var now = new Date();
if(timeout){
clearTimeout(timeout);
timeout = null;
}
if(now - pre > time){
fn.apply(this,arguments);
pre = now;
}else if(!timeout){
timeout = setTimeout(fn.bind(this,arguments),time)
}
}
}
10.es6的常用新特性
1.箭头函数
let obj = {
num:0,
add(){
setInterval( ()=>{
this.num++ ;
},1000)
}
}
箭头函数和普通函数的区别?
1.箭头函数语法简洁
2.箭头函数不会创建自己的this,它只会从自己的作用域的上一级继承this(就是定义时所处的外层环境的this)
.call.apply.bind不会修改箭头函数的this指向
箭头函数不能作为构造函数使用,没有原型
2.解构赋值
let [a,b] = [1,2];
3.参数默认值
function test(a = 'a'){
console.log(a)
}
4.多行字符串
let str = `aaa
aaaa
aaa
aaa`;
5.字符串模板
let name = 'tang';
let hello = `hello ${name}`;//hello tang
6.块级作用域意思的变量只在{}内有效访问,let const
let不可以重复声明,var可以
7.模块化开发
export导出
import导入
11.对this的理解
参考链接
this 实际上是在函数被调用时建立的一个绑定,它指向 什么 是完全由函数被调用的调用点来决定的。
在浏览器中setTimeout,setInterval和匿名函数执行时的当前对象是全局对象window
情况1:如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。
function a(){
var user = "追梦子";
console.log(this.user); //undefined
console.log(this); //Window
}
a();
情况2:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。
var o = {
user:"追梦子",
fn:function(){
console.log(this.user); //追梦子
}
}
o.fn();
情况3:如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //12
}
}
}
o.b.fn();
bind:undefined的情况
请注意,在非严格模式下,运行时可能会将全局对象(浏览器中的窗口)替换为未定义,但我找不到规定该行为的规范。在严格模式下,不执行此类替换。
12.bind/call/apply
参考博客
用来改变函数执行时this的指向。
核心理念:借用方法
比如obj需要一个sayName的方法,现在不用重写一个,只需要sayName.bind(obj)
这样调用。
手写实现call/apply/bind
核心思想就是在Function构造函数的原型加上call函数,把当前函数加到目标上下文中,并把参数传过去,执行后再把这个函数从目标上下文中移除。
1.实现call函数
let a = {
val:'hello'
}
function sayName(age,name) {
console.log(this.val,age,name)
}
sayName.call(a,20,'tang');//hello 20 tang
Function.prototype.myCall = function(context = window){
let argument = [...arguments].slice(1,[...arguments].length);
context.fn = this;
let result = context.fn(...argument);
delete context.fn;
return result;
}
sayName.myCall(a,20,'tang');//hello 20 tang
2.实现apply函数
let a = {
val:'hello'
}
function sayName(age,name) {
console.log(this.val,age,name)
}
sayName.apply(a,[20,'tang']);//hello 20 tang
Function.prototype.myApply = function(context = window){
let argument = [...arguments][1];
context.fn = this;
let result = context.fn(...argument);
delete context.fn
return result;
}
sayName.myApply(a,[20,'tang']);//hello 20 tang
3.实现bind函数,最重要是要识别bind构造函数的场景
let a = {
val:'hello'
}
function sayName(age,name) {
this.test = "test";
console.log(this.val,this.test,age,name)
}
let sayNameA = sayName.bind(a,20);//hello 20 tang
let s1 = new sayNameA('tang');
console.log(s1)
Function.prototype.myBind = function(context = window){
let argument = [...arguments].slice(1);
let fn = this;
var fbound = function () {
fn.call(this instanceof fn ? this : context, ...argument,...arguments);
}
fbound.prototype = this.prototype;
return fbound;
}
let sayNameB = sayName.myBind(a,20);//hello 20 tang
let s2 = new sayNameB('tang2');
console.log(s2)
12.常见的跨域解决方案
参考博客
1.为什么会有跨域的问题?
因为同源策略的限制,同协议同域名同端口号的资源才可以互相通信,为了保障用户的安全才限制跨域。
2.接口请求跨域的解决方案?
- get请求:jsonp,利用script标签的src属性不受跨域影响的原理
- post请求:
- 后台配置cors
3.dom操作的跨域
- HTML5 postmessage
13.浏览器兼容性的解决方案
https://juejin.im/post/59a3f2fe6fb9a0249471cbb4#heading-8
- 1.抹平不同浏览器标签的默认样式
- 2.引入html5shiv.js文件,解决ie9以下对html5新增标签不识别的问题
- 3.引入respond.js文件,解决ie9以下不支持CSS3 Media Query的问题
- 4.添加浏览器css兼容前缀,-o-,-ms-,-moz-,-webkit-,