让我们来看一下这个以函数形式定义的`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