https://juejin.cn/post/6940945178899251230
function debounce(fn, wait, immediate=false) {
let timer
return function () {
let ctx = this
let args = arguments
if (timer) clearTimeout(timer)
if (immediate) {
const callNow = !timer
timer = setTimeout(function () {
timer = null
}, wait)
if (callNow) fn.apply(cxt, args)
} else {
timer = setTimeout(function () {
fn.apply(ctx, args)
}, wait)
}
}
}
function throttle(func, delay, immediate=false) {
let start=0
let timer=null
return function (...args) {
if(immediate){
let now = +new Date();
if (now - start > delay) {
start = now;
func.apply(this, args);
} else {
if (timer) clearTimeout(timer)
const rest = delay - (now - start)
timer = setTimeout(() => {
start = now;
func.apply(this, args);
}, rest > 0 ? rest : 0);
}
} else {
if (!timer) {
timer = setTimeout(function () {
timer = null
fn.apply(this, args)
}, delay)
}
}
}
}
function Parent(value) {
this.value = value
}
function Child(value) {
Parent.call(this, value)
}
Child.prototype = new Parent()
Child.prototype.constructor = Child
function myNew(Func, ...args) {
const instance = {}
if (Func.prototype) {
Object.setPrototypeOf(instance, Func.prototype)
}
const res = Func.apply(instance, args)
// 如果构造函数返回引用类型的对象,则返回该对象作为实例
// 如果构造函数没有return或者返回基本数据类型,则返回instance
if (res instanceof Object) {
return res
}
return instance
}
Function.prototype.myBind = function (context = window, ...args) {
// this 是调用myBind方法的对象
const fn = this
const newFun = function () {
const newArgs = args.concat(...arguments)
// newFun 执行时判断
if (this instanceof newFunc) {
// 通过 new 调用,绑定 this 为实例对象
fn.apply(this, newArgs)
} else {
// 通过普通函数形式调用,绑定 context
fn.apply(context, newArgs)
}
}
newFun.prototype = Object.create(fn.prototype)
return newFun
}
Function.prototype.myCall = function (context = globalThis, ...args) {
// 在 context 上调用方法,触发 this 绑定为 context
// 使用 Symbol 防止原有属性的覆盖
const key = Symbol('key')
context[key] = this
const res = context[key](...args)
delete context[key]
return res
}
Function.prototype.myApply = function (context = window, arr = []) {
// 在 context 上调用方法,触发 this 绑定为 context
// 使用 Symbol 防止原有属性的覆盖
const key = Symbol('key')
context[key] = this
const res = context[key](...arr)
delete context[key]
return res
}
function shuffle(arr) {
let _arr = arr.slice()
for (let i = _arr.length - 1; i > 0; i--) {
// Math.random()返回介于 0(包含) ~ 1(不包含) 之间的一个随机数
let index = Math.floor(Math.random() * i)
let temp = _arr[i]
_arr[i] = _arr[index]
_arr[index] = temp
}
return _arr
}
function formatZero(val, len) {
// 如果数值的长度等于我们规定的长度
if (String(val).length === len) return val
// 因为Array(2)的两个元素为空,join是把数组的分隔符逗号变成0,再连接所有元素变成字符串
// Array(2).join(0) 输出 '0', Array(1).join(0) 输出 '',因为数组只有一个元素没有逗号
// slice(start, end) 方法可提取数组字符串的某个部分,并以新的字符串返回被提取的部分
// 如果start为-1,则代表从最后一个开始取(包括)
return (Array(len).join(0) + val).slice(-len)
}
function deepCopy(obj, cache = new WeakMap()) {
if (!obj instanceof Object) return obj
// 防止循环引用
if (cache.get(obj)) return cache.get(obj)
// 支持函数
if (obj instanceof Function) {
return function () {
return obj.apply(this, arguments)
}
}
// 支持日期
if (obj instanceof Date) return new Date(obj)
// 支持正则对象
if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags)
// 还可以增加其他对象,比如:Map, Set等,根据情况判断增加即可,面试点到为止就可以了
// 数组是 key 为数字索引的特殊对象
const res = Array.isArray(obj) ? [] : {}
// 缓存 copy 的对象,用于处理循环引用的情况
// 每次缓存,obj都是不同的对象,即使名称一样
cache.set(obj, res)
Object.keys(obj).forEach(key => {
if (obj[key] instanceof Object) {
res[key] = deepCopy(obj[key], cache)
} else {
res[key] = obj[key]
}
})
return res
}
function curry(fun) {
return function curried(...args) {
// 关键知识点:function.length 用来获取函数的形参个数
// 补充:arguments.length 获取的是实参个数
if (args.length >= fun.length) {
return func.apply(this, args)
}
return function (...args2) {
// 实参个数小于获取函数的形参个数时,实参不断向后拼接实参,直至实参个数等于形参个数
return curried.apply(this, args.concat(args2))
}
}
}
// 测试
function sum(a, b, c) {
return a + b + c
}
const curriedSum = curry(sum)
console.log(curriedSum(1, 2, 3))
console.log(curriedSum(1)(2, 3))
console.log(curriedSum(1)(2)(3))
function isInstanceOf(instance, klass) {
let proto = instance.__proto__
let prototype = klass.prototype
while (true) {
if (proto === null) return false // 原型链终点
if (proto === prototype) return true
// 通过原型链一直向上查找
proto = proto.__proto__
}
}
function recursionFlat(ary = []) {
const res = []
ary.forEach(item => {
if (Array.isArray(item)) {
res.push(...recursionFlat(item))
} else {
res.push(item)
}
})
return res
}
function myPromise(constructor) {
// 用self接受this,可以在resolve、reject中使用
let self = this
self.status = 'pending' //定义状态改变前的初始状态
self.value = undefined //定义状态为resolved的时候的状态
self.reason = undefined //定义状态为rejected的时候的状态
function resolve(value) {
//两个==="pending",保证了了状态的改变是不不可逆的
if (self.status === 'pending') {
self.value = value
self.status = 'resolved'
}
}
function reject(reason) {
//两个==="pending",保证了了状态的改变是不不可逆的
if (self.status === 'pending') {
self.reason = reason
self.status = 'rejected'
}
}
//捕获构造异常
try {
constructor(resolve, reject)
} catch (e) {
reject(e)
}
}
myPromise.prototype.then = function (onFullfilled, onRejected) {
let self = this
switch (self.status) {
case 'resolved':{
let res;
setTimeout(()=>{
res = onFullfilled(self.value)
})
return res
}
case 'rejected':{
let reason;
setTimeout(()=>{
reason = onRejected(self.reason)
})
return reason
}
default:
break;
}
}
// 测试
var p = new myPromise(function (resolve, reject) {
resolve(1)
})
p.then(function (x) {
console.log(x)
})
console.log(111)
// 111
// 1
// 发布订阅中心, on-订阅, off取消订阅, emit发布, 内部需要一个单独事件中心caches进行存储;
interface CacheProps {
[key: string]: Array<(data?: any) => void>
}
class Observer {
private caches: CacheProps = {} // 事件中心
on(eventName: string, fn: (data?: any) => void) {
// eventName事件名-独一无二, fn是订阅后执行的自定义行为
this.caches[eventName] = this.caches[eventName] || []
this.caches[eventName].push(fn)
}
emit(eventName: string, data?: any) {
// 发布 => 将订阅的事件进行统一执行
if (this.caches[eventName]) {
this.caches[eventName].forEach((fn: (data?: any) => void) => fn(data))
}
}
off(eventName: string, fn?: (data?: any) => void) {
// 取消订阅 => 若fn不传, 直接取消该事件所有订阅信息
if (this.caches[eventName]) {
const newCaches = fn ? this.caches[eventName].filter(e => e !== fn) : []
this.caches[eventName] = newCaches
}
}
}
let handler={
get(obj,prop){
const v = Reflect.get(obj,prop);
if(v !== null && typeof v === 'object'){
return new Proxy(v,handler); // 代理内层
}else{
return v; // 返回obj[prop]
}
},
set(obj,prop,value){
obj[prop] = value
return true;
// 或 return Reflect.set(obj,prop,value) // 设置成功返回true
}
};
let p=new Proxy(obj,handler);
// Dep module
class Dep {
static stack = []
static target = null
deps = null
constructor() {
this.deps = new Set()
}
depend() {
if (Dep.target) {
this.deps.add(Dep.target)
}
}
notify() {
this.deps.forEach(w => w.update())
}
static pushTarget(t) {
if (this.target) {
this.stack.push(this.target)
}
this.target = t
}
static popTarget() {
this.target = this.stack.pop()
}
}
// reactive
function reactive(o) {
if (o && typeof o === 'object') {
Object.keys(o).forEach(k => {
defineReactive(o, k, o[k])
})
}
return o
}
function defineReactive(obj, k, val) {
let dep = new Dep()
Object.defineProperty(obj, k, {
get() {
dep.depend()
return val
},
set(newVal) {
val = newVal
dep.notify()
}
})
if (val && typeof val === 'object') {
reactive(val)
}
}
// watcher
class Watcher {
constructor(effect) {
this.effect = effect
this.update()
}
update() {
Dep.pushTarget(this)
this.value = this.effect()
Dep.popTarget()
return this.value
}
}
// 测试代码
const data = reactive({
msg: 'aaa'
})
new Watcher(() => {
console.log('===> effect', data.msg);
})
setTimeout(() => {
data.msg = 'hello'
}, 1000)
// 原生ajax
// ajax更多内容(方法、参数等等)请到W3school查看
function myAjax(url) {
return new Promise((resolve, reject) => {
// 1、创建XMLHttpRequest对象(简称XHR)
var XHR = new XMLHttpRequest()
// 2、建立链接
XHR.open('GET', url, true)
// 3、发送请求
XHR.send()
// 4、接受并处理来自服务器的响应结果
// 当XHR对象的就绪状改变就触发
XHR.onreadystatechange = function () {
if (XHR.readyState == 4 && XHR.status == 200) {
resolve(JSON.parse(XHR.responseText))
} else if (XHR.status >= 400) {
reject('发生错误')
}
}
})
}
语义化标签;
丰富input中type类型;
音频(audio)和视频(video);
拖放API;
地理位置;
canvas画布;
svg;
WebWorker;
websocket
history
postMessage
本地缓存
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
margin: auto;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 50%;
left: 50%;
width:200px;
height:200px;
margin-top: -100px;
margin-left: -100px;
样式文件应当在 head 标签内,脚本文件在 body 结束前,防止阻塞
简化CSS选择器,将嵌套层级减小到最小
避免使用CSS表达式
提取公共样式,通过定义类名,一次性修改样式
需要要对元素进行复杂的操作时,可以先隐藏display:"none"
,操作完成后再显示,不可见元素不会重绘和回流
DOM的多个读或写操作应该分别写在一起,不要两个读操作之间加入一个写操作
需要创建多个DOM
节点时,使用DocumentFragment
创建完后一次性的加入document
尽量使用transform做位移和形变,transform、opacity、filters等式使用GPU加速
position属性为 absolute 或 fixed 的 元素,回流的开销会表较小,对具有复杂动画的元素使用绝对或固定定位
使用 window.requestAnimationFrame() 执行动画
重绘:元素的几何尺寸和位置没有发生改变,外观改变
回流:元素的几何尺寸或位置发生改变
回流必将引起重绘,而重绘不一定会引起回流
<link>
代替@import;HTL的解析和CSS的解析是并行的,DOM解析和CSS解析会阻塞render树的生成,即会阻塞DOM的渲染。注意,两者都是一边解析一边渲染。
脚本执行会阻塞render树的生成。
因此srcipt 元素放在 body 元素底部,或使用 defer
和async
属性
<script src="***.js" defer></script>
当 HTML 文档被解析时如果遇见 defer 脚本,则在后台加载脚本,文档解析过程不中断,而等文档解析结束之后,defer 脚本执行。另外,defer 脚本的执行顺序与定义时的位置有关。
当 HTML 文档被解析时如果遇见 async 脚本,则在后台加载脚本,文档解析过程不中断。脚本加载完成后,文档停止解析,脚本执行,执行结束后文档继续解析。
如果同时存在defer和async ,则defer 的优先级更高。
display:"none"
,操作完成后再显示,不可见元素不会重绘和回流DOM
节点时,使用DocumentFragment
创建完后一次性的加入document
重绘:元素的几何尺寸和位置没有发生改变,外观改变
回流:元素的几何尺寸或位置发生改变
回流必将引起重绘,而重绘不一定会引起回流
压缩图片
url-loader:小图片则进行base64编码
图片懒加载
将不需要打包的静态资源从构建逻辑中剔除出去,而使用 CDN
的方式去引用它们
使用 gzip 压缩。可以通过向 HTTP 请求头中的 Accept-Encoding 头添加 gzip 标识来开启这一功能(Nginx),服务器也得开启这一功能。
动态引入
依赖ESM的静态特性,剔除无用代码
配置目标浏览器,以提供前端工具进行配置:
Autoprefixer
Stylelint
@babel/preset-env
查看React.md对应章节
减少http请求次数,避免重复请求
静态资源使用 CDN
浏览器一般会同一个域名建立 6-8 个 TCP 链接,也就是 6-8 个队
如果把静态资源部署在不同的域名下,这样每个域名都能并发 6-8 个下载请求,网页打开的速度自然就会快很多。这种优化手段叫做“域名分片”,CDN 一般都支持这个。
缓存策略(强缓存,协商缓存)
HTTP2:二进制分帧、多路复用、头部压缩、服务端推送
MVVM是model-View-ViewModel的缩写,其中model代表数据模型是业务操作的基础;View代表视图层,界面UI组件负责展示数据;ViewModel监听数据模型的改变和控制视图行为。MVVM架构中数据模型和视图分离,数据模型仅与ViewModel进行双向交互,View数据变化会同步传入ViewModel中,进而改变数据模型的值,同理数据模型的改变也会间接反映到View视图层上。ViewModel通过双向绑定把view视图和Model数据模型连接,同步操作自动完成,复杂的数据状态完全由MvvM统一管理。
new Vue():创建实例;
beforeCreate:事件和生命周期钩子已初始化;
created:data已初始化,此时可以使用data,或者修改data,但不会触发updated;
beforeMount:挂在前,render函数首次被调用,生成虚拟DOM,尚不能对DOM操作;
mounted:挂载完成,DOM树已经渲染到页面,可以操作DOM节点,;
beforeUpdate:更新前,数据更新前调用,发生在虚拟DOM重新渲染和打好补丁之前,不会触发附加的重渲染过程;
updated:更新后,数据更新导致虚拟DOM重新渲染;
beforeDestroy:销毁前,vue实例销毁前调用,此时仍然可以访问使用实例的数据;
destroyed:销毁后,vue实例销毁后调用,此时组件实例和事件监听已经被销毁;
vue实现数据双向绑定的主要是采用数据劫持结合发布者订阅者者模式的方式,通过definedProperty()定义属性特征以及属性访问器getter/setter进行数据拦截,当数据发生变动时进入getter访问器,通过notify()通知订阅者,触发回调update方法更新数据视图。vue的双向绑定将MVVM作为绑定数据的入口,整合Observer,Compile和Watcher三者,开局Observe监听自己数据变化生成发布者,通过Compile解析模板指令,生成属性订阅者。
1、父传子:props
2、子传父:$emit (自定义事件),用法this.$emit(‘事件名’,参数[可选])
3、事件总线:
Vue.prototype.$bus = new Vue();
this.$bus.$on( event, callback )
this.$bus.$off( [event, callback] ) // [ ]是指可选,非数组
4、this.$parent、this.$children、this.$refs
5、vuex
https://juejin.cn/post/6905931622374342670
通过DNS解析域名的实际IP地址
DNS 解析首先会从你的浏览器的缓存中去寻找是否有这个网址对应的 IP 地址,如果没有就向OS系统的 DNS 缓存中寻找,如果没有就是路由器的 DNS 缓存, 如果没有就是 ISP 的DNS 缓存中寻找。 所以,缓存的寻找过程就是: 浏览器 -> 系统 -> 路由器 -> ISP。 如果在某一个缓存中找到的话,就直接跳到下一步。 如果都没有找到的话,就会向 ISP 或者公共的域名解析服务发起 DNS 查找请求。这个查找的过程还是一个递归查询的过程。
检查浏览器是否缓存(强缓存、协商缓存)
与服务器建立TCP/IP连接
若协议是https则会做加密
浏览器发送请求获取页面html
服务器响应html
浏览器解析 HTML
浏览器渲染页面
浏览器解析执行js脚本
浏览器发起网络请求
服务器响应ajax请求
浏览器处理事件循环等异步逻辑
interface
和 type
都可以被继承
类可以实现(implements)interface
以及 type
(除联合类型外)
interface
能够声明合并,type
不能JS 类型有哪些?
大数相加、相乘算法题,可以直接使用 bigint
,当然再加上字符串的处理会更好。
判断是否是数组
Array.isArray(arr)
Object.prototype.toString.call(arr) === '[Object Array]'
arr instanceof Array
array.constructor === Array
NaN
如何判断
JS 类型如何判断,有哪几种方式可用
instanceof
原理
手写 instanceof
什么是作用域
什么是作用域链
JS 中如何实现继承
通过原型实现的继承和 class
有何区别
ES5 构造函数的静态成员是定义在prototype上,方法是可以手动定义到prototype上
class 的静态成员和方法都是定义在prototype上
ES5的继承,实质是先创造子类的实例对象,然后再将父类的属性添加到实例对象上面。
class的继承, 实质是先创造父类的实例对象,然后再将子类的属性添加到this上面。必须先通过父类的构造函数完成塑造,然后再对其加工,加上子类自身的属性和方法。如果不调用super方法,子类就得不到this对象。
手写任意一种原型继承
new的过程
1.创建一个新对象
2.将新对象的__proto__
关联到构造函数的prototype
3.构造函数中的this指向该对象,执行构造函数中的代码
4.返回新对象(隐式返回)
数组去重
// ES6利用set去重
Array.from(new Set(arr))
// indexOf去重
arr.forEach((item)=> {
if(arry.indexOf(item) === -1) {
array.push(item);
}
})
Event Loop 执行顺序如下所示:
手写发布订阅
script defer async属性
defer:
后台加载脚本,文档解析过程不中断,执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成,并且多个defer会按照顺序执行。
async:
后台加载脚本,文档解析过程不中断,async则是在js加载好之后就会执行,并且多个async,哪个加载好就执行哪个,执行时会阻塞文档解析
如果同时存在defer和async ,则defer 的优先级更高
图片的懒加载和预加载
预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。new Image()
懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。
Ajax
// 原生ajax
// ajax更多内容(方法、参数等等)请到W3school查看
function myAjax(url) {
return new Promise((resolve, reject) => {
// 1、创建XMLHttpRequest对象(简称XHR)
// 前者 for IE7+, Firefox, Chrome, Opera, Safari
// 后者 for IE6, IE5
var XHR = window.XMLHttpRequest
? new XMLHttpRequest()
: new ActiveXObject('Microsoft.XMLHTTP')
// 2、建立链接
/*
参数:
1、method:请求的类型;GET 或 POST
*GET:请求参数在url后边拼接,send方法为空参
*POST:请求参数在send方法中定义,支持多种格式,详细请查看这个网址
https://blog.csdn.net/hsl0530hsl/article/details/88558353
请求无副作用时(如进行搜索),便可使用GET方法
请求有副作用时(如添加数据行),则用POST方法
2、url:所请求的文件在服务器上的位置
3、async:true(异步)或 false(同步)
*/
XHR.open('GET', url, true)
// post 方式发送数据 需要设置请求头
// XHR.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
// xhr.responseType:设置返回数据的类型
// 3、发送请求
XHR.send()
// xhr.onload()只有状态码为4时才能回调一次函数。
// xhr.onprogress(),也就是当状态码为3时(在请求完成之前),会周期性执行这个函数。
// 4、接受并处理来自服务器的响应结果
// 当XHR对象的就绪状改变就触发
XHR.onreadystatechange = function () {
// readyState == 4 :请求已完成,且响应已就绪
// XHR.status == 200 :'请求成功'
if (XHR.readyState == 4 && XHR.status == 200) {
// responseText 获得字符串形式的响应数据
resolve(JSON.parse(XHR.responseText))
} else if (XHR.status >= 400) {
reject('发生错误')
}
}
})
}
WebSocket 单例模式
存储
状态码
TCP/IP 三次握手 四次挥手
HTTP、HTTPS
HTTPS 协议(HyperText Transfer Protocol over Secure Socket Layer):一般理解为HTTP+SSL/TLS,通过 SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密。
https
就是为了解决上述http协议的安全性问题诞生的。https并非是应用层的新协议,是基于http协议的,在http与tcp通信之间新增SSL/TLS协议。
http: IP ➜ TCP ➜ HTTP(应用层)
https: IP ➜ TCP ➜ SSL / TLS ➜ HTTP(应用层)
HTTPS的不足
1、HTTPS协议多次握手,导致页面的加载时间延长近50%;
2、HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗;
3、申请SSL证书需要钱,功能越强大的证书费用越高。
4、SSL涉及到的安全算法会消耗 CPU 资源,对服务器资源消耗较大。
5、不是绝对安全的,掌握加密算法的组织同样可以进行中间人形式的攻击
HTTP请求方法
GET:从指定的资源请求数据
POST:向指定的资源提交要被处理的数据,可能会建立新资源或修改已有资源,常用于注册、提交表单
HEAD :类似于get请求,只不过返回的响应中没有具体的内容,只有头部
PUT:更新资源
DELETE:删除资源
GET 和 POST 的区别
DNS 解析顺序
浏览器缓存:浏览器会按照一定的频率缓存 DNS 记录。
操作系统缓存:如果浏览器缓存中找不到需要的 DNS 记录,那就去操作系统中找。
路由缓存:路由器也有 DNS 缓存。
ISP 的 DNS 服务器:ISP 是互联网服务提供商(Internet Service Provider)的简称,ISP 有专门的 DNS 服务器应对 DNS 查询请求。
根服务器:ISP 的 DNS 服务器还找不到的话,它就会向根服务器发出请求,进行递归查询(DNS 服务器先问根域名服务器.com 域名服务器的 IP 地址,然后再问.baidu 域名服务器,依次类推)
CDN
CDN的全称是Content Delivery Network,即内容分发网络。CDN的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网络中,在用户访问网站时,利用全局负载技术将用户的访问指向距离最近的工作正常的缓存服务器上,由缓存服务器直接响应
优点
进程和线程
进程是资源分配的最小单位,线程是CPU调度的最小单位
进程是一个工厂,工厂有它的独立资源 -> 系统分配的内存(独立的一块内存)
工厂之间相互独立 -> 进程之间相互独立(不同进程之间可以相互通信,不过代价很大)
线程是工厂中的工人,多个工人协作完成任务 -> 多个线程在进程中协作完成任务(代价很小)
工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成
工人之间共享空间 - -> 同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)
Webpack
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle。
webpack 和 gulp 区别
gulp强调的是前端开发的工作流程,我们可以通过配置一系列的task,定义task处理的事务(例如文件压缩合并、雪碧图、启动server、版本控制等),然后定义执行顺序,来让gulp执行这些task,从而构建项目的整个前端开发流程。
webpack是一个前端模块化方案,更侧重模块打包,我们可以把开发中的所有资源(图片、js文件、css文件等)都看成模块,通过loader(加载器)和plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源。
前端模块化
前端模块化就是复杂的文件编程一个一个独立的模块,比如js文件等等,分成独立的模块有利于重用(复用性)和维护(版本迭代),这样会引来模块之间相互依赖的问题,所以有了commonJS 规范,AMD,CMD规范等等,以及用于js打包(编译等处理)的工具 webpack
模块化规范
组件化
组件化的思想是先分治,后复用。即先将页面上各个独立的UI功能区域拆分并封装为对应组件,再进行组合复用。
我们可以利用组件化开发,拆分功能,封装组件,单独维护。
组件化的优势
组件化的特点:独立性、复用性、可嵌套、可扩展
组件化大大提高了维护性,复用性,扩展性,真正做到做到一处封装,处处使用。
组件化和模块化的区别
组件化侧重于UI部分的封装
模块化侧重于功能或者数据的封装。一组相关的组件可以定义成一个模块,一个暴露了通用验证方法的对象可以定义成一个模块,一个全局的json配置文件也可以定义成一个模块。
前端工程化
将系统化的方法用于前端应用的开发、运行和维护过程,可从以下四方面展开:
模块化、组件化、规范化、自动化
建议你改为在 JavaScript 文件中导入资源
这种机制提供了许多好处:
1、脚本和样式被压缩并打包在一起,以避免额外的网络请求。
2、缺少文件会导致编译错误,而不是给用户 404 错误。
3、结果文件名包含内容哈希,因此你无需担心浏览器会缓存旧版本。
前端安全
XSS(跨站脚本攻击)
存储型
攻击的代码被服务端写入进数据库中,常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。
反射型
一般通过修改 URL 参数的方式加入攻击代码,当用户打开带有恶意代码的URL的时候,网站服务端将恶意代码从URL中取出,并且返回给浏览器端。
这种常见于通过 URL 传递参数的功能,如网站搜索、跳转等。
防御
CSRF(跨站点请求伪造)
冒充用户发起请求(在用户不知情的情况下), 完成一些违背用户意愿的事情(如修改用户信息,删除评论等)。
CSRF攻击是有条件的
1、客户端必须一个网站并生成cookie凭证存储在浏览器中
2、该cookie没有清除,客户端又打开一个危险页面向受信任网站发送合法请求
防御
点击劫持(UI 覆盖攻击)
攻击者使用一个透明的iframe,覆盖在一个网页上,然后诱使用户在该页面上进行操作,此时用户将在不知情的情况下点击透明的iframe页面
防御
设置我们的网页不允许使用iframe被加载到其他网页中就可以避免这种情况了法。
响应头中设置X-Frame-Options
(服务器端进行)
中间人攻击
中间人(Man-in-the-middle attack, MITM)是指攻击者和通讯的两端分别创建独立的联系,并交换其得到的数据,攻击者可以拦截通信双方的通话并插入新的内容。
防御
采用HTTPS通信,检验证书。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。