核心思想:列一份前端知识架构图,知识点查缺补漏
数据类型
undefined不是关键字而是变量,在局部作用域有被重写的可能性
精度问题
js的精度问题会导致浮点数计算问题,判断相等可使用
console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON);
异步任务
宿主发起的任务称为宏任务(比如setTimeout),js引擎发起的任务称为微任务(比如promise)
宏任务的队列就相当于事件循环
js引擎发起的微任务需要再一个宏任务中完成,所以每个宏任务中包含一个微任务队列
函数
var定义的变量作用域会穿透for if的范围,而let const 定位的变量作用域只在大括号范围
this 和上下文
function定义的函数,内的this指向调用者,class类里定义的function,this明确指向调用者(比如将类实例的该方法赋给全局变量,没有明确调用者this就是undefinded
JavaScript 用一个栈来管理执行上下文,这个栈中的每一项又包含一个链表。当函数调用时,会入栈一个新的执行上下文,函数调用结束时,执行上下文被出栈
call、bind、apply接受箭头函数也不会报错,它无法改变this,但是可以传参
new的执行过程
- 以构造器的 prototype 属性(注意与私有字段[[prototype]]的区分)为原型,创建新对象;
- 将 this 和调用参数传给构造器,执行;
- 如果构造器返回的是对象,则返回,否则返回第一步创建的对象。
Completion Record
try里执行了return,finally还会执行吗?会,而且finally里的return会覆盖try里的return
Completion Record 表示一个语句执行完之后的结果,它有三个字段:
- [[type]] 表示完成的类型,有 break continue return throw 和 normal 几种类型;
- [[value]] 表示语句的返回值,如果语句没有,则是 empty;
- [[target]] 表示语句的目标,通常是一个 JavaScript 标签(标签在后文会有介绍)。
普通语句(不带控制能力的语句)
普通语句中只有表达式语句会产生 [[value]]
如果你经常使用 Chrome 自带的调试工具,可以知道,输入一个表达式,在控制台可以得到结果,但是在前面加上 var,就变成了 undefined。
Chrome 控制台显示的正是语句的 Completion Record 的[[value]]。
控制语句
看开始的问题:因为 finally 中的内容必须保证执行,所以 try/catch 执行完毕,即使得到的结果是非 normal 型的完成记录,也必须要执行 finally。而当 finally 执行也得到了非 normal 记录,则会使 finally 中的记录作为整个 try 结构的结果。
带标签的语句
实际上,任何 JavaScript 语句是可以加标签的,在语句前加冒号即可
大部分时候,这个东西类似于注释,没有任何用处。唯一有作用的时候是:与完成记录类型中的 target 相配合,用于跳出多层循环。
break/continue 语句如果后跟了关键字,会产生带 target 的完成记录。一旦完成记录带了 target,那么只有拥有对应 label 的循环语句会消费它。
文法
为什么
12.toString()会报错
十进制的 Number 可以带小数,小数点前后部分都可以省略,但是不能同时省略,比如.01,12.,12.01,这就导致12.被当作整体相当于12toString(),这时候应该在12后面加空格(或者给12加括号)
文法还有很多约束,都是不说不知道一看就理所当然的,比如
- 正则
/RegularExpressionBody/g的body部分第一个字符不能是*,会跟注释的词法冲突
ps: \u200d是零宽连字,没有任何意义,所以'哇\u200d哈哈'.length === 4是成立的
到底要不要用分号
觉得不要分号的观点是js有自动补全分号的机制,分号没必要,如下
- 要有换行符,且下一个符号是不符合语法的,那么就尝试插入分号。
- 有换行符,且语法中规定此处不能有换行符,那么就自动插入分号。
- 源代码结束处,不能形成完整的脚本或者模块结构,那么就自动插入分号。
觉得要分号的观点是自动补全并不100%有效,比如
- 以小括号开头的语句,容易被理解为函数的执行
(function(a){
console.log(a);
})()/*这里没有被自动插入分号*/
(function(a){
console.log(a);
})()
- 以数组开头的语句可能被理解为按索引取值
var a = [[]]/*这里没有被自动插入分号*/
[3, 2, 1, 0].forEach(e => console.log(e))
- 正则开头的语句可能被理解为除法
var x = 1, g = {test:()=>0}, b = 1/*这里没有被自动插入分号*/
/(a)/g.test("abc")
console.log(RegExp.$1)
- 模版字符串的语句可能被理解成模版字符串解析
var f = function(){
return "";
}
var g = f/*这里没有被自动插入分号*/
`Template`.match(/(a)/);
console.log(RegExp.$1)
ps: 在 JavaScript 中,当您在函数名称后使用模板字符串时,在内部会调用一个特殊的函数:tag函数。这个函数可以接收模板字符串的各个部分作为参数,并对它们进行处理。
以下是一个示例解释了这个行为:
function greet(strings, ...values) {
console.log(strings); // 模板字符串的文本部分
console.log(values); // 表达式的值
}
greet`Hello, ${name}!`; // 调用 greet 函数,并将字符串和表达式传递给它
在上面的示例中,greet 函数被调用,并将模板字符串 Hello, ${name}! 作为参数传递给它。这个模板字符串被拆分成两个部分:["Hello, ", "!"] 是字符串的文本部分,${name} 是表达式的值。这些部分分别作为参数传递给 greet 函数的第一个和第二个参数。
通过 tag函数 的使用,您可以对模板字符串进行自定义处理。这在实现类似模板引擎的功能时非常有用。然而,默认情况下,如果 greet 函数没有传入一个自定义的 tag函数,它将按照原样将模板字符串和表达式的值返回给用户。
鉴于这些不确定性,个人比较倾向加分号,加了也没什么性能消耗,习惯了加就没必要特意去改掉
语法
导入导出
export {a}导出的是变量/引用,变量a重新赋值import拿到的数据也会改变
export default a 相当于是把a赋值给default后导出,a重新赋值不会影响import拿到的数据
指令序言
use strict是js中唯一一种指令序言,目的是留给 JavaScript 的引擎和实现者一些统一的表达方式,在静态扫描时指定 JavaScript 代码的一些特性
iterator
for of 可以遍历数组,背后就是iterator机制
生成器函数执行返回的结果就带有iterator,可以用for of 遍历获取每一个yield的结果
function* foo(){
yield 0;
yield 1;
yield 2;
yield 3;
}
for(let e of foo())
console.log(e);
with
with 语句把对象的属性在它内部的作用域内变成变量
let o = {a:1, b:2}
with(o){
console.log(a, b);
}
with把 JavaScript 的变量引用关系变得不可分析,所以一般都认为这种语句都属于糟粕。
表达式语句
优先级
- 主要表达式
- 成员表达式
- new表达式
- 函数调用表达式
- 左值表达式
- 赋值表达式
- 逗号表达式
主要表达式
各种直接量,包括基本数据类型和引用类型
"abc";
123;
null;
true;
false;
({});
(function(){});
(class{ });
[];
/abc/g;
成员表达式
对象成员访问、new.target、super.x都属于成员表达式
另外还有带函数的模版,如f`a${b}c`;带参数列表的 new 运算如new Cls()。
这两种跟成员表达式关系不大,只是跟成员表达式是同一优先级
new.target属性允许你检测函数或构造方法是否是通过new运算符被调用的。在通过new运算符被初始化的函数或构造方法中,new.target返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target的值是undefined。
function Foo() {
if (!new.target) throw "Foo() must be called with new";
console.log("Foo instantiated with new");
}
Foo(); // throws "Foo() must be called with new"
new Foo(); // logs "Foo instantiated with new"
new表达式
不带参数的new表达式,比如
new new Cls(1);
// 等价于
new (new Cls(1))
函数表达式
一些变体的函数表达式虽然有对象属性访问,但是优先级还是要低于new表达式
a.b(c)(d)(e);
a.b(c)[3];
a.b(c).d;
a.b(c)`xyz`;
css
at 规则
- @charset 比如
@charset "utf-8"; - @import 比如
@import "mystyle.css"; - @media 比如
@media print { body { font-size: 10pt }}; - @page 分页媒体访问网页的表现,比如打印预览
@page {
size: 8.5in 11in;
margin: 10%;
@top-left {
content: "Hamlet";
}
@top-right {
content: "Page " counter(page);
}
}
- @counter-style 定义列表项的表现
@counter-style triangle {
system: cyclic;
symbols: ‣;
suffix: " ";
}
// 使用方式
ul li {
list-style-type: counter(triangle);
}
- @key-frames 定义动画关键帧
@keyframes diagonal-slide {
from {
left: 0;
top: 0;
}
to {
left: 100px;
top: 100px;
}
}
- @fontface 定义一种字体
@font-face {
font-family: Gentium;
src: url(http://example.com/fonts/Gentium.woff);
}
p { font-family: Gentium, serif; }
参考