重学前端-学习笔记

179 阅读6分钟

image.png

核心思想:列一份前端知识架构图,知识点查缺补漏

数据类型

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]]。

控制语句

image.png 看开始的问题:因为 finally 中的内容必须保证执行,所以 try/catch 执行完毕,即使得到的结果是非 normal 型的完成记录,也必须要执行 finally。而当 finally 执行也得到了非 normal 记录,则会使 finally 中的记录作为整个 try 结构的结果。

带标签的语句

实际上,任何 JavaScript 语句是可以加标签的,在语句前加冒号即可

大部分时候,这个东西类似于注释,没有任何用处。唯一有作用的时候是:与完成记录类型中的 target 相配合,用于跳出多层循环。

break/continue 语句如果后跟了关键字,会产生带 target 的完成记录。一旦完成记录带了 target,那么只有拥有对应 label 的循环语句会消费它。

文法

为什么12.toString()会报错

十进制的 Number 可以带小数,小数点前后部分都可以省略,但是不能同时省略,比如.0112.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 的变量引用关系变得不可分析,所以一般都认为这种语句都属于糟粕。

表达式语句

优先级

  1. 主要表达式
  2. 成员表达式
  3. new表达式
  4. 函数调用表达式
  5. 左值表达式
  6. 赋值表达式
  7. 逗号表达式

主要表达式

各种直接量,包括基本数据类型和引用类型

"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; }

参考