常用的前端JS方法封装

78 阅读7分钟

DOM操作

找元素的第N级父元素

/**
 * 找元素的第N级父元素
 * @param {HTMLElement} ele DOM元素
 * @param {Number} n 要查找几级 默认1级
 * @returns {HTMLElement}
 */
export function parents (ele, n = 1) {
  while (ele && n) {
    ele = ele.parentElement ? ele.parentElement : ele.parentNode
    n--
  }
  return ele
}

获取指定元素距离屏幕的距离

/**
 * 获取指定元素距离屏幕的距离
 * @param {HTMLElement} element
 * @returns {Boolean|Object}
 *   Boolean: 返回false表示DOM元素不正确
 *   Object: {top: 距离顶部距离, left: 距离左侧距离, offsetWidth: 元素宽度, offsetHeight: 元素高度}
 */
export function getAbsoluteLocation (element) {
  if (arguments.length != 1 || element == null) {
    return false
  }
  let offsetTop = element.offsetTop
  let offsetLeft = element.offsetLeft
  let offsetWidth = element.offsetWidth
  let offsetHeight = element.offsetHeight
  while (element = element.offsetParent) {
    offsetTop += element.offsetTop
    offsetLeft += element.offsetLeft
  }
  return {
    top: offsetTop,
    left: offsetLeft,
    offsetWidth: offsetWidth,
    offsetHeight: offsetHeight
  }
}

给定页面上的DOM元素,将访问元素本身及其所有后代(不仅仅是它的直接子元素)

/**
 * 给定页面上的DOM元素,将访问元素本身及其所有后代(不仅仅是它的直接子元素)
 * 对于每个访问的元素,函数将元素传递给提供的回调函数
 * @param {HTMLElement} element
 * @param {Function} callback 每次迭代的回调函数 参数为当前迭代的DOM元素
 * @returns void
 */
export function traverse (element, callback) {
  callback(element)
  var list = element.children
  for (var i = 0; i < list.length; i++) {
    traverse(list[i], callback)
  }
}

平滑的返回到页面顶端

/**
 * 平滑的返回到页面顶端
 * @returns void
 */
export function goTop () {
  let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
  scrollTop = scrollTop / 1.1

  if (scrollTop > 0) {
    window.scrollTo(0, scrollTop)
    requestAnimFrame(goTop)
  } else {
    scrollTop = 0
    window.scrollTo(0, scrollTop)
  }
}

字符串相关

检测是否为JSON字符串

/**
 * 检测是否为JSON字符串
 * @param {String} str 要检测的字符串
 * @returns {Boolean}
 */
export function isJSON (str) {
  if (typeof str === 'string') {
    try {
      const obj = JSON.parse(str)
      return !!(typeof obj === 'object' && obj)
    } catch (e) {
      return false
    }
  }
  return false
}

验证指定的内容是否包含emoji表情符

/**
 * 验证指定的内容是否包含emoji表情符
 * @param {String} str
 * @returns {Boolean}
 */
export function emoji (str) {
  return /[^\u0020-\u007E\u00A0-\u00BE\u2E80-\uA4CF\uF900-\uFAFF\uFE30-\uFE4F\uFF00-\uFFEF\u0080-\u009F\u2000-\u201f\u2026\u2022\u20ac]/.test(str)
}

检测字符串是否是回文

/**
 * 检测字符串是否是回文
 * @param {String} str
 * @returns {Boolean}
 */
export function isPalina (str) {
  if (Object.prototype.toString.call(str) !== '[object String]') {
    return false
  }
  let len = str.length
  for (var i = 0; i < len / 2; i++) {
    if (str[i] != str[len - 1 - i]) {
      return false
    }
  }
  return true
}

脱敏函数

/**
 * 脱敏函数
 * @param {String|Number} value 要脱敏的信息
 * @param {Number} index 开始脱敏的起始下标 默认3(索引从0开始)
 * @param {Number} length 要脱敏几个数字 默认4
 * @param {String} symbol 要替换的字符 默认*
 * @return {String} 脱敏后的信息
 */
export function desensitization (value, index = 3, length = 4, symbol = '*') {
  if (value) {
    value = value.toString().split('')
    let _symbol = []
    let i = length
    while (i--) {_symbol.push(symbol)}
    Array.prototype.splice.apply(value, [index, length, ..._symbol])
    return value.join('')
  } else {
    return ''
  }
}

查询字符串转对象

/**
 * 查询字符串转对象
 * @param {String} queryString
 * @returns {Object}
 */
export function queryStringToObject (queryString) {
  let a = queryString.split(/[&=]/g)
  let result = {}
  while (a.length) {
    result[a.shift()] = a.shift()
  }
  return result
}

对象转查询字符串

/**
 * 对象转查询字符串
 * @param {Object} object
 * @returns {String}
 */
export function objectToQueryString (object) {
  let str = ''
  for (let i in object) {
    str = str + (str != '' ? '&' : '') + i + '=' + encodeURIComponent(object[i])
  }
  return str
}

过滤html代码(把<>转换)

/**
 * 过滤html代码(把<>转换)
 * @param {String} str 要过滤的字符串
 * @return {String} 过滤后的字符串
 */
export function filterTag(str) { // 过滤html代码(把<>转换)
  str = str.replace(/&/ig, '&')
  str = str.replace(/</ig, '<')
  str = str.replace(/>/ig, '>')
  str = str.replace(' ', ' ')
  return str
}

对象相关

检测是否为空对象({})

/**
 * 检测是否为空对象({})
 * @param {Object} obj 要检测的对象
 * @returns {Boolean}
 */
export function isEmptyObj (obj) {
  if (typeof obj === 'object' && !Array.isArray(obj) && obj !== null) {
    return Object.keys(obj).length === 0
  }
  return false
}

深度合并对象

/**
 * 深度合并对象
 * @param {Objec} obj1 要进行深度合并的对象1
 * @param {Objec} obj2 要进行深度合并的对象2
 * @returns {Objec} 深度合并后的新对象
 */
export function deepMerge (obj1, obj2) {
  for (let key in obj2) {
    // 如果target(也就是obj1[key])存在,且是对象的话再去调用deepMerge,否则就是obj1[key]里面没这个对象,需要与obj2[key]合并
    obj1[key] = (obj1[key] && isObj(obj1[key])) ? deepMerge(obj1[key], obj2[key]) : obj1[key] = obj2[key]
  }
  return obj1
}

深度拷贝

/**
 * 深度拷贝
 * ※ 为什么不用 JSON.parse(JSON.stringify(...)) 的方式实现?
 * ※ JSON.parse 方式无法拷贝函数
 * @param {Object} data 要拷贝的对象
 * @returns {Object} 拷贝出的新对象
 */
export function deepCopy (data) {
  const t = typeOf(data)
  let o

  if (t === 'array') {
    o = []
  } else if (t === 'object') {
    o = {}
  } else {
    return data
  }

  if (t === 'array') {
    for (let i = 0; i < data.length; i++) {
      o.push(deepCopy(data[i]))
    }
  } else if (t === 'object') {
    for (let i in data) {
      o[i] = deepCopy(data[i])
    }
  }
  return o
}

时间相关

格式化时间函数

/**
 * 格式化时间函数
 * @param {String} fmt 格式化日期的格式字符串 yyyy-MM-dd hh:mm:ss:S q
 * @param {String|Date} '2017-01-11 12:12:55' 时间字符串 | '2017/01/11 12:12:55' 时间字符串 | 时间戳 秒、毫秒 | new Data() 时间对象
 * @returns {String} 2017-12-09 12:22:03:233 4
 */
export function dateFtt (fmt, date) {
  // 检测是否是时间戳
  if (!date) {
    return ''
  } else if (/^\d{13}$/.test(date)) { // 毫秒
    // 转成整数
    date = parseInt(date)
  } else if (/^\d{10}$/.test(date)) { // 秒
    // 转成整数,乘以1000用以转成毫秒
    date = parseInt(date) * 1000
  } else if (isStr(date)) {
    // 将时间字符串中的-转换为/,因为IOS不支持-格式的时间字符串
    date = date.replace(/-/g, '/')
  }

  date = new Date(date)

  var o = {
    'M+': date.getMonth() + 1, // 月份
    'd+': date.getDate(), // 日
    'h+': date.getHours(), // 小时
    'm+': date.getMinutes(), // 分
    's+': date.getSeconds(), // 秒
    'S': date.getMilliseconds(), // 毫秒
    'q+': Math.floor((date.getMonth() + 3) / 3) // 季度
  }
  if (/(y+)/.test(fmt)) {
    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
  }
  for (var k in o) {
    if (new RegExp('(' + k + ')').test(fmt)) {
      fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
    }
  }
  return fmt
}

数组相关

判断两个数组完全相等

/**
 * 判断两个数组完全相等
 * @param {Array} a 数组a
 * @param {Array} b 数组b
 * @returns {Boolean}
 */
export function arrayEquals (a, b) {
  // see: https://stackoverflow.com/questions/3115982/how-to-check-if-two-arrays-are-equal-with-javascript
  if (a === b) return true
  if (!(a instanceof Array)) return false
  if (!(b instanceof Array)) return false
  if (a.length !== b.length) return false
  for (let i = 0; i !== a.length; ++i) {
    if (a[i] !== b[i]) return false
  }
  return true
}

其他

/**
 * 将阿拉伯数字翻译成中文的大写数字
 * @param {Number} num
 * @return {String}
 */
export function numberToChinese (num) {
  let AA = new Array('零', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十')
  let BB = new Array('', '十', '百', '仟', '萬', '億', '点', '')
  let a = ('' + num).replace(/(^0*)/g, '').split('.')
  let k = 0
  let re = ''
  for (let i = a[0].length - 1; i >= 0; i--) {
    switch (k) {
      case 0:
        re = BB[7] + re
        break
      case 4:
        if (!new RegExp('0{4}//d{' + (a[0].length - i - 1) + '}$').test(a[0])) {
          re = BB[4] + re
        }
        break
      case 8:
        re = BB[5] + re
        BB[7] = BB[5]
        k = 0
        break
    }
    if (k % 4 === 2 && a[0].charAt(i + 2) !== 0 && a[0].charAt(i + 1) === 0) {
      re = AA[0] + re
    }
    if (a[0].charAt(i) !== 0) {
      re = AA[a[0].charAt(i)] + BB[k % 4] + re
    }
    k++
  }
  if (a.length > 1) { // 加上小数部分(如果有小数部分)
    re += BB[6]
    for (let i = 0; i < a[1].length; i++) {
      re += AA[a[1].charAt(i)]
    }
  }
  if (re === '一十') {
    re = '十'
  }
  if (re.match(/^一/) && re.length === 3) {
    re = re.replace('一', '')
  }
  return re
}

/**
 * 下载图片
 * @param {String} imgSrc 要下载的图片地址
 * @param {String} name 下载后的图片名称
 * @returns void
 */
export function downLoadImg (imgSrc, name) {
  let image = new Image()
  image.setAttribute("crossOrigin", "anonymous") // 解决跨域 Canvas 污染问题
  image.onload = function() {
    let canvas = document.createElement("canvas")
    canvas.width = image.width
    canvas.height = image.height
    let context = canvas.getContext("2d")
    context.drawImage(image, 0, 0, image.width, image.height)
    let url = canvas.toDataURL("image/png") //得到图片的base64编码数据
    let a = document.createElement("a") // 生成一个a元素
    let event = new MouseEvent("click") // 创建一个单击事件
    a.download = name ? name + '.png' : "photo.png" // 设置图片名称
    a.href = url // 将生成的URL设置为a.href属性
    a.dispatchEvent(event) // 触发a的单击事件
  }
  image.src = imgSrc
}

/**
 * 将二进制数据下载到本地
 * @param {Object} res 接口返回结果
 * @param {String} fileName 文件名
 */
export function downloadFile (res = { data: '', headers: {} }, fileName = '') {
  let blob = new Blob([res.data])
  fileName = fileName || res.headers['content-disposition'].split('=')[1]
  let alink = document.createElement('a')
  alink.download = decodeURIComponent(fileName)
  alink.style.display = 'none'
  alink.href = URL.createObjectURL(blob) // 这里是将文件流转化为一个文件地址
  document.body.appendChild(alink)
  alink.click()
  URL.revokeObjectURL(alink.href) // 释放URL对象
  document.body.removeChild(alink)
}

/**
 * UUID通用唯一识别码(Universally Unique Identifier)
 * @param {String} split 分割符
 * @return {String}
 */
export function uuid (split = '-') {
  let s = []
  let hexDigits = '0123456789abcdef'
  let num = split ? 36 : 32
  for (let i = 0; i < num; i++) {
    s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
  }
  s[14] = '4' // bits 12-15 of the time_hi_and_version field to 0010
  s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1) // bits 6-7 of the clock_seq_hi_and_reserved to 01
  if (split) {
    s[8] = s[13] = s[18] = s[23] = split
  }
  let uuid = s.join('')
  return uuid
}

/**
 * 获取浏览器滚动条的宽度
 * @returns {Number}
 */
export function getScrollWidth () {
  let outer = document.createElement('div')
  outer.className = 'el-scrollbar__wrap'
  outer.style.visibility = 'hidden'
  outer.style.width = '100px'
  outer.style.position = 'absolute'
  outer.style.top = '-9999px'
  document.body.appendChild(outer)

  let widthNoScroll = outer.offsetWidth
  outer.style.overflow = 'scroll'

  let inner = document.createElement('div')
  inner.style.width = '100%'
  outer.appendChild(inner)

  let widthWithScroll = inner.offsetWidth
  outer.parentNode.removeChild(outer)

  return widthNoScroll - widthWithScroll
}

/**
 * 打印指定区域内容
 * @param {HTMLElement} printContainer
 * @returns void
 */
export function print (printContainer) {
  var iframe = document.createElement('iframe')
  iframe.setAttribute('style', 'position: absolute; left: -99999px;')
  document.body.appendChild(iframe)

  var win = iframe.contentWindow || iframe.contentDocument
  var doc = iframe.contentDocument || iframe.contentWindow.document
  doc.body.onload = function () {
    win.print()
  }

  var headHtml = document.head.innerHTML
  doc.head.innerHTML = headHtml
  doc.body.innerHTML = document.getElementById(printContainer).outerHTML
}

/**
 * 获取当前url中查询参数的值
 * @param {String} name 要查询的key名
 * @returns {String}
 */
export function getQueryString (name) {
  var url = document.location.toString()
  var arrObj = url.split('?')

  if (arrObj.length > 1) {
    var arrPara = arrObj[1].split('&')
    var arr
    for (var i = 0; i < arrPara.length; i++) {
      arr = arrPara[i].split('=')
      if (arr != null && arr[0] === name) {
        return decodeURIComponent(arr[1])
      }
    }
    return ''
  } else {
    return ''
  }
}

/**
 * 根据身份证号,获取生日、性别、年龄、地址码
 * @param {String} val 身份证号
 * @returns {Boolean|Object}
 *    Boolean: 返回false表示身份证不正确
 *    Object: {birthday: 生日, sexName: 性别, sexCode: 性别码, age: 年龄(周岁), areaCode: 地址码}
 */
export function idCardNo (val) {
  if (idCard(val)) {
    let birthdayno, birthdaytemp, sexno
    if (val.length === 18) {
      birthdayno = val.substring(6, 14)
      sexno = val.substring(16, 17)
    } else if (val.length === 15) {
      birthdaytemp = val.substring(6, 12)
      birthdayno = '19' + birthdaytemp
      sexno = val.substring(14, 15)
    }
    let tempid = sexno % 2
    let sexName, sexCode
    if (tempid === 0) {
      sexName = '女'
      sexCode = '2'
    } else {
      sexName = '男'
      sexCode = '1'
    }
    let birthday = birthdayno.substring(0, 4) + '-' + birthdayno.substring(4, 6) + '-' + birthdayno.substring(6, 8)
    let areaCode = val.substring(0, 6)
    return {
      birthday,
      sexName,
      sexCode,
      age: jsGetAge(birthday),
      nominalAge: getNominalAge(birthdayno),
      areaCode
    }
  }
  return false
}

/**
 * 设备判断:android、ios、web
 * @returns {String} android || ios || web
 */
export function isDevice () {
  let u = navigator.userAgent
  let isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1 // g
  let isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) // ios终端
  if (isAndroid) {
    return 'android'
  }
  if (isIOS) {
    return 'ios'
  }
  return 'web'
}

/**
 * 获取IOS版本号
 * @returns {Number} IOS版本号
 */
export function iosVersion () {
  let u = navigator.userAgent.toLowerCase()
  let v = u.match(/cpu iphone os (.*?) like mac os/)
  return parseFloat(v[1].replace(/_/g, '.'))
}

/**
 * 验证身份证号是否合法
 * 兼容脱敏格式的数据
 *  脱敏规则:显示前1位和后1位,其余*显示,适用于15位和18位身份证。
 * 
 * 中国居民身份证号码编码规则
 * 第一、二位表示省(自治区、直辖市、特别行政区)。
 * 第三、四位表示市(地级市、自治州、盟及国家直辖市所属市辖区和县的汇总码)。其中,01-20,51-70表示省直辖市;21-50表示地区(自治州、盟)。
 * 第五、六位表示县(市辖区、县级市、旗)。01-18表示市辖区或地区(自治州、盟)辖县级市;21-80表示县(旗);81-99表示省直辖县级市。
 * 第七、十四位表示出生年月日(单数字月日左侧用0补齐)。其中年份用四位数字表示,年、月、日之间不用分隔符。例如:1981年05月11日就用19810511表示。
 * 第十五、十七位表示顺序码。对同地区、同年、月、日出生的人员编定的顺序号。其中第十七位奇数分给男性,偶数分给女性。
 * 第十八位表示校验码。作为尾号的校验码,是由号码编制单位按统一的公式计算出来的,校验码如果出现数字10,就用X来代替,详情参考下方计算方法。
 * 其中第一代身份证号码为15位。年份两位数字表示,没有校验码。
 * 前六位详情请参考省市县地区代码
 * X是罗马字符表示数字10,罗马字符(1-12):Ⅰ、Ⅱ、Ⅲ、Ⅳ、Ⅴ、Ⅵ、Ⅶ、Ⅷ、Ⅸ、Ⅹ、Ⅺ、Ⅻ……,详情请参考罗马字符
 * 
 * ============================================================================================================================================
 * 
 * 中国居民身份证校验码算法
 * 步骤如下:
 * 将身份证号码前面的17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7-9-10-5-8-4-2-1-6-3-7-9-10-5-8-4-2。
 * 将这17位数字和系数相乘的结果相加。
 * 用加出来和除以11,取余数。
 * 余数只可能有0-1-2-3-4-5-6-7-8-9-10这11个数字。其分别对应的最后一位身份证的号码为1-0-X-9-8-7-6-5-4-3-2。
 * 通过上面计算得知如果余数是3,第18位的校验码就是9。如果余数是2那么对应的校验码就是X,X实际是罗马数字10。
 * 例如:某男性的身份证号码为【53010219200508011x】, 我们看看这个身份证是不是合法的身份证。首先我们得出前17位的乘积和【(5*7)+(3*9)+(0*10)+(1*5)+(0*8)+(2*4)+(1*2)+(9*1)+(2*6)+(0*3)+(0*7)+(5*9)+(0*10)+(8*5)+(0*8)+(1*4)+(1*2)】是189,然后用189除以11得出的结果是189/11=17----2,也就是说其余数是2。最后通过对应规则就可以知道余数2对应的检验码是X。所以,可以判定这是一个正确的身份证号码。
 * 
 * @param {String} idCard
 * @returns {Boolean}
 */
export function idCard (idCard) {
  // 转字符串
  idCard += ''
  // 最后一位的x统一转为大写
  idCard = idCard.replace(/x/i, 'X')

  if (/\*/.test(idCard)) {
    return ((idCard.length === 15 || idCard.length === 18) && /^\d\*+[\dxX]$/.test(idCard))
  } else {
    // 前17位每项的系数
    let coefficient = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
    // 除11取余的结果对应的校验位(最后一位)的值
    let checkDigitMap = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
    if (/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)/.test(idCard)) {
      if (idCard.length == 18) {
        let sum = 0
        for (let i = 0; i < 17; i++) {
          sum += idCard[i] * coefficient[i]
        }
        return checkDigitMap[sum % 11] == idCard[17]
      }
      return true
    } else {
      return false
    }
  }
}

/**
 * requestAnimationFrame兼容写法
 * @param {Function} callback 回调函数
 */
export let requestAnimFrame = (function () {
  try {
    return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    function (callback) {
      window.setTimeout(callback, 1000 / 60)
    }
  } catch (e) {}
})()

/**
 * cancelAnimFrame兼容写法
 * @param {Function} callback 回调函数
 */
export let cancelAnimFrame = (function () {
  try {
    return window.cancelAnimationFrame ||
    window.webkitCancelAnimationFrame ||
    window.mozCancelAnimationFrame ||
    function (id) {
      window.clearTimeout(id)
    }
  } catch (e) {}
})()

/**
 * 判断软件盘是否弹出
 * @param {Function} callback 弹出或关闭软键盘后的回调
 * @param {Boolean} recovery 键盘收起后,是否恢复页面位置
 * @param {String} type 要监听的系统 默认安卓和IOS都监听 也可以设置 'android' 或 'ios'
 * @returns {Function} 返回注销函数句柄 一般在vue组件的销毁生命周期中调用
 */
export function keyboardIsOpen (callback = function () {}, recovery = false, type = 'all') {
  let windowHeight = window.innerHeight
  let flag = false
  let position = 0
  if (typeof callback == 'boolean') {
    recovery = callback
    callback = function () {}
  }

  function getScrollTop () {
    let scrollTop = 0
    if (document.documentElement && document.documentElement.scrollTop) {
      scrollTop = document.documentElement.scrollTop
    } else if (document.body) {
      scrollTop = document.body.scrollTop
    }
    return scrollTop
  }

  function androidHandler () {
    let oldWindowHeight = windowHeight
    windowHeight = window.innerHeight
    if (windowHeight < oldWindowHeight) {
      flag = true
    } else {
      flag = false
    }
    callback && callback(flag)
  }

  function iosFocusinHandler (event) {
    let tagName = event.target.tagName.toLowerCase()
    if (tagName == 'input' || tagName == 'textarea' || event.target.isContentEditable) {
      flag = true
      if (recovery) {
        position = getScrollTop()
      }
      callback && callback(flag)
    }
  }

  function iosFocusoutHandler () {
    flag = false
    setTimeout(() => {
      if (recovery) {
        window.scrollTo(0, position)
      }
      callback && callback(flag)
    }, 0)
  }

  // 注册安卓处理事件
  function registerAndroidEvent() {
    window.addEventListener('resize', androidHandler, false)
  }

  // 注册ios处理事件
  function registerIosEvent() {
    document.body.addEventListener('focusin', iosFocusinHandler, false)
    document.body.addEventListener('focusout', iosFocusoutHandler, false)
  }

  // 销毁已注册的事件
  function destroyEventListener () {
    window.removeEventListener('resize', androidHandler, false)
    document.body.removeEventListener('focusin', iosFocusinHandler, false)
    document.body.removeEventListener('focusout', iosFocusoutHandler, false)
  }

  // 先销毁旧的处理程序
  destroyEventListener()

  if (type == 'all') {
    if (os() == 'android') {
      registerAndroidEvent()
    } else if (os() == 'ios') {
      registerIosEvent()
    }
  } else if (os() == 'android' && type == 'android') {
    registerAndroidEvent()
  } else if (os() == 'ios' && type == 'ios') {
    registerIosEvent()
  }

  // 返回注销函数句柄
  return destroyEventListener
}