持续更新中
-
同步和异步的区别
js是单线程的,同步代码会阻塞运行,异步不会阻塞
-
手写Promise加载图片
loadImg(url) {
return new Promise((resolve,reject) => {
const img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
resolve('加载出错了')
}
img.src = url
})
}
-
讲讲event loop(事件循环/事件轮询)机制
- 同步代码,一行一行放在Call Stack执行
- 遇到异步代码,会先记录下,等待时机(定时,网络请求等)
- 时机到了,就移动到Callback Queue
- 当Call Stack 为空(即同步代码执行完),event loop开始工作
- 轮询查找Callback Queue,如有任务就将其移动到Call Stack 执行
- 然后继续轮询查找
-
JS如何执行
- 从前到后,一行一行执行
- 如果某一行执行报错,则停止下面代码的执行
- 先把同步代码执行完,再执行异步代码
-
Promise的三种状态
- pending 状态下,不会触发then和catch
- resolved 状态下,会触发后续的then回调函数
- rejected 状态下,会触发后续的catch回调函数
-
Promise中的then方法和catch方法对状态的影向
- then正常返回resolved,里面有报错则返回rejected
- catch正常返回resolved,里面有报错则返回rejected
-
async/await 和 Promise 的关系
- 执行async函数,返回的是Promise对象
- await 相当于Promise的then方法
- try…catch 用来捕获异常,替代了Promise的catch
-
一道笔试题,输出执行结果
async function test1() {
console.log('test1 start') //2
await test2()
console.log('test2 end') //5
await test3()
console.log('test3 end') //7
}
async function test2() {
console.log('test2 start') //3
}
async function test3() {
console.log('test3 start') //6
}
console.log('script start') //1
test1()
console.log('script end') //4
-
宏任务和微任务
- 宏任务: setTimeout,setInterval, Ajax, DOM事件,setImmediate,I/O(Node.js)
- 微任务: Promise.then,MutaionObserve, process.nextTick
注意: 定时器的时间是指从执行定时器开始计时的,当时间到了之后就会把这个任务放到Callback Queue中
二者的执行时间:
微任务 --》 DOM渲染 --》 宏任务执行顺序是:
- Call Stack清空
- 执行当前的微任务
- 尝试渲染DOM
- 触发event loop机制
验证执行顺序
// 修改 DOM
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
.append($p1)
.append($p2)
.append($p3)
// // 微任务:渲染之前执行(DOM 结构已更新)
Promise.resolve().then(() => {
const length = $('#container').children().length
alert(`micro task ${length}`)
})
// 宏任务:渲染之后执行(DOM 结构已更新)
setTimeout(() => {
const length = $('#container').children().length
alert(`macro task ${length}`)
})
- 一道题目,输出执行顺序
async function async1 () {
console.log('async1 start') // 2
await async2()
console.log('async1 end') // 6
}
async function async2 () {
console.log('async2') // 3
}
console.log('script start') // 1
setTimeout(function () {
console.log('setTimeout') // 8
}, 0)
async1()
new Promise (function (resolve) {
console.log('promise1') // 4
resolve()
}).then (function () {
console.log('promise2') // 7
})
console.log('script end') // 5
- 手写Promise
class myPromise{
state = 'pending' //状态 'pending' 'fulfilled' 'rejected'
value = undefined //成功回调的值
reason = undefined //失败回调的值
resolveCallbacks = [] // pending状态下,存储成功的回调
rejectCallbacks = [] // pending状态下,存储失败的回调
constructor(fn) {
const resolveHandler = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.resolveCallbacks.forEach(fn => fn(this.value))
}
}
const rejectHandler = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.rejectCallbacks.forEach(fn => fn(this.reason))
}
}
try{
fn(resolveHandler,rejectHandler)
} catch (err) {
rejectHandler(err)
}
}
then(fn1, fn2) {
fn1 = typeof fn1 === 'function' ? fn1 : (v) => v
fn2 = typeof fn2 === 'function' ? fn2 : (e) => e
if (this.state === 'pending') {
const p1 = new myPromise((resolve,reject) => {
this.resolveCallbacks.push(() => {
try {
const newValue = fn1(this.value)
resolve(newValue)
} catch (e) {
reject(e)
}
})
this.rejectCallbacks.push(() => {
try{
const newReason = fn2(this.reason)
reject(newReason)
} catch (e) {
reject(e)
}
})
})
return p1
}
if (this.state === 'fulfilled') {
const p1 = new myPromise((resolve,reject) => {
try {
const newValue = fn1(this.value)
resolve(newValue)
} catch (e) {
reject(e)
}
})
return p1
}
if (this.state === 'rejected') {
const p1 = new myPromise((resolve,reject) => {
try{
const newReason = fn2(this.reason)
reject(newReason)
} catch (e) {
reject(e)
}
})
return p1
}
}
catch(fn) {
return this.then(null, fn)
}
}
myPromise.resolve = function(value) {
return new myPromise((resolve,reject) => resolve(value))
}
myPromise.reject = function(reason) {
return new myPromise((resolve,reject) => reject(reason))
}
myPromise.all = function(promiseList = []) {
if(!Array.isArray(promiseList)) throw new typeError('请输入一个数组')
const p1 = new myPromise((resolve,reject) => {
let resolvedCount = 0
let result = []
const length = promiseList.length
promiseList.forEach((p,index) => {
p.then(data => {
result[index] = data
resolvedCount ++
if (length === resolveCount) {
resolve(result)
}
}).catch(err => {
reject(err)
})
})
})
return p1
}
myPromise.race = function(promiseList = []) {
if(!Array.isArray(promiseList)) throw new typeError('请输入一个数组')
let resolved = false
const p1 = new myPromise((resolve,reject) => {
promiseList.forEach(p => {
p.then(data => {
if(!resolved) {
resolve(data)
resolved = true
}
}).catch(err => {
if(!resolved) {
reject(data)
resolved = true
}
})
})
})
return p1
}
补充:catch后无法再传递下去
- 获取DOM节点的几种方式
document.getElementById() //单个
document.getElementsByTagName() //集合
document.getElementsByClassName() //集合
document.querySelector() // 单个
document.querySelectorAll() //集合
- property和attribute的区别
- property: 修改对象属性,但不会体现到html结构中
- attribute: 修改html属性,会改变html结构
<div>s</div>
const b = document.querySelector('div')
b.a = 100 //不会体现在Html中
b.setAttribute('b', 100) //会体现的html中
-
DOM性能优化
- DOM查询缓存
- 对于DOM的多次操作,使用文档片段(createDocumentFragment),操作完成后再将该片段插入到DOM中
- 对于要操作的DOM,先克隆(cloneNode),对克隆的节点进行操作,然后再使用replaceChild方法将克隆节点替换原先的
- 对于DOM的多种样式操作,可以拼接在一起一次操作
-
location的一些相关知识
- href: 完整的url
- protocol: url协议
- pathname: url路径名
- host: 主机名和端口
- hostname: 主机名
- hash: url的hash
- search: url的查询部分
-
编写一个通用的事件监听函数
<!-- 普通绑定和代理绑定 -->
function bindEvent(ele, type, selector, fn) {
if (fn == null) {
fn = selector
selector = null
}
ele.addEventListener(type, event => {
const target = event.target
if(selector) {
if (target.matches(selector)) {
fn.call(target, event)
}
} else {
fn.call(ele, event)
}
})
}
补充: event.preventDefault(); 阻止默认事件发生
-
描述事件冒泡的流程
在嵌套的父子元素中,子元素的事件会一层一层向上传递,一直到顶层元素window对象
补充:event.stopPropagation(); 可以阻止捕获和冒泡 -
什么是事件代理
事件代理就是在父元素上绑定事件,然后通过父元素的事件来处理子元素的相关操作。
好处是代码简洁,减少浏览器内存 -
手写XMLHttpRequest
const xhr = new XMLHttpRequest()
xhr.open('GET', '/api', true) // true代表异步
let res
xhr.onreadystatechange = function () {
if(xhr.readState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText)
res = xhr.responseText
} else {
console.log('其他情况')
}
}
}
xhr.send(res)
-
readyState的几种状态
- 状态0表示尚未调用open方法
- 状态1表示open方法已经被调用
- 状态2表示send方法已经被调用,header已经被接收
- 状态3表示下载中,responseText中有部分内容
- 状态4表示下载完成
-
什么是同源策略
- 同源是指:协议,端口,域名三者必须保持一致
- 当Ajax发送请求时,浏览器要求当前网页和server必须同源
-
那些情况可以无视同源策略
- 加载图片
- 加载css
- 加载js
-
如何理解jsonp
- js是可以跨域的,服务器可以任意动态拼接数据返回,只要符合js文件格式。
- 使用js发送跨域请求,服务器根据发送的请求返回数据到callback函数中,然后前端定义好相应的函数来执行返回的函数即可
-
关于axios了解那些
- 关于axios,是一个基于promise的请求库,可以用在Node和浏览器中
- 在浏览器中创建XMLHttpRequest,在node中创建http请求
- 拦截请求和响应
-
如何封装axios
- 获取axios实例,实例里面可以设置baseurl和timeout超时时间还有headers
- 设置拦截器,在请求拦截中可以设置url,method,baseurl,transformRequest(在发送前转换数据格式),headers,params以及data等
- 在响应拦截中可以设置data,status,statusText,headers,confing和request(生成此次响应的请求)
-
关于cookie,localStorage和sessionStorage的理解
- localStorage和sessionStorage最大可存储5M。API可直接使用setItem,getItem和removeItem。并且都不会随着http请求发送出去
- localStorage数据会永久有效,除非手动或代码删除
- sessionStorage数据只存在于当前会话中,关闭页面或浏览器都会被清除
- cookie一般由服务器生成,可以设置失效时间。若没有设置时间,则关闭浏览器就会失效。存放数据大小为4k左右。会随着HTTP请求发送出去。通过document.cookie来修改
-
HTTP常见的状态码有那些
- 1xx 服务器收到请求
- 2xx 请求成功
- 3xx 重定向
- 4xx 客户端错误
- 5xx 服务端错误
常见的状态码: - 200 请求成功
- 301 永久重定向(配合location,浏览器自动处理)
- 302 临时重定向(配合location,浏览器自动处理)
- 304 资源未被修改
- 404 资源未找到
- 403 没有权限
- 500 服务器错误
- 504 网关超时
-
HTTP常见的headers有那些
Request Headers- Accept 浏览器可接收的数据格式
- Accept-Encoding 浏览器可接收的压缩算法,如gZIP
- Accept-Languange 浏览器可接收的语言,如zh-CN
- Connection: keep-alive 一次TCP连接重复使用
- cookie
- Host
- User-Agent 浏览器信息
- Content-type 发送数据的格式,如application/json
Response Headers
- Content-type 返回数据的格式,如application/json
- Content-length 返回数据的大小,比如多少字节
- Content-Encoding 返回数据的压缩算法,如gzip
- Set-Cookie 设置cookie
-
什么是Restful API
传统的api设计是把每个url当成一个功能,而Restful api是把每个url当成一个唯一的资源
通过get(获取数据),post(新建数据),patch/put(更新数据)以及delete(删除数据)来判断对资源的操作,尽量不使用url参数 -
描述一下HTTP的缓存机制
为什么要使用缓存? 可以减少网络请求的数量和体积,提高页面显示速度,使用户体验更好那些资源可以被缓存? 静态资源(js,css,img)
什么是强制缓存?
设置Cache-control(Response Headers中):max-age(设置过期时间),no-cache(强制向服务器发送请求,由服务器判断该资源是否有更新,有则返回新内容,没有则使用缓存),no-store(不用本地缓存,也不使用服务端的缓存措施),private(针对个人用户),public(允许中间代理或路由进行缓存处理)什么是协商缓存?
由服务器判断客户端资源是否和服务端资源一样,一致则返回304,否则返回200和最新的资源
第一次请求时,会返回资源和资源标识,通过判断这个资源标识来辨别资源是否有更新
资源标识(Response Headers中):- Last-Modified 资源的最后修改时间
- Etag 资源的唯一标识
- 优先使用Etag
- Last-Modified只能精确到秒级
- 如果资源被重复生成,而内容不变,使用Etag更精准
-
刷新状态对于缓存的影响
- 正常操作: 地址栏输入url,跳转链接,前进后退等 --> 强制和协商都有效
- 手动刷新: F5,点击刷新按钮,右击菜单刷新 --> 强制失效,协商有效
- 强制刷新: ctrl+f5 --> 强制和协商都失效
-
常用的git命令
- git clone 克隆文件到本地
- git status 查看改动的文件
- git diff 查看具体的改动地方,后可接文件名
- git add + 文件名 提交文件到缓存区
- git log 查看提交记录
- git show + commit的编号 可以查看修改的内容
- git checkout + 文件名 可以回溯到文件提交前的状态
- git push -u origin master 提交到主线上
- git pull origin master 拉取主线上最新的代码
- git checkout -b + 分支名 使用该分支
- git add . 添加文件
- git commit -m ‘注释’
- git push origin + 分支名 提交该分支的代码到线上
- git checkout master 切换到master主线
- git merge + 分支名 合并该分支内容到主线上
- git push origin master 提交主线上的内容到线上
-
移动端h5页面抓包工具
- window 一般用fiddler
- Mac OS 一般用charles
-
常见的Linux命令
- ssh work@ + 地址 可以远程登录到服务器上
- ls ll 可以查看文件
- ls -a 可以查看隐藏文件
- mkdir 创建文件夹
- rm -rf 删除文件夹和里面的所有文件
- mv 可以修改文件名或移动文件
- cp 可以拷贝文件
- touch + 文件名 可以新建文件
- cat + 文件名 可以查看文件内容,但不能修改
- vim + 文件名 可以查看文件内容,可以修改
-
webpack基本配置
- entry对象 入口文件
- output对象 输出文件
- plugins数组 放入插件
- module对象中有rules数组,可以指定检测规则。每个规则是一个对象,有test正则、use数组、include(包含文件)和exclude(不包含文件)。use数组中也是对象,里面有loader和options对象,而options对象中又有name,outputpath和limit等
- 在开发模式下,有mode模式为development,devserve启动服务,devtool错误定位为’eval-cheap-module-source-map’
- 在生产模式下,有mode模式为production,devtool错误定位为 ‘source-map’
-
基本的webpack项目配置中TreeShaking/热更新/Shimming/CodeSplitting
- TreeShaking
- 描述:用来移除没有引用的代码
- 配置:在package.json中配置"sideEffects",当为false时表示删除任何未引用的代码。也可以变为数组,在里面添加不能删除的代码,比如’.css,.scss’
- 热更新
- 描述:只能用在开发环境中
- 配置:devServer中配置hot,host和static
- Shimming
- 描述:提供预置依赖,即在某个地方会用到这个库,但是没有引入进去
- 配置: 在plugins中添加new webpack.ProvidePlugin({
_:“loadsh”
})
- CodeSplitting
- 描述:把引入的库单独放一个文件,把业务代码单独放一个文件。这样引入的库的代码不用重新加载,只需要重新加载业务代码部分即可
- 配置:在optimization中配置runtimeChunk,usedExports,providedExports以及splitChunks即可
- TreeShaking
-
从输入url到渲染出页面的整个过程
- 加载资源的形式 HTML,媒体文件,js和css
- 加载资源的过程
- DNS解析: 把域名解析为IP地址
- 浏览器根据IP地址向服务器发送HTTP请求
- 服务器处理HTTP请求,并返回数据给浏览器
- 渲染页面的过程
- 根据HTML代码生成DOM tree (om指对象模型)
- 根据css代码生成CSSOM
- 将DOM tree和 CSSOM整合成 Render Tree(渲染树)
- 根据Render tree 渲染页面
- 遇到
<!-- 验证 -->
console.log(a) // function a() {}
var a= 100
function a() {}
----------------------------------
console.log(a) // function a() {}
var a= 100
function a() {}
console.log(a) // 100
-
typeof能判断那些类型
- undefined,string,number,boolean,symbol
- object (null == ‘object’)
- function
-
手写深度比较isEqual
function isObject(obj) {
return typeof obj === 'object' && obj !== null
}
function isSameType(obj1,obj2) {
return Object.prototype.toString.call(obj1) === Object.prototype.toString.call(obj2)
}
function isEqual(obj1,obj2){
if (!isObject(obj1) || !isObject(obj2)) {
return obj1 === obj2
}
if (!isSameType(obj1,obj2)) {
return false
}
if (obj1 === obj2) {
return true
}
const key1 = Object.keys(obj1)
const key2 = Object.keys(obj2)
if(key1.length !== key2.length) {
return false
}
for (const key in obj1) {
if (!obj2.hasOwnProperty(key)) {
return false
}
const res = isEqual(obj1[key],obj2[key])
if(!res) {
return false
}
}
return true
}
-
介绍下数组中的pop,push,unshift,shift
- pop
功能:去除数组中最后一个值
返回值:去除的值
影响:会改变源数组 - push
功能:在数组最后添加一个值
返回值:数组的长度
影响:会改变源数组 - unshift
功能:在数组的最前面添加一个值
返回值:数组的长度
影响:会改变源数组 - shift
功能:去除数组最前的一个值
返回值:去除的值
影响:会改变源数组
- pop
-
讲讲纯函数,以及那些是纯函数
纯函数的特征:- 不会改变源数组(没有副作用)
- 返回一个数组
纯函数有那些
- concat
- map
- filter
-----更新
-
数组slice和splice的区别
- 功能区别:slice – 切片, splice – 剪切
- 参数
- slice(start,end) 包括开始,但不包括结束
- splice(index,count,items…) 从index位置开始剪切,第二个参数表示要删除的个数,第三个参数表示要添加的值
- 返回值
- slice 返回新的数组
- splice 返回被删除的值组成的数组
- 是否为纯函数
- slice 纯函数
- spice 非纯函数
-
new Object() 和 Object.create()的区别
- new Object() 表示创建一个新对象
- Object.create() 是指将传入的对象作为新对象的prototype
const obj1 = {a:1,b:2}
const obj2 = new Object(obj1)
// obj2 === obj1
const obj3 = new Object({a:1,b:2})
// obj3 !== obj1
-
常见的正则表达式语法
- *表示0到多次
- ?表示0到1次
- +表示1到多次
- \d 匹配一个数字
- \D 匹配一个非数字
- \s 匹配一个空白符
- \S 匹配一个非空白符
- \w 匹配一个单字字符(字母、数字或者下划线) 等价于 [A-Za-z0-9_]
- \W 匹配一个非单字字符 等价于 [^A-Za-z0-9_]
-
手写字符串trim方法,保证浏览器的兼容性
if (!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^\s+/, '').replace(/\s+$/, '')
}
}
-
讲讲json
- json是一种数据结构,本质上是一段字符串
- json格式和js对象结构一致,对js语言更加友好
- window.JSON是全局对象。JSON.stringify将对象JSON化,JSON.parse将JSON对象化
-
手写函数用于解析url参数
<!-- 传统方式 -->
function query(name) {
const search = location.search.substr(1)
const reg = new RegExp(`(^|&)&{name}=([^&]*)(&|$)`,'i')
const res = search.match(reg)
if (res === null){
return null
}
return res[2]
}
<!-- URLSearchParams -->
function query(name) {
const search = location.search
const p = new URLSearchParams(search)
return p.get(name)
}
- 手写flatern
<!-- 嵌套一层数组 -->
const arr = [].concat.apply([],arr)
<!-- 多重数组 -->
function flatern(arr) {
if(!Array.isArray(arr)) return false
const result = arr.some(item => item instanceof Array)
if(!result) {
return arr
}
const res = Array.prototype.concat.apply([],arr)
return flatern(res)
}
- 手写数组去重
<!-- 传统方式 -->
function unique(arr) {
let res = []
arr.forEach(item => {
if(!res.includes(item)){
res.push(item)
}
})
return res
}
<!-- set -->
function unique(arr) {
const res = new Set(arr)
return [...res]
}
- 手写深拷贝
function deepClone(obj) {
if (typeof obj !== 'object' || obj == null) {
return obj
}
let result
if(obj instanceof Array) {
result = []
} else {
result = {}
}
for(const i in obj) {
if(obj.hasOwnProperty(i)) {
result[i] = deepClone(obj[i])
}
}
return result
}
- 值和引用类型
- 值存储在栈中
- 引用类型存储在堆中
-----更新
-
常见的假值
- 0
- NaN
- ‘’
- null
- undefined
- false
-
watch和computed的区别
- computed是计算属性,计算属性的返回值,可以直接使用;watch是监听器,监听某个值发生变化然后执行对应的回调函数
- computed中所依赖的属性值没有变化的时候,就不会再计算,而是直接使用之前的值。
- computed默认第一次加载的时候就开始监听;watch默认第一次加载不做监听
- computed必须有return,而watch没有
-
原型,原型链和继承 这篇文章讲的很简单易懂
- 简单版本:
- 原型是什么???就是函数的prototype指向的对象
- 原型继承又是什么??? 就是通过new调用函数创造的对象的内部[[prototype]]链接函数的prototype指向的对象/通过extends
- 原型链又是什么??? 对象之间通过prototype链接而形成的关系
- 详细了解:
- prototype:每个函数/class都有一个prototype属性,这个属性指向其原型对象
- proto:每个实例(除null外)都会有的属性,叫做__proto__,这个属性会指向该实例的原型
- constructor: 每个原型对象都有一个constructor属性,指向其关联的构造函数/class
- 函数/class、原型和实例的关系:每个函数/class都有一个原型对象,原型对象都包含一个指向函数/class的指针,而实例都包含一个指向原型对象的内部指针。
- 最顶层的对象原型是null:Object.prototype.proto === null
- 简单版本:
代码实践
<!-- 用class方式 -->
class Person {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name}在吃东西`)
}
}
class Student extends Person{
constructor(name,number) {
super(name)
this.number = number
}
study(){
console.log(`学号为${this.number}的学生${this.name}正在学习`)
}
}
class Teacher extends Person{
constructor(name,major) {
super(name)
this.major = major
}
teach() {
console.log(`${this.name}是教${this.major}的老师`)
}
}
// 实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.study() // 学号为100的学生夏洛正在学习
xialuo.eat() // 夏洛在吃东西
// 实例
const wanglaoshi = new Teacher('王老师', '语文')
console.log(wanglaoshi.name) // 王老师
console.log(wanglaoshi.major) // 语文
wanglaoshi.teach() // 王老师是教语文的老师
wanglaoshi.eat() // 王老师在吃东西
// 关系检测 ---> 都为true
console.log(xialuo.__proto__ === Student.prototype)
console.log(Student.prototype.constructor === Student)
console.log(Student.prototype.__proto__ === Person.prototype)
console.log(Person.prototype.constructor === Person)
console.log(Person.prototype.__proto__ === Object.prototype)
console.log(Object.prototype.constructor === Object)
<!-- 用 new 方式 -->
function Person(name) {
this.name = name
this.eat = function() {
console.log(`${this.name}在吃东西`)
}
}
function Student(name,number) {
Person.call(this,name)
this.number = number
this.study = function() {
console.log(`学号为${this.number}的学生${this.name}正在学习`)
}
}
Student.prototype = new Person()
function Teacher(name,major) {
Person.call(this,name)
this.major = major
this.teach = function() {
console.log(`${this.name}是教${this.major}的老师`)
}
}
Student.prototype = new Person()
// 实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name) // 夏洛
console.log(xialuo.number) // 100
xialuo.study() // 学号为100的学生夏洛正在学习
xialuo.eat() // 夏洛在吃东西
// 实例
const wanglaoshi = new Teacher('王老师', '语文')
console.log(wanglaoshi.name) // 王老师
console.log(wanglaoshi.major) // 语文
wanglaoshi.teach() // 王老师是教语文的老师
wanglaoshi.eat() // 王老师在吃东西
// 关系检测 --->除了第二个都为true
console.log(xialuo.__proto__ === Student.prototype)
console.log(Student.prototype.hasOwnProperty('constructor')) // false 表明Student.prototype没有'constructor'。它调用的是Person.prototype的'constructor'
console.log(Student.prototype.constructor === Person)
console.log(Student.prototype.__proto__ === Person.prototype)
console.log(Person.prototype.constructor === Person)
console.log(Person.prototype.__proto__ === Object.prototype)
console.log(Object.prototype.constructor === Object)
- js中的反射是什么?
- 在传统的面向类环境中,检查一个实例(JavaScript中的对象)的继承祖先(JavaScript中的委托关联)通常被称为内省或反射
- 方式:
function foo(){}
var a=new foo();
console.log(a instanceof foo);//true
function foo(){}
var a=new foo();
console.log(foo.prototype.isPrototypeOf(a));//true
- 手写一个简易的jQuery,考虑插件和拓展性
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector)
const length = result.length
for (let i = 0; i < length; i++) {
this[i] = result[i]
}
this.length = length
this.selector = selector
}
get(index) {
return this[index]
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i]
fn(elem)
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
// 扩展很多 DOM API
}
// 插件
jQuery.prototype.dialog = function (info) {
alert(info)
}
// “造轮子”
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 扩展自己的方法
addClass(className) {
}
style(data) {
}
}
// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))
-----更新
- this的不同应用场景,如何取值?
- this的运行机制:this是在运行时进行绑定的,不是在编写时绑定的,它的上下文关系取决于函数调用时的各种关系。this的绑定和函数声明的位置没有关系,只取决于函数的调用方式。就是说this指向是动态绑定的,但是总是指向调用函数的那个对象,而不管函数是声明在哪里
- 默认绑定:就是独立函数调用时,默认绑定到window
- 隐式绑定:当一个对象拥有或者包含这个函数的引用时,this就会指向这个对象
- 显示绑定:使用apply,call或bind方法,第一个参数传入this的指向,后面就是参数
- new绑定:
- 创建一个新的对象
- 对这个新对象进行prototype链接
- 函数的this会指向这个新对象
- 如果没有返回内容,则会自动返回这个新对象
- 优先级:new > 显示绑定 > 隐式绑定 > 默认绑定
- 箭头函数:this指向定义时所在的对象,不是运行时所在的对象
<!-- 默认绑定 -->
function fn1(){
fn1.a = 10
console.log(this.a)
}
const a = 1oo
fn1() // 100
<!-- 隐式绑定 -->
function fn2(){
console.log(this.a)
}
const obj = {
a: 10,
fn2
}
obj.fn2() // 10
注意: 当有多个嵌套时,只会绑定最初的那个。如:
function fn2(){
console.log(this.a)
}
const obj = {
a: 10,
fn2
}
const obj2 = {
obj,
a: 20
}
obj2.obj.fn2() // 10
<!-- 显示绑定 -->
function fn3(){
console.log(this.a)
}
const obj = { a: 10}
function fn4() {
return fn3.call(obj)
}
const a = 20
fn4() // 10
window.fn4() // 10
<!-- new绑定 -->
function fn5(a){
this.a=a;
}
const bar=new fn5(2);
console.log(bar.a);//输出 2
<!-- 箭头函数 -->
function fn6(){
setTimeout(function(){
console.log(this.a);
},1000);//输出1 --> 指向window
setTimeout(()=>{
console.log("====");
console.log(this.a);
},2000);//输出10 --> 指向obj
}
const a=1;
const obj={a:10}
fn6.call(obj);
- 手写bind函数
if(!Function.prototype.softBind){
Function.prototype.softBind=function(obj){
var fn=this;
var args=Array.prototype.slice.call(arguments,1);
var bound=function(){
return fn.apply(
(!this||this===(window||global))?obj:this,
args.concat.apply(args,arguments)
);
};
bound.prototype=Object.create(fn.prototype);
return bound;
};
}
- 实际开发中闭包的应用场景,举例说明
function createCache() {
const data = {}
return {
set: function(key,val) {
data[key] = val
},
get: function(key) {
return data[key]
}
}
}
- 闭包代码问题
<!-- 函数作为返回值 -->
function fn1() {
const a = 100
return function() {
console.log(a)
}
}
const a = 200
const fn2 = fn1()
fn2() // a = 100
<!-- 函数作为参数 -->
function fn1(fn) {
const a = 100
fn()
}
const a=200
function fn2() {
console.log(a)
}
fn1(fn2) // 200
// 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找
// 不是在执行的地方!!!
-----更新
-
有序和无序区别
- 有序:操作慢
- 无序:操作快
-
Map和Object区别
- API不同,Map可以用任意类型为key
- Map是有序结构(对象)
- Map操作同样很快
- Map可以用来关联两个不相干的数据
<!-- Map -->
const obj = {
a:1
}
const map = new Map([
['c' , '3'],
[obj, true]
])
map.set({a:1},1)
map.set(function aa(){},'2')
map.delete('c')
console.log(map.has(obj))
console.log(map.get(obj))
console.log(map)
- Set和数组区别
- API不同
- Set元素不能重复
- Set是无序结构,操作很快
const set = new Set([1,2,3,4,3])
console.log(set)
set.add(5)
set.has(3)
set.delete(2)
set.clear()
-
WeakMap和WeakSet(了解)
- 弱引用,防止内存泄漏
- WeakMap只能用对象作为Key,WeakSet只能用对象作为Value
- 没有forEach和size,只能add delete has
-
reduce的简单了解和使用
- 参数有四个
- previousValue: 上一次调用callbackFn时的返回值。在第一次调用时,若指定了初始值InitialValue,则其值为initalValue,否则为数组索引为0的元素
- currentValue: 数组中正在处理的元素
- currentIndex: 数组中正在处理的元素的索引
- array: 用于遍历的数组
- 初始值initialValue,在参数后面,可有可无
- 简单使用
- 数组累加
- 计数
- 输出字符串
- 二维数组变一维数组
- 数组去重
- 参数有四个
<!-- 数组累加 -->
const arr = [1,2,3,4,5,6]
const sum = arr.reduce((sum,value) => sum + value)
console.log(sum)
<!-- 计数 -->
const arr = [1,2,3,4,5,1,2,1,3,3,5]
const countNames = arr.reduce((counts,value) => {
if (value in counts) {
counts[value] += 1
} else {
counts[value] = 1
}
return counts
},{})
console.log(countNames)
<!-- 输出字符串 -->
const arr = [
{ name: '张三', age: '20' },
{ name: '李四', age: '21' },
{ name: '小明', age: '22' }
]
const str = arr.reduce((str,val) => {
return `${str}${val.name} - ${val.age}\n`
},'')
console.log(str)
<!-- 数组扁平化 -->
const arr = [1,2,3,[1,2,3],[1,2,3]]
const newArr = arr.reduce((newarr,val) => newarr.concat(val),[])
console.log(newArr)
<!-- 数组去重 -->
const arr = [1,2,3,1,3,2,4,5]
const newArr = arr.reduce((newarr,val) => {
if (!newarr.includes(val)) {
newarr.push(val)
}
return newarr
},[])
console.log(newArr)
-----更新(进阶学习)
- ajax,fetch和axios的区别
- ajax 是一种技术统称,用于实现客户端和服务端异步通信,实现局部刷新页面的效果
- fetch
- 浏览器原生API,用于网络请求
- 和XMLHttpRequest是一个级别的
- 更加简洁好用,支持promise
- 服务器返回 400,500 等错误码时并不会 reject。仅当网络故障时或请求被阻止时,才会标记为 reject
- axios
- 是一个第三方库
- 支持node和浏览器(从浏览器中创建 XMLHttpRequest,从 node.js 创建 http 请求)
- 支持 Promise API
- 可以拦截和响应请求
- 自动转换JSON数据
- 可以取消请求(CancelToken 已经被弃用了)
<!-- axios取消请求 -->
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
// 取消请求
controller.abort()
- 节流和防抖
<!-- 防抖 -->
防抖:n秒钟内执行事件,如果在n秒内重复触发,则重新计时
场景:搜索框搜索输入。只需要用户最后一次输入完,再发送请求;窗口resize事件,只有窗口调整完后,才计算窗口大小。防止重复渲染
function debounce(fn,delay=300) {
let timer = null
return function(...args) {
const ctx = this
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(ctx, args)
}, delay)
}
}
<!-- 节流 -->
节流:在n秒内只执行一次,如果多次触发也是只执行一次
场景:滚动加载,加载更多或滚动到底部监听;搜索框,搜索联想
//使用时间戳写法,事件会立即执行,停止触发后没有办法再次执行
function throttled1(fn,delay=300) {
let start = Date.now()
return function(...args) {
const end = Date.now()
const ctx = this
if (delay <= (end - start)) {
fn.apply(ctx, args)
start = Date.now()
}
}
}
//delay毫秒后第一次执行,第二次事件停止触发后依然会再一次执行
function throttled2(fn,delay=300) {
let timer = null
return function(...args) {
const ctx = this
if(!timer) {
timer = setTimeout(()=>{
fn.apply(this, args)
timer = null
},delay)
}
}
}
-
px % rpx em rem vw/vh的区别
- px:绝对单位值,一个像素
- rpx:微信小程序解决自适应屏幕尺寸的尺寸单位,可以根据屏幕宽度进行自适应
- em:在font-size中是相对于父元素的字体大小,在其他属性中是相对于自身的字体大小,如width
- rem: 是相对于根元素html,只需要设置根元素大小,即可将整个页面的比例设置好。浏览器默认字体大小,一般是16px
- %: 相对于父元素大小的百分比值
- vh和vw:相对于可视窗口的高度和宽度,1vh等于1/100的视口高度,1vw等于1/100的视口宽度。比如:浏览器高度900px,宽度为750px,1vh=900px/100=9px,1vw = 750px/100=7.5px
- vmin和vmax:vmin是vh和vw中的较小值,vmax是vh和vw中的较大值
-
箭头函数有什么缺点?什么时候不能使用箭头函数?
- 箭头函数的缺点
- 没有arguments
- 无法通过apply,call和bind改变this指向
- 某些嵌套箭头函数代码难以阅读
- 不能使用箭头函数的场景
- 对象方法中。当要通过this来获取对象自身的属性或方法时,不能使用箭头函数
- 对象的原型方法中。
- 构造函数
- 动态上下文的回调函数中。如监听事件,想要获取this的对象,就不能使用箭头函数
- vue2中的生命周期函数和method方法。vue的组件本质上是一个对象。(补充:React组件本质上是class,这里面可以使用箭头函数,是可以获取自身的属性和方法的)
- 箭头函数的缺点
-----更新
-
请描述TCP三次握手和四次挥手(网络连接TCP协议,传输内容是HTTP协议)
- 三次握手
- client发包,server接收。server知道有client要找它
- server发包,client接收。client知道server已经接收到消息了
- client发包,server接收。server知道client要准备发送请求了
- 四次挥手
- client发包,server接收。server知道client已经发完请求了
- server发包,client接收。client知道server已经收到消息,等待server关闭。此时,server可能还在返回数据,还没停止
- server发包,client接收。client知道server已经发送完数据了,此时可以关闭连接了
- client发包,server接收。server关闭连接
- 三次握手
-
for-in和for-of的区别
- for-in 用于遍历可枚举数据,即Object.getOwnProperDescritors()。 如对象,数组,字符串
- for-of 用于遍历可迭代数据,即有Symbol.iterator属性的。 如对象,数组,字符串,Map,Set
- 注意
- 遍历对象:for-in可以,for-of不可以
- 遍历Map,Set:for-of可以,for-in不可以
- 遍历generator:for-of可以,for-in不可以
- for-in会遍历原型链上的可枚举属性
- for-of可以与break、continue和return配合使用
- (连环问题)for-await-of的作用:用于遍历多个Promise。类似于Promise.all
function createPromise(val) { return new Promise((resolve) => { setTimeout(() => { resolve(val) }, 2000); }) } !(async function() { // const p1 = createPromise(10) // const p2 = createPromise(20) // const p3 = createPromise(30) // 同时出现 // const r1 = await p1 // console.log(r1) // const r2 = await p2 // console.log(r2) // const r3 = await p3 // console.log(r3) // 同时出现 // const list = [p1,p2,p3] // Promise.all(list).then(res => console.log(res)) // 同时出现 // const list = [p1,p2,p3] // for await (let res of list) { // console.log(res) // } //---------分割------------ // 实现依次出现,而不是同时出现 // const p1 = await createPromise(10) // console.log(p1) // const p2 = await createPromise(20) // console.log(p2) // const p3 = await createPromise(30) // console.log(p3) // 实现依次出现,而不是同时出现 const list = [10,20,30] for (let num of list) { const res = await createPromise(num) console.log(res) } })()
-
offsetHeight,scrollHeight和clientHeight的区别
- offsetHeight/offsetWidth: border + padding + content
- clientHeight/clientWidth: padding + content
- scrollHeight/scrollWidth: padding + 实际内容尺寸(可能会被子元素撑开)
-
HTMLConllection和NodeList区别
- HTMLConllection 是Element的集合
- NodeList是Node集合
- Node和Element区别
- DOM是一棵树,所有节点都是Node
- Node是Element的基类
- Element是其他HTML元素的基类,如HTMLDivElement
- 获取Node和Element的返回结果可能不一样:如ele.childNodes和ele.children不一样。前者包含Text和Comment节点,后者不会
- HTMLConllection和NodeList都是类数组,转成数组方式有:
- Array.from(list)
- Array.property.slice.call(list)
- […list]
-
computed和watch的区别
- computed是计算属性,计算属性的返回值,可以直接使用;watch是监听器,监听某个值发生变化然后执行对应的回调函数
- computed中所依赖的属性值没有变化的时候,就不会再计算,而是直接使用之前的值。
- computed默认第一次加载的时候就开始监听;watch默认第一次加载不做监听
- computed必须有return,而watch没有
- 使用场景: computed可以用在当一个值受多个属性影响的时候–>购物车商品结算, watch----当一条数据影响多条数据的时候–>搜索框。
-
Vue组件的通讯方式
- props和$emit 父子组件传值
- 自定义事件 兄弟组件或不相干的组件传值,vue3中需要引入event-emitter插件
- a t t r s 作为 p r o p s 和 attrs 作为props和 attrs作为props和emit的替换方案。会获取除了props外的传入的值和事件。可以通过 v-bind=“$attrs” 传入内部组件
- $parent 可以获取父组件的方法和属性
- $refs 可以获取子组件的方法和属性
- provide和inject 可以跨多级传值。当需要传入响应值是要把provide写成方法的形式
- Vuex
-
vuex中action和mutation的区别
- mutation是原子操作,必须是同步代码
- action是可以包含多个mutation,也可以包含异步代码
-
js中严格模式下有什么特点 ‘use strict’
- 全局变量必须先声明再使用
- 禁止使用with
- 不使用eval创建作用域
- 禁止this指向window
- 函数参数不能重名
-
HTTP跨域请求时为何发送options请求
- options请求是跨域请求之前的预检查,可以知道服务端支持那些方法
- 浏览器自动发起的,不需要我们干预
- 不会影响开发的实际功能
-
JS垃圾回收
- 什么是垃圾回收?就是函数执行完成,回收再也用不到的对象或变量
- 之前是用引用计数来清除,循环引用无法清除
- 现在是用标记清除,即从window开始逐步向下遍历,能获取到的对象或变量就不清除,找不到的就清除
- 连环问题:js闭包是内存泄漏吗?内存泄漏是指那些非预期的情况,即本应该被回收,但不能被回收的情况。而闭包是数据不可以被回收,是预期之内的。
-
JS内存泄漏如何检测呢?
- 使用chrome devTools的Performance和Memory工具来检测,使用前得先点击小垃圾箱清除垃圾。点击小黑原点,然后执行页面操作,然后点击stop即完成检测
- 一个合理的内存使用情况应该是一个锯齿状的,即有创建也有回收。如果没有回收,则是一个一直递增的形状。
-
内存泄漏的场景有那些?(vue为例)
- 被全局变量、函数引用,组件销毁时未清除
- 被全局事件、定时器引用,组件销毁时未清除
- 被自定义事件引用,组件销毁时未清除
- 连环问题:WeakMap和WeakSet的垃圾回收?它们都是弱引用,即不能影响正常的垃圾回收,该回收的还是会被回收。
-
浏览器和nodejs的事件循环有什么区别?
- 浏览器
- 同步任务 + 微任务 + DOM渲染 + 宏任务
- 微任务:Promise.then,async/await
- 宏任务:setTimeout,setInterval,网络请求,DOM事件
- Node
- 同步任务 + 微任务 + 按顺序执行6个类型的宏任务(每个开始时都会先执行当前的微任务)
- 微任务:Promise.then,async/await,process.nextTick(优先级最高,优先执行)
- 宏任务:
- Timer: setTimeout setInterval
- I/O callbacks 处理网络请求
- Idle,prepare 闲置状态(nodejs内部使用)
- Poll轮询 执行poll中的i/o队列
- Check查询 存储setImmediate回调
- Close callbacks 关闭回调,如socket.on(‘close’)
- 浏览器
-
vdom真的很快吗?
- vdom是指用js对象模拟DOM节点数据
- Vue和React等框架的核心价值
- 组件化
- 数据视图分离,数据驱动视图—>核心价值点
- 只关注业务数据,不用再关心DOM变化
- 总结
- vdom并不快,直接用js操作DOM才是最快的
- 但是“数据驱动视图”必须要有合适的技术方案,不能全部DOM重建
- vdom就是目前来说最合适的技术方案,不是因为它很快,而是合适
-
遍历一个数组,for和forEach那个更快?
- for更快
- forEach每次都要创建一个函数来调用,而for不会创建函数
- 函数需要独立的作用域,会有额外的开销
-
nodejs中进程和线程
- 进程:os进行资源分配和调度的最小单位,有独立内存空间
- 线程:os进行运算调度的最小单位,共享进程内存空间
- js是单线程的,但是可以开启多进程执行,如WebWorker
-
为什么需要多进程?
- 多核CPU,更适合处理多进程
- 内存较大,多个进程才能更好的利用(单进程有内存上限)
- 压榨机器资源,更快更节省
-
nodejs开启进程的方式
- child_process:主要用于开启单个进程进行计算
const fork = require('child_process').fork // 开启子进程 const computeProcess = fork(js文件地址) computeProcess.send('xxx') // 传递消息给子进程 computeProcess.on('message', data => { console.log(data) }) //接收子进程返回的数据 computeProcess.on('close', () => { console.log('子进程意外关闭') }) //处理当子进程意外关闭的情况 <!-- 子进程-js文件地址 --> process.on('message', data => { console.log(process.pid) //进程id console.log(data) //从主线程传递的消息 const sum = 'xxx' process.send(sum) //传递消息给主线程 })
- cluster:主要用于开启多个服务多个进程
const http = require('http') const cpuCoreLength = require('os').cpus().length const cluster = require('cluster') if(cluster.isMaster) { for(let i = 0;i < cpuCoreLength; i++) { cluster.fork() } cluster.on('exit', worker => { console.log('子进程退出') cluster.fork() //进程守护 }) } else { // 多个子进程会共享一个tcp连接,提供一份网络服务 const server = http.createServer((req,res) => { res.writeHead(200) res.end('done') }) server.listen(3000) }
-
什么是JS Bridge
- APP内的网页中的js无法直接调用API,只能通过APP自己封装一些特定API到网页中,然后网页中的JS调用这些特定API执行某些功能
- js无法直接调用native API
- 需要通过一些封装后的特定格式来调用
- 这些特定格式就是JS Bridge,比如微笑JSSDK
- JS Bridge目前有两种方式
- 注册全局API: 只能处理一些简单的同步的情况
- URL Scheme: 可以处理比较复杂的情况
-
requestIdleCallback和requestAnimationFrame的区别
- requestAnimationFrame是每次渲染完都会执行,高优
- requestIdleCallback是空闲时才会执行,低优
- 连环问题:它们是宏任务还是微任务? 都是宏任务。都要等到DOM渲染后才执行。
-
Vue的生命周期
- beforeCreate:创建一个空白的Vue实例。data和method尚未被初始化,不可使用
- created:初始化完成,完成响应式绑定。data和method已经初始化完成,可以调用。尚未开始渲染模板。$el还不可用
- beforeMount:编译模板,调用render生成vdom,此时还没开始渲染DOM
- mounted:完成DOM渲染,组件创建完成。开始由“创建阶段”进入“运行阶段”
- beforeUpdate:data发生变化之后,准备更新DOM(尚未更新DOM)
- Updated:data发生变化,且DOM更新完成。(不要在updated中修改data,可能会导致死循环)
- beforeUnmount:组件进行销毁(尚未销毁,可正常使用)。可移除、解绑一些全局事件和自定义事件
- unmounted:组件被销毁了,所有指令、事件侦听器都被移除。所有子组件实例都被卸载。
- activated:被keep-alive缓存的组件激活时调用
- deactivated: 被keep-alive缓存的组件失活时调用
- 连环问题:什么时候操作DOM比较合适?mounted和updated都不能保证子组件全部加载完成,可以使用$nextTick渲染DOM
- 连环问题:Vue3有什么区别?使用setup替代了beforeCreate和Created。使用hooks函数形式。
-
vue2、vue3以及React的diff算法不同点
- vue2 - 双端比较:用四个指针来同时比较,然后指针移动再比较
- vue3 - 最长递增子序列
- React - 仅右移
- Tree diff的优化
- 只比较同一层,不跨级比较
- tag不同层则删掉重建(不再去比较内部的细节)
- 子节点通过key区分
- 连环问题: Vue和React为何循环时必须要用key?
- vdom diff算法会根据key判断元素是否要删除
- 匹配了key,则只需要移动元素 – 性能较好
- 未匹配key,则需要删掉重建 – 性能较差
-
Vue-router的三种模式
- Hash
- WebHistory
- MemoryHistory
-
Retina 屏 1px 像素问题,如何实现
- 有些手机屏幕的 DPR = 2 ,即 1px 它会用两个物理像素来显示,就粗了
- 使用 css 伪类 + transform 来优化这一问题。即把默认的 1px 宽度给压缩 0.5 倍
#box { padding: 10px 0; position: relative; } #box::before { content: ''; position: absolute; left: 0; bottom: 0; width: 100%; height: 1px; background: #d9d9d9; transform: scaleY(0.5); transform-origin: 0 0; }
- 连环问题:如果有 border-radius 怎么办? 设置box-shadow
X 偏移量 0 Y 偏移量 0 阴影模糊半径 0 阴影扩散半径 0.5px 阴影颜色 #box2 { margin-top: 20px; padding: 10px; border-radius: 5px; /* border: 1px solid #d9d9d9; */ box-shadow: 0 0 0 0.5px #d9d9d9; }
-
网络请求中,token和cookie有什么区别?
- cookie
- 介绍
- HTTP无状态,每次请求都要带上cookie,以帮助识别身份
- 服务端也可以向客户端set-cookie,cookie大小限制4kb
- 默认有跨域限制:不可跨域共享、传递cookie(可以通过设置withCredentials来实现跨域共享和传递;或者设置domain主域名)
- 现代浏览器开始禁止第三方cookie
- 和跨域限制不同。这里是:禁止网页引入的第三方js设置cookie
- 打击第三方广告,保护用户隐私
- 新增属性SameSite:Strict/Lax/None;可自行选择值
- cookie和session
- cookie用于登录验证,存储用户标识(如userID)
- session在服务器端(内存中),存储用户详细信息,和cookie信息一一对应
- cookie+session是常见的登录验证解决方案
- 介绍
- token
- 介绍
- cookie是HTTP规范,而token是自定义传递
- cookie会默认被浏览器存储,而token需要自己存储
- token默认没有跨域限制
- JWT(json web token)
- 前端发起登录,后端验证成功之后,返回一个加密的token
- 前端自行存储这个token(包含了用户信息,加密了)
- 以后访问服务器端口,都要带着这个token,作为用户信息
- 介绍
- 连环问题:Session和JWT那个更好?
- Session
- 优点
- 原理简单,易于学习
- 用户信息存储在服务端,可快速封禁某个用户
- 缺点
- 占用服务器内存,硬件成本高
- 多进程,多服务器时,不好同步 — 需要第三方缓存,如redis
- 默认有跨域限制
- 优点
- JWT
- 优点
- 不占用服务器内存
- 多进程,多服务器不受影响
- 没有跨域限制
- 缺点
- 用户信息存储在客户端,无法快速封禁某个用户(可用黑名单)
- 万一服务器端密钥被泄漏,则用户信息全部丢失
- token体积一般大于cookie,会增加请求的数据量
- 优点
- 比较
- 如果有严格管理用户信息的需求(保密、快速封禁)推荐Session
- 如果没有特殊要求,则使用JWT
- Session
- 如何实现sso单点登录
- 有A网站,B网站和第三方独立网站(专门用来验证)
- 第一次访问A网站,A网站会将登录重定向到第三方网站
- 验证信息等都交给第三方网站来验证,验证通过后会回传一个token和sso凭证。这个token值会保存在A网站下和第三方网站下
- 下次登录A网站时会带上token值,继续到第三方验证,通过后即可登录
- 当登录B网站时(不用输入账号密码),会带上第三方网站下存储的token,然后去验证,通过后又会返回一个token到B网站下
- 总结一点就是:都要通过第三方独立网站去验证,只要在第三方验证后都可以登录
- cookie
-
HTTP协议和UDP协议的区别?
- HTTP协议在应用层
- TCP和UDP在传输层
- TCP协议
- 有连接(三次握手)
- 有断开(四次挥手)
- 稳定传输
- UDP协议
- 无连接,无断开
- 不稳定传输,但效率高
- 如视频会议,语音通话
- 连环问题:HTTP协议1.0,1.1和2.0有什么区别
- 1.0版本
- 最基础的HTTP协议
- 支持最基本的get,post协议
- 1.1版本
- 缓存策略Cache-control E-tag等
- 支持长连接Connection:keep-alive,一次TCP连接多次请求
- 断点续传,状态码206
- 支持新的方法PUT DELETE等,可用于Restful API
- 2.0版本
- 可压缩header,减少体积
- 多路复用,一次TCP连接可以多个HTTP并行请求
- 服务端推送
- 1.0版本
-
什么是HTTPS中间人攻击?如何预防?
- HTTPS加密过程 —> 先非对称加密后对称加密
- 使用非对称加密,生成公钥和私钥
- 将公钥传输给客户端
- 客户端使用公钥生成随机码,并将这个随机码加密后传输给服务端
- 服务端使用私钥解密随机码,获取解密后的随机码,并使用这个随机码对数据进行加密
- 客户端使用之前生成的随机码来解密数据
- 中间人攻击
- 劫持通信,伪造CA证书,生成公钥和私钥
- 利用伪造生成的公钥和私钥进行数据加密
- 如何预防:使用安全的,可靠的SSL证书,不要使用免费的,不知名的证书
- HTTPS加密过程 —> 先非对称加密后对称加密
-
JS中defer和async的区别
- 无:HTML暂停解析,下载JS,执行JS,再继续解析HTML
- defer:HTML继续解析,并行下载JS,HTML解析完后再执行JS
- async:HTML继续解析,并行下载JS,执行JS,再解析HTML
- 连环问题:prefetch和dns-prefetch有什么区别?
- prefetch和preload
- prefetch表示资源在未来页面可能会使用,空闲时加载
- preload表示资源在当前页面使用,要优先加载
- dns-prefetch和preconnet
- dns-prefetch即DNS预查询
- preconnet即DNS预连接
- 答案:无关,前者是资源预获取,后者是DNS预查询
- prefetch和preload
-
常见的前端攻击有那些?如何预防?
- XSS
- Cross Site Script 跨站脚本攻击
- 手段:将JS代码插入到网页内容中,渲染时执行JS代码
- 预防:特殊字符替换(前端和后端都可以)
- CSRF
- Cross Site Request Forgery 跨站请求伪造
- 诱导用户去访问另一个网站的接口,伪造请求
- 严格的跨域限制 + 验证码机制 + 为Cookie设置SameSite
- 点击劫持
- Click Jacking
- 诱导页面上蒙一个透明的iframe,诱导用户进行点击
- 让iframe不能跨域加载:设置X-Frame-Options:sameorigin
- DDoS
- 分布式拒绝服务
- 手段:分布式的、大规模的流量访问,使服务器瘫痪
- 做硬件预防(阿里云WAF)
- SQL注入
- 提交内容时写入SQL语句,破坏数据库
- 处理输入的内容,替换特殊字符
- XSS
-
WebSocket和HTTP的区别
- WebSocket
- 支持端对端通讯
- 可以由client发起,也可以由server发起
- 用于:消息通知,直播间讨论区,聊天室,协同编辑等
- 实际项目推荐使用scoket.io,API更见简洁
- WebSocket连接过程
- 先发起一个HTTP请求
- 成功之后再升级到WebSocket协议,再通讯
- 二者区别
- WebSocket协议名是ws://,可双端发起请求
- WebSocket没有跨域限制
- 通过send和onmessage通讯(HTTP通过req和res)
- 连环问题:WebSocket和HTTP长轮询的区别?
- HTTP长轮询:客户端发起请求,服务端阻塞,不会立即返回。当返回后,客户端会再立即发送请求。再重复这个过程。
- WebSocket:客户端可发起请求,服务端也可以发起请求
- WebSocket
-
描述从输入url到页面展示的完整过程
- 网络请求
- DNS查询(得到IP),建立TCP连接(三次握手)
- 浏览器发起HTTP请求
- 收到请求响应,得到数据(HTML,css,js,媒体资源)
- 解析
- HTML构建DOM树
- css构建CSSOM树
- 两者结合,形成render Tree
- 渲染
- 计算各个DOM的尺寸,定位等,最后绘制到页面上
- 遇到JS会先执行JS
- 异步加载CSS、图片,可能会触发重新渲染
- 连环问题:重绘repaint和重排reflow的区别?
- 重绘:元素外观改变,如颜色,背景色等。但元素的尺寸和定位不会变,不会影响到其他元素的位置
- 重排:重新计算尺寸和布局,可能会影响到其他元素的位置。比如元素增高,可能会使得相邻元素位置下移
- 减少重排方案:
- 集中修改样式,或者直接切换css class
- 修改之前先设置display:none,脱离文档流。修改后再改回来
- 使用BFC特性,不会影响到其他元素的位置
- 频繁触发(resize scroll)使用节流和防抖
- 使用createDocumentFragment 批量操作DOM
- 优化动画,使用CSS3和requestAnimationFrame
- 拓展BFC
- 解释:块级格式化上下文。内部元素无论如何改动,都不会影响其他元素的位置
- 触发条件:
- 根节点
<html>
- float:left/right
- overflow:auto/scroll/hidden
- display:inline-block/table/table-row/table-cell
- display:flex/grid;的直接子元素
- position:absolute/fixed
- 根节点
- 网络请求