前端面试重点

362 阅读25分钟

meta标签

  • 必须属性: content(定义与 http-equiv 或 name 属性相关的元信息)
  • 可选属性: http-equiv(把 content属性中的值关联到 HTTP 头部),http-equiv属性的值可为content-type,expires,refresh,set-cookie

name(把 content属性的值关联到name的值上) ,name属性的值可为author,description,keywords,revised,generator,others

bfc 块级格式化上下文

定义

BFC是一个完全独立的布局环境,让空间里的子元素不会影响到外面的布局.

触发条件

  • overflow: hidden
  • 固定定位,相对定位
  • flex布局
  • 页面根元素

bfc的规则

  • BFC就是一个块级元素,块级元素会在垂直方向一个接一个的排列

  • BFC就是页面中的一个隔离的独立容器,容器里的标签不会影响到外部标签

  • 垂直方向的距离由margin决定, 会出现垂直外边距塌陷

  • 计算BFC的高度时,浮动元素也参与计算

解决的问题

  • 使用float时,脱离文档流,父元素没有高度的问题
  • 两个元素,第一个元素使用了浮动布局会压在第二个元素上.设置第二个元素为bfc可以解决这个问题

null 与 undefined

null

指示变量未指向任何对象,也可理解为null 作为尚未创建的对象.是一个字面量.typeof为object

undefined

undefined 是 全局对象 的一个属性,代表着未定义(变量声明未赋值).

作用域和作用域链

作用域

当前上下文中,值和表达式在其中能被访问到的上下文.
作用域就是变量的使用范围.

作用域链

当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域,如果在全局作用域里仍然找不到该变量,它就会直接报错

http协议

超文本传输协议

http0.9 :只支持get,返回值只支持html格式.请求和返回值都是ASCII码.

http1.0:多了post和head.支持任意返回的格式.增加头部信息.返回值增加状态码

http1.1:支持长连接(TCP连接默认不关闭,可以被多个请求复用), 管道机制(一个TCP连接可以同时发起多个请求),支持了更多的请求方式.

http2:全部采用二进制(以前头部是ASCII码,数据体可以是ASCII可以是二进制.统称为帧), 多路复用(客户端和服务端都可以同同时发送多个请求/回应,解决了队头阻塞),头部压缩,服务器推送(服务器无法感觉,做缓存)

路由

hash路由

缺点

hash本来用来做页面定位的,用了hash路由,锚点功能就没了.

hash传参是基于url的,传复杂参数有体积限制

原理

实现原理用window.onhashchange来监听hash的改变

history路由

缺点

由于请求时会带上路径,所以后台需要对路径做相应的处理,不然会404.(hash路由中#后面的请求时不会携带)

原理

利用history API.

popstate事件能够监听到:

1.点击浏览器的前进和后退操作

2.调用 history 的 backforward 和 go 方法

但是监听不到pushStatereplaceState方法.

所以需要,给history增加一个属性,这个属性的方法和popstate事件的方法相同,在调用pushStatereplaceState方法时时再调用一下自己定义的这个属性

script标签

浏览器在解析 HTML 的时候,如果遇到一个没有任何属性的 script 标签,就会暂停解析,先发送网络请求获取该 JS 脚本的代码内容,然后让 JS 引擎执行该代码,当代码执行完毕后恢复解析

  • async(仅适用于外联式):当浏览器遇到带有 async 属性的 script 时,请求该脚本的网络请求是异步的,不会阻塞浏览器解析 HTML,一旦网络请求回来之后,如果此时 HTML 还没有解析完,浏览器会暂停解析,先让 JS 引擎执行代码,执行完毕后再进行解析.

async 是不可控的,因为执行时间不确定(可能网络请求回来以后html已经解析完成,也有可能没有解析完成),异步JS 脚本中获取某个 DOM 元素,有可能获取到也有可能获取不到。而且如果存在多个 async 的时候,它们之间的执行顺序也不确定,完全依赖于网络传输结果,谁先到执行谁

  • defer:浏览器遇到带有 defer 属性的 script 时,获取该脚本的网络请求也是异步的,不会阻塞浏览器解析 HTML,一旦网络请求回来之后,如果此时 HTML 还没有解析完,浏览器不会暂停解析并执行 JS 代码,而是等待 HTML 解析完毕再执行 JS 代码 存在多个 defer script 标签,浏览器(IE9及以下除外会保证它们按照在 HTML 中出现的顺序执行,不会破坏 JS 脚本之间的依赖关系

Promise

Promise有Pending,Fulfilled,Rejected三种状态.状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改.Promise 中使用 resolve 和 reject 两个函数来更改状态

  • .then的参数可以接受两个回调函数,第一个是成功的回调,第二个是失败的回调.
  • .catch的参数是一个回调,作用和.then的第二个回调一样的;还可以在执行resolve回调时被抛出错误也会进入到.catch中.
  • Promise.all():接受一个数组,可以并行执行多个异步操作.全部执行成功则进入.then得到的结果也是一个数组的形式;有一个失败就进入.catch,抛出第一失败的错误信息
  • Promise.race():接受一个数组,只要有一个resolve或reject就会进入.then或.catch.(看谁跑得快)

promise实现原理

  • 维护一个state状态
  • 维护一个存储值的变量
  • 执行new MyPromise 传入的回调
  • resolve 方法

state 由Pending --> Fulfilled
把resolve方法调用里面的值赋值给变量存储

  • reject方法,

state 由 Pending --> Rejected
把resolve方法调用里面的值赋值给变量存储

  • then方法 执行传入回调,把结果包装成promise对象返回
     class MyPromise {

            // 构造方法
            constructor(executor) {
                this.PromiseState = 'pending'
                this.onFulfilledCallbacks = [] // 保存成功回调
                this.onRejectedCallbacks = [] // 保存失败回调
                this.PromiseResult = null // 终值
                // 初始化值
                // 初始化this指向
                // 执行传进来的函数
                this.initBind()
                executor(this.resolve, this.reject)
            }

            initBind() {
                // 初始化this
                this.resolve = this.resolve.bind(this)
                this.reject = this.reject.bind(this)
            }


            resolve(value) {


                // state是不可变的
                if (this.PromiseState !== 'pending') return
                // 如果执行resolve,状态变为fulfilled
                this.PromiseState = 'fulfilled'
                // 终值为传进来的值
                this.PromiseResult = value


            }

            reject(reason) {
                // state是不可变的
                if (this.PromiseState !== 'pending') return
                // 如果执行reject,状态变为rejected
                this.PromiseState = 'rejected'
                // 终值为传进来的reason
                this.PromiseResult = reason


            }

            then(onFulfilled, onRejected) {
                // 接收两个回调 onFulfilled, onRejected
                // 参数校验,确保一定是函数
                onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
                onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }


                var thenPromise = new MyPromise((resolve, reject) => {
                    const resolvePromise = cb => {

                        try {
                            const x = cb(this.PromiseResult)
                            if (x === thenPromise) {
                                // 不能返回自身哦
                                throw new Error('不能返回自身。。。')
                            }
                            if (x instanceof MyPromise) {
                                // 如果返回值是Promise
                                // 如果返回值是promise对象,返回值为成功,新promise就是成功
                                // 如果返回值是promise对象,返回值为失败,新promise就是失败
                                // 谁知道返回的promise是失败成功?只有then知道
                                x.then(resolve, reject)
                            } else {
                                // 非Promise就直接成功


                                resolve(x)
                            }
                        } catch (err) {
                            // 处理报错
                            reject(err)
                            throw new Error(err)
                        }
                    }

                    if (this.PromiseState === 'fulfilled') {
                        // 如果当前为成功状态,执行第一个回调

                        resolvePromise(onFulfilled)
                    } else if (this.PromiseState === 'rejected') {
                        // 如果当前为失败状态,执行第二个回调
                        resolvePromise(onRejected)
                    }
                })

                // 返回这个包装的Promise
                return thenPromise

            }
        }
        const test3 = new MyPromise((resolve, reject) => {

            resolve(100) // 输出 状态:成功 值: 200

        }).then(res => 2 * res, err => 3 * err)
            .then(res => console.log('成功', res), err => console.log('失败', err))

async await

解决多层回调嵌套的问题,用同步的方式操作异步.

async函数返回的是一个Promise对象.

async/await是一种语法糖,可以通过generate函数去实现

generate函数

generator函数 跟普通函数在写法上的区别就是,多了一个星号*

function* gen() { yield 1 yield 2 yield 3 }
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false } 
console.log(g.next()) // { value: undefined, done: true }

只有在generator函数中才能使用yield,它相当于generator函数执行的中途暂停点.使用到next方法才能继续往后走.next方法执行后会返回一个对象,对象中有value 和 done两个属性.

  • value:暂停点后面接的值,也就是yield后面接的值.可以看到最后一个是undefined,这取决于你generator函数有无返回值
  • done:是否generator函数已走完,没走完为false,走完为true
function* gen() { yield 1 yield 2 yield 3 return 4 } 
const g = gen()
console.log(g.next()) // { value: 1, done: false } 
console.log(g.next()) // { value: 2, done: false } 
console.log(g.next()) // { value: 3, done: false } 
console.log(g.next()) // { value: 4, done: true }

yield后面接函数,到了对应暂停点yield,会马上执行此函数,并且该函数的执行返回值,会被当做此暂停点对象的value.

function fn(num) { return new Promise(resolve => { setTimeout(() => { resolve(num) }, 1000) }) } 
function* gen() { yield fn(1) yield fn(2) return 3 } 
const g = gen() 
console.log(g.next()) // { value: Promise { <pending> }, done: false } 
console.log(g.next()) // { value: Promise { <pending> }, done: false } 
console.log(g.next()) // { value: 3, done: true }

generator函数可以用next方法来传参,并且可以通过yield来接收这个参数

第一次next传参是没用的,只有从第二次开始next传参才有用

next方法的参数表示上一个yield表达式的返回值

function* gen() { 
const num1 = yield 1 
console.log(num1)
const num2 = yield 2 
console.log(num2) 
return 3
} 
const g = gen() console.log(g.next()) // { value: 1, done: false }
console.log(g.next(11111))   // 此时是把111111复制给了num1
// 11111 // { value: 2, done: false } 
console.log(g.next(22222)) 
// 22222 // { value: 3, done: true }

EventLoop

浏览器事件循环

过程

js的任务被分为了同步任务和异步任务. 同步任务在调用栈中立即执行,异步任务会被放进任务列表中(异步任务有了结果就把回调放入对应的任务队列中(分宏任务队列和微任务队列)).等到同步任务全部完成后,再依次执行任务队列中的任务.

异步任务又分为宏任务和微任务,微任务先于宏任务执行(执行完所有微任务执行下一个宏任务,再去清空所有微任务,再执行一个宏任务.如此循环).

其实是宏任务先于微任务的,因为整个script就是一个宏任务.

宏任务和微任务

微任务是线程之间的切换,速度快。不用进行上下文切换,可以快速的一次性做完所有的微任务。

宏任务是进程之间的切换,速度慢,且每次执行需要切换上下文。因此一个Eventloop中只执行一个宏任务。

区分宏任务微任务的原因:是为了插队。由于微任务执行快,一次性可以执行很多个
反观如果不区分微任务和宏任务,那么新注册的任务不得不等到下一个宏任务结束后,才能执行。

  • 宏任务:setTimeout,setInterval,script(整体代码)
  • 微任务:promise,MutationObserver(监听dom的变化) 注意:定时器时间是不准的(因为他只是到时间了才开始加入任务队列,而不是到时间了执行.)

缓存(强缓存与协商缓存)

强缓存

通过http请求头中的Cache-Control和Expire两个字段控制.Cache-Control优先于Expire. 命中强缓存时客户端不会再求,直接从缓存中读取内容,并返回HTTP状态码200

缺点:时间过期就会请求,可能过期的时候我的文件并没有发生变动

Expires:

代表该资源的过期时间,是一个GMT 格式的标准时间.

Expires的缺点:

  • 缓存过期以后,服务器不管文件有没有变化会再次请求服务器。
  • 缓存过期时间是一个具体的时间,这个时间依赖于客户端的时间,如果时间不准确或者被改动则缓存也会受到影响
Cache-Control:

客户端可以在HTTP请求中使用的标准 Cache-Control 指令:

  • max-age:在多少秒内有效
  • no-cache:不使用强缓存,使用缓存协商
  • no-store:禁止浏览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源
  • max-stale: 5     客户端愿意接收一个已经过期的资源。可以设置一个可选的秒数,表示响应不能已经过时超过该给定的时间。
  • min-fresh: 5     表示客户端希望获取一个能在5秒内保持其最新状态的响应。
  • only-if-cached 这个字段加上后表示客户端只会接受代理缓存,而不会接受源服务器的响应。如果代理缓存无效,则直接返回 504(Gateway Timeout)。

服务器可以在响应中使用的标准 Cache-Control 指令

  • max-age
  • s-maxage: 就是用于表示 cache 服务器上(比如 cache CDN,缓存代理服务器)的缓存的有效时间的,并只对 public 缓存有效
  • no-cache
  • no-store
  • public:可以被所有的用户缓存,包括终端用户和中间代理服务器
  • private:只能被终端用户的浏览器缓存,不允许中间缓存代理进行缓存(这个是默认值)

协商缓存

商缓存主要有四个头字段,它们两两组合配合使用.If-Modified-Since 和 Last-Modified一组,Etag 和 If-None-Match一组.当同时存在的时候会以Etag 和 If-None-Match为主.当命中协商缓存时,服务器会返回HTTP状态码304,让客户端直接从本地缓存里面读取文件.

If-Modified-Since 和 Last-Modified
  • If-Modified-Since:请求头,资源最近修改时间,由浏览器告诉服务器。其实就是第一次访问服务端返回的Last-Modified的值 当客户端第一次请求服务器的时候,服务端会返回一个Last-Modified响应头,该字段是一个标准时间。客户端请求服务器的时候会带上If-Modified-Since请求头字段,该字段的值就是服务器返回的Last-Modified的值。服务器接收到请求后会比较这两个值是否一样,一样就返回304,让客户端从缓存中读取,不一样就会返回新文件给客户端并更新Last-Modified响应头字段的值

If-Modified-Since 和 Last-Modified的缺点:时间只能精确到秒

Etag 和 If-None-Match
  • Etag:是由文件修改时间与文件大小计算而成,只有当文件文件内容或修改时间变了Etag的值才会发生变化. 服务端会返回一个Etag响应头。客户端请求服务器的时候会带上If-None-Match请求头字段,该字段的值就是服务器返回的Etag的值。服务器接收到请求后会比较这两个值是否一样,一样就返回304,让客户端从缓存中读取,不一样就会返回新文件给客户端并更新Etag响应头字段的值。
缓存失效问题

比如我们明明更新了系统版本,为什么客户端看到的还是老文件.

解决方法:html文件不使用缓存或者使用协商缓存.每次更改js文件名加上版本号或者时间戳(老方法)/webpack打包用hash值命名(新方法),这样客户端请求文件会当新文件去请求.

垃圾回收

原理:JavaScript 垃圾回收机制的原理说白了也就是定期找出那些不再用到的内存(变量),然后释放其内存

标记清除算法:

为 标记 和 清除 两个阶段,记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁

标记阶段: 在此阶段,垃圾回收器会从mutator(应用程序)根对象开始遍历。 每一个可以从根对象访问到的对象都会被添加一个标识,于是这个对象就被标识为可到达对象。

清除阶段: 在此阶段中,垃圾回收器会对堆内存从头到尾进行线性遍历,如果发现有对象没有被标识为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作

当前上下文执行完后,移动记录当前执行状态的指针向下移动.旧的上下文就被销毁了,新的上下文被压入.

新生代

新生代区域不大,若太大,回收就需要隔很久.

生存时间短的放入新生代区域.

新生代区域分为对象区域和空闲区域.

新加入的都会放入对象区域,当对象区域快被写满时就执行一次垃圾清理操作.

垃圾回收过程中,对对象区域中的垃圾做标记,标记完成之后,就进入垃圾清理阶段.

副垃圾回收器会把这些存活的对象复制到空闲区域中,同时它还会把这些对象有序地排列起来(相当于内存整理)

完成复制以后,对象区域与空闲区域进行交换.这样就完成了垃圾的回收操作

经过两次垃圾回收依然还存活的对象,会被移动到老生代区域中

老生代

除了新生代区域中晋升的对象,一些大的对象会直接被分配到老生代区域.老生代区域中的对象有两个特点,一个是对象占用空间大,另一个是对象存活时间长

由于老生代区域的对象比较大,复制操作就会比较耗时.还会浪费一半的空间 所以主垃圾回收器是采用标记-清除

然后还会进行标记-整理,让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存

全停顿

执行垃圾回收算法,要将正在执行的 JavaScript 脚本暂停,待垃圾回收完毕后再恢复脚本执行.我们把这种行为叫做全停顿

使用增量标记算法,可以把一个完整的垃圾回收任务拆分为很多小的任务,这些小的任务执行时间比较短,可以穿插在其他的 JavaScript 任务中间执行.最后统一清理整理

渲染

html解析为dom树,css解析为css规则树,通过dom树和css规则树来构造渲染树.最后就是绘制.

构建dom:字节数据(网络中都是0,1这种字节数据)=>字符串=>标签=>node=>dom

渲染过程遇到js,会停止渲染,执行js.

css和dom

css不会阻塞dom解析,会阻塞dom渲染.

css和dom是并行构建的,render树依赖css树和dom树,所以必须两个都加载完成才能开始render树渲染.所以css会影响dom渲染

css 和js

css会阻塞js执行,不会阻塞js下载. js可以操作样式所以js依赖css,所以要等css加载执行完毕后才开始js

dom和js

js会阻塞dom解析,也就会影响dom渲染

重绘和回流(重排)

  • 重绘:修改样式不改变几何属性 -回流(重排):改变了几何属性

TCP三次握手四次挥手

三次握手

  • 发送syn,请求连接
  • 回SYN/ACK,收到请求,同意建立连接
  • 去确认同意连接(seq(序号),ack(确认码),syn(建立连接的包))

握手为什么不是两次:无法确认客户端的接收能力.发了syn想建立tcp连接,由于网络拥堵没有迟迟不到,客户端便超时重发,然后建立上了.

关闭连接,后来之前拥堵的syn包到了服务器,由于是两次,直接建立了连接,但是客户端已经关闭了连接.服务器会处于等待发送数据状态,这样会造成连接资源浪费

四次挥手:

  • 请求关闭连接(seq,fin)
  • 收到关闭连接的请求(seq,ACK)
  • 服务器传完数据,通知客户端它关闭连接了(ack,seq,fin)
  • 客户端收到服务器关闭连接的消息,然后告诉服务器自己处于time_wait状态.

是四次挥手而不是三次挥手的原因:服务器收到fin后会先发个ack,然后等到服务端所有报文发送以后才给客户端发fin

如果是三次,把ack和fin合并为一次,等待发送时间可能会延长,客户端会以为服务器没收到 fin,而一直发fin

最后等待2MSL的意义:

  • 如果不等待,客户端直接跑路,当服务端还有很多数据包要给客户端发,且还在路上的时候,若客户端的端口此时刚好被新的应用占用,那么就接收到了无用数据包,造成数据包混乱。
  • 1 个 MSL 确保四次挥手中主动关闭方最后的 ACK 报文最终能达到对端
  • 第四次挥手的ack如果没有收到,服务器在经历1个MSL时段后将重新发送第三次挥手FIN包,客户端再次接收到FIN包时会再次发送ACK包,直至2MSL时间终结

观察者模式

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

被观察者:

变量:
声明一个数组存放观察者,
再声明一个状态的变量
方法:
设置状态值的方法:设置状态和调用通知所有观察者
通知观察者的方法:遍历存放观察者的数组,调用观察者的update方法
添加观察者的方法:把观察者加入存放观察者的数组

观察者


实例化的时候接收被观察者,调用被观察者的attach方法,把自己加入存放观察者的数组
本身有一个update方法

过程

发布者发生改变,遍历所有观察者,调用观察者的更新方法.
每次观察者被实例化时,都调用发布者的方法把自己存入观察者数组
    class Subject {
         let observers = [];
         let state;

         getState() {
            return this.state;
         }

         setState(state) {
            this.state = state;
            notifyAllObservers();
         }

         attach(observer){
            observers.push(observer);
         }

         notifyAllObservers(){
            for (observer in observers) {
               observer.update();
            }
         }
      }

      class Observer {
         let subject;
         update();
      }

      class BinaryObserver extends Observer {
        constructor(subject) {
          super();
          subject.attach(this);
        }
        update() {
          console.log("Binary");
        }
      }

      class OctalObserver extends Observer {
        constructor(subject) {
          super();
          subject.attach(this);
        }
        update() {
          console.log("Octal");
        }
      }

      var subject = new Subject();
      var binaryObserver = new BinaryObserver(subject);
      var octalObserver = new OctalObserver(subject);

      subject.setState(15);

发布订阅模式

  
    * handle:存放事件处理函数的对象
    * on: 订阅事件
    * emit: 发布事件
    * off: 删除事件

handle:{},
on事件:判断handle里面是否存有这个事件,没有就加上,然会把处理函数存到对应的事件里面去
emit事件:有这个事件就循环hanlde对象对应的事件存放的数组去执行处理函数
删除事件:删除对应事件中的对应处理函数

    /**
    * 发布订阅模式
    * handles: 事件处理函数集合
    * on: 订阅事件
    * emit: 发布事件
    * off: 删除事件
    **/
    class PubSub {
      constructor() {
        this.handles = {};
      }

      // 订阅事件
      on (eventType, handle) {
        if (!this.handles.hasOwnProperty(eventType)) {
          this.handles[eventType] = [];
        }
        if (typeof handle == 'function') {
          this.handles[eventType].push(handle);
        } else {
          throw new Error('缺少回调函数');
        }
        return this;
      }

      // 发布事件
      emit (eventType, ...args) {
        if (this.handles.hasOwnProperty(eventType)) {
          this.handles[eventType].forEach((item, key, arr) => {
            item.apply(null, args);
          })
        } else {
          throw new Error(`"${eventType}"事件未注册`);
        }
        return this;
      }

      // 删除事件
      off (eventType, handle) {
        if (!this.handles.hasOwnProperty(eventType)) {
          throw new Error(`"${eventType}"事件未注册`);
        } else if (typeof handle != 'function') {
          throw new Error('缺少回调函数');
        } else {
          this.handles[eventType].forEach((item, key, arr) => {
            if (item == handle) {
              arr.splice(key, 1);
            }
          })
        }
        return this; // 实现链式操作
      }
    }

    // 下面做一些骚操作
    let callback = function () {
      console.log('you are so nice');
    }
    let pubsub = new PubSub();
    pubsub.on('completed', (...args) => {
      console.log(args.join(' '));
    })
    pubsub.on('completed', callback);
    pubsub.emit('completed', 'what', 'a', 'fucking day');
    pubsub.off('completed', callback);
    pubsub.emit('completed', 'what', 'a', 'fucking day');
    // 控制台打印
    // what a fucking day
    // you are so nice
    // what a fucking day

模块化

定义:复杂的程序拆成多个文件.

模块化优点

  • 更好的分离, 按需加载
  • 更高复用性
  • 高可维护性

commonJs

module对象记录模块信息
用moudule.exports或者exports导出模块
用require导入


exports为moduel对象的一个属性
moudule.exports===exports

当moudule.exportsexports混用时,moudule.export会覆盖exports

CommonJS 模块输出的是一个值的拷贝
CommonJS 模块是运行时加载

es6

ES6 模块输出的是值的引用
ES6 模块是编译时输出接口

XSS 攻击

Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击.可分为存储型、反射型和 DOM 型三种

  1. 存储型:将恶意代码提交到目标网站的数据库中
  • 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器
  • 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
  • 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作
  • 这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等
  1. 反射型:
  • 攻击者构造出特殊的 URL,其中包含恶意代码
  • 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器
  • 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
  • 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作

反射型 XSS 跟存储型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里

反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等

  1. DOM 型:
  • 攻击者构造出特殊的 URL,其中包含恶意代码。
  • 用户打开带有恶意代码的 URL
  • 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行
  • 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作 DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞
预防存储型和反射型 XSS 攻击:

改成纯前端渲染,把代码和数据分隔开(前后分离)。 对 HTML 做充分转义(对于 HTML 转义通常只有一个规则,就是把 & < > " ' / 这几个字符转义掉) 对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度

预防 DOM 型 XSS 攻击:

在使用 .innerHTML、.outerHTML、document.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent、.setAttribute() 等 避免 eval() 对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度

webpack

loader

本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。Webpack 只认识 JavaScript,对其他类型的资源进行转译的预处理工作

常用loader

file-loader(处理图片和字体),babel-loader(高版本语法转低版本),sass-loader,css-loader,style-loader(SS 代码注入到 JavaScript 中)

Plugin

在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果.插件可以扩展 Webpack 的功能

常用plugin

html-webpack-plugin(简化 HTML 文件创建),clean-webpack-plugin(清理目录)

Webpack构建流程

初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler,

编译:从入口触发,对模块进行递归编译

输出:将编译后的 Module 组合成 Chunk,将 Chunk 转换成文件,输出到文件系统中

打包原理

对文件的依赖关系进行静态分析,生成静态资源.递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle.