最后
除了简历做到位,面试题也必不可少,整理了些题目,前面有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
异或运算符^满足以下三个性质:
- 任何数和0做异或运算,结果仍然是原来的数字:�⊕0=�x⊕0=x
- 任何数和自身做异或运算,结果是0:�⊕�=0x⊕x=0
- 异或运算满足交换律和结合律:�⊕�⊕�=�⊕�⊕�=�⊕(�⊕�)=�⊕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来说不是自有属性;同时给自己创建了自由属性myName和age,但是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道汇总的面试到的题目,后面包括了HTML、CSS、JS、ES6、vue、微信小程序、项目类问题、笔试编程类题等专题。
* **[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://docs.qq.com/doc/DSmRnRGxvUkxTREhO)**

