工作总结 7.24

关于可选链操作符、隐藏元素的几种方案差异、Promise 配合 async/await 使用。

可选链操作符

近期项目负责人做出了一个规范修正:

if (json.ret && json.data.length > 0) {
    const data = json.data
    .......
}

改为

const data = json?.ret && json?.data || []

这个语法被称为可选链操作符

ES2020 内容,Chrome>80 版本可用。

语法

obj?.prop
obj?.[expr]
arr?.[index]
func?.(args)

逻辑

可选链操作符 ?. 可以按照操作符之前的属性是否有效,链式读取对象的属性或者使整个对象链返回 undefined?. 运算符的作用与 . 运算符类似,不同之处在于,如果对象链上的引用是 nullish (null 或者 undefined),. 操作符会抛出一个错误,而 ?. 操作符则会按照短路计算的方式进行处理,返回 undefined。可选链操作符也可用于函数调用,如果操作符前的函数不存在,也将会返回 undefined

动机

当尝试调用一个可能不存在的方法时也可以使用可选链。这将是很有帮助的,比如,当使用一个 API 的方法可能不可用时,要么因为实现的版本问题要么因为当前用户的设备不支持该功能。

函数调用时如果被调用的方法不存在,使用可选链可以使表达式自动返回 undefined 而不是抛出一个异常。

参考链接:可选链操作符 - JavaScript | MDN

隐藏元素的几种方案差异

  1. display: none
    • DOM 结构:浏览器不会渲染 display 属性为 none 的元素,会让元素完全从渲染树中消失,渲染的时候不占据任何空间;
    • 事件监听:无法进行 DOM 事件监听,不能点击;
    • 性能:修改元素会造成文档回流(reflow 与 repaint),读屏器不会读取 display: none 元素内容,性能消耗较大;
    • 继承:是非继承属性,由于元素从渲染树消失,造成子孙节点消失,即使修改子孙节点属性子孙节点也无法显示,毕竟子类也不会被- 渲染;
    • 场景:显示出原来这里不存在的结构;
    • transition:transition 不支持 display。
  2. visibility: hidden
    • DOM 结构:不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见;
    • 事件监听:无法进行 DOM 事件监听,不能点击;
    • 性能:修改元素只会造成本元素的重绘(repaint),是重回操作,比回流操作性能高一些,性能消耗较少;读屏器读取 visibility: hidden 元素内容;
    • 继承:是继承属性,子孙节点消失是由于继承了 visibility: hidden,子元素可以通过设置 visibility: visible 来取消隐藏;
    • 场景:显示不会导致页面结构发生变动,不会撑开;
    • transition:transition 支持 visibility,visibility 会立即显示,隐藏时会延时。
  3. opacity: 0
    • DOM 结构:透明度为 100%,不会让元素从渲染树消失,渲染元素继续占据空间,只是内容不可见; 事件监听:可以进行 DOM 事件监听,可以点击;
    • 性能:提升为合成层,是重建图层,不和动画属性一起则不会产生 repaint(不脱离文档流,不会触发重绘),性能消耗较少;
    • 继承:会被子元素继承,且子元素并不能通过 opacity: 1 来取消隐藏;
    • 场景:可以跟 transition 搭配;
    • transition:transition 支持 opacity,opacity 可以延时显示和隐藏。
  4. rgba
    • background:rgba(R, G, B, 0),只是背景颜色透明,元素透明,依然占据空间。
    • background:rgba(R, G, B, 0)不会被子元素继承 依然能触发已经绑定的事件。
    • transition 有效。
  5. z-index: -1
    • 在元素当前 dom 脱离文档流(position:absolute)的前提下,设置 z-index 才起作用。
    • 设置 z-index:-1 本质是改变当前 dom 的层叠上下文,使器置于其他元素之下,达到被隐藏的目的。 部分重排,不影响其他图层布局 被其他元素遮挡部分,无法响应事件,即使上层元素设置了 pointer-events:none;也无法点击到(注:这个属性会被继承的)

打个比方:

  • display: none: 从这个世界消失了, 不存在了;
  • opacity: 0: 视觉上隐身了, 看不见, 可以触摸得到;
  • visibility: hidden: 视觉和物理上都隐身了, 看不见也摸不到, 但是存在的;

附加题:CSS 隐藏页面上的一个元素有哪几种方法?

  • display:none,visibility:hiden,opacity:0 这三种;
  • 设置 fixed 并设置足够大负距离的 left top 使其“隐藏”;
  • 用层叠关系 z-index 把元素叠在最底下使其“隐藏”;
  • 用 text-indent:-9999px 使其文字隐藏。

怎么把 Promise.then()中的数据拿出来?

因为我们没办法现在取出来自未来的东西,所以 js 中“一次异步 处处异步。”

现在要用 fetch 获取一段数据

原始写法:

getData(code) {
      fetch('http://urlurlurl', {
        method: 'POST',
        credentials: 'same-origin',
        headers: {
          'Accept': 'application/json, text/javascript, */*; q=0.01',
          'content-type': 'application/json'
        },
        body: JSON.stringify({ "type": code })
      }).then(res => {
        return res.json()
      }).then(json => {
        json // 只能在这里进行对json的操作。
      })
}

这个函数的问题是,只能在内部进行对获取到的 json 的操作,如果在外部直接返回只能得到 undefined。

getData(code) {
    let result = {};
      fetch('http://urlurlurl', {
        method: 'POST',
        credentials: 'same-origin',
        headers: {
          'Accept': 'application/json, text/javascript, */*; q=0.01',
          'content-type': 'application/json'
        },
        body: JSON.stringify({ "type": code })
      }).then(res => {
        return res.json()
      }).then(json => {
        result = json // 只能在这里进行对json的操作。
      })
      return result
}

let ans = this.getData(10) // ans is undefined.

尝试:

getData(code) {
    return new Promise((resolve)=>{
        fetch('http://urlurlurl', {
        method: 'POST',
        credentials: 'same-origin',
        headers: {
          'Accept': 'application/json, text/javascript, */*; q=0.01',
          'content-type': 'application/json'
        },
        body: JSON.stringify({ "type": code })
      }).then(res => {
        resolve(res.json())
      })
    })
}

// 此时调用写法为
this.getData(10).then(data=>{return data})

上面这个写法 data 还是在 then 里,没有在实质上解决问题。

引入 ES7 async/await:

async getData1(code) {
      // return new Promise((resolve)=>{
      let res = await fetch('http://urlurlurl', {
        method: 'POST',
        credentials: 'same-origin',
        headers: {
          'Accept': 'application/json, text/javascript, */*; q=0.01',
          'content-type': 'application/json'
        },
        body: JSON.stringify({ "type": code })
      })
      // let json = await res.json()
      return res.json()
}
// 获取数据
async function(){
    let res = await getData1(10)
}

此时 res 已经可以获取到了。

推荐方式

(以下示例代码在 node 平台上运行)

  • 写法 1
// bash: npm install node-fetch
const fetch = require('node-fetch')
const api = "https://wis.qq.com/weather/common?source=pc&province=%E4%B8%8A%E6%B5%B7&city=%E4%B8%8A%E6%B5%B7&county=%E5%BE%90%E6%B1%87&weather_type=observe";

// 方法非异步
function getData(_api){
    return new Promise((resolve)=>{
        fetch(_api).then(req=>{
            return req.json()
        }).then(json=>{
            resolve(json.data)
        })
    })
}
// 调用异步
async function init(){
    let data = await getData(api)
    console.log(data)
}

init();
  • 写法 2
// bash: npm install node-fetch
const fetch = require('node-fetch')
const api = "https://wis.qq.com/weather/common?source=pc&province=%E4%B8%8A%E6%B5%B7&city=%E4%B8%8A%E6%B5%B7&county=%E5%BE%90%E6%B1%87&weather_type=observe";

// 方法异步
async function getData(_api){
    let req = await fetch(_api)
    let json = await req.json()
    return json.data
}
// 调用非异步
function init(){
    getData(api).then(data=>{
        console.log(data)
    })
    // console.log(getData(api)) // 返回 Promise { <pending> }
}

init();

另附:使用 Fetch - Web API 接口参考