Null和Undefined
1.null和undefined的区别
console.log(null==undefined) // true
console.log(null===undefined) // false
null:Null类型代表“空值”,代表一个空对象指针,使用typeof运算得到object,所以可以认为它是一个特殊的对象值。
undefined: Undefined类型,当一个变量声明但是未初始化时,得到的就是undefined
实际上,undefined值是派生自null的,ECMAScript标准规定对二者进行相等性测试要返回true
2.null和undefined的区分
null表示“没有对象”,即该处不应该有值。
典型用法:
(1)作为函数的参数,表示该函数的参数不是对象
(2)作为对象原型链的终点
undefined表示缺少值,即此处应该有一个值
典型用法:
(1)变量被声明了,但是没有被赋值,就等于undefined
(2)调用函数时,应提供的参数没有提供,该参数等于undefined
(3)对象没有赋值的属性,该属性的值为undefined
(4)函数没有返回值,默认返回undefined
VUE的两种路由模式及区别
hash模式:
不包含在http请求中,对后端无影响,改变hash值不会重载页面
仅hash符号之前的内容会被包含在请求中,因此对后端来说,及时没有做到对路由的全覆盖,也不会返回404错误
设置的新值,必须与原来不一样才会触发动作将记录添加到栈中
只可修改#后面的部分,因此只能设置与当前URL同文档的URL
只可添加短字符串
history模式:
该模式仅在服务器环境下才有效果
包含go,back,forward三个方法,对应浏览器的跳转,后退和前进的三个操作
修改历史状态,包含pushState和replaceState两个方法,均接收参数state,title,url
该模式会请求服务器,所以需要后端配合,并在无法获取对应的服务器资源的情况下,跳转到对应的404页面
闭包
概念:有权访问另一个函数作用域中变量的函数
创建闭包的常见方式是,在一个函数中创建另一个函数
作用:访问函数内部变量,保持函数在环境中一直存在,不会被垃圾回收机制处理
特点:
1.让外部访问函数内部变量称为可能
2.局部变量会常驻内存中
3.可以避免使用全局变量,放置全局变量污染
4.会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
引申:
1.内存泄漏:是指变量在被创建之后没有被及时的清理或销毁掉,而占用空闲的内存,泄漏过多会导致后续程序申请不到内存,出现内存溢出
2.堆栈溢出:指内存空间已经被申请完了,后续程序无法再申请
解决办法:
- 尽量避免使用全局变量
- 谨慎绑定DOM事件,在销毁阶段解绑相关事件
- 避免过度使用闭包
箭头函数
引入箭头函数有两个方面的作用:更简短的函数并且不绑定this
当箭头函数只有一个参数且只有一个return时,可以省略圆括号和return关键字
箭头函数不会创建自己的this,他只会从自己的作用域链的上一层继承this
由于箭头函数没有自己的this指针,通过call()和apply()方法调用函数时,只能传递参数,第一个参数会被忽略
箭头函数不绑定arguments对象
箭头函数不能用作构造器,和new一起用会抛出错误
箭头函数没有prototype属性
yield关键字通常不能在箭头函数中使用,因此,箭头函数不能用作函数生成器
Vue组件传值
父传子:props
子传父:$emit
兄弟传值:
1.在vue文件main.js中,我们利用 Vue.prototype.bus=new Vue()
来定义,此时我们就有了一个中间量。
传值方式:this.bus.$emit(methodName,param)
接收方式:this.bus.$on(methodName,msg => {})
2.vuex
vue性能优化
1.路由懒加载
2.图片懒加载
3.keep-alive缓存页面
4.复用DOM使用v-show而非v-if
5.长列表性能优化
6.定时器等事件的销毁
7.第三方插件按需引入
8.变量本地化
写React/Vue项目时,为什么要在列表组件里写key,其作用是什么
为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素
尽可能在使用v-for时提供key attribute,除非遍历输出的DOM非常简单,或者刻意依赖默认行为以获取性能上的提升
不要使用对象或者数组之类的非基本类型值作为v-for的key。请使字符串或者数值类型的值
如果不使用key,vue会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用key时,它会基于key的变化重新排列元素顺序,并且会移除key不存在的元素。
补充:不要用index值作为key值来使用
- 尽量用数据列表提供的id作为key值使用,如果实在没有,就用一些工具库生成唯一id
- 列表顺序会改变,不要用index值作为key,会导致vue复用很多旧子节点,做很多额外的工作
- 不要用随机数作为key值,不然旧节点会被全部删掉,新节点重新创建
分析 [‘1‘, ‘2‘, ‘3‘].map(parseInt) 答案是多少?
parseInt(string, radix)
string:需要转化的字符,
radix:表示进制。省略radix或者radix为0时,数字将以10进制被解析。radix应取2-36之间的整型,默认用10
如parseInt(234, 5) // 2*5^2 + 3*5^1 + 4*5^0 = 166
array.map(callback[, this.Arg])
将数组的各个元素依次传入回调函数callback,回调函数返回的结果依次替换原数组对应的元素。
回调函数callback会被自动传入的三个参数:数组元素,元素索引,原数组本身。
map为parseInt传三个参数(elem, index, array),其中index为数组索引
因此,map遍历['1', '2', '3'],相应的parseInt接收的参数如下
parseInt('1', 0) // 1 radix为0时,默认取10
parseInt('2', 1) // NaN radix只能取2-36之间的整数
parseInt('3', 2) // NaN radix为2时,3无法识别
map函数返回的是一个数组,所以最后结果为[1, NaN, NaN]
浏览器缓存机制
以下两点是浏览器缓存机制的关键,它确保了每个缓存请求的缓存存入与读取
- 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
- 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
强制缓存
强缓存就是浏览器不会向服务器发送任何请求,直接从本地缓存中读取文件并返回statusCode:200 OK
200 form memory cache : 不访问服务器,一般已经加载过该资源且缓存在了内存当中,直接从内存中读取缓存。浏览器关闭后,数据将不存在(资源被释放掉了),再次打开相同的页面时,不会出现from memory cache。
200 from disk cache: 不访问服务器,已经在之前的某个时间加载过该资源,直接从硬盘中读取缓存,关闭浏览器后,数据依然存在,此资源不会随着该页面的关闭而释放掉下次打开仍然会是from disk cache。
优先访问memory cache,其次是disk cache,最后是请求网络资源
强制缓存的三种情况:
- 不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求
- 存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存
- 存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果
Cache-control
在Http/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:
- public:所有内容都将被缓存(客户端和代理服务器都可以缓存)
- private:所有内容只有客户端可以缓存,Cache-Control的默认取值
- no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
- no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
- max-age=xxx:缓存内容将在xxx秒后失效
Expires
Expires是HTTP/1.0控制你网页缓存的字段,其值为服务器返回该请求结果缓存的到期时间,即再次发起该请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。
到HTTP/1.1该字段已被Cache-Control替代
协商缓存
协商缓存会向服务器发送请求,服务器会根据这个请求的request header的一些参数来判断是否命中协商缓存,如果命中,则返回304状态码并带上新的request header通知浏览器从缓存中读取资源
Last-Modifed/If-Modified-Since和Etag/If-None-Match是分别成对出现的,呈一一对应关系
Etag是属于HTTP 1.1属性,它是由服务器(Apache或者其他工具)生成返回给前端,用来帮助服务器控制Web端的缓存验证。 Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。
If-None-Match:当资源过期时,浏览器发现响应头里有Etag,则再次像服务器请求时带上请求头if-none-match(值是Etag的值)。服务器收到请求进行比对,决定返回200或304
Vue中的diff算法
virtual dom是一个Object对象模拟dom节点的属性,再通过render渲染成为真实的dom。diff算法则是通过js层面的计算,返回一个patch对象,解析patch对象来完成页面的重新渲染。
- 用对象模拟虚拟dom
- 将虚拟dom转换成真实dom并渲染到页面上
- 监控事件改变修改虚拟dom
- 比较新旧虚拟dom的修改得到不同之处patch对象
- 将差异对象渲染到页面上
function createElement() {
constructor(el, attr, child) {
this.el = el
this.attrs = attr
this.child = child || []
}
render() {
let virtualDom = document.createElement(this.el)
// 遍历attr对象属性赋值到虚拟dom上
for(let attr in this.attrs){
virtualDom.setAttribute(attr, this.attrs[attr])
}
// 深度遍历child
this.child.forEach(el => {
let childElement = (el instanceof createElement) ? el.render() : document.createTextNode(el)
virtualDom.appendChild(childElement)
})
return virtualDom
}
}
module.exports = element
用JavaScript对象构建一个虚拟dom树之后,再将虚拟dom渲染成真实的dom树
let element = require(./element) // 此处模拟element模块引入
let myobj = {
'class': 'my-obj'
}
let ul = element('div', myobj, [
'我是文字',
element('div',{'id': 'one'}, ['1'])
element('div',{'id': 'two'}, ['2'])
element('div',{'id': 'three'}, ['3'])
])
ul = ul.render()
document.body.appendChild(ul)
比较只会同层级比较,不会跨层级比较
- 比较是否都是文本节点,如果是,则替换文本
- 比较是否只是属性更改,如果是,则替换属性
- 检查是否原节点还存在,如果是,则进行节点的增删
- 检查整个节点是否都更改了,如果是,则替换原来的节点内容
let utils = require('./utils');
let keyIndex = 0;
function diff(oldTree, newTree) {
//记录差异的空对象。key就是老节点在原来虚拟DOM树中的序号,值就是一个差异对象数组
let patches = {};
keyIndex = 0; // 儿子要起另外一个标识
let index = 0; // 父亲的表示 1 儿子的标识就是1.1 1.2
walk(oldTree, newTree, index, patches);
return patches;
}
//遍历
function walk(oldNode, newNode, index, patches) {
let currentPatches = [];//这个数组里记录了所有的oldNode的变化
if (!newNode) {//如果新节点没有了,则认为此节点被删除了
currentPatches.push({ type: utils.REMOVE, index });
//如果说老节点的新的节点都是文本节点的话
} else if (utils.isString(oldNode) && utils.isString(newNode)) {
//如果新的字符符值和旧的不一样
if (oldNode != newNode) {
///文本改变
currentPatches.push({ type: utils.TEXT, content: newNode });
}
} else if (oldNode.tagName == newNode.tagName) {
//比较新旧元素的属性对象
let attrsPatch = diffAttr(oldNode.attrs, newNode.attrs);
//如果新旧元素有差异 的属性的话
if (Object.keys(attrsPatch).length > 0) {
//添加到差异数组中去
currentPatches.push({ type: utils.ATTRS, attrs: attrsPatch });
}
//自己比完后再比自己的儿子们
diffChildren(oldNode.children, newNode.children, index, patches, currentPatches);
} else {
currentPatches.push({ type: utils.REPLACE, node: newNode });
}
if (currentPatches.length > 0) {
patches[index] = currentPatches;
}
}
//老的节点的儿子们 新节点的儿子们 父节点的序号 完整补丁对象 当前旧节点的补丁对象
function diffChildren(oldChildren, newChildren, index, patches, currentPatches) {
oldChildren.forEach((child, idx) => {
walk(child, newChildren[idx], ++keyIndex, patches);
});
}
function diffAttr(oldAttrs, newAttrs) {
let attrsPatch = {};
for (let attr in oldAttrs) {
//如果说老的属性和新属性不一样。一种是值改变 ,一种是属性被删除 了
if (oldAttrs[attr] != newAttrs[attr]) {
attrsPatch[attr] = newAttrs[attr];
}
}
for (let attr in newAttrs) {
if (!oldAttrs.hasOwnProperty(attr)) {
attrsPatch[attr] = newAttrs[attr];
}
}
return attrsPatch;
}
module.exports = diff;
Vue是如何实现数据双向绑定的
- 监听器Observer,遍历数据对象的每个属性及子属性的每个属性,利用Object.defineProperty()对每个属性绑定getter和setter,劫持并监听属性变化
- 订阅器Dep,用来收集Observer的属性变化,并通知到订阅者
- 订阅者Watcher,用来沟通Observer和Compile的桥梁,接收Observer中通知的属性变化并通知Compile调用更新函数更新视图
- 解析器Compile,解析模板指令,将模板中的变量解析为具体的数据,初始化整个视图;并在每个模板指令对应的节点绑定更新函数;添加监听数据的订阅者,一旦数据发生变化,就会调用更新函数更改视图。
如图:
手写一个EventEmitter
class EventEmitter {
contructor() {
this.listeners = {}
}
// 注册事件监听者
on(type, cb) {
if (!this.listeners[type]) {
this.listeners[type] = []
}
this.listeners[type].push(cb)
}
// 发布事件
emit(type, ...args) {
if (this.listeners[type]) {
this.listeners[type].forEach(cb => {
cb(...args)
})
}
}
// 移除某个事件监听者
off(type, cb) {
if (this.listeners[type]) {
const index = this.listeners[type].findIndex(item => item === cb)
if (index !== -1) {
this.listeners[type].splice(index, 1)
}
if (this.listeners[type].length === 0) {
delete this.listeners[type]
}
}
}
// 移除所有事件监听者
offAll(type) {
if (this.listeners[type]) {
delete this.listeners[type]
}
}
}
扁平数组转树
// 普通的递归
const getChildren = (data, result, pid) => {
data.forEach(item => {
if (item.pid === pid) {
const newItem = {...item, children: []}
result.push(newItem)
getChildren(data, item.children, item.id)
}
})
}
const arrayToTree = (data, pid) => {
let result = []
getChildren(data, result, pid)
return result
}
// map方法
const arrayToTree = (data) => {
let result = []
let itemMap = {}
for (const item of data) {
itemMap[item.id] = {...item, children: []}
}
for (const item of data) {
const id = item.id
const pid = item.pid
const treeItem = itemMap[item.id]
if (pid === 0) {
result.push(treeItem)
} else {
if (!item[pid]) {
itemMap[pid] = {
children: []
}
}
itemMap[pid].children.push(treeItem)
}
}
return result
}
浅拷贝与深拷贝
-
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
-
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
实现方式:
浅拷贝:Object.assign()、lodash._clone、展开运算符...、Array.prototype.concat()、Array.prototype.slice()
深拷贝:JSON.parse(JSON.stringfy())、lodash._cloneDeep、手写递归(ps: JSON.parse(JSON.stringfy())方法无法处理函数和正则,正则会变为空,函数会变为null)
// 该深拷贝方法只考虑了是否是数组
const deepClone = (source) => {
if (typeof source === 'object' && source != null) {
let targetObj = source.constructor === Array ? [] : {}
for (const key in source) {
if (source[key] && typeof(source[key]) === 'object') {
targetObj[key] = source[key].constructor === Array ? [] : {}
targetObj[key] = deepClone(source[key])
} else {
targetObj[key] = source[key]
}
}
return targetObj
} else {
return source
}
}
详解请见:如何写出一个惊艳面试官的深拷贝?
promise和async await
promise可实现链式调用
promise本身是无法终止的,它本身是一个状态机,一旦请求发出,必须闭环,无法取消请求
async函数是Generator函数的语法糖
async有以下几个特点
- 建立在promise的基础上。不能配合回调函数使用,但是会声明一个异步函数,并隐式的返回一个promise。
- 和promise一样是非阻塞的,但是不用写then及回调函数,减少了代码行数。
- 可以使异步代码在形式上接近于同步代码。
- await是一个运算符,会阻塞后面的代码,遇到Promise对象会直接resolve,否则会得到一个表达式的运算结果
async/await相较于Promise的优势
- 避免了代码嵌套,使代码更加简洁
- async/await使用try/catch处理同步和异步错误,但是promise需要使用.catch中进行代码逻辑的判断
- 易于调试打断点
手写Promise
// 手写Promise
class MyPromise {
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
constructor(executor) {
try {
executor(this.resolve, this.reject)
} catch (err) {
this.reject(err)
}
}
status = PENDING
value = null
reason = null
onFulfilledCallbacks = []
onRejectedCallback = []
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
while (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(value)
}
}
}
reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason)
}
}
}
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) => {
const realOnFulfilled = typeof onFulfilled === 'object' ? onFulfilled : value => value
const realOnRejected = typeof OnRejected === 'object' ? onRejected : reason => {throw reason}
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const x = realOnFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch(err) {
reject(err)
}
})
}
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = realOnRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (err) {
reject(err)
}
})
}
if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(fulfilledMicrotask)
this.onFulfilledCallbacks.push(rejectedMicrotask)
}
})
return promise2
}
static resolve = (parameter) => {
if (parameter instanceof MyPromise) {
return parameter
}
return new MyPromise(resolve => {
resolve(parameter)
})
}
static reject = (reason) => {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
return reject('抛出类型错误信息')
}
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
resolve(x)
}
}
module.exports = MyPromise
手写call、apply、bind
// 手写call
// 思路
// 1.根据call的规则设置上下文对象,也就是this的指向。
// 2.通过设置context的属性,将函数的this指向隐式绑定到context上
// 3.通过隐式绑定执行函数并传递参数。
// 4.删除临时属性,返回函数执行结果
Function.prototype.call = function (context, ...args) {
if (context === null || context === undefined) {
context = window
} else {
context = Object(context)
}
const specialPrototype = Symbol('唯一属性')
context[specialPrototype] = this
let result = context[specialPrototype](...args)
delete context[specialPrototype]
return result
}
// 手写apply
Function.prototype.myApply = function (context) {
if (context === null || context === undefined) {
context = window
} else {
context = Object(context)
}
const specialPrototype = Symbol('唯一属性')
context[specialPrototype] = this
let args = arguments[1]
let result
if (args) {
args = Array.from(args)
result = context[specialPrototype](...args)
} else {
result = context[specialPrototype]()
}
delete context[specialPrototype]
return result
}
// 手写bind
// 思路:
// 拷贝源函数:
// 通过变量储存源函数
// 使用Object.create复制源函数的prototype给fToBind
//返回拷贝的函数
//调用拷贝的函数:
// new调用判断:通过instanceof判断函数是否通过new调用,来决定绑定的context
// 绑定this+传递参数
// 返回源函数的执行结果
Function.prototype.myBind = function (objThis, ...params) {
let thisFn = this
let fToBind = function (...secondParams) {
const isNew = this instanceof fToBind
const context = isNew ? this : Object(objThis)
return thisFn.call(context, ...params, ...secondParams)
}
if (thisFn.prototype) {
fToBind.prototype = Object.create(thisFn.prototype)
}
return fToBind
}
基本类型和引用类型的区别
基本类型:
- 基本数据类型的值是不可变的
- 基本数据类型不可添加数据和方法
- 基本数据类型赋值是简单赋值
- 基本数据类型的比较是值的比较
- 基本数据类型是存放在栈区的
引用类型:
- 引用类型的值是可以改变的
- 引用类型的值可以添加属性和方法
- 引用类型的赋值是对象引用
- 引用类型的比较是引用的比较
- 引用类型是同时保存在栈区和堆区的,引用类型的存储需要在内存的栈区和堆区共同完成,栈区保存变量标识符和指向堆内存的地址
HTTP发展史
http发展分为四个阶段:http0.9、http1.0、http1.1、http2.0
http0.9:
http0.9是基于TCP/IP协议的应用层协议。
特点:不涉及到数据包的传输,主要规定了服务端和客户端之间的通信格式,默认使用80端口,只有一个GET命令
http1.0:
在http0.9的基础上增加了以下几点:
- 任何格式的内容都可以发送。不再限制于文字,图片、视频、二级制文件等均可传输
- 引入了POST/HEAD命令,丰富了浏览器和服务器的握手动作
- 加入头信心HTTP header
- 还增加了包括状态码status code、权限authorization、缓存cache等
仍存在一些缺点:
- 每次TCP链接都只能发送一个请求,想重新发起请求,只能在链接结束后再建立一个新链接
- TCP连接新建的成本很高,由于三次握手且初始发送速率较慢
http1.1:
该版本晚于1.0版本半年发布,基于前一版本进行了以下优化:
- 长链接:1.1支持长链接和请求的流水线处理,在一个TCP连接上可以进行多个http请求和响应,减少了建立和关闭的耗时,默认开启Connection:keep-alive
- 更多的缓存处理:1.1引入了更多的缓存控制策略,如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略
- 1.1在请求头引入了range头域,允许只请求资源的某个部分,让开发者可以更好的节约和运用带宽
- 新增了24个错误状态响应码,便于明确错误来源
- Host头处理,主要针对虚拟机的出现,即一台物理服务器存在多个虚拟主机,且他们共享一个IP地址
同时存在以下问题:
- 1.1虽然 允许TCP复用,但是通信是按照次序进行的,如果某一个请求比较慢,就会出现阻塞的情况,该情况就叫做队头阻塞
- 1.x传输数据的内容都是明文,无法保证数据安全
- header内容过大,增加了传输成本
- keep-alive使用较多会给服务器带来较大压力
SPDY协议:
- 采取多路复用降低延迟
- 设置请求优先级,保证重要资源先加载
- header压缩
- 基于HTTPS加密数据传输,保证数据传输安全性
- 服务端推送,服务端会推送给客户端追加的文件,供客户端请求缓存使用
HTTP2:
http2是基于SPDY的基础上建立的,但是两者略有不同:
- HTTP2支持明文传输,SPDY只支持HTTPS
- HTTP2header的压缩算法采用HPACK,SPDY采用DEFLATE
HTTP2的新特性:
- 二进制分帧
- 多路复用
- 请求优先级
- header压缩
- 服务端推送
前端的请求方式 :
- GET:该请求方式是安全的和幂等的。所谓安全就是该操作用于获取信息而非修改信息,幂等意味着对同一URL的多个请求应该返回同样的结果。GET的请求是可缓存的。URL长度是受限制的,最多为2048个字符。
- POST:向指定资源提交数据进行处理请求。
- CONNECT:开启一个客户端与资源之间的双向沟通通道
- DELETE:删除指定资源
- HEAD:请求资源的头部信息,跟GET作用差不多
- OPTIONS:用于获取目的资源所支持的通信选项
- PUT:使用请求中的负载创建或替换资源。PUT指定了资源存放位置,POST没有
- PATCH:用于对资源进行部分修改
- TEACE:消息回环测试,一种实用的debug机制
同源策略和跨域
同源策略:所谓的同源是一种安全机制。为了预防某些恶意行为,浏览器限制了从同一个源加载的文档或脚本与另一个源的资源进行交互,从协议、域名和端口号三方面来进行判断。
跨域:浏览器试图执行其他网站的脚本。由于同源策略的限制,导致我们无法跨域。
方式:
- CORS跨域,该方式关键在于服务器,Access-Control-Allow-Origin 字段为必须
- JSONP跨域,利用js的script标签的src属性携带callback
- postmessage
- websocket
- Node中间件代理
- nginx反向代理
详细的跨域解析可以参看九种跨域方式实现原理(完整版)