【面试题】Javascript的这些运算符,你都都掌握哪些?,前端开发基础有哪些

25 阅读9分钟

最后

除了简历做到位,面试题也必不可少,整理了些题目,前面有117道汇总的面试到的题目,后面包括了HTML、CSS、JS、ES6、vue、微信小程序、项目类问题、笔试编程类题等专题。

开源分享:docs.qq.com/doc/DSmRnRG… 在一些算法题中可以得到应用:

已知2019年的第一天是周四,求第x天是周几:

function getDay(x) {
    return [4, 5, 6, 0, 1, 2, 3][x % 7]
}

?. 可选链运算符

我们知道当读取null或者undefined的属性时,js会抛出一个TypeError

null.name // TypeError: Cannot read properties of null (reading 'name')
undefined.name //  TypeError: Cannot read properties of undefined (reading 'name')

ES2020 新增可选链运算符,可以作用在上述情况,并短路返回undefined

null?.name // undefined
undefined?.name // undefined

针对函数调用的可选链:

let personList = [    { name: 'jude' }]
// personList[0].sleep() // person.sleep is not a function
if (personList[0].sleep) {
    personList[0].sleep()
}
// better
personList[0].sleep?.() // undefined

// 如果前面的对象也可能不存在的话:
personList[1]?.sleep?.() // undefined

// 当然如果,该属性虽然存在但是不是一个函数,就会报is not a function:
personList[0]?.name() // TypeError: personList[0]?.name is not a function

也可用于方括号属性访问器 和 访问数组元素

let propertyName = 'name'
null?.[propertyName] // undefined

let arr = []
arr?.[0] // undefined

要注意的是可选链运算符不可用于赋值操作

({})?.name = 'jude' // SyntaxError: Invalid left-hand side in assignment

&& 逻辑与运算符 和 || 逻辑或运算符

从左往右,&&Falsy, ||Truthy,找到了则将返回找到的值,否则返回下一个:

1 && {} && ' ' && NaN && undefined // NaN
'' || 0 || null ||  [] || 1 // []

因此它们都属于短路运算符

&&可以用作函数的判断执行:

if (age >= 22) {
    work()
}
// or
age >= 22 && work() 

||可以用来设置备用值:

name => {
    if (name) {
        return name
    } else {
        return '未知'
    }
}
// better
name => name ? name : '未知'
// or
name => name || '未知'

以上写法都会判断name是否是Falsy来设置其默认值,而ES6默认值只会判断是否是undefined

(name = '未知') => name
// 相当于
name => name === undefined ? name : '未知'

// such as
((name = '未知') => name)(null) // null
((name = '未知') => name)(0) // 0
((name = '未知') => name)(undefined) // '未知'
((name = '未知') => name)() // '未知'

要注意的是&&的优先级高于||

1 || 1 && 0  // 1

?? 空值合并运算符

空值合并运算符??当且仅当左侧操作数为null或者undefined时才会返回右侧操作数。

上边说到逻辑或运算符||可以用来设置备用值,但其实有隐患:

function getScore(x) {
    x = x || '未知'
    console.log('张三的英语成绩是:' + x)
}
getScore(0) // '张三的英语成绩是:未知'

逻辑或运算符||会在左侧操作数为Falsy时返回右侧操作数。而0,''也属于Falsy,但是实际某些场景中它们正想要的结果,如上代码。

空值合并运算符??解决了这个问题:

function getScore(x) {
    x = x ?? '未知'
    console.log('张三的英语成绩是:' + x)
}
getScore(0) // '张三的英语成绩是:0'

常与可选链运算符?.一起用:

let person
person?.name ?? '未注册' // '未注册'

!! 双非运算符

逻辑非运算符!,会检测操作数是真值还是假值,如果是Truthy则返回false,如果是Falsy则返回true。 而双飞运算符!!,在这基础上再取反,其作用相当于Boolean()

Boolean('') // false

// or
!!'' // false

下面介绍一些位运算符

位运算符将操作数看作是4byte(32bit)二进制串。在这基础上进行运算,但最终返回十进制数字。

<< 左移操作符 和 >> 右移操作符

x << n 会将 x 转成 32 位的二进制,然后左移 n 位,左侧越界的位被丢弃:

10 * 2³ // 80

// better
10 << 3 // 80

x >> n 会将 x 转成 32 位的二进制,然后右移 n位,右侧越界的位被丢弃:

Math.floor(a / Math.pow(2,n))
// or
Math.floor(a / 2 ** n)

// better
a >> n

这在二分查找可以得以应用:

function BinarySearch(arr, target) {
    const n = arr.length
    let left = 0, 
        right = n - 1
    while (left <= right) {
      // let mid = Math.floor((left + right) / 2)
      // better
      let mid = (left + right) >> 1
      if (arr[mid] === target) {
          return mid
      } else if (arr[mid] > target) {
          right = mid - 1
      } else {
          left = mid + 1
      }
    }
    return -1
}


^ 按位异或运算符

按位异或运算符 ^ 将两边的操作数都转成32位的二进制数后,逐一比较每一位,有且仅有一个1时,则返回1

3 ^ 5 // 6
// 00000000000000000000000000000011    // 3
// 00000000000000000000000000000101    // 5
// 00000000000000000000000000000110    // 6

可以用于交换两个数值:

let a = 3,
    b = 5;
let temp = a
a = b // 5
b = temp //3

// 以上交换使用额外的内存temp,而^可以in-place原地交换:
// better
a = a ^ b
b = a ^ b // 5
a = a ^ b // 3

异或运算符^满足以下三个性质:

  1. 任何数和0做异或运算,结果仍然是原来的数字:�⊕0=�x⊕0=x
  2. 任何数和自身做异或运算,结果是0:�⊕�=0x⊕x=0
  3. 异或运算满足交换律和结合律:�⊕�⊕�=�⊕�⊕�=�⊕(�⊕�)=�⊕0=�x⊕y⊕x=y⊕x⊕x=y⊕(x⊕x)=y⊕0=y

下面是一道利用上述三个性质求解的一道算法题:

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。(你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。)

/**
 * @param {number[]} nums
 * @return {number}
 */
function singleNumber (nums) {
    let ans = 0
    for (let i = 0; i < nums.length; i++) {
        ans ^= nums[i]
    }
    return ans
};
singleNumber([4, 1, 2, 1, 2]) // 4

此外异或运算符可以用于简单的加密和解密操作。例如,我们可以将一个字符串的每个字符和一个密钥进行异或运算,得到一个加密后的字符串,然后再将加密后的字符串和密钥进行异或运算,就可以得到原来的字符串:

let str = 'Hello World'
let key = 123
let encrypted = ''
for (let i = 0; i < str.length; i++) {
  encrypted += String.fromCharCode(str.charCodeAt(i) ^ key)
}
console.log(encrypted) // '3[,	'
let decrypted = ''
for (let i = 0; i < encrypted.length; i++) {
  decrypted += String.fromCharCode(encrypted.charCodeAt(i) ^ key)
}
console.log(decrypted) // 'Hello World'

~ 按位非运算符

按位非运算符~将操作数转换成32位有符号整型,然后按位取反:

重点:

  • 计算机存储数字都以补码的形式存储(至于为什么涉及到电路,只能做加运算,不能减,因此设计了反码和补码来存储负数)
  • 正数的反码和补码等于原码
  • 负数的补码等于原码取反+1

const a = 5;     // 32位二进制:00000000000000000000000000000101
// 取反后:11111111111111111111111111111010(补码)
~a  // -6


总之: 按位非运算时,任何数字 x 的运算结果都是 -(x + 1)

因此可以用~代替!== -1的判断:

// == -1 的写法不是很好称为“抽象渗漏”,意思是在代码中暴露了底层的实现细节,这里指用-1作为失败的返回值,这些细节应该屏蔽调。————出自《你不知道的JavaSript(中卷)》
if (str.indexOf('xxx') !== -1) {}

// better
if (~str.indexOf('xxx')) {}

最后介绍很有用的...

... 扩展符

扩展符...可以在函数调用/数组构造时,将数组表达式或者 string 在语法层面展开;还可以在构造字面量对象时,将对象表达式按 key-value 的方式展开。

对象展开

只会复制目标对象的自有可枚举属性

let _a = { name: 'jude' }
let a = Object.create(
    _a, // 原型链上的属性name,不自有
    { 
        myName: { // 自有属性myName,可枚举
            value: '张三',
            enumerable: true
        },
        age: { // 自由属性age,不可枚举
            value: 30,
            enumerable: false
        }
    }
)
let b = {...a} // {myName: '张三'}

上述代码中,使用 Object.create()") 将_a作为a的原型对象,因此_a上的属性name对于a来说不是自有属性;同时给自己创建了自由属性myNameage,但是age设置为不可枚举。最后使用扩展符实现对a对象的克隆,只克隆了myName这个自有且可枚举属性。这和 Object.assign()") 的结果一样:

let c = Object.assign({}, a) // {myName: '张三'}

数组展开

用于(浅)克隆数组,对于复杂数据类型的数组项,只会克隆其引用:

let arr = [{ a: 1 }]
let copyArr = [...arr] // [{ a: 1 }]

arr[0].a = 2
copyArr // [{ a: 2 }]

用于连接数组:

let arr1 = [0, 1, 2]
let arr2 = [3, 4, 5]
let arr3 = arr1.concat(arr2) // [0, 1, 2, 3, 4, 5]
// better
let arr4 = [...arr1, ...arr2] // [0, 1, 2, 3, 4, 5]

用于函数调用:

function fn(a, b, c) { }
let args = [0, 1, 2]
fn.apply(null, args)

// better
fn(...args)

字符串展开
'123'.split('') // ['1', '2', '3']

// or
[...'123']  // ['1', '2', '3']

不能说用于类数组转数组

类数组对象是具有.length属性的对象。

Array.from()")从可迭代类数组对象创建一个新的浅拷贝的数组实例。 而在数组或函数参数中使用展开语法时,该语法只能用于 可迭代对象

let fakeArray = {
    0 : 1,
    1 : 2,
    2 : 3,
    length: 3
}
Array.from(fakeArray) // [1, 2, 3]
[...fakeArray] // TypeError: fakeArray is not iterable

或许你会问[...'123']不也是在数组中使用展开语法吗,而'123'是基本数据类型啊,怎么会可迭代呢?

其实,引擎会将'123'包装成String对象,而String对象上封装了Symbol.iterator()方法:

let str = '123';

let strIterator = str[Symbol.iterator]();
strIterator.next() // {value: '1', done: false}
strIterator.next() // {value: '2', done: false}
strIterator.next() // {value: '3', done: false}
strIterator.next() // {value: undefined, done: true}

...剩余参数

如果函数最后一个参数以...为前缀,则它将是由剩余参数组成的真数组,而arguments是伪数组:

function fn (a, ...b) {
    console.log(b)
    console.log(arguments)
}
fn(1, 2, 3) 
// [2, 3]
// Arguments(3) [1, 2, 3, callee: (...), Symbol(Symbol.iterator): ƒ]

剩余参数...可以被解构:

function fn(...[a, b, c]) {
  return a + b + c;
}
fn(1) // NaN (b and c are undefined)
fn(1, 2, 3) // 3
fn(1, 2, 3, 4) // 6

剩余参数...必须在末尾:

function fn (a, ...b, c) {} // SyntaxError: Rest parameter must be last formal parameter

... 剩余属性

在解构赋值中,剩余属性...可以获取数组或对象剩余的属性,并存储到新的数组或对象中:

const { a, ...others } = { a: 1, b: 2, c: 3 }
console.log(others) // { b: 2, c: 3 }

const [first, ...others2] = [1, 2, 3]
console.log(others2) // [2, 3]

同样,这里的...必须在末尾:

let [a , ...b , c] = [1, 2, 3]  // SyntaxError: Rest element must be last element
let { a, ...b, c } = { a: 1, b: 2, c: 3 } // SyntaxError: Rest element must be last element



### 最后

除了简历做到位,面试题也必不可少,整理了些题目,前面有117道汇总的面试到的题目,后面包括了HTMLCSSJSES6vue、微信小程序、项目类问题、笔试编程类题等专题。

* **[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://docs.qq.com/doc/DSmRnRGxvUkxTREhO)**

  ![](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/b647036b246f4f229120590c173a019b~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3MzM5MTQ5MjgwNjA=:q75.awebp?rk3s=f64ab15b&x-expires=1771317254&x-signature=XnKjFv82ukih4z%2BwSHgzp9DGz%2Bk%3D)


![](https://p3-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/9484ba5f808b47c8bd6a6abe1d453d3d~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3MzM5MTQ5MjgwNjA=:q75.awebp?rk3s=f64ab15b&x-expires=1771317254&x-signature=J%2FcsQDJIM9PVmLY7RvnVoLSVc7o%3D)