前言
记录一些个人认为比较常见的JavaScript相关的常见面试题以及个人理解答案,如有错误请见谅以及指出,谢谢
1.1、 Javascript数据类型
- 原始类型:
Boolean,Null,undefined,Number,String - 引用类型:
Object,Array - typeof null 是object, 是因为不同对象在底层都是二进制,而object和null的二进制表示都全是0,所以返回了object
1.2、数据类型判断
- 1、typeof 对于原始类型来说,除了null都可以显示正确的类型,引用类型则除了函数都是返回object
typeof 1 // number
typeof '1' // string
typeof undefined // undefined
typeof null // object 变量在底层都表示为二进制,在js中二进制前三位都为0的话会被判断为object类型,null的二进制全是0
typeof true // boolean
typeof [] // object
typeof {} // object
typeof console.log // function
- 2、 instanceof 可以判断对象的类型,是根据原型链来判做判断依据的
var a = 'test';
a instanceof String // false
var b = new String('test');
b instanceof String // true
var c = function(){};
var d = new c();
d instanceof c // true
- 3、 Object.prototype.toString
Object.prototype.toString.call('') // [object String]
Object.prototype.toString.call(1) // [object Number]
Object.prototype.toString.call(true) // [object Boolean]
Object.prototype.toString.call(null) // [object Null]
Object.prototype.toString.call({}) // [object Object]
Object.prototype.toString.call([]) // [object Array]
Object.prototype.toString.call(new RegExp()) // [object RegExp]
Object.prototype.toString.call(new Error()) // [object Error]
Object.prototype.toString.call(document) // [object HTMLDocument]
Object.prototype.toString.call(window) // [object Window]
1.3、this指向
- 1、在非严格模式下,全局作用域下的普通函数的this指向window,严格模式下,this指向undefined
- 2、在对象中,this指向被调用的对象
- 3、在构造函数中,this指向实例对象
- 4、在箭头函数中,this指向外层作用域的this
// 在函数中直接使用,指向window
function get(){
console.log(this)
}
get('1') // 也等于 get.call(window, '1') 打印window
// 函数作为对象的方法被调用,谁调用我,我指向谁
var person = {
name: '张三',
run: function(age){
console.log(`${this.name}今年${age}岁`);
}
}
person.run(30); // 指向person, 等于person.run.call(person, 30) 打印张三今年30岁
// 在箭头函数中的this是定义函数的时候绑定,而不是在执行函数的时候绑定
var x = 1;
var obj = {
x: 2,
say: () => { console.log(this.x) }
}
obj.say() // 1
/**
箭头函数的this,是继承自父级执行上下文的this,
如上的this.x, 箭头函数本身与say平级,也就是箭头函数本身所在的对象为obj,而obj的父级执行上下文是window,所以此时this指向window
*/
1.4、 == 和 ===
== 和 === 的区别是 == 不会判断双方的数据类型,而 === 会判断数据类型
1.5、闭包
什么是闭包
闭包是可以读取其他函数内部变量的函数
闭包的特性
- 函数嵌套函数
- 函数内部可以引用外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
闭包的作用
闭包最常见的两个用处是 读取函数内部的变量 和 让变量的值始终保持在内存中
1.6、内存泄漏
什么是内存泄漏
指的是你用不到(访问不到)的变量,却依旧占据这内存空间,不会再被利用起来,就是内存泄漏
闭包造成内存泄漏
闭包本身不会造成内存泄漏,主要是因为旧版浏览在使用完闭包后,回收不了闭包里面引用的变量,所以造成内存泄漏
常见的内存泄漏
- 意外的全局变量
- 被遗忘的计数器或者回调函数
- 脱离DOM的引用
- 闭包
1.7、事件循环 Event Loop
单线程
- Javascrpt是
单线程,也就是同一时间只能做一件事 任务队列- 单线程就意味着,所有任务都需要排队,前面一个任务结束后才执行下一个任务- 所有任务可分为
同步任务和异步任务
什么是事件循环
- 1、所有同步任务都在主线程上执行,行程一个
执行栈 - 2、主线程之外,还存在一个
任务队列(消息队列),只要异步任务有了运行结果,就在这个任务队列中放置一个事件 - 3、一旦执行栈中所以的同步任务执行完毕,系统会读取任务队列,看看里面有哪些事件,那些对应的异步任务,就会结束等待状态,进入执行栈,开始执行
- 4、主线程不断重复上面3步
事件循环机制简单说明执行顺序
-
1、浏览器的事件循环由一个宏任务队列+多个微任务队列组成。
-
2、首先,执行第一个宏任务:全局Script脚本。产生的的宏任务和微任务进入各自的队列中。执行完Script后,把当前的微任务队列清空。完成一次事件循环。
-
3、接着再取出一个宏任务,同样把在此期间产生的回调入队。再把当前的微任务队列清空。以此往复。
-
4、宏任务队列只有一个,而每一个宏任务都有一个自己的微任务队列,每轮循环都是由一个宏任务+多个微任务组成。
异步任务
-
异步任务是通过回调函数实现的,所有回调函数都是异步任务,一般有3种类型
- 普通事件,比如click,resize等
- 资源加载,如load等
- 定时器,如setTimeout,setInterval等
-
异步任务可细分为
宏任务和微任务- 微任务包括: Promise,mutationObserve,Process.nextTick
- 宏任务,非微任务的都是宏任务,如script,setTimeout,setInterval,SetImmediate,I/O,DOM事件,Ajax等
1.8、进程、线程、执行栈
进程、线程是CPU工作时间片的一个描述
- 进程: CPU在运行指令及加载和保存上下文所需要的时间
- 线程:是进程中更小的单位,描述了执行一段指令所需的时间
执行栈
- 执行栈是一个存储函数调用的栈结构,遵循先进后出的原则,js代码执行就是往执行栈放入函数
1.9、原型和原型链
原型
任务对象实例都有原型,也叫原型对象,这个原型对象由对象的内置属性 __proto__指向它的构造函数的prototype指向的对象,即任务对象都是由一个构造函数创建的,但不是每一个对象都有prototype,只有方法才有prototype
原型链
当访问一个引用类型的属性时,在自身属性找不到这个属性时,会从隐式原型 __proto__上找,在这个原型找不到时,就会到这个原型的下一级原型上找,如此形成一个链式,就叫原型链
// prototype: 显式原型
// __proto__: 隐式原型
class Person{
constructor(name){
this.name = name
}
drink() {}
}
class Teacher extends Person {
constructor(name){
this.name = name
},
teach(){
console.log(`我是${this.name}`)
}
}
const teacher = new Teacher("陈老师)
teacher.teach() // 我是陈老师,teaher自身没有teach方法,其原型
// teacher.__proto__ === Teacher.prototype
1.10、变量提升和函数提升
javascript中,函数以及变量的生命会提升到函数的最顶部,这个叫函数的 函数提升和变量提升
// 变量提升
console.log(a)
var a = 1
// 以上代码实际编译时,顺序如下
var a;
console.log(a)
a = 1
// 函数提升,函数提升只会提升函数声明,不会提升函数表达式
console.log(fn1) // [Function: fn1]
fn1(); // fn1
console.log(fn2); // undefined
fn2(); // typeError: fn2 is not a function
function fn1() {console.log('fn1')}
var fn2 = function(){console.log('fn2')}
1.11、防抖和节流
防抖: 对于短时间内连续触发的事件,让其在一个时间期限内,只执行一次事件,如滚动事件
function debounce(fn, time){
let timer = null;
return function(){
if(timer){ clearTimeout(timer) }
timer = setTimeout(fn, time)
}
}
debounce(todo, 1000); // 1秒内只执行一次todo
节流: 如果一个事件在短时间内大量触发,让它执行一次后,在指定时间内不再执行,如实时搜索框input事件
function throttle(fn, time){
let valid = true
return function(){
if(!valid) return false; // 处于暂停执行时间
valid = false
setTimeout(()=>{
fn()
valid = true
}, time)
}
}
throttle(todo, 1000); // 执行todo后1秒内不再执行
1.12、事件冒泡和事件委托
事件冒泡
一个事件触发后,会在子元素和父元素之间传播,传播分为3个阶段
- 捕获阶段 - 从window对象传导到目标节点,这个阶段不会响应任何事件
- 目标阶段 - 在目标节点上触发
- 冒泡阶段 - 从目标节点传导回window对象
事件委托(事件代理)
事件委托是利用事件冒泡,只制定一个事件处理程序就可以管理某一列子元素的所有事件
比如把原本 item子元素列表的响应事件定义到父元素,由父元素根据实际情况判断触发哪个item子元素
1.13、es6新特性
const,let,模板字符串,箭头函数,函数的默认参数值,对象和数组的解构赋值,for……of,for……in,展开运算符等
1.14、垃圾回收机制
- JS的垃圾回收机制是为了防止内存泄漏,间歇的不定期的寻找到不再使用的变量,并释放它们所指向的内存
- 垃圾回收的方式有
- 1、标记清除算法 - 标记阶段给活动的对象做上标记,清除阶段把没有标记的对象销毁(缺点:内存碎片化,分配速度慢)
- 2、引用计数算法 - 如果没有引用指向该对象,则销毁,现在很少使用这算法了
1.15、new操作符做了哪些事情
- 1、创建一个空对象,并且this变量引用该对象,同时继承了该函数的原型
- 2、属性和方法被加入到this引用的对象中
- 3、新创建的对象由this所引用,最后隐式地返回this
一句话概括 - 新建一个空对象,这个对象原型指向构造函数的prototype,执行构造函数后返回这个对象
1.16、bind,apply,call
- call 、apply、bind都是改变this指向的,不同在于他们的调用方式和调用参数不一样
A.apply(B, [a,b])
A.call(B, a, b)
A.bind(B,a,b)()
1.17、箭头函数
- 箭头函数是ES6的API
- 箭头函数没有prototype,所以箭头函数本身没有this
- 因为没有this,所以无法使用call/apply/call去改变this的指向
- 箭头函数不绑定arguments,取而代之用rest参数代替去访问箭头函数的参数列表
- 箭头函数不能用作generator函数,不能使用yeild关键字
1.18、构造函数
- 构造函数和普通函数创建方式一样,但调用方式不同 var a = new A()
- 构造函数对比普通函数的作用不一样,构造函数是用来创建实例对象的
- 构造函数的this指向它创建的对象实例
1.19、Promise
promise常用方法
- resolve()
- reject()
- then()
- catch()
- race() - 多个任务同时执行,返回最先执行结束的任务结果,不管成功还是失败
- all() - 多个任务同时执行,只有全部成功才返回成功结果,否则都是返回失败结果
promise的3种状态
- pending - 初始状态,也叫等待状态
- fulfiled - 成功状态
- rejected - 失败状态 状态一旦改变,就不会再变,创造promise实例后,它会立即执行
promise的特点
- 1、Promise对象的状态不受外界影响
- 2、Promise的状态是不可逆的
promise 的缺点
- 无法取消promise,创建后立即执行,无法中途取消
- 如果不设置回调函数,promise内部会抛出错误,不会反映到外部
- 当处于pending状态时,无法得知目前是在哪一阶段
promise解决的问题
- 解决回调地狱、难以维护的代码
- promise 支持多并发,获取并发请求中的数据
1.20、generator yield
generator:生成器,是ES6的新内容,作为异步编程的解决方案,用来解决异步任务- 用法是在函数上加上*,可以让我们在函数执行的任意地方暂停,下一步执行则使用next,与yield关键字一起使用
- yield 表达式本身没有返回值,或者返回undefined
- next,方法可以带一个参数,这个参数会被当做上一个yield的返回值
function* funA(){
yield console.log(1)
yield console.log('2')
}
function run(){
console.log(3)
}
const iter = funA()
iter.next(); // 1
run(); // 3
iter.next(); // 2
1.21、async await
- async 是ES7新出的特性,是一个异步编程的解决方案, 是 generator 函数的语法糖,返回值是promised对象
- async 对应的是*
- await 对应的是yield
1.22、cookie/locaStorage/settionStorage/session
- cookie数据始终在同源的http请求中携带,会在浏览器和服务器直接来回传递,而localStorage只存本地
- 存储大小限制不同,cookie不能超过4k左右,而localStorage和sessionStorage可以到5M
- 数据有效期不同,sessionStorage仅在的当前窗口关闭前有效,localStorage始终有效,cookie也是所有同源窗口共享的
- cookie数据存放在客户的浏览器上,session存储在服务器
1.23、script标签的defer和async
- 1、不设置async和defer,那么脚本会同步下载并执行,会阻塞后续dom的渲染
- 2、设置了defer,脚本异步加载,加载完毕后,在触发domContentloader事件之前执行
- 3、设置async,脚本异步加载,加载完毕后立即执行,并阻塞后续dom渲染,不影响domContentLoaded事件触发
1.24、设计模式
1、外观模式
- 就是把多个子级组件中复杂逻辑进行抽离,然后提供一个统一、简洁、易用的API
- 优点: 减少组件的相互依赖性,提高灵活性
- 缺点:要修改的时候可能会麻烦,因为多个地方用到,但修改不一定所有地方都要跟着变
2、工程模式
- 定义一个对象接口,由子类决定实例化哪个类,然后使子类可以创建或者重写指定的对象,就是一些经常反复使用的合计
- 优点:提高复用性,代码容易理解,不管过程只管结果
function demo1(name){console.log(name)}
function demo2(name){console.log(name)}
function exportDemo(demoName, name){
switch(demoName){
case 'demo1': demo1(name);break;
case 'demo2': demo2(name);break;
}
}
exportDemo('demo1','名字')
3、单例模式
某个功能可以贯穿整个系统去执行的,比如登录框,vuex,redux的store
4、观察者模式(发布订阅模式)
全局定义一个可以发布、订阅的对象、包含on/emit/off/store,比如vue的响应式
5、代理模式
就是为一个对象找一个替代对象,以便对原对象进行访问,使用代理的原因是我们不想对原对象直接进行操作
1.25、Map和Set的区别,Map和Object的区别
- Set:是一种数据结构,类似数组,成员唯一,Set本身是一种构造函数
- Map:是一组键值对的结构
- Set和Map主要的应用场景在于 数据重组和数据储存
- Set是一种叫做集合的数据结构,Map是一种叫做字典的数据结构
Set和Map区别
- 相同点:集合、字典可以存储不重复的值
- 不同点:集合Set是以 [value,value]的形式存储元素,字典Map是以 [key,valuye]的形式存储
Map和Object区别
- 在Object中,key必须是简单数据类型(整数、字符串),而Map可以是js支持的所有数据类型
- Map元素的顺序遵循插入的顺序,而Object没有这个特性
- Map继承自Object对象
2.1、浏览器输入URL后发生了什么
- 1、DNS域名解析,域名解析为IP地址
- 2、浏览器与目标服务器建立一个TCP链接
- 3、浏览器向服务器发送请求报文
- 4、服务器向浏览器发送响应报文
- 5、浏览器进行渲染
- 6、关闭TCP链接
2.2、跨域的方式
- 1、jsonp - 利用
<script>标签没有跨域限制的漏洞,可以获取到json数据,简单兼容性好,仅支持get方式,容易被攻击 - 2、CORS - 服务器端配置
- 3、postMessage - HTML5 XML httpRequest的API
- 4、websocket - HTML5的持久化协议,基于TCP协议,是一种双向通信协议,建立连接后,两端都能主动向对方发送或接受数据,一般用Socket.io,封装了websocket的接口
- 5、Node中间件代理
- 6、Nginx 反向代理
2.3、浏览器渲染步骤
- 1、HTML转换为DOM
- 2、CSS转换为浏览器可理解的styleSheets,计算DOM节点的样式
- 3、创建布局树,计算元素的布局信息
- 4、对布局树进行分层,构建分层树
- 5、为每个图层生产绘制列表,并将其体积到合成线程
- 6、合成线程将图层转换为图块,进而将图块转为成位图
- 7、合成线程发送绘制命令给浏览器
- 8、浏览器根据绘制命令生产页面,并显示到显示器上
2.4、页面渲染优化
- 1、HTML文档结构层次尽量少,最好不深于6层
- 2、脚本尽量放最后
- 3、样式结构层次尽量简单
- 4、脚本减少DOM操作,减少回流和重绘
- 5、减少js修改样式,通过修改class名称解决
2.5、强缓存和协商缓存
- 强缓存:第一次请求资源时在http响应头设置一个过期时间,在时效内都将直接从浏览器获取,比如cache-control和expires
- 协商缓存:通过http响应头字段etag或者last-modified等判断服务器上资源是否修改,有修改则重新获取,没修改则从浏览器缓存获取
2.6、post和get区别
- 1、get参数通过URL传递,post放在body中
- 2、get请求在URL中传递的参数有长度限制,post没有
- 3、get在浏览器回退是无害的,而post会再次提交请求
- 4、get请求会被浏览器主动cache,post不会
- 5、get只接受ASCLL字符,post没有限制
- 6、get产生一个tcp数据包,post产生两个tcp数据包
2.7、HTTP和HTTPS区别
2.8、同源策略
同源策略指的是 协议、域名、端口相同,是一种安全协议
2.9、前端安全方法
-
XSS脚本攻击: 跨站脚本攻击,是常见和基本的攻击web网站的方法,攻击者通过注入非法的html标签或者javascript代码,从而当用户浏览该网页时,控制用户浏览器
-
XSS防御方法
-
httpOnly: 在cookie中设置httpOnly属性,使js脚本无法读取cookie信息
-
前端加上输入检查,后端做过滤检查
-
对用户输入的数据做标签转换
-
CSRF: 跨站点请求伪造,冒充用户发起请求,完成一些违背用户意愿的事,如修改用户信息等
-
CSRF防御方法
-
验证码 - 强制用户必须与应用进行交互,才完成请求
-
表单提交尽量使用post,get相对容易被哪来做CSRF攻击
-
请求来源限制
-
token验证 - 默认适合的方案
2.10、html5新特性
- 新增了语义化标签 - header/nav/footer/section等
- 增强型表单 - 新增input的number/url/email/range/color/date等输入型控件,还添加了placeholder/min/max/height/width等表单属性
- DOM拓展 - 新增getElementByClassName、classList属性等
- 原生拖放 - draggable标识是否可拖拽
<div draggable="true">James</div> - 媒体元素 - audio和video
- web socket - 提供全双工、双向通信
- web storage - localStorage和sessionStorage
- 地理位置 - Geolocation API
- canvas绘图
2.11、css3新特性
- 过渡 - transition
- 动画 - animation
- 形状转换 - transform
- 选择器 - :nth-child,:last-child,:root,:checked等 选择器参考手册
- 阴影 - box-shadow
- 边框 - border-image,border-radius
- 背景 - background-clip/background-origin/background-size
- 文字 - word-break(换行),text-overflow
- 颜色 - rgba
- 渐变 - gradient
- 滤镜 - filter
- flex - 弹性布局
- 媒体查询 - @media
2.22、常规前端性能优化
content方面
- 减少HTTP请求
- 减少DNS查询 - DNS缓存
- 避免重定向 - 多余的中间访问
- 使用ajax可缓存
- 非必须组件延迟加载
- 未来所需组件预加载
- 减少DOM元素数量
- 将资源放到不同域下 - 浏览器同时从一个域下载资源的数目有限
- 避免404
Server方面
- 使用CDN
- 添加Expires或者cache-control响应头
- 对组件使用gzip压缩
- 配置etag
- ajax使用get进行请求
- 避免空src的img标签
css方面
- 将样式表放到页面顶部
- 不使用CSS表达式
- 不使用IE的Filter
javascript方面
- 将脚本放到页面底部
- 将javascript和css从外部引入
- 压缩javascript和css
- 删除不要的脚本
- 减少DOM操作