Coder Social home page Coder Social logo

blog's People

Contributors

supertapir avatar

Watchers

 avatar  avatar

blog's Issues

JavaScript 中的相等性判断

这个其实是一个很容易让人忽略的问题,我们知道一些在 JavaScript 中判断相等性的方法,但是对于一些特别的值,他们之间有一些微妙的差别。

另外,在 JavaScript 的内部实现里,到底是如何判断相等的,这是一件很有趣的事情。

JavaScript 中的值的比较

  • === 严格相等
  • == 宽松相等
  • Object.is

这里 宽松相等的转型太多太杂,就不做对比了,主要比较 ===Object.is

x y === Object.is
null null
+0 -0
NaN NaN

在这里对比了一些比较微妙的值,其实对于 ===Object.is 就只有这两个不同

  • Object.is 的 NaN 和 NaN 相等
  • Object.is 的 +0 和 -0 不等

JavaScript 中的相等算法

  • Strict equality using ===
  • Loose equality using ==
  • Same-value equality using Object.is
  • Same-value-zero equality
    • Same-value equality 区别在于,认为 +0 和 -0 相等

这些算法用在哪里?

  • Strict equality using ===
    • Array.prototype.indexOf
    • Array.prototype.lastIndexOf
    • switch 语句 case 匹配
  • Loose equality using ==
  • Same-value-zero equality
    • 用于 %TypedArray%ArrayBuffer 构造函数
    • MapSet 操作
    • String.prototype.includes
  • Same-value equality
    • Object.defineProperty 在试图修改不可变属性时,如果这个属性确实被修改了则会抛出异常,反之什么都不会发生。这里的相等判断。
    • 其他所有地方

实际应用

我认为,一般来说 === 在大多数场合中,都够用了。

只有在一些特殊情况下,我们需要判断 +0 和 -0 时,才需要使用手动使用 Object.is

另外,比较有趣的是 MapSet 操作使用 Same-value-zero equality

参考资料

JavaScript 中的相等性判断

跨域问题

浏览器的同源策略

同源策略用于限制一个 源的文档或加载的脚本 对于另一个源的资源进行交互。

实际上是为了 安全 作出的策略。

定义同源

两个URL同源 = 两个URL的 protocol port host 都相同

对于 host 这块,特地做了调查,域名必须完全一致,即使是子域名也是不可以的,比如 https://baidu.comhttps://xueshu.baidu.comhttps://music.baidu.com 都不是同源的

源的继承

如何在某页面,通过 window.open 打开 about:blankjavascript:, 那么新打开的页面可以继承之前的源。产生一个同源的关系。

跨源网络访问

同源策略虽然提高了安全性,但是也为开发带来了很多不便,所以前辈们总结了很多跨域的方法来绕过同源策略或者进行安全的跨域访问。

<img/>

由于 html 标签都是可以进行跨域访问的,所以可以通过它来绕过同源策略的限制。

如果想要进行一个 GET 请求,但是又不需要获取请求的响应,那么可以通过 <img/> 解决

const img = new Image();
img.onload = img.onerror = function(){
    console.log("Done!");
};
img.src = "test.html?a=10";

JSONP(JSON with Padding)

这个非常出名,使用量也相当高,可以说是过去解决跨域访问问题的通用方案,并且 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 参数。

  • params: test
  • callback: foo

这里的 callback ,对于JSONP,一般来说是一个特殊的参数。它代表了最后得到数据以后,应该调用什么回调函数来进行接收。

在本例中,当这段 <script/> 解析后,得到的是:

foo({"Name": "小明", "Id" : 1823, "Rank": 7})

这里 foo 的参数,应当是本次请求真正想得到的JSON数据。

这段代码被浏览器解析后,相当于直接执行了这个回调函数,并且也携带了从后端返回的动态参数,实现了一次跨域访问

CORS (Cross-Origin Resource Sharing)

解决跨域访问的终极策略。从服务提供者方面解决问题,利用 Access-Control 系列的 HTTP Headers 来允许和控制对于跨域请求的处理。

规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。

简单请求

某些请求不会触发预检,这些请求被视为 “简单请求”(这不是规范的说法)。以下为条件:

  • 方法: GET HEAD POST 三选一
  • Content-Typetext/plain multipart/form-data application/x-www-form-urlencoded 三选一
  • 除了被用户代理自动设置的首部字段(例如 Connection ,User-Agent)和在 Fetch 规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合

附带身份凭证的请求

对于跨域的 XHR 或 Fetch 请求,默认不会发送凭证信息(Cookie)。必须 withCredentials 设为 true 才能发送。这时服务器响应中还必须携带 Access-Control-Allow-Credentials: true

另外,如果 Access-Control-Allow-Origin: *,则携带 Cookie 的请求会直接失败。只有该首部为具体的值才能正常使用。

参考

JavaScript 的常见错误类型

SyntaxError

在解析代码的过程中发生的语法错误。

let 1test = 20
// Uncaught SyntaxError: Invalid or unexpected token

ReferenceError

无效引用。

c = a + b
// Uncaught ReferenceError: a is not defined

RangeError

数值变量或参数超出其有效范围。

let num = 22.3333333;
num.toFixed(-1)
// Uncaught RangeError: toFixed() digits argument must be between 0 and 100

TypeError

变量或参数不属于有效类型。

null.test()
// Uncaught TypeError: Cannot read property 'test' of null

URIError

encodeURI()decodeURl() 传递的参数无效。

decodeURIComponent('%@');
// Uncaught URIError: URI malformed

EvalError

eval() 有关。

EvalError 不在当前ECMAScript规范中使用,因此不会被运行时抛出. 但是对象本身仍然与规范的早期版本向后兼容.

InternalError

代表Javascript引擎内部错误。

例:

  • "too many switch cases"(过多case子句);
  • "too many parentheses in regular expression"(正则表达式中括号过多);
  • "array initializer too large"(数组初始化器过大);
  • "too much recursion"(递归过深)。

参考资料

我的 Linux 常用命令

作为一个程序员,不可避免的,要跟 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

列出文件系统的整体磁盘使用量

$ 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

检查某目录的磁盘空间使用量,并且按照从大到小排序

$ 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), 不符合题目的要求。

所以应该通过双指针将整个链表进行分拆,然后进行对比,大致思路如下:

  1. 设置快慢指针遍历链表
  2. 遍历的同时,慢指针顺便进行链表的反转
  3. 快指针遍历完成后,慢指针应当到达链表的中点,停止遍历
  4. 这时候可以通过快指针是否正好完成遍历来得知链表的奇偶,如果为奇数项,则需要进行针对中点的矫正
  5. 这时就可以以进行两条链表的遍历,如果同时遍历后值都相同,则返回 true, 反之 false

解答

/**
 * 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 中 Object 的迭代

JavaScript 中对于对象迭代有多种方式,但是各种方式之间都有一些微小的不同

  • for...in
  • Object.getOwnPropertyNames
  • Object.getOwnPropertySymbols
  • Reflect.ownKeys
  • Object.keys, Object.values, Object.entries

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 中的 生成器 和 迭代器

JavaScript 中的函数以前都是原子性的,一旦开始执行,就会运行到结束,期间无法插入其他代码,但是 生成器 打破了这种常规。

上手

function* 这种声明方式会定义一个生成器函数 (generator function)

function* gen() {
  yield 'test';
  let temp = yield 'foo';
  temp = temp + (yield 'add2foo');
  return temp;
}

而执行这个生成器函数,就能得到一个这个生成器的迭代器(iterator)对象,可以通过执行 iteratornext 方法来启动它

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 出来的值,其实是很合理的。

iterable

一般认为如果一个对象,拥有 [Symbol.iterator] 这个属性,并且这个属性的值是一个可以返回迭代器的函数,那么这个对象就是可迭代的。

JavaScript 内置的可迭代对象

  • String
  • Array
  • TypedArray
  • Map
  • Set

用于可迭代对象的语法

  • for...of 循环
  • 展开语法
  • yield*
  • 结构赋值

结合Promise

将生成器和Promise结合在一起,就能结合成两者的优点

  • 生成器用来将异步代码以同步的方式呈现
  • 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/Await

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 手动实现

Promise/A+ 规范概览

1. Promise 是一个状态机,状态变化不可逆

pending ———>  Fulfilled
   |
   |———————>  Rejected

2. Promise 有 then 方法,可被调用多次,返回 Promise 对象

3. 支持链式调用,内部记录 value,用来记录上次计算的结果值,如果报错,说明保存的是异常

实现步骤

实现 Promise 的基本框架

实现了 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 方法

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 基本的链式调用

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 会被展开并作为当前链式调用的决议值

实现 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 _;
          };

    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);
  }
}

实现 catch 的异常处理

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 的关系与区别

对于基本的 Session 和 Cookie 的关系相信大部分人都明白。Token 也是我们做用户认证非常常用的工具,但是作为一个前度工程师,还是经常会对其中的概念感到混乱,所以在写本文的过程中,我希望能够将这三个概念区分清楚。

Cookie

首先,说说我最熟悉的 Cookie。

Cookie 就是指存储在客户端的小型存储。在进行 HTTP 请求的时候,一般会自动携带上 Cookie 信息。

Cookie 可以指定一个特定的过期时间(Expires)或有效期(Max-Age)。如果不设置的话,就是会话期Cookie,在浏览器关闭后会被自动删除。

当Cookie的过期时间被设定时,设定的日期和时间只与客户端相关,而不是服务端。

另外,还可以设置 SecureHttpOnly 标记

  • Secure 只应通过被HTTPS协议加密过的请求发送给服务端
  • HttpOnly JavaScript 无法访问的 Cookie

Session

我觉得相比于 Cookie 这种真实存在的东西来说,Session 更像是一种概念性的东西。是为了给 无状态的HTTP协议 施加状态而产生的。

举个例子,一个用户进行登录操作,服务端保存用户当前的会话信息到某个介质(内存或者本地存储)。这个过程可以认为生成了一个 Session信息。接着从会话信息中拿出一个当前会话的 ID (session ID)并通过 HTTP响应 设置Cookie保存在客户端。之后该客户端的请求就会携带 session ID 的信息,服务端再和本地保存的 Session 进行查找对比,就能认出该用户了。这维持了一个HTTP的会话。

Token

Token 也是一种抽象的概念。它是辨认用户信息的标识,用来进行授权等操作。

Token 可以在服务端进行持久化。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。

另外,也有将Token 等会话信息直接保存在客户端的方案,比如 JWT。

JWT(JSON Web Token)

JWT 是一种 Token 的实现标准。使用 JSON 作为 Token。

JWT 将会话信息全部保存在客户端,这样服务端就是无状态的了,利于后续扩展。

最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

参考资料

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.