1.padStart(),padEnd()
ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
上面代码中,padStart()和padStart()一共接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串。
如果原字符串的长度等于或大于指定的最小长度,则返回原字符串。
'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'
如果省略第二个参数,默认使用空格补全长度。
'x'.padStart(4) // ' x'
'x'.padEnd(4) // 'x '
padStart()的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。
'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"
另一个用途是提示字符串格式。
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
2.日历相关(2023/1/17)
2.1 获取当月最后一天
//获取当月最后一天的日期
function getLastDay(){
var year = new Date().getFullYear(); //获取年份
var month = new Date().getMonth() + 1; //获取月份
var lastDate = new Date(year, month , 0).getDate(); //获取当月最后一日
month = month < 10 ? '0' + month : month ; //月份补 0
return [year,month ,lastDate ].join("-")
}
2.2 获取当前月有多少天
new Date(year, month, 0).getDate();
new Date(2019, 2, 0).getDate();
// 28
new Date(2019, 3, 0).getDate();
// 31
2.3 获取时间戳
//获取当前时的时间戳
new Date().getTime()
//获取某个时间的时间戳
new Date(“2021-11-18 18:00:00”).getTime()
//获取的是以ms为单位的,想要获取以s为单位的,除以1000就可以了
new Date().getTime()/1000
//获取当前时间2分钟后的时间戳
new Date().getTime()/1000 + 120
3.$emit调用父组件异步方法,返回结果后再执行子组件的方法
父组件
<子组件 @updateList=“getTaskListDetails”></子组件>
async getTaskListDetails(callback = () => {}) {
...
this.dataList = xxx;
...
callback(this.dataList);
}
子组件
this.$emit('updateList', (res) => {
// res = this.dataList
this.checkTaskProgress(res);
})
4.使用扩展运算符解构对象和数组
扩展运算符由三个点 ... 表示,可用于对象和数组的解构。对于对象,它允许使用另一个对象的属性子集轻松创建一个新对象。
const numbersObj = { a: 1, b: 2, c: 3 };
const newObject = { ...numbersObj, b: 4 };
console.log(newObject); // { a: 1, b: 4, c: 3 }
对于数组,使用扩展运算符可以轻松提取和操作数组元素。
const numbersArray = [1, 2, 3, 4, 5];
const newArray = [...numbersArray.slice(0, 2), 6, ...numbersArray.slice(4)];
console.log(newArray); // [ 1, 2, 6, 5 ]
5.reduce用法
5.1 验证括号
结果为0时表示括号全部配对
[..."(())()(()())"].reduce((a,i)=> i === '(' ? a+1 : a-1, 0);
// 0
[..."((())()(()())"].reduce((a,i)=> i === '(' ? a+1 : a-1, 0);
// 1
[..."(())()(()()))"].reduce((a,i)=> i === '(' ? a+1 : a-1, 0);
// -1
5.2 反转字符串
const res = str => [...str].reduce((a, v) => v + a);
6.设计模式
6.1 工厂模式
class Product {
constructor (name) {
this.name = name
}
init() {}
fun() {}
}
// 工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。
class Factory {
create(name) {
return new Product(name)
}
}
//use 子类可以重写接口方法以便创建的时候指定自己的对象类型。
let factory = new Factory()
let p = factory.create('p1')
p.init()
p.fun()
适用场景
- 如果你不想让某个子系统与较大的那个对象之间形成强耦合,而是想运行时从许多子系统中进行挑选的话,那么工厂模式是一个理想的选择
- 将new操作简单封装,遇到new的时候就应该考虑是否用工厂模式;
- 需要依赖具体环境创建不同实例,这些实例都有相同的行为,这时候我们可以使用工厂模式,简化实现的过程,同时也可以减少每种对象所需的代码量,有利于消除对象间的耦合,提供更大的灵活性
优点
- 创建对象的过程可能很复杂,但我们只需要关心创建结果。
- 构造函数和创建者分离, 符合“开闭原则”
- 一个调用者想创建一个对象,只要知道其名称就可以了。
- 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
缺点
- 添加新产品时,需要编写新的具体产品类,一定程度上增加了系统的复杂度
- 考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度
class jQuery {
constructor() {
super(selector)
}
add() {}
...
}
window.$ = function(selector) {
return new jQuery(selector)
}
6.2 单例模式
class LoginForm {
constructor() {
this.state = 'hide'
}
show() {
if(this.state === 'show') {
alert('show')
return
}
this.state = 'show'
console.log('login success')
}
hide() {
if(this.state === 'hide') {
alert('hide')
return
}
this.state = 'hide'
console.log('hide success')
}
}
LoginForm.getInstance = (function() {
let instance
return function(){
if(!instance) {
instance = new LoginForm()
}
return instance
}
})()
let obj1 = LoginForm.getInstance()
obj1.show()
let obj2 = LoginForm.getInstance()
obj2.hide()
优点
- 划分命名空间,减少全局变量
- 增强模块性,把自己的代码组织在一个全局变量名下,放在单一位置,便于维护
- 且只会实例化一次。简化了代码的调试和维护
缺点
- 由于单例模式提供的是一种单点访问,所以它有可能导致模块间的强耦合 从而不利于单元测试。无法单独测试一个调用了来自单例的方法的类,而只能把它与那个单例作为一个单元一起测试。
7.for in 与 for of
for...in适用于对象上面可枚举属性的遍历,并且只遍历非Symbol类型键以及对象自身以及原型链上的可枚举属性。
for...of适用于实现了Iterator接口的对象(也称作可迭代对象)的遍历,其遍历方式由自身实现,例如对于数组是遍历其每个下标对应元素的值,对于Map遍历值为键值对组成的数组。
7.1 for in
for...in语句以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性。 for in 遍历数组则输出数组对应的下标,如果是遍历对象则输出对象的键名。
const arr = [1, 2, 3, 4, 5];
arr.value = "array"; // 给数组对象上添加一个value属性
const s1 = Symbol("symbol1"); // 定义两个symbo类型的值
const s2 = Symbol("symbol2");
const obj = {
mark: "mark-v",
jack: "jack-v",
amy: "amy-v",
[s1]: "symbol1-v", // 给obj初始化这两个symbol键值
[s2]: "symbol2-v",
};
// 给obj原型上添加一个属性
Object.getPrototypeOf(obj).myProto = 'proto'
console.log(obj);
// 打印 {
// mark: 'mark-v',
// jack: 'jack-v',
// amy: 'amy-v',
// [Symbol(symbol1)]: 'symbol1-v',
// [Symbol(symbol2)]: 'symbol2-v'
// }
for (const i in arr) {
console.log(i); // 输出 0 1 2 3 4 value myProto
}
for (const i in obj) {
console.log(i); // 输出 mark jack amy myProto
}
无论是数组元素或者对象上的键值,都是可枚举属性,并且可以看出Symbol类型的键,虽然定义在了对象中,但是for in会跳过Symbol类型键的遍历
并且for in还会往原型链上遍历,那为什么遍历obj和arr都会输出 'myProto' 呢,那是因为obj是一个对象,默认的构造函数是Object(),而arr是一个数组对象,默认的构造函数是Array(),但是Array的prototype原型属性本质上也是一个对象,所以Array原型的原型就是Object上的prototype属性
Object.getPrototypeOf(obj) === Object.getPrototypeOf(Object.getPrototypeOf(arr)) // true
可枚举属性是指那些内部“可枚举”标志设置为 true 的属性,对于通过直接的赋值和属性初始化的属性,该标识值默认为即为
true,对于通过 Object.defineProperty 等定义的属性,该标识值默认为false。可枚举的属性可以通过 for...in 循环进行遍历(除非该属性名是一个 Symbol)。属性的所有权是通过判断该属性是否直接属于某个对象决定的,而不是通过原型链继承的。
console.log打印obj,只包含了可枚举的属性,也就是说可枚举标识为false的属性是不会出现在整个obj对象的打印内容中,也就验证了就算Symbol类型键的属性可枚举标识为true,也是会被for in跳过。 另外:数组的下标也是数组对象的属性,设置了下标0不可枚举和不可修改后,for in遍历不到下标0,并且也修改不了下标0的值,但是好玩的是打印数组还是会完整打印出每个元素。
7.2 for of
for...of语句在可迭代对象(包括Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
// 迭代数组
const array = [10, 20, 30];
for (const value of array) {
console.log(value); // 输出 10 20 30
}
// 迭代String
const string = 'kong'
for (const s of string) {
console.log(s); // 输出 k o n g
}
// 迭代map
const map = new Map([["a", 1], ["b", 2], ["c", 3]]);
for (const item of map) {
console.log(item); // 输出 [ 'a', 1 ] [ 'b', 2 ] [ 'c', 3 ]
}
// 迭代set
const set = new Set([1, 1, 2, 2, 3, 3]);
for (let item of set) {
console.log(item); // 输出 1 2 3
}
对于for...of的循环,可以由 break, throw 或 return 终止。在这些情况下,迭代器关闭。
8.Object 和 Map
8.1 Object
Map是可迭代的。 使用JavaScript的 for...of 表达式并不能直接迭代对象,for...in允许你迭代一个对象的可枚举属性。 使用for...in存在如下问题:
- 会遍历对象所有的可枚举属性,包括原型链上的属性
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log("Hello, I'm " + this.name);
};
var obj = new Person("Tom");
for (const prop in obj) {
console.log(prop) // name, sayHello
}
可以看到obj除了自身属性name之外,还会遍历到原型链上的属性sayHello。这是因为sayHello是可枚举的。如果不想遍历原型链上的属性,可以按照如下写法:
// 方法一:使用hasOwnProperty()
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
console.log(prop); // name
}
}
// 方法二:使用Object.keys()
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
console.log(keys[i]); // name
}
- 遍历顺序不一定按照对象属性定义的顺序
const obj = {
a: 1,
b: 2,
c: 3,
"1": "one",
"2": "two",
"3": "three"
};
for (var key in obj) {
console.log(key + ": " + obj[key]);
}
//结果如下:
1: one
2: two
3: three
a: 1
b: 2
c: 3
- 遍历的索引为字符串类型的数字,并不能直接进行计算
const obj = {
1: "one",
2: "two",
3: "three"
};
for (const key in obj) {
console.log(key, typeof key); // 1 string; 2 string; 3 string
}
const arr = [4, 5, 6]
for (const index in arr) {
console.log(index, typeof index); // 0 string; 1 string; 2 string;
}
8.2 Map
如果是Map,你可以使用标准的for循环、标准的迭代器和使用解构来获取key和value,例如:
const map1 = new Map();
map1.set('a', 1);
map1.set('b', 2);
map1.set('c', 3);
for (const [key, value] of map1) {
console.log(key, value) // a 1; b 2; c 3
}
对于对象,我们还有一个Object.entries() 来做类似的事情:
const myObject = {a: 1, b: 2, c: 3}
for (const [key, value] of Object.entries(myObject)) {
console.log(key, value) // // a 1; b 2; c 3
}
对于Map,有更简单的办法直接内置迭代:
for (const value of myMap.values()) {
console.log(value)
}
for (const key of myMap.keys()) {
console.log(key)
}
9.移除数组中的指定元素
var arr = [0, 1, 2];
// indexOf()方法可返回某个指定的字符串值在字符串中首次出现的位置
var index = arr.indexOf(1);
// splice函数的第二个参数指删除的数目,splice直接修改原数组,并把删除的所有元素以另一个新数组的方式返回
if(index > -1) arr.splice(index, 1);
// [0, 2]
10.数组添加元素
10.1 push
可向数组的末尾添加一个或多个元素,并返回新的长度 它会直接修改原数组
arr.push(new1, new2, ..., newX)
10.2 unshift
unshift() 方法将把它的参数插入 arrayObject 的头部,并将已经存在的元素顺次地移到较高的下标处,以便留出空间。该方法的第一个参数将成为数组的新元素 0,如果还有第二个参数,它将成为新的元素 1,以此类推。 不创建新的数组,直接修改原有数组,返回数组的新长度。
arr.unshift(new1, new2, ..., newX)
console.log(arr); // ["ZhangQian","LinFang","HaiKun"]
console.log(arr.unshift("C")); // 4
console.log(arr); // ["C","ZhangQian","LinFang","HaiKun"]
10.3 splice
splice() 方法可删除从 index 处开始的零个或多个元素,并且用参数列表中声明的一个或多个值来替换那些被删除的元素。 如果从 arrayObject 中删除了元素,则返回的是含有被删除的元素的数组。 splice() 方法会直接对数组进行修改。
arr.splice(index, num, item1, ..., itemX)
| 参数 | 描述 |
|---|---|
| index | 必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。 |
| num | 必需。要删除的项目数量。如果设置为 0,则不会删除项目。 |
| item1, ..., itemX | 可选。向数组添加的新项目。 |
var arr = ["A","ZhangQian","LinFang","HaiKun"];
console.log(arr.splice(1,0,"B","C"));// []
console.log(arr);// ["A","B","C","ZhangQian","LinFang","HaiKun"]
console.log(arr.splice(1,2));// ["B","C"]
console.log(arr);// ["A","ZhangQian","LinFang","HaiKun"]
console.log(arr.splice(1,1,"D"));// ["ZhangQian"]
console.log(arr);// ["A","D","LinFang","HaiKun"]
11.break 和 continue 的区别
break用于完全结束一个循环,跳出循环体执行循环后面的语句;而continue是跳过当次循环中剩下的语句,执行下一次循环。简单点说就是break完全结束循环,continue终止本次循环。
for (let i = 1; i < 5; i++) {
if (i === 2) {
continue;
}
console.log(i) // 1 3 4
}
for (let i = 1; i < 5; i++) {
if (i === 2) {
break;
}
console.log(i) // 1
}
另外,forEach 不支持continue和break,也不支持return的方式,如果需要跳出循环只能通过抛异常的方式实现。
try {
methodInfoList.forEach(element => {
if (element.value == null || element.value == "") {
this.msgError("检查方法不能输入空值");
throw new Error("检查方法不能输入空值");
}
});
} catch(e) {
console.log(e.message);
}
12.实现深拷贝
由于 Object 类型与 Array 类型是引用类型,而引用类型在变量间的相互赋值是将指向内存的指针赋予过去,这样就会导致,当改变b的数据会将a的数据一同改变。例如:
let obj = {
a: 1
}
let obj2 = obj;
obj2.a = 2;
console.log(obj, obj2);
// 结果如下:
//obj
{
a: 2
}
//obj2
{
a: 2
}
而在实际的开发过程中,有很多时候需要将两个变量间的关联断开
12.1 使用延展操作符(...)实现深拷贝
但是只能深克隆一层,第二层的引用依然指向原来的位置
let obj = {
a: 1,
e: {
ea: 1
}
}
let obj2 = {...obj};
obj2.a = 2;
obj2.e.ea = 2;
console.log(obj, obj2);
// 结果如下:
//obj
{
a: 1
e: {
ea: 2
}
}
//obj2
{
a: 2,
e: {
ea: 2
}
}
12.2 使用JSON实现深拷贝
可以实现多层的深克隆,但是无法复制function
let obj = {
a: 1,
e: {
ea: 1
},
d: ()=> {
let f = 3;
console.log(f);
}
}
let obj2 = JSON.parse(JSON.stringify(obj));
obj2.a = 2;
obj2.e.ea = 2;
console.log(obj, obj2);
obj.d();
obj2.d();
// 结果如下:
//obj
{
a: 1
e: {
ea: 1
},
d: f d()
}
3
//obj2
{
a: 2,
e: {
ea: 2
}
}
obj2.d is not a function
12.3 使用递归和循环挨个创建参数和赋值
该方法能够完全复制一个对象
// 深克隆
function deepCopy(value) {
if(value instanceof Function)return value
else if (value instanceof Array) {
var newValue = []
for (let i = 0; i < value.length; ++i) newValue[i] = deepCopy(value[i])
return newValue
} else if (value instanceof Object) {
var newValue = {}
for (let i in value) newValue[i] = deepCopy(value[i])
return newValue
} else return value
}