关于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()

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理