[面试]-JavaScript常见面试题

163 阅读17分钟

前言

记录一些个人认为比较常见的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

WX20220920-172214.png

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操作