- 做者:陈大鱼头
- github: KRISACHAN
在资源不足的设备上,将服务合并到浏览器进程中javascript
负责浏览器界面显示html
各个页面的管理,建立以及销毁前端
将渲染进程的结果绘制到用户界面上html5
网络资源管理java
页面渲染node
脚本执行android
事件处理nginx
输入网址git
浏览浏览器解析 URLgithub
生成 HTTP 请求信息
收到响应
状态码 | 含义 |
---|---|
1xx | 告知请求的处理进度和状况 |
2xx | 成功 |
3xx | 表示须要进一步操做 |
4xx | 客户端错误 |
5xx | 服务端错误 |
Socket 库提供查询 IP 地址的功能
经过解析器向 DNS 服务器发出查询
寻找相应的 DNS 服务器并获取 IP 地址
经过缓存加快 DNS 服务器的响应
协议栈经过 TCP 协议收发数据的操做。
浏览器调用 Socket.connect
浏览器调用 Socket.write
将 HTTP 请求消息交给协议栈
对较大的数据进行拆分,拆分的每一块数据加上 TCP 头,由 IP 模块来发送
使用 ACK 号确认网络包已收到
根据网络包平均往返时间调整 ACK 号等待时间
使用窗口有效管理 ACK 号
ACK 与窗口的合并
接收 HTTP 响应消息
浏览器调用 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。
HTTP/0.9:GET,无状态的特色造成
HTTP/1.0:支持 POST,HEAD,添加了请求头和响应头,支持任何格式的文件发送,添加了状态码、多字符集支持、多部分发送、权限、缓存、内容编码等
HTTP/1.1:默认长链接,同时 6 个 TCP 链接,CDN 域名分片
HTTPS:HTTP + TLS( 非对称加密 与 对称加密 )
HTTP/2.0:多路复用(一次 TCP 链接能够处理多个请求),服务器主动推送,stream 传输。
HTTP/3:基于 UDP 实现了 QUIC 协议
注:RTT = Round-trip time
构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成
建立 DOM tree
样式计算
生成 layout tree
分层
将图层转换为位图
合成位图并显示在页面中
V8 编译 JS 代码的过程
生成抽象语法树(AST)和执行上下文
第一阶段是分词(tokenize),又称为词法分析
第二阶段是解析(parse),又称为语法分析
生成字节码
字节码就是介于 AST 和机器码之间的一种代码。可是与特定类型的机器码无关,字节码须要经过解释器将其转换为机器码后才能执行。
执行代码
高级语言编译器步骤:
对象在转换类型的时候,会执行原生方法 ToPrimitive 。
其算法以下:
toSting
方法,若是此时是 原始类型 则直接返回,不然再调用valueOf
方法并返回结果;valueOf
方法,若是此时是 原始类型 则直接返回,不然再调用toString
方法并返回结果;固然,咱们能够经过重写Symbol.toPrimitive
来制定转换规则,此方法在转原始类型时调用优先级最高。
const data = {
valueOf() {
return 1;
},
toString() {
return "1";
},
[Symbol.toPrimitive]() {
return 2;
}
};
data + 1; // 3
复制代码
对象转换为布尔值的规则以下表:
参数类型 | 结果 |
---|---|
Undefined | 返回 false 。 |
Null | 返回 false 。 |
Boolean | 返回 当前参数。 |
Number | 若是参数为+0 、-0 或NaN ,则返回 false ;其余状况则返回 true 。 |
String | 若是参数为空字符串,则返回 false ;不然返回 true 。 |
Symbol | 返回 true 。 |
Object | 返回 true 。 |
对象转换为数字的规则以下表:
参数类型 | 结果 |
---|---|
Undefined | 返回 NaN 。 |
Null | Return +0. |
Boolean | 若是参数为 true ,则返回 1 ;false 则返回 +0 。 |
Number | 返回当前参数。 |
String | 先调用 ToPrimitive ,再调用 ToNumber ,而后返回结果。 |
Symbol | 抛出 TypeError 错误。 |
Object | 先调用 ToPrimitive ,再调用 ToNumber ,而后返回结果。 |
对象转换为字符串的规则以下表:
参数类型 | 结果 |
---|---|
Undefined | 返回 "undefined" 。 |
Null | 返回 "null" 。 |
Boolean | 若是参数为 true ,则返回 "true" ;不然返回 "false" 。 |
Number | 调用 NumberToString ,而后返回结果。 |
String | 返回 当前参数。 |
Symbol | 抛出 TypeError 错误。 |
Object | 先调用 ToPrimitive ,再调用 ToString ,而后返回结果。 |
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(); // 4
getNum(); // 5
复制代码
对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期。
函数内部定义的变量或者函数,而且定义的变量或者函数只能在函数内部被访问。函数执行结束以后,函数内部定义的变量会被销毁。
使用一对大括号包裹的一段代码,好比函数、判断语句、循环语句,甚至单独的一个{}均可以被看做是一个块级做用域。
词法做用域就是指做用域是由代码中函数声明的位置来决定的,因此词法做用域是静态的做用域,经过它就可以预测代码在执行过程当中如何查找标识符。
词法做用域是代码阶段就决定好的,和函数是怎么调用的没有关系。
其实每一个 JS 对象都有 __proto__
属性,这个属性指向了原型。
原型也是一个对象,而且这个对象中包含了不少函数,对于 obj
来讲,能够经过 __proto__
找到一个原型对象,在该对象中定义了不少函数让咱们来使用。
原型链:
Object
是全部对象的爸爸,全部对象均可以经过 __proto__
找到它Function
是全部函数的爸爸,全部函数均可以经过 __proto__
找到它prototype
是一个对象__proto__
属性指向原型, __proto__
将对象和原型链接起来组成了原型链原始类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址。
回收调用栈内的数据:执行上下文结束且没有被引用时,则会经过向下移动 记录当前执行状态的指针(称为 ESP) 来销毁该函数保存在栈中的执行上下文。
回收堆里的数据:
V8 中会把堆分为新生代和老生代两个区域,
新生代中存放的是生存时间短的对象,
老生代中存放的生存时间久的对象。
垃圾回收重要术语:
- 代际假说
- 大部分对象在内存中存在的时间很短
- 不死的对象,会活得更久
- 分代收集
副垃圾回收器:
主要负责新生代的垃圾回收。
这个区域不大,可是垃圾回收比较频繁。
新生代的垃圾回收算法是 Scavenge 算法。
主要把新生代空间对半划分为两个区域:对象区域,空闲区域。
当对象区域快被写满时,则会进行一次垃圾清理。
流程以下:
主垃圾回收器:
主垃圾回收器主要负责老生区中的垃圾回收。
除了新生区中晋升的对象,一些大的对象会直接被分配到老生区。
所以老生区中的对象有两个特色,一个是对象占用空间大,另外一个是对象存活时间长。
流程以下:
一旦执行垃圾回收算法,会致使 全停顿(Stop-The-World) 。
可是 V8 有 增量标记算法 。
V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成。
xss:将代码注入到网页
csrf:跨站请求伪造。攻击者会虚构一个后端请求地址,诱导用户经过某些途径发送请求。
中间人攻击:中间人攻击是攻击方同时与服务端和客户端创建起了链接,并让对方认为链接是安全的,可是实际上整个通讯过程都被攻击者控制了。攻击者不只能得到双方的通讯信息,还能修改通讯信息。
使用转义字符过滤 html 代码
const escapeHTML = value => {
if (!value || !value.length) {
return value;
}
return value
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
};
复制代码
过滤 SQL 代码
const replaceSql = value => {
if (!value || !value.length) {
return value;
}
return value.replace(/select|update|delete|exec|count|'|"|=|;|>|<|%/gi, "");
};
复制代码
预防 CSRF
预防中间人攻击
内容安全策略 (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">
Expires
Cache-Control
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。
Last-Modified 与 If-Modified-Since 配对。Last-Modified
把 Web 应用最后修改时间告诉客户端,客户端下次请求之时会把 If-Modified-Since
的值发生给服务器,服务器由此判断是否须要从新发送资源,若是不须要则返回 304,若是有则返回 200。这对组合的缺点是只能精确到秒,并且是根据本地打开时间来记录的,因此会不许确。
Etag 与 If-None-Match 配对。它们没有使用时间做为判断标准,而是使用了一组特征串。Etag
把此特征串发生给客户端,客户端在下次请求之时会把此特征串做为If-None-Match
的值发送给服务端,服务器由此判断是否须要从新发送资源,若是不须要则返回 304,若是有则返回 200。
基础概念:
进程:进程(英语:process),是指计算机中已运行的程序。进程曾经是分时系统的基本运做单位。
线程:线程(英语:thread)是操做系统可以进行运算调度的最小单位。大部分状况下,它被包含在进程之中,是进程中的实际运做单位。
协程:协程(英语:coroutine),又称微线程,是计算机程序的一类组件,推广了协做式多任务的子程序,容许执行被挂起与被恢复。
Node 中最核心的是 v8 引擎,在 Node 启动后,会建立 v8 的实例,这个实例是多线程的,各个线程以下:
主线程:编译、执行代码。
编译/优化线程:在主线程执行的时候,能够优化代码。
分析器线程:记录分析代码运行时间,为 Crankshaft 优化代码执行提供依据。
垃圾回收的几个线程。
阻塞 是指在 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()
的调度回调函数。
待定回调 :执行延迟到下一个循环迭代的 I/O 回调。
idle, prepare :仅系统内部使用。
轮询 :检索新的 I/O 事件;执行与 I/O 相关的回调(几乎全部状况下,除了关闭的回调函数,那些由计时器和 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;
};
复制代码
效果很好,正确输出了正无穷~
若是你喜欢探讨技术,或者对本文有任何的意见或建议,很是欢迎加鱼头微信好友一块儿探讨,固然,鱼头也很是但愿能跟你一块儿聊生活,聊爱好,谈天说地。 鱼头的微信号是:krisChans95 扫码公众号,回复『面试资料』能够获取约200M前端高质量面试资料,千万不要错过哦。