作为前端人员要回答这个问题,需要了解这三个知识点:
* 同步
* 异步
* Async/Await
首先,**js 是单线程的(重复三遍)**,所谓单线程, 通俗的讲就是,一根筋,执行代码是一行一行的往下走(即所谓的**同步**), 如果上面的没执行完,就痴痴的等着(是不是很像恋爱中在路边等她/他的你,假装 new 了个对象,啊哈哈哈,调皮一下很开心), 还是举个 🌰 吧:
~~~javascript
// chrome 75
function test() {
let d = Date.now();
for (let i = 0; i < 1e8; i++) {}
console.log(Date.now() - d); // 62ms左右
}
function test1() {
let d = Date.now();
console.log(Date.now() - d); // 0
}
test();
test1();
~~~
上面仅仅是一个 for 循环,而在实际应用中,会有大量的网络请求,它的响应时间是不确定的,这种情况下也要痴痴的等么?显然是不行的,因而 js 设计了异步,即 发起网络请求(诸如 IO 操作,定时器),由于需要等服务器响应,就先不理会,而是去做其他的事儿,等请求返回了结果的时候再说(即**异步**)。 那么如何实现异步呢?其实我们平时已经在大量使用了,那就是`callback`,例如:
~~~javascript
$.ajax({
url: 'http://xxx',
success: function(res) {
console.log(res);
},
});
~~~
success 作为函数传递过去并不会立即执行,而是等请求成功了才执行,即**回调函数**(callback)
~~~javascript
const fs = require('fs');
fs.rename('旧文件.txt', '新文件.txt', err => {
if (err) throw err;
console.log('重命名完成');
});
~~~
和网络请求类似,等到 IO 操作有了结果(无论成功与否)才会执行第三个参数:`(err)=>{}`
从上面我们就可以看出,**实现异步的核心就是回调钩子**,将 cb 作为参数传递给异步执行函数,当有了结果后在触发 cb。想了解更多,去看看`event-loop`机制吧。
至于 async/await 是如何出现的呢,在 es6 之前,大多 js 数项目中会有类似这样的代码:
~~~javascript
ajax1(url, () => {
ajax2(url, () => {
ajax3(url, () => {
// do something
});
});
});
~~~
这种函数嵌套,大量的回调函数,使代码阅读起来晦涩难懂,不直观,形象的称之为**回调地狱(callback hell)**,所以为了在写法上能更通俗一点,es6+陆续出现了`Promise`、`Generator`、`Async/await`,力求在写法上简洁明了,可读性强。
\=========================我是分割线==========================
以上只是铺垫,下面在进入正题 👇,开始说道说道主角:`async/await`
\=========================我是分割线==========================
`async/await`是参照`Generator`封装的一套异步处理方案,可以理解为`Generator`的语法糖,
所以了解`async/await`就不得不讲一讲`Generator`,
而`Generator`又依赖于迭代器`Iterator`,
所以就得先讲一讲`Iterator`,
而`Iterator`的思想呢又来源于单向链表,
终于找到源头了:**单向链表**
## 1. 单向链表
> wiki:链表(Linked list)是一种常见的基础数据结构,是一种[线性表](https://zh.wikipedia.org/wiki/%E7%BA%BF%E6%80%A7%E8%A1%A8),但是并不会按线性的顺序储存数据,而是在每一个节点里存到下一个节点的[指针](https://zh.wikipedia.org/wiki/%E6%8C%87%E6%A8%99_(%E9%9B%BB%E8%85%A6%E7%A7%91%E5%AD%B8))(Pointer)。由于不必须按顺序储存,链表在插入的时候可以达到 o(1)的复杂度,比另一种线性表[顺序表](https://zh.wikipedia.org/wiki/%E9%A1%BA%E5%BA%8F%E8%A1%A8)快得多,但是查找一个节点或者访问特定编号的节点则需要 o(n)的时间,而顺序表响应的时间复杂度分别是 o(logn)和 o(1)。
总结一下链表优点:
* 无需预先分配内存
* 插入/删除节点不影响其他节点,效率高(典型的例子:dom 操作)
单向链表:是链表中最简单的一种,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。
![](https://img.kancloud.cn/20/cf/20cf2a60be27c7ccc5750023171e35fa_825x418.png)
一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接
单链特点:节点的链接方向是单向的;相对于数组来说,单链表的的随机访问速度较慢,但是单链表删除/添加数据的效率很高。
理解 js 原型链/作用域链的话,理解这个很容易,他们是相通的。编程语言中,数组的长度是固定的,所以数组中的增加和删除比较麻烦,需要频繁的移动数组中的其他元素,而 js 作为一门动态语言,数组本质是一个类似数组的对象,是动态的,不需要预先分配内存~
那么如何设计一个单向链表呢?这个取决于我们需要哪些操作,通常有:
* append(element):追加节点
* insert(element,index):在索引位置插入节点
* remove(element):删除第一个匹配到的节点
* removeAt(index):删除指定索引节点
* removeAll(element):删除所有匹配的节点
* get(index):获取指定索引的节点信息
* set(element,index):修改指定索引的节点值
* indexOf(element):获取某节点的索引位置
* clear():清除所有节点
* length():返回节点长度
* printf():打印节点信息
看到这些方法是不是有些许熟悉,当你用原生 js 或 jq 时常会用上面类似的方法,现在根据上面列出的方法进行实现一个单向链:
~~~javascript
// 节点模型
class LinkNode {
constructor(element, next) {
this.element = element;
this.next = next;
}
}
class LinkedList {
constructor() {
this._head = null;
this._size = 0;
this._errorBoundary = this._errorBoundary.bind(this);
this._getNodeByIndex = this._getNodeByIndex.bind(this);
this.append = this.append.bind(this);
this.insert = this.insert.bind(this);
this.remove = this.remove.bind(this);
this.removeAt = this.removeAt.bind(this);
this.removeAll = this.removeAll.bind(this);
this.getElement = this.getElement.bind(this);
this.setIndex = this.setIndex.bind(this);
this.indexOf = this.indexOf.bind(this);
this.clear = this.clear.bind(this);
this.length = this.length.bind(this);
this.printf = this.printf.bind(this);
}
// 边界检验
_errorBoundary(index) {
if (index < 0 || index >= this._size) {
throw `超出边界(${0}~${this._size}),目标位置${index}不存在!`;
}
}
// 根据索引获取目标对象
_getNodeByIndex(index) {
this._errorBoundary(index);
let obj = this._head;
for (let i = 0; i < index; i++) {
obj = obj.next;
}
return obj;
}
// 追加节点
append(element) {
if (this._size === 0) {
this._head = new LinkNode(element, null);
} else {
let obj = this._getNodeByIndex(this._size - 1);
obj.next = new LinkNode(element, null);
}
this._size++;
}
// 在索引位置插入节点
insert(element, index) {
if (index === 0) {
this._head = new LinkNode(element, this._head);
} else {
let obj = this._getNodeByIndex(index - 1);
obj.next = new LinkNode(element, obj.next);
}
this._size++;
}
// 删除第一个匹配到的节点
remove(element) {
if (this._size < 1) return null;
if (this._head.element == element) {
this._head.element = this._head.next;
this._size--;
return element;
} else {
let temp = this._head;
while (temp.next) {
if (temp.next.element == element) {
temp.next = temp.next.next;
this._size--;
return element;
} else {
temp = temp.next;
}
}
}
return null;
}
// 删除指定索引节点
removeAt(index) {
this._errorBoundary(index);
let element = null;
if (index === 0) {
element = this._head.element;
this._head = this._head.next;
} else {
let prev = this._getNodeByIndex(index - 1);
element = prev.next.element;
prev.next = prev.next.next;
}
this._size--;
return element;
}
// 删除所有匹配的节点
removeAll(element) {
// 创建虚拟头节点,
let v_head = new LinkNode(null, this._head);
let tempNode = v_head;
// let tempEle = null;
while (tempNode.next) {
if (tempNode.next.element == element) {
tempNode.next = tempNode.next.next;
this._size--;
// tempEle = element;
} else {
tempNode = tempNode.next;
}
}
this._head = v_head.next;
}
// 获取指定索引的节点信息
getElement(index) {
return this._getNodeByIndex(index).element;
}
// 修改指定索引的节点值
setIndex(element, index) {
this._errorBoundary(index);
let obj = this._getNodeByIndex(index);
obj.element = element;
}
// 获取某节点的索引位置
indexOf(element) {
let obj = this._head;
let index = -1;
for (let i = 0; i < this._size; i++) {
if (obj.element == element) {
index = i;
break;
}
obj = obj.next;
}
return index;
}
// 清除所有节点
clear() {
this._head = null;
this._size = 0;
}
// 返回节点长度
length() {
return this._size;
}
// 打印节点信息
printf() {
let obj = this._head;
const arr = [];
while (obj != null) {
arr.push(obj.element);
obj = obj.next;
}
const str = arr.join('->');
return str || null;
}
}
const obj = new LinkedList();
obj.append(0);
obj.append(1);
obj.append(2);
obj.printf();
// "0->1->2"
obj.insert(3, 3);
obj.printf();
// "0->1->2->3"
obj.remove(3);
obj.printf();
// "0->1->2"
obj.removeAt(0);
obj.printf();
// "1->2"
obj.setIndex(0, 0);
obj.printf();
// "0->2"
obj.indexOf(2);
// 1
obj.length();
// 2
obj.clear();
obj.printf();
// null
~~~
[查看源码](https://github.com/Mr-jiangzhiguo/book/code/linked.js)
通过以上,我假装你明白什么是单向链表,并且能够用代码实现一个单向链表了,下一步开始说一说**迭代器**`Iterator`
## 2. Iterator
`Iterator`翻译过来就是 **迭代器(遍历器)** 让我们先来看看它的遍历过程(类似于单向链表):
* 创建一个**指针对象**,指向当前数据结构的起始位置
* 第一次调用指针对象的`next`方法,将指针指向数据结构的第一个成员
* 第二次调用指针对象的`next`方法,将指针指向数据结构的第二个成员
* 不断的调用指针对象的`next`方法,直到它指向数据结构的结束位置
一个对象要变成可迭代的,必须实现`@@iterator`方法,即对象(或它原型链上的某个对象)必须有一个名字是`Symbol.iterator`的属性(原生具有该属性的有:字符串、数组、类数组的对象、Set 和 Map):
| 属性 | 值 |
| --- | --- |
| [Symbol.iterator]: | 返回一个对象的无参函数,被返回对象符合迭代器协议 |
当一个对象需要被迭代的时候(比如开始用于一个`for..of`循环中),它的`@@iterator`方法被调用并且无参数,然后返回一个用于在迭代中获得值的迭代器
迭代器协议:产生一个有限或无限序列的值,并且当所有的值都已经被迭代后,就会有一个默认的返回值
当一个对象只有满足下述条件才会被认为是一个迭代器:
它实现了一个`next()`的方法,该方法**必须返回一个对象**,对象有两个必要的属性:
* `done`(bool)
* true:迭代器已经超过了可迭代次数。这种情况下,value 的值可以被省略
* 如果迭代器可以产生序列中的下一个值,则为 false。这等效于没有指定 done 这个属性
* `value`迭代器返回的任何 JavaScript 值。done 为 true 时可省略
根据上面的规则,咱们来自定义一个简单的迭代器:
~~~javascript
const makeIterator = arr => {
let nextIndex = 0;
return {
next: () =>
nextIndex < arr.length
? { value: arr[nextIndex++], done: false }
: { value: undefined, done: true },
};
};
const it = makeIterator(['人月', '神话']);
console.log(it.next()); // { value: "人月", done: false }
console.log(it.next()); // { value: "神话", done: false }
console.log(it.next()); // {value: undefined, done: true }
~~~
我们还可以自定义一个可迭代对象:
~~~javascript
const myIterable = {};
myIterable[Symbol.iterator] = function*() {
yield 1;
yield 2;
yield 3;
};
for (let value of myIterable) {
console.log(value);
}
// 1
// 2
// 3
//or
console.log([...myIterable]); // [1, 2, 3]
~~~
了解了迭代器,下面可以进一步了解生成器了
## 3. Generator
`Generator`:生成器对象是生成器函数(GeneratorFunction)返回的,它符合**可迭代协议**和**迭代器协议**,既是迭代器也是可迭代对象,可以调用`next`方法,但它不是函数,更不是构造函数
生成器函数(GeneratorFunction):
> function\* name([param[, param[, ... param]]]) { statements }
>
> * name:函数名
> * param:参数
> * statements:js 语句
调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的迭代器对象,当这个迭代器的`next()`方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现`yield`的位置为止(让执行处于**暂停状**),`yield`后紧跟迭代器要返回的值。或者如果用的是`yield*`(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器**暂停执行**),调用`next()`(再启动)方法时,如果传入了参数,那么这个参数会作为**上一条执行的`yield`语句的返回值**,例如:
~~~javascript
function* another() {
yield '人月神话';
}
function* gen() {
yield* another(); // 移交执行权
const a = yield 'hello';
const b = yield a; // a='world' 是 next('world') 传参赋值给了上一个 yidle 'hello' 的左值
yield b; // b=! 是 next('!') 传参赋值给了上一个 yidle a 的左值
}
const g = gen();
g.next(); // {value: "人月神话", done: false}
g.next(); // {value: "hello", done: false}
g.next('world'); // {value: "world", done: false} 将 'world' 赋给上一条 yield 'hello' 的左值,即执行 a='world',
g.next('!'); // {value: "!", done: false} 将 '!' 赋给上一条 yield a 的左值,即执行 b='!',返回 b
g.next(); // {value: undefined, done: false}
~~~
看到这里,你可能会问,`Generator`和`callback`有啥关系,如何处理异步呢?其实二者没有任何关系,我们只是通过一些方式强行的它们产生了关系,才会有`Generator`处理异步
我们来总结一下`Generator`的本质,暂停,它会让程序执行到指定位置先暂停(`yield`),然后再启动(`next`),再暂停(`yield`),再启动(`next`),而这个暂停就很容易让它和异步操作产生联系,因为我们在处理异步时:开始异步处理(网络求情、IO 操作),然后暂停一下,等处理完了,再该干嘛干嘛。不过值得注意的是,**js 是单线程的(又重复了三遍)**,异步还是异步,callback 还是 callback,不会因为`Generator`而有任何改变
下面来看看,用`Generator`实现异步:
~~~javascript
const promisify = require('util').promisify;
const path = require('path');
const fs = require('fs');
const readFile = promisify(fs.readFile);
const gen = function*() {
const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
console.log(res1);
const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
console.log(res2);
};
const g = gen();
const g1 = g.next();
console.log('g1:', g1);
g1.value
.then(res1 => {
console.log('res1:', res1);
const g2 = g.next(res1);
console.log('g2:', g2);
g2.value
.then(res2 => {
console.log('res2:', res2);
g.next(res2);
})
.catch(err2 => {
console.log(err2);
});
})
.catch(err1 => {
console.log(err1);
});
// g1: { value: Promise { <pending> }, done: false }
// res1: {
// "a": 1
// }
// {
// "a": 1
// }
// g2: { value: Promise { <pending> }, done: false }
// res2: {
// "b": 2
// }
// {
// "b": 2
// }
~~~
以上代码是`Generator`和`callback`结合实现的异步,可以看到,仍然需要手动执行`.then`层层添加回调,但由于`next()`方法返回对象`{value: xxx,done: true/false}`所以我们可以简化它,写一个自动执行器:
~~~javascript
const promisify = require('util').promisify;
const path = require('path');
const fs = require('fs');
const readFile = promisify(fs.readFile);
function run(gen) {
const g = gen();
function next(data) {
const res = g.next(data);
// 深度递归,只要 `Generator` 函数还没执行到最后一步,`next` 函数就调用自身
if (res.done) return res.value;
res.value.then(function(data) {
next(data);
});
}
next();
}
run(function*() {
const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
console.log(res1);
// {
// "a": 1
// }
const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
console.log(res2);
// {
// "b": 2
// }
});
~~~
说了这么多,怎么还没有到`async/await`,客官别急,马上来了(其实我已经漏了一些内容没说:Promise 和 callback 的关系,thunk 函数,co 库,感兴趣的可以去 google 一下,yuanyifeng 老师讲的[es6 入门](http://es6.ruanyifeng.com/)非常棒,我时不时的都会去看一看)
## 4\. Async/Await
首先,`async/await`是`Generator`的语法糖,上面*我是分割线*下的第一句已经讲过,先来看一下二者的对比:
~~~javascript
// Generator
run(function*() {
const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
console.log(res1);
const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
console.log(res2);
});
// async/await
const readFile = async ()=>{
const res1 = await readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
console.log(res1);
const res2 = await readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
console.log(res2);
return 'done';
}
const res = readFile();
~~~
可以看到,`async function`代替了`function*`,`await`代替了`yield`,同时也无需自己手写一个自动执行器`run`了
现在再来看看`async/await`的特点:
* 当`await`后面跟的是 Promise 对象时,才会异步执行,其它类型的数据会同步执行
* 执行`const res = readFile();`返回的仍然是个 Promise 对象,上面代码中的`return 'done';`会直接被下面`then`函数接收到
~~~javascript
res.then(data => {
console.log(data); // done
});
~~~
## 摘自
[第 9 题:Async/Await 如何通过同步的方式实现异步](https://www.muyiy.cn/question/async/9.html)
- 文档说明
- 大厂面试题
- 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