常用 JavaScript 速查

判断值是否为 null

  • 错误示范:
    • val == null,忽略了 val 为 undefined(要同时判断 null 和 undefined 时可使用本法)。
    • !val,忽略了数字 0、空字符串。

typeof null 的是 'object'。

  • 正确示范:
    • !val && typeof(val)!='undefined' && val!=0 && val != '',排除所有可能性剩下的的唯一结果就是真相
      • val === null,最简单的方法

有关 axios 的 put 传值问题

    axios.put('xxxxUrl', {
        //.....
        // 这里是 body
    }, {
        params: {
            // ...
            // 这里是 query
        }
    })

safari 和 chrome 兼容性问题

  • 问题描述:chrome 上样式正常,safari 13.05 版本样式错位。

  • 出现原因:safari 中 document.getElementsByClassname 时获得元素的顺序与 chrome 不同。(坑爹啊)

  • 解决方式:改为循环遍历元素,并将赋值(classname = )改为 remove。

分片上传时后端解析文件失败的问题

今天碰见和很奇葩的问题,又起了新的上传后发现,上传的内容后端无法解析,检查以后发现上传的是 arrayBuffer 类,猜测应该是之前使用 arrayBuffer 时被后端兼容了。转换成 blob 后,问题解决。

分片上传时线程启动太多无法响应 websocket 造成链接丢失的问题

  • 问题描述:

使用切片上传时,通过 websocket 长连接获得上传以及同步进度。而切片上传因为要启动多个线程进行并发上传,很容易造成上内存占满无法和长连接进行交互,造成长连接丢失。

  • 处理方式:

  • 缩短并发连接数量(Chrome 理论上线 255,适当缩短)

  • 使用 setTimeout 延迟连接建立

  • 拓展阅读:所谓的伪多线程

跨域问题

  • 问题描述:

使用 post 上传时,部分接口始终提示跨域(即便是配置了 axios 的跨域参数)不通过,错误如下:Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.

no-prototype-builtins 问题

  • 问题描述

主页莫名其妙的崩了,看了眼 Jenkins build 报错:error Do not access Object.prototype method 'hasOwnProperty' from target object no-prototype-builtins

  • 问题排查

查询发现报错的原因是 eslint 为了安全起见禁止目标对象直接调用 Object 原型方法,以避免原型链改变后 Object 对象也被改变。

  • 解决方法

调用 Object 的方法并改变 this 的指向:Object.prototype.hasOwnProperty.call(headers, header)

jade 模板转义字符的处理

  • 一个很坑爹的老项目,传递

  • 解决方法

jade 模板转义符处理方式123

elementPlus 导航 menu,打开一个收起其余展开的

     <el-menu
      :router="false"
      :default-active="currentMenu"
      active-text-color="00ff00"
      ref="menu"
        @open="handleOpen"
      @select="handleSelect"
      :uniqueOpened="true"  // <=== 就是这里
      >

差点自己写了一个展开收起,凸(艹皿艹 )...

使用 push 进入页面后,滚动条自动到顶

  router.afterEach((to,from,next) => { window.scrollTo(0,0); });

element 表单项 label 修改

     <el-form-item prop="license" :label-width="formLabelWidth">
          <div slot="label"> // <== 使用 slot 添加,自定义 label
            <el-popover placement="top-start"  trigger="hover" content="该应用若需要在新浪新闻推广,此项为必填项!">           
              <icon slot="reference" class="sf-tools-tools-info" />
            </el-popover>

            应用权限
          </div>
          <ad-permission
            v-model="editAppForm.permission"
            placeholder="请选择应用权限"
          ></ad-permission>
        </el-form-item>

艹,又差点自己轮了一个

vue dom 监听鼠标按下与弹起

     <p :class="!subItem.id ? 'cv-menu-button' : ''" @mousedown="testChange(1)" @mouseup="testChange(0)">

excel 兼容性问题

使用 xlsx 处理 excel,windows 和 mac 生成的 .xls 结构不同。mac 导出的只能在 mac 用,windows 导出的两边都不能用。为了兼容双系统,excel 使用 .xlsx 比较好。

Object.assign 问题

  • 问题描述:

今天测试报了个 bug,仔细研究以后发现造成 bug 的原因是表单项数据未重置。排查代码的时候发现,使用 v-model 绑定的属性 creative_license 明明已经重置为 ''(空字符串),但是表单项中依旧保留有上一次表单提交数据。

  • 解法1:

在表单项上增加 v-if(表单本身是个 dialog),使用 dialog 本身的 visible 属性即时加载/卸载表单项。

  • 解法2:

深入了解以后发现,未能重置的原因在这里。简单的说就是,下面这种写法无法触发 this.editAppForm.creative_license 的 set 方法,因而不会触发双向绑定。

   this.editAppForm = Object.assign(this.editAppForm, data.appInfo, {
          app_permission,
          creative_license,
          privacy_policy,
        })

以下这种写法可以有效避免:

          this.editAppForm = Object.assign({},this.editAppForm, data.appInfo, {
          app_permission,
          creative_license,
          privacy_policy,
        });

Vue 使用 clipboard 进行复制粘贴

  • 安装
  npm install clipboard --save
  • 在组件中引入
  import ClipboardJS from 'clipboard';
  • 具体实现代码 html
    <div>
      <span id="copycode"></span>
      <el-button class="sf-wd_cp_btn" @click="doCopy" type="text" data-clipboard-target="#copycode">复制</el-button>
    </div>
    

2 个关键点:1. 被复制的 html 需要有 id;2. 复制功能的按钮组件要有属性 data-clipboard-target 且值为 1 的 id。

js 代码

      const clipboard = new ClipboardJS('.sf-wd_cp_btn')
      clipboard.on('success', (e) => {
        Message.closeAll()
        Message({
          message: `已复制:${this.wmid}`,
          type: 'success',
          offset: 100
        })
        clipboard.destroy()
      })
    },

弹出提示后要主动调用clipboard.destroy()。否则的话因为组件未销毁、clipboard 多次创建,会出现多次提示(即执行多次复制操作)。 多次点击前需要使用 Message.closeAll() 清除之前的操作。

vue 在循环中使用 slot 的方法

废话不多说,直接从项目里挖代码gi

  • 父组件(slot 提供者)
  <sc-account-container :activeName="sources[0].name" v-model="sources" v-slot:default="{source}">  <==关键第一步
    <component v-model="source.data" :is="source.component"/>
  </sc-account-container>
  • 子组件(slot 接收者)
    <el-tabs
      v-model="activeName2"
    >
      <el-tab-pane 
        v-for="(source, idx) in value"
        :key="idx"
        :name="source.name"
        :label="source.label"
      >
        <slot
          :source="source"   <== 关键第二步
        />
      </el-tab-pane>
    </el-tabs>
  • 引申研究 vue2 与 vue3 中的 slot 比较。

比较对象的方法

算是爬坑记录。

今天在处理代码的时候,因为在 watch 中有 emit 双向绑定,所以多次出现数据重载的情况,于是决定比对象相同则不触发 emit。

一开始是使用每个 key 比较,但是消耗有点大。于是使用了一个偷鸡的方法,JSON.stringify(obj) === JSON.stringify(oldObj),于是暂时解决了。

结果在改变值的时候,偶尔还是会出现多次重载、甚至卡死的情况。多次定位后,发现即便是相同的对象 JSON.stringify 后也会因为 key 的位置有变动,结果得出了错误的结论。

坑爹。

获取窗口宽度

  mounted() {
    testPut()
    this.screenHeight = document.documentElement.clientHeight 
    const _this = this
    window.onresize = function () { // 定义窗口大小变更通知事件
      // _this.screenWidth = document.documentElement.clientWidth // 窗口宽度
      _this.screenHeight = document.documentElement.clientHeight // 窗口高度
    }
  }

js 格式化数字

  • 方法一:
  return (num/10000).toFixed(2)

方法一的问题是,如果 number/10000 的值是 2.10,那么格式化后的值是 2.10。

  • 方法二:完美解决小数问题
     formatNumber(num, precision = 2) {
      const numStr = num.toString()
      const index = numStr.indexOf('.')
      return Number( numStr.slice(0, index + precision) ) 
    }

一个快速复制对象的方法

const iValue = JSON.parse(JSON.stringify(this.value))

PS:此方法可以复制对象的值,但是无法复制对象的方法(function)。

直接在 html 中使用 vue

今天做个旧需求,需要改造一个老项目,做一个落地页。考虑为一个页面就安装 vue 使用 npm 有点不值当,因此决定直接在 html 中使用 vue。

  1. 在 html 中引用 vue 的 pro 版本: <script src="https://cdn.jsdelivr.net/npm/vue"></script>

我引用的是公司提供的特定版本,保证版本稳定性。

  1. 在 html 中增加 div, 并给予 id。
  <div id="app"></div>
  1. 新建 script 标签,编写 vue
   new Vue({
        template: `<div>
                    // 正常编写 html
                  </div>`,
        data:() => ({
            values: 123
        }),
        mounted() {

        },
        // ... 正常的 vue
    }).$mount('#app')

后记:npm 用多了以后,突然发现已经忘记 vue 的本质是一个 html 框架,只是对原有 html 进行改造和替换。

后记2:vue 2.0 和 vue 3.0 使用的是不同的构造方法,new Vue 要替换成 Vue.createApp$mount 要替换成 mount

节流

  function throttle(fn, time) {
      let _arguments = arguments
      let canRun = true 
      return function () {
          if (!canRun) return
          canRun = false
          setTimeout(() => {
              fn.call(this, _arguments)
              canRun = true
          }, time);
      }
  }

vue 的事件处理

今天处理了一个业务上的 bug,发现原本点击 checkbox 能展开下一级菜单的交互不见了。经过排查,发现是有人在上一级 div 中增加了 click.stop.prevent

Vue 中的事件处理

防抖组件库

格式化时间输出

    formateDate (date) {
      const time = (date == null ? new Date() : new Date(date))
      const month = (time.getMonth() + 1).toString().padStart(2, '0')
      const strDate = time.getDate().toString().padStart(2, '0')
      return `${time.getFullYear()}-${month}-${strDate}`
    },

element-plus message 保持只有一个 message

ElMessage.closeAll()

一个随手写的数据缓存

  async getInfo () {
    const info = localStorage.getItem('abcd') // <== 先从本地拿
    if (info) {
      return JSON.parse(info)
    }
    try {
      return (await this.fetchInfo()) || JSON.parse(INFO)  // <== 本地没有从服务端取
    } catch (_) {
      return JSON.parse(INFO) // 服务端没有用写死的
    }
  }

升级完 mac git 没了

xcode-select --install

判断图片是横图还是竖图

    isHZ() {
      if(this.url){
        const img = new Image();
        img.src = this.url;
        return img.width > img.height;
      }
      return false;
    },
  • 加入延时处理宽高为 0 的操蛋情况 ``` countImageStatus(url) {

    this.$nextTick( ()=> {
      const im = new Image();
      im.src = url;
      setTimeout(() => {
        this.imgWidth =im.width;
        this.imgHeight = im.height;
        if(this.imgWidth === 0 || this.imgHeight === 0) {
          setTimeout(() => {
            this.countImageStatus()
          }, 500)
        } 
      }, 100)
    
  })

}
## 压缩 json

json2str(json) { if(typeof json === 'number') { return json } try { let _json = (typeof json === "string") ? JSON.parse(json) : json; if (Array.isArray(_json)) { return [${_json.map((item) => { return this.json2str(item); })}]; } if(typeof _json === 'object') { const val = Object.keys(_json) .map((key) => "${key}": ${this.json2str(_json[key])}) .join(","); return {${val}}; } return "${${_json}.replaceAll("\"", "'")}" } catch (error) { return "${${json}.replaceAll("\"", "'")}" } },


## sock 一直报错的问题

> 今日份的奇葩,` http://127.0.0.1:8900/sockjs-node/info?t=xxxxx`一直报错,虽然不影响开发调试但是实在碍眼。

解决方法:[vue/cle3项目运行报错sockjs-node/info解决方案](https://cloud.tencent.com/developer/article/1489598)


## 傻逼的产物

> 脑残,写完以后才发现这 TMD 不就是 JSON.stringify 么?!

json2str(json) { if (typeof json === 'number') { return json; } try { const _json = typeof json === 'string' ? JSON.parse(json) : json; if (Array.isArray(_json)) { return [${_json.map((item) => { return this.json2str(item); })}]; } if (typeof _json === 'object') { const val = Object.keys(_json) .map((key) => "${key}": ${this.json2str(_json[key])}) .join(','); return {${val}}; } return "${${_json}.replace(/\"/g, "'")}"; } catch (error) { return "${${json}.replace(/\"/g, "'")}"; } } }


## 防止浏览器拦截 window.open
viewProfile() {
  const url =`https://hello.world/u/${this.option.id}`;
  this.newTab = window.open('about:blank');
  this.newTab.location.href = url
},


## 使用 compose 写的一个递归调用

> 参考的 redux,阅读源码万岁!

export class ProgrammaticParser { constructor() { this.programmatic = []; }

setProgrammatic(programmatic) { this.programmatic = programmatic; } compose(buildCreativeFunctions = []) { if ( !Array.isArray(buildCreativeFunctions) || buildCreativeFunctions.length === 0 ) { return args => args; }

if (buildCreativeFunctions.length === 1) {
  return buildCreativeFunctions[0];
}

return buildCreativeFunctions.reduce(
  (nextBuildCreativesFunction, currentBuildCreativesFunction) => (
    arrays = []
  ) => nextBuildCreativesFunction(currentBuildCreativesFunction(arrays))
);

}

applyMappings(programmatic, mappings = []) { const chains = mappings.map((mapping) => this.buildMappingFunction(mapping) ); return this.compose(chains)([programmatic]); }

create() { const programmatic = this.programmatic; const { creative_style: style } = programmatic || {}; if (!style) { return []; } const type = STYLE[style]; const mappings = PROGRAMMATIC[type];

return this.applyMappings(programmatic, mappings);

}

buildMappingFunction(mapping) { return (preCreatives) => { // target 为需要转化为普通创意时,普通创意的属性 // source 为待转化程序创意中,程序化创意需要转换的属性 const { target, source } = mapping || {}; if (!target || !source) { return []; } const temp = []; preCreatives.forEach((preCreative) => { const prossibleValues = preCreative[source] || []; // prossibleValues.forEach((prossibleValue) => { const newValue = { ...preCreative,

        [target]: prossibleValue,
      };
      temp.push(newValue);
    });
  });
  return  temp.length ? temp : preCreatives;
};

} }



## RAP2 中隐藏的一个很深的 bug

`src/components/editor/InterfaceEditor.tsx` 中 `handleDeleteMemoryProperty` 方法

handleDeleteMemoryProperty = (property: any, cb: any) => { const properties = [...this.state.properties] const index = properties.findIndex((item) => item.id === property.id) if (index >= 0) { properties.splice(index, 1)

  // 清除后代属性
  const deletedParentIds = [property.id]
  for (let index = 0; index < properties.length; index++) {
    if (deletedParentIds.indexOf(properties[index].parentId) !== -1) {
      deletedParentIds.push(properties[index].id)
      properties.splice(index--, 1)
      index = 0 // 强制从头开始查找,避免漏掉后代属性
    }
  }

  this.setState({ properties }, () => {
    cb && cb()
  })
}

}


- 在给 `index` 赋值 `0` 后,循环结束、**`index++`**。再次开始循环时,index 的值为 `1`。即对于 `deletedParentIds.indexOf(properties[index].parentId) !== -1`,等价于 `deletedParentIds.indexOf(properties[1].parentId) !== -1`,永远判断数组第二位元素。

- `index--` 为**先运行、后计算**,对于 `index === 1` 的情况 ` properties.splice(index--, 1)` 等价于 ` properties.splice(1, 1)`,依然会只判断数组第二位元素而忽略第一位元素。

建议修改为:
    if (deletedParentIds.indexOf(properties[index].parentId) !== -1) {
      deletedParentIds.push(properties[index].id)
      properties.splice(index, 1)
      index = -1 // 强制从头开始查找,避免漏掉后代属性
    }


### 驼峰与下划线互转

// 下划线转换驼峰 function toHump(name) { return name.replace(/_(\w)/g, function(all, letter){ return letter.toUpperCase(); }); } // 驼峰转换下划线 function toLine(name) { return name.replace(/([A-Z])/g,"_$1").toLowerCase(); }


## a 标签刷新

刷新


## `element-ui` 组件库中 `radio-group` 切换改变值的问题

> 所有编程中的问题都可以通过引入一个中间层来解决,如果没有解决则再引入一个

问题描述:对于 `radio-group` 里的 `@change` 事件,一个坑爹的前提是:该事件的触发是在值改变以后。因此如果想做一些切换前防手贱的操作 —— 比如防止误触而弹出确认框,该事件无法进行拦截。

而 `radio-group` 亦不支持 `before-change` 这样的骚操作,所以如何处理此类场景需要换个思路。

具体场景:假设有组件 A,其中有 RadioGroup 内包含 2 个 RadioBotton B 和 C,以及由 BC 分辨控制显示的 D 和 E 界面。要求选中 B 时展示 D,选中 C 时展示 E。切换 B 和 C 选项时弹出提示,而只有确认时才进行切换操作。而 D 和 E 中分别会有一些半持久化的数据,要求弹出提示并取消时保留这些数据,而确认切换时清掉数据。

代码 & 处理思路:

使用 innerStatus 表示 `RadioGroup` 的状态,使用 status 标记 B 和 C 的状态。

因此组件写法为:

// A.vue

render() { return

{ status === 'B' ? : }
}


此时 `innerStatus` 与 `status` 的值是互相独立的,即便 `@change` 时 `innerStatus` 的值发生了改变,而 `status` 的值是木有问题的。因此只要在 `@change` 中进行判断,如果用户确认则 `status =innerStatus`,否则 `innerStatus = status`。

完美结束。

## 查看本机链接 github 地址

[这里](http://www.github.com.ipaddress.com/)

## element-plus dialog 相关问题

今日份的坑爹问题,原本可以滚动的页面“偶现”滚动不能。经过排查发现,当使用 element 的 lock-scroll 的时候,启动 dialog 的同时会自动给最顶层的 html 增加 class `el-popup-parent--hidden`,唯一的属性就是 `overflow: hidden`。因此如果原本有表单项的 dialog 的惯常做法


会造成 dialog 消失后 `el-popup-parent--hidden` 依然挂在 html 标签的情况,造成原本可以滚动的页面无法滚动,影响体验。

解决方法是,重置表单的时候使用 form 的 reset 方法。

## false 与 undefined 不相等的问题

今日份的坑爹 bug,线上版本。

直接原因是因为 watch 中 `newVal !== oldVal` 时,newVal 的值为 `false` 而 oldVal 的值为 `undefined`。两者同为`非 true`却并不相同,造成了 watch 毫无必要的触发、对属性进行了二次修改。

处理方式:

所有 `true` `false` 比较类判断 `newVal === oldVal` 前面都应该加上双 `!`,写作 `!!newVal === !!oldVal`。

## 通过字体计算 div 的实际宽度

export function calculateTextWidth (text, font) { const canvas = calculateTextWidth.canvas || (calculateTextWidth.canvas = document.createElement('canvas')) const context = canvas.getContext('2d') context.font = font const metrics = context.measureText(text) return metrics.width }


## 今日份的坑爹问题

写了一个线上 bug,分片上传时计算出的 md5 值与后端计算出的 md5 值不匹配。

原因是在进行分片的时候,直接使用 file.size/10 当作每一片的长度,未进行取整计算。因此对于不能整除的 size 来说,会有很高的概率切丢一片。

解决办法是使用 `Math.ceil` 向上取整,保证切片长度。

## 读取 & 反解 xpath

//获取xpath function readXPath(element) { console.log(element.id)

if (element.id ) {//判断id属性,如果这个元素有id,则显 示//*[@id="xPath"]  形式内容
    return '//*[@id=\"' + element.id + '\"]';
}
//这里需要需要主要字符串转译问题,可参考js 动态生成html时字符串和变量转译(注意引号的作用)
if (element == document.body) {//递归到body处,结束递归
    return '/html/' + element.tagName.toLowerCase();
}
var ix = 1,//在nodelist中的位置,且每次点击初始化
     siblings = element.parentNode.childNodes;//同级的子元素

for (var i = 0, l = siblings.length; i < l; i++) {
    var sibling = siblings[i];
    //如果这个元素是siblings数组中的元素,则执行递归操作
    if (sibling == element) {
        return arguments.callee(element.parentNode) + '/' + element.tagName.toLowerCase() + '[' + (ix) + ']';
        //如果不符合,判断是否是element元素,并且是否是相同元素,如果是相同的就开始累加
    } else if (sibling.nodeType == 1 && sibling.tagName == element.tagName) {
        ix++;
    }
}

};

function x(xpath) { var result = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null); return result.iterateNext() }


## 如何用正则替换 http 为 https

export const urlConversion = (path) => { const reg = /^(https?:\/\/)([0-9a-z.]+)(:[0-9]+)?([/0-9a-z.]+)?(\?[0-9a-z&=]+)?(#[0-9-a-z]+)?/i; path = path.replace(reg, 'https://$2$3$4$5$6'); return path; };


## 今日份的 bug 解决

vue 2.x 需要配合 antd-vue 1.x,用 latest 会引用 2 或者 3,对应都是 vue3,会报错。

## 今日份的操蛋问题解决

执行 es-lint 的时候一直报错:

✖ vue-cli-service lint: ERROR TypeError: Cannot read property 'range' of null


解决方式:[修改 eslint.js ](https://github.com/babel/babel-eslint/issues/681)

## 今日份的踩坑记录

对于和权限相关的菜单,调整层级以及增删菜单项时一定要先确认权限关系。有可能原本的有权限的二级菜单挪动到无权限的一级菜单后,二级菜单权限会一并丢失。

确认删除的权限是否与多数人关联。

确认删除权限的逻辑是硬删除还是软删除。

没事少动数据库,多备份。

实在要改时,提前通知后端备份数据库。

离删库到跑路只有一步之遥。

## 今日份的踩坑之旅

使用 el-upload 的时候,在 `before-upload` 返回时,要么返回 false,要么返回 Promise 并用 reject 返回错误, async/await 并返回 false 是无法阻止继续进行上传的。

## 8.8 踩坑

控制台一直在报错:

Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "placement" ```

错误原因:element 版本太高了。

9。19

今日份的傻逼问题:为 element-plus 设置 table 宽度后,在 windows 机器上不起作用。 解决方式:设置宽度的同时设置 min-width。

results matching ""

    No results matching ""