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))
能够简单有效的深拷贝对象,但有以下缺点
- function和undefined类型数据丢失
- RegExp、Error对象,将只得到空对象
- 循环引用无法深拷贝
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 => {
....
});
获取视图宽高

// 浏览器正文可见区域的高度和宽度。包括滚动条的部分
// 在通常情况下 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()+-~!deletetypeofvoid*/%++--<<>>>>><<=>>=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框架函数。

浙公网安备 33010602011771号