
在资源不足的设备上,将服务合并到浏览器进程中


协议栈通过 TCP 协议收发数据的操作。

浏览器调用 Socket.connect

浏览器调用 Socket.write

浏览器调用 Socket.close



同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。
如果两个 URL 的 protocol 、 port (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。
例如:
| URL | 结果 | 原因 | 
|---|---|---|
| http://store.company.com/dir2/other.html | 同源 | 只有路径不同 | 
| http://store.company.com/dir/inner/another.html | 同源 | 只有路径不同 | 
| https://store.company.com/secure.html | 失败 | 协议不同 | 
| http://store.company.com:81/dir/etc.html | 失败 | 端口不同 ( http:// 默认端口是80) | 
| http://news.company.com/dir/other.html | 失败 | 主机不同 | 
JSONP
JSONP的原理是:静态资源请求不受同源策略影响。实现如下:
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = 'https://www.domain.com/a?data=1&callback=cb'
const cb = res => {
    console.log(JSON.stringify(res))
}
CORS
CORS:跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。
在各种服务端代码实现如下:
// 根据不同语言规则,具体语法有所不同,此处以NodeJs的express为例
//设置跨域访问  
app.all('*', function(req, res, next) {  
    res.header("Access-Control-Allow-Origin", "*");  
    res.header("Access-Control-Allow-Headers", "X-Requested-With");  
    res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
    next();  
});   
Nginx实现如下:
server {
    ...
    
    add_header Access-Control-Allow-Credentials true;
    add_header Access-Control-Allow-Origin $http_origin;
    
        
    location /file {
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin $http_origin;
            add_header Access-Control-Allow-Methods $http_access_control_request_method;
            add_header Access-Control-Allow-Credentials true;
            add_header Access-Control-Allow-Headers $http_access_control_request_headers;
            add_header Access-Control-Max-Age 1728000;
            return 204;
        }         
    }
	
    ...
}

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由 IETF 的 RFC 793 定义。
Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。RFC 768 描述了 UDP。
注:RTT = Round-trip time
构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成
V8 编译 JS 代码的过程
高级语言编译器步骤:
对象在转换类型的时候,会执行原生方法 ToPrimitive 。
其算法如下:
toSting方法,如果此时是 原始类型 则直接返回,否则再调用valueOf方法并返回结果;valueOf方法,如果此时是 原始类型 则直接返回,否则再调用toString方法并返回结果;当然,我们可以通过重写Symbol.toPrimitive来制定转换规则,此方法在转原始类型时调用优先级最高。
const data = {
  valueOf() {
    return 1;
  },
  toString() {
    return "1";
  },
  [Symbol.toPrimitive]() {
    return 2;
  }
};
data + 1; // 3
对象转换为布尔值的规则如下表:
返回 false
。对象转换为数字的规则如下表:
返回 NaN
。对象转换为字符串的规则如下表:
返回 "undefined"
。this 是和执行上下文绑定的。
执行上下文:
根据优先级最高的来决定 this 最终指向哪里。
首先,new 的方式优先级最高,接下来是 bind 这些函数,然后是 obj.foo() 这种调用方式,最后是 foo 这种调用方式,同时,箭头函数的 this 一旦被绑定,就不会再被任何方式所改变。
三点注意:
没有被引用的闭包会被自动回收,但还存在全局变量中,则依然会内存泄漏。
在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。
var getNum;
function getCounter() {
  var n = 1;
  var inner = function() {
    n++;
  };
  return inner;
}
getNum = getCounter();
getNum(); // 2
getNum(); // 3
getNum(); // 5
getNum(); // 5
对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期。
函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后,函数内部定义的变量会被销毁。
使用一对大括号包裹的一段代码,比如函数、判断语句、循环语句,甚至单独的一个{}都可以被看作是一个块级作用域。
词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。
词法作用域是代码阶段就决定好的,和函数是怎么调用的没有关系。
其实每个 JS 对象都有 __proto__ 属性,这个属性指向了原型。
原型也是一个对象,并且这个对象中包含了很多函数,对于 obj 来说,可以通过 __proto__ 找到一个原型对象,在该对象中定义了很多函数让我们来使用。
原型链:
Object 是所有对象的爸爸,所有对象都可以通过 __proto__ 找到它Function 是所有函数的爸爸,所有函数都可以通过 __proto__ 找到它prototype 是一个对象__proto__ 属性指向原型, __proto__ 将对象和原型连接起来组成了原型链原始类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址。
副垃圾回收器:
主要负责新生代的垃圾回收。
这个区域不大,但是垃圾回收比较频繁。
新生代的垃圾回收算法是 Scavenge 算法。
主要把新生代空间对半划分为两个区域:对象区域,空闲区域。
当对象区域快被写满时,则会进行一次垃圾清理。
流程如下:
主垃圾回收器:
主垃圾回收器主要负责老生区中的垃圾回收。
除了新生区中晋升的对象,一些大的对象会直接被分配到老生区。
因此老生区中的对象有两个特点,一个是对象占用空间大,另一个是对象存活时间长。
流程如下:
一旦执行垃圾回收算法,会导致 全停顿(Stop-The-World) 。
但是 V8 有 增量标记算法 。
V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成。
const escapeHTML = value => {
 if (!value || !value.length) {
 return value;
  }
 return value
    .replace(/&/g, "&")
    .replace(/</g, "<")
    .replace(/>/g, ">")
    .replace(/"/g, """)
    .replace(/'/g, "'");
};
const replaceSql = value => {
 if (!value || !value.length) {
 return value;
  }
 return value.replace(/select|update|delete|exec|count|'|"|=|;|>|<|%/gi, "");
};
内容安全策略 (CSP) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件,这些攻击都是主要的手段。
措施如下:
Content-Security-Policy<meta http-equiv="Content-Security-Policy"><link rel="dns-prefetch" href="" /><meta http-equiv="x-dns-prefetch-control" content="off|on">


协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。
Last-Modified 与 If-Modified-Since 配对。Last-Modified 把 Web 应用最后修改时间告诉客户端,客户端下次请求之时会把 If-Modified-Since 的值发生给服务器,服务器由此判断是否需要重新发送资源,如果不需要则返回 304,如果有则返回 200。这对组合的缺点是只能精确到秒,而且是根据本地打开时间来记录的,所以会不准确。
Etag 与 If-None-Match 配对。它们没有使用时间作为判断标准,而是使用了一组特征串。Etag把此特征串发生给客户端,客户端在下次请求之时会把此特征串作为If-None-Match的值发送给服务端,服务器由此判断是否需要重新发送资源,如果不需要则返回 304,如果有则返回 200。

基础概念:
Node 中最核心的是 v8 引擎,在 Node 启动后,会创建 v8 的实例,这个实例是多线程的,各个线程如下:
阻塞 是指在 Node.js 程序中,其它 JavaScript 语句的执行,必须等待一个非 JavaScript 操作完成。这是因为当 阻塞 发生时,事件循环无法继续运行 JavaScript。
在 Node.js 中,JavaScript 由于执行 CPU 密集型操作,而不是等待一个非 JavaScript 操作(例如 I/O)而表现不佳,通常不被称为 阻塞。在 Node.js 标准库中使用 libuv 的同步方法是最常用的 阻塞 操作。原生模块中也有 阻塞 方法。
   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘
注意:每个框被称为事件循环机制的一个阶段。
在 Windows 和 Unix/Linux 实现之间存在细微的差异,但这对演示来说并不重要。
阶段概述:
setTimeout() 和 setInterval() 的调度回调函数。setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞。setImmediate() 回调函数在这里执行。socket.on('close', ...)。在每次运行的事件循环之间,Node.js 检查它是否在等待任何异步 I/O 或计时器,如果没有的话,则完全关闭。
process.nextTick() :它是异步 API 的一部分。从技术上讲不是事件循环的一部分。不管事件循环的当前阶段如何,都将在当前操作完成后处理 nextTickQueue。这里的一个操作被视作为一个从底层 C/C++ 处理器开始过渡,并且处理需要执行的 JavaScript 代码。
Libuv 是一个跨平台的异步 IO 库,它结合了 UNIX 下的 libev 和 Windows 下的 IOCP 的特性,最早由 Node.js 的作者开发,专门为 Node.js 提供多平台下的异步 IO 支持。Libuv 本身是由 C++ 语言实现的,Node.js 中的非阻塞 IO 以及事件循环的底层机制都是由 libuv 实现的。
在 Windows 环境下,libuv 直接使用 Windows 的 IOCP 来实现异步 IO。在 非 Windows 环境下,libuv 使用多线程(线程池 Thread Pool)来模拟异步 IO,这里仅简要提一下 libuv 中有线程池的概念,之后的文章会介绍 libuv 如何实现进程间通信。
var New = function(Fn) {
  var obj = {}; // 创建空对象
  var arg = Array.prototype.slice.call(arguments, 1);
  obj.__proto__ = Fn.prototype; // 将obj的原型链__proto__指向构造函数的原型prototype
  obj.__proto__.constructor = Fn; // 在原型链 __proto__上设置构造函数的构造器constructor,为了实例化Fn
  Fn.apply(obj, arg); // 执行Fn,并将构造函数Fn执行obj
  return obj; // 返回结果
};
const getType = data => {
  // 获取数据类型
  const baseType = Object.prototype.toString
    .call(data)
    .replace(/^\[object\s(.+)\]$/g, "$1")
    .toLowerCase();
  const type = data instanceof Element ? "element" : baseType;
  return type;
};
const isPrimitive = data => {
  // 判断是否是基本数据类型
  const primitiveType = "undefined,null,boolean,string,symbol,number,bigint,map,set,weakmap,weakset".split(
    ","
  ); // 其实还有很多类型
  return primitiveType.includes(getType(data));
};
const isObject = data => getType(data) === "object";
const isArray = data => getType(data) === "array";
const deepClone = data => {
  let cache = {}; // 缓存值,防止循环引用
  const baseClone = _data => {
    let res;
    if (isPrimitive(_data)) {
      return data;
    } else if (isObject(_data)) {
      res = { ..._data };
    } else if (isArray(_data)) {
      res = [..._data];
    }
    // 判断是否有复杂类型的数据,有就递归
    Reflect.ownKeys(res).forEach(key => {
      if (res[key] && getType(res[key]) === "object") {
        // 用cache来记录已经被复制过的引用地址。用来解决循环引用的问题
        if (cache[res[key]]) {
          res[key] = cache[res[key]];
        } else {
          cache[res[key]] = res[key];
          res[key] = baseClone(res[key]);
        }
      }
    });
    return res;
  };
  return baseClone(data);
};
Function.prototype.bind2 = function(context) {
  if (typeof this !== "function") {
    throw new Error("...");
  }
  var that = this;
  var args1 = Array.prototype.slice.call(arguments, 1);
  var bindFn = function() {
    var args2 = Array.prototype.slice.call(arguments);
    var that2 = this instanceof bindFn ? this : context; // 如果当前函数的this指向的是构造函数中的this 则判定为new 操作。如果this是构造函数bindFn new出来的实例,那么此处的this一定是该实例本身。
    return that.apply(that2, args1.concat(args2));
  };
  var Fn = function() {}; // 连接原型链用Fn
  // 原型赋值
  Fn.prototype = this.prototype; // bindFn的prototype指向和this的prototype一样,指向同一个原型对象
  bindFn.prototype = new Fn();
  return bindFn;
};
const curry = fn => {
  if (typeof fn !== "function") {
    throw Error("No function provided");
  }
  return function curriedFn(...args) {
    if (args.length < fn.length) {
      return function() {
        return curriedFn.apply(null, args.concat([].slice.call(arguments)));
      };
    }
    return fn.apply(null, args);
  };
};
// 来源于 https://github.com/bailnl/promise/blob/master/src/promise.js
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;
const isFunction = fn => typeof fn === "function";
const isObject = obj => obj !== null && typeof obj === "object";
const noop = () => {};
const nextTick = fn => setTimeout(fn, 0);
const resolve = (promise, x) => {
  if (promise === x) {
    reject(promise, new TypeError("You cannot resolve a promise with itself"));
  } else if (x && x.constructor === Promise) {
    if (x._stauts === PENDING) {
      const handler = statusHandler => value => statusHandler(promise, value);
      x.then(handler(resolve), handler(reject));
    } else if (x._stauts === FULFILLED) {
      fulfill(promise, x._value);
    } else if (x._stauts === REJECTED) {
      reject(promise, x._value);
    }
  } else if (isFunction(x) || isObject(x)) {
    let isCalled = false;
    try {
      const then = x.then;
      if (isFunction(then)) {
        const handler = statusHandler => value => {
          if (!isCalled) {
            statusHandler(promise, value);
          }
          isCalled = true;
        };
        then.call(x, handler(resolve), handler(reject));
      } else {
        fulfill(promise, x);
      }
    } catch (e) {
      if (!isCalled) {
        reject(promise, e);
      }
    }
  } else {
    fulfill(promise, x);
  }
};
const reject = (promise, reason) => {
  if (promise._stauts !== PENDING) {
    return;
  }
  promise._stauts = REJECTED;
  promise._value = reason;
  invokeCallback(promise);
};
const fulfill = (promise, value) => {
  if (promise._stauts !== PENDING) {
    return;
  }
  promise._stauts = FULFILLED;
  promise._value = value;
  invokeCallback(promise);
};
const invokeCallback = promise => {
  if (promise._stauts === PENDING) {
    return;
  }
  nextTick(() => {
    while (promise._callbacks.length) {
      const {
        onFulfilled = value => value,
        onRejected = reason => {
          throw reason;
        },
        thenPromise
      } = promise._callbacks.shift();
      let value;
      try {
        value = (promise._stauts === FULFILLED ? onFulfilled : onRejected)(
          promise._value
        );
      } catch (e) {
        reject(thenPromise, e);
        continue;
      }
      resolve(thenPromise, value);
    }
  });
};
class Promise {
  static resolve(value) {
    return new Promise((resolve, reject) => resolve(value));
  }
  static reject(reason) {
    return new Promise((resolve, reject) => reject(reason));
  }
  constructor(resolver) {
    if (!(this instanceof Promise)) {
      throw new TypeError(
        `Class constructor Promise cannot be invoked without 'new'`
      );
    }
    if (!isFunction(resolver)) {
      throw new TypeError(`Promise resolver ${resolver} is not a function`);
    }
    this._stauts = PENDING;
    this._value = undefined;
    this._callbacks = [];
    try {
      resolver(value => resolve(this, value), reason => reject(this, reason));
    } catch (e) {
      reject(this, e);
    }
  }
  then(onFulfilled, onRejected) {
    const thenPromise = new this.constructor(noop);
    this._callbacks = this._callbacks.concat([
      {
        onFulfilled: isFunction(onFulfilled) ? onFulfilled : void 0,
        onRejected: isFunction(onRejected) ? onRejected : void 0,
        thenPromise
      }
    ]);
    invokeCallback(this);
    return thenPromise;
  }
  catch(onRejected) {
    return this.then(void 0, onRejected);
  }
}
const debounce = (fn = {}, wait = 50, immediate) => {
  let timer;
  return function() {
    if (immediate) {
      fn.apply(this, arguments);
    }
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
    }, wait);
  };
};
var throttle = (fn = {}, wait = 0) => {
  let prev = new Date();
  return function() {
    const args = arguments;
    const now = new Date();
    if (now - prev > wait) {
      fn.apply(this, args);
      prev = new Date();
    }
  };
};
const instanceOf = (left, right) => {
  let proto = left.__proto__;
  let prototype = right.prototype;
  while (true) {
    if (proto === null) {
      return false;
    } else if (proto === prototype) {
      return true;
    }
    proto = proto.__proto__;
  }
};
instanceof 运算符用来检测 constructor.prototype是否存在于参数 object 的原型链上。
typeof 操作符返回一个字符串,表示未经计算的操作数的类型。
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 "object"。
递归(英语:Recursion),又译为递回,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。 例如: 大雄在房里,用时光电视看着未来的情况。电视画面中的那个时候,他正在房里,用时光电视,看着未来的情况。电视画面中的电视画面的那个时候,他正在房里,用时光电视,看着未来的情况…… 简单来说,就是 无限套娃
我们以斐波那契数列(Fibonacci sequence)为例,看看输入结果会为正无穷的值的情况下,各种递归的情况。

首先是普通版
const fib1 = n => {
  if (typeof n !== "number") {
    throw new Error("..");
  }
  if (n < 2) {
    return n;
  }
  return fib1(n - 1) + fib1(n - 2);
};
从上面的代码分析,我们不难发现,在fib1里,JS 会不停创建执行上下文,压入栈内,而且在得出结果前不会销毁,所以数大了之后容易爆栈。

所以我们可以对其进行优化,就是利用 尾调用 进行优化。
尾调用是指函数的最后一步只返回一个纯函数的调用,而没有别的数据占用引用。代码如下:
const fib2 = (n, a = 0, b = 1) => {
  if (typeof n !== "number") {
    throw new Error("..");
  }
  if (n === 0) {
    return a;
  }
  return fib2(n - 1, b, a + b);
};
不过很遗憾,在 Chrome 83.0.4103.61 里还是会爆。

然后我们还有备忘录递归法,就是另外申请空间去存储每次递归的值,是个自顶向下的算法。

可惜,还是挂了。
不过在一些递归问题上,我们还可以利用动态规划(Dynamic programming,简称 DP)来解决。
动态规划是算法里比较难掌握的一个概念之一,但是基本能用递归来解决的问题,都能用动态规划来解决。
动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再根据子问题的解以得出原问题的解。
跟备忘录递归刚好相反,是自底向上的算法。具体代码如下:
const fib3 = n => {
  if (typeof n !== "number") {
    throw new Error("..");
  }
  if (n < 2) {
    return n;
  }
  let a = 0;
  let b = 1;
  while (n--) {
    [a, b] = [b, a + b];
  }
  return a;
};

效果很好,正确输出了正无穷~