0%

介绍

Pull和Push是两种数据流的传递方式,他们决定了数据如何从生产者传递到消费者。

Pull

Pull是拉模型,也就是消费者主动向生产者请求数据,消费者是主动的一方。在JavaScript中,所有的函数都是Pull模型的,函数相当于数据的生产者,函数调用者相当于数据的消费者。调用这个函数,就是在请求数据。

下面的代码中num.reduce函数用来求数组的和,const sum = nums.reduce(...)调用函数请求数据。

1
2
3
const nums = [1, 2, 3, 4, 5];
const sum = nums.reduce((acc, cur) => acc + cur, 0);
console.log(sum); // 15

Push

Push是推模型,由数据的生产者主动向外推送数据,生产者是主动的一方。在JavaScript中,Promise就是Push模型的,与函数不同,Promise决定什么时候发送数据。

下面代码中的p.then(console)会在Promise resolve之后执行,Promise决定了何时发送数据。

1
2
3
4
5
6
const p = new Promise((resolve) => {
setTimeout(() => {
resolve(42);
}, 1000);
});
p.then(console.log); // 42

RxJS中的Observable也是推模型的,Observable决定了何时发送数据。

References

  1. https://rxjs.dev/guide/observable

概述

今天来学习一下RxJS中的Subject,有人说它既是Observable,又是Observer,那么它到底是什么呢?先来看一下官方的定义:

1
Subject is equivalent to an EventEmitter, and the only way of multicasting a value or event to multiple Observers.

翻译一下:Subject等价于EventEmitter,是唯一一种将值或事件多播给多个观察者的方法。

Subject的特点

多播

下面的代码中,有两个订阅者,但是他们共享数据流,所以他们会输出相同的随机数。这就是Subject的多播特性。

1
2
3
4
5
6
import { Subject } from 'rxjs';

const subject = new Subject();
subject.subscribe((v) => console.log('observerA: ' + v));
subject.subscribe((v) => console.log('observerB: ' + v));
subject.next(Math.random());

但是如果改用observable, 那么每个订阅者都会收到一份数据,以下代码打印出不同的随机数。也就是说:对于普通的observable,每次订阅都会发起新的请求。一次订阅只能有一个观察者。

1
2
3
4
5
6
7
8
import { Observable } from 'rxjs';

const observable = new Observable((subscriber) => {
subscriber.next(Math.random());
});

observable.subscribe((v) => console.log(v));
observable.subscribe((v) => console.log(v));

总结

特性 Observable Subject
单播多播 单播 多播
数据产生时机 订阅时产生 由Subject.next()产生
冷热
是否是Observer
订阅时机的影响 每次订阅后产生新的数据 共享数据流,订阅后接收续生成的数据

今天来一篇RxJS的科普。

什么是RxJS

先看RxJS这个词,RxJS = Rx + JS,JS就是Javascript,那么Rx又是什么呢?

Rx = Reactive extension, 最早是由微软开发的一套软件库,用于处理异步数据流和事件流。后来,ReactiveX被移植到了多种编程语言中,如RxJS(Javascript)、RxJava(Java)、RxSwift(Swift)等。

下面是Wikipedia对ReactiveX的定义:
ReactiveX is an API for asynchronous programming with observable streams。

RxJS就是Rx的JavaScript版本。

RxJS的核心概念

Rx中使用了两个设计模式,观察者模式和迭代器模式。Rx还大量使用了函数式编程的思想。

Observable

这是RxJS中最终要的概念,没有之一,Observable,顾名思义,就是可观察对象,我们看一个现实中的例子,你在看风景,那么风景就是一个Observable,你在观察风景,风景发生了变化,你就会感知到,这就是Observable的概念。

Observer

Observer - 观察者,还是以现实中的例子来解释,你在看风景,你就是一个Observer。

Subscription

Subscription - 订阅,订阅是联系Observable和Observer的桥梁,我们常说,Observable发生变化时,Observer会收到通知,那么Observer如何收到通知呢?就是通过订阅机制来实现的。

Operators

Operators - 操作符,RxJS提供了许多操作符,用于对Observable进行操作,比如mapfilterreduce等。Operators都是纯函数,它们不会改变原始的Observable,而是返回一个新的Observable。

Subject

Subject - 主题,Subject是一种特殊的Observable,它允许将值多播给多个Observer。也就是说,它是用来实现多播的,同时,Subject也是Observer。

Scheduler

Scheduler - 调度器,RxJS提供了调度器,用于控制Observable何时开始发送通知。

References

  1. https://en.wikipedia.org/wiki/ReactiveX
  2. https://rxjs.dev/guide/overview

概述

RxJS中有许多和map相关的操作符,如mapmapTomergeMapswitchMap等,本文一一介绍。

map

map操作符是RxJS中最常用的操作符之一,它类似于数组的map方法,可以对Observable发出的每个值进行转换。

1

今天介绍一下JS中的Web Worker。

概述

今天来聊一聊Angular中的effect, 使用过React框架的同学对effect应该都不陌生,不过,Angular中的effect和React中的effect有所不同。

1
2
3
effect(() => {
console.log(`The current count is: ${count()}`);
});

effect函数特点

effect函数有如下特点:

  1. effect函数至少运行一次,无论effect函数中是否读取signal,也无论signal的值是否发生变化。
  2. 如果effect函数中读取了signal,那么当signal的值发生变化时,effect函数会再次运行。
  3. 如果effect函数中没有读取signal,那么effect函数只会运行一次。当signal变化时,effect函数不会再次运行。
  4. effect函数中不要更新signal的值,否则会导致死循环。(因为signal更新—>effect运行—>更新signal—>effect运行…)

比如下面这个effect函数,只会运行一次,因为effect函数中没有读取count

1
2
3
effect(() => {
console.log('This is a effect function');
});

比如下面这个effect函数,会导致死循环。

1
2
3
effect(() => {
count.set(count() + 1);
});

effect函数只能运行在injection context中(比如构造函数体内), 如果effect函数的使用位置不对(比如放到了ngOnInit函数中),那么Angular会报如下错误:关于injection context的更多信息,可以查看这里

1
core.mjs:6643 ERROR RuntimeError: NG0203: effect() can only be used within an injection context such as a constructor, a factory function, a field initializer, or a function used with `runInInjectionContext`. Find more at https://angular.dev/errors/NG0203

effect函数的使用场景

Effects are rarely needed in most application code, but may be useful in specific circumstances. Here are some examples of situations where an effect might be a good solution:

  • Logging data being displayed and when it changes, either for analytics or as a debugging tool.
  • Keeping data in sync with window.localStorage.
  • Adding custom DOM behavior that can’t be expressed with template syntax.
  • Performing custom rendering to a <canvas>, charting library, or other third party UI library.

Destroy effect

effect函数会在组件销毁时自动销毁,不需要手动清理。当然effect函数返回EffectRef对象,可以手动调用destroy方法销毁effect函数。

1
2
3
4
5
const effectRef = effect(() => {
console.log('This is a effect function');
});

effectRef.destroy();

untrack模式

https://angular.dev/guide/signals#reading-without-tracking-dependencies

cleanup function.

effect函数中可以定义一个清理函数onCleanup,当effect函数被销毁时,清理函数会被调用。下面的代码演示了如何使用onCleanup函数清理定时器。

1
2
3
4
5
6
7
8
9
10
effect((onCleanup) => {
const user = currentUser();
const timer = setTimeout(() => {
console.log(`1 second ago, the user became ${user}`);
}, 1000);

onCleanup(() => {
clearTimeout(timer);
});
});

Reference

  1. https://angular.dev/guide/signals#effects

请问以下代码输出什么?为什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import {Component} from "react";

interface DemoComponentState {
val: number;
}

export class DemoComponent extends Component<{}, DemoComponentState> {
constructor(props: {}) {
super(props);
this.state = {
val: 0,
};
}

componentDidMount() {
this.setState({val: this.state.val + 1}, () => {
console.log(this.state.val);
});

this.setState({val: this.state.val + 1}, () => {
console.log(this.state.val);
});

setTimeout(() => {
this.setState({val: this.state.val + 1}, () => {
console.log(this.state.val);
});

this.setState({val: this.state.val + 1}, () => {
console.log(this.state.val);
});
}, 0);
}

render() {
return null;
}
}

答案:1 1 2 2。
解析:

概述

position是CSS中非常重要的一个属性,也是面试中常考的考点之一!比如昨天我的面试中面试官就问我,absolute和fixed有什么区别?可惜我没有答上来。

那么这个position到底代表什么呢?顾名思义,就是位置,position决定了浏览器如何确定一个元素的位置。来看一下官方的定义:

1
2
The position CSS property sets how an element is positioned in a document. 
The top, right, bottom, and left properties determine the final location of positioned elements.

我们翻译一下这段话:position属性设置了一个元素如何在文档中定位。top, right, bottom, left属性决定了定位元素的最终位置。也就是说:position决定了如何放置一个元素,toprightbottomleft决定了元素的最终位置。

position的取值有5种,分别是:staticrelativeabsolutefixedsticky。下面我们来详细了解一下这5种取值。

positioned element

A positioned element is an element whose computed position value is either relative, absolute, fixed, or sticky. (In other words, it’s anything except static.)

说白了,只要元素的position属性不是static,那么这个元素就是一个positioned element

static

这个是默认值,可以这么说,平时开发的时候我们基本用不到这个值,因为它什么也不做,元素就是正常按照文档流一个一个排列就完了。而且由于是默认值,如果不设置position属性,那么元素就是static的。

1
2
3
4
5
<body>
<div class="div1"></div>
<div class="div2"></div>
<div class="div3"></div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
div {
width: 100px;
height: 100px;
}
.div1 {
background-color: red;
}
.div2 {
background-color: green;
}
.div3 {
background-color: blue;
}

static

注意在position: static模式下,top, right, bottom, left是不起作用的,因为这些属性只对positioned elements有效。

1
2
3
4
#child {
top: 20px; /* This doesn't work! need nonstatic position */
background-color: red;
}

relative

记得之前有一次面试,面试官突然问我,position: relative你知道吧,我说那必须知道啊!然后他说:那么你说说relative到底相对的是谁?我一下就懵了,之前根本没有思考过这个问题。其实答案很简单,相对的是元素自己本来的位置。也就是说,relative是相对于元素自己本来的位置进行定位的。- 注意不是相对于父元素,这是误解。

下图中绿色方块定位方式设置为relative,相对其原始位置向右偏移100像素,向下偏移20像素,原始位置保留,没有影响红色方块和蓝色方块。

1
2
3
4
5
<body>
<div class="div1"></div>
<div class="div2"></div>
<div class="div3"></div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
div {
width: 100px;
height: 100px;
}
.div1 {
background-color: red;
}
.div2 {
position: relative;
top: 20px;
left: 100px;
background-color: green;
}
.div3 {
background-color: blue;
}

relative

设置为relative的元素不会脱离文档流,最终位置相对于元素自己本来的位置进行偏移。原始的位置会保留,不影响其他元素(子元素除外)布局。

注意:relative会创建一个stack context,详情请看这里.

absolute

absolute是指绝对定位,它是相对于最近的非static定位的父元素进行定位的。如果没有非static定位的父元素,那么它是相对于<html>元素进行定位的。

标记为position: absolute的元素会脱离文档流,不占据空间,不影响其他元素的布局。下图中绿色方块定位方式设置为absolute,相对于其最近的非static定位的父元素(body)进行定位,向右偏移100像素,向下偏移20像素,原始位置不保留,影响蓝色方块的布局(蓝色方块补充了绿色方块原来的位置)

1
2
3
4
5
6
7
<body>
<div class="container">
<div class="div1"></div>
<div class="div2"></div>
<div class="div3"></div>
</div>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
div {
width: 100px;
height: 100px;
}
.div1 {
background-color: red;
}
.div2 {
position: absolute;
top: 20px;
left: 100px;
background-color: green;
}
.div3 {
background-color: blue;
}

absolute

fixed

fixedabsolute非常相似,元素也会脱离文档流,不同的是fixed是相对于viewport进行定位的。也就是说,无论你怎么滚动页面,fixed元素都会固定在浏览器视口的某个位置。(仿佛固定在屏幕上一样)

fixed的使用场景有很多,比如常见的页面右下角的返回顶部按钮,基本都是用fixed实现的。比如下面这个例子,注意这种场景不能使用absolute来实现,否则当页面滚动时,元素会跟着滚动。

1
2
3
4
5
#child {
position: fixed;
bottom: 32px;
right: 32px;
}

sticky

sticky - 黏性的;黏的,其实从这个名字就可以看出来,这种布局可以实现黏性效果,比如常见的吸顶效果(当页面滚动时,某个元素,比如顶部菜单栏固定不动),下面我们看看如何实现sticky效果。

1
2
3
<div class="container">
<div class="content">This is a sticky bar</div>
</div>
1
2
3
4
5
6
7
8
9
div.container {
height: 500vh; // 500个视口高度,使页面可以垂直滚动
}
div.content {
position: sticky; // 设置为sticky
top: 16px; // 当滚动至距离父元素顶部16px时,元素固定,也就是变得sticky.
height: 64px;
background-color: #ad1414;
}

sticky元素不会脱离文档流,它会按正常的文档流进行布局,但是当页面滚动至临界距离时(上面设置的top: 16px),元素就会固定住,不会继续滚动了,所以sticky也是有参照元素的,它参照的元素是它最近的可滚动的祖先元素。

sticky是相对定位和固定定位的结合体。它的行为就像是relativefixed的合体,当元素在屏幕中可见时,它的行为就像relative,当元素在屏幕外时,它的行为就像fixed

position属性如何影响top, right, bottom, left的表现?

position属性的值会直接影响top, right, bottom, left的表现,具体如下:

总结

是否脱离文档流 是否保留原来位置 是否影响 top/bottom/left/right 参照元素 是否创建堆叠上下文
static 无(按照正常文档流布局)
relative 元素自身原本的位置 否(除非设置了 z-index 或其他触发条件)
absolute 最近的已定位祖先元素(非 static),否则为 <html> 否(除非设置了 z-index 或其他触发条件)
fixed 浏览器视口(viewport) 否(除非设置了 z-index 或其他触发条件)
sticky 部分(滚动时脱离) 元素原本的位置与视口之间的相对关系 否(除非设置了 z-index 或其他触发条件)

References

  1. https://developer.mozilla.org/en-US/docs/Web/CSS/position

什么是盒模型?

当浏览器对一个页面进行布局的时候,浏览器的渲染引擎会把每个元素看视为一个盒子(box),这个盒子就是盒模型。盒模型是CSS布局的基础,理解盒模型对于理解CSS布局非常重要。

box-model

盒模型的四个组成部分

内容区域(content)

以上图为例,中间的黑色矩形就是内容区域,内容区域是盒模型的核心部分,包含了元素的实际内容,比如文本、图片等。

内边距(padding)

上图中黑色矩形和蓝色矩形框之间的白色部分就是padding,内边距是内容区域和边框之间的空间。内边距可以用来增加内容区域和边框之间的距离。

边框(border)

上图中蓝色矩形框就是边框,边框是元素的边界,边框也是有宽度和颜色的。

外边距(margin)

上图中蓝色矩形框和最外层的黑色矩形框之间的透明色部分就是外边距,外边距是元素和其他元素之间的空间。外边距可以用来增加元素之间的距离。

box-sizing对于盒模型的影响

CSS中的box-sizing属性决定了如何计算一个元素的宽度和高度,它有两个取值:content-boxborder-box

content-box

content-box是默认值,表示元素的宽度和高度只包括内容区域,不包括内边距(padding)和边框(border)。也就是说,如果你设置了一个元素的宽度为100px,高度为100px,那么它的实际宽度和高度就是100px,不包括内边距和边框。

计算公式如下:

1
2
元素的实际宽度 = CSS中的width
元素的实际高度 = CSS中的height

看一段CSS代码,因为没有指定box-sizing属性,所以默认是content-box,所以box元素的宽度是100px,高度是100px。

1
2
3
4
5
6
.box {
width: 100px;
height: 100px;
padding: 10px;
border: 5px solid black;
}

border-box

border-box表示元素的宽度和高度包括内容区域、内边距和边框。也就是说,如果你设置了一个元素的宽度为100px,高度为100px,那么它的实际宽度和高度就是100px - padding - border。计算公式如下:

1
2
元素的实际宽度 = CSS中的width - padding-left - padding-right - border-left - border-right
元素的实际高度 = CSS中的height - padding-top - padding-bottom - border-top - border-bottom

看一段CSS代码,你知道如何计算元素的实际宽高吗?

1
2
3
4
5
6
7
.box {
box-sizing: border-box;
width: 100px;
height: 100px;
padding: 10px;
border: 5px solid black;
}

根据border-box的计算公式:

  • box元素的实际宽度 = 100px - 10px * 2 - 5px * 2 = 70px
  • box元素的实际高度 = 100px - 10px * 2 - 5px * 2 = 70px

Reference

  1. https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_box_model/Introduction_to_the_CSS_box_model