JS 原型链

构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型有一个属性([[Prototype]])指回构造函数,而实例有一个内部指针指向原型。

如果原型是另一个类型的实例呢?那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。

function SuperType() {
    this.property = true;
}
SuperType.protype.getSuperValue = function() {
    return this.property;
}
function SubType() {
    this.subproperty = false;
}
// 继承SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
    return this.subproperty;
};
let instance = new SubType();
console.log(instance.getSuperValue()); // true;

JS this

this关键字是JS中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。

为什么要使用this

this提供了一种更优雅的方式来隐式 ”传递“ 一个对象引用,因此可以把API设计得更加简洁且易于复用。随着使用模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱,使用this则不会这样。

this是在运行时进行绑定的,他的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式

当一个函数被调用时,会创建一个活动记录(有时也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

this既不指向函数自身也不指向函数的词法作用域,this实际上是函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

调用位置

function baz() {
    // 当前调用栈是:baz
    // 因此,当前调用位置是全局作用域
    console.log("baz");
    bar();
}
function bar() {
    // 当前调用栈是:baz - bar
    // 因此,当前调用位置是baz
    console.log("bar");
    foo();
}
function foo() {
    // 当前调用栈是:baz - bar - foo
    // 因此,当前调用位置是bar
    console.log("foo");
}
baz();

绑定规则

函数的执行过程中调用位置如何决定this的绑定对象:你必须找到调用位置,然后判断需要应用下面四条规则中的哪一条。

默认绑定

最常见的函数调用类型:独立函数调用。

function foo() {
    console.log(this.a);
}
var a = 2;
foo(); // 2

当调用foo时,this.a被解析成全局变量a。为什么?因为在本例中,函数调用时应用了this的默认绑定,因此this指向全局对象。

怎么知道是应用默认绑定?可以通过分析调用位置。在代码中,foo是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。

如果使用严格模式,则不能将全局对象用于默认绑定,this会绑定到undefined

function foo() {
    "use strict"
    console.log(this.a);
}
var a = 2;
foo(); // TypeError: Cannot read property 'a' of undefined

这里有一个微妙但非常重要的细节,虽然this的绑定规则完全取决于调用位置,但只有foo运行在非严格模式下时默认绑定才能绑定到全局对象,在严格模式下调用foo不影响绑定。

function foo() {
    console.log(this.a);
}
var a = 2;
"use strict"
foo(); // 2

隐式绑定

另一条需要考虑的规则是调用位置是否有上下文对象,或者说被某个对象拥有或者包含。

function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
obj.foo(); // 2

foo 被调用时,他的前面加上了对 obj 的引用。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。因此,this.aobj.a 是一样的。

对象属性引用链中只有最后一层在调用位置中起作用。

function foo() {
    console.log(this.a);
}
var obj2 = {
    a: 2,
    foo: foo
};
var obj1 = {
    a: 1,
    obj2: obj2
};
obj1.obj2.foo(); // 2

隐式丢失

一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说他会应用默认绑定,从而把this绑定到全局对象或者undefined上。

function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
};
var bar = obj.foo;
var a = 3;
bar(); // 3

一种更微妙、更常见、更出乎意料的情况发生在传入回调函数时:

function foo() {
    console.log(this.a);
}
function doFoo(fn) {
    fn();
}
var obj = {
    a: 2,
    foo: foo
}
var a = "global";
doFoo(obj.foo); // "global" , 无论全局变量a是在obj之前还是之后声明并赋值

参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值。如果把函数传入语言内置函数而不是传入自己声明的函数,结果也是一样的。

function foo() {
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
}
var a = "global";
setTimeout(obj.foo, 100); // "global"

如上,回调函数丢失this绑定是非常常见的,此外,调用回调函数还可能会修改this

显式绑定

如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,该怎么做呢?

具体来说,可以使用callapply方法。这两个方法如何工作呢,他们的第一个参数是一个对象,是给this准备的,接着在调用函数时将其绑定到this。因为你可以直接指定this的绑定对象,因此我们称之为显式绑定。

function foo() {
    console.log(this.a);
}
var obj = { a: 2 };
foo.call(obj); // 2

可惜,显示绑定仍然无法解决丢失绑定的问题。

硬绑定

硬绑定可以解决丢失绑定的问题。

我们创建函数bar并在内部手动调用foo.call(obj),因此强制把foothis绑定到了obj,之后无论如何调用函数bar,他总会在obj上调用foo。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。

function foo() {
    console.log(this.a);
}
var obj = { a: 2 };
var bar = function() {
    foo.call(obj);
}
bar(); // 2
setTimeout(bar, 100); // 2
// 硬绑定的bar不可能再修改this
bar.call(window) // 2

硬绑定的典型应用场景就是创建一个包裹函数,负责接收参数并返回值

function foo(something) {
    console.log(this.a, something);
    return this.a + something;
}
var obj = { a: 2 };
var bar = function() {
    return foo.apply(obj, arguments);
}
var b = bar(3); // 2 3 
console.log(b); // 5

另一种是创建一个可以重复使用的辅助函数

function foo(something) {
    console.log(this.a, something);
    return this.a + something;
}
// 简单的辅助绑定函数
function bind(fn, obj) {
    return function() {
        return fn.apply(obj, arguments);
    };
}
var obj = { a: 2 };
var bar = bind(foo, obj);
var b = bar(3); // 2 3
console.log(b); //5

由于硬绑定是一种非常常用的模式,所有ES5提供了内置的方法Function.protoprototype.bind

function foo(something) {
    console.log(this.a, something);
    return this.a + something;
}
var obj = { a: 2};
var bar = foo.bind(obj);
var b = bar(3); // 2 3
console.log(b); // 5

new 绑定

JS中,构造函数只是一些使用new操作符时被调用的函数,他们并不会属于某个类,也不会实例化一个类。实际上,他们甚至不能说是一种特殊的函数类型,他们只是被new操作符调用的普通函数而已。

function foo(a) {
    this.a = a;
}
var bar = new foo(2);
console.log(bar.a); // 2

使用new来调用foo时,我们会构造一个新对象并把它绑定到foo调用中的this上。

优先级

如果某个调用位置应用了多条规则怎么办?为了解决这个问题就必须给这些规则设定优先级。

毫无疑问,默认绑定的优先级是四条规则中最低的,new绑定比显式绑定优先级高。显示绑定的优先级比隐式绑定高。

JS 闭包

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

function foo() {
    var a = 2;
    function bar() {
        console.log(a);
    }
    return bar;
}
var baz = foo();
baz(); // 2 —— 这就是闭包的效果

函数bar的词法作用域可以访问foo的词法作用域。在foo执行后,通常会期待foo的整个内部作用域被销毁,因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间。而闭包可以阻止这件事情的发生。

bar所声明的位置所赐,它拥有涵盖foo内部作用域的闭包使得该作用域能够一直存活,以供bar在之后任何时间进行引用。

bar依然持有对该作用域的引用,这个引用就叫做闭包

本质上无论何时何地,如果将(访问它们各自词法作用域的)函数当作第一级的值类型并到处传递,你就会看到闭包在这些函数中的应用。在定时器、事件监听器、Ajax请求、跨窗口通信、Web Workers或者任何其他异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!

function wait(message) {
    setTimeout(() => console.log(message), 1000);
}
wait("Hello Closure"); 

将一个匿名函数传递给setTimeout,内部函数具有涵盖wait作用域的闭包,因此保留对变量message的引用。

循环与闭包

for (var i = 0; i < 5; ++i) {
    setTimeout(() => console.log(i), i * 1000);
}

正常情况下,我们对这段代码的预期是分别输出0-4,每秒一次,一次一个。但实际上,这段代码在运行时会以每秒一次的频率输出五次5。

5从哪里来?循环终止条件是i < 5, 条件首次成立时i的值为5,因此,输出显示的是循环结束时i的最终值。

仔细想一下,延迟函数的回调会在循环结束时才执行。事实上,当定时器运行时即使每个迭代中执行的是setTimeout(…, 0),所有的回调函数依然是在循环结束后才会被执行。

为什么代码的行为跟语义所暗示的不一致?

我们试图假设循环中的每个迭代在运行时都会给自己捕获一个i的副本,但根据作用域的工作原理,实际情况是尽管循环中五个函数是在各个迭代中分别定义的,但是他们都被封闭在一个共享的全局作用域中,因此实际上只有一个i

缺陷是什么?我们需要更多的闭包作用域,特别是在循环的过程中每个迭代都需要一个闭包作用域。

IIFE通过声明并立即执行一个函数来创建作用域。

for (var i = 0; i < 5; ++i) {
    (function(j) { // IIFE需要有自己的变量在每次迭代中存储i的值
        setTimeout(function timer() { console.log(j); }, j * 1000); 
    })(i);
}

在迭代中使用IIFE会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问。

块作用域

我们使用IIFE在每个迭代都生成一个新的作用域,换句话说,每次迭代都需要一个块作用域。let声明可以用来劫持块作用域,并且在这个块作用域中声明一个变量。

for (var i = 0; i < 5; ++i) {
    let j = i;
    setTimeout(function timer() {
        console.log(j);
    }, j * 1000);
}

for循环头部的let声明还会有一个特殊的行为。这个行为指出变量在循环过程中不只被声明一次,每次迭代都会声明。随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。

for (let i = 0; i < 5; ++i) {
    setTimeout(function timer() {
        console.log(i);
    }, i * 1000);
}

图解HTTP笔记(1 – 4章)

HTTP协议用于客户端和服务器端之间的通信

在两台计算机之间使用HTTP协议通信时,在一条通信线路上必定有一端是客户端,另一端是服务器端。有时候,两台计算机作为客户端和服务器端的角色有可能互换,但仅从一条通信路线来说,服务器端和客户端的角色是确定的,而用HTTP协议能够明确区分哪段是客户端,哪段是服务器端。

HTTP协议规定,请求从客户端发出,最后服务器端响应请求并返回。换句话说,肯定是先从客户端开始建立通信,服务器端在没有收到请求之前不会发送响应。

请求报文是由请求方法、请求URI,协议版本、可选请求首部字段和内容实体构成的。

HTTP是不保存状态的协议

HTTP是一种不保存状态,即无状态(stateless)协议。HTTP自身不对请求和响应之间的通信状态进行保存,也就是说,在HTTP这个级别,协议对于发送过的请求或响应不做持久化处理。这是为了更快地处理大量事务,确保协议的可伸缩性。

为了实现保持状态的功能,引入了cookie技术。

HTTP请求方法

  • GET:获取资源
  • POST:传输实体主体
    GET也可以传输实体主体,但一般不用。POSTGET相似,但POST的主要目的不是获取响应的主体内容
  • PUT:传输文件
    就像FTP的文件上传一样,要求在请求报文的主体中包含文件内容,然后保存到请求URI指定位置
  • HEAD:获得报文首部
    用于确认URI有效性及资源更新的日期时间等
  • DELETE:删除文件
  • OPTIONS:询问支持的方法
  • TRACE:追踪路径
    Web服务器端将之前的请求通信返回给客户端的方法。容易引发XSTCross-Site Tracing,跨站追踪)攻击,通常不会用到
  • CONNECT:要求用隧道协议连接代理
    主要使用SSLSecure Sockets Layer,安全套接层)和TLSTransport Layer Security,传输层安全)协议将通信内容加密后经网络隧道传输

持久连接节省通信量

HTTP协议的初始版本中,每进行一次HTTP通信就要断开一次TCP连接。发送请求一份包含多张图片的HTML文档对应的Web页面,会产生大量的通信开销。

持久连接

持久连接(HTTP Persistent Connections,也称为HTTP keep-aliveHTTP connection reuse)特点是:只要任意一端没有明确提出断开连接,则保持TCP连接状态。

HTTP1.1中,所有连接默认都是持久连接。服务器和客户端都需要支持持久连接。

管线化

从前发送请求后需等待并受到响应才能发送下一个请求。管线化技术出现后,不用等待响应亦可直接发送下一个请求。

管线化技术比持久连接更快,请求数越多,时间差就越明显。

使用Cookie的状态管理

Cookie技术通过在请求和响应报文中写入Cookie信息来控制客户端的状态。

Cookie会根据从服务器发送的响应报文中的set-cookie首部字段信息,通知客户端保存Cookie。当下次客户端再往该服务器发送请求时,客户端会自动在请求报文中加入Cookie值后发送出去。

服务器端发现客户端发送过来的Cookie后,会去检查究竟是从哪一个客户端发来的连接请求,然后比对服务器上的记录,最后得到之前的状态信息。

HTTP报文

用于HTTP协议交互的信息称为HTTP报文。HTTP报文大致可分为报文首部和报文主体两块。通常,并不一定要有报文主体。

请求报文和响应报文的首部内容由以下数据组成。

  • 请求行
    包含请求的方法,请求URIHTTP版本。
  • 状态行
    包含表明响应结果的状态码,原因短语和HTTP版本。
  • 首部字段
    包含请求和响应的各种条件和属性的各类首部,一般有4种首部:通用首部、请求首部、响应首部和实体首部。
  • 其他
    可能包含HTTPRFC里未定义的首部(Cookie等)。

编码提升传输速率

HTTP在传输数据时可以按照数据原貌直接传输,也可以在传输过程中通过编码提升传输速率。通过在传输时编码,能有效处理大量的访问请求。但是,编码的操作需要计算机来完成,因此会消耗更多的CPU等资源。

报文主体和实体主体的差异

  • 报文(message)
    HTTP通信中的基本单位,由8位组字节流(octet sequence,其中octet为8个比特)组成,通过HTTP通信传输。
  • 实体(entity)
    作为请求或响应的有效载荷数据(补充项)被传输,其内容由实体首部和实体主体组成。

HTTP报文的主体用于传输请求或响应的实体主体。

通常,报文主体等于实体主体。只有当传输中进行编码操作时,实体主体的内容发生变化,才导致它和报文主体产生差异。

分割发送的分块传输编码

HTTP通信过程中,请求的编号实体资源尚未传输完成之前,浏览器无法显示请求页面。在传输大容量数据时,通过把数据分割成多块,能够让浏览器逐步显示页面。这种把实体主体分块的功能称为分块传输编码(Chunked Transfer Coding)。

分块传输编码会将实体主体分成多个部分(块),每一块都会用十六进制来标记块的大小,而实体主体的最后一块会使用“0(CR+LF)”来标记。

常见的内容编码有以下几种:

  • gzip(GNU zip)
  • compress(UNIX 系统的标准压缩)
  • deflate(zlib)
  • identity(不进行编码)

发送多种数据的多部分对象集合

发送邮件时,我们可以在邮件里写入文字并添加多份附件,这是因为采用了MIME(Multipurpose Internet Mail Extensions, 多用途因特网邮件扩展)机制,它允许邮件处理文本、图片、视频等多个不同类型的数据。

MIME扩展中会使用一种称为多部份对象集合(Multipart)的方法,来容纳多份不同类型的数据。相应地,HTTP协议中也采纳了多部份对象集合,发送的一份报文主体内可含有多类型实体。通常是在图片或文本文件等上传时使用。

多部分对象集合包含的对象如下:

  • multipart/form-data
    Web表单文件上传时使用。
  • multipart/byteranges
    状态码206(Partial Content,部分内容)响应报文包含了多个范围的内容时使用。

获取部分内容的范围请求

指定范围发送的请求叫做范围请求(Range Request),如对一份10 000字节大小的资源,可以只请求5001 – 10 000字节内的资源。

执行范围请求时,会用到首部字段Range来指定资源的byte范围。

// 指定范围5001-10000
Range: bytes=5001-10000

// 从5001字节之后全部
Range: bytes=5001-

// 多个部分
Range: bytes=0-5000, 5000-7000

针对范围请求,响应会返回状态码206 Partial Content 的响应报文。另外,对于多重范围的范围请求,响应会在首部字段Content-Type标明multipart/byteranges后返回响应报文。

如果服务器端无法响应范围请求,则会返回状态码200OK 和完整的实体内容。

内容协商返回最合适的内容

当浏览器的默认语言为英语或中文,访问相同URIWeb页面时,则会显示对应的语言版本的页面,这样的机制称为内容协商(Content Negotiation)。

内容协商机制是指客户端和服务器端就响应的资源内容进行交涉,然后提供给客户端最为适合的资源。内容协商会以语言、字符集、编码方式等为基准判断响应的资源。

包含在请求报文中的某些首部字段(如下)就是判断的基准:

  • Accept
  • Accept-Charset
  • Accept-Encoding
  • Accept-Language
  • Content-Language

状态码告知从服务器端返回的请求结果

状态码的职责是当客户端向服务器端发送请求时,描述返回的请求结果。

类别原因短语
1XXInformational(信息性状态码)接收的请求正在处理
2XXSuccess(成功状态码)请求正常处理完毕
3XXRedirection(重定向状态码)需要进行附加操作以完成请求
4XXClient Error(客户端错误状态码)服务器无法处理请求
5XXServer Error(服务器错误状态码)服务器处理请求出错
状态码的类别

2XX 成功

  • 200 OK
  • 204 No Content
  • 206 Partial Content

3XX 重定向

  • 301 Moved Permanently
    永久性重定向,该状态码表示请求的资源已被分配了新的URI,以后应使用资源现在所指的URI
  • 302 Found
    临时性重定向,该状态码表示请求的资源已被分配了新的URI,希望用户本次能使用新的URI访问。与301相似,但302状态码代表的资源不是被永久移动,只是临时性质的。
  • 303 See Other
    该状态码表示由于请求对应的资源存在着另一个URI,应使用GET方法定向获取请求的资源。
  • 304 Not Modified
    该状态码表示客户端发送附带条件的请求时,服务器端允许请求访问资源,但因发生请求未满足条件的情况后,直接返回304Not Modified(服务器资源未改变,可直接使用客户端未过期的缓存)。304虽然被划分在3XX类别中,但和重定向没关系。
    附带条件的请求是指采用GET方法的请求报文中包含If-MatchIf-Modified-SinceIf-None-MatchIf-RangeIf-UnModified-Since中任一首部。
  • 307 Temporary Redirect
    临时重定向,与302有着相同的含义,尽管302禁止POST变换成GET,但实际使用时大家并不遵守。307会遵守浏览器标准,不会从POST变成GET

4XX 客户端错误

  • 400 Bad Request
    表示请求报文中存在语法错误。
  • 401 Unauthorized
    表示发送的请求需要有通过HTTP认证的认证信息。
  • 403 Forbidden
    表示请求资源的访问被服务器拒绝了。
  • 404 Not Found
    服务器上无法找到请求的资源,也可以在服务器拒绝请求且不想说明理由时使用。

5XX 服务器错误

  • 500 Internal Server Error
    表明服务器端在执行请求时发生了错误,也可能是Web应用存在的bug或某些临时的故障。
  • 503 Service Unavailable
    表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。

Angular 图片上传

input控件上传图片,div实现外观

//html
<div (click)="upload()" id="imagePreview">
    // 设置为自己的图片路径
    <img src="assets/increase.svg">
    <span id="text">上传图片</span>
</div>
<input type="file" id="file" accept="image/*" (change)="imagePreview($event)">
// css
#imagePreview {
    width: 80px;
    height: 80px;
    border: 1px dotted;
    padding-bottom: 5px;
    background-color: white;
    // 设置背景图片大小、不重复、居中
    background-repeat: no-repeat;
    background-position: center;
    background-size: 80px 80px;
}
input {
    display: none;
}
span {
    color: #707070;
}

效果:

添加交互效果

// js
upload() {
    document.getElementById('file').click(); // 触发上传
} 
// 预览图片
imagePreview(event) {
    const previewImage = document.getElementById('imagePreview');
    const uploadFile = document.getElementById('file');
    const reader = new FileReader();
    reader.onloadend = () => {
        // 设置图片为背景
        previewImage.style.backgroundImage = 'url(' + reader.result + ')';
        // 将原“+”号和文本内容隐藏
        const image = document.getElementById('image');
        image.style.display = 'none';
        const text = document.getElementById('text');
        text.style.display = 'none';
    };
    if (uploadFile) {
        reader.readAsDataURL(event.target.files[0]);
    }
}

上传效果:

mat-tab-group选中指定tab触发事件

使用selectedTabChange方法结合selectedIndex可实现。

selectedTabChange监听tabs是否切换,selectedIndex返回被选中tab的索引。

// component.html
<mat-tab-group (selectedTabChange)="change()" [(selectedIndex)]="index">
  <mat-tab label="fiest">
    Content 1
  </mat-tab>
  <mat-tab label="second">
   Content 2
  </mat-tab>
</mat-tab-group>

// component.ts
change() {
  if (this.index === 1) {doSomething();}
}

New a project with Angular Material

1 Install Node.js and NPM

It is not mandatory to use node.js for developing angular application. You can very well go ahead without node.js for developing angular application but it would not be wise to do so. Let me explain you some of the reasons how node.js makes angular app development process easier for us:

  • Node allows you to spin up a lightweight web server to host your application locally in your system.
  • NPM (Node Package Manager) comes with node.js by default. NPM allows you to manage your dependencies. So, you don’t have to worry for operations like adding a dependency, removing some, updating your package.json.
  • Third and the most important, npm gives you angular cli or ng cli(angular command line interface) . Angular CLI is a great tool for scaffolding your application. So, you don’t need to write boilerplates manually.
  • Angular recommends the use of TypeScript. Now, your browser does not understand TypeScript. It needs to be transpiled to JavaScript. Also, you need to bundle your js files and stylesheets together with the html doc so as to get the web app CLI which is ready to be hosted. Angular CLI helps you to do all these behind the scene. By default, ng cli uses webpack for bundling your application and is very helpful for beginners who have just jumped into web development with angular as it abstracts such complexities.
  • Resource: https://www.quora.com/Why-does-Angular-2-need-Node-js

2 Install Angular CLI

  npm install -g @angular/cli

3 New Project

IDE: IntelliJ IDEA, or “ng new project-name”

4 Install Angular Material

  ng add @angular/material

5 Use Material component in your project

  • import module in module.ts
  • use component in component.ts

No provider for FlexOffsetStyleBuilder

多模块项目: 每个模块都要导入 FlexLayoutModule

版本问题:模块版本与cli版本不同
在我的项目中,FlexLayoutModule版本为9.0.0 @angular/cli版本为8.0
解决:删除FLexLayout模块,重新下载
npm install @angular/flex-layout@8.0.0-beta.27

CSS position引发的问题

背景:实现如下顶部导航栏

将第一行位置设置为fixed,元素被移出正常文档流。第二行被第一行挡住

相关文档解释:

fixed元素会被移出正常文档流,并不为元素预留空间,而是通过指定元素相对于屏幕视口(viewport)的位置来指定元素位置。元素的位置在屏幕滚动时不会改变。打印时,元素会出现在的每页的固定位置。fixed 属性会创建新的层叠上下文。当元素祖先的 transformperspective 或 filter 属性非 none 时,容器由视口改为该祖先。

更多:
static该关键字指定元素使用正常的布局行为,即元素在文档常规流中当前的布局位置。此时 toprightbottomleft 和 z-index 属性无效。

relative该关键字下,元素先放置在未添加定位时的位置,再在不改变页面布局
的前提下调整元素位置(因此会在此元素未添加定位时所在位置留下空白)。
position: relative 对 table-*-group, table-row, table-column, table-cell, table-caption 元素无效。

absolute元素会被移出正常文档流,并不为元素预留空间,通过指定元素相对于最近的非 static 定位祖先元素的偏移,来确定元素位置。绝对定位的元素可以设置外边距(margins),且不会与其他边距合并。

sticky元素根据正常文档流进行定位,然后相对它的最近滚动祖先(nearest scrolling ancestor)和 containing block (最近块级祖先 nearest block-level ancestor),包括table-related元素,基于toprightbottom, 和 left的值进行偏移。偏移值不会影响任何其他元素的位置。该值总是创建一个新的层叠上下文(stacking context)。注意,一个sticky元素会“固定”在离它最近的一个拥有“滚动机制”的祖先上(当该祖先的overflow 是 hiddenscrollauto, 或 overlay时),即便这个祖先不是真的滚动祖先。这个阻止了所有“sticky”行为(详情见Github issue on W3C CSSWG)。

解决:position改为relative

mat-toolbar {
box-shadow: 0 3px 5px -1px rgba(0,0,0,.2), 0 6px 10px 0 rgba(0,0,0,.14), 0 1px 18px 0 rgba(0,0,0,.12);
position: relative;
z-index: 2;
}