supertapir / blog Goto Github PK
View Code? Open in Web Editor NEWMy Blog
My Blog
这个其实是一个很容易让人忽略的问题,我们知道一些在 JavaScript 中判断相等性的方法,但是对于一些特别的值,他们之间有一些微妙的差别。
另外,在 JavaScript 的内部实现里,到底是如何判断相等的,这是一件很有趣的事情。
这里 宽松相等的转型太多太杂,就不做对比了,主要比较 ===
和 Object.is
x | y | === | Object.is |
---|---|---|---|
null | null | ✅ | ✅ |
+0 | -0 | ✅ | ❌ |
NaN | NaN | ❌ | ✅ |
在这里对比了一些比较微妙的值,其实对于 ===
和 Object.is
就只有这两个不同
Object.is
的 NaN 和 NaN 相等Object.is
的 +0 和 -0 不等Strict equality
using ===Loose equality
using ==Same-value equality
using Object.isSame-value-zero equality
Same-value equality
区别在于,认为 +0 和 -0 相等Strict equality
using ===
Array.prototype.indexOf
Array.prototype.lastIndexOf
Loose equality
using ==Same-value-zero equality
%TypedArray%
和 ArrayBuffer
构造函数Map
和 Set
操作String.prototype.includes
Same-value equality
Object.defineProperty
在试图修改不可变属性时,如果这个属性确实被修改了则会抛出异常,反之什么都不会发生。这里的相等判断。我认为,一般来说 ===
在大多数场合中,都够用了。
只有在一些特殊情况下,我们需要判断 +0 和 -0 时,才需要使用手动使用 Object.is
另外,比较有趣的是 Map
和 Set
操作使用 Same-value-zero equality
同源策略用于限制一个 源的文档或加载的脚本 对于另一个源的资源进行交互。
实际上是为了 安全 作出的策略。
两个URL同源 = 两个URL的 protocol
port
host
都相同
对于
host
这块,特地做了调查,域名必须完全一致,即使是子域名也是不可以的,比如https://baidu.com
,https://xueshu.baidu.com
和https://music.baidu.com
都不是同源的
如何在某页面,通过 window.open
打开 about:blank
或 javascript:
, 那么新打开的页面可以继承之前的源。产生一个同源的关系。
同源策略虽然提高了安全性,但是也为开发带来了很多不便,所以前辈们总结了很多跨域的方法来绕过同源策略或者进行安全的跨域访问。
<img/>
由于 html 标签都是可以进行跨域访问的,所以可以通过它来绕过同源策略的限制。
如果想要进行一个 GET 请求,但是又不需要获取请求的响应,那么可以通过 <img/>
解决
const img = new Image();
img.onload = img.onerror = function(){
console.log("Done!");
};
img.src = "test.html?a=10";
这个非常出名,使用量也相当高,可以说是过去解决跨域访问问题的通用方案,并且 JQuery 里也有 JSONP 的请求模式。
JSONP 是利用 <script/>
来获取外部动态产生的 JSON 数据。但利用这种方式直接获取的数据并不是JSON,而是一段 JavaScript 代码,通过这段代码来间接的进行 JSON 数据的获取。
例:
这里我举例说明一下。
<script type="text/javascript" src="http://api.com/getSomething?params=test&callback=foo"></script>
这里是一段 JSONP 的代码,这个 <script/>
的 src
为我们要发起请求的一个 API 地址,注意其中的 query 参数。
这里的 callback
,对于JSONP,一般来说是一个特殊的参数。它代表了最后得到数据以后,应该调用什么回调函数来进行接收。
在本例中,当这段 <script/>
解析后,得到的是:
foo({"Name": "小明", "Id" : 1823, "Rank": 7})
这里 foo
的参数,应当是本次请求真正想得到的JSON数据。
这段代码被浏览器解析后,相当于直接执行了这个回调函数,并且也携带了从后端返回的动态参数,实现了一次跨域访问
解决跨域访问的终极策略。从服务提供者方面解决问题,利用 Access-Control
系列的 HTTP Headers
来允许和控制对于跨域请求的处理。
规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。
某些请求不会触发预检,这些请求被视为 “简单请求”(这不是规范的说法)。以下为条件:
GET
HEAD
POST
三选一Content-Type
: text/plain
multipart/form-data
application/x-www-form-urlencoded
三选一对于跨域的 XHR 或 Fetch 请求,默认不会发送凭证信息(Cookie)。必须 withCredentials
设为 true
才能发送。这时服务器响应中还必须携带 Access-Control-Allow-Credentials: true
。
另外,如果 Access-Control-Allow-Origin: *
,则携带 Cookie 的请求会直接失败。只有该首部为具体的值才能正常使用。
在解析代码的过程中发生的语法错误。
let 1test = 20
// Uncaught SyntaxError: Invalid or unexpected token
无效引用。
c = a + b
// Uncaught ReferenceError: a is not defined
数值变量或参数超出其有效范围。
let num = 22.3333333;
num.toFixed(-1)
// Uncaught RangeError: toFixed() digits argument must be between 0 and 100
变量或参数不属于有效类型。
null.test()
// Uncaught TypeError: Cannot read property 'test' of null
给 encodeURI()
或 decodeURl()
传递的参数无效。
decodeURIComponent('%@');
// Uncaught URIError: URI malformed
与 eval()
有关。
EvalError
不在当前ECMAScript规范中使用,因此不会被运行时抛出. 但是对象本身仍然与规范的早期版本向后兼容.
代表Javascript引擎内部错误。
例:
作为一个程序员,不可避免的,要跟 Linux 打交道,而跟 Linux 很多场景下最高效的方式就是命令行了,这里总结了我日常中比较常用的,以便今后查找
符号链接类似 Windows 中的快捷方式,通过利用符号链接,我在项目里可以将一些程序内部的配置提到项目目录外面进行修改和管理
$ touch a.txt
$ ll
total 0
-rw-r--r-- 1 kilo staff 0B Apr 5 23:22 a.txt
$ ln -s a.txt a.link.txt
$ ll
total 0
lrwxr-xr-x 1 kilo staff 5B Apr 5 23:23 a.link.txt -> a.txt
-rw-r--r-- 1 kilo staff 0B Apr 5 23:22 a.txt
ln 命令的 -s 指的就是创建 create symbolic links instead of hard links,不加就意味着创建 硬链接。
硬连接是 指向文件的指针,而符号链接是 指向文件路径的指针。
由于硬连接有诸如不能关联目录,不能关联所在文件系统之外的文件的局限性,所以一般使用符号链接而不是硬连接
不得不承认,这可能是 Linux 命令行里最酷的东西
>>
追加标准输出2>
重定向标准错误&>
重定向标准输出和标准错误2> /dev/null
丢弃(忽略)错误输出$ ll / ./err >> stdout.txt 2>> stderr.txt
$ ll
total 16
-rw-r--r-- 1 kilo staff 37B Apr 5 23:48 stderr.txt
-rw-r--r-- 1 kilo staff 947B Apr 5 23:48 stdout.txt
列出文件系统的整体磁盘使用量
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 11G 28G 28% /
devtmpfs 3.9G 0 3.9G 0% /dev
tmpfs 3.9G 0 3.9G 0% /dev/shm
tmpfs 3.9G 612K 3.9G 1% /run
tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup
tmpfs 783M 0 783M 0% /run/user/1001
检查某目录的磁盘空间使用量,并且按照从大到小排序
$ du -s ./* | sort -nr
389928 ./node_modules
560 ./yarn.lock
48 ./dist
24 ./src
24 ./build
16 ./env
8 ./tsconfig.json
8 ./package.json
8 ./index.html
8 ./babel.config.js
du 的 -s 代表 only report total for each argument,也就是计算目录的总大小
sort 的 -n 代表 compare according to string numerical value,也就是通过数字排序而不是字典序。-r 代表 reverse the result of comparisons,也就是逆序
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/palindrome-linked-list
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这道题可以通过简单的将链表转换为数组,在进行回文的检测,但是如此一来,空间复杂度应该是 O(n), 不符合题目的要求。
所以应该通过双指针将整个链表进行分拆,然后进行对比,大致思路如下:
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {boolean}
*/
var isPalindrome = function (head) {
if (!head || !head.next) {
return true;
}
let slow, fast;
slow = fast = head;
let p = null;
let isOdd = true;
while (fast.next !== null) {
fast = fast.next.next;
let temp = slow.next;
slow.next = p;
p = slow;
slow = temp;
if (!fast) {
isOdd = false;
break;
}
}
if (isOdd) {
slow = slow.next;
}
while (slow && p) {
if (slow.val !== p.val) {
return false;
}
slow = slow.next;
p = p.next;
}
return true;
};
#leetcode
JavaScript 中对于对象迭代有多种方式,但是各种方式之间都有一些微小的不同
for…of 不适用于 Object
以下为我的测试代码
function Foo() {
this.normal = true;
this[Symbol('symbol')] = true;
}
Foo.prototype.protoFlag = true;
let obj = new Foo();
Object.defineProperty(obj, 'unenumerable', {
enumerable: false,
});
let forInArr = [];
for (let key in obj) {
forInArr.push(key);
}
console.log('for...in', forInArr);
console.log('Object.getOwnPropertyNames', Object.getOwnPropertyNames(obj));
console.log('Object.getOwnPropertySymbols', Object.getOwnPropertySymbols(obj));
console.log('Reflect.ownKeys', Reflect.ownKeys(obj));
console.log('Object.keys', Object.keys(obj));
在测试代码中, 进行遍历的对象是这样的,unenumerable
为不可枚举的属性
{normal: true, unenumerable: undefined, Symbol(symbol): true}
经代码测试后,发现
for...in ["normal", "protoFlag"]
Object.getOwnPropertyNames ["normal", "unenumerable"]
Object.getOwnPropertySymbols [Symbol(symbol)]
Reflect.ownKeys ["normal", "unenumerable", Symbol(symbol)]
Object.keys ["normal"]
另外要说明一点,虽然 for…in
无法遍历出不可枚举的属性,但是
'unenumerable' in obj === true
通过分析测试结果可以得出初步结论
方法 string key Symbol key Prototype 不可枚举
for...in ✅ ❌ ✅ ❌
Object.getOwnPropertyNames ✅ ❌ ❌ ✅
Object.getOwnPropertySymbols ❌ ✅ ❌ ✅
Reflect.ownKeys ✅ ✅ ❌ ✅
Object.keys ✅ ❌ ❌ ❌
通过结论,结合日常使用,进行分析,由于 Symbol
一般来说是用来做内建属性的,不需要被遍历出来,而 prototype
中的属性也类似,一般是不希望被遍历出来的,对于不可枚举的属性也是同样的
所以我认为,最常用的应当是 Object.keys
这一类。它能遍历出来的东西最少,收到的干扰也最少。
#前端
JavaScript 中的函数以前都是原子性的,一旦开始执行,就会运行到结束,期间无法插入其他代码,但是 生成器 打破了这种常规。
function*
这种声明方式会定义一个生成器函数 (generator function)
function* gen() {
yield 'test';
let temp = yield 'foo';
temp = temp + (yield 'add2foo');
return temp;
}
而执行这个生成器函数,就能得到一个这个生成器的迭代器(iterator)对象,可以通过执行 iterator
的 next
方法来启动它
let g = gen()
g.next(); // 启动 iterator {value: "test", done: false}
g.next(); // {value: "foo", done: false}
g.next(10); // {value: "add2foo", done: false}
g.next(30); // {value: 40, done: true}
上面的代码里,生成器中有 3 个yield
,迭代器中运行了 4 次next
可以把 yield
看作代码中的断点,每当执行到断点位置,迭代器都会提示外部自己当前的状态,并可能接收外部的参数。这样看的话,每一个 yield
就是一次提示,再加上最后 rentu 出来的值,其实是很合理的。
一般认为如果一个对象,拥有 [Symbol.iterator]
这个属性,并且这个属性的值是一个可以返回迭代器的函数,那么这个对象就是可迭代的。
将生成器和Promise结合在一起,就能结合成两者的优点
function* main() {
try {
var text = yield fetch('http://some.url.1');
console.log(text);
} catch (err) {
console.error(err);
}
}
var it = main();
var p = it.next().value;
// 等待promise p决议
p.then(
text => {
it.next(text);
},
err => {
it.throw(err);
}
);
async function
就简化了上面的 “生成器+Promise“ 的模式,通过语法的方式实现了
async function main() {
try {
var text = await fetch('http://some.url.1');
console.log(text);
return text;
} catch (err) {
console.error(err);
}
}
async function
可以包含 await
指令,该指令会暂停异步函数的执行,并等待Promise执行,然后继续执行异步函数,并返回结果。
async function
会返回一个 Promise, 而这个 Promise 的 value 就是这个 async function
的返回值
main().then(v => console.log(v)); // v 为 text
这种模式,使得我们能够以一种方便的语法,通过书写同步形式的代码表达 JavaScript 中的异步行为
Promise/A+
规范概览pending ———> Fulfilled
|
|———————> Rejected
实现了 Promise 的构造函数
class MyPromise {
constructor(executor) {
this.value = null;
this.reason = null;
const resolve = value => {
this.value = value;
};
const reject = reason => {
this.reason = reason;
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
}
将 Promise 的三种状态引入实现,并且在正确的时机改变状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.value = null;
this.reason = null;
this.state = PENDING;
const resolve = value => {
if (this.state === PENDING) {
this.value = value;
this.state = FULFILLED;
}
};
const reject = reason => {
if (this.state === PENDING) {
this.reason = reason;
this.state = REJECTED;
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
}
then 函数,需要在 Promise fulfilled 的时候调用成功回调, rejected 时调用失败回调
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.value = null;
this.reason = null;
this.state = PENDING;
const resolve = (value) => {
if (this.state === PENDING) {
this.value = value;
this.state = FULFILLED;
}
};
const reject = (reason) => {
if (this.state === PENDING) {
this.reason = reason;
this.state = REJECTED;
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
// 判别传入的值,是否为函数,如果不是,需要给予缺省值
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (_) => _;
onRejected =
typeof onRejected === 'function'
? onRejected
: (_) => {
throw _;
};
if (this.state === FULFILLED) {
onFulfilled(this.value);
}
if (this.state === REJECTED) {
onRejected(this.reason);
}
}
}
通过两个数组保存 then 方法传入的函数,并且在 resolve 或 reject 的时候调用数组内的方法
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.value = null;
this.reason = null;
this.state = PENDING;
this.onFulfilledCbs = [];
this.onRejectedCbs = [];
const resolve = (value) => {
if (this.state === PENDING) {
this.value = value;
this.state = FULFILLED;
this.onFulfilledCbs.forEach((fn) => fn());
}
};
const reject = (reason) => {
if (this.state === PENDING) {
this.reason = reason;
this.state = REJECTED;
this.onRejectedCbs.forEach((fn) => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
// 判别传入的值,是否为函数,如果不是,需要给予缺省值
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (_) => _;
onRejected =
typeof onRejected === 'function'
? onRejected
: (_) => {
throw _;
};
this.onFulfilledCbs.push(() => onFulfilled(this.value));
this.onRejectedCbs.push(() => onRejected(this.value));
}
}
Then 方法需要返回一个 Promise
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.value = null;
this.reason = null;
this.state = PENDING;
this.onFulfilledCbs = [];
this.onRejectedCbs = [];
const resolve = (value) => {
if (this.state === PENDING) {
this.value = value;
this.state = FULFILLED;
this.onFulfilledCbs.forEach((fn) => fn());
}
};
const reject = (reason) => {
if (this.state === PENDING) {
this.reason = reason;
this.state = REJECTED;
this.onRejectedCbs.forEach((fn) => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
// 判别传入的值,是否为函数,如果不是,需要给予缺省值
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (_) => _;
onRejected =
typeof onRejected === 'function'
? onRejected
: (_) => {
throw _;
};
return new MyPromise((resolve, reject) => {
const fulfilledPromise = () => {
setTimeout(() => {
try {
resolve(onFulfilled(this.value));
} catch (error) {
reject(error);
}
});
};
const rejectPromise = () => {
setTimeout(() => {
try {
reject(onRejected(this.reason));
} catch (error) {
reject(error);
}
});
};
if (this.state === PENDING) {
this.onFulfilledCbs.push(fulfilledPromise);
this.onRejectedCbs.push(rejectPromise);
} else if (this.state === FULFILLED) {
fulfilledPromise();
} else if (this.state === REJECTED) {
rejectPromise();
}
});
}
}
到这里,其实 then 方法还有一个很重要的点没有实现,就是在then方法里,是可以通过回调函数 return 一个 Promise 值的,而这时,这个 Promise 会被展开并作为当前链式调用的决议值
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.value = null;
this.reason = null;
this.state = PENDING;
this.onFulfilledCbs = [];
this.onRejectedCbs = [];
const resolve = (value) => {
if (this.state === PENDING) {
this.value = value;
this.state = FULFILLED;
this.onFulfilledCbs.forEach((fn) => fn());
}
};
const reject = (reason) => {
if (this.state === PENDING) {
this.reason = reason;
this.state = REJECTED;
this.onRejectedCbs.forEach((fn) => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
// 判别传入的值,是否为函数,如果不是,需要给予缺省值
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (_) => _;
onRejected =
typeof onRejected === 'function'
? onRejected
: (_) => {
throw _;
};
const nextPromise = new MyPromise((resolve, reject) => {
const fulfilledPromise = () => {
setTimeout(() => {
try {
resolvePromise(nextPromise, onFulfilled(this.value), resolve, reject);
} catch (error) {
reject(error);
}
});
};
const rejectPromise = () => {
setTimeout(() => {
try {
resolvePromise(nextPromise, onRejected(this.reason), resolve, reject);
} catch (error) {
reject(error);
}
});
};
if (this.state === PENDING) {
this.onFulfilledCbs.push(fulfilledPromise);
this.onRejectedCbs.push(rejectPromise);
} else if (this.state === FULFILLED) {
fulfilledPromise();
} else if (this.state === REJECTED) {
rejectPromise();
}
});
return nextPromise;
}
}
function resolvePromise(promise, value, resolve, reject) {
// 防止循环
if (promise === value) {
reject(new TypeError('Chaining cycle'));
}
// 判断 value 是否为一个 thenable 值
if (
value !== null &&
(typeof value === 'object' || typeof value === 'function') &&
typeof value.then === 'function'
) {
try {
value.then(
(v) => {
resolvePromise(promise, v, resolve, reject);
},
(err) => {
reject(err);
}
);
} catch (error) {
reject(error);
}
} else {
resolve(value);
}
}
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
constructor(executor) {
this.value = null;
this.reason = null;
this.state = PENDING;
this.onFulfilledCbs = [];
this.onRejectedCbs = [];
const resolve = (value) => {
if (this.state === PENDING) {
this.value = value;
this.state = FULFILLED;
this.onFulfilledCbs.forEach((fn) => fn());
}
};
const reject = (reason) => {
if (this.state === PENDING) {
this.reason = reason;
this.state = REJECTED;
this.onRejectedCbs.forEach((fn) => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
// 判别传入的值,是否为函数,如果不是,需要给予缺省值
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (_) => _;
onRejected =
typeof onRejected === 'function'
? onRejected
: (_) => {
throw _;
};
const nextPromise = new MyPromise((resolve, reject) => {
const fulfilledPromise = () => {
setTimeout(() => {
try {
resolvePromise(nextPromise, onFulfilled(this.value), resolve, reject);
} catch (error) {
reject(error);
}
});
};
const rejectPromise = () => {
setTimeout(() => {
try {
resolvePromise(nextPromise, onRejected(this.reason), resolve, reject);
} catch (error) {
reject(error);
}
});
};
if (this.state === PENDING) {
this.onFulfilledCbs.push(fulfilledPromise);
this.onRejectedCbs.push(rejectPromise);
} else if (this.state === FULFILLED) {
fulfilledPromise();
} else if (this.state === REJECTED) {
rejectPromise();
}
});
return nextPromise;
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
function resolvePromise(promise, value, resolve, reject) {
// 防止循环
if (promise === value) {
reject(new TypeError('Chaining cycle'));
}
// 判断 value 是否为一个 thenable 值
if (
value !== null &&
(typeof value === 'object' || typeof value === 'function') &&
typeof value.then === 'function'
) {
try {
value.then(
(v) => {
resolvePromise(promise, v, resolve, reject);
},
(err) => {
reject(err);
}
);
} catch (error) {
reject(error);
}
} else {
resolve(value);
}
}
根据上述步骤,大致完成了 Promise 的一个实现,经过我的测试大部分情况应该是都能通过的。
我和真正的 Promise/A+ 规范进行了比对,又参考了很多大神的实现方式,了解到想要真正做到完全的符合规范是需要做非常多判断的,需要做大量的工作,感觉理解规范的意图和其作用非常困难,我还是准备偷个懒,我已经大致了解了如何去实现一个 Promise, 最后只剩一些小细节了,这部分细节准备先放一放,等下次有空好好参照规范写一下
#前端
对于基本的 Session 和 Cookie 的关系相信大部分人都明白。Token 也是我们做用户认证非常常用的工具,但是作为一个前度工程师,还是经常会对其中的概念感到混乱,所以在写本文的过程中,我希望能够将这三个概念区分清楚。
首先,说说我最熟悉的 Cookie。
Cookie 就是指存储在客户端的小型存储。在进行 HTTP 请求的时候,一般会自动携带上 Cookie 信息。
Cookie 可以指定一个特定的过期时间(Expires)或有效期(Max-Age)。如果不设置的话,就是会话期Cookie,在浏览器关闭后会被自动删除。
当Cookie的过期时间被设定时,设定的日期和时间只与客户端相关,而不是服务端。
另外,还可以设置 Secure
和 HttpOnly
标记
Secure
只应通过被HTTPS协议加密过的请求发送给服务端HttpOnly
JavaScript 无法访问的 Cookie我觉得相比于 Cookie 这种真实存在的东西来说,Session 更像是一种概念性的东西。是为了给 无状态的HTTP协议 施加状态而产生的。
举个例子,一个用户进行登录操作,服务端保存用户当前的会话信息到某个介质(内存或者本地存储)。这个过程可以认为生成了一个 Session信息。接着从会话信息中拿出一个当前会话的 ID (session ID)并通过 HTTP响应 设置Cookie保存在客户端。之后该客户端的请求就会携带 session ID 的信息,服务端再和本地保存的 Session 进行查找对比,就能认出该用户了。这维持了一个HTTP的会话。
Token 也是一种抽象的概念。它是辨认用户信息的标识,用来进行授权等操作。
Token 可以在服务端进行持久化。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
另外,也有将Token 等会话信息直接保存在客户端的方案,比如 JWT。
JWT 是一种 Token 的实现标准。使用 JSON 作为 Token。
JWT 将会话信息全部保存在客户端,这样服务端就是无状态的了,利于后续扩展。
最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.