最后前端到底应该怎么学才好?
如果你打算靠自己摸索自学,那么你首先要了解学习前端的基本大纲,这是你将要学习的主要内容,理解以及掌握好这些内容,便可以找到一份初级的前端开发工作。你还需要有一套完整的前端学习教程,作为初学者最好的方式就是看视频教程学习,初学者容易理解接受。
不要选择买书学习,这样的方式没有几个人能学会,基本都是看不下去书,也看不懂书。如果喜欢看书的学弟,可以买一些经典的书籍作为辅助即可,主要还是以看教程为主。每天抽出固定几个小时学习,做好长期学习的准备。学习编程并不是每天光看视频,你学习编程最重要的目的是为了编写软件产品,提供给大众使用,所以用手写出代码实现功能才是我们要做的事情。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
这意味着您的代码可能会因为大量await
的promises相继发生而变慢。每个await
都会等待前一个完成,而你实际想要的是所有的这些promises同时开始处理(就像我们没有使用async/await
时那样)。
有一种模式可以缓解这个问题——通过将 Promise
对象存储在变量中来同时开始它们,然后等待它们全部执行完毕。如果想更加深入的了解,请参考 MDN[1]
ES2018(ES9)
===========
Object Rest & Spread
在 ES9 新增 Object 的 Rest & Spread 方法,直接看下示例:
const input = {
a: 1,
b: 2,
c: 3,
}
const output = {
…input,
c: 4
}
console.log(output) // {a: 1, b: 2, c: 4}
这块代码展示了 spread 语法,可以把 input 对象的数据都拓展到 output 对象,这个功能很实用。需要注意的是,如果存在相同的属性名,只有最后一个会生效。
注意点
const obj = { x: { y: 10 } };
const copy1 = { …obj };
const copy2 = { …obj };
obj.x.y = “jimmy”;
console.log(copy1, copy2); // x: {y: “jimmy”} x: {y: “jimmy”}
console.log(copy1.x === copy2.x); // → true
如果属性的值是一个对象的话,该对象的引用会被拷贝,而不是生成一个新的对象。
我们再来看下 Object rest
的示例:
const input = {
a: 1,
b: 2,
c: 3
}
let { a, …rest } = input
console.log(a, rest) // 1 {b: 2, c: 3}
当对象 key-value 不确定的时候,把必选的 key 赋值给变量,用一个变量收敛其他可选的 key 数据,这在之前是做不到的。注意,rest 属性必须始终出现在对象的末尾,否则将抛出错误。
for await of
异步迭代器(for-await-of):循环等待每个Promise对象变为resolved状态才进入下一步。
我们知道 for…of 是同步运行的,看如下代码
function TimeOut(time){
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(time)
}, time)
})
}
async function test() {
let arr = [TimeOut(2000), TimeOut(1000), TimeOut(3000)]
for (let item of arr) {
console.log(Date.now(),item.then(console.log))
}
}
test()
上面打印结果如下图
1640436987(1).png
上述代码证实了 for of 方法不能遍历异步迭代器,得到的结果并不是我们所期待的,于是 for await of 就粉墨登场啦!
ES9 中可以用 for…await…of 的语法来操作
function TimeOut(time) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(time)
}, time)
})
}
async function test() {
let arr = [TimeOut(2000), TimeOut(1000), TimeOut(3000)]
for await (let item of arr) {
console.log(Date.now(), item)
}
}
test()
// 1560092345730 2000
// 1560092345730 1000
// 1560092346336 3000
for await of 环等待每个Promise对象变为resolved状态才进入下一步。所有打印的结果为 2000,1000,3000
Promise.prototype.finally()
Promise.prototype.finally() 方法返回一个Promise,在promise执行结束时,无论结果是fulfilled或者是rejected,在执行then()和catch()后,都会执行finally指定的回调函数。这为指定执行完promise后,无论结果是fulfilled还是rejected都需要执行的代码提供了一种方式,避免同样的语句需要在then()和catch()中各写一次的情况。
示例
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(‘success’)
// reject(‘fail’)
}, 1000)
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
}).finally(() => {
console.log(‘finally’)
})
使用场景
loading关闭
需要每次发送请求,都会有loading提示,请求发送完毕,就需要关闭loading提示框,不然界面就无法被点击。不管请求成功或是失败,这个loading都需要关闭掉,这时把关闭loading的代码写在finally里再合适不过了
String 扩展
放松对标签模板里字符串转义的限制, 遇到不合法的字符串转义会返回undefined,并且从raw上可获取原字符串。
下面是一个es6 的标签模板 如果对这个语法感到陌生,请参考 标签模板[2]
const foo = (a, b, c) => {
console.log(a)
console.log(b)
console.log©
}
const name = ‘jimmy’
const age = 18
foo 这是${name},他的年龄是${age}岁
参数打印如下:
ES9开始,模板字符串允许嵌套支持常见转义序列,移除对ECMAScript在带标签的模版字符串中转义序列的语法限制。
function foo(a, b, c) {
console.log(a, b, c)
}
// 在标签函数中使用
// unicode字符\u{61} 对应的值为 a
// unicode字符\u{62} 对应的值为 b
// \unicode 是一个无效的unicode字符
foo \u{61} and \u{62}
foo \u{61} and \unicode
1640441321(1).png
注意点
在模板字符串中,如果输入无效的unicode字符,还是会报错。只有在便签模板中 从es9开始才不会报错。
let string = \u{61} and \unicode
;
console.log(string); // Uncaught SyntaxError: Invalid Unicode escape sequence
ES2019(ES10)
============
Object.fromEntries()
方法 Object.fromEntries() 把键值对列表转换为一个对象,这个方法是和 Object.entries() 相对的。
Object.fromEntries([
[‘foo’, 1],
[‘bar’, 2]
])
// {foo: 1, bar: 2}
案例1:Object 转换操作
const obj = {
name: ‘jimmy’,
age: 18
}
const entries = Object.entries(obj)
console.log(entries)
// [Array(2), Array(2)]
// ES10
const fromEntries = Object.fromEntries(entries)
console.log(fromEntries)
// {name: “jimmy”, age: 18}
案例2:Map 转 Object
const map = new Map()
map.set(‘name’, ‘jimmy’)
map.set(‘age’, 18)
console.log(map) // {‘name’ => ‘jimmy’, ‘age’ => 18}
const obj = Object.fromEntries(map)
console.log(obj)
// {name: “jimmy”, age: 18}
案例3:过滤
course表示所有课程,想请求课程分数大于80的课程组成的对象:
const course = {
math: 80,
english: 85,
chinese: 90
}
const res = Object.entries(course).filter(([key, val]) => val > 80)
console.log(res) // [ [ ‘english’, 85 ], [ ‘chinese’, 90 ] ]
console.log(Object.fromEntries(res)) // { english: 85, chinese: 90 }
案例4:url的search参数转换
// let url = “https://www.baidu.com?name=jimmy&age=18&height=1.88”
// queryString 为 window.location.search
const queryString = “?name=jimmy&age=18&height=1.88”;
const queryParams = new URLSearchParams(queryString);
const paramObj = Object.fromEntries(queryParams);
console.log(paramObj); // { name: ‘jimmy’, age: ‘18’, height: ‘1.88’ }
Array.prototype.flat()
语法
let newArray = arr.flat([depth])
depth
可选
指定要提取嵌套数组的结构深度,默认值为 1。
示例
flat()
方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat()); // [0, 1, 2, 3, 4]
const arr2 = [0, 1, 2, [[[3, 4]]]];
console.log(arr2.flat(2)); // [0, 1, 2, [3, 4]]
//使用 Infinity,可展开任意深度的嵌套数组
var arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// flat()
方法会移除数组中的空项:
var arr5 = [1, 2, , 4, 5];
arr5.flat(); // [1, 2, 4, 5]
Array.prototype.flatMap()
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。从方法的名字上也可以看出来它包含两部分功能一个是 map,一个是 flat(深度为1)。
语法
var new_array = arr.flatMap(function callback(currentValue[, index[, array]]) {
// 返回新数组的元素
}[, thisArg])
callback
可以生成一个新数组中的元素的函数,可以传入三个参数:
currentValue
当前正在数组中处理的元素
index
可选 数组中正在处理的当前元素的索引。
array
可选 被调用的 map
数组
thisArg
可选
执行 callback
函数时 使用的this
值。
示例
const numbers = [1, 2, 3]
numbers.map(x => [x * 2]) // [[2], [4], [6]]
numbers.flatMap(x => [x * 2]) // [2, 4, 6]
这个示例可以简单对比下 map 和 flatMap 的区别。当然还可以看下下面的示例:
let arr = [‘今天天气不错’, ‘’, ‘早上好’]
arr.map(s => s.split(‘’))
// [[“今”, “天”, “天”, “气”, “不”, “错”],[“”],[“早”, “上”, “好”]]
arr.flatMap(s => s.split(‘’))
// [“今”, “天”, “天”, “气”, “不”, “错”, “”, “早”, “上”, “好”]
flatMap
方法与 map
方法和深度depth为1的 flat
几乎相同.
String.prototype.trimStart()
trimStart() 方法从字符串的开头删除空格,trimLeft()是此方法的别名。
let str = ’ foo ’
console.log(str.length) // 8
str = str.trimStart() // 或str.trimLeft()
console.log(str.length) // 5
String.prototype.trimEnd()
trimEnd() 方法从一个字符串的右端移除空白字符,trimRight 是 trimEnd 的别名。
let str = ’ foo ’
console.log(str.length) // 8
str = str.trimEnd() // 或str.trimRight()
console.log(str.length) // 6
可选的Catch Binding
在 ES10 之前我们都是这样捕获异常的:
try {
// tryCode
} catch (err) {
// catchCode
}
在这里 err 是必须的参数,在 ES10 可以省略这个参数:
try {
console.log(‘Foobar’)
} catch {
console.error(‘Bar’)
}
应用
验证参数是否为json格式
这个需求我们只需要返回true或false,并不关心catch的参数。
const validJSON = json => {
try {
JSON.parse(json)
return true
} catch {
return false
}
}
Symbol.prototype.description
我们知道,Symbol 的描述只被存储在内部的 Description
,没有直接对外暴露,我们只有调用 Symbol 的 toString() 时才可以读取这个属性:
const name = Symbol(‘es’)
console.log(name.toString()) // Symbol(es)
console.log(name) // Symbol(es)
console.log(name === ‘Symbol(es)’) // false
console.log(name.toString() === ‘Symbol(es)’) // true
现在可以通过 description 方法获取 Symbol 的描述:
const name = Symbol(‘es’)
console.log(name.description) // es
name.description = “es2” // 只读属性 并不能修改描述符
console.log(name.description === ‘es’) // true
// 如果没有描述符 输入undefined
const s2 = Symbol()
console.log(s2.description) // undefined
JSON.stringify() 增强能力
JSON.stringify 在 ES10 修复了对于一些超出范围的 Unicode 展示错误的问题。因为 JSON 都是被编码成 UTF-8,所以遇到 0xD800–0xDFFF 之内的字符会因为无法编码成 UTF-8 进而导致显示错误。在 ES10 它会用转义字符的方式来处理这部分字符而非编码的方式,这样就会正常显示了。
// \uD83D\uDE0E emoji 多字节的一个字符
console.log(JSON.stringify(‘\uD83D\uDE0E’)) // 打印出笑脸
// 如果我们只去其中的一部分 \uD83D 这其实是个无效的字符串
// 之前的版本 ,这些字符将替换为特殊字符,而现在将未配对的代理代码点表示为JSON转义序列
console.log(JSON.stringify(‘\uD83D’)) // “\ud83d”
修订 Function.prototype.toString()
以前函数的toString方法来自Object.prototype.toString(),现在的 Function.prototype.toString() 方法返回一个表示当前函数源代码的字符串。以前只会返回这个函数,不包含注释、空格等。
function foo() {
// es10新特性
console.log(‘imooc’)
}
console.log(foo.toString())
// 打印如下
// function foo() {
// // es10新特性
// console.log(“imooc”);
// }
将返回注释、空格和语法等详细信息。
ES2020(ES11)
============
空值合并运算符(Nullish coalescing Operator)
空值合并操作符( ??
)是一个逻辑操作符,当左侧的操作数为 null
或者undefined
时,返回其右侧操作数,否则返回左侧操作数。
const foo = undefined ?? “foo”
const bar = null ?? “bar”
console.log(foo) // foo
console.log(bar) // bar
与逻辑或操作符(||
)不同,逻辑或操作符会在左侧操作数为假值时返回右侧操作数。也就是说,如果使用 ||
来为某些变量设置默认值,可能会遇到意料之外的行为。比如为假值(例如''
,0
,NaN
,false
)时。见下面的例子。
const foo = “” ?? ‘default string’;
const foo2 = “” || ‘default string’;
console.log(foo); // “”
console.log(foo2); // “default string”
const baz = 0 ?? 42;
const baz2 = 0 || 42;
console.log(baz); // 0
console.log(baz2); // 42
注意点
将 ??
直接与 AND(&&
)和 OR(||
)操作符组合使用是不可取的。
null || undefined ?? “foo”; // 抛出 SyntaxError
true || undefined ?? “foo”; // 抛出 SyntaxError
可选链 Optional chaining
介绍
可选链操作符( ?.
)允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?.
操作符的功能类似于 .
链式操作符,不同之处在于,在引用为 null
或者 undefined
的情况下不会引起错误,该表达式短路返回值是 undefined
。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined
。
当尝试访问可能不存在的对象属性时,可选链操作符将会使表达式更短、更简明。在探索一个对象的内容时,如果不能确定哪些属性必定存在,可选链操作符也是很有帮助的。
const user = {
address: {
street: ‘xx街道’,
getNum() {
return ‘80号’
}
}
}
在之前的语法中,想获取到深层属性或方法,不得不做前置校验,否则很容易命中 Uncaught TypeError: Cannot read property...
这种错误,这极有可能让你整个应用挂掉。
const street = user && user.address && user.address.street
const num = user && user.address && user.address.getNum && user.address.getNum()
console.log(street, num)
用了 Optional Chaining ,上面代码会变成
const street2 = user?.address?.street
const num2 = user?.address?.getNum?.()
console.log(street2, num2)
可选链中的 ? 表示如果问号左边表达式有值, 就会继续查询问号后面的字段。根据上面可以看出,用可选链可以大量简化类似繁琐的前置校验操作,而且更安全。
常见用法
// 对象中使用
let obj = {
name: “jimmy”,
age: “18”,
};
let property = “age”;
let name = obj?.name;
let age = obj?.age;
let ages = obj?.[property];
let sex = obj?.sex;
console.log(name); // jimmy
console.log(age); // 18
console.log(ages); // 18
console.log(sex); // undefined
// 数组中使用
let arr = [1,2,2];
let arrayItem = arr?.[42]; // undefined
// 函数中使用
let obj = {
func: function () {
console.log(“I am func”);
},
};
obj?.func(); // I am func
与空值合并操作符一起使用
let customer = {
name: “jimmy”,
details: { age: 18 }
};
let customerCity = customer?.city ?? “成都”;
console.log(customerCity); // “成都”
注意点
可选链不能用于赋值
let object = {};
object?.property = 1; // Uncaught SyntaxError: Invalid left-hand side in assignment
globalThis
在以前,从不同的 JavaScript 环境中获取全局对象需要不同的语句。在 Web 中,可以通过 window
、self
取到全局对象,在 Node.js 中,它们都无法获取,必须使用 global
。
在松散模式下,可以在函数中返回 this
来获取全局对象,但是在严格模式和模块环境下,this
会返回 undefined
。
以前想要获取全局对象,可通过一个全局函数
const getGlobal = () => {
if (typeof self !== ‘undefined’) {
return self
}
if (typeof window !== ‘undefined’) {
return window
}
if (typeof global !== ‘undefined’) {
return global
}
throw new Error(‘无法找到全局对象’)
}
const globals = getGlobal()
console.log(globals)
现在globalThis
提供了一个标准的方式来获取不同环境下的全局 this
对象(也就是全局对象自身)。不像 window
或者 self
这些属性,它确保可以在有无窗口的各种环境下正常工作。所以,你可以安心的使用 globalThis
,不必担心它的运行环境。
为便于记忆,你只需要记住,全局作用域中的 this
就是globalThis
。以后就用globalThis就行了。
BigInt
BigInt
是一种内置对象,它提供了一种方法来表示大于 2的53次方 - 1
的整数。这原本是 Javascript中可以用 Number
表示的最大数字。BigInt
可以表示任意大的整数。
使用 BigInt 有两种方式:
方式一:数字后面增加n
const bigInt = 9007199254740993n
console.log(bigInt)
console.log(typeof bigInt) // bigint
// BigInt
和 [Number
]不是严格相等的,但是宽松相等的。
console.log(1n == 1) // true
console.log(1n === 1) // false
// Number
和 BigInt
可以进行比较。
1n < 2 // ↪ true
2n > 1 // ↪ true
方式二:使用 BigInt 函数
const bigIntNum = BigInt(9007199254740993n)
console.log(bigIntNum)
运算
let number = BigInt(2);
let a = number + 2n; // 4n
let b = number * 10n; // 20n
let c = number - 10n; // -8n
console.log(a);
console.log(b);
console.log©;
注意点
BigInt不能用于 [Math
] 对象中的方法;不能和任何 [Number
] 实例混合运算,两者必须转换成同一种类型。在两种类型来回转换时要小心,因为 BigInt
变量在转换成 [Number
] 变量时可能会丢失精度。
String.prototype.matchAll()
matchAll()
方法返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。
const regexp = /t(e)(st(\d?))/g;
const str = ‘test1test2’;
const array = […str.matchAll(regexp)];
console.log(array[0]); // [“test1”, “e”, “st1”, “1”]
console.log(array[1]); // [“test2”, “e”, “st2”, “2”]
Promise.allSettled()
我们都知道 Promise.all() 具有并发执行异步任务的能力。但它的最大问题就是如果其中某个任务出现异常(reject),所有任务都会挂掉,Promise直接进入reject 状态。
场景:现在页面上有三个请求,分别请求不同的数据,如果一个接口服务异常,整个都是失败的,都无法渲染出数据
我们需要一种机制,如果并发任务中,无论一个任务正常或者异常,都会返回对应的的状态,这就是Promise.allSettled
的作用
const promise1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(“promise1”);
// reject("error promise1 ");
}, 3000);
});
};
const promise2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(“promise2”);
// reject("error promise2 ");
}, 1000);
});
};
const promise3 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// resolve(“promise3”);
reject("error promise3 ");
}, 2000);
});
};
// Promise.all 会走到catch里面
Promise.all([promise1(), promise2(), promise3()])
.then((res) => {
console.log(res);
})
.catch((error) => {
console.log(“error”, error); // error promise3
});
// Promise.allSettled 不管有没有错误,三个的状态都会返回
Promise.allSettled([promise1(), promise2(), promise3()])
.then((res) => {
console.log(res);
// 打印结果
// [
// {status: ‘fulfilled’, value: ‘promise1’},
// {status: ‘fulfilled’,value: ‘promise2’},
// {status: ‘rejected’, reason: 'error promise3 '}
// ]
})
.catch((error) => {
console.log(“error”, error);
});
Dynamic Import(按需 import)
import()
可以在需要的时候,再加载某个模块。
button.addEventListener(‘click’, event => {
import(‘./dialogBox.js’)
.then(dialogBox => {
dialogBox.open();
})
.catch(error => {
/* Error handling */
})
});
上面代码中,import()
方法放在click
事件的监听函数之中,只有用户点击了按钮,才会加载这个模块。
ES2021(ES12)
============
逻辑运算符和赋值表达式(&&=,||=,??=)
&&=
逻辑与赋值 x &&= y
等效于:
x && (x = y);
上面的意思是,当x为真时,x=y。具体请看下面的示例:
let a = 1;
let b = 0;
a &&= 2;
console.log(a); // 2
b &&= 2;
console.log(b); // 0
||=
逻辑或赋值(x ||= y
)运算仅在 x
为false时赋值。
x ||= y
等同于:x || (x = y);
const a = { duration: 50, title: ‘’ };
a.duration ||= 10;
console.log(a.duration); // 50
a.title ||= ‘title is empty.’;
console.log(a.title); // “title is empty”
??=
逻辑空赋值运算符 (x ??= y
) 仅在 x
是 nullish[3] (null
或 undefined
) 时对其赋值。
x ??= y
等价于:x ?? (x = y);
示例一
const a = { duration: 50 };
a.duration ??= 10;
console.log(a.duration); // 50
a.speed ??= 25;
console.log(a.speed); // 25
示例二
function config(options) {
options.duration ??= 100;
options.speed ??= 25;
return options;
}
config({ duration: 125 }); // { duration: 125, speed: 25 }
config({}); // { duration: 100, speed: 25 }
String.prototype.replaceAll()
介绍
replaceAll()
方法返回一个新字符串,新字符串中所有满足 pattern
的部分都会被replacement
替换。pattern
可以是一个字符串或一个RegExp
,replacement
可以是一个字符串或一个在每次匹配被调用的函数。
原始字符串保持不变。
示例
‘aabbcc’.replaceAll(‘b’, ‘.’); // ‘aa…cc’
使用正则表达式搜索值时,它必须是全局的。
‘aabbcc’.replaceAll(/b/, ‘.’);
TypeError: replaceAll must be called with a global RegExp
这将可以正常运行: