高级web前端面试题(高级前端面试知识点)
高级web前端面试题(高级前端面试知识点)参数类型结果Undefined返回 false 。Null返回 false 。Boolean返回 当前参数。Number如果参数为 0 、 -0 或 NaN ,则返回 false ;其他情况则返回 true 。String如果参数为空字符串,则返回 false ;否则返回 true 。Symbol返回 true 。Object返回 true 。const data = { valueOf () { return 1; } toString () { return '1'; } [Symbol.toPrimitive]() { return 2; } }; data 1 // 3 复制代码转换为布尔值对象转换为布尔值的规则如下表:对象在转换类型的时候,会执行原生方法 ToPrimitive 。其
GPU进程
- 用于3D渲染绘制
网络进程
- 发起网络请求
插件进程
- 第三方插件处理,运行在沙箱中
渲染进程
- 页面渲染
- 脚本执行
- 事件处理
网络传输流程
生成HTTP请求消息
- 输入网址
- 浏览浏览器解析URL
- 生成HTTP请求信息
- 收到响应状态码含义1xx告知请求的处理进度和情况2xx成功3xx表示需要进一步操作4xx客户端错误5xx服务端错误
向DNS服务器查询Web服务器的IP地址
- Socket库提供查询IP地址的功能
- 通过解析器向DNS服务器发出查询
全世界DNS服务器的大接力
- 寻找相应的DNS服务器并获取IP地址
- 通过缓存加快DNS服务器的响应
委托协议栈发送消息
协议栈通过TCP协议收发数据的操作。
- 创建套接字浏览器,邮件等一般的应用程序收发数据时用TCPDNS查询等收发较短的控制数据时用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 。NullReturn 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
建立白名单
Content-Security-Policy
<meta http-equiv="Content-Security-Policy">
浏览器性能
DNS预解析
<link rel="dns-prefetch" href="" />
<meta http-equiv="x-dns-prefetch-control" content="off|on">
强缓存
- 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 │<─────┤ 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
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" 。
参考资料
- 浏览器工作原理与实践
- 浏览器的运行机制—2.浏览器都包含哪些进程?
- 「中高级前端面试」JavaScript手写代码无敌秘籍
- JavaScript 深拷贝
- bailnl/promise
- 网络是怎样连接的?
- 浏览器工作原理与实践
- 浏览器的工作原理:新式网络浏览器幕后揭秘
- 前端面试之道
- HTTP各版本的区别
- 你觉得Node.js是单线程这个结论对吗?
- Node指南
- 深入理解浏览器的缓存机制
- 公司要求会使用框架vue,面试题会被问及哪些?
- 「面试题」20 Vue面试题整理
还有更多面试经验和知识点有兴趣的小伙伴们可以私信我。