问题1
为什么async1 success
async1 end
没有打印
async function async1() {
console.log('async1 start')
await new Promise(resolve => {
console.log('promise1')
})
console.log('async1 success')
return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
打印结果
分析:有些容易让人产生疑惑的代码,这里的resolve没有执行,promise状态一直保持在pending状态
问题2
实现Scheduler
类及add
方法
class Scheduler {
add(promiseCreator) {
// 补充···
}
}
const timeout = (time) =>
new Promise((resolve) => {
setTimeout(resolve, time);
});
const scheduler = new Scheduler();
const addTask = (time, order) => {
scheduler.add(() => timeout(time)).then(() => console.log(order));
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4"); // output: 2 3 1 4
// 一开始,1、2两个任务进入队列
// 500ms时,2完成,输出2,任务3进队
// 800ms时,3完成,输出3,任务4进队
// 1000ms时,1完成,输出1
// 1200ms时,4完成,输出4
分析:要实现一个带并发限制的异步调度器,保证同时最多运行2个任务。这里的timeout方法和add方法都返回的是promise。
需要两个队列,一个存储待执行任务,一个存储正在运行的任务。当执行队列小于2就直接执行任务,否则加入待执行队列,执行函数中执行任务后要从执行队列移除。
class Scheduler {
constructor() {
this.pending = []; // 待执行
this.excute = []; // 正在运行
}
add(promiseCreator) {
return new Promise((resolve, reject) => {
promiseCreator.resolve = resolve;
if (this.excute.length < 2) {
this.run(promiseCreator);
} else {
this.pending.push(promiseCreator);
}
});
}
run(promiseCreator) {
this.excute.push(promiseCreator);
promiseCreator().then(() => {
promiseCreator.resolve();
this.remove(promiseCreator);
if (this.pending.length) {
this.run(this.pending.shift());
}
});
}
remove(promiseCreator) {
let index = this.excute.findIndex((item) => item === promiseCreator);
index !== -1 && this.excute.splice(index, 1);
}
}
问题3
require查找包过程
分析:require加载文件分几种类型
1,内置模块(http,fs)
2,./
../
打头文件绝对路径文件,
3,第三方包
查找包时缓存优先,优先重缓存加载,第三方包名不会和核心模块名重复
1,判断是否是原生模块
2,非原生模块会判断是否有路径标识
3,第三方包先找到当前文件所在目录中的node_module文件夹(npm出来的第三方),找到node_module文件夹中对应加载模块名的文件夹,找到package.json文件中的 main 这个属性(main属性记录了 第三方包的入口模块)。如果文件package.json不存在或者main属性没有值,require会默认加载该包中的 index 文件(index 为默认被选项),如果加载文件中的当前目录中没有node_module文件夹的话,require就会查找上一级目录中是否有node_module文件夹,如果有,继续执行以上操作,如果没有在到上上一级查找,直到找到磁盘根目录好找不到就报错
can not find module xxx.
问题4
如何取消异步请求
分析:
1,ajax取消请求使用abort()方法
2,fetch请求
JavaScript 规范中添加了新的 AbortController,允许开发人员使用信号中止一个或多个 fetch 调用。
- 创建一个 AbortController 实例
- 该实例具有 signal 属性
- 将 signal 传递给 fetch option 的 signal
调用 AbortController 的 abort 属性来取消所有使用该信号的 fetch。
const abortController = new AbortController(); const { signal } = abortController; fetch(url, { signal }) .then((res) => res.json()) .then((res) => { // handle }) .catch((e) => { if (e.name === "AbortError") { // abort } }); setTimeout(() => { abortController.abort(); }, 1000);
问题5
箭头函数不适用场景
1,对象方法
2,原型挂载方法
3,构造函数(实例化时会提示不是构造函数)
4,动态上下文中的回调函数。(如addEventListener中回调要用到this)
5,Vue生命周期和method
问题6
HTMLCollection和NodeList区别
- HTMLCollection是Element的集合,NodeList是的节点集合
- 获取Node和Element返回结果可能不一样(elem.childNodes和elem.children不一样)
- 前者会包含Text和comment节点,后者不会
问题7
Js严格模式特点
- 全局变量必须先声明
- 禁止使用with
- 创建eval作用域
- 禁止this指向window
- 函数参数不能重名
问题8
for和forEach谁更快
for更快,forEach每次都要创建一个函数来调用,for不会,函数需要独立作用域,会有额外的开销
问题9
Node.js如何创建子进程
开启子进程child_process.fork
和cluster.fork
使用send
和on
传递消息child_process.fork
--compute.js
function getSum() {
let sum = 0
for (let i = 0; i < 10000; i++) {
sum += i
}
return sum
}
process.on('message', data => {
console.log('子进程 id', process.pid)
console.log('子进程接受到的信息: ', data)
const sum = getSum()
// 发送消息给主进程
process.send(sum)
})
---process-fork
const http = require('http')
const fork = require('child_process').fork
const server = http.createServer((req, res) => {
if (req.url === '/get-sum') {
console.info('主进程 id', process.pid)
// 开启子进程
const computeProcess = fork('./compute.js')
computeProcess.send('开始计算')
computeProcess.on('message', data => {
console.info('主进程接受到的信息:', data)
res.end('sum is ' + data)
})
computeProcess.on('close', () => {
console.info('子进程因报错而退出')
computeProcess.kill()
res.end('error')
})
}
})
server.listen(3000, () => {
console.info('localhost: 3000')
})
cluster.fork
问题10
js-bridge实现原理
实现方式
1,注册全局变量(适合获取简单信息,不适合异步)
2,URL Scheme(适合所有情况)
封装一个简易的js-bridge
const sdk = {
invoke(url, data = {}, onSuccess, onErr) {
const iframe = document.createElement("iframe");
iframe.style.visibility = "hidden";
document.body.append(iframe);
iframe.onload = () => {
const content = iframe.contentWindow.document.body.innerHTML;
onSuccess(JSON.parse(content));
iframe.remove();
};
iframe.onErr = () => {
onErr();
iframe.remove();
};
iframe.src = `my-app-name://${url}?data=${JSON.stringify(data)}`;
},
// 能力一
fn1(data, onSuccess, onErr) {
this.invoke("api/fn1", data, onSuccess, onErr);
},
// 能力二
fn1(data, onSuccess, onErr) {
this.invoke("api/fn1", data, onSuccess, onErr);
},
// 能力三
fn1(data, onSuccess, onErr) {
this.invoke("api/fn1", data, onSuccess, onErr);
},
};
问题11
requestAnimationFrame
requestIdleCallback
是宏任务还是微任务?
宏任务,需要等待DOM渲染完成才执行
问题12
移动端H5点击有300ms延迟,如何解决?
早期使用了FastClick库,监听touchend事件(touchstart touchend 会先于click触发)
使用自定义DOM事件模拟一个click事件,把默认click事件(300ms之后触发)禁止掉
window.addEventListener('load',function(){
FastClick.attach(document.body)
},false)
后期meta标签进行了改进,width=device-width
属性解决了问题
问题13
token和cookie区别?
cookie
- HTTP无状态,每次请求都要带cookie,以帮助识别身份
- 服务端也可以向客户端set-cookie,cookie大小限制为4kb
- 默认有跨域限制:不可跨域共享,传递cookie(ajax请求配置withCredentials,var xhr = new XMLHttpRequest() xhr.withCredentials = true 可以设置允许跨域携带cookie。fetch配置credentials。omit: 默认值,忽略cookie的发送 same-origin: 表示cookie只能同域发送,不能跨域发送。include: cookie既可以同域发送,也可以跨域发送)
- HTML5之前cookie常被用于本地存储,HTML5之后常用localStorage和sessionStorage
session优缺点
- 用户信息存储在服务端,可快速封禁某个用户
- 占用服务端内存,硬件成本高
- 多进程,多服务器时,不好同步——需要用到第三方如Redis缓存
- 默认有跨域限制
cookie和session
- cookie用于登录验证,存储用户标识(如userId)
- session存在服务端,存储用户相信信息,和cookie信息一一对应
- cookie+session是常见登录验证解决方案
token vs cookie
- cookie是HTTP规范,跨域限制,配合session使用,token是自定义传递
- cookie默认会被浏览器存储,token需要自己存储
- token默认也有跨域限制,用于JWT(Json Web Token)
JWT优缺点
- 不占用服务端内存
- 多进程,多服务器不受影响
- 用户信息存储在客户端,无法快速封禁某用户
- 服务端秘钥被泄露,则用户信息全部丢失
- token体积一般大于cookie,会增加请求数据量
问题14
HTTP1.0 1.1 2.0区别
HTTP1.0
最基础HTTP请求,支持基本GET POST方法
HTTP1.1
- 缓存策略cache-control E-tag等,支持长连接Connection:keep-alive,一次TCP连接多次请求
- 断点续传,状态码206
- 支持PUT DELETE等,可用于Restful API
HTTP2.0
- 可压缩header,减少体积
- 多路复用,一次TCP连接中可以多个HTTP并行请求
- 服务端推送
问题15
script中defer和async区别
默认情况下HTML暂停解析,下载js,执行js,再继续解析HTML
defer:HTML继续解析,并行下载JS,HTML解析完之后再执行JS
async:HTML继续解析,并行下载JS,执行JS,再解析HTML
问题16
prefetch和dns-prefetch有什么区别
preload资源在当前页面使用,会优先加载
prefetch资源在未来页面使用,空闲时加载
dns-prefetch即DNS预查询
preconnect即DNS预连接
问题17
从URL输入到页面显示的完整过程
网络请求
- DNS查询(得到IP),建立TCP连接(三次握手)
- 浏览器发起HTTP请求
- 收到请求回应,得到HTML源码
- 解析HTML过程中,遇到静态资源还会继续发起网络请求
JS CSS 图片 视频等(静态资源如果有强缓存,此时不必请求)
解析
- 字符串->结构化数据
- HTML构建DOM树
- CSS构建CSSDOM树(style tree)
- 两者结合,形成render tree
- 解析过程很复杂
- CSS可能来自