阅读 1224
【青训营】月影老师告诉我写好JavaScript的四大技巧——妙用特性

【青训营】月影老师告诉我写好JavaScript的四大技巧——妙用特性

如何写好JavaScript是每一个前端工程师一直以来在思考的问题,月影老师告诉我们一些写好JavaScript的原则,同时也教了一些我们如何写好JavaScript的技巧,今天来继续跟着月影老师学JavaScript吧~~

起步

我们平时在刷题或者网上找资料的时候,得到的很多答案可能都不是用JavaScript实现的,我们就直接照搬翻译成JavaScript过来了,其实JavaScript有很多自己的特性,用的好的话直接起飞~

话不多说,我们来看案例

案例1:判断4的幂

菜鸟版

这题拿到太简单了,直接循环除以4最后能除尽就是啦

① 负数直接返回 false

② 对n进行循环除以4的遍历,只要有一次不能被4整除,就返 false

③ 否则返回 true

var isPowerOfFour = function(n) {
    if( n < 1) return false 
    while(n > 1){
        if(n % 4) return false
        n /= 4
    }
    return true
};
复制代码

看看LeetCode上执行的结果

image.png

还不错,够用了~

菜鸟学废版

我们使用位运算来提高效率啊

一个数x模4就相当于 x 的二进制形式 与 二进制数 11 也就是十进制的 3 按位相,(与运算是两个数都为1结果才为1,否则结果都是0)因为11前面都是0,所以x的二进制形式最后只剩最后两位,最后两位 与 11 做与运算,结果还是自身(x的二进制形式的最后两位),所以 n % 4n & 3 是一样的;

一个数除以4,相当于把他的二进制形式向右移动2位(向右移动一位相当于除以2),所以 n / 4n >>> 2 是一样的

var isPowerOfFour = function(n) {
    if( n < 1) return false 
    while(n > 1){
        if(n & 3) return false
        n >>>= 2
    }
    return true
};
复制代码

看看LeetCode上执行的结果

image.png

进阶版

4的幂的前提是必须是2的幂,2的幂的前提必须是非负数

我们这里有三个判断,全部符合才是true

function isPowerOfFour(n) { 
    return n > 0 && (n & (n - 1)) === 0 && (n & 0xAAAAAAAA) === 0; 
}
复制代码

n > 0 —— > 必须是非负数

(n & (n - 1)) === 0 ——> 必须是2的幂

n & (n - 1)之前我们说过这样操作可以将 n 二进制表示的最低位 1 移除

我们再来看看2的幂数的二进制表示

1 0000 0001

2 0000 0010

4 0000 0100

8 0000 1000

16 0001 0000

32 0100 0000

64 1000 0000

所以满足 (n & (n - 1)) === 0 的数 都是 2 的幂

【注意】这里位元算外面的括号,由于位运算的优先级比较低,所以这个括号是不可省略的

(n & 0xAAAAAAAA) === 0 ——> 在2的幂的基础上必须是4的幂

0xAAAAAAAA也就是0b1010101010101010 ,它的所有偶数二进制位都是 0,所有奇数二进制位都是 1。 这样一来,我们将 n 和 0xAAAAAAAA 进行按位运算,如果结果为 0,说明 n 二进制表示中的 1 出现在偶数的位置,这样就排除了 2, 8, 32 之类的是2的幂不是4的幂的数字。

看看LeetCode上执行的结果

image.png

效率不咋地啊 不过多测几次还不一样

image.png

image.png

JS特性技巧版

都做到位运算了,为什么不更深层次的看看4的幂的数的二进制的特点呢? 1 0000 0001

4 0000 0100

16 0001 0000

64 0100 0000

4的幂的二进制都是1开头,后面偶数个零这样的形式

我们将n转换成2进制,然后 用 正则匹配结果作为返回值即可

function isPowerOfFour(n) { 
    n = n.toString(2) 
    return /^1(?:00)*$/.test(n) 
}
复制代码

看看LeetCode上执行的结果 image.png

还可以的样子啊~

案例2:深拷贝

平平无奇普通版

function deepClone(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] = deepClone(obj[key])
		}
	}
	return result
}
复制代码

JS特性技巧版

我们用JSON这个API就很简单啦

function deepClone1(target) {
  // 通过数据创建JSON格式的字符串
  let str = JSON.stringify(target);
  // 将 JSON 字符串创建为JS数据
  let data = JSON.parse(str);
  return data;
}
复制代码

写成一行就是这样啦~

function deepClone1(target) {
  return JSON.parse(JSON.stringify(target));
}
复制代码

案例3:归并排序

《算法(第四版)》Java直译版

在《算法(第四版)》中关于归并排序的代码从Java翻译成JavaScript就是这样

/**
 * 排序数组
 * @param {number[]} nums
 * @return {number[]}
 */
var sortArray = function (nums) {
  const N = nums.length;
  let temp = new Array();
  mergeSort(nums, 0, N - 1, temp);
  return nums;
};

/**
 * 归并排序  采用自顶向下的递归方法
 * @param {number[]} nums
 * @param {number} left
 * @param {number} right
 * @param {number[]} temp
 * @returns
 */
var mergeSort = function (nums, left, right, temp) {
  // 如果指针重叠了就返回
  if (left >= right) {
    return;
  }

  // let mid = Math.floor(left + (right - left) / 2);
  let mid = (left + right) >> 1;
  // 递归调用mergeSort
  mergeSort(nums, left, mid, temp);
  mergeSort(nums, mid + 1, right, temp);
  // 归并两个有序数组
  merge(nums, left, mid, right, temp);
};

/**
 * 归并两个有序数组
 * @param {number[]} nums
 * @param {number} left
 * @param {number} mid
 * @param {number} right
 * @param {number[]} temp
 */
var merge = function (nums, left, mid, right, temp) {
  // 将nums复制到temp中去
  for (let k = left; k <= right; k++) {
    temp[k] = nums[k];
  }

  // 给两个数组分别定义一个指针
  let i = left;
  let j = mid + 1;

  // 将temp中的元素按规则写回nums
  for (let k = left; k <= right; k++) {
    if (i > mid) {
      // 左半边取尽,取右半边元素
      nums[k] = temp[j];
      j++;
    } else if (j > right) {
      // 右半边取尽,取左半边元素,左指针右移
      nums[k] = temp[i];
      i++;
    } else if (temp[i] <= temp[j]) {
      // 谁小就取谁 ,左边小
      nums[k] = temp[i];
      i++;
    } else {
      // 右边小
      nums[k] = temp[j];
      j++;
    }
  }
};

复制代码

其实在JavaScript数组有很多自带的方法,使得我们的数组可以直接当作队列或者栈才操作,用起来很方便

JS特性技巧版

使用JavaScript中数组的一些方法,使得归并排序实现起来异常简单,不用涉及过多的指针问题

更多内容我在之前的博文中有所介绍【算法】经典排序算法总结-JavaScript描述-图解-复杂度分析-插入-快速-归并-堆排序等

/**
 * 归并排序算法
 * @param {*} arr 数组
 * @returns 有序数组
 */
function mergeSort(arr) {
  // 采用自顶向下的递归方法
  const N = arr.length;
  if (N < 2) {
    // 递归出口,数组只有一个元素,直接返回这个数组
    return arr;
  }
  // x >> 1 是位运算中的右移运算,表示右移一位,等同于 x 除以 2 再取整,即 x >> 1 === Math.floor(x / 2)
  // N >> 1 和 Math.floor(N / 2) 等价
  let middle = N >> 1;
  // 拆分为两个子数组
  let left = arr.slice(0, middle);
  let right = arr.slice(middle);
  // 递归调用mergeSort
  return merge(mergeSort(left), mergeSort(right));
}


/**
 * 对两个有序数组进行合并操作
 * @param {*} left left数组
 * @param {*} right right数组
 * @returns 一个有序数组temp
 */
function merge(left, right) {
  // 临时数组存储归并后的数据
  const temp = [];
  // 两个数组都还没有遍历结束
  while (left.length && right.length) {
    // 注意: 判断的条件是小于或等于,如果只是小于,那么排序将不稳定.
    if (left[0] <= right[0]) {
      // left[0]小,删除left数组中第一项left[0],并将它放入temp数组中
      temp.push(left.shift());
    } else {
      // 删除right数组中第一项,并将它放入temp数组中
      temp.push(right.shift());
    }
  }
  // left数组还有元素,right数组遍历完了
  while (left.length) {
    // 将left数组剩下的元素都放入temp数组中
    temp.push(left.shift());
  }
  // right数组还有元素,left数组遍历完了
  while (right.length) {
    temp.push(right.shift());
  }
  // 返回排序好的数组
  return temp;
}
复制代码

一些小技巧

转进制

将十进制数转换成各种进制

124.toString(12)
复制代码

交换元素

let a = 1, b = 2
复制代码

一般情况下是这样操作的

let temp = a
a = b
b = temp
复制代码

JavaScript可以直接这样交换元素的值

[a, b] = [b ,a]
复制代码

JavaScript的自有特性当然远远不止这些,后面我也会继续进行归纳总结的~

总结

  1. 一些位元算可以提高代码的执行效率,可以在数字的一些运算的时候考虑他们的二进制
  2. JavaScript中有很多好用的特性,我们要会利用好
  3. 后面我会再总结关于JavaScript位运算与小技巧的博文,敬请期待

更多相关博文

【青训营】月影老师告诉我写好JavaScript的三大原则——各司其责

【青训营】月影老师告诉我写好JavaScript的三大原则——组件封装

【青训营】月影老师告诉我写好JavaScript的三大原则——过程抽象

【青训营】月影老师告诉我写好JavaScript的四大技巧——风格优先

【青训营】月影老师告诉我写好JavaScript的四大技巧——保证正确

【青训营】月影老师告诉我写好JavaScript的四大技巧——封装函数

【青训营】月影老师告诉我写好JavaScript的四大技巧——妙用特性

也可以关注专栏: 【青训营笔记专栏】

文章分类
阅读
文章标签