面试题汇总 (一)-前端基础

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-,
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351