JavaScript开发技巧必备【一】

1、使用 Object.entries() 和 Object.fromEntries()

// 将对象转换为数组并转换回来,以便于操作
const person = { name: 'jack', age: 20 };
const entries = Object.entries(person); // [['name', 'jack'],['age', 20]]
const newPerson = Object.fromEntries(entries); // { name: 'jack', age: 20 }

2、new Set()数组去重

const numbers = [1, 1, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)]; // [1, 2, 3, 4, 5]

3、使用 bind() 进行函数柯里化

function multiply(a, b) {
  return a * b;
}
const double = multiply.bind(null, 2);
console.log(double(5)); // 10

bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

上面代码也可以改成如下:

function multiply(a, b) {
  return a * b;
}
const double = multiply.bind(null, 2, 5);
console.log(double()); // 10

4、使用 Array.from() 将类似数组或可迭代的对象转换为真正的数组

// 将类似数组或可迭代的对象转换为真正的数组。
let nodeList = document.querySelectorAll('a');
let nodeArray = Array.from(nodeList);

5、可迭代对象的 for…of 循环

// 直接迭代可迭代对象(包括数组、映射、集合等)。
for (const value of ['a', 'b']) {
  console.log(value); 
}

结果:

a
b

6、使用 ^ 交换值

let b = 4;
a ^= b;
b ^= a;
a ^= b;
console.log(a, b); // a = 4, b = 2

这段JavaScript代码使用了位异或(XOR)运算符(^=)来实现两个变量a和b的数值交换,而不需要引入第三个临时变量。位异或运算符^对两个数的二进制表示进行操作,如果两个相应的二进制位不相同,则结果为1;如果相同,则结果为0。

解析:

1.初始状态:

let a = 2; // 二进制表示为 010  
let b = 4; // 二进制表示为 100

2.第一步:a ^= b;

执行a ^= b;相当于a = a ^ b;。这里,a和b进行位异或操作。

  • a的二进制是010
  • b的二进制是100
  • a ^ b的结果是110(因为只有在一位上两者不相同时,结果才为1)
  • 因此,a的新值是110(即十进制的6)
    现在
a = 6; // 二进制表示为 110  
b = 4; // 二进制表示为 100

3.第二步:b ^= a;

执行b ^= a;,即b = b ^ a;。

  • b的二进制是100
  • a(更新后)的二进制是110
  • b ^ a的结果是010(因为只有在b的第三位和a的第二位上两者不同时,结果才为1)
  • 因此,b的新值是010(即十进制的2,它原来是a的值)
    现在,
a = 6; // 二进制表示为 110  
b = 2; // 二进制表示为 010

4.第三步:a ^= b;

再次执行a ^= b;,即a = a ^ b;。

  • a的二进制是110
  • b(更新后)的二进制是010
  • a ^ b的结果是100(因为只有在a的第二位和b的第二位上两者不同时,结果才为1,其他位上相同则为0)
  • 因此,a的新值是100(即十进制的4,它原来是b的值)
    最终,
a = 4; // 二进制表示为 100  
b = 2; // 二进制表示为 010

7、使用 flat() 展平数组

// 使用 flat() 方法轻松展平嵌套数组,展平深度作为可选参数。
const arr = [1, [2, [3, [4]]]];
const flatArr = arr.flat(Infinity);
console.log(flatArr); // [1, 2, 3, 4]

const arr2 = [{ a: 1 }, [3, { b: 2 }, [{ c: 3 }]]];
const flatArr2 = arr2.flat(Infinity);
console.log(flatArr2); // [{a:1},3,{b:2},{c:3}]

8、使用+将字符串转换成数字

// 使用一元加法运算符快速将字符串或其他值转换为数字。
const str = '123';
const num = +str;
console.log(num, typeof num); // 123 'number'

注意:不过其只适合用于字符串数据,否则将返回NaN

当然我们可以将该功能写成一个函数,代码如下:

function toNumber(strNumber) {
    return +strNumber;
}
console.log(toNumber("1234")); // 1234
console.log(toNumber("ACB")); // NaN

这个也适用于Date,在本例中,它将返回的是时间戳数字:

console.log(+new Date()) // 1461288164385

9、Function.prototype.bind() 的强大功能

// 将函数绑定到上下文(此值)并部分应用参数,创建更可重用和模块化的代码。
const print = function (msg, mark) {
  return `${msg}, ${this.name}${mark}`;
};
const printInfo = print.bind({ name: 'Tom' }, 'Hello');
console.log(printInfo('!')); // "Hello, Tom!"

10、Object.freeze()防止对象修改

// 使用 Object.freeze() 防止对对象的修改,使其不可变。为了实现更深层次的不变性,请考虑更彻底地实施不变性的库或者递归调用freeze。
const obj = { name: 'Tom' };
Object.freeze(obj);
obj.name = 'Jack'; // error:Cannot assign to read only property 'name' of object '#<Object>'

11、使用Web Workers提高响应性和性能

于复杂和耗时的任务,可以考虑使用Web Workers。Web Workers允许你在后台线程中运行JavaScript代码,这样就不会阻塞主线程,提高了应用的响应性和性能。

主线程中的耗时任务:

// 假设这是一个耗时的计算任务
function longRunOpert() {
    let result = 0;
    for (let i = 0; i < 10000; i++) {
        result += i;
    }
    return result;
}
console.log(longRunOpert());

使用Web Worker,main.js代码:

import Worker from './app.worker.js';

const worker = new Worker();
worker.postMessage({ code: 'start' });// 告诉worker开始执行代码
worker.onmessage = function (e) {
  console.log(`Result: ${e.data}`);
};
console.log('others things');

app.worker.js代码:

self.onmessage = function (e) {
  if (e.data.code === 'start') {
    let result = 0;
    for (let i = 0; i < 10000; i++) {
      result += i;
    }
    self.postMessage(result);
  }
};

最终打印结果以及顺序:

others things
Result: 49995000

注:在vue工程中需要安装worker-loader,并进行配置,如下webpack中rules配置

module: {
		rules: [
			.....
			{
				test: /\.worker\.js$/,
				use: { loader: 'worker-loader' }
			}
			.....
		]
	},

12、 使用事件委托,避免大量的子元素添加事件监听器

当为大量的子元素添加事件监听器会增加内存的使用,导致页面响应变慢。事件委托是一种技术,它利用了事件冒泡的原理

我们只在父元素上设置一个事件监听器来管理所有子元素的事件。这种方法不仅减少了内存的使用,还简化了事件处理器的管理。

不推荐的逐个添加事件监听器(vue3):

<template>
	<div class="container-main">
		<div class="list">
			<ul>
				<li v-for="(item, index) in list" :key="item.id" class="list-item" @click="clickLi(item)">{{ item.title }}</li>
			</ul>
		</div>
	</div>
</template>

<script setup>
import { reactive } from 'vue';

const list = reactive([
  { title: '列表一', id: 1 },
  { title: '列表二', id: 2 },
  { title: '列表三', id: 3 }
]);

const clickLi = (item) => {
  console.log('li clicked');
  console.log(item); // 当前对应的列表数据
};
</script>

推荐的事件委托方式(vue3):

<template>
	<div class="container-main">
		<div class="list">
			<ul @click="clickLi">
				<li v-for="(item, index) in list" :key="item.id" class="list-item" :data-index="index">{{ item.title }}</li>
			</ul>
		</div>
	</div>
</template>

<script setup>
import { reactive } from 'vue';

const list = reactive([
  { title: '列表一', id: 1 },
  { title: '列表二', id: 2 },
  { title: '列表三', id: 3 }
]);

const clickLi = (e) => {
  if (e.target.classList.contains('list-item')) {
    console.log('li clicked');
    const index = e.target.getAttribute('data-index');
    const curLiObj = list[index];
    console.log(curLiObj); // 当前对应的列表数据
  }
};
</script>

从这儿可以看出,我们把事件绑定到了 ul上,当我们的数据特别多的时候,建议可以这样去做,当然数据量小的情况下,第一种是我们习惯的编写方式。

原生代码操作DOM方式,不推荐的逐个添加事件监听器:

document.querySelectorAll('.button').forEach(button => {
    button.addEventListener('click', function() {
        console.log('Button clicked!');
    });
});

原生代码操作DOM方式,推荐的事件委托方式:

document.addEventListener('click', function(e) {
    if (e.target.classList.contains('button')) {
        console.log('Button clicked!');
    }
});

13、在循环中缓存array.length

这个技巧很简单,这个在处理一个很大的数组循环时,对性能影响将是非常大的。基本上,大家都会写一个这样的同步迭代的数组:

for(let i = 0; i < array.length; i++) {
    console.log(array[i]);
}

如果是一个小型数组,这样做很好,如果你要处理的是一个大的数组,这段代码在每次迭代都将会重新计算数组的大小,这将会导致一些延误。为了避免这种现象出现,可以将array.length做一个缓存:

const length = array.length;
for(let i = 0; i < length; i++) {
    console.log(array[i]);
}

你也可以写在这样:

for(let i = 0, length = array.length; i < length; i++) {
    console.log(array[i]);
}

14、获取数组中最后一个元素

Array.prototype.slice(begin,end)用来获取begin和end之间的数组元素。如果你不设置end参数,将会将数组的默认长度值当作end值。但有些同学可能不知道这个函数还可以接受负值作为参数。如果你设置一个负值作为begin的值,那么你可以获取数组的最后一个元素。如:

var array = [1,2,3,4,5,6];
console.log(array.slice(-1)); // [6]
console.log(array.slice(-2)); // [5,6]
console.log(array.slice(-3)); // [4,5,6]

15、数组截断

这个小技巧主要用来锁定数组的大小,如果用于删除数组中的一些元素来说,是非常有用的。例如,你的数组有10个元素,但你只想只要前五个元素,那么你可以通过array.length=5来截断数组。如下面这个示例:

let array = [1,2,3,4,5,6];
console.log(array.length); // 6
array.length = 3;
console.log(array.length); // 3
console.log(array); // [1,2,3]

16、合并数组

如果你要合并两个数组,一般情况之下你都会使用Array.concat()函数:

const array1 = [1,2,3];
const array2 = [4,5,6];
console.log(array1.concat(array2)); // [1,2,3,4,5,6];

然后这个函数并不适合用来合并两个大型的数组,因为其将消耗大量的内存来存储新创建的数组。在这种情况之个,可以使用Array.push().apply(arr1,arr2)来替代创建一个新数组。这种方法不是用来创建一个新的数组,其只是将第一个第二个数组合并在一起,同时减少内存的使用:

const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
array1.push.apply(array1, array2); // 6
console.log(array1); // [1,2,3,4,5,6];