1、使用‘?:’三目运算符优化if-else 语句
这是许多编程语言的共同特征。你可以使用三元运算符用一行代码编写整个语句,而不是在多行上编写 if-else:
let age = 12;
let ageGroup;
// LONG FORM
if (age > 18) {
ageGroup = "An adult";
} else {
ageGroup = "A child";
}
// SHORTHAND
ageGroup = age > 18 ? "An adult" : "A child";
但是,不要过度使用它。它会使你的代码更加冗长。明智的做法是仅用此替换简单的表达式以提高可读性并减少代码行数。
2、使用‘||’和‘??’ 运算符高效判断值
使用'||' 或运算符 判断obj的值
let obj = 0;
// LONG FORM
let value;
if(obj === 0 || obj === "" || obj === false || obj === null || obj === undefined){
value = -1
} else {
value = obj;
}
//SHORTHAND
let value = obj || -1 //-1
使用'??' 非空运算符 判断obj的值,仅当值为 null 或 undefined 时,此赋值运算符才会赋值
let obj = 0;
// LONG FORM
let value;
if( obj === null || obj === undefined){
value = -1
} else {
value = obj;
}
//SHORTHAND
let value = obj ?? -1 //0
ps://下面可以满足 输入框里判断输入框未输入值的场景
if(( value ?? '' ) !== ''){ //... }
javascript中有6种假值,分别是:
- false
- null
- undefined
- NaN
- 0(数字)
- ''(空字符串)
假值的意思就是非真值,这样说可能有点绕,其实这涉及到我们在程序中做真值判断,比如我们有一个变量 foo,当我们用 if (!foo) 来做判断的时候,如果 foo 是上述6种假值之一,这个判断就会返回 true。
下面我们就来看一下这6种假值之间的相等情况,注意这里是相等(==),不是全等(===),如果全等判断的话,6种假值任何2个都是非全等的。
null == undefined // truenull或undefined和其他任何4个假值相等判断都是false,例如:null == 0 // falsefalse,0,'', 这3种假值互相做相等判断都是true,即:false==0 // true,0=='' // true,''==false // trueNaN和其他任何假值相等判断都是false,例如:NaN==false // false
另外有2种特殊情况,判断的对方都是真值,但是相等判断为true。
- 字符串
'0'是真值,但是和数字0判断时会先转换类型为数字0,所以0=='0' // true,同时false=='0' // true,但是''=='0' // false,这里要多加注意。 - 空数组
[]是真值,但是和数字0,false,''判断都是true,即0==[] // true,false==[] // true,''==[] // true
为什么 []==![]结果为true,[]==[]结果为false?
首先我们知道,!的优先级要大于==的,所以先运算右边
-
计算
![]!是逻辑非运算符,会将其操作数转换为布尔值然后取反。- 在 JavaScript 中,任何对象(包括空数组
[])都被认为是truthy值。 - 因此,
[]转换为布尔值时得到true。 !true则为false。
所以,
![]结果为false。现在表达式变为:
[] == false -
比较
[]和false-
当使用
==运算符比较不同类型时,JavaScript 会进行类型转换。 -
根据类型转换规则,当布尔值与其他类型进行比较时,布尔值会先转换为数值:
false -> 0
现在表达式变为:
[] == 0 -
-
比较
[]和0-
当对象(如数组
[])与数值进行比较时,对象会被转换为原始类型。 -
空数组
[]被转换为字符串时,会调用其toString方法:[].toString() -> "" -
空字符串
""再转换为数值时:"" -> 0
最终,比较变为:
0 == 0 -
最终结果
0 == 0 // true
因此,[] == ![] 最终的结果是 true。
[] == [] 是false 是因为 两个是对象类型
由于 == 比较的是对象的引用(reference),而不是对象的值,都是对象所以不存在隐式转换,所以两个空数组虽然内容相同,但引用不同,因此返回的结果是 false。
3、使用 '&&'与运算符 进行短路评估
不必用if语句检查某事是否为真,你可以使用&&运算符:
let isReady = true;
function doSomething(){
console.log("king!");
}
// LONGER FORM
if(isReady){
doSomething();
}
// SHORTHAND
isReady && doSomething();
4、防止崩溃的‘?.’链判断运算符
如果访问未定义的属性,则会产生错误。这就是可选链的用武之地。
在未定义属性时使用可选链运算符,undefined将返回而不是错误。这可以防止你的代码崩溃:
let student = {
name: "king",
age: 18,
address: {
state: "New York"
},
};
//Error
console.log(student.address.ZIPCode.a); //TypeError: Cannot read property 'a' of undefined
// LONG FORM
console.log(student && student.address && student.address.ZIPCode && student.address.ZIPCode.a); // Doesn't exist - Returns undefined
// SHORTHAND
console.log(student?.address?.ZIPCode?.a); // Doesn't exist - Returns undefined
5、从数组中删除重复项
在 JavaScript 中,Set 是一个集合,它允许你仅存储唯一值。这意味着删除任何重复的值。
因此,要从数组中删除重复项,你可以将其转换为集合,然后再转换回数组:
let numbers = [1, 1, 20, 3, 3, 3, 9, 9];
let uniqueNumbers = [...new Set(numbers)]; // -> [1, 20, 3, 9]
困惑吗?让我解释一下它是如何工作的:
1)、new Set(numbers)从数字列表中创建一个集合。创建集合会自动删除所有重复值。
2)、展开运算符...将任何可迭代对象转换为数组。这意味着将集合转换回数组。[...new Set(numbers)]
6、在没有第三个变量的情况下交换两个变量
在 JavaScript 中,你可以使用解构从数组中拆分值。这可以应用于交换两个变量而无需第三个:
let x = 1;
let y = 2;
// LONGER FORM
let temp = x;
x = y;
y = temp;
// SHORTHAND
[x, y] = [y, x];
7、用!!做类型判断
在 JavaScript 中,你可以使用“!”在 JS 中将任何内容转换为布尔值:
console.log(!null) //true
console.log(!undefined) //true
console.log(!'') //true
console.log(!100) //false
console.log(!'abc') //false
“!!”则是逻辑与的取反运算,尤其后者在判断类型时代码简洁高效,省去了多次判断null、undefined和空字符串的冗余代码:
//判断变量obj为非空,非未定义或者非空串才能执行方法体的内容。
let obj;
// LONGER FORM
if(!(obj === 0 || obj === "" || obj === false || obj === null || obj === undefined)){
//obj有内容才执行的代码
}
// SHORTHAND
if(!!obj){
//a有内容才执行的代码...
}
//BEST 这种和上述作用一样,唯一不同的点在于!!obj会把obj转换为boolean类型
if(obj){
//a有内容才执行的代码...
}
8、扩展运算符
使用扩展运算符组合两个数组或两个对象:
//数组类型
let nums1 = [1, 2, 3],nums2 = [4, 5, 6];
//对象类型
let obj1 = {a:1},obj2 = {b:2};
// LONG FORM
let newArray = nums1.concat(nums2);
let newObj = Object.assign({},{obj1,obj2});
// SHORTHAND
newArray = [...nums1, ...nums2];
newObj = {...obj1,...obj2};
ps: Object.assign和扩展运算符(...),对一级属于属于深拷贝,对后面对级别属于浅拷贝
当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝:
let bar = { a: 1, b: 2 }; let baz = { ...bar }; // { a: 1, b: 2 }
let bar = { a: 1, b: 2 }; let baz = Object.assign({}, bar); // { a: 1, b: 2 } //深拷贝
let bar = { a: 1, b: {c:3,d:4} };
let baz = { ...bar }; //浅拷贝
(…)等价于Object.assign 性能方面 后者高
ps:通过jQuery的extend方法实现深拷贝,lodash.cloneDeep()实现深拷贝。
扩展运算符本质上也就是for..of循环的一种实现:
let arr = ["a","b","c"]
console.log(...arr) //a b c
9、传播解构
使用解构存取和使用多属性对象。 为什么?
因为解构能减少临时引用属性。
//eg1:
let student = {
name: "king",
age: 18,
city: "Helsinki",
state: "Finland",
};
// LONGER FORM
let name = student.name;
let age = student.age;
let address = { city: student.city, state: student.state };
// SHORTHAND
let { name, age, ...address } = student;
//eg2:
function foo() {
return {
name: 'Anna',
age: 56,
job: { company: 'Tesco', title: 'Manager' }
};
}
// LONGER FORM
let a = foo(), name = a.name, age = a.age, company = a.job.company;
// SHORTHAND
let { name, age, job: {company}} = foo();
//eg3:
let user = {firstName:"king",lastName:"kang"};
// LONGER FORM
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
return `${firstName} ${lastName}`;
}
// SHORTHAND good
function getFullName(obj) {
const { firstName, lastName } = obj;
return `${firstName} ${lastName}`;
}
// SHORTHAND best
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
数组的批量赋值:
let num1, num2;
// LONGER FORM
num1 = 10;
num2 = 100;
// SHORTHAND
[num1, num2] = [10, 100];
10、模版字符串
通过将字符串包装在反引号内并${}用于嵌入值,从而在字符串之间插入变量:
let age = 18;
let sentence = `I'm ${age} years old`;
// result: I'm 18 years old
11、从数组中查找特定元素
使用find()方法查找匹配特定条件的元素:
let fruits = [
{ type: "Banana", color: "Yellow" },
{ type: "Apple", color: "Green" }
];
// LONGER FORM
let yellowFruit;
for (let i = 0; i < fruits.length; ++i) {
if (fruits[i].color === "Yellow") {
yellowFruit = fruits[i];
}
}
// SHORTHAND
yellowFruit = fruits.find((fruit) => fruit.color === "Yellow");
12、对象属性赋值
你是否希望对象键与值具有相同的名称?你可以省略对象文字来执行此操作:
let name = "king", city = "Paris", age = 43, favoriteFood = "Spaghetti";
// LONGER FORM
let person = {
name: name,
city: city,
age: age,
favoriteFood: favoriteFood
};
// SHORTHAND
let person = { name, city, age, favoriteFood };
13、压缩 For 循环
使用内置forEach()方法通过一行代码循环遍历数组:
let numbers = [1, 2, 3, 4, 5];
// LONGER FORM
for(let i = 0; i < numbers.length; i++){
console.log(numbers[i]);
}
// SHORTHAND
numbers.forEach(number => console.log(number));
ps:不要使用 iterators。使用高阶函数例如 map() 和 reduce() 替代 for-of 。
为什么?这加强了我们不变的规则。处理纯函数的回调值更易读,这比它带来的副作用 更重要。
const numbers = [1, 2, 3, 4, 5];
// LONGER FORM
let sum = 0;
for (let num of numbers) {
sum += num;
}
sum === 15;
// good SHORTHAND
let sum = 0;
numbers.forEach((num) => sum += num);
sum === 15;
// best SHORTHAND (use the functional force)
const sum = numbers.reduce((total, num) => total + num, 0);
sum === 15;
14、默认功能参数
你可以为函数参数提供默认值:
// LONG FORM
function pickUp(fruit) {
if(fruit === undefined){
console.log("I picked up a Banana");
} else {
console.log(`I picked up a ${fruit}`);
}
}
// SHORTHAND
function pickUp(fruit = "Banana") {
console.log(`I picked up a ${fruit}`)
}
pickUp("Mango"); // -> I picked up a Mango
pickUp(); // -> I picked up a Banana
15、将对象的值收集到数组中
用于Object.values()将对象的所有值收集到一个新数组中:
let info = { name: "Matt", country: "Finland", age: 18 };
// LONGER FORM
let data = [];
for (let key in info) {
data.push(info[key]);
}
// SHORTHAND
let valueDatas = Object.values(info);
//Object.keys() 可以获取对象所有的键
let keyDatas = Object.keys(info);
16、检查一个项目是否存在于数组中
使用 includes() 方法,而不是使用 indexOf() 方法来检查元素是否在数组中。可以使意图更明确:
let numbers = [1, 2, 3];
// LONGER FORM
let hasNumber1 = numbers.indexOf(1) > -1 // -> True
// SHORTHAND/CLEANER APPROACH
let hasNumber1 = numbers.includes(1) // -> True
ps: 1:indexOf查找不到NaN,用ES6 includes方法代替
let ary = [NaN];
console.log(ary.indexOf(NaN)) //-1
console.log(ary.includes(NaN)) //true
2:判断稀疏数组结果不同
let ary = [,,];
console.log(ary.indexOf(undefined))//-1
console.log(ary.includes(undefined))//true
这是因为 indexOf 认为稀疏数组,省略掉的值是不存在的,但 includes 认为是undefined
JS世界里值是否与本身不相等(NaN是唯一有这样特征的值)
数组和字符串有很多相似的对方,比如数组和字符串都有以下方法:
- concat
- indexOf
- lastIndexOf
- slice
- includes
- toString
17、压缩多个条件
避免使用长|| 检查多个条件链,你可以使用你刚刚在上一个技巧中学到的东西——即,使用 includes() 方法:
let num = 1;
// LONGER FORM
if(num == 1 || num == 2 || num == 3){
console.log("king");
}
// SHORTHAND
if([1,2,3].includes(num)){
console.log("king");
}
18、使用filter函数过滤空值
let arr = [3, 4, 5, 2, 3, undefined, null, 0, ""];
arr = arr.filter(boolean);//(5) [3, 4, 5, 2, 3]
// 或者 arr = arr.filter(item=>item)
19、使用map函数修改数据并返回新数组
let users = [
{ username: "Kelly", isVIP: true, balance: 20 },
{ username: "Tom", isVIP: false, balance: 19 },
{ username: "Stephanie", isVIP: true, balance: 30 }
];
users.map(o=>o.isVIP?(o.balance+=10,o):o); //修改原数组 users的balance值改变
// 或者 users.map(o=>o.isVIP?{...o,balance:o.balance+10}:o) //不修改原数组,users的balance值不变
ps:如果想让箭头函数向外返回一个对象,则需要将该字面量包裹在小括号里。就像这样:
let getTempItem = id => ({ id: id, name: 'Temp' });
如果一个函数适合用一行写出并只有一个参数,那就把花括号、圆括号和 return 都 省略掉。
//箭头函数没有{}时会自动return
[1, 2, 3].map(x => x * x);
对原数组修改和不修改的方法对比:
对象数组修改时的问题
疑问:上例中是map方法,按理说不会修改原数组,为什么结果还是修改了?
解答:如果操作的数组是一个对象数组,并且在 map 方法中修改了对象的属性值,那么会改变原数组中相应对象的属性值。这是因为对象在 JavaScript 中是引用类型,对象的引用被复制到新数组中,而不是对象的副本。
以下是一个示例代码:
const originalArray = [{ value: 1 }, { value: 2 }, { value: 3 }];
const newArray = originalArray.map(function(item) {
item.value *= 2;
return item;
});
console.log(originalArray); // [{ value: 2 }, { value: 4 }, { value: 6 }]
console.log(newArray); // [{ value: 2 }, { value: 4 }, { value: 6 }]
在上述示例中,我们定义了一个名为 originalArray 的对象数组。在 map 方法中,我们对每个对象的 value 属性进行修改,将其乘以 2。由于对象是引用类型,对于原始数组和新数组中的对象,它们都引用了相同的对象实例。因此,当修改某个对象的属性时,无论是通过原数组还是新数组访问该对象,结果都会相应地反映出来。
需要注意的是,虽然修改了对象的属性值,但原数组和新数组中的对象引用保持不变。因此,原数组和新数组中的对象仍然指向同一个对象实例。
如果你希望避免修改原数组中对象的属性值,可以使用 map 方法时创建对象的副本,然后对副本进行修改。这样就不会改变原数组中对象的属性值。
通过扩展运算符创建副本(详见第8条)
当处理对象数组时,可以使用 map 方法创建对象的副本,然后对副本进行修改,以避免改变原数组中对象的属性值。下面是一个示例代码:
const originalArray = [{ value: 1 }, { value: 2 }, { value: 3 }];
const newArray = originalArray.map(function(item) {
// 创建对象的副本
const newItem = { ...item };
// 修改副本的属性值
newItem.value *= 2;
return newItem;
});
console.log(originalArray); // [{ value: 1 }, { value: 2 }, { value: 3 }]
console.log(newArray); // [{ value: 2 }, { value: 4 }, { value: 6 }]
在上述示例中,我们在 map 方法中创建了对象的副本 newItem,使用对象展开运算符 { ...item } 来复制原始对象的属性,然后对副本进行修改。这样就不会影响原数组中对象的属性值。
在实际应用中,创建对象副本的方法还有其他方式,比如使用 Object.assign 方法或者手动复制对象的属性。无论采用哪种方式,目的都是为了避免直接修改原数组中对象的属性值。
20、判断用户是否全部是成年人
let users = [
{ name: "Jim", age: 23 },
{ name: "Lily", age: 17 },
{ name: "Will", age: 25 }
];
users.every(o=>o.age>=18) //true
//或者 users.some(o=>o.age<18)
ps:找出上面用户中的第一个未成年人
users.find(o=>o.age<18) //{name: "Lily", age: 17}
21、判断字符串中是否含有元音字母
let randomStr = "kingkang";
let isVowel = (char)=>["a","o","u","i","e"].includes(char);
let containsVowel = str=>[...str].some(isVowel);
containsVowel(randomStr) //true
ps:返回有元音的字母
let randomStr = "kingkang";
[...randomStr].filter(o=>{
return ['a','o','u','e','i'].includes(o)
})
//(2) ["i", "a"]
//map 用return是返回 boolean类型
[...randomStr].map(o=>{
return ['a','o','u','e','i'].includes(o)
})
//(8) [false, true, false, false, false, true, false, false]
//这也是filter和map的最大的区别,相同点是都不改变原数组
22、将下面数组转成对象
let objLikeArr = [["name", "Jim"], ["age", 18], ["single", true]];
let fromPairs = pairs =>
pairs.reduce((res, pair) => ((res[pair[0]] = pair[1]), res), {});
fromPairs(objLikeArr);
ps: reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。 reduce() 方法接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce() 的数组
//模拟reduce方法
Array.prototype.myReduce = function(func) {
let result = this[0];
for (let i = 1; i < this.length; i++) {
result = func(result, this[i], i)
}
return result
}
//表达式后面的0是额外增加的数值,若为10,结果则是6+10=16
[1, 2, 3].myReduce((total, n) => { return total + n; }, 0); //6
23、使用class简明创建一个函数
class 语法更为简洁更易读。
// LONGER FORM
function Queue(contents = []) {
this._queue = [...contents];
}
Queue.prototype.pop = function() {
const value = this._queue[0];
this._queue.splice(0, 1); return value;
}
// SHORTHAND
class Queue {
constructor(contents = []) {
this._queue = [...contents];
}
pop() {
const value = this._queue[0];
this._queue.splice(0, 1); return value; }
}
}
24、通过返回 this 来实现链式调用
// LONGER FORM
function King(){};
King.prototype.jump = function () {
this.jumping = true;
};
King.prototype.setHeight = function (height) {
this.height = height;
};
let chenjg = new King();
chenjg.jump();
chenjg.setHeight(180);
console.log(chenjg.height); //180
// SHORTHAND
class King {
jump() {
this.jumping = true;
return this;
}
setHeight(height) {
this.height = height;
return this;
}
}
let chenjg = new King();
chenjg.jump().setHeight(180);
console.log(chenjg.height); //180
25、通过sort实现对象数组的特定排序
// LONGER FORM
const objArray = [
{ id: 1, name: '小李', age: 25 },
{ id: 2, name: '小刚', age: 30 },
{ id: 3, name: '小红', age: 20 },
{ id: 4, name: '小明', age: 35 }
];
const nameOrder = ['小明', '小刚', '小红', '小李'];
const sortedArray = nameOrder.map(name => objArray.find(obj => obj.name === name));
console.log(sortedArray);
// SHORTHAND
const objArray = [
{ id: 1, name: '小李', age: 25 },
{ id: 2, name: '小刚', age: 30 },
{ id: 3, name: '小红', age: 20 },
{ id: 4, name: '小明', age: 35 }
];
const nameOrder = ['小明', '小刚', '小红', '小李'];
objArray.sort((a, b) => {
const indexA = nameOrder.indexOf(a.name);
const indexB = nameOrder.indexOf(b.name);
return indexA - indexB;
});
console.log(objArray);
//`Array.prototype.sort()`方法是 JavaScript 内置的高效排序算法,
//通常在大多数 JavaScript 引擎中进行了高度优化。因此,当数组规模较大时,
//使用`Array.prototype.sort()`方法可能提供更好的性能。