js笔记

forEach map 对原函数的影响

arr1 = [{a:1},{a:2},{a:3},{a:4}];
arr2 = [1,2,3,4];

// forEach没有返回值
arr1.forEach(i=>{    // 引用类型 整个赋值不变
    i=i.a   // [ { a: 1 }, { a: 2 }, { a: 3 }, { a: 4 } ]
})
arr1.forEach(i=>{    // 引用类型
    i.a=1   // [ { a: 1 }, { a: 1 }, { a: 1 }, { a: 1 } ]
})
arr2.forEach(i=>{   // 值类型 原数组不改变
    i=1;    // [ 1, 2, 3, 4 ]
}) 
// map相当于映射,返回一个新函数 此处探讨对原函数的改变
arr1.map(i=>i=i.a );    // 引用类型 
// [ { a: 1 }, { a: 2 }, { a: 3 }, { a: 4 } ]  整个赋值不变
arr1.map(i=>i=i.a = 1 );    // 引用类型 
// [ { a: 1 }, { a: 1 }, { a: 1 }, { a: 1 } ]  改变
arr2.map(i=>i=1);   // 值类型 原数组不改变

# 可以验证是浅拷贝

forEach 跳出当此循环 和 跳出所有循环

list.forEach(item => {
    if(item.xxx === 0) return; // 跳出当此循环
})

try {
    list.forEach(item => {
        if(xxx) throw new Error(); // 跳出所有循环
    })
} catch(e) {}
# 跳出所有循环 某些情况下可以使用 every 
list.every(item => {
    return item === xxx; // 当判断为false 返回false 并跳出循环
})

判断是不是空对象 & 浅/深层比较

# 错误用法
{}=={} {}==={} Object.is(obj, {});
// 永远是 false 引用类型比较 只有内存地址也相同才为 true
// == 相比 === 会自动类型转换,比如string和number比较

# 正确用法
Obejct.keys(obj).length === 0

# 同理 判断两个对象是否相等 除非指向同一个实例,请使用浅层比较 或 深层比较
// 浅层比较
function shallowEqual(object1, object2) {
  const keys1 = Object.keys(object1);
  const keys2 = Object.keys(object2);

  if (keys1.length !== keys2.length) {
    return false;
  }

  for (let index = 0; index < keys1.length; index++) {
    const val1 = object1[keys1[index]];
    const val2 = object2[keys2[index]];
    if (val1 !== val2) {
      return false;
    }
  }

  return true;
}
// 深层比较
function deepEqual(obj1, obj2) {
    const keys1 = Object.keys(obj1);
    const keys2 = Object.keys(obj2);

    if (keys1.length !== keys2.length) {
        return false;
    }

    for (let index = 0; index < keys1.length; index++) {
        const val1 = obj1[keys1[index]];
        const val2 = obj2[keys2[index]];
        if (isObject(val1) && isObject(val2)) {
            if(!deepEqual(val1, val2)) return false;
        } else if (val1 !== val2) {
            return false;
        }
    }
    return true;
}

function isObject(data) {
    return data != null && typeof data === 'object';
}

浅/深层拷贝

const obj = {
  a: 1,
  b: ['e', 'f', 'g'],
  c: { h: 20 },
  d: () => { }
}
1. JSON转换
JSON.parse(JSON.stringify(obj))

能够简单有效的深拷贝对象,但有以下缺点

  1. function和undefined类型数据丢失
  2. RegExp、Error对象,将只得到空对象
  3. 循环引用无法深拷贝
2. 自定义深拷贝函数
function deepCopy(obj) {
  if (typeof obj !== 'object' || obj == null) return obj;
  let result = Array.isArray(obj) ? [] : {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = deepCopy(obj[key]);
    }
  }
  return result;
}

此函数在工作中经常遇到,解决了方法1 的前两个问题,适用绝大多数情况

3. 解决循环引用深拷贝

创建一个循环引用对象

const obj = {
  a: 1,
  b: ['e', 'f', 'g'],
  c: { h: 20 },
  d: () => { }
}
obj.b.push(obj.c)
obj.c.j = obj.b

此时使用方法2将会报错栈溢出,解决方法是引入map对象。拷贝循环引用时指向引用对象而非继续深拷贝

function deepCopyLoop(obj, map = new Map()) {
  if (typeof obj !== 'object' || obj == null) return obj;
  let result = Array.isArray(obj) ? [] : {};
  let cache = map.get(obj);
  if (cache) return cache;
  map.set(obj, result);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = deepCopyLoop(obj[key], map);   //递归复制
    }
  }
  return result;
}
4. 函数优化
function deepCopyLoopOptimize(obj, map = new Map()) {
  if (typeof obj !== 'object' || obj == null) return obj;
  let cache = map.get(obj);
  if (cache) return cache;
  let isArray = Array.isArray(obj);
  let result = isArray ? [] : {};
  map.set(obj, result);
  if (isArray) {
    obj.forEach((item, index) => {
      result[index] = deepCopyLoopOptimize(item, map);
    })
  } else {
    Object.keys(obj).forEach(key => {
      result[key] = deepCopyLoopOptimize(obj[key], map);
    })
  }
  return result;
}

使用Array.forEach和Object.keys避免继承的原型链属性的干扰。

类数组对象

一个定义: 任何一个具有length属性以及对应的非负整数属性的对象作为一种数组

 var a={"0":"a","1":"b","2":"c",length:3};  //这是一个类数组对象
 Array.prototype.join.call(a,"+");      //"a+b+c"
 Array.prototype.slice.call(a,0);       //["a","b","c"],真正数组的副本
 Array.prototype.map.call(a,function (x) {
     return x.toUpperCase();      //["A","B","C"]
 });
  • 可以使用 for 循环
  • 类数组对象没有继承 Array.prototype,不能直接调用数组方法

常见的类数组对象

  • arguments 对象
  • document.getElementsByTagName()
  • jQuery获取的 dom 对象

for in 和 for of 区别

  • for in 是 es5 标准,该方法遍历对象的属性名称(key),以原始插入顺序迭代对象的可枚举属性

    • 遍历 array,值为元素索引,类型 string

    • 遍历数组可能输出顺序不固定

    • 原理是 Object.keys(),遍历所有可枚举属性,包括原型

Array.prototype.method=function(){}
var myArray=[1,2,4];
myArray.name="数组";

for (var index in myArray)
console.log(myArray[index]);    //0,1,2,method,name

for (var value of myArray) 
console.log(value);    //1,2,4
  • for of 是 es6 标准,该方法遍历对象的属性对应的值(value)

    • 可迭代对象包括 array, map, set, string, typedArray, arguments 等
    • 不能遍历普通对象
let arr = {
	a:1,b:2,c:3
}
for(let i of arr) {
	console.log(i);
}
# TypeError: arr is not iterable at Object.<anonymous>

for(var key of Object.keys(arr)){
	//使用Object.keys()方法获取对象key的数组
	console.log(arr[key]);
}
  • vue 中 v-for 的 in 和 of 没有区别
<li v-for="(item, key, index) in obj" :key="key"></li>
<li v-for="(item, index) in arr" :key="index"></li>

<li v-for="(item, key, index) of obj" :key="key"></li>
<li v-for="(item, index) of arr" :key="index"></li>

可遍历数组/对象,遍历数组时,key=index,一般省略 key

vue data 重置

Object.assign(this.$data.form, this.$options.data().form);

NaN isNaN

JavaScript中,NaN是一个特殊的数字值(typeof NaN的结果为number),是not a number的缩写,表示不是一个合法的数字。

NaN是唯一一个和自身不相等的值:

isNaN(NaN)  // true
isNaN(10)  // false
isNaN('10')  // false
# 首先做的就是把这些值转换成数字 再判断一个数值是不是一个非数字

展开:判断NaN是不是NaN

function isValueNaN(value) {
	return value !== value
}

function isValueNaN(value) {
	return typeof value === 'number' && isNaN(value)
}

Object.is(val, NaN)

时间的自定义函数

function secondToTime(second) {
    let hh = ('00' + Math.floor(second / 3600)).slice(-2);
    let mm = ('00' + (Math.floor(second / 60) % 60)).slice(-2);
    return hh + ':' + mm;
}
function timeToSecond(time) {
    let times = time.split(':').map((i) => parseInt(i));
    return times[0] * 3600 + times[1] * 60;
}
function timeFormat(val) {
    let standardReg = /^(([0-2][0-3]|[0-1][0-9]):[0-5][0-9]|24:00|)$/;
    if (standardReg.test(val)) return val;
    let reg = /(\d{0,2})([^\d]?(\d{1,2}))?/;
    let result = val.match(reg);
    if (parseInt(result[1]) > 23) return '24:00';
    let hour = '00' + result[1];
    let minute = '00' + (parseInt(result[3]) > 59 ? '59' : result[3] || '00');
    return hour.slice(-2) + ':' + minute.slice(-2);
}

tums 单选 多选 下拉框 绑定value

const chargingModeOptions = {
    0: 'fixed',
    1: 'divided'
}
const mode = 0;
<tums-radio-group v-model="mode" @change="changeMode">
    <tums-radio v-for="(item, key) in chargingModeOptions" :key="item" :label="key">
        {{ $t(`chargeRules.checkbox.${key}`) }}
    </tums-radio>
</tums-radio-group>
// 显示有问题 因为 label 绑定的是 "0" "1" String

// 反过来 则没有问题 绑定的是 0 1 Number
const chargingModeOptions = {
    'fixed': 0,
    'divided': 1
}
<tums-radio-group v-model="mode" @change="changeMode">
    <tums-radio v-for="(item, key) in chargingModeOptions" :key="key" :label="item">
        {{ $t(`chargeRules.checkbox.${item}`) }}
    </tums-radio>
</tums-radio-group>

vue provide inject 响应式

provide

data() {
    return {
        ruleList: []
    };
},
provide() {
    return {
        ruleList: this.ruleList,
    };
},
# Array改变值的时候不能直接赋值 否则不是响应式
this.ruleList = res.ruleList;  ×
this.ruleList.splice(0, this.ruleList.length); √
this.ruleList.push(...res.ruleList); √
# 或者 方式二
res.ruleList.forEach((rule, index) => {
    this.$set(this.ruleList, index, rule);
});

inject

inject: ['ruleList'],
// 使用 this.ruleList

Promise 的两种处理方式

try {
    let data = await file.arrayBuffer();
    console.log(file, data);
} catch (error) {
    this.fileCorruptionErrorMsgShow = $t('chargeUsers.hint.fileCorruptionError');
}
blob.arrayBuffer().then(buffer => {
    ...
}).catch(err => {
    ....
});

获取视图宽高

img

// 浏览器正文可见区域的高度和宽度。包括滚动条的部分
// 在通常情况下 window.innerHeight == document.body.clientHeight :
window.innerHeight/window.innerWidth
// 浏览器可见区域 :
window.outerHeight/window.outerWidth
// 浏览器所有正文部分的高度/宽度(即你一眼看不完的区域的高度和宽度也算在里面):
document.body.scrollHeight/document.body.scrollWidth
// 水平和垂直滚动条滚动的距离 :
document.body.scrollTop/document.body.scrollLeft
// 通常情况下 :
document.body.scrollTop + window.innerHeight(document.body.clientHeight) = document.body.scrollHeight
document.body.scrollLeft + window.innerWidth(document.body.clientWidth) = document.body.scrollWidth
// 判断一个滚动容器是否滚动容器底部
element.scrollHeight - element.scrollTop == element.clientHeight
# document.documentElement 指的是文档根元素,即html元素

document.body.scrollTop等属性,在xhtml标准网页或者更简单的说是带<!DOCTYPE ..>标签的页面里得到的结果是0,需使用document.documentElement

jquery

$(window).height()//浏览器当前窗口可视区域高度,是获取当前 也就是你浏览器所能看到的页面的那部分的高度 这个大小在你缩放浏览器窗口大小时 会改变 与document是不一样的
$(window).width()//浏览器当前窗口可视区域宽度
$(document).height()//浏览器当前窗口文档的高度 
$(document).width()//浏览器当前窗口文档的宽度 
$(document.body).height()//浏览器当前窗口文档body的高度 
$(document.body).width()//浏览器当前窗口文档body的宽度 
$(document.body).outerHeight(true)//浏览器当前窗口文档body的总高度 包括border padding margin 
$(document.body).outerWidth(true)//浏览器当前窗口文档body的总宽度 包括border padding margin

getBoundingClientRect()

rectObject = object.getBoundingClientRect();
 
rectObject.top // 元素上边到视窗上边的距离;
rectObject.right // 元素右边到视窗左边的距离;
rectObject.bottom // 元素下边到视窗上边的距离;
rectObject.left // 元素左边到视窗左边的距离;
rectObject.width // 是元素自身的宽
rectObject.height // 是元素自身的高

新建指定长度数组并填充

let list = new Array(5).fill(0);

Array去重

this.phoneList = Array.from(new Set(this.phoneList));

Arrray.prototype.sort

this.staticGridData.sort((next, prev) => {
    return value === 'asc' ? next[key] - prev[key] : prev[key] - next[key];
});
# < 0 交换

下载文件/导出的标准写法

axios.post(Constant.url.exportChargingUsers, {
    projectId: this.projectId,
    chargingShopIdList: this.params.filterAnd.chargingShopIdList
}, {responseType: 'arraybuffer'}).then(res => {
    if (res.status === 200) {
        console.log(res);
        if (res.headers['content-type'].indexOf('application/json') > -1) {
            let uintMsg = new Uint8Array(res.data);
            let decodedString = String.fromCharCode.apply(null, uintMsg);
            let data = JSON.parse(decodedString);
            if (data[errorCode.ENAME] !== errorCode.ENONE) {
                let errorMsg = errorCode.get(data[errorCode.ENAME]);
                TumsMessage.error(errorMsg);
                return;
            }
        }
        let date = new Date();
        let blob = new Blob([res.data], {
            type: res.headers.contentType
        });
        let fileName = $t('chargeUsers.fileTemplate') +
            '_' + date.format('yyyy-MM-dd_hh:mm:ss') + '.xlsx';
        if (window.navigator.msSaveOrOpenBlob) {
            # msSaveOrOpenBlob 已弃用:不再推荐此特性。
            window.navigator.msSaveBlob(blob, fileName);
        } else {
            let objUrl = URL.createObjectURL(blob);
            let link = document.createElement('a');
            link.href = objUrl;
            link.setAttribute('download', fileName);
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
    }
});

上传读取文件

<input type="file" id="upload" accept=".xlsx"><br>

<script>
let upload = document.getElementById('upload');
/* 写法1  IE10 支持 */
upload.addEventListener('change', (e) => {
    let file = e.target.files[0];
    var reader = new FileReader();
    reader.onload = function(e) {
      var data = e.target.result;
      /* reader.readAsArrayBuffer(file) -> data will be an ArrayBuffer */
    };
    reader.onerror = function(e) {
        console.log('oops Error!!');
    }
    reader.readAsArrayBuffer(file);
});
/* 写法2 Blob.arrayBuffer()方法是一种更新的基于promise的API,用于将文件读取为ayyatBuffer IE不支持 */
upload.addEventListener('change', (e) => {
    let file = e.target.files[0];
    // promise 使用1 错误返回用 try catch 包围
    let data = await file.arrayBuffer();
    // promise 使用2 错误返回用 .catch()
    file.arrayBuffer().then(buffer => /* process the ArrayBuffer */);
});
</script>

正则表达式之正向预搜索、反向预搜索

它们只匹配某些位置,在匹配过程中,不占用字符

正向预搜索
  • (?=pattern):代表字符串中的一个位置,紧接该位置之后的字符序列能够匹配 pattern
  • (?!pattern):代表字符串中的一个位置,紧接该位置之后的字符序列不能匹配 pattern
var regExp = /^xxx(?=mask)/;
var xxxxxxxxxx = 'xxxmaskre';
console.log(regExp.test(xxxxxxxxxx));	// true

var regExp = /^xxx(?!mask)/;
var xxxxxxxxxx = 'xxxmaskre';
console.log(regExp.test(xxxxxxxxxx));	// false
反向预搜索
  • (?<=pattern):代表字符串中的一个位置,紧接该位置之前的字符序列能够匹配 pattern
  • (?<!pattern):代表字符串中的一个位置,紧接该位置之前的字符序列不能匹配 pattern
var regExp = /(?<=mask)re$/;
var xxxxxxxxxx = 'xxxmaskre';
console.log(regExp.test(xxxxxxxxxx));	// true

var regExp = /(?<!mask)re$/;
var xxxxxxxxxx = 'xxxmaskre';
console.log(regExp.test(xxxxxxxxxx));	// false

注:chome使用反向预搜索时,(?<=pattern)(?<!pattern)前不能写东西。IE使用反向预搜索会报错。JS中最好不要使用反向预搜索。

ES2018引入了反向肯定预查,V8引擎4.9版(Chrome 62)已经支持 。实测有上述缺陷,或者是因为babel实际环境版本降低。

call apply bind

1. call()、apply()、bind() 都是用来重定义 this 这个对象的
let obj = {
    name: '小菊',
    age: 17,
    intro: function() {
        console.log(this.name + '---' + this.age);
    },
};
let p = { name: '里斯', age: 20 };

obj.intro.call(p);	// 里斯---20
obj.intro.apply(p);	// 里斯---20
obj.intro.bind(p)();// 里斯---20

bind 返回的是一个新的函数,你必须调用它才会被执行。

2. 对比传参
let obj = {
    name: '小菊',
    age: 17,
    intro: function (from, to) {
        console.log(this.name + '---' + this.age + ' from ' + from + ' to ' + to);
    },
};
let p = { name: '里斯', age: 20 };

obj.intro.call(p, '成都', '上海');	// 里斯---20 from 成都 to 上海
obj.intro.apply(p, ['成都', '上海']);	// 里斯---20 from 成都 to 上海
obj.intro.bind(p, '成都', '上海')();	// 里斯---20 from 成都 to 上海
obj.intro.bind(p, ['成都', '上海'])();	// 里斯---20 from 成都,上海 to undefined
  • 三个函数的第一个参数都是 this 的指向对象
  • 不传任何参数,则默认为将this指向修改为 windows
  • apply 的所有参数都必须放在一个数组里面传进去

JS跳转页面

let bindUrl = 'https://baidu.com/';
# 在新窗口打开链接
window.open(bindUrl);
# 在当前窗口跳转链接
window.location.href = bindUrl;

JS 浮点数精度问题

toFixed()

JS 运算符优先级

  • [] . new
  • ()
  • + - ~ ! delete typeof void
  • * / %
  • ++ --
  • << >> >>>
  • < <= > >= instanceof
  • == != === !==
  • &
  • ^
  • |
  • &&
  • ||
  • ? :
  • = += -= *= /= %= &= |= ^= <<= >>= >>>=
  • ,

~~

~~其实是一种利用符自号进行的类型转换,转换成数字类型

日期格式转换

Date.prototype.format = function(fmt) {   // eslint-disable-line
    var o = {
        'M+': this.getMonth() + 1, // 月份
        'd+': this.getDate(), // 日
        'h+': this.getHours(), // 小时
        'm+': this.getMinutes(), // 分
        's+': this.getSeconds(), // 秒
        'q+': Math.floor((this.getMonth() + 3) / 3), // 季度
        'S': this.getMilliseconds() // 毫秒
    };
    if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (this.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;
};

JS字节流读取

第一步: buffer转bytes[]
let uintMsg = new Uint8Array(data);
第二步: 二进制编码转字符

静态string.fromcharcode()方法返回一个由指定的UTF-16代码单元序列创建的字符串。

let decodedString = String.fromCharCode.apply(null, unitMsg);
// 或
let decodedString = String.fromCharCode(...unitMsg);

静态string.fromcodepoint()方法返回一个使用指定的代码点序列创建的字符串,es6后支持。

let decodedString = String.fromCodePoint.apply(null, unitMsg);
// 或
let decodedString = String.fromCodePoint(...unitMsg);

string.fromcharcode() 和 string.fromcodepoint() 区别

  • String.fromCharCode() 只对不大于0xFFFF的码点有效(UTF16)。
  • string.fromCodePoint() 可以返回4字节的补充字符,以及更常见的2字节的BMP字符,通过指定它们的代码点(相当于UTF-32代码单元)。
第三步: 转码(没中文可省略)

方式一: 一步到位

TextDecoder接口表示特定文本编码的解码器,缺点:IE不支持

let decc = new TextDecoder() // default 'utf-8'
let result = decc.decode(unit8Msg);

方式二: 适配IE

let decodedString2 = escape(decodedString)
let decodedString3 = decodeURIComponent(decodedString2);
// 或
decodedString = decodeURIComponent(escape(decodedString));

生成二维码

web

import QRCode from 'qrcodejs2';
import html2canvas from 'html2canvas';

this.$nextTick(() => {
    if (!this.invitationQRCodeObj) {
        this.invitationQRCodeObj = new QRCode('invitationQRCode', {
            text: this.QRCodeUrl,
            width: 144,
            height: 144,
            correctLevel: QRCode.CorrectLevel.H
        });
        this.invitationQRCodeObj.clear();
        this.invitationQRCodeObj.makeCode(this.QRCodeUrl);
    } else {
        this.invitationQRCodeObj.clear();
        this.invitationQRCodeObj.makeCode(this.QRCodeUrl);
    }
});

getCanvas(el) {
    return html2canvas(el, {
        useCORS: true,
        width: el.offsetWidth,
        height: el.offsetHeight
    });
}

let canvasData = await this.getCanvas(el);

Date toLocaleDateString toLocaleTimeString

toLocaleTimeString在不同浏览器返回格式不同,在ie和低版火狐会在数字左右插入看不见的空格U+200E,导致显示错误

尽量使用 Date.prototype.format框架函数。

js -1是true,正负整数都是true,只有0 false

posted @ 2023-03-22 14:13  少年と山  阅读(44)  评论(0)    收藏  举报