乐趣区

关于javascript:问题集合1

问题 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.forkcluster.fork
应用 sendon传递音讯
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属性解决了问题

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

问题 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

<script src="xxx" async></script>
<script src="xxx" defer></script>

问题 16

prefetch 和 dns-prefetch 有什么区别
preload 资源在以后页面应用,会优先加载
prefetch 资源在将来页面应用,闲暇时加载
dns-prefetch 即 DNS 预查问
preconnect 即 DNS 预连贯

<link rel="preload"   href="xxx.js" as="javascript">
<link rel="prefetch"  href="xxx.js" as="javascript">
<link rel="dns-prefetch"   href="http://xxx">
<link rel="preconnect"   href="http://xxx">

问题 17

从 URL 输出到页面显示的残缺过程

网络申请
  • DNS 查问(失去 IP), 建设 TCP 连贯(三次握手)
  • 浏览器发动 HTTP 申请
  • 收到申请回应,失去 HTML 源码
  • 解析 HTML 过程中,遇到动态资源还会持续发动网络申请
  • JS CSS 图片 视频等(动态资源如果有强缓存,此时不用申请)

    解析
  • 字符串 -> 结构化数据
  • HTML 构建 DOM 树
  • CSS 构建 CSSDOM 树(style tree)
  • 两者联合,造成 render tree
  • 解析过程很简单
  • CSS 可能来自 <style><link>
  • js 可能内嵌或者外链,还有 defer async 逻辑
  • img 可能内嵌(base64),可能外链
渲染
  • 计算各个 DOM 的尺寸,定位,最初绘制到页面
  • 遇到 JS 可能会执行(参考 defer async)
  • 异步 CSS,图片加载,可能会触发从新渲染

问题 17

重绘 repaint 和重拍 reflow 区别?
重绘,元素外观扭转,如色彩,背景, 然而地位不变,重排会从新计算尺寸和布局,可能会影响其余元素地位

问题 18

如何实现网页多标签 tab 通信
localStorage 实现

-- a.html
const btn1 = document.getElementById('btn1')
        btn1.addEventListener('click', () => {
            const newInfo = {
                id: 100,
                name: '题目' + Date.now()}
            localStorage.setItem('changeInfo', JSON.stringify(newInfo))
        })

-- b.html 监听
        window.addEventListener('storage', event => {console.info('key', event.key)
            console.info('value', event.newValue)
        })

SharedWorker WebSocket也能够。SharedWorker 兼容性不是很好,比方 safari,IE 兼容性不太好

问题 19

遍历 DOM 树
深度优先遍历,递归和非递归版

function visitNode(n: Node) {if (n instanceof Comment) {console.log("正文节点", n.textContent);
  }
  if (n instanceof Text) {const t = n.textContent?.trim();
    if (t) {console.log("文本节点", t);
    }
  }
  if (n instanceof HTMLElement) {console.log("元素节点", `<${n.tagName.toLowerCase()}>`);
  }
}

function depthFirstTraverse(root: Node) {visitNode(root);
  const childNodes = root.childNodes; // .childNodes .chldren 不一样,.chldren 只获取元素,.childNodes 有 text 和 comment
  if (childNodes.length) {childNodes.forEach((child) => {depthFirstTraverse(child);
    });
  }
}
 -- 非递归
function depthStackTraverse(root: Node) {const stack: Node[] = [];
  // root node in stack
  stack.push(root);
  while (stack.length > 0) {const curNode = stack.pop();
    if (curNode === null) break;
    visitNode(curNode);
    // 子节点压栈
    const childNodes = curNode.childNodes;
    if (childNodes.length) {Array.from(childNodes)
        .reverse() // 反向入栈,先进后出
        .forEach((child) => stack.push(child));
    }
  }
}

广度优先遍历

function breadthFirstTraverse(root: Node) {const queue: Node[] = [];
  //   inqueue
  queue.unshift(root);
  while (queue.length) {const curNode = queue.pop();
    if (curNode === null) break;
    visitNode(curNode);

    const childNodes = curNode.childNodes;
    if (childNodes.length) {childNodes.forEach((child) => queue.unshift(child));
    }
  }
}

问题 20

实现一个 LazyMan

class LazyMan {
  name = "";
  tasks = [];

  constructor(name) {
    this.name = name;
    setTimeout(() => {this.next();
    });
  }

  next() {const task = this.tasks.shift();
    task && task();}

  eat(food) {const task = () => {console.info(`${this.name} eat ${food}`);
      this.next(); // 立即执行下一个工作};
    this.tasks.push(task);
    return this; // 链式调用
  }
  sleep(sec) {const task = () => {console.info(`${this.name} 开始睡觉 `);
      setTimeout(() => {console.info(`${this.name} 曾经睡完了 ${sec}s,开始执行下一个工作 `);
        this.next(); // xx 秒之后再执行下一个工作}, sec * 1000);
    };

    this.tasks.push(task);
    return this;
  }
}

const me = new LazyMan("jack");
me.eat("苹果")
  .eat("香蕉")
  .sleep(2)
  .eat("葡萄")
  .eat("西瓜")
  .sleep(2)
  .eat("橘子");

问题 21

手写 curry 函数,实现科里化

function curry(fn) {
  const fnArgsLen = fn.length;  // 传入函数的参数长度
  let args = [];

  function calc(...newArgs) {args = [...args, ...newArgs];
    if (args.length < fnArgsLen) {return calc;} else {return fn.apply(this, args.slice(0, fnArgsLen));
    }
  }
  return calc;
}

function add(a, b, c) {return a + b + c;}
const curryAdd = curry(add);
const res = curryAdd(10)(20)(30); // 60
console.info(res);

问题 21

参加到网页生成流程中,有哪些线程?

  • GUI 渲染线程
  • JS 引擎线程
  • 定时器触发线程
  • 异步 HTTP 申请线程
  • 事件触发线程
  1. JS 分为同步工作和异步工作 => 同步异步辨别
  2. 同步工作都在主线程上执行,造成一个执行栈(JS 引擎线程)=> 主线程执行形式
  3. 主线程之外,事件触发线程保护工作队列,异步工作有了回调,就在队列中插入一个事件 => 工作队列的调度
  4. 一旦执行栈所有同步工作执行实现,零碎就会读取工作队列,并且把可运行的异步工作转移到可执行栈中 => 事件调度线程

问题 22

实现一个 H5 图片懒加载 SDK?
页面中有很多图片,一次性展现不完,能够设置 img 标签的 data-src 属性

 <img src="./img/loading.gif" data-src="./img/realsrc.webp"/>

设计逻辑
获取 img 标签的在页面中距离顶部高度值通过 elem.getBoundingClientRect() 与 innerHeight 比拟,如果间隔顶部值小于window.innerHeight, 就能够显示了

        function mapImagesAndTryLoad() {const images = document.querySelectorAll('img[data-src]')
            if (images.length === 0) return

            images.forEach(img => {const rect = img.getBoundingClientRect()
                if (rect.top < window.innerHeight) {
                    // 显示进去
                    // console.info('loading img', img.dataset.src)
                    img.src = img.dataset.src
                    img.removeAttribute('data-src') // 移除 data-src 属性,为了下次执行时缩小计算成本
                }
            })
        }
        // 节流
        window.addEventListener('scroll', _.throttle(() => {mapImagesAndTryLoad()
        }, 100))

        mapImagesAndTryLoad()
退出移动版