重学前端

前端知识回顾

Posted by JXH on March 13, 2021

知识架构的层级:

  • 文法 -词法 -语法
  • 语义
  • 运行时 -类型 -执行过程

词法中有各种直接量、关键字、运算符,语法和语义则是表达式、语句、函数、对象、模块,类型则包含了对象、数字、字符串等……

知识体系

知识体系

html 和 css

html 和 css

浏览器的实现原理和API

浏览器的实现原理和API

前端工程实践

前端工程实践

JavaScript类型:关于类型,有哪些你不知道的细节?

这 8 种语言类型是:Undefined;Null;Boolean;String;Number;Symbol;Object, bigInt.

Undefined、Null

为什么有的编程规范要求用 void 0 代替 undefined?
因为 JavaScript 的代码 undefined 是一个变量,而并非是一个关键字,这是 JavaScript 语言公认的设计失误之一,所以,我们为了避免无意中被篡改,我建议使用 void 0 来获取 undefined 值。

JavaScript对象:面向对象还是基于对象?

JavaScript 标准对基于对象的定义,这个定义的具体内容是:“语言和宿主的基础设施由对象来提供,并且 JavaScript 程序即是一系列互相通讯的对象集合”。

在《面向对象分析与设计》这本书中,Grady Booch 替我们做了总结,他认为,从人类的认知角度来说,对象应该是下列事物之一:

  1. 一个可以触摸或者可以看见的东西;
  2. 人的智力可以理解的东西;
  3. 可以指导思考或行动(进行想象或施加动作)的东西。 有了对象的自然定义后,我们就可以描述编程语言中的对象了。在不同的编程语言中,设计者也利用各种不同的语言特性来抽象描述对象,最为成功的流派是使用“类”的方式来描述对象,这诞生了诸如 C++、Java 等流行的编程语言。而 JavaScript 早年却选择了一个更为冷门的方式:原型。这是我在前面说它不合群的原因之一。

JavaScript 对象的特征

  • 对象具有唯一标识性:即使完全相同的两个对象,也并非同一个对象。
  • 对象有状态:对象具有状态,同一对象可能处于不同状态之下。
  • 对象具有行为:即对象的状态,可能因为它的行为产生变迁。 在 JavaScript 中,对象的状态和行为其实都被抽象为了属性。如果你用过 Java,一定不要觉得奇怪,尽管设计思路有一定差别,但是二者都很好地表现了对象的基本特征:标识性、状态和行为。

在实现了对象基本特征的基础上, 我认为,JavaScript 中对象独有的特色是:对象具有高度的动态性,这是因为 JavaScript 赋予了使用者在运行时为对象添改状态和行为的能力。

JavaScript 对象的两类属性

数据属性具有四个特征。

  • value:就是属性的值。
  • writable:决定属性能否被赋值。
  • enumerable:决定 for in 能否枚举该属性。
  • configurable:决定该属性能否被删除或者改变特征值。

访问器(getter/setter)属性,它也有四个特征。

  • getter:函数或 undefined,在取属性值时被调用。
  • setter:函数或 undefined,在设置属性值时被调用。
  • enumerable:决定 for in 能否枚举该属性。
  • configurable:决定该属性能否被删除或者改变特征值。

我们通常用于定义属性的代码会产生数据属性,其中的 writable、enumerable、configurable 都默认为 true。我们可以使用内置函数 getOwnPropertyDescriptor 来查看。如果我们要想改变属性的特征,或者定义访问器属性,我们可以使用 Object.defineProperty。

你甚至可以理解为什么会有“JavaScript 不是面向对象”这样的说法了。这是由于 JavaScript 的对象设计跟目前主流基于类的面向对象差异非常大。

JavaScript 语言标准也已经明确说明,JavaScript 是一门面向对象的语言,我想标准中能这样说,正是因为 JavaScript 的高度动态性的对象系统。

在一些争论中,有人强调:JavaScript 并非“面向对象的语言”,而是“基于对象的语言”。这个想法都清除。JavaScript 本身就是面向对象的,它并不需要模拟,只是它实现面向对象的方式和主流的流派不太一样,所以才让很多人产生了误会。

JavaScript对象:我们真的需要模拟类吗?

实际上,我认为“基于类”并非面向对象的唯一形态,如果我们把视线从“类”移开,Brendan 当年选择的原型系统,就是一个非常优秀的抽象对象的形式。

### 什么是原型?

  • 如果所有对象都有私有字段[[prototype]],就是对象的原型;
  • 读一个属性,如果对象本身没有,则会继续访问对象的原型,直到原型为空或者找到为止。

但从 ES6 以来,JavaScript 提供了一系列内置函数,以便更为直接地访问操纵原型。三个方法分别为:

  • Object.create 根据指定的原型创建新对象,原型可以是 null;
  • Object.getPrototypeOf 获得一个对象的原型;
  • Object.setPrototypeOf 设置一个对象的原型。

var cat = {
    say(){
        console.log("meow~");
    },
    jump(){
        console.log("jump");
    }
}

var tiger = Object.create(cat,  {
    say:{
        writable:true,
        configurable:true,
        enumerable:true,
        value:function(){
            console.log("roar!");
        }
    }
})


var anotherCat = Object.create(cat);

anotherCat.say();

var anotherTiger = Object.create(tiger);

anotherTiger.say();

但是,在更早的版本中,程序员只能通过 Java 风格的类接口来操纵原型运行时,可以说非常别扭。考虑到 new 和 prototype 属性等基础设施今天仍然有效,而且被很多代码使用,学习这些知识也有助于我们理解运行时的原型工作原理,下面我们试着回到过去,追溯一下早年的 JavaScript 中的原型和类。

早期版本中的类与原型

在早期版本的 JavaScript 中,“类”的定义是一个私有属性 [[class]],语言标准为内置类型诸如 Number、String、Date 等指定了[[class]]属性,以表示它们的类。语言使用者唯一可以访问[[class]]属性的方式是 Object.prototype.toString。因此,在 ES3 和之前的版本,JS 中类的概念是相当弱的,它仅仅是运行时的一个字符串属性。

在 ES5 开始,[[class]] 私有属性被 Symbol.toStringTag 代替,
Object.prototype.toString 的意义从命名上不再跟 class 相关。我们甚至可以自定义 Object.prototype.toString 的行为,以下代码展示了使用 Symbol.toStringTag 来自定义

new 运算接受一个构造器和一组调用参数,实际上做了几件事:

  • 以构造器的 prototype 属性(注意与私有字段[[prototype]]的区分)为原型,创建新对象;
  • 将 this 和调用参数传给构造器,执行;
  • 如果构造器返回的是对象,则返回,否则返回第一步创建的对象。

### ES6 中的类 好在 ES6 中加入了新特性 class,new 跟 function 搭配的怪异行为终于可以退休了(虽然运行时没有改变),在任何场景,我都推荐使用 ES6 的语法来定义类,而令 function 回归原本的函数语义。
子类和父类中的this 是父类的

## JavaScript对象:你知道全部的对象分类吗? 我们可以把对象分成几类。

  • 宿主对象(host Objects):由 JavaScript 宿主环境提供的对象,它们的行为完全由宿主环境决定。
  • 内置对象(Built-in Objects):由 JavaScript 语言提供的对象。
    • 固有对象(Intrinsic Objects ):由标准规定,随着 JavaScript 运行时创建而自动创建的对象实例。
    • 原生对象(Native Objects):可以由用户通过 Array、RegExp 等内置构造器或者特殊语法创建的对象。
    • 普通对象(Ordinary Objects):由{}语法、Object 构造器或者 class 关键字定义类创建的对象,它能够被原型继承。

### 宿主对象 BOM

### 内置对象·固有对象 固有对象是由标准规定,随着 JavaScript 运行时创建而自动创建的对象实例。

固有对象在任何 JavaScript 代码执行前就已经被创建出来了,它们通常扮演者类似基础库的角色。我们前面提到的“类”其实就是固有对象的一种。ECMA 标准为我们提供了一份固有对象表,里面含有 150+ 个固有对象。

### 内置对象·原生对象 我们把 JavaScript 中,能够通过语言本身的构造器创建的对象称作原生对象。 原生对象

### 用对象来模拟函数与构造器:函数对象与构造器对象 事实上,JavaScript 为这一类对象预留了私有字段机制,并规定了抽象的函数对象与构造器对象的概念。函数对象的定义是:具有[[call]]私有字段的对象,构造器对象的定义是:具有私有字段[[construct]]的对象。

值得一提的是,在 ES6 之后 => 语法创建的函数仅仅是函数,它们无法被当作构造器使用, 对于用户使用 function 语法或者 Function 构造器创建的对象来说,[[call]]和[[construct]]行为总是相似的,它们执行同一段代码。

JavaScript执行(一):Promise里的代码为什么比setTimeout先执行?

采纳 JSC 引擎的术语,我们把宿主发起的任务称为宏观任务,把 JavaScript 引擎发起的任务称为微观任务。

宏观和微观任务

JavaScript 引擎等待宿主环境分配宏观任务,在操作系统中,通常等待的行为都是一个事件循环,所以在 Node 术语中,也会把这个部分称为事件循环。

Promise

Promise 是 JavaScript 语言提供的一种标准化的异步管理方式,它的总体思想是,需要进行 io、等待或者其它异步操作的函数,不返回真实结果,而返回一个“承诺”,函数的调用方可以在合适的时机,选择等待这个承诺兑现(通过 Promise 的 then 方法的回调)。

新特性:async/await

async/await 是 ES2016 新加入的特性,它提供了用 for、if 等代码结构来编写异步的方式。它的运行时基础是 Promise,面对这种比较新的特性,我们先来看一下基本用法。async 函数必定返回 Promise,我们把所有返回 Promise 的函数都可以认为是异步函数。async 函数是一种特殊语法,特征是在 function 关键字之前加上 async 关键字,这样,就定义了一个 async 函数,我们可以在其中使用 await 来等待一个 Promise。

JavaScript执行(二):闭包和执行上下文到底是怎么回事?

闭包和上下文‘ 我们却不难根据古典定义,在 JavaScript 中找到对应的闭包组成部分。

  • 环境部分
    • 环境:函数的词法环境(执行上下文的一部分)
    • 标识符列表:函数中用到的未声明的变量
  • 表达式部分:函数体 至此,我们可以认为,JavaScript 中的函数完全符合闭包的定义。它的环境部分是函数词法环境部分组成,它的标识符列表是函数中用到的未声明变量,它的表达式部分就是函数体。这里我们容易产生一个常见的概念误区,有些人会把 JavaScript 执行上下文,或者作用域(Scope,ES3 中规定的执行上下文的一部分)这个概念当作闭包。实际上 JavaScript 中跟闭包对应的概念就是“函数”,可能是这个概念太过于普通,跟闭包看起来又没什么联系,所以大家才不自觉地把这个概念对应到了看起来更特别的“作用域”吧

执行上下文:执行的基础设施

JavaScript 标准把一段代码(包括函数),执行所需的所有信息定义为:“执行上下文”。 执行上下文在 ES3 中,包含三个部分。

  • scope:作用域,也常常被叫做作用域链。
  • variable object:变量对象,用于存储变量的对象。
  • this value:this 值。 在 ES5 中,我们改进了命名方式,把执行上下文最初的三个部分改为下面这个样子。
  • lexical environment:词法环境,当获取变量时使用。
  • variable environment:变量环境,当声明变量时使用。
  • this value:this 值。 在 ES2018 中,执行上下文又变成了这个样子,this 值被归入 lexical environment,但是增加了不少内容。
  • lexical environment:词法环境,当获取变量或者 this 值时使用。
  • variable environment:变量环境,当声明变量时使用。
  • code evaluation state:用于恢复代码执行位置。
  • Function:执行的任务是函数时使用,表示正在被执行的函数。
  • ScriptOrModule:执行的任务是脚本或者模块时使用,表示正在被执行的代码。
  • Realm:使用的基础库和内置对象实例。
  • Generator:仅生成器上下文有这个属性,表示当前生成器。

JavaScript执行(三):你知道现在有多少种函数吗?

在 JavaScript,切换上下文最主要的场景是函数调用。

函数

  • 第一种,普通函数:用 function 关键字定义的函数。
  • 第二种,箭头函数:用 => 运算符定义的函数。
  • 第三种,方法:在 class 中定义的函数。
  • 第四种,生成器函数:用 function * 定义的函数。
  • 第五种,类:用 class 定义的类,实际上也是函数。
  • 第六 / 七 / 八种,异步函数:普通函数、箭头函数和生成器函数加上 async 关键字。

对普通变量而言,这些函数并没有本质区别,都是遵循了“继承定义时环境”的规则,它们的一个行为差异在于 this 关键字。

this 关键字的行为

this 是执行上下文中很重要的一个组成部分。同一个函数调用方式不同,得到的 this 值也不同.

function showThis(){
    console.log(this);
}

var o = {
    showThis: showThis
}

showThis(); // global
o.showThis(); // oss

在这个例子中,我们定义了函数 showThis,我们把它赋值给一个对象 o 的属性,然后尝试分别使用两个引用来调用同一个函数,结果得到了不同的 this 值。

普通函数的 this 值由“调用它所使用的引用”决定,其中奥秘就在于:我们获取函数的表达式,它实际上返回的并非函数本身,而是一个 Reference 类型(记得我们在类型一章讲过七种标准类型吗,正是其中之一)。

Reference 类型由两部分组成:一个对象和一个属性值。不难理解 o.showThis 产生的 Reference 类型,即由对象 o 和属性“showThis”构成。

当做一些算术运算(或者其他运算时),Reference 类型会被解引用,即获取真正的值(被引用的内容)来参与运算,而类似函数调用、delete 等操作,都需要用到 Reference 类型中的对象。

在这个例子中,Reference 类型中的对象被当作 this 值,传入了执行函数时的上下文当中。

至此,我们对 this 的解释已经非常清晰了:调用函数时使用的引用,决定了函数执行时刻的 this 值。

this 关键字的机制

在 JavaScript 标准中,为函数规定了用来保存定义时上下文的私有属性[[Environment]]。当一个函数执行时,会创建一条新的执行环境记录,记录的外层词法环境(outer lexical environment)会被设置成函数的[[Environment]]。这个动作就是切换上下文了

当函数调用时,会入栈一个新的执行上下文,函数调用结束时,执行上下文被出栈。
而 this 则是一个更为复杂的机制,JavaScript 标准定义了 [[thisMode]] 私有属性。
[[thisMode]] 私有属性有三个取值。

  • lexical:表示从上下文中找 this,这对应了箭头函数。
  • global:表示当 this 为 undefined 时,取全局对象,对应了普通函数。
  • strict:当严格模式时使用,this 严格按照调用时传入的值,可能为 null 或者 undefined。

操作 this 的内置函数

Function.prototype.call 和 Function.prototype.apply 可以指定函数调用时传入的 this 值。有 Function.prototype.bind 它可以生成一个绑定过的函数,这个函数的 this 值固定了参数。

JavaScript执行(四):try里面放return,finally还会执行吗?

JavaScript 语句执行。我们用一个标准类型来表示:Completion Record。

Completion Record 表示一个语句执行完之后的结果,它有三个字段:

  • [[type]] 表示完成的类型,有 break continue return throw 和 normal 几种类型;
  • [[value]] 表示语句的返回值,如果语句没有,则是 empty;
  • [[target]] 表示语句的目标,通常是一个 JavaScript 标签(标签在后文会有介绍)。 JavaScript 正是依靠语句的 Completion Record 类型,方才可以在语句的复杂嵌套结构中,实现各种控制。
    语句的分类: 语句分类

普通语句

在 JavaScript 中,我们把不带控制能力的语句称为普通语句。普通语句有下面几种。

  • 声明类语句
    • var 声明
    • const 声明
    • let 声明
    • 函数声明
    • 类声明
  • 表达式语句
  • 空语句
  • debugger 语句

普通语句执行后,会得到 [[type]] 为 normal 的 Completion Record,JavaScript 引擎遇到这样的 Completion Record,会继续执行下一条语句。

语句块

语句块就是拿大括号括起来的一组语句,它是一种语句的复合结构,可以嵌套。
语句块本身并不复杂,我们需要注意的是语句块内部的语句的 Completion Record 的[[type]] 如果不为 normal,会打断语句块后续的语句执行。

控制型语句

控制型语句带有 if、switch 关键字,它们会对不同类型的 Completion Record 产生反应。

控制类语句分成两部分,一类是对其内部造成影响,如 if、switch、while/for、try。
另一类是对外部造成影响如 break、continue、return、throw,这两类语句的配合,会产生控制代码执行顺序和执行逻辑的效果,这也是我们编程的主要工作。

带标签的语句

唯一有作用的时候是:与完成记录类型中的 target 相配合,用于跳出多层循环。break/continue 语句如果后跟了关键字,会产生带 target 的完成记录。一旦完成记录带了 target,那么只有拥有对应 label 的循环语句会消费它。

JavaScript词法:为什么12.toString会报错?

接下来的,我们来了解一下 JavaScript 的文法部分。

文法是编译原理中对语言的写法的一种规定,一般来说,文法分成词法和语法两种。

词法分析技术上可以使用状态机或者正则表达式来进行。

  • JavaScript 源代码中的输入可以这样分类:
  • WhiteSpace 空白字符
  • LineTerminator 换行符
  • Comment 注释
  • Token 词
    • IdentifierName 标识符名称,典型案例是我们使用的变量名,注意这里关键字也包含在内了。 - Punctuator 符号,我们使用的运算符和大括号等符号。
    • NumericLiteral 数字直接量,就是我们写的数字。
    • StringLiteral 字符串直接量,就是我们用单引号或者双引号引起来的直接量。
    • Template 字符串模板,用反引号` 括起来的直接量。

理解编译原理:一个四则运算的解释器

  • 定义四则运算:产出四则运算的词法定义和语法定义。
  • 词法分析:把输入的字符串流变成 token。
  • 语法分析:把 token 变成抽象语法树 AST。
  • 解释执行:后序遍历 AST,执行得出结果。

JavaScript语法(预备篇):到底要不要写分号呢?

自动插入分号规则

自动插入分号规则其实独立于所有的语法产生式定义,它的规则说起来非常简单,只有三条。

  • 要有换行符,且下一个符号是不符合语法的,那么就尝试插入分号。
  • 有换行符,且语法中规定此处不能有换行符,那么就自动插入分号。
  • 源代码结束处,不能形成完整的脚本或者模块结构,那么就自动插入分号。

JavaScript语法(一):在script标签写export为什么会抛错?

脚本和模块

首先,JavaScript 有两种源文件,一种叫做脚本,一种叫做模块。这个区分是在 ES6 引入了模块机制开始的,在 ES5 和之前的版本中,就只有一种源文件类型(就只有脚本)。

脚本是可以由浏览器或者 node 环境引入执行的,而模块只能由 JavaScript 代码用 import 引入执行。

JavaScript语法(二):你知道哪些JavaScript语句?

JavaScript语法(三):什么是表达式语句?

表达式语句实际上就是一个表达式,它是由运算符连接变量或者直接量构成的.

PrimaryExpression 主要表达式

首先我们来给你讲解一下表达式的原子项:Primary Expression。它是表达式的最小单位,它所涉及的语法结构也是优先级最高的。如:’abc’, 123等等。

MemberExpression 成员表达式

Member Expression 通常是用于访问对象成员的。它有几种形式:a.b;a[“b”];new.target;super.b;

NewExpression NEW 表达式

Member Expression 加上 new 就是 New Expression(当然,不加 new 也可以构成 New Expression,JavaScript 中默认独立的高优先级表达式都可以构成低优先级表达式)。

CallExpression 函数调用表达式

。它的基本形式是 Member Expression 后加一个括号里的参数列表,或者我们可以用上 super 关键字代替 Member Expression。

LeftHandSideExpression 左值表达式

New Expression 和 Call Expression 统称 LeftHandSideExpression,左值表达式。

ignmentExpression 赋值表达式

AssignmentExpression 赋值表达式也有多种形态,最基本的当然是使用等号赋值.

HTML语义:div和span不是够用了吗?

语义类标签是什么,使用它有什么好处?

语义类标签也是大家工作中经常会用到的一类标签,它们的特点是视觉表现上互相都差不多,主要的区别在于它们表示了不同的语义,比如大家会经常见到的 section、nav、p,这些都是语义类的标签。

  • 语义类标签对开发者更为友好,使用语义类标签增强了可读性,即便是在没有 CSS 的时候,开发者也能够清晰地看出网页的结构,也更为便于团队的开发和维护。
  • 除了对人类友好之外,语义类标签也十分适宜机器阅读。它的文字表现力丰富,更适合搜索引擎检索(SEO),也可以让搜索引擎爬虫更好地获取到更多有效信息,有效提升网页的搜索量,并且语义类还可以支持读屏软件,根据文章可以自动生成目录等等。

CSS语法:除了属性和选择器,你还需要知道这些带@的规则

CSS 的顶层样式表由两种规则组成的规则列表构成,一种被称为 at-rule,也就是 at 规则,另一种是 qualified rule,也就是普通规则。

HTML元信息类标签:你知道head里一共能写哪几种标签吗?

所谓元信息,是指描述自身的信息,元信息类标签,就是 HTML 用于描述文档自身的一类标签,它们通常出现在 head 标签中,一般都不会在页面被显示出来(与此相对,其它标签,如语义类标签,描述的是业务)。

head 标签

首先我们先来了解一下 head 标签,head 标签本身并不携带任何信息,它主要是作为盛放其它语义类标签的容器使用。

title 标签

title 标签表示文档的标题.

base 标签

它的作用是给页面上所有的 URL 相对地址提供一个基础。base 标签最多只有一个,它改变全局的链接地址。

meta 标签

meta 标签是一组键值对,它是一种通用的元信息表示标签。 在 head 中可以出现任意多个 meta 标签。一般的 meta 标签由 name 和 content 两个属性来定义。name 表示元信息的名,content 则用于表示元信息的值。

CSS选择器:伪元素是怎么回事儿?

伪元素本身不单单是一种选择规则,它还是一种机制。
伪元素的语法跟伪类相似,但是实际产生的效果却是把不存在的元素硬选出来。
目前兼容性达到可用的伪元素有以下几种。::first-line::first-letter::before::after

接下来我们说说 ::before 和 ::after 伪元素。

::before 表示在元素内容之前插入一个虚拟的元素,::after 则表示在元素内容之后插入。
这两个伪元素所在的 CSS 规则必须指定 content 属性才会生效,我们看下例子:

<p class="special">I'm real element</p>
p.special::before {
    display: block;
    content: "pseudo! ";
}

HTML链接:除了a标签,还有哪些标签叫链接?

链接

CSS排版:从毕升开始,我们就开始用正常流了

正常流的原理

在 CSS 标准中,规定了如何排布每一个文字或者盒的算法,这个算法依赖一个排版的“当前状态”,CSS 把这个当前状态称为“格式化上下文(formatting context)”。
我们可以认为排版过程是这样的:

格式化上下文 + 盒 / 文字 = 位置 我们需要排版的盒,是分为块级盒和行内级盒的,所以排版需要分别为它们规定了块级格式化上下文和行内级格式化上下文。
当我们要把正常流中的一个盒或者文字排版,需要分成三种情况处理。

  • 当遇到块级盒:排入块级格式化上下文。
  • 当遇到行内级盒或者文字:首先尝试排入行内级格式化上下文,如果排不下,那么创建一个行盒,先将行盒排版(行盒是块级,所以到第一种情况),行盒会创建一个行内级格式化上下文。
  • 遇到 float 盒:把盒的顶部跟当前行内级上下文上边缘对齐,然后根据 float 的方向把盒的对应边缘对到块级格式化上下文的边缘,之后重排当前行盒。

我们以上讲的都是一个块级格式化上下文中的排版规则,实际上,页面中的布局没有那么简单,一些元素会在其内部创建新的块级格式化上下文,这些元素有:

  1. 浮动元素;
  2. 绝对定位元素;
  3. 非块级但仍能包含块级元素的容器(如 inline-blocks, table-cells, table-captions);
  4. 块级的能包含块级元素的容器,且属性 overflow 不为 visible。

HTML替换型元素:为什么link一个CSS要用href,而引入js要用src呢?

凡是替换型元素,都是使用 src 属性来引用文件的,而我们之前的课程中已经讲过,链接型元素是使用 href 标签的。虽然我不知道当初是怎么设计的,但是 style 标签并非替换型元素,不能使用 src 属性,这样,我们用 link 标签引入 CSS 文件,当然就是用 href 标签啦。

CSS Flex排版:为什么垂直居中这么难?

React Native 则更为大胆地使用了纯粹的 Flex 排版,不再支持正常流,最终也很好地支持了大量的应用界面布局,这一点也证明了 Flex 排版的潜力。

Flex的设计

Flex 排版的核心是 display:flex 和 flex 属性,它们配合使用。具有 display:flex 的元素我们称为 flex 容器,它的子元素或者盒被称作 flex 项。

CSS动画与交互:为什么动画要用贝塞尔曲线这么奇怪的东西?

CSS 中跟动画相关的属性有两个:animation 和 transition。

@keyframes mykf
{
  from {background: red;}    //或者百分号
  to {background: yellow;}
}

div
{
    animation:mykf 5s infinite;
}

这里展示了 animation 的基本用法,实际上 animation 分成六个部分:

  • animation-name 动画的名称,这是一个 keyframes 类型的值(我们在第 9 讲“CSS 语法:除了属性和选择器,你还需要知道这些带 @的规则”讲到过,keyframes 产生一种数据,用于定义动画关键帧);
  • animation-duration 动画的时长;
  • animation-timing-function 动画的时间曲线;
  • animation-delay 动画开始前的延迟;
  • animation-iteration-count 动画的播放次数;
  • animation-iteration-count 动画的播放次数;

接下来我们来介绍一下 transition。transition 与 animation 相比来说,是简单得多的一个属性。

  • transition-property 要变换的属性;
  • transition-duration 变换的时长;
  • transition-timing-function 时间曲线;
  • transition-delay 延迟。

浏览器:一个浏览器是如何工作的?(阶段一)

HTTP Status code(状态码)和 Status text(状态文本)

接下来我们看看 response line 的状态码和状态文本。常见的状态码有以下几种。

  • 1xx:临时回应,表示客户端请继续。
  • 2xx:请求成功。
    • 200:请求成功。
  • 3xx: 表示请求的目标有变化,希望客户端进一步处理。
    • 301&302:永久性与临时性跳转。
    • 304:跟客户端缓存没有更新。
  • 4xx:客户端请求错误。
    • 403:无权限。
    • 404:表示请求的页面不存在。
    • 418:It’s a teapot. 这是一个彩蛋,来自 ietf 的一个愚人节玩笑。(超文本咖啡壶控制协议)
  • 5xx:服务端请求错误。
    • 500:服务端错误。
    • 503:服务端暂时性错误,可以一会再试。

Request Header。

Request Header

Response Header

Response Header

HTTP Request Body

  • application/json
  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/xml

浏览器:一个浏览器是如何工作的?(阶段二)

浏览器DOM:你知道HTML的节点有哪几种吗?

DOM API 大致会包含 4 个部分。

  • 节点:DOM 树形结构中的节点相关 API。
  • 事件:触发和监听事件相关 API。
  • Range:操作文字范围相关 API。
  • 遍历:遍历 DOM 需要的 API。

节点

DOM 的树形结构所有的节点有统一的接口 Node,我们按照继承关系,给你介绍一下节点的类型。 节点类型

Node

Node 是 DOM 树继承关系的根节点,它定义了 DOM 节点在 DOM 树上的操作,首先,Node 提供了一组属性,来表示它在 DOM 树中的关系,它们是:

  • parentNode
  • childNodes
  • firstChild
  • lastChild
  • nextSibling
  • previousSibling

Node 中也提供了操作 DOM 树的 API,主要有下面几种。

  • appendChild
  • insertBefore
  • removeChild
  • replaceChild

Node 还提供了一些高级 API,我们来认识一下它们。

  • compareDocumentPosition 是一个用于比较两个节点中关系的函数。
  • contains 检查一个节点是否包含另一个节点的函数。
  • isEqualNode 检查两个节点是否完全相同。
  • isSameNode 检查两个节点是否是同一个节点,实际上在 JavaScript 中可以用“===”。
  • cloneNode 复制一个节点,如果传入参数 true,则会连同子元素做深拷贝。

DOM 标准规定了节点必须从文档的 create 方法创建出来,不能够使用原生的 JavaScript 的 new 运算。于是 document 对象有这些方法。

  • reateElement
  • createTextNode
  • createCDATASection
  • createComment
  • createProcessingInstruction
  • createDocumentFragment

Element 与 Attribute

Element 表示元素,它是 Node 的子类。元素对应了 HTML 中的标签,它既有子节点,又有属性。所以 Element 子类中,有一系列操作属性的方法。
首先,我们可以把元素的 Attribute 当作字符串来看待,这样就有以下的 API:

  • getAttribute
  • setAttribute
  • removeAttribute
  • hasAttribute

查找元素

document 节点提供了查找元素的能力。比如有下面的几种。

  • querySelector
  • querySelectorAll
  • getElementById
  • getElementsByName
  • getElementsByTagName
  • getElementsByClassName

浏览器CSSOM:如何获取一个元素的准确位置

顾名思义,CSSOM 是 CSS 的对象模型,在 W3C 标准中,它包含两个部分:描述样式表和规则等 CSS 的模型部分(CSSOM),和跟元素视图相关的 View 部分(CSSOM View)。

我们首先了解一下 CSSOM API 的基本用法,一般来说,我们需要先获取文档中所有的样式表:

document.styleSheets

我们虽然无法用 CSSOM API 来创建样式表,但是我们可以修改样式表中的内容。

document.styleSheets[0].insertRule("p { color:pink; }", 0)
document.styleSheets[0].removeRule(0)

更进一步,我们可以获取样式表中特定的规则(Rule),并且对它进行一定的操作,具体来说,就是使用它的 cssRules 属性来实现:

document.styleSheets[0].cssRules

我们在 CSS 语法部分,已经为你整理过 at-rule 的完整列表,多数 at-rule 都对应着一个 rule 类型:

  • CSSStyleRule
  • CSSCharsetRule
  • CSSImportRule
  • CSSMediaRule
  • CSSFontFaceRule
  • CSSPageRule
  • CSSNamespaceRule
  • CSSKeyframesRule
  • CSSKeyframeRule
  • CSSSupportsRule

CSSStyleRule 有两个属性:selectorText 和 style,分别表示一个规则的选择器部分和样式部分。

selector 部分是一个字符串,这里显然偷懒了没有设计进一步的选择器模型,我们按照选择器语法设置即可。

style 部分是一个样式表,它跟我们元素的 style 属性是一样的类型,所以我们可以像修改内联样式一样,直接改变属性修改规则中的具体 CSS 属性定义,也可以使用 cssText 这样的工具属性。

CSSOM 还提供了一个非常重要的方法,来获取一个元素最终经过 CSS 计算得到的属性:

window.getComputedStyle(elt, pseudoElt);

CSSOM View

CSSOM View 这一部分的 API,可以视为 DOM API 的扩展,它在原本的 Element 接口上,添加了显示相关的功能,这些功能,又可以分成三个部分:窗口部分,滚动部分和布局部分,下面我来分别带你了解一下。

窗口 API

窗口 API 用于操作浏览器窗口的位置、尺寸等。

  • moveTo(x, y) 窗口移动到屏幕的特定坐标;
  • moveBy(x, y) 窗口移动特定距离;
  • resizeTo(x, y) 改变窗口大小到特定尺寸;
  • resizeBy(x, y) 改变窗口大小特定尺寸。

此外,窗口 API 还规定了 window.open() 的第三个参数:

window.open("about:blank", "_blank" ,"width=100,height=100,left=100,right=100" )

滚动 API

视口滚动 API

可视区域(视口)滚动行为由 window 对象上的一组 API 控制,我们先来了解一下:

  • scrollX 是视口的属性,表示 X 方向上的当前滚动距离,有别名 pageXOffset;
  • scrollY 是视口的属性,表示 Y 方向上的当前滚动距离,有别名 pageYOffset;
  • scroll(x, y) 使得页面滚动到特定的位置,有别名 scrollTo,支持传入配置型参数 {top, left};
  • scrollBy(x, y) 使得页面滚动特定的距离,支持传入配置型参数 {top, left}。

元素滚动 API

接下来我们来认识一下元素滚动 API,在 Element 类(参见 DOM 部分),为了支持滚动,加入了以下 API。

  • scrollTop 元素的属性,表示 Y 方向上的当前滚动距离。
  • scrollLeft 元素的属性,表示 X 方向上的当前滚动距离。
  • scrollWidth 元素的属性,表示元素内部的滚动内容的宽度,一般来说会大于等于元素宽度。
  • scrollHeight 元素的属性,表示元素内部的滚动内容的高度,一般来说会大于等于元素高度。
  • scroll(x, y) 使得元素滚动到特定的位置,有别名 scrollTo,支持传入配置型参数 {top, left}。
  • scrollBy(x, y) 使得元素滚动到特定的位置,支持传入配置型参数 {top, left}。
  • scrollIntoView(arg) 滚动元素所在的父元素,使得元素滚动到可见区域,可以通过 arg 来指定滚到中间、开始或者就近。

布局 API

全局尺寸信息

全局尺寸信息

  • window.innerHeight, window.innerWidth 这两个属性表示视口的大小。
  • window.outerWidth, window.outerHeight 这两个属性表示浏览器窗口占据的大小,很多浏览器没有实现,一般来说这两个属性无关紧要。
  • window.devicePixelRatio 这个属性非常重要,表示物理像素和 CSS 像素单位的倍率关系,Retina 屏这个值是 2,后来也出现了一些 3 倍的 Android 屏。
  • window.screen (屏幕尺寸相关的信息)
  • window.screen.width, window.screen.height 设备的屏幕尺寸。
  • window.screen.availWidth, window.screen.availHeight 设备屏幕的可渲染区域尺寸,一些 Android 机器会把屏幕的一部分预留做固定按钮,所以有这两个属性,实际上一般浏览器不会实现的这么细致。
  • window.screen.colorDepth, window.screen.pixelDepth 这两个属性是固定值 24,应该是为了以后预留。

### 我们是否能够取到一个元素的宽(width)和高(height)呢? 实际上,我们首先应该从脑中消除“元素有宽高”这样的概念,我们已经多次提到了,有些元素可能产生多个盒,事实上,只有盒有宽和高,元素是没有的。

所以我们获取宽高的对象应该是“盒”,于是 CSSOM View 为 Element 类添加了两个方法:

  • getClientRects();
  • getBoundingClientRect()。 getClientRects 会返回一个列表,里面包含元素对应的每一个盒所占据的客户端矩形区域,这里每一个矩形区域可以用 x, y, width, height 来获取它的位置和尺寸。

getBoundingClientRect ,这个 API 的设计更接近我们脑海中的元素盒的概念,它返回元素对应的所有盒的包裹的矩形区域,需要注意,这个 API 获取的区域会包括当 overflow 为 visible 时的子元素区域。