5月每日一题

197 阅读6分钟

5.1

1. 谈一下同步和异步的理解?

  1. 首先要先知道js是单线程语言,只能同时处理一件事情。
  2. 同步就是在一件事情完成之前,无法进行下一件事情
console.log(100)
alert(200)
console.log(300)

就像上方的代码一样,在alert执行完成之前(浏览器出现弹窗,你不点击弹窗上的确定按钮),控制台是无法打印出300的。这样它就会发生卡顿,会阻塞下方代码的执行。 3. 异步就是一件或者多件事情在等待的时候,可以进行下一件事情。

console.log(100)
ajax(url,function(res){console.log(res})
console.log(300)

上方代码在执行ajax,等待请求结果的同时,代码会继续向下进行,不会阻塞下方代码的执行,不会发生卡顿。效率也会更高。

2. 异步的使用场景

总体来说在需要等待的情景下就需要使用异步,它通常伴随着回调函数。

  1. 网络请求,最主要的就是ajax请求数据
  2. 定时器的使用

5.2

1. DOM属于什么数据结构

树(DOM树)

2.DOM操作常用的API

  1. 获取DOM元素
<body>
  <p id="pid" class="pclass" name="pname">123</p>
  <p class="pclass" name="pname">888</p>
  <input type="text" name="pname">
  <div id="divId"></div>
</body>
<script>
  document.getElementById("pid");//返回id为pid的标签
  document.getElementsByClassName("pclass");//返回NodeList
  document.getElementsByTagName("p");//返回NodeList
  document.getElementsByName("pname");//返回NodeList
  document.querySelectorAll("p");//返回NodeList
  document.querySelector("p");//返回第一个p标签
</script>
  1. 增加/修改属性
    const pid = document.getElementById("pid");//返回id为pid的标签
    pid.style.width = '100px'
    console.log(pid.style.width)
pid.setAttribute('nameNew', "hello")
console.log(pid.getAttribute("nameNew"))
  1. 新增/插入节点
const p_new = document.createElement('p');//新增
p_new.innerText = "hello a";
const divId = document.getElementById("divId");
divId.appendChild(p_new);//父元素插入子元素;如果p_new在文档中其他位置已经存在,则是移动操作
  1. 获取子节点/父节点
  const parentNode = pid.parentNode;//获取父节点

  const childNodes = divId.childNodes;//获取子节点结合NodeList
  1. 删除子元素
divId.removeChild(childNodes[0])

3. 一次性插入多个节点

DOM操作时十分耗费性能的,尽可能避免DOM的频繁操作;如何可以的话,可以对DOM查询做缓存; 使用createDocumentFragment

  • 错误示例
<ul id="list"></ul>
  <script>
    //错误示例
    const list = document.getElementById("list");
    for (let i = 0; i < 10; i++) {
      const li = document.createElement('li');
      li.innerText = 'li-item' + i;
      list.appendChild(li)
    }
  </script>
  • 正确示例
<ul id="list"></ul>
  <script>
    //正确示例
    const list = document.getElementById("list");
    //创建一个文档片段,此时还没有插入到DOM树中
    const fragment = document.createDocumentFragment()
    for (let i = 0; i < 20; i++) {
      const li = document.createElement('li');
      li.innerText = 'li-item' + i;
      fragment.appendChild(li)
    }
    //都完成后插入到DOM树中
    list.appendChild(fragment)
  </script>  

tips1:不建议使用innerHTML,可能存在安全问题
tips2:可以对DOM查询做缓存

const len = document.getElementsByTagName("p");
  for(let i = 0; i < len; i++){
    
  }

使用i < len而不是i < document.getElementsByTagName("p")

5.3

1. 如何识别浏览器类型

主要使用navigator.userAgent,通过其返回的值的不同来进行判断浏览器的类型。这里只是判断最多的可能性。

2. 如何拆解url

通过location对象来解析url

https://www.runoob.com/jsref/obj-location.html?a=1&b=2#qqq

hash:返回一个URL的锚部分
location.hash   //'#qqq'


host:返回一个URL的主机名和端口
location.host  //'www.runoob.com'

hostname:返回url的主机名
location.hostname  //'www.runoob.com'

href:返回完整的url
location.href   //'https://www.runoob.com/jsref/obj-location.html?a=1&b=2#qqq'

pathname:返回url的路径名
location.pathname   //'/jsref/obj-location.html'

port:返回url的端口
location.port   //''

protocol:返回一个URL协议
location.protocol   //'https:'

search:返回一个URL的查询部分
location.search  //'?a=1&b=2'

3. 编写一个通用的事件监听函数

这里主要使用的api是addEventListener。

document.addEventListener( event, function, useCapture)
event:事件名称
function:执行函数
useCapture:传入布尔值,为true表示事件在捕获阶段执行,为false表示事件在冒泡执行阶段执行,false是默认值

除此之外还需要考虑普通执行函数和函数代理(基于事件冒泡)。 普通函数执行

const btn = document.getElementById('btn');
btn.addEventListener('click',function(e){
    e.preventDefault()//阻止默认行为(比如a标签的跳转)
    console.log(e.target)//获取触发元素
},false)

事件代理

const body = document.getElementById('body');
body.addEventListener('click',function(e){
    e.preventDefault()//阻止默认行为(比如a标签的跳转)
    if(e.target.nodeName === 'A'){
        console.log(e.target)//获取触发元素
    }
},false)

将事件绑定在父元素上,根据条件判断是否点击在目标元素上。

function bindEvent(ele,type,fn,bol,agent){
    if(!bol) bol = false;
    ele.addEventListener(type,function(e){
        const target = e.target;
        if(agent){//事件代理
            if(target.matches(agent)){
                fn.call(target,e)
            }
        }else{/普通事件
            fn.call(target,e)
        }
    },bol)
}

4. 描述事件冒泡的流程

const btn = document.getElementById('btn');
btn.addEventListener('click',function(e){
    console.log('btn')//获取触发元素
},false)
const body = document.getElementsByTagName('body')[0];
body.addEventListener('click',function(e){
    console.log('body')//获取触发元素
},false)

在点击btn后会先打印'btn'再打印'body',当父子元素或者祖子元素同时绑定相同的事件时,点击子元素会先执行子元素上绑定的事件再执行父元素上绑定的事件,就像水底的气泡一样向上浮动。

5. 无限下拉列表,如何监听每个图片的点击事件

利用事件代理

5.4

1. 实现一个简易版本的ajax

js

const url = './test.json'
  const xhr = new XMLHttpRequest();
  // 创建 Http 请求
  xhr.open('GET', url, true)
  // 设置状态监听函数
  xhr.onreadystatechange = function () {
    if (this.readyState === 4) {
      if (this.status === 200) {
        console.log(this.response)
      } else {
        console.log("error")
      }
    }
  }
  // 设置错误监听函数
  xhr.onerror = function () {
    reject(new Error(this.statusText));
  };
  // 设置响应的数据类型
  xhr.responseType = "json";
  // 设置请求头信息
  xhr.setRequestHeader("Accept", "application/json");
  //发送请求
  xhr.send(null)

promise

function getData(url) {
    return new Promise(function (resolve, reject) {
      const xhr = new XMLHttpRequest();
      // 创建 Http 请求
      xhr.open('GET', url, true)
      // 设置状态监听函数
      xhr.onreadystatechange = function () {
        if (this.readyState === 4) {
          if (this.status === 200) {
            console.log(this.response)
            resolve(this.response)
          } else {
            reject(new Error(this.statusText))
          }
        }
      }
      // 设置错误监听函数
      xhr.onerror = function () {
        reject(new Error(this.statusText));
      };
      // 设置响应的数据类型
      xhr.responseType = "json";
      // 设置请求头信息
      xhr.setRequestHeader("Accept", "application/json");
      //发送请求
      xhr.send(null)
    })
  }
  getData(url).then(getData(url))

如果是post请求,需要xhr.send(JSON.stringfy(postData)),postData是一个对象

5.5

1. 跨域的常用实现方式

  1. jsonp script可以跨过跨域
    服务器可以任意动态拼接数据返回
<script>
  window.callback = function (data) {
    console.log(data)
  }
</script>
<script src="http://localhost:5500/exercise/jsonp.js"></script>
jsonp.js
callback({
  name: 'hello'
})

callback不是固定的,可以修改

<script>
  window.new_callback = function (data) {
    console.log(data)
  }
</script>
<script src="http://localhost:5500/exercise/jsonp.js?callback=new_callback"></script>
jsonp.js
new_callback({
  name: 'hello'
})
  1. cors 服务端设置http header
response.setHeader('Access-Control-Allow-Origin:*');//这里第二个参数写具体,不建议写*
response.setHeader('Access-Control-Allow-Methods:OPTIONS, GET, POST');
response.setHeader('Access-Control-Allow-Headers:x-requested-with');
  1. 利用webpack等工具
    这里暂时不介绍,属于服务端的数据交互

2. 谈谈你对浏览器本地存储的理解

本地存储的方式主要有cookie、localStorage、sessionStorage三个

  • cookie
    1. 本身用于浏览器和server端通讯
    2. 被借用到用于本地存储
    3. 前端用document.cookie = "a = 10",不同名追加,同名覆盖
    4. 缺点:最大4kb;http请求会发送到服务端,增加请求数据量
  • localStorage
    1. 最大5M(浏览器不同可能存在区别)
    2. 使用方式:localStorage.setItem (key,val)、localStorage.getItem (key);
    3. 不会跟随http请求发送
    4. 永久存储,手动清空
  • sessionStorage
    1. 最大5M(浏览器不同可能存在区别)
    2. 使用方式:sessionStorage.setItem (key,val)、sessionStorage.getItem (key);
    3. 不会跟随http请求发送
    4. 只存在当前会话,浏览器关闭会清空

5.6

1.px、%、em、rem、vw、vh有什么区别?

  1. px:是基本单位,绝对单位。
  2. %:一般认为子元素的百分比相对于直接父元素,也有相对于自身的情况比如(border-radius、translate 等)
  3. em: 文本相对长度单位。相对于当前对象内文本的字体尺寸(如果设置font-size,则相对于父元素)。
  4. rem: rem 是 CSS3 新增的一个相对单位,相对于根元素(html 元素)的 font-size 的倍数。
  5. vw,wh:是与视图窗口有关的单位,vw 表示相对于视图窗口的宽度,vh 表示相对于视图窗口高度;
    • vmin:vw 和 vh 中的较小值
    • vmax:vw 和 vh 中的较大值;

2.什么时候不能使用箭头函数?

  • 箭头函数缺点
    1. 没有arguments
    2. 无法通过call、apply、bind等改变this指向
  • 不适应的情景
    1. 作为对象方法
      const obj = {
          name: "xiaoming",
          getName() {
            console.log(this.name)
          }
        }
        const obj1 = {
          name: "xiaoming",
          getName: () => {
            console.log(this.name)
          }
        }
        obj.getName()//"xiaoming"
        obj1.getName()//这里this指向window
      
    2. 原型方法
      const obj2 = {
          name: "dagang"
        }
        const obj3 = {
          name: "dagang"
        }
        Object.prototype.getName1 = function () {
          console.log(this.name)
        }
        Object.prototype.getName2 = () => {
          console.log(this.name)
        }
        obj2.getName1();//"dagang"
        obj3.getName2()//这里this指向window
      
    3. 构造函数
      function Person(name) {
          this.name = name;
          console.log(this.name)
        }
      
        const Fn = (name) => {
          this.name = name;
          console.log(this.name)
        }
      
        const p = new Person("xiaohong")//"xiaohong"
        const p1 = new Fn('xiaohong')//Uncaught TypeError: Fn is not a constructor
      
    4. 动态上下文中的回调函数
      比如点击事件中的回调函数,如果使用this,那么如果使用箭头函数,其this会指向window
    5. Vue生命周期和method 其中的this会指向window,而不会指向vue实例

总结
箭头函数不能使用的情景,大部分是因为this的指向问题

5.7

1. 移动端H5点击有300ms延迟,该如何解决

tips:这个算是历史遗留问题,现在大部分浏览器都是双指缩放,现代浏览器(设置content="width=device-width")已经不在存在300ms的延迟

最直接的办法就是使用fastClick库
原理: touchend和touchstart事件会先于click触发

2. script标签的defer和async有什么区别

image.png tips:这里默认将script标签放在body中部,这样其上下方均存在DOM元素要加载 script标签对应的js文件会存在两步操作,加载和执行。

  1. 文档遇到不带defer和async的script标签时,会立即停止对后续文档的加载,并立即加载script标签所指向的文件,加载完成后立即执行文件内容,执行完毕后继续加载后续的文档
  2. 带有defer属性:
    1. 文档遇到带有defer属性的script标签,会继续加载后续文档,同时加载script标签所指向的文件A,待文档加载完毕后(DOMContentLoaded事件触发之前)在执行文件A
    2. 存在多个带有defer属性的script标签,会并行加载,但最后的执行是按照顺序进行的
  3. 带有async属性:
    1. 文档遇到带有async属性的script标签,会继续加载后续文档B,同时加载script标签所指向的文件A,当加载完毕后,如果文档B没有加载完毕,则会停止B的加载,待A执行完毕后再继续加载B
    2. 存在多个带有defer属性的script标签,会并行加载,但最后的执行无法保证按照顺序进行的

5.8

1. offsetHeight、scrollHeight、clientHeight区别?

  • offsetHeight = content + padidng + border
  • clientHeight = content + padding
  • scrollHeight = padding + 实际内容尺寸

2. HTTP跨域时为何要发送options请求

  1. options请求,是跨域请求之前的预检查
  2. 浏览器自行发起,无需我们干预
  3. 不会影响实际功能

5.9

1. 重绘和重排有什么区别?

  • 重绘repaint
    元素外观变化,颜色、背景等
    元素尺寸、位置不变,不会影响其他元素的位置

  • 重排reflow
    重新计算尺寸和布局,可能会影响其他元素的位置
    改变元素高度、宽度等

  • 对比

    1. 重排比重绘消耗性能要大
  • 减少重排方法

    1. 集中修改样式或者切换class而不是设置高度再设置宽度
    2. 设置前可以先设置display为none,脱离文档流,改变万样式之后再出现
    3. 使用BFC特性,这样不影响其他元素
    4. 特定情况使用防抖截流
    5. createDocumentFragment批量操作DOM
    6. 优化动画,requestAnimationFrame不会发生重排

2. H5页面如何进行首页优化

  1. 路由懒加载
    • 适用于SPA不适用于MPA
    • 路由拆分,优先保证首页加载
  2. 服务端渲染SSR
  3. APP预取
    • 如果H5在webView中展示,可以使用APP预取
    • 用户访问H5页面前的入口页面时,App预加载H5页面首页内容
    • 用户进入H5页,直接从app中获取,显示速度很快
  4. 分页
    • 如果存在内容较多,可以分也展示
    • 默认显示第一页
    • 上滑加载更多(下一页)
  5. 图片懒加载lazyLoad
    • 针对详情页或者图片较多的页面
    • 先只展示文字,然后出发图片懒加载
    • 尽可能的设置好图片的尺寸或者占位,尽量重绘别重排
  6. Hybird
    • 提前将HTML、CSS、JS下载到appp内部
    • 在webView中使用file:// 协议加载页面
    • 在使用ajax获取内容并展示,可以结合APP预取

5.10

  1. 后端一次性返回10w条数据,你该如何渲染?
    首先要明确这个设计是不合理的,正常的话应该后端返回少量数据进行分页处理

    1. 使用自定义中间层
    2. 使用虚拟列表,比较麻烦,最好使用第三方成熟的lib,比如Vue-virtual-scroll-list,react-virtualiszed
  2. 手写一个getType函数,获取详细的数据类型

5.11

  1. instanceof原理是什么,请写代码表示
  2. 前端攻击手段有哪些,该如何预防

5.12

  1. 前端常用的设计模式和使用场景
  2. 观察者模式和发布订阅模式的区别

5.13

  1. 虚拟DOM(vdom)真的很快吗

5.14

  1. 从输入URL到网页显示的完整过程

5.15

  1. 如何实现网页多标签tab通讯