讓我們來看一下這個以函數形式定義的`Greeting`組件:
~~~jsx
function Greeting() {
return <p>Hello</p>;
}
~~~
React 也支持將他定義成一個類:
~~~jsx
class Greeting extends React.Component {
render() {
return <p>Hello</p>;
}
}
~~~
(直到[最近](https://reactjs.org/docs/hooks-intro.html),這是使用 state 特性的唯一方式)
當你要渲染一個`<Greeting />`組件時,你并不需要關心它是如何定義的:
~~~jsx
// 是類還是函數 —— 無所謂
<Greeting />
~~~
但*React 本身*在意其中的差別!
如果`Greeting`是一個函數,React 需要調用它。
~~~jsx
// 你的代碼
function Greeting() {
return <p>Hello</p>;
}
// React 內部
const result = Greeting(props); // <p>Hello</p>
~~~
但如果`Greeting`是一個類,React 需要先用`new`操作符將其實例化,*然后*調用剛才生成實例的`render`方法:
~~~jsx
// 你的代碼
class Greeting extends React.Component {
render() {
return <p>Hello</p>;
}
}
// React 內部
const instance = new Greeting(props); // Greeting {}
const result = instance.render(); // <p>Hello</p>
~~~
無論哪種情況 React 的目標都是去獲取渲染后的節點(在這個案例中,`<p>Hello</p>`)。但具體的步驟取決于`Greeting`是如何定義的。
**所以 React 是怎么知道某樣東西是 class 還是 function 的呢?**
就像我[上一篇博客](https://overreacted.io/why-do-we-write-super-props/)中提到的,**你并不*需要*知道這個才能高效使用 React。**我多年來都不知道這個。請不要把這變成一道面試題。事實上,這篇博客更多的是關于 JavaScript 而不是 React。
這篇博客是寫給那些對 React 具體是**如何**工作的表示好奇的讀者的。你是那樣的人嗎?那我們一起深入探討一下吧。
**這將是一段漫長的旅程,系好安全帶。這篇文章并沒有多少關于 React 本身的信息,但我們會涉及到`new`、`this`、`class`、箭頭函數、`prototype`、`__proto__`、`instanceof`等方面,以及這些東西是如何在 JavaScript 中一起工作的。幸運的是,你并不需要在使用 React 時一直想著這些,除非你正在實現 React…**
(如果你真的很想知道答案,直接翻到最下面。)
* * *
首先,我們需要理解為什么把函數和類分開處理很重要。注意看我們是怎么使用`new`操作符來調用一個類的:
~~~jsx
// 如果 Greeting 是一個函數
const result = Greeting(props); // <p>Hello</p>
// 如果 Greeting 是一個類
const instance = new Greeting(props); // Greeting {}
const result = instance.render(); // <p>Hello</p>
~~~
我們來簡單看一下`new`在 JavaScript 是干什么的。
* * *
在過去,JavaScript 還沒有類。但是,你可以使用普通函數來模擬。**具體來講,只要在函數調用前加上`new`操作符,你就可以把*任何*函數當做一個類的構造函數來用:**
~~~jsx
// 只是一個函數
function Person(name) {
this.name = name;
}
var fred = new Person('Fred'); // ? Person {name: 'Fred'}
var george = Person('George'); // ?? 沒用的
~~~
現在你依然可以這樣寫!在 DevTools 里試試吧。
如果你調用`Person('Fred')`時**沒有**加`new`,其中的`this`會指向某個全局且無用的東西(比如,`window`或者`undefined`),因此我們的代碼會崩潰,或者做一些像設置`window.name`之類的傻事。
通過在調用前增加`new`,我們說:「嘿 JavaScript,我知道`Person`只是個函數,但讓我們假裝它是個構造函數吧。**創建一個`{}`對象并把`Person`中的`this`指向那個對象,以便我可以通過類似`this.name`的形式去設置一些東西,然后把這個對象返回給我。**」
這就是`new`操作符所做的事。
~~~jsx
var fred = new Person('Fred'); // 和 `Person` 中的 `this` 等效的對象
~~~
`new`操作符同時也把我們放在`Person.prototype`上的東西放到了`fred`對象上:
~~~jsx
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() { alert('Hi, I am ' + this.name);}
var fred = new Person('Fred');
fred.sayHi();
~~~
這就是在 JavaScript 直接支持類之前,人們模擬類的方式。
* * *
`new`在 JavaScript 中已經存在了好久了,然而類還只是最近的事,它的出現讓我們能夠重構我們前面的代碼以使它更符合我們的本意:
~~~jsx
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
alert('Hi, I am ' + this.name);
}
}
let fred = new Person('Fred');
fred.sayHi();
~~~
***捕捉開發者的本意***是語言和 API 設計中非常重要的一點。
如果你寫了一個函數,JavaScript 沒辦法判斷它應該像`alert()`一樣被調用,還是應該被視作像`new Person()`一樣的構造函數。忘記給像`Person`這樣的函數指定`new`會導致令人費解的行為。
**類語法允許我們說:「這不僅僅是個函數 —— 這是個類并且它有構造函數」。**如果你在調用它時忘了加`new`,JavaScript 會報錯:
~~~jsx
let fred = new Person('Fred');
// ? 如果 Person 是個函數:有效
// ? 如果 Person 是個類:依然有效
let george = Person('George'); // 我們忘記使用 `new`
// ?? 如果 Person 是個長得像構造函數的方法:令人困惑的行為
// ?? 如果 Person 是個類:立即失敗
~~~
這可以幫助我們在早期捕捉錯誤,而不會遇到類似`this.name`被當成`window.name`對待而不是`george.name`的隱晦錯誤。
然而,這意味著 React 需要在調用所有類之前加上`new`,而不能把它直接當做一個常規的函數去調用,因為 JavaScript 會把它當做一個錯誤對待!
~~~jsx
class Counter extends React.Component {
render() {
return <p>Hello</p>;
}
}
// ?? React 不能簡單這么做:
const instance = Counter(props);
~~~
這意味著麻煩。
* * *
在我們看到 React 如何處理這個問題之前,很重要的一點就是要記得大部分 React 的用戶會使用 Babel 等編譯器來編譯類等現代化的特性以便能在老舊的瀏覽器上運行。因此我們需要在我們的設計中考慮編譯器。
在 Babel 的早期版本中,類不加`new`也可以被調用。但這個問題已經被修復了 —— 通過生成額外的代碼的方式。
~~~jsx
function Person(name) {
// 稍微簡化了一下 Babel 的輸出:
if (!(this instanceof Person)) {
throw new TypeError("Cannot call a class as a function");
}
// Our code:
this.name = name;
}
new Person('Fred'); // ? OK
Person('George'); // ?? 無法把類當做函數來調用
~~~
你或許已經在你構建出來的包中見過類似的代碼,這就是那些`_classCallCheck`函數做的事。(你可以通過啟用「loose mode」來關閉檢查以減小構建包的尺寸,但這或許會使你最終轉向真正的原生類時變得復雜)
* * *
至此,你應該已經大致理解了調用時加不加`new`的差別:
| | `new Person()` | `Person()` |
| --- | --- | --- |
| `class` | ?`this`是一個`Person`實例 | ??`TypeError` |
| `function` | ?`this`是一個`Person`實例 | ??`this`是`window`或`undefined` |
這就是 React 正確調用你的組件很重要的原因。**如果你的組件被定義為一個類,React 需要使用`new`來調用它**。
所以 React 能檢查出某樣東西是否是類嗎?
沒那么容易!即便我們能夠[在 JavaScript 中區分類和函數](https://stackoverflow.com/questions/29093396/how-do-you-check-the-difference-between-an-ecmascript-6-class-and-function),面對被 Babel 等工具處理過的類這還是沒用。對瀏覽器而言,它們只是不同的函數。這是 React 的不幸。
* * *
好,那 React 可以直接在每次調用時都加上`new`嗎?很遺憾,這種方法并不總是有用。
對于常規函數,用`new`調用會給它們一個`this`作為對象實例。對于用作構造函數的函數(比如我們前面提到的`Person`)是可取的,但對函數組件這或許就比較令人困惑了:
~~~jsx
function Greeting() {
// 我們并不期望 `this` 在這里表示任何類型的實例
return <p>Hello</p>;
}
~~~
這暫且還能忍,還有兩個*其他*理由會扼殺這個想法。
* * *
關于為什么總是使用`new`是沒用的的第一個理由是,對于原生的箭頭函數(不是那些被 Babel 編譯過的),用`new`調用會拋出一個錯誤:
~~~jsx
const Greeting = () => <p>Hello</p>;
new Greeting(); // ?? Greeting 不是一個構造函數
~~~
這個行為是遵循箭頭函數的設計而刻意為之的。箭頭函數的一個附帶作用是它*沒有*自己的`this`值 ——`this`解析自離得最近的常規函數:
~~~jsx
class Friends extends React.Component {
render() {
const friends = this.props.friends;
return friends.map(friend =>
<Friend
// `this` 解析自 `render` 方法
size={this.props.size}
name={friend.name}
key={friend.id}
/>
);
}
}
~~~
OK,所以**箭頭函數沒有自己的`this`。**但這意味著它作為構造函數是完全無用的!
~~~jsx
const Person = (name) => {
// ?? 這么寫是沒有意義的!
this.name = name;
}
~~~
因此,**JavaScript 不允許用`new`調用箭頭函數。**如果你這么做,你或許已經犯了錯,最好早點告訴你。這和 JavaScript 不讓你*不加*`new`去調用一個類是類似的。
這樣很不錯,但這也讓我們的計劃受阻。React 不能簡單對所有東西都使用`new`,因為會破壞箭頭函數!我們可以利用箭頭函數沒有`prototype`的特點來檢測箭頭函數,不對它們使用`new`:
~~~jsx
(() => {}).prototype // undefined
(function() {}).prototype // {constructor: f}
~~~
但這對于被 Babel 編譯過的函數是[沒用](https://github.com/facebook/react/issues/4599#issuecomment-136562930)的。這或許沒什么大不了,但還有另一個原因使得這條路不會有結果。
* * *
另一個我們不能總是使用`new`的原因是它會妨礙 React 支持返回字符串或其它原始類型的組件。
~~~jsx
function Greeting() {
return 'Hello';
}
Greeting(); // ? 'Hello'
new Greeting(); // ?? Greeting {}
~~~
這,再一次,和[`new`操作符](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new)的怪異設計有關。如我們之前所看到的,`new`告訴 JavaScript 引擎去創建一個對象,讓這個對象成為函數內部的`this`,然后把這個對象作為`new`的結果給我們。
然而,JavaScript 也允許一個使用`new`調用的函數返回另一個對象以**覆蓋**`new`的返回值。或許,這在我們利用諸如「對象池模式」來對組件進行復用時是被認為有用的:
~~~jsx
// 創建了一個懶變量 zeroVector = null;
function Vector(x, y) {
if (x === 0 && y === 0) {
if (zeroVector !== null) {
// 復用同一個實例
return zeroVector;
}
zeroVector = this;
}
this.x = x;
this.y = y;
}
var a = new Vector(1, 1);
var b = new Vector(0, 0);
var c = new Vector(0, 0); // ?? b === c
~~~
然而,如果一個函數的返回值*不是*一個對象,它會被`new`*完全忽略*。如果你返回了一個字符串或數字,就好像完全沒有`return`一樣。
~~~jsx
function Answer() {
return 42;
}
Answer(); // ? 42
new Answer(); // ?? Answer {}
~~~
當使用`new`調用函數時,是沒辦法讀取原始類型(例如一個數字或字符串)的返回值的。因此如果 React 總是使用`new`,就沒辦法增加對返回字符串的組件的支持!
這是不可接受的,因此我們必須妥協。
* * *
至此我們學到了什么?React 在調用類(包括 Babel 輸出的)時*需要用*`new`,但在調用常規函數或箭頭函數時(包括 Babel 輸出的)**不需要用**`new`,并且沒有可靠的方法來區分這些情況。
**如果我們沒法解決一個籠統的問題,我們能解決一個具體的嗎?**
當你把一個組件定義為類,你很可能會想要擴展`React.Component`以便獲取內置的方法,比如`this.setState()`。**與其試圖檢測所有的類,我們能否只檢測`React.Component`的后代呢?**
劇透:React 就是這么干的。
* * *
或許,檢查`Greeting`是否是一個 React 組件類的最符合語言習慣的方式是測試`Greeting.prototype instanceof React.Component`:
~~~jsx
class A {}
class B extends A {}
console.log(B.prototype instanceof A); // true
~~~
我知道你在想什么,剛才發生了什么?!為了回答這個問題,我們需要理解 JavaScript 原型。
你或許對「原型鏈」很熟悉。JavaScript 中的每一個對象都有一個「原型」。當我們寫`fred.sayHi()`但`fred`對象沒有`sayHi`屬性,我們嘗試到`fred`的原型上去找`sayHi`屬性。要是我們在這兒找不到,就去找原型鏈的下一個原型 ——`fred`的原型的原型,以此類推。
**費解的是,一個類或函數的`prototype`屬性*并不*指向那個值的原型。**我沒開玩笑。
~~~jsx
function Person() {}
console.log(Person.prototype); // ?? 不是 Person 的原型
console.log(Person.__proto__); // ?? Person 的原型
~~~
因此「原型鏈」更像是`__proto__.__proto__.__proto__`而不是`prototype.prototype.prototype`,我花了好幾年才搞懂這一點。
那么函數和類的`prototype`屬性又是什么?**是用`new`調用那個類或函數生成的所有對象的`__proto__`!**
~~~jsx
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
alert('Hi, I am ' + this.name);
}
var fred = new Person('Fred'); // 設置 `fred.__proto__` 為 `Person.prototype`
~~~
那個`__proto__`鏈才是 JavaScript 用來查找屬性的:
~~~jsx
fred.sayHi();
// 1. fred 有 sayHi 屬性嗎?不。
// 2. fred.__proto__ 有 sayHi 屬性嗎?是的,調用它!
fred.toString();
// 1. fred 有 toString 屬性嗎?不。
// 2. fred.__proto__ 有 toString 屬性嗎?不。
// 3. fred.__proto__.__proto__ 有 toString 屬性嗎?是的,調用它!
~~~
在實戰中,你應該幾乎永遠不需要直接在代碼里動到`__proto__`,除非你在調試和原型鏈相關的問題。如果你想讓某樣東西在`fred.__proto__`上可用,你應該把它放在`Person.prototype`,至少它最初是這么設計的。
`__proto__`屬性甚至一開始就不應該被瀏覽器暴露出來,因為原型鏈應該被視為一個內部概念,然而某些瀏覽器增加了`__proto__`并最終勉強被標準化(但已被廢棄并推薦使用`Object.getPrototypeOf()`)。
**然而一個名叫「原型」的屬性卻給不了我一個值的「原型」這一點還是很讓我困惑**(例如,`fred.prototype`是未定義的,因為`fred`不是一個函數)。個人觀點,我覺得這是即便有經驗的開發者也容易誤解 JavaScript 原型鏈的最大原因。
* * *
這篇博客很長,是吧?已經到 80% 了,堅持住。
我們知道當說`obj.foo`的時候,JavaScript 事實上會沿著`obj`,`obj.__proto__`,`obj.__proto__.__proto__`等等一路尋找`foo`。
在使用類時,你并非直接面對這一機制,但`extends`的原理依然是基于這項老舊但有效的原型鏈機制。這也是的我們的 React 類實例能夠訪問如`setState`這樣方法的原因:
~~~jsx
class Greeting extends React.Component {
render() {
return <p>Hello</p>;
}
}
let c = new Greeting();
console.log(c.__proto__); // Greeting.prototype
console.log(c.__proto__.__proto__); // React.Component.prototype
console.log(c.__proto__.__proto__.__proto__); // Object.prototype
c.render(); // 在 c.__proto__ (Greeting.prototype) 上找到
c.setState(); // 在 c.__proto__.__proto__ (React.Component.prototype) 上找到
c.toString(); // 在 c.__proto__.__proto__.__proto__ (Object.prototype) 上找到
~~~
換句話說,**當你在使用類的時候,實例的`__proto__`鏈「鏡像」了類的層級結構:**
~~~jsx
// `extends` 鏈
Greeting
→ React.Component
→ Object (間接的)
// `__proto__` 鏈
new Greeting()
→ Greeting.prototype
→ React.Component.prototype
→ Object.prototype
~~~
2 條鏈。
* * *
既然`__proto__`鏈鏡像了類的層級結構,我們可以檢查一個`Greeting`是否擴展了`React.Component`,我們從`Greeting.prototype`開始,一路沿著`__proto__`鏈:
~~~jsx
// `__proto__` chain
new Greeting()
→ Greeting.prototype // ??? 我們從這兒開始
→ React.Component.prototype // ? 找到了!
→ Object.prototype
~~~
方便的是,`x instanceof Y`做的就是這類搜索。它沿著`x.__proto__`鏈尋找`Y.prototype`是否在那兒。
通常,這被用來判斷某樣東西是否是一個類的實例:
~~~jsx
let greeting = new Greeting();
console.log(greeting instanceof Greeting); // true
// greeting (???? 我們從這兒開始)
// .__proto__ → Greeting.prototype (? 找到了!)
// .__proto__ → React.Component.prototype
// .__proto__ → Object.prototype
console.log(greeting instanceof React.Component); // true
// greeting (???? 我們從這兒開始)
// .__proto__ → Greeting.prototype
// .__proto__ → React.Component.prototype (? 找到了!)
// .__proto__ → Object.prototype
console.log(greeting instanceof Object); // true
// greeting (???? 我們從這兒開始)
// .__proto__ → Greeting.prototype
// .__proto__ → React.Component.prototype
// .__proto__ → Object.prototype (? 找到了!)
console.log(greeting instanceof Banana); // false
// greeting (???? 我們從這兒開始)
// .__proto__ → Greeting.prototype
// .__proto__ → React.Component.prototype
// .__proto__ → Object.prototype (??? 沒找到!)
~~~
但這用來判斷一個類是否擴展了另一個類還是有效的:
~~~jsx
console.log(Greeting.prototype instanceof React.Component);
// greeting
// .__proto__ → Greeting.prototype (???? 我們從這兒開始)
// .__proto__ → React.Component.prototype (? 找到了!)
// .__proto__ → Object.prototype
~~~
這種檢查方式就是我們判斷某樣東西是一個 React 組件類還是一個常規函數的方式。
* * *
然而 React 并不是這么做的。 ??
關于`instanceof`解決方案有一點附加說明,當頁面上有多個 React 副本,并且我們要檢查的組件繼承自**另一個**React 副本的`React.Component`時,這種方法是無效的。在一個項目里混合多個 React 副本是不好的,原因有很多,但站在歷史角度來看,我們試圖盡可能避免問題。(有了 Hooks,我們[或許得](https://github.com/facebook/react/issues/13991)強制避免重復。)
另一點啟發可以是去檢查原型鏈上的`render`方法。然而,當時還[不確定](https://github.com/facebook/react/issues/4599#issuecomment-129714112)組件的 API 會如何演化。每一次檢查都有成本,所以我們不想再多加了。如果`render`被定義為一個實例方法,例如使用類屬性語法,這個方法也會失效。
因此, React 為基類[增加了](https://github.com/facebook/react/pull/4663)一個特別的標記。React 檢查是否有這個標記,以此知道某樣東西是否是一個 React 組件類。
最初這個標記是在`React.Component`這個基類自己身上:
~~~jsx
// React 內部
class Component {}
Component.isReactClass = {};
// 我們可以像這樣檢查它
class Greeting extends Component {}
console.log(Greeting.isReactClass); // ? 是的
~~~
然而,有些我們希望作為目標的類實現[并沒有](https://github.com/scala-js/scala-js/issues/1900)復制靜態屬性(或設置非標準的`__proto__`),標記也因此丟失。
這也是為什么 React 把這個標記[移動到了](https://github.com/facebook/react/pull/5021)`React.Component.prototype`:
~~~jsx
// React 內部
class Component {}
Component.prototype.isReactComponent = {};
// 我們可以像這樣檢查它
class Greeting extends Component {}
console.log(Greeting.prototype.isReactComponent); // ? 是的
~~~
**說真的這就是全部了。**
你或許奇怪為什么是一個對象而不是一個布爾值。實戰中這并不重要,但早期版本的 Jest(在 Jest 商品化之前)是默認開始自動模擬功能的,生成的模擬數據省略掉了原始類型屬性,[破壞了檢查](https://github.com/facebook/react/pull/4663#issuecomment-136533373)。謝了,Jest。
一直到今天,[React 都在用](https://github.com/facebook/react/blob/769b1f270e1251d9dbdce0fcbd9e92e502d059b8/packages/react-reconciler/src/ReactFiber.js#L297-L300)`isReactComponent`進行檢查。
如果你不擴展`React.Component`,React 不會在原型上找到`isReactComponent`,因此就不會把組件當做類處理。現在你知道為什么解決`Cannot call a class as a function`錯誤的[得票數最高的答案](https://stackoverflow.com/a/42680526/458193)是增加`extends React.Component`。最后,我們還[增加了一項警告](https://github.com/facebook/react/pull/11168),當`prototype.render`存在但`prototype.isReactComponent`不存在時會發出警告。
* * *
你或許會覺得這個故事有一點「標題黨」。**實際的解決方案其實真的很簡單,但我花了大量的篇幅在轉折上來解釋為什么 React 最終選擇了這套方案,以及還有哪些候選方案。**
以我的經驗來看,設計一個庫的 API 也經常會遇到這種情況。為了一個 API 能夠簡單易用,你經常需要考慮語義化(可能的話,為多種語言考慮,包括未來的發展方向)、運行時性能、有或沒有編譯時步驟的工程效能、生態的狀態以及打包方案、早期的警告,以及很多其它問題。最終的結果未必總是最優雅的,但必須要是可用的。
**如果最終的 API 成功的話,*它的用戶*永遠不必思考這一過程**。他們只需要專心創建應用就好了。
但如果你同時也很好奇…知道它是怎么工作的也是極好的。
## 摘自
[ React 如何區分 Class 和 Function?](https://overreacted.io/zh-hans/how-does-react-tell-a-class-from-a-function/)
- 文檔說明
- 大廠面試題
- HTML
- 001.如何遍歷一個dom樹
- 002.為什么操作DOM會很慢
- 003.瀏覽器渲染HTML的步驟
- 004.DOM和JavaScript的關系
- JS
- 001.數組扁平化并去重排序
- 002.高階函數
- 003.sort() 對數組進行排序
- 004.call 、 apply 和bind的區別
- 006.0.1+0.2為什么等于0.30000000000000004
- 011.var、let、const 的區別及實現原理?
- 010.new操作符都做了什么
- 009.a.b.c.d 和 a['b']['c']['d'],哪個性能更高?
- 016.什么是防抖和節流?有什么區別?如何實現?
- 017.['1', '2', '3'].map(parseInt) what & why ?
- 018.為什么 for 循環嵌套順序會影響性能?
- 019.介紹模塊化發展歷程
- 020.push輸出問題
- 021.判斷數組的三個方法
- 022.全局作用域中,用 const 和 let 聲明的變量不在 window 上,那到底在哪里?如何去獲取?
- 023.輸出以下代碼的執行結果并解釋為什么
- 024.ES6 代碼轉成 ES5 代碼的實現思路是什么
- 025.為什么普通 for 循環的性能遠遠高于 forEach 的性能,請解釋其中的原因。
- 026.數組里面有10萬個數據,取第一個元素和第10萬個元素的時間相差多少
- 027.變量類型
- 028.原型和原型鏈
- 029.作用域和閉包
- 030. 異步
- 031.ES6/7 新標準的考查
- 024.事件冒泡/事件代理
- 025.手寫 XMLHttpRequest 不借助任何庫
- 026.什么是深拷貝?
- 0027.克隆數組的方法
- 0028.ES6之展開運算符(...)
- 0029.arguments
- 0030. requestAnimationFrame
- 0031.遞歸爆棧問題與解決
- 021.簡單改造下面的代碼,使之分別打印 10 和 20
- 032.箭頭函數與普通函數
- 033.去除掉html標簽字符串里的所有屬性
- 034.查找公共父節點
- 035.Promise
- 0036.JSON.stringify ()
- CSS
- 001. BFC
- 002.介紹下 BFC、IFC、GFC 和 FFC
- 003.分析比較 opacity: 0、visibility: hidden、display: none 優劣和適用場景
- 004.怎么讓一個 div 水平垂直居中
- 005.重排重繪
- 006.inline/block/inline-block的區別
- 007.選擇器的權重和優先級
- 008.盒模型
- 009.清除浮動
- 010.flex
- 011.nth-child和nth-of-type的區別
- 0012.overflow
- 0013.CSS3中translate、transform和translation的區別和聯系
- 0014.flex
- 0015.px、em、rem
- 0016.width:100%
- 網絡
- 001.講解下HTTPS的工作原理
- 002.介紹下 HTTPS 中間人攻擊
- 003.談談你對TCP三次握手和四次揮手的理解
- 004.A、B 機器正常連接后,B 機器突然重啟,問 A 此時處于 TCP 什么狀態
- 005.簡單講解一下http2的多路復用
- 006. 介紹下 http1.0、1.1、2.0 協議的區別?
- 007.永久性重定向(301)和臨時性重定向(302)對 SEO 有什么影響
- 008.URL從輸入到頁面展示的過程
- 009.接口如何防刷
- 010.http狀態碼?
- 0111.跨域/如何解決?
- 012.cookie 和 localStorage 有何區別?
- 013.Fetch API
- 014.跨域Ajax請求時是否帶Cookie的設置
- 0015.協商緩存和強緩存
- 性能優化
- 001.前后端分離的項目如何seo
- 002.性能優化的方法
- 003.防抖和節流
- React
- 001.React 中 setState 什么時候是同步的,什么時候是異步的?
- 002.Virtual DOM 真的比操作原生 DOM 快嗎?談談你的想法。
- 003.Hooks 的特別之處
- 004.元素和組件有什么區別?
- 005.什么是 Pure Components?
- 006.HTML 和 React 事件處理有什么區別?
- 007.如何將參數傳遞給事件處理程序或回調函數?
- 008.如何創建 refs?
- 009.什么是 forward refs?
- 010.什么是 Virtual DOM?
- 011.什么是受控組件、非受控組件?
- 012.什么是 Fragments ?
- 013.為什么React元素有一個$$typeof屬性?
- 014.如何在 React 中創建組件?
- 015.React 如何區分 Class 和 Function?
- 016.React 的狀態是什么?
- 017.React 中的 props 是什么?
- 018.狀態和屬性有什么區別?
- 019.如何在 JSX 回調中綁定方法或事件處理程序?
- 020.什么是 "key" 屬性,在元素數組中使用它們有什么好處?
- 021.為什么順序調用對 React Hooks 很重要?
- 022.setState如何知道該做什么?
- 023.hook規則?
- 024.Hooks 與 Class 中調用 setState 有不同的表現差異么?
- 025.useEffect
- 026.fiber的作用
- 027.context的作用?
- 028.setState何時同步何時異步?
- 029.react性能優化
- 030.fiber
- 031.React SSR
- 異步
- 001.介紹下promise
- 002.Async/Await 如何通過同步的方式實現異步
- 003.setTimeout、Promise、Async/Await 的區別
- 004.JS 異步解決方案的發展歷程以及優缺點
- 005.Promise 構造函數是同步執行還是異步執行,那么 then 方法呢?
- 006.模擬實現一個 Promise.finally
- 012.簡單手寫實現promise
- 015.用Promise對象實現的 Ajax
- 007.簡單實現async/await中的async函數
- 008.設計并實現 Promise.race()
- 009.Async/await
- 010.珠峰培訓promise
- git
- 001.提交但沒有push
- 002.gitignore沒有作用?
- Node
- 001.用nodejs,將base64轉化成png文件
- Koa
- 001.koa和express的區別
- 數據庫
- redux
- 001.redux 為什么要把 reducer 設計成純函數
- 002.在 React 中如何使用 Redux 的 connect() ?
- 003.mapStateToProps() 和 mapDispatchToProps() 之間有什么區別?
- 004.為什么 Redux 狀態函數稱為 reducers ?
- 005.如何在 Redux 中發起 AJAX 請求?
- 006.訪問 Redux Store 的正確方法是什么?
- 007.React Redux 中展示組件和容器組件之間的區別是什么?
- 008.Redux 中常量的用途是什么?
- 009.什么是 redux-saga?
- 設計模式
- 公司題目
- 001.餓了么
- 001.div垂直水平居中(flex、絕對定位)
- 002.React子父組件之間如何傳值
- 003.Emit事件怎么發,需要引入什么
- 004.介紹下React高階組件,和普通組件有什么區別
- 005.一個對象數組,每個子對象包含一個id和name,React如何渲染出全部的name
- 006.在哪個生命周期里寫
- 007.其中有幾個name不存在,通過異步接口獲取,如何做
- 008.渲染的時候key給什么值,可以使用index嗎,用id好還是index好
- 009.webpack如何配sass,需要配哪些loader
- 010.配css需要哪些loader
- 011.如何配置把js、css、html單獨打包成一個文件
- 012.監聽input的哪個事件,在什么時候觸發
- 013.兩個元素塊,一左一右,中間相距10像素
- 014.上下固定,中間滾動布局如何實現
- 016.取數組的最大值(ES5、ES6)
- 017.apply和call的區別
- 018.ES5和ES6有什么區別
- 019.some、every、find、filter、map、forEach有什么區別
- 020.上述數組隨機取數,每次返回的值都不一樣
- 021.如何找0-5的隨機數,95-99呢
- 022.頁面上有1萬個button如何綁定事件
- 023.如何判斷是button
- 024.頁面上生成一萬個button,并且綁定事件,如何做(JS原生操作DOM)
- 025.循環綁定時的index是多少,為什么,怎么解決
- 026.頁面上有一個input,還有一個p標簽,改變input后p標簽就跟著變化,如何處理
- 瀏覽器相關
- 001.性能優化
- 002.web安全
- 003.獲取瀏覽器大小
- 004.從輸入 URL 到頁面加載完成的過程中都發生了什么事情?
- 后端
- 001.分布式
- zuku
- 字節
- webpack
- webpack的打包原理是什么
- Webpack-- 常見面試題
- webscoket