代码输入后果
console.log(1);setTimeout(() => { console.log(2); Promise.resolve().then(() => { console.log(3) });});new Promise((resolve, reject) => { console.log(4) resolve(5)}).then((data) => { console.log(data);})setTimeout(() => { console.log(6);})console.log(7);
代码输入后果如下:
1475236
代码执行过程如下:
- 首先执行scrip代码,打印出1;
- 遇到第一个定时器setTimeout,将其退出到宏工作队列;
- 遇到Promise,执行外面的同步代码,打印出4,遇到resolve,将其退出到微工作队列;
- 遇到第二个定时器setTimeout,将其退出到红工作队列;
- 执行script代码,打印出7,至此第一轮执行实现;
- 指定微工作队列中的代码,打印出resolve的后果:5;
- 执行宏工作中的第一个定时器setTimeout,首先打印出2,而后遇到 Promise.resolve().then(),将其退出到微工作队列;
- 执行完这个宏工作,就开始执行微工作队列,打印出3;
- 继续执行宏工作队列中的第二个定时器,打印出6。
响应式设计的概念及基本原理
响应式网站设计(Responsive Web design
)是一个网站可能兼容多个终端,而不是为每一个终端做一个特定的版本。
对于原理: 基本原理是通过媒体查问(@media)
查问检测不同的设施屏幕尺寸做解决。
对于兼容: 页面头部必须有mate申明的viewport
。
<meta name="’viewport’" content="”width=device-width," initial-scale="1." maximum-scale="1,user-scalable=no”"/>
具体阐明 Event loop
家喻户晓 JS 是门非阻塞单线程语言,因为在最后 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话,咱们在多个线程中解决 DOM 就可能会产生问题(一个线程中新加节点,另一个线程中删除节点),当然能够引入读写锁解决这个问题。
JS 在执行的过程中会产生执行环境,这些执行环境会被程序的退出到执行栈中。如果遇到异步的代码,会被挂起并退出到 Task(有多种 task) 队列中。一旦执行栈为空,Event Loop 就会从 Task 队列中拿出须要执行的代码并放入执行栈中执行,所以实质上来说 JS 中的异步还是同步行为。
console.log('script start');setTimeout(function() { console.log('setTimeout');}, 0);console.log('script end');
以上代码尽管 setTimeout
延时为 0,其实还是异步。这是因为 HTML5 标准规定这个函数第二个参数不得小于 4 毫秒,有余会主动减少。所以 setTimeout
还是会在 script end
之后打印。
不同的工作源会被调配到不同的 Task 队列中,工作源能够分为 微工作(microtask) 和 宏工作(macrotask)。在 ES6 标准中,microtask 称为 jobs
,macrotask 称为 task
。
console.log('script start');setTimeout(function() { console.log('setTimeout');}, 0);new Promise((resolve) => { console.log('Promise') resolve()}).then(function() { console.log('promise1');}).then(function() { console.log('promise2');});console.log('script end');// script start => Promise => script end => promise1 => promise2 => setTimeout
以上代码尽管 setTimeout
写在 Promise
之前,然而因为 Promise
属于微工作而 setTimeout
属于宏工作,所以会有以上的打印。
微工作包含 process.nextTick
,promise
,Object.observe
,MutationObserver
宏工作包含 script
, setTimeout
,setInterval
,setImmediate
,I/O
,UI rendering
很多人有个误区,认为微工作快于宏工作,其实是谬误的。因为宏工作中包含了 script
,浏览器会先执行一个宏工作,接下来有异步代码的话就先执行微工作。
所以正确的一次 Event loop 程序是这样的
- 执行同步代码,这属于宏工作
- 执行栈为空,查问是否有微工作须要执行
- 执行所有微工作
- 必要的话渲染 UI
- 而后开始下一轮 Event loop,执行宏工作中的异步代码
通过上述的 Event loop 程序可知,如果宏工作中的异步代码有大量的计算并且须要操作 DOM 的话,为了更快的 界面响应,咱们能够把操作 DOM 放入微工作中。
Node 中的 Event loop
Node 中的 Event loop 和浏览器中的不雷同。
Node 的 Event loop 分为6个阶段,它们会依照程序重复运行
┌───────────────────────┐┌─>│ timers ││ └──────────┬────────────┘│ ┌──────────┴────────────┐│ │ I/O callbacks ││ └──────────┬────────────┘│ ┌──────────┴────────────┐│ │ idle, prepare ││ └──────────┬────────────┘ ┌───────────────┐│ ┌──────────┴────────────┐ │ incoming: ││ │ poll │<──connections─── ││ └──────────┬────────────┘ │ data, etc. ││ ┌──────────┴────────────┐ └───────────────┘│ │ check ││ └──────────┬────────────┘│ ┌──────────┴────────────┐└──┤ close callbacks │ └───────────────────────┘
timer
timers 阶段会执行 setTimeout
和 setInterval
一个 timer
指定的工夫并不是精确工夫,而是在达到这个工夫后尽快执行回调,可能会因为零碎正在执行别的事务而提早。
上限的工夫有一个范畴:[1, 2147483647]
,如果设定的工夫不在这个范畴,将被设置为1。
I/O
I/O 阶段会执行除了 close 事件,定时器和 setImmediate
的回调
idle, prepare
idle, prepare 阶段外部实现
poll
poll 阶段很重要,这一阶段中,零碎会做两件事件
- 执行到点的定时器
- 执行 poll 队列中的事件
并且当 poll 中没有定时器的状况下,会发现以下两件事件
- 如果 poll 队列不为空,会遍历回调队列并同步执行,直到队列为空或者零碎限度
如果 poll 队列为空,会有两件事产生
- 如果有
setImmediate
须要执行,poll 阶段会进行并且进入到 check 阶段执行setImmediate
- 如果没有
setImmediate
须要执行,会期待回调被退出到队列中并立刻执行回调
- 如果有
如果有别的定时器须要被执行,会回到 timer 阶段执行回调。
check
check 阶段执行 setImmediate
close callbacks
close callbacks 阶段执行 close 事件
并且在 Node 中,有些状况下的定时器执行程序是随机的
setTimeout(() => { console.log('setTimeout');}, 0);setImmediate(() => { console.log('setImmediate');})// 这里可能会输入 setTimeout,setImmediate// 可能也会相同的输入,这取决于性能// 因为可能进入 event loop 用了不到 1 毫秒,这时候会执行 setImmediate// 否则会执行 setTimeout
当然在这种状况下,执行程序是雷同的
var fs = require('fs')fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });});// 因为 readFile 的回调在 poll 中执行// 发现有 setImmediate ,所以会立刻跳到 check 阶段执行回调// 再去 timer 阶段执行 setTimeout// 所以以上输入肯定是 setImmediate,setTimeout
下面介绍的都是 macrotask 的执行状况,microtask 会在以上每个阶段实现后立刻执行。
setTimeout(()=>{ console.log('timer1') Promise.resolve().then(function() { console.log('promise1') })}, 0)setTimeout(()=>{ console.log('timer2') Promise.resolve().then(function() { console.log('promise2') })}, 0)// 以上代码在浏览器和 node 中打印状况是不同的// 浏览器中打印 timer1, promise1, timer2, promise2// node 中打印 timer1, timer2, promise1, promise2
Node 中的 process.nextTick
会先于其余 microtask 执行。
setTimeout(() => { console.log("timer1"); Promise.resolve().then(function() { console.log("promise1"); });}, 0);process.nextTick(() => { console.log("nextTick");});// nextTick, timer1, promise1
什么是 CSRF 攻打?
(1)概念
CSRF 攻打指的是跨站申请伪造攻打,攻击者诱导用户进入一个第三方网站,而后该网站向被攻打网站发送跨站申请。如果用户在被攻打网站中保留了登录状态,那么攻击者就能够利用这个登录状态,绕过后盾的用户验证,假冒用户向服务器执行一些操作。
CSRF 攻打的实质是利用 cookie 会在同源申请中携带发送给服务器的特点,以此来实现用户的假冒。
(2)攻打类型
常见的 CSRF 攻打有三种:
- GET 类型的 CSRF 攻打,比方在网站中的一个 img 标签里构建一个申请,当用户关上这个网站的时候就会主动发动提交。
- POST 类型的 CSRF 攻打,比方构建一个表单,而后暗藏它,当用户进入页面时,主动提交这个表单。
- 链接类型的 CSRF 攻打,比方在 a 标签的 href 属性里构建一个申请,而后诱导用户去点击。
代码输入问题
function A(){}function B(a){ this.a = a;}function C(a){ if(a){this.a = a; }}A.prototype.a = 1;B.prototype.a = 1;C.prototype.a = 1;console.log(new A().a);console.log(new B().a);console.log(new C(2).a);
输入后果:1 undefined 2
解析:
- console.log(new A().a),new A()为构造函数创立的对象,自身没有a属性,所以向它的原型去找,发现原型的a属性的属性值为1,故该输入值为1;
- console.log(new B().a),ew B()为构造函数创立的对象,该构造函数有参数a,但该对象没有传参,故该输入值为undefined;
- console.log(new C(2).a),new C()为构造函数创立的对象,该构造函数有参数a,且传的实参为2,执行函数外部,发现if为真,执行this.a = 2,故属性a的值为2。
代码输入后果
function a() { console.log(this);}a.call(null);
打印后果:window对象
依据ECMAScript262标准规定:如果第一个参数传入的对象调用者是null或者undefined,call办法将把全局对象(浏览器上是window对象)作为this的值。所以,不论传入null 还是 undefined,其this都是全局对象window。所以,在浏览器上答案是输入 window 对象。
要留神的是,在严格模式中,null 就是 null,undefined 就是 undefined:
'use strict';function a() { console.log(this);}a.call(null); // nulla.call(undefined); // undefined
参考 前端进阶面试题具体解答
深拷贝浅拷贝
浅拷贝:浅拷贝通过ES6新个性Object.assign()或者通过扩大运算法...来达到浅拷贝的目标,浅拷贝批改正本,不会影响原数据,但毛病是浅拷贝只能拷贝第一层的数据,且都是值类型数据,如果有援用型数据,批改正本会影响原数据。深拷贝:通过利用JSON.parse(JSON.stringify())来实现深拷贝的目标,但利用JSON拷贝也是有毛病的,当要拷贝的数据中含有undefined/function/symbol类型是无奈进行拷贝的,当然咱们想我的项目开发中须要深拷贝的数据个别不会含有以上三种类型,如有须要能够本人在封装一个函数来实现。
compose
题目形容:实现一个 compose 函数
// 用法如下:function fn1(x) { return x + 1;}function fn2(x) { return x + 2;}function fn3(x) { return x + 3;}function fn4(x) { return x + 4;}const a = compose(fn1, fn2, fn3, fn4);console.log(a(1)); // 1+4+3+2+1=11
实现代码如下:
function compose(...fn) { if (!fn.length) return (v) => v; if (fn.length === 1) return fn[0]; return fn.reduce( (pre, cur) => (...args) => pre(cur(...args)) );}
数组去重
应用 indexOf/includes 实现
function unique(arr) { var res = []; for(var i = 0; i < arr.length; i++) { if(res.indexOf(arr[i]) === -1) res.push(arr[i]); // if(!res.includes(arr[i])) res.push(arr[i]); } return res;}
应用 filter(forEach) + indexOf/includes 实现
// filterfunction unique(arr) { var res = arr.filter((value, index) => { // 只存第一个呈现的元素 return arr.indexOf(value) === index; }); return res;}// forEachfunction unique(arr) { var res = []; arr.forEach((value) => { if(!res.includes(value)) res.push(value); }); return res;}
非 API 版本(原生)实现
function unique(arr) { var res = []; for(var i = 0; i < arr.length; i++) { var flag = false; for(var j = 0; j < res.length; j++) { if(arr[i] === res[j]) { flag = true; break; } } if(flag === false) res.push(arr[i]); } return res;}
ES6 应用 Set + 扩大运算符(...)/Array.from() 实现
function unique(arr) { // return [...new Set(arr)]; return Array.from(new Set(arr));}
深拷贝
实现一:不思考 Symbol
function deepClone(obj) { if(!isObject(obj)) return obj; let newObj = Array.isArray(obj) ? [] : {}; // for...in 只会遍历对象本身的和继承的可枚举的属性(不含 Symbol 属性) for(let key in obj) { // obj.hasOwnProperty() 办法只思考对象本身的属性 if(obj.hasOwnProperty(key)) { newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]; } } return newObj;}
实现二:思考 Symbol
// hash 作为一个查看器,防止对象深拷贝中呈现环援用,导致爆栈function deepClone(obj, hash = new WeakMap()) { if(!isObject(obj)) return obj; // 查看是有存在雷同的对象在之前拷贝过,有则返回之前拷贝后存于hash中的对象 if(hash.has(obj)) return hash.get(obj); let newObj = Array.isArray(obj) ? [] : {}; // 备份存在hash中,newObj目前是空对象、数组。前面会对属性进行追加,这里存的值是对象的栈 hash.set(obj, newObj); // Reflect.ownKeys返回一个数组,蕴含对象本身的(不含继承的)所有键名,不论键名是 Symbol 或字符串,也不论是否可枚举。 Reflect.ownKeys(obj).forEach(key => { // 属性值如果是对象,则进行递归深拷贝,否则间接拷贝 newObj[key] = isObject(obj[key]) ? deepClone(obj[key], hash) : obj[key]; }); return newObj;}
Node 中的 Event Loop 和浏览器中的有什么区别?process.nextTick 执行程序?
Node 中的 Event Loop 和浏览器中的是齐全不雷同的货色。
Node 的 Event Loop 分为 6 个阶段,它们会依照程序重复运行。每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量达到零碎设定的阈值,就会进入下一阶段。
(1)Timers(计时器阶段):首次进入事件循环,会从计时器阶段开始。此阶段会判断是否存在过期的计时器回调(蕴含 setTimeout 和 setInterval),如果存在则会执行所有过期的计时器回调,执行结束后,如果回调中触发了相应的微工作,会接着执行所有微工作,执行完微工作后再进入 Pending callbacks 阶段。
(2)Pending callbacks:执行推延到下一个循环迭代的I / O回调(零碎调用相干的回调)。
(3)Idle/Prepare:仅供外部应用。
(4)Poll(轮询阶段):
- 当回调队列不为空时:会执行回调,若回调中触发了相应的微工作,这里的微工作执行机会和其余中央有所不同,不会等到所有回调执行结束后才执行,而是针对每一个回调执行结束后,就执行相应微工作。执行完所有的回调后,变为上面的状况。
- 当回调队列为空时(没有回调或所有回调执行结束):但如果存在有计时器(setTimeout、setInterval和setImmediate)没有执行,会完结轮询阶段,进入 Check 阶段。否则会阻塞并期待任何正在执行的I/O操作实现,并马上执行相应的回调,直到所有回调执行结束。
(5)Check(查问阶段):会查看是否存在 setImmediate 相干的回调,如果存在则执行所有回调,执行结束后,如果回调中触发了相应的微工作,会接着执行所有微工作,执行完微工作后再进入 Close callbacks 阶段。
(6)Close callbacks:执行一些敞开回调,比方socket.on('close', ...)等。
上面来看一个例子,首先在有些状况下,定时器的执行程序其实是随机的
setTimeout(() => { console.log('setTimeout')}, 0)setImmediate(() => { console.log('setImmediate')})
对于以上代码来说,setTimeout
可能执行在前,也可能执行在后
- 首先
setTimeout(fn, 0) === setTimeout(fn, 1)
,这是由源码决定的 - 进入事件循环也是须要老本的,如果在筹备时候破费了大于 1ms 的工夫,那么在 timer 阶段就会间接执行
setTimeout
回调 - 那么如果筹备工夫破费小于 1ms,那么就是
setImmediate
回调先执行了
当然在某些状况下,他们的执行程序肯定是固定的,比方以下代码:
const fs = require('fs')fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0) setImmediate(() => { console.log('immediate') })})
在上述代码中,setImmediate
永远先执行。因为两个代码写在 IO 回调中,IO 回调是在 poll 阶段执行,当回调执行结束后队列为空,发现存在 setImmediate
回调,所以就间接跳转到 check 阶段去执行回调了。
下面都是 macrotask 的执行状况,对于 microtask 来说,它会在以上每个阶段实现前清空 microtask 队列,
setTimeout(() => { console.log('timer21')}, 0)Promise.resolve().then(function() { console.log('promise1')})
对于以上代码来说,其实和浏览器中的输入是一样的,microtask 永远执行在 macrotask 后面。
最初来看 Node 中的 process.nextTick
,这个函数其实是独立于 Event Loop 之外的,它有一个本人的队列,当每个阶段实现后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其余 microtask 执行。
setTimeout(() => { console.log('timer1') Promise.resolve().then(function() { console.log('promise1') })}, 0)process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') process.nextTick(() => { console.log('nextTick') }) }) })})
对于以上代码,永远都是先把 nextTick 全副打印进去。
Object.assign()
形容:Object.assign()
办法用于将所有可枚举(Object.propertyIsEnumerable()
返回 true
)和自有(Object.hasOwnProperty()
返回 true
)属性的值从一个或多个源对象复制到指标对象。它将返回批改后的指标对象(请留神这个操作是浅拷贝)。
实现:
Object.assign = function(target, ...source) { if(target == null) { throw new TypeError('Cannot convert undefined or null to object'); } let res = Object(target); source.forEach(function(obj) { if(obj != null) { // for...in 只会遍历对象本身的和继承的可枚举的属性(不含 Symbol 属性) // hasOwnProperty 办法只思考对象本身的属性 for(let key in obj) { if(obj.hasOwnProperty(key)) { res[key] = obj[key]; } } } }); return res;}
代码输入后果
async function async1 () { console.log('async1 start'); await new Promise(resolve => { console.log('promise1') }) console.log('async1 success'); return 'async1 end'}console.log('srcipt start')async1().then(res => console.log(res))console.log('srcipt end')
输入后果如下:
script startasync1 startpromise1script end
这里须要留神的是在async1
中await
前面的Promise是没有返回值的,也就是它的状态始终是pending
状态,所以在await
之后的内容是不会执行的,包含async1
前面的 .then
。
冒泡排序--工夫复杂度 n^2
题目形容:实现一个冒泡排序
实现代码如下:
function bubbleSort(arr) { // 缓存数组长度 const len = arr.length; // 外层循环用于管制从头到尾的比拟+替换到底有多少轮 for (let i = 0; i < len; i++) { // 内层循环用于实现每一轮遍历过程中的反复比拟+替换 for (let j = 0; j < len - 1; j++) { // 若相邻元素后面的数比前面的大 if (arr[j] > arr[j + 1]) { // 替换两者 [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; } } } // 返回数组 return arr;}// console.log(bubbleSort([3, 6, 2, 4, 1]));
代码输入后果
(function(){ var x = y = 1;})();var z;console.log(y); // 1console.log(z); // undefinedconsole.log(x); // Uncaught ReferenceError: x is not defined
这段代码的关键在于:var x = y = 1; 实际上这里是从右往左执行的,首先执行y = 1, 因为y没有应用var申明,所以它是一个全局变量,而后第二步是将y赋值给x,讲一个全局变量赋值给了一个局部变量,最终,x是一个局部变量,y是一个全局变量,所以打印x是报错。
动静布局求解硬币找零问题
题目形容:给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算能够凑成总金额所需的起码的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1
示例1:输出: coins = [1, 2, 5], amount = 11输入: 3解释: 11 = 5 + 5 + 1示例2:输出: coins = [2], amount = 3输入: -1
实现代码如下:
const coinChange = function (coins, amount) { // 用于保留每个指标总额对应的最小硬币个数 const f = []; // 提前定义已知状况 f[0] = 0; // 遍历 [1, amount] 这个区间的硬币总额 for (let i = 1; i <= amount; i++) { // 求的是最小值,因而咱们预设为无穷大,确保它肯定会被更小的数更新 f[i] = Infinity; // 循环遍历每个可用硬币的面额 for (let j = 0; j < coins.length; j++) { // 若硬币面额小于指标总额,则问题成立 if (i - coins[j] >= 0) { // 状态转移方程 f[i] = Math.min(f[i], f[i - coins[j]] + 1); } } } // 若指标总额对应的解为无穷大,则意味着没有一个符合条件的硬币总数来更新它,本题无解,返回-1 if (f[amount] === Infinity) { return -1; } // 若有解,间接返回解的内容 return f[amount];};
数字证书是什么?
当初的办法也不肯定是平安的,因为没有方法确定失去的公钥就肯定是平安的公钥。可能存在一个中间人,截取了对方发给咱们的公钥,而后将他本人的公钥发送给咱们,当咱们应用他的公钥加密后发送的信息,就能够被他用本人的私钥解密。而后他伪装成咱们以同样的办法向对方发送信息,这样咱们的信息就被窃取了,然而本人还不晓得。为了解决这样的问题,能够应用数字证书。
首先应用一种 Hash 算法来对公钥和其余信息进行加密,生成一个信息摘要,而后让有公信力的认证核心(简称 CA )用它的私钥对音讯摘要加密,造成签名。最初将原始的信息和签名合在一起,称为数字证书。当接管方收到数字证书的时候,先依据原始信息应用同样的 Hash 算法生成一个摘要,而后应用公证处的公钥来对数字证书中的摘要进行解密,最初将解密的摘要和生成的摘要进行比照,就能发现失去的信息是否被更改了。
这个办法最要的是认证核心的可靠性,个别浏览器里会内置一些顶层的认证核心的证书,相当于咱们主动信赖了他们,只有这样能力保证数据的平安。
代码输入后果
var obj = { name : 'cuggz', fun : function(){ console.log(this.name); } } obj.fun() // cuggznew obj.fun() // undefined
应用new构造函数时,其this指向的是全局环境window。
函数防抖
触发高频事件 N 秒后只会执行一次,如果 N 秒内事件再次触发,则会从新计时。
简略版:函数外部反对应用 this 和 event 对象;
function debounce(func, wait) { var timeout; return function () { var context = this; var args = arguments; clearTimeout(timeout) timeout = setTimeout(function(){ func.apply(context, args) }, wait); }}
应用:
var node = document.getElementById('layout')function getUserAction(e) { console.log(this, e) // 别离打印:node 这个节点 和 MouseEvent node.innerHTML = count++;};node.onmousemove = debounce(getUserAction, 1000)
最终版:除了反对 this 和 event 外,还反对以下性能:
- 反对立刻执行;
- 函数可能有返回值;
- 反对勾销性能;
function debounce(func, wait, immediate) { var timeout, result; var debounced = function () { var context = this; var args = arguments; if (timeout) clearTimeout(timeout); if (immediate) { // 如果曾经执行过,不再执行 var callNow = !timeout; timeout = setTimeout(function(){ timeout = null; }, wait) if (callNow) result = func.apply(context, args) } else { timeout = setTimeout(function(){ func.apply(context, args) }, wait); } return result; }; debounced.cancel = function() { clearTimeout(timeout); timeout = null; }; return debounced;}
应用:
var setUseAction = debounce(getUserAction, 10000, true);// 应用防抖node.onmousemove = setUseAction// 勾销防抖setUseAction.cancel()
常见的HTTP申请头和响应头
HTTP Request Header 常见的申请头:
- Accept:浏览器可能解决的内容类型
- Accept-Charset:浏览器可能显示的字符集
- Accept-Encoding:浏览器可能解决的压缩编码
- Accept-Language:浏览器以后设置的语言
- Connection:浏览器与服务器之间连贯的类型
- Cookie:以后页面设置的任何Cookie
- Host:发出请求的页面所在的域
- Referer:发出请求的页面的URL
- User-Agent:浏览器的用户代理字符串
HTTP Responses Header 常见的响应头:
- Date:示意音讯发送的工夫,工夫的形容格局由rfc822定义
- server:服务器名称
- Connection:浏览器与服务器之间连贯的类型
- Cache-Control:管制HTTP缓存
- content-type:示意前面的文档属于什么MIME类型
常见的 Content-Type 属性值有以下四种:
(1)application/x-www-form-urlencoded:浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 形式提交数据。该种形式提交的数据放在 body 外面,数据依照 key1=val1&key2=val2 的形式进行编码,key 和 val 都进行了 URL转码。
(2)multipart/form-data:该种形式也是一个常见的 POST 提交形式,通常表单上传文件时应用该种形式。
(3)application/json:服务器音讯主体是序列化后的 JSON 字符串。
(4)text/xml:该种形式次要用来提交 XML 格局的数据。