作者:陈大鱼头

Chrome浏览器进程

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

浏览器主进程

负责浏览器界面显示

各个页面的管理,创建以及销毁

将渲染进程的结果绘制到用户界面上

网络资源管理

GPU进程

用于3D渲染绘制

网络进程

发起网络请求

插件进程

第三方插件处理,运行在沙箱中

渲染进程

页面渲染

脚本执行

事件处理

网络传输流程

生成HTTP请求消息

输入网址

浏览浏览器解析URL

生成HTTP请求信息

收到响应

| 状态码 | 含义 |

| ------ | ------------------------ |

| 1xx | 告知请求的处理进度和情况 |

| 2xx | 成功 |

| 3xx | 表示需要进一步操作 |

| 4xx | 客户端错误 |

| 5xx | 服务端错误 |

向DNS服务器查询Web服务器的IP地址

Socket库提供查询IP地址的功能

通过解析器向DNS服务器发出查询

全世界DNS服务器的大接力

寻找相应的DNS服务器并获取IP地址

通过缓存加快DNS服务器的响应

委托协议栈发送消息

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

创建套接字

浏览器,邮件等一般的应用程序收发数据时用TCP

DNS查询等收发较短的控制数据时用UDP

连接服务器

浏览器调用Socket.connect

在TCP模块处创建表示连接控制信息的头部

通过TCP头部中的发送方和接收方端口号找到要连接的套接字

收发数据

浏览器调用Socket.write

将HTTP请求消息交给协议栈

对较大的数据进行拆分,拆分的每一块数据加上TCP头,由IP模块来发送

使用ACK号确认网络包已收到

根据网络包平均往返时间调整ACK号等待时间

使用窗口有效管理ACK号

ACK与窗口的合并

接收HTTP响应消息

断开管道并删除套接字

数据发送完毕后断开连接

删除套接字

客户端发送FIN

服务端返回ACK号

服务端发送FIN

客户端返回ACK号

网络协议

TCP

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 定义。

基于流的方式

面向连接

丢包重传

保证数据顺序

UDP

Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。RFC 768 描述了 UDP。

UDP是非连接的协议,也就是不会跟终端建立连接

UDP包信息只有8个字节

UDP是面向报文的。既不拆分,也不合并,而是保留这些报文的边界

UDP可能丢包

UDP不保证数据顺序

HTTP

HTTP/0.9:GET,无状态的特点形成

HTTP/1.0:支持POST,HEAD,添加了请求头和响应头,支持任何格式的文件发送,添加了状态码、多字符集支持、多部分发送、权限、缓存、内容编码等

HTTP/1.1:默认长连接,同时6 个 TCP连接,CDN 域名分片

HTTPS:HTTP + TLS(非对称加密 与 对称加密)

客户端发出https请求,请求服务端建立SSL连接

服务端收到https请求,申请或自制数字证书,得到公钥和服务端私钥,并将公钥发送给客户端

户端验证公钥,不通过验证则发出警告,通过验证则产生一个随机的客户端私钥

客户端将公钥与客户端私钥进行对称加密后传给服务端

服务端收到加密内容后,通过服务端私钥进行非对称解密,得到客户端私钥

服务端将客户端私钥和内容进行对称加密,并将加密内容发送给客户端

客户端收到加密内容后,通过客户端私钥进行对称解密,得到内容

HTTP/2.0:多路复用(一次TCP连接可以处理多个请求),服务器主动推送,stream传输。

HTTP/3:基于 UDP 实现了QUIC 协议

建立好HTTP2连接

发送HTTP2扩展帧

使用QUIC建立连接

如果成功就断开HTTP2连接

升级为HTTP3连接

注:RTT = Round-trip time

页面渲染流程

构建 DOM 树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成

创建DOM tree

遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中。

不可见的节点会被布局树忽略掉。

样式计算

创建CSSOM tree

转换样式表中的属性值

计算出DOM节点样式

生成layout tree

分层

生成图层树(LayerTree)

拥有层叠上下文属性的元素会被提升为单独的一层

需要剪裁(clip)的地方也会被创建为图层

图层绘制

将图层转换为位图

合成位图并显示在页面中

页面更新机制

更新了元素的几何属性(重排)

更新元素的绘制属性(重绘)

直接合成

CSS3的属性可以直接跳到这一步

JS执行机制

代码提升(为了编译)

变量提升

函数提升(优先级最高)

编译代码

生成抽象语法树(AST)和执行上下文

第一阶段是分词(tokenize),又称为词法分析

第二阶段是解析(parse),又称为语法分析

生成字节码

字节码就是介于 AST 和机器码之间的一种代码。但是与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码后才能执行。

执行代码

执行代码

执行全局代码时,创建全局上下文

调用函数时,创建函数上下文

使用eval函数时,创建eval上下文

执行局部代码时,创建局部上下文

类型

基本类型

Undefined

Null

Boolean

String

Symbol

Number

Object

BigInt

复杂类型

Object

隐式转换规则

基本情况

转换为布尔值

转换为数字

转换为字符串

转换为原始类型

对象在转换类型的时候,会执行原生方法ToPrimitive。

其算法如下:

如果已经是 原始类型,则返回当前值;

如果需要转 字符串 则先调用toSting方法,如果此时是 原始类型 则直接返回,否则再调用valueOf方法并返回结果;

如果不是 字符串,则先调用valueOf方法,如果此时是 原始类型 则直接返回,否则再调用toString方法并返回结果;

如果都没有 原始类型 返回,则抛出 TypeError类型错误。

当然,我们可以通过重写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 是和执行上下文绑定的。

执行上下文:

全局执行上下文:全局执行上下文中的 this 也是指向 window 对象。

函数执行上下文:使用对象来调用其内部的一个方法,该方法的 this 是指向对象本身的。

eval 执行上下文:执行eval环境内部的上两个情况。

根据优先级最高的来决定 this 最终指向哪里。

首先,new 的方式优先级最高,接下来是 bind 这些函数,然后是 obj.foo() 这种调用方式,最后是 foo 这种调用方式,同时,箭头函数的 this 一旦被绑定,就不会再被任何方式所改变。

三点注意:

当函数作为对象的方法调用时,函数中的 this 就是该对象;

当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window;

嵌套函数中的 this 不会继承外层函数的 this 值。

我们还提了一下箭头函数,因为箭头函数没有自己的执行上下文,所以箭头函数的 this 就是它外层函数的 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__ 将对象和原型连接起来组成了原型链

V8工作原理

数据存储

栈空间:调用栈,存储执行上下文,以及存储原始类型的数据

堆空间:存储引用类型

原始类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址。

垃圾回收

回收调用栈内的数据:执行上下文结束且没有被引用时,则会通过向下移动 记录当前执行状态的指针(称为 ESP) 来销毁该函数保存在栈中的执行上下文。

回收堆里的数据:

V8 中会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。

副垃圾回收器,主要负责新生代的垃圾回收。

主垃圾回收器,主要负责老生代的垃圾回收。

垃圾回收重要术语:

代际假说

分代收集

工作流程:

标记空间中活动对象和非活动对象

回收非活动对象所占据的内存

内存整理

一旦执行垃圾回收算法,会导致 全停顿(Stop-The-World) 。但是V8有 增量标记算法。V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成。

事件循环

微任务(microtask)

process.nextTick

promise

Object.observe (已废弃)

MutationObserver

宏任务(macrotask)

script

setTimeout

setInterval

setImmediate

I/O

UI rendering

执行顺序

执行同步代码,这属于宏任务

执行栈为空,查询是否有微任务需要执行

必要的话渲染 UI

然后开始下一轮 Event loop,执行宏任务中的异步代码

浏览器安全

攻击方式

xss:将代码注入到网页

持久型:写入数据库

非持久型:修改用户代码

csrf:跨站请求伪造。

Get 请求不对数据进行修改

不让第三方网站访问到用户 Cookie

阻止第三方网站请求接口

请求时附带验证信息,比如验证码或者 Token

中间人攻击:中间人攻击是攻击方同时与服务端和客户端建立起了连接,并让对方认为连接是安全的,但是实际上整个通信过程都被攻击者控制了。攻击者不仅能获得双方的通信信息,还能修改通信信息。

当然防御中间人攻击其实并不难,只需要增加一个安全通道来传输信息。

CSP

建立白名单

HTTP Header 中的 Content-Security-Policy

浏览器性能

DNS预解析

Chrome 和 Firefox 3.5+ 能自动进行预解析

关闭DNS预解析:

强缓存

Expires

缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。

Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。

Cache-Control

协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程。

服务器响应头:Last-Modified,Etag

浏览器请求头:If-Modified-Since,If-None-Match

**Last-Modified ** 与 If-Modified-Since 配对。Last-Modified 把Web应用最后修改时间告诉客户端,客户端下次请求之时会把 If-Modified-Since 的值发生给服务器,服务器由此判断是否需要重新发送资源,如果不需要则返回304,如果有则返回200。这对组合的缺点是只能精确到秒,而且是根据本地打开时间来记录的,所以会不准确。

**Etag ** 与 If-None-Match 配对。它们没有使用时间作为判断标准,而是使用了一组特征串。Etag把此特征串发生给客户端,客户端在下次请求之时会把此特征串作为If-None-Match的值发送给服务端,服务器由此判断是否需要重新发送资源,如果不需要则返回304,如果有则返回200。

NodeJs

单线程

基础概念:

进程:进程(英语:process),是指计算机中已运行的程序。进程曾经是分时系统的基本运作单位。

线程:线程(英语:thread)是操作系统能够进行运算调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。

协程:协程(英语:coroutine)是计算机程序的一类组件,推广了协作式多任务的子程序,允许执行被挂起与被恢复。

Node 中最核心的是 v8 引擎,在 Node 启动后,会创建 v8 的实例,这个实例是多线程的,各个线程如下:

主线程:编译、执行代码。

编译/优化线程:在主线程执行的时候,可以优化代码。

分析器线程:记录分析代码运行时间,为 Crankshaft 优化代码执行提供依据。

垃圾回收的几个线程。

非阻塞I/O

阻塞 是指在 Node.js 程序中,其它 JavaScript 语句的执行,必须等待一个非 JavaScript 操作完成。这是因为当 阻塞 发生时,事件循环无法继续运行 JavaScript。

在 Node.js 中,JavaScript 由于执行 CPU 密集型操作,而不是等待一个非 JavaScript 操作(例如 I/O)而表现不佳,通常不被称为 阻塞。在 Node.js 标准库中使用 libuv 的同步方法是最常用的 阻塞 操作。原生模块中也有 阻塞 方法。

事件循环

┌───────────────────────────┐

┌─>│ timers │

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

│ │ pending callbacks │

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

│ │ idle, prepare │

│ └─────────────┬─────────────┘ ┌───────────────┐

│ ┌─────────────┴─────────────┐ │ incoming: │

│ │ poll │

│ └─────────────┬─────────────┘ │ 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

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 如何实现进程间通信。

手写代码

new操作符

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

};

手写bind

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)

}

}

手写Promise

// 来源于 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();

};

}

}

手写instanceOf

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__;

};

}

其它知识

typeof vs instanceof

instanceof 运算符用来检测 constructor.prototype是否存在于参数 object 的原型链上。

typeof 操作符返回一个字符串,表示未经计算的操作数的类型。

在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 "object"。

参考资料

服务器前端机中转机制,『中高级前端面试』之终极知识点相关推荐

  1. 中高级前端必须了解的--JS中的内存管理

    前言 像C语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()用于分配内存和释放内存. 而对于JavaScript来说,会在创建变量(对象,字符串等)时分配内存,并且在不 ...

  2. 写给中高级前端关于性能优化的9大策略和6大指标 | 网易四年实践

    「链接和长图失效,请大家点击阅读原文查看详情」 前言 笔者近半年一直在参与项目重构,在重构过程中大量应用「性能优化」和「设计模式」两方面的知识.「性能优化」和「设计模式」两方面的知识不管在工作还是面试 ...

  3. 前端更新需要清空浏览器缓存_浏览器缓存机制分析及前端缓存清理

    浏览器缓存机制分析及前端缓存清理 发布时间:2018-06-03 16:56, 浏览次数:857 本文主题:理清浏览器的缓存机制的内部逻辑,并给出避免浏览器缓存的相关解决方案 相信很多新手前端发布页面 ...

  4. (中篇)中高级前端大厂面试秘籍,寒冬中为您保驾护航,直通大厂

    感恩!~~没想到上篇文章能这么受大家的喜欢,激动不已.?.但是却也是诚惶诚恐,这也意味着责任.下篇许多知识点都需要比较深入的研究和理解,博主也是水平有限,担心自己无法承担大家的期待.不过终究还是需要摆 ...

  5. 「中高级前端进阶」从零开始手写一个 vue-cli 脚手架

    关注我的小伙伴应该知道,我之前写过一篇脚手架相关的文章,在掘金收获了近一千个赞,被前端大全和奇舞周刊公众号转载. 可以说自定义脚手架是每一个中高级前端都应该具备的能力. 1. 脚手架带来的便利 在现在 ...

  6. 前端html页面如何结合后端,前端开发与后台交互机制

    传统开发模式: 一般传统上的开发协作模式有两种: 一种是前端先写一个静态页面,写好后,让后端去套模板.静态页面可以本地开发,也无需考虑业务逻辑只需要实现View即可.不足是还需要后端套模板,这些前端代 ...

  7. 中高级前端面试宝典之浏览器篇

    中高级前端面试宝典 作为一名前端开发工程师,要掌握的知识点是多而杂的,在面试刷题阶段,经常没头没脑的,我将面试题系统化,分了好几个系列,祝愿大家(包括我)在这个疫情刚过去的互联网寒冬找到事儿少钱多

  8. 前端vue里面点击加载更多_js实现『加载更多』功能实例

    DEMO : 滚动加载示例 关于如何实现『加载更多』功能,网上有插件可用,例如比较著名的使用iscroll.js实现的上拉加载更多.下拉刷新功能. 但实际用起来却是很麻烦.由于是第三方插件,要按照对方 ...

  9. java web前端哪个城市,Java Web 是前端还是后端

    Java Web 是前端还是后端 Java Web 是前端还是后端? Java Web是属于后端,Java Web就是用Java技术开发的Web应用,而Java是一种可以编写跨平台应用软件.完全面向对 ...

最新文章

  1. Neighbor2Neighbor: Self-Supervised Denoising from Single Noisy Images
  2. robot向linux发送命令,linux发送手机短信 利用fesion robot
  3. request.getRealPath不推荐使用
  4. 敏捷开发方法学及应用
  5. python爬虫xpath提取数据_python爬虫三大解析库之XPath解析库通俗易懂详讲
  6. 【C++深度剖析教程24】C++中不同的继承方式
  7. Java内存配太大导致fullgc_记一次因为短命大对象导致fullGC的问题
  8. Python常见数据结构整理,分享给你们
  9. Apache Common-cli简单使用
  10. WINDOWS登录系统之前(欢迎界面)运行指定程序脚本服务
  11. AVIator -- Bypass AV tool
  12. Nginx源码分析 - HTTP模块篇 - HTTP模块的初始化(20)
  13. Kata: 从随机的三字符列表组中恢复秘密字符串
  14. linux支持usb打印机
  15. 福利:appium+selenium+python 模拟手工点击趣头条(app赚钱软件)
  16. 计算机1946考试试题,统考计算机考试试题及答案
  17. python正态分布函数_数学之美_正态分布(Python代码)
  18. iPhone尺寸大全(包含iPhone14系列)
  19. php测试教程,PHP单元测试基础教程
  20. Hello Goodbye

热门文章

  1. Java中使用HSSFWorkbook POI导出下载excel文件
  2. Python 在线免费批量美颜,不比某秀秀方便好用一些吗!
  3. 【MySQL 数据库】JDBC 编程之 Java 连接 MySQL
  4. 编程之余对人品的感悟
  5. vs未找到导入的项目,请确认 声明中的路径正确
  6. 一个能在vue3中运行的JSON编辑器,能展示JSON数据的高亮,打开时有默认数据
  7. 配置华为s系列交换机mode lacp
  8. 远程连接服务器突然失败
  9. IOS在Windows自动化测试之tidevice
  10. 谷歌浏览器F12抓包如何过滤只显示接口请求不显示图片、js那些请求