使用ReactHook重寫ClassComponent

前言

在 React16.8 中,新增hook功能,能夠在函數組件(Function Component)中使用 state 及其它原本在類組件才能使用的功能。今日我們來試一下怎麼把一個類組件(Class Component)使用新的 Hook 功能改寫成函數組件。

為甚麼要用函數組件

函數組件和類組件最大的區別在於語法,函數組件就是一個函數接受props作為參數,返回 React Element。

1
const Home = props => <div>Welcome, ${props.name}</div>;

而類組件則需要繼承 React.Component,並實現 render 方法。與函數組件相比,在代碼簡潔程度來說,類組件略遜一籌。

1
2
3
4
5
class Home extends React.Component {
render() {
return <div>Welcome, ${props.name}</div>;
}
}

但在 React16.8 之前,函數組件一個致命的弱點,函數組件無法使用 state、refs 和生命週期鈎子,如果要使用這些功能,就必須寫成類組件。

在一些 React 的最佳實踐中推薦能使用函數組件就使用函數組件,當需要用到 state、refs 和生命週期鈎子時才改為類組件。

原因包括:

  • 函數組件代碼更簡潔
  • 因為沒有生命週期和狀態,函數組件更容易測試和閱讀(React16.8前)
  • 更符合最佳實踐中,把組件分為展示組件(Presentation Component)和容器組件(Container Component)的理念

要改寫的目標

可以來試試改改傳說 Antd 的組件,挑了一個相對簡單的 BackTop 組件來改。
GitHub: 點我

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
function getDefaultTarget() {
return window;
}

export interface BackTopProps {
visibilityHeight?: number;
onClick?: React.MouseEventHandler<HTMLElement>;
target?: () => HTMLElement | Window;
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
visible?: boolean; // Only for test. Don't use it.
}

export default class BackTop extends React.Component<BackTopProps, any> {
static defaultProps = {
visibilityHeight: 400
};

scrollEvent: any;

constructor(props: BackTopProps) {
super(props);
this.state = {
visible: false
};
}

componentDidMount() {
const getTarget = this.props.target || getDefaultTarget;
this.scrollEvent = addEventListener(
getTarget(),
"scroll",
this.handleScroll
);
this.handleScroll();
}

componentWillUnmount() {
if (this.scrollEvent) {
this.scrollEvent.remove();
}
}

scrollToTop = (e: React.MouseEvent<HTMLDivElement>) => {
const { target = getDefaultTarget, onClick } = this.props;
scrollTo(0, {
getContainer: target
});
if (typeof onClick === "function") {
onClick(e);
}
};

handleScroll = () => {
const { visibilityHeight, target = getDefaultTarget } = this.props;
const scrollTop = getScroll(target(), true);
this.setState({
visible: scrollTop > (visibilityHeight as number)
});
};

renderBackTop = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
className = "",
children
} = this.props;
const prefixCls = getPrefixCls("back-top", customizePrefixCls);
const classString = classNames(prefixCls, className, {
[`${prefixCls}-rtl`]: direction === "rtl"
});

const defaultElement = (
<div className={`${prefixCls}-content`}>
<div className={`${prefixCls}-icon`} />
</div>
);

// fix https://fb.me/react-unknown-prop
const divProps = omit(this.props, [
"prefixCls",
"className",
"children",
"visibilityHeight",
"target",
"visible"
]);

const visible =
"visible" in this.props ? this.props.visible : this.state.visible;

const backTopBtn = visible ? (
<div {...divProps} className={classString} onClick={this.scrollToTop}>
{children || defaultElement}
</div>
) : null;

return (
<Animate component="" transitionName="fade">
{backTopBtn}
</Animate>
);
};

render() {
return <ConfigConsumer>{this.renderBackTop}</ConfigConsumer>;
}
}

先分離一下關注點。這一段的代碼是執行事件的注冊和移除,相應到 Hook 的方法就是useEffectuseEffect可以返回函數,該函數將在unmount時被調用。

1
2
3
4
5
6
7
8
9
10
11
componentDidMount() {
const getTarget = this.props.target || getDefaultTarget;
this.scrollEvent = addEventListener(getTarget(), 'scroll', this.handleScroll);
this.handleScroll();
}

componentWillUnmount() {
if (this.scrollEvent) {
this.scrollEvent.remove();
}
}

轉換後

1
2
3
4
5
6
7
8
9
10
useEffect(() => {
const getTarget = this.props.target || getDefaultTarget;
const scrollEvent = addEventListener(getTarget(), "scroll", handleScroll); // 先假設有這個方法
handleScroll();
return () => {
if (scrollEvent) {
scrollEvent.remove();
}
};
});

然後是這個很明顯的constructor,是用useStateuseState返回一個數組,第一個元素就是state,第二個元素就是改變狀態的函數,具體可以在數組解構的時候重新命名,一般的命名習慣是xxx和setXxx,useState的實踐方法也分為兩種,一種是每一個狀態的調用一次useState,得到一組的xxx和setXxx,另一種作法是把所有狀態放在一個,只有一個state和setState。

1
2
3
4
5
6
constructor(props: BackTopProps) {
super(props);
this.state = {
visible: false,
};
}

轉換後

1
const [visible, setVisible] = useState(false);

其它的就沒有甚麼特別了,就照抄一遍就行

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

const BackTop: FunctionComponent<BackTopProps> = (props: BackTopProps) => {
const [visible, setVisible] = useState(false);
const handleScroll = () => {
const { visibilityHeight, target = getDefaultTarget } = this.props;
const scrollTop = getScroll(target(), true);
setVisible(
visible: scrollTop > (visibilityHeight as number),
);
};

useEffect(() => {
const getTarget = this.props.target || getDefaultTarget;
const scrollEvent = addEventListener(getTarget(), "scroll", handleScroll);
handleScroll();
return () => {
if (scrollEvent) {
scrollEvent.remove();
}
};

const scrollToTop = (e: React.MouseEvent<HTMLDivElement>) => {
const { target = getDefaultTarget, onClick } = this.props;
scrollTo(0, {
getContainer: target
});
if (typeof onClick === "function") {
onClick(e);
}
};

const renderBackTop = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const {
prefixCls: customizePrefixCls,
className = "",
children
} = this.props;
const prefixCls = getPrefixCls("back-top", customizePrefixCls);
const classString = classNames(prefixCls, className, {
[`${prefixCls}-rtl`]: direction === "rtl"
});

const defaultElement = (
<div className={`${prefixCls}-content`}>
<div className={`${prefixCls}-icon`} />
</div>
);

const divProps = omit(this.props, [
"prefixCls",
"className",
"children",
"visibilityHeight",
"target",
"visible"
]);

const visible =
"visible" in this.props ? this.props.visible : this.state.visible;

const backTopBtn = visible ? (
<div {...divProps} className={classString} onClick={this.scrollToTop}>
{children || defaultElement}
</div>
) : null;

return (
<Animate component="" transitionName="fade">
{backTopBtn}
</Animate>
);
};

return <ConfigConsumer>{this.renderBackTop}</ConfigConsumer>;
});

}

用getDerivedStateFromProps代替componentWillReceiveProps

Why

在React 16.3版本中,React官方推出了新的生命週期方法,而有一部分舊的生命週期方法則被標記為Unsafe,該方法在React17中將會被廢棄,而今日的主角componentWillReceiveProps則是其中一個。

componentWillReceiveProps

先來看看componentWillReceiveProps是怎麼用的。

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import * as React from "react";
import * as ReactDOM from "react-dom";

interface IInputProps {
value?: string;
defaultValue?: string;
onChange?: (value: string) => void;
}

interface IInputState {
value: string;
}

class Input extends React.Component<IInputProps, IInputState> {
public constructor(props: IInputProps) {
super(props);

const initialValue = props.value
? props.value
: props.defaultValue
? props.defaultValue
: "";

this.state = {
value: initialValue
};
}

public UNSAFE_componentWillReceiveProps(nextProps: IInputProps) {
if (nextProps.value !== undefined && nextProps.value !== this.props.value) {
this.setState({ value: nextProps.value });
}
}

handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { onChange } = this.props;
if (onChange) {
// 受控
onChange(e.target.value);
} else {
this.setState({ value: e.target.value });
}
};

public render(): React.ReactNode {
const { value } = this.state;
return <input value={value} onChange={this.handleChange} />;
}
}

const App = () => {
const [value, setValue] = React.useState();
return (
<>
<Input
value={value}
onChange={value => {
setValue(value);
}}
/>
</>
);
};

ReactDOM.render(<App />, document.getElementById("root"));

getDerivedStateFromProps

getDerivedStateFromPropscomponentWilLReceiveProps最大的不同就是前者靜態(static),後者是類方法,也就是getDerivedStateFromProps不能訪問this。第二個不同是調用的次數和時機。

看看這個React15版本的生命週期圖。

再看看這個React16版本比較比較。

在React15中,componentWilLReceiveProps只會在更新(Updating)的時候調用,而React16的getDerivedStateFromProps在掛載(Mounting)和更新(Updating)時都會調用,因此轉換要需要注意添加處理掛載時的邏輯。

開始轉換

可以看到在掛載時, componentWilLReceiveProps並沒有被調用,在輸入的時候(Input的input -> handleChange -> props.onChange -> App的setValue -> App的value改變 -> Input更新),componentWillReceiveProps被調用一次。

componentWilLReceiveProps替換為getDerivedStateFromProps

1
2
3
4
5
6
7
8
9
10
11
12
static getDerivedStateFromProps(
nextProps: IInputProps,
prevState: IInputState
) {
if (nextProps.value !== undefined && nextProps.value !== prevState.value) {
return {
value: nextProps.value
};
} else {
return null;
}
}

在第一次掛載的時候,getDerivedStateFromProps就已經被調用了一次,這時prevState就是construtor裡設置的。

在替換成getDerivedStateFromProps的時候,需要注意週用時機和次數即可。

奇怪的Array.prototype.length

先說結論

根據使用場景,length不一定代表數組的長度。實際上,length是指大於數組中最後一個元素的索引的32位無符號整數。

稠密數組(Dense Array) vs 稀疏數組(Sparse Array)

稠密數組是指一個數組的元素由0開始連續索引。

1
2
const denseArray = ["A", "B", "C"];
// A, B, C連續索引於0, 1, 2

顯然而見,稀疏數組就是一個不連續索引的數組

1
2
const sparseArray = ["A", , "C", "D"];
// A, C, D 分散地索引於0, 2, 3

甚麼時候length是指元素個數

當數組是稠密數組時,length是指元素個數。

1
2
3
4
5
6
7
8
const fruits = ['orange', 'apple', 'banana']; // fruits 是稠密數組
fruits.length // 3,正確的元素個數

fruits.push('mango');
fruits.length // 4,增加了一個元素

var empty = [];
empty.length // 0,空數組

甚麼時候是最後一個索引+1

當數組是稀疏數組,length等於最後一個索引加一,但不代表元素個數。

1
2
3
4
5
6
const animals = ['cat', 'dog', , 'monkey']; // animals 是稀疏數組
animals.length // 4,最後一個索引加一,但元素只有三個

var words = ['hello'];
words[6] = 'welcome'; // 這時最後的索引變成了6
words.length // 7

修改length

JavaScript允許修改length屬性,可以刪除元素或令數組變成稀疏。

1
2
3
4
const numbers = [1, 3, 5, 7, 8];

numbers.length = 3; // 修改length
console.log(numbers) // [1, 3, 5] 7和8都被刪除了

當把length設置為大於元素個數時,會令數組變成稀疏。

1
2
3
4
5
var osTypes = ['OS X', 'Linux', 'Windows'];

osTypes.length = 5; // index 4和5的元素為undefined, osTypes是稀疏

console.log(osTypes) // ['OS X', 'Linux', 'Windows', , , ]

你不知道的HTML標籤

meter

<meter>標籤定義度量衡。僅用於已知最大和最小值的度量。

1
2
<meter value="4" min="0" max="10">4 out of 10</meter>
<meter value="0.3">30%</meter>

效果如下
4 out of 10
30%

video

<video>標籤定義視頻,比如電影片段或其他視頻流。

1
2
3
4
5
<video width="320" height="240" controls>
<source src="catMovie.mp4" type="video/mp4">
<source src="catMovie.ogg" type="video/ogg">
Your browser does not support the video tag.
</video>

效果如下

progress

<progress>標籤定義運行中的任務進度(進程)。是HTML5中的新標籤。

1
<progress value="42" max="100"></progress>

效果如下

picture

<picture>允许我们在不同的设备上显示不同的图片,一般用于响应式。也是HTML5的新元素。

1
2
3
4
5
<picture>
<source media="(min-width: 650px)" srcset="images/logo.png">
<source media="(min-width: 450px)" srcset="images/logo.png">
<img src="zzz.jpg" style="width: auto;" alt="zzz">
</picture>

效果如下




wbr

<wbr>(Word Break Opportunity) 標籤規定在文本中的何處適合添加換行符。

1
2
3
<p>
Cats are the most majestic and <wbr>graceful<wbr>animals on the planet.
</p>

效果如下

Cats are the most majestic and gracefulanimals on the planet.

output

<output>標籤作為計算結果輸出顯示(比如執行腳本的輸出)。

1
2
3
4
5
<form oninput="totalWeight.value=parseInt(catAWeight.value)+parseInt(catBWeight.value)">
<input type="range" id="catAWeight" value="50">100
<input type="number" id="catBWeight" value="50">
<output name="totalWeight" for="catAWeight catBWeight"></output>
</form>

效果如下

100

blockquote

<blockquote>標籤定義摘自另一個源的塊引用。

1
2
3
<blockquote cite="http://www.worldwildlife.org/who/index.html">
For 50 years, WWF has been protecting the future of nature. The world's leading conservation organization, WWF works in 100 countries and is supported by 1.2 million members in the United States and close to 5 million globally.
</blockquote>

效果如下

For 50 years, WWF has been protecting the future of nature. The world's leading conservation organization, WWF works in 100 countries and is supported by 1.2 million members in the United States and close to 5 million globally.

time

1
2
<p> My cat wakes up at <time>11:00</time> each day.</p>
<p> I have a date with my cat on <time datetime="2019-12-25 20:00">Christmas</time>.</p>

效果如下

My cat wakes up at each day.

I have a date with my cat on .

JavaScript中的類型及其判斷方法

內置類型

JavaScript有七種的內置類型:

  1. 空值(null)
  2. 未定義(undefined)
  3. 布爾值(boolean)
  4. 數學(number)
  5. 字符串(string)
  6. 對象(object)
  7. 符號(symbol)

其中除了對象之外,其它六種統稱為基本類型

如何進行類型判斷

使用typeof

使用typeof進行類型判斷的返回值是該類型的字符串值,返回值只能為"object""number""undefined""string""boolean""symbol""function"其中一個。可以注意到的是,這些返回值跟JavaScript的內置類型不是一一進應的。
缺少了null,而多了function

與內置類型一一對應的就不詳細說了。重點關注nullfunction

對於null比較特殊,這算一個JavaScript歷史悠久的一個Bug,是因為object的底層實現中頭三位都是0,而null的底層實現直接是全0,因此typeof在判斷null才會誤判為object

1
console.log(typeof null); // object

function屬於object的子類型,屬於一個特例吧。

1
2
3
function a() {}

console.log(typeof a); // function

對於其它object的子類型,例如數組,都會直接返回object

1
2
3
const a = [1, 2, 3];

console.log(typeof a); // object

其它的判斷方法還有instance ofconstructorObject.prototype.toString.cal等方法,之後再詳細再說。

值和類型

JavaScript中的變量是沒有類型的。只有值才有類型,變量可以隨時持有任何類型的值。

1
2
3
4
5
let a = 42;
console.log(typeof a); // "number"

a = true;
console.log(typeof a); // "boolean"

學習Flex

前言

今天我們先過一過Flex的屬性的用法,再通過一個Flex的小遊戲,來深入學習Flex佈局。小遊戲連接(https://flexboxfroggy.com),在文章最後將附上參考答案。

Flex屬性

flex-direction

先說一下,在Flex佈局,會把頁面分為兩個方向,分別是主軸和交錯軸,主軸和交錯軸是通過flex-direction來決定的,flex-directionrowcolumncolumn-reverserow-reverse

舉個例子,若設定了flex-direction: row,則主軸是行(水平)方向,交錯軸則是列(垂直)方向。

justify-content

這個justify-content屬性是設置上面所說的主軸的排序方向,選項有flex-startflex-endcenterspace-betweenspace-around

具體它們有甚麼區別,來看看下圖。

align-items

align-itemsjustify-content是交錯軸版本,選項有flex-startflex-endbaselinecenterstretch

align-content

align-contentalign-items是多行版本,選項有flex-startflex-endcenterstretchspace-centerspace-around

內元件的屬性

在Flex中,分為外容器和內元件,外容器是在聲明display:flex的標籤,被外容器包含著的標籤則是內元件,上述所講的屬性都是用外容器上的。

而在內元件中則有以下幾個屬性可以設置,分別flex-growflex-shrinkflex-basisalign-selforder

  • flex-grow: 元件的伸展性,是一個數值,當空間分配還有剩餘時的當前元件的伸展性,預設值為 0,如果設置為 0 則不會縮放。
  • flex-shrink: 元件的收縮性: 元件的伸展性,是一個數值,當空間分配還不足時的當前元件的收縮性,預設值為 0,如果設置為 0 則不會縮放。
  • flex-basis: 元件的基準值,可使用不同的單位值。
  • align-self: align-self 可以調整內元件交錯軸的對齊設定(主軸線則不能另外做設定),且可以個別設定單一元件的值。
  • order: 這是一個相當特別的屬性,可以重新定義元件的排列順序,順序會依據數值的大小排列。

Flexbox Froggy 答案

1) justify-content: flex-end;
2) justify-content: center;
3) justify-content: space-around;
4) justify-content: space-between;
5) align-items: flex-end;
6) align-items: center;
justify-content: center;
7) justify-content: space-around;
8) flex-direction: row-reverse;
9) flex-direction: column;
10) flex-direction: row-reverse;
justify-content: flex-end;
11) flex-direction: column;
justify-content: flex-end;
12) flex-direction: column-reverse;
justify-content: space-between;
13) flex-direction: row-reverse;
justify-content: center;
align-items: flex-end;
14) order: 1;
15) order: -3;
16) align-self: flex-end;
17) align-self: flex-end;
order: 1;
18) flex-wrap: wrap;
19) flex-direction: column;
flex-wrap: wrap;
20) flex-flow: column wrap;
21) align-content: flex-start;
22) align-content: flex-end;
23) flex-direction: column-reverse;
align-content: center;
24) flex-flow:column-reverse wrap-reverse;
justify-content:center;
align-content:space-between;

淺談JavaScript的垃圾回收(V8為例)

前言

JavaSciptJava一樣都是由垃圾回收機制進行自動內存管理,與C/C++不同,JavaScript不需要時刻關注內存的分配和釋放問題

由於Node的出現,JavaScript已不僅僅是一門瀏覽器的腳本語言,Node極大地拓寬了JavaScript的應用場景,從客戶端延申到服務器端之後,這使得JavaScript使用者需要更加注意,在服務器端,內存管理的好壞將嚴重影響服務器的性能。

目前主流的JavaScript引擎是由Google開發的V8引擎V8性能優秀,使Google贏得了瀏覽器大戰。因此Node亦選用了V8。

V8的垃圾回收機制

主要的垃圾回收算法

V8的垃圾回數策略主要基於分代式垃圾回收機制。分代式垃圾回收機制是指將對象按照存活時間劃歸不同的世代,不同世代之後採用不同的回收算法。之所以這樣劃分是因為在本質上沒有任何一種回收算法能勝任所有場景

V8的內存分代主要分為新生代老生代兩代。其中新生代是指存活時間較短的對象,老生代中的對象則存活時間較長或常駐內存的對象

新生代

在分代的基础上,新生代中的對象主要通過Scavenge算法進行垃圾回數。在Scavenge的具體採用了Cherry算法。

Cherry算法採用複制&移動的方式實現垃圾回收。它將堆內存分為兩等份,每一部份的空間稱為semispace,兩個semispace只有一個處於使用中,另一個處理閑置狀態,使用中的叫做from空間,閑置的叫做to空間

當開始進行垃圾回收時,先檢查From空間中的存活對象,這些存活對象則會複制到To空間,而非存活的對象則會被直接釋放。然後From空間To空間的⻆色就會調換

對象晉升

當一個對象經歷過Scavenge回收,又或者To空間的內存占用比超過限制,則該對象將會由新生代晉升至老生代

老生代

對於老生代中的對象,V8主要採用Mark-SweepMark-Compact相結合的方式進行垃圾回收。Mark-Sweep是標記清除的意思,分為標記和清除兩個步驟,和Scavenge不同,Mark-Sweep不需要把內存劃分兩等份,在標記階段,Mark-Sweep會遍歷堆中的所有對象,并標記活著的對象,在清除階段,則只會清除沒有標記的對象。

Mark-Sweep會導致內存出現碎片化的現象,當需要分配一個大對象,會觸發一次不必要的垃圾回收。這時就有Mark-Compact,它跟Mark-Sweep差不多,區別只在對象在標記為死亡後,在整理的過程中,將活動的對象往一端移動,移動完成後,直接清理掉邊界外的內存。

V8主要使用Mark-Sweep,在空間不足以對從新生代中晉升過來的對象進行分配時才使用Mark-Compact

3種垃圾回收算法的簡單對比

回數算法 Mark-Sweep Mark-Compact Scavenge
速度 中等 最慢 最快
空間開銷 少(有碎片) 少(無碎片) 雙倍空間(無碎片)
是否移動對象

Javascript的This機制

先簡單說兩句

相信很多朋友在剛學習Javascript都被Javascriptthis弄得很混亂,由於我一開始就是學習Java先的,很自然就Java中的this代入了Javascript中,其實這是一個重大的錯誤,有人()說「如果上帝用七天打造這個世界,那麼Javascript就是在最後0.01秒才匆匆設計的」,現在就讓我跟大家詳細說說Javascriptthis吧!

this的意義

Java中,this是一個引用對象,引用的就是這個對象自身,在Javascript中也會有這種場景,如以下代碼的this便指向new出來的那個對象自身

1
2
3
4
5
6
7
8
9
10
11
12
13
export class People{
constructor(name, age){
this.name = name;
this.age = age;
}

sayHi(){
return `Hi, My name is ${this.name} and I am ${this.age} old`;
}
}

let patrick = new People("Patrick", 21);
console.log(patrick.sayHi()); // Hi, My name is Patrick and I am 21 old

又或者在使用Javascript的對象時,this也是指向car對象本身,但這種情況上面跟通過new關鍵字又有一些不一樣。

1
2
3
4
5
6
const car = {
speed: "100km/h",
introduce: function(){
console.log(`I can run ${this.speed}`);
}
}

在我現在的工作中,經過需要使用到React,一般在Reactconstructor中需要做以下這件事,就是需要bind綁定,為甚麼需要呢,不綁定會怎樣?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export class Input extends React.Component{
constructor(props){
super(props);
// here
this.handleChange = this.handleChange.bind(this);

this.state = {value: ""};
}

handleChange(value){
this.setState({value: value});
}

render(){
return (
<input value={this.state.value} onChange={this.onChange}/>
)
}
}

還有一種更簡單的情況,在瀏覽器打開chrome devTools,在console面版中輸入this,看看會出現甚麼東西。為甚麼會輸出window呢?

接著我跟大家解釋釋,究竟this的意義是甚麼,其實this是指運行期間的上下文,根據不同的情況有不同的綁定規則。

this的綁定規則

默認綁定

默認綁定就是最簡單的情況,在全域或者不在函數內部使this的時候,this指向的便全局變量,在瀏覽器中是window,在nodejs中便是global,默認綁定會在嚴格下失效,即嚴格模式,默認綁定的this會指向undefined。上面第四個例子便是默認綁定。

隱式綁定

當函數被調用時,若函數引用具有上下文對象,則隱式綁定會將 this 綁定到這個上下文對象。第二個例子說的就是隱式綁定,在this的綁定規則裡,最容易出錯的就是隱式綁定,來看一段代碼,最後的那兩個函數會分別返回asurprise,這種情況叫做隱式丟失,丟失的就是thisbarobj.foo的一個函數別名,在調用bar的時候會丟失對objthis引用,而直接是綁定到全局的對象中。

1
2
3
4
5
6
7
8
9
10
11
12
// 非嚴格模式
this.a = "surprise"
const obj = {
a: "a",
foo: function(){
return this.a;
}
}

const bar = obj.foo;
obj.foo();
bar();
顯式綁定

顯式綁定就要為了避免隱式丟失的出現,通過applycallbind三個方法來顯式的綁定要調用函數內部的this,看看例子最直接,在例子就直接指定this的值,這樣可以避免出現丟失的問題,所以現在為甚麼第三個例子中React constructor需要用bind綁定一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const obj1 = {
content: "i am object 1"
};
const obj2 = {
content: "i am object 2"
};
const obj3 = {
content: "i am object 3"
}
function foo(x, y ,z){
return `${this.content} arg ${x} ${y} ${z}`;
}

// use apply
foo.apply(obj1, ["a", "a", "a"]); // i am object 1 arg a a a

// use call
foo.call(obj2, "b", "b", "b"); // i am object 2 arg b b b

// use bind
const bindedFoo = foo.bind(obj3); // i am object 3 arg c c c
bindedFoo("c", "c", "c");

new綁定

第一個例子中,我們通過一個new關鍵字「實例化」了一個對象,實際上的new過程只做了下面幾件事:

  1. 創建一個新的對象
  2. 將新的對象的__prototype__屬性指向函數的prototype
  3. 將函數的this綁定向新的對象
  4. 調用函數

注意第三步,這裡就是實際上new綁定出現的原因,因為在new關鍵字的執行過程已經把新創建的對象綁定到函數的this裡,所以在new出來的對象裡面的this在運行時是會指向這個對象的。

優先級

依次為new綁定顯式綁定隱式綁定默認綁定

Javascript中的this機制其實還有一層,就是thisprototype原型鏈上的查找,可以理解this是一個二維平面,水平方向是綁定規則,垂直方向就是prototype原型鏈和JS的各種作用域。

下期將為大家介紹Javascript的原型鏈和作用域。

本人獻醜了,大家加油~!

試試CSS3的animation屬性

一. 小目標

實現長頁截屏的滑動效果。

二. 分析過程

首先,在鼠停留在圖片上的時候需要觸發動畫效果,這裡可以用偽類:hover來實現,把實現動畫效果的CSS代碼寫在:hover裡。

因為要通過CSS來控制圖片的顯示,所以這裡用background-image來加載圖片,再用background-postition來控制圖片的位置。

最後就用animation指定@keyframes,並設置持續時間即可。

三. 實現

1
2
3
4
5
6
7
8
9
/* html 略 */
.image{
width: 400px;
height: 500px;
background-image: url(https://gw.alipayobjects.com/zos/rmsportal/GnDiHuTNxcXpSaJxhmRl.jpg);
background-position-y: 0%;
background-size: cover;
background-repeat: no-repeat;
}

因為這段CSS是加在一個div上,而div本身沒有高度和寬度,所以需要加上高度和寬度。

注意一點,一般在使用background-size時要加background-repeat: no-repeat,否則如果圖片太小,會在容器不停的重複來填滿容器。有興趣的讀者可以試一下把widthheight調大,然後刪掉background-repeat

background-position-y是設置了圖片垂直方向上的開始位置,這裡0%就是由最上面開始顯示。backgroun-size: cover會把圖片盡可能的拉大以填充整個圖片,我們這個圖片是height大於width,所以會截斷了下面的一部份,截斷了的部份會通過動態去變化background-position-y慢慢顯示。

1
2
3
4
5
6
7
8
9
10
11
@keyframes scrollUpAndDown{
0%{
background-position-y: 0%;
}
50%{
background-position-y: 100%;
}
100%{
background-position-y: 0%;
}
}

要使用animation就必須掌握@keyframes,基本可以這樣理解,@keyframes就是幀的一個變化過程,可以用百份比或者from to的方式來聲明變化的過程,在裡面再聲明要變化的屬性。上面的代碼就是說,前半段動畫先把background-position-y0%遞增至100%,後半段再將其遞減至0%

1
2
3
4
5
.image:hover{
animation: scrollUpAndDown 8s;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}

最後便是如何觸發動畫的問題,這裡我們用:hover,當鼠標停留在圖片上的時候觸發。animation的第一個參數是@keyframes的名字,第二個參數是動畫持續的時間,可用sms作單位。animation-timing-function是指定動畫的速度函數,ease-in-out是以低速開始和結束。其它選項有lineareaseease-inease-outcubic-bezier(x,x,x,x)等等,感興趣的讀者可以自行換個選項試試不的效果。

至此我們的這個簡單的動畫就已經完成了。

完整的代碼在這裡,大家加油!

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×