文章大部份内容是作一些知识点的总结,不会面面俱到,对于一些具体的实现步骤和底层原理的代码并不会贴出来,否则篇幅实在是太长啦css
不过没必要担忧,相关知识点的详细讲解会贴出文章连接供你们参考,这些都是博主日常写的笔记和看过的一些优秀的博文,但愿可以帮助你查漏补缺,梳理起你的前端知识体系~html
文章内容较多,建议先 mark 再看哟。前端
<meta charset="UTF-8" >
复制代码
<meta http-equiv="expires" content="Wed, 20 Jun 2019 22:33:00 GMT">
复制代码
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
复制代码
标准模型:宽高计算不包含 padding 和 border ;经过 box-sizing: content-box; 来设置(浏览器默认)。vue
IE模型:宽高计算包含 padding 和 border ;经过 box-sizing: border-box; 来设置。html5
特色:node
建立方式:react
做用:webpack
div.parent {
position: relative;
}
div.child {
width: 100px;
height: 100px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -50px;
margin-top: -50px;
}
或
div.child {
width: 100px;
height: 100px;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
}
复制代码
div.parent {
display: flex;
justify-content: center;
align-items: center;
}
或
div.parent{
display:flex;
}
div.child{
margin:auto;
}
或
div.parent {
position: relative;
}
div.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
或
div.parent {
display: grid;
}
div.child {
justify-self: center;
align-self: center;
}
复制代码
更多布局类型可参考:干货!各类常见布局实现+知名网站实例分析git
更多内容可参考:7 种方法解决移动端 Retina 屏幕 1px 边框问题es6
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
复制代码
overflow: hidden;
text-overflow: ellipsis; // 超出显示'...'
display: -webkit-box; // 将元素做为弹性伸缩盒子模型显示 。
-webkit-line-clamp: 2; // 用来限制在一个块元素显示的文本的行数
-webkit-box-orient: vertical; // 设置或检索伸缩盒对象的子元素的排列方式
复制代码
一、
.clearfix:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
二、clear:both
三、overflow:hidden
复制代码
.sector {
width: 0;
height: 0;
border-width: 50px;
border-style: solid;
border-color: red transparent transparent;
border-radius: 50px;
}
复制代码
tips: NaN 也属于 number 类型,而且 NaN 不等于自身。
console.log(typeof 1); // number
console.log(typeof 'a'); // string
console.log(typeof true); // boolean
console.log(typeof undefined); // undefined
console.log(typeof function fn(){}); // function
console.log(typeof {}); // object
console.log(typeof null); // object
console.log(typeof []); // object
console.log(typeof new Error()); // object
复制代码
tips:typeof 对于基本类型,除了 null 均可以显示正确的类型;对于对象,除了函数都会显示 object
var number = 1; // [object Number]
var string = '123'; // [object String]
var boolean = true; // [object Boolean]
var und = undefined; // [object Undefined]
var nul = null; // [object Null]
var obj = {a: 1} // [object Object]
var array = [1, 2, 3]; // [object Array]
var date = new Date(); // [object Date]
var error = new Error(); // [object Error]
var reg = /a/g; // [object RegExp]
var func = function a(){}; // [object Function]
function checkType() {
for (var i = 0; i < arguments.length; i++) {
console.log(Object.prototype.toString.call(arguments[i]))
}
}
checkType(number, string, boolean, und, nul, obj, array, date, error, reg, func)
复制代码
更多内容可参考:JavaScript温故而知新——类型判断
更多内容可参考:JavaScript温故而知新——原型和原型链
更多内容可参考:JavaScript温故而知新——执行环境和做用域
经典面试题:改造下面的代码,使之输出0 - 9
for (var i = 0; i< 10; i++){
setTimeout(() => {
console.log(i);
}, 1000)
}
方法1、利用 setTimeout 函数的第三个参数,会做为回调函数的第一个参数传入
for (var i = 0; i < 10; i++) {
setTimeout(i => {
console.log(i);
}, 1000, i)
}
方法2、使用 let 变量 的特性
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i);
}, 1000)
}
等价于
for (let i = 0; i < 10; i++) {
let _i = i;// const _i = i;
setTimeout(() => {
console.log(_i);
}, 1000)
}
方法3、利用函数自执行的方式,把当前 for 循环过程当中的 i 传递进去,构建出块级做用域。
for (var i = 0; i < 10; i++) {
(i => {
setTimeout(() => {
console.log(i);
}, 1000)
})(i)
}
复制代码
this 的指向取决于函数以哪一种方式调用:
具体可参考:JavaScript温故而知新——函数的4种调用方式
function a() {
return () => {
return () => {
console.log(this)
}
}
}
console.log(a()()())
复制代码
箭头函数实际上是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,由于调用 a 符合前面代码中的第一个状况,因此 this 是 window。而且 this 一旦绑定了上下文,就不会被任何代码改变。
Function.prototype.call2 = function(context) {
var context = context || window;
context.fn = this;
var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
var result = eval('context.fn(' + args + ')');
delete context.fn
return result;
}
Function.prototype.apply = function (context, arr) {
var context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
}
else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result;
}
复制代码
实现细节可参考:JavaScript温故而知新——call()和apply()的实现
Function.prototype.bind2 = function (context) {
if (typeof this !== "function") {
throw new TypeError("error");
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
// 经过一个空函数做一个中转,避免绑定函数的 prototype 的属性被修改
var fNOP = function () {};
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
}
复制代码
实现细节可参考:JavaScript温故而知新——bind()方法的实现
new 操做符作了什么?
function objectFactory() {
var obj = new Object(),
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
return typeof ret === 'object' ? ret : obj;
};
复制代码
实现细节可参考:JavaScript温故而知新——new操做符的实现
function instance_of(L, R) {
//L 表示左表达式,R 表示右表达式
var O = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {
if (L === null) return false;
if (O === L)
// 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
复制代码
// 数组浅拷贝:slice()、concat()
// 对象浅拷贝:Object.assign()、ES6的扩展运算符
复制代码
// 递归实现
function clone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
if (typeof source[i] === 'object') {
target[i] = clone(source[i]); // 若是是引用类型,则继续遍历
} else {
target[i] = source[i];
}
}
}
return target;
}
复制代码
固然这只是简单的实现,没有考虑到特殊的状况,如对象或数组中的函数,正则等特殊类型的拷贝等。
// JSON.parse(JSON.stringify)
var arr = [
{ value: 1 },
{ value: 2 },
{ value: 3 }
];
var copyArr = JSON.parse(JSON.stringify(arr))
copyArr[0].value = 0;
console.log(arr); // [{value: 1}, { value: 2 }, { value: 3 }]
console.log(copyArr); // [{value: 0}, { value: 2 }, { value: 3 }]
复制代码
上面这种方法简单粗暴,缺点是不能拷贝函数。
了解深拷贝更多实现细节,能够参考:深拷贝的终极探索(90%的人都不知道)
funtion debounce(fn) {
// 建立一个标记用来存放定时器的返回值
let timeout = null;
return function() {
// 每次触发事件时都取消以前的延时调用方法
clearTimeout(timeout);
// 而后又建立一个新的 setTimeout, 这样就能保证 1000ms 间隔内若是重复触发就不会执行 fn 函数
timeout = setTimeout(() => {
fn.apply(this, arguments);
}, 1000);
};
}
复制代码
function throttle(fn) {
// 经过闭包保存一个标记
let canRun = true;
return function(){
// 每次开始执行函数时都先判断标记是否为 true,不为 true 则 return
if (!canRun) return;
// 上一次定时器执行完后 canRun 为 true,因此要先设置为false
canRun = false;
setTimeout(() => {
fn.apply(this, arguments);
// 最后在 setTimeout 执行完毕后再把标记设置为true(关键)表示能够执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被 return 掉
canRun = true;
}, 1000)
}
}
复制代码
// 组合继承
function SuperType(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
}
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
console.log(this.age);
}
复制代码
ajax('XXX1', () => {
// callback 函数体
ajax('XXX2', () => {
// callback 函数体
ajax('XXX3', () => {
// callback 函数体
})
})
})
复制代码
优势:解决了同步的问题
缺点:回调地狱,不能用 try catch 捕获错误,不能 return
ajax('XXX1')
.then(res => {
// 操做逻辑
return ajax('XXX2')
}).then(res => {
// 操做逻辑
return ajax('XXX3')
}).then(res => {
// 操做逻辑
})
复制代码
优势:解决了回调地狱的问题
缺点:没法取消 Promise ,错误须要经过回调函数来捕获
function *fetch() {
yield ajax('XXX1', () => {})
yield ajax('XXX2', () => {})
yield ajax('XXX3', () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
// 配合 co 库使用
const co = require('co')
function *fetch() {
yield ajax('XXX1', () => {})
yield ajax('XXX2', () => {})
yield ajax('XXX3', () => {})
}
co(fetch()).then(data => {
//code
}).fetch(err => {
//code
})
复制代码
优势:能够控制函数的执行,配合自动执行器 co 模块 简化了手动执行的步骤
缺点:不配合 co 函数库的话使用起来比较麻烦
// async实际上是一个语法糖,它的实现就是将 Generator 函数和自动执行器(co),包装在一个函数中
async function test() {
// 如下代码没有依赖性的话,彻底可使用 Promise.all 的方式
// 若是有依赖性的话,其实就是解决回调地狱的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
read().then((data) => {
//code
}).catch(err => {
//code
});
复制代码
优势:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
缺点:await 将异步代码改形成同步代码,若是多个异步操做没有依赖性而使用 await 会致使性能上的下降。
对于这个问题首先要弄明白 JS 的事件循环(Event Loop)机制,推荐你们先看一下这篇文章:这一次,完全弄懂 JavaScript 执行机制
console.log('script start') //1. 打印 script start
setTimeout(function() {
console.log('settimeout') // 4. 打印 settimeout
}) // 2. 调用 setTimeout 函数,并定义其完成后执行的回调函数
console.log('script end') //3. 打印 script start
// 输出顺序:script start->script end->settimeout
复制代码
console.log('script start')
let promise1 = new Promise(function (resolve) {
console.log('promise1')
resolve()
console.log('promise1 end')
}).then(function () {
console.log('promise2')
})
setTimeout(function() {
console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout
复制代码
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start');
async1();
console.log('script end')
// 输出顺序:script start->async1 start->async2->script end->async1 end
复制代码
promise 的使用(有关 Promise 的详细用法,可参考 阮一峰老师的ES6文档)
var promise = new Promise((resolve,reject) => {
if (操做成功) {
resolve(value)
} else {
reject(error)
}
})
promise.then(function (value) {
// success
},function (value) {
// failure
})
复制代码
简单实现
function myPromise(constructor) {
let self = this;
self.status = "pending" // 定义状态改变前的初始状态
self.value = undefined; // 定义状态为resolved的时候的状态
self.reason = undefined; // 定义状态为rejected的时候的状态
function resolve(value) {
if(self.status === "pending") {
self.value = value;
self.status = "resolved";
}
}
function reject(reason) {
if(self.status === "pending") {
self.reason = reason;
self.status = "rejected";
}
}
// 捕获构造异常
try {
constructor(resolve,reject);
} catch(e) {
reject(e);
}
}
复制代码
添加 then 方法
myPromise.prototype.then = function(onFullfilled,onRejected) {
let self = this;
switch(self.status) {
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
}
}
var p = new myPromise(function(resolve,reject) {
resolve(1)
});
p.then(function(x) {
console.log(x) // 1
})
复制代码
有关 Promise 原理的更多细节推荐 Promise实现原理(附源码),这篇文章讲的很仔细,比较好理解。
(function(){
return {
data:[]
}
})()
复制代码
define('./index.js',function(code){
// code 就是index.js 返回的内容
})
复制代码
define(function(require, exports, module) {
var indexCode = require('./index.js');
});
复制代码
var fs = require('fs');
复制代码
import a from 'a';
复制代码
1xx:指示信息 —— 表示请求已接受,继续处理
2xx: 成功 —— 表示请求已被成功接受
3xx:重定向 —— 要完成请求必须进行更进一步操做
4xx:客户端错误——请求有语法错误或请求没法实现
5xx:服务端错误——服务器未能实现合法的请求
三次握手和四次挥手能够模拟成对讲机通话的过程
A: 你好,我是A
B:收到,我是B
A:好的,咱们能够开始通话啦
复制代码
A:我已经没什么话说了,结束通话吧
B:稍等,我还有最后一句话要说
B:我已经说完啦
A:好的,你能够关掉对讲机了,不用回复了(而后A等待2MSL无回复,也关掉对讲机)
复制代码
更详细的内容可参考:面试 -- 网络 TCP/IP
HTTPS 仍是经过了 HTTP 来传输信息,可是信息经过 TLS 协议进行了加密。
TLS 中的加密:
HTTPS 握手过程:
捕获阶段 —— window 往事件触发处传播,遇到注册的捕获事件会触发
目标阶段 —— 传播到事件触发处时触发注册的事件
冒泡阶段 —— 从事件触发处往 window 传播,遇到注册的冒泡事件会触发
复制代码
window对象 => document对象 => html标签 => body标签 => ... => 目标元素(冒泡反之)
复制代码
event.preventDefault() // 阻止默认事件,例如a标签的跳转行为
event.stopPropagation() // 阻止冒泡
event.stopImmediatePropagation() // 事件响应优先级:例如同一元素绑定不一样事件时,触发a事件不让b事件触发
event.currentTarget // 当前绑定事件的元素
event.target // 当前被点击的元素
复制代码
同源策略:
跨域的几种解决方法:
<script>
标签没有跨域限制的特色,经过 <script>
标签指向一个须要访问的地址并提供一个回调函数来接收数据。<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
<script>
function jsonp(data) {
console.log(data)
}
</script>
复制代码
封装一个 jsonp 方法
function jsonp({url, params, cb}) {
return new Promise((resolve, reject) => {
//建立script标签
let script = document.createElement('script');
//将回调函数挂在 window 上
window[cb] = function(data) {
resolve(data);
//代码执行后,删除插入的script标签
document.body.removeChild(script);
}
//回调函数加在请求地址上
params = {...params, cb}
let arrs = [];
for(let key in params) {
arrs.push(`${key}=${params[key]}`);
}
script.src = `${url}?${arrs.join('&')}`;
document.body.appendChild(script);
});
}
//使用
function sayHi(data) {
console.log(data);
}
jsonp({
url: 'http://localhost:3000/say',
params: {
//code
},
cb: 'sayHi'
}).then(data => {
console.log(data);
});
复制代码
Access-Control-Allow-Origin
就能够开启 CORS。 该属性表示哪些域名能够访问资源,若是设置通配符则表示全部网站均可以访问资源。a.test.com
和b.test.com
,只须要给页面添加 document.domain = 'test.com'
表示二级域名都相同就能够实现跨域。# 在A中发送数据
window.postMessage('data', 'http://B.com');
# 在窗口B中监听
window.addEventListener('message', function(event){
console.log(event.origin);
console.log(event.source);
console.log(event.data);
}, false)
复制代码
# 使用场景:当页面A经过iframe或frame嵌入了跨域的页面B
# 在A中的代码:
var B = document.getElementByTagName('iframe');
B.src = B.src + '#' + 'data';
# 在B中的代码:
window.onhashchange = function () {
var data = window.location.hash;
}
复制代码
requestAnimationFrame
重绘和回流更详细的内容可参考:你真的了解回流和重绘吗
缓存的原理 —— 将请求来的资源存放到本地磁盘当中,下次获取资源则直接在磁盘当中读取而再也不去向服务器发送请求。
缓存的分类:
Expires
和 Cache-Control
。强缓存表示在缓存期间不须要请求,state code 为 200Expires: Wed, 22 Oct 2018 08:41:00 GMT
// Expires 是 HTTP / 1.0 的产物,表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过时,须要再次请求。
// 而且 Expires 受限于本地时间,若是修改了本地时间,可能会形成缓存失效。
Cache-control: max-age=30
// Cache-Control 出现于 HTTP / 1.1,优先级高于 Expires 。该属性表示资源会在 30 秒后过时,须要再次请求。
复制代码
Last-Modified
和 If-Modified-Since
—— 表示本地文件最后修改日期,If-Modified-Since
会将 Last-Modified
的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来。(可是若是在本地打开缓存文件,就会形成 Last-Modified
被修改,因此在 HTTP / 1.1 出现了 ETag
。)ETag
和 If-None-Match
—— ETag
相似于文件指纹,If-None-Match
会将当前 ETag
发送给服务器,询问该资源 ETag
是否变更,有变更的话就将新的资源发送回来。而且 ETag
优先级比 Last-Modified
高了解更多有关浏览器缓存机制可参考:深刻理解浏览器的缓存机制。
XSS
CSRF
关于 Web 安全更多内容可参考:【面试篇】寒冬求职之你必需要懂的Web安全
<link rel="dns-prefetch" href="//host_name_to_prefetch.com" />
复制代码
<link rel="preload" href="http://example.com" />
复制代码
<link rel="prerender" href="http://example.com" />
复制代码
图片优化:
其余文件优化:
head
中使用 Webpack 优化项目:
错误监控:
performance.getEntries()
能够获取已经加载完成的资源进而监控加载失败的资源错误上报:
顾名思义,即 Model-View-ViewModel 模式
优势:
缺点:
Vue 采用数据劫持结合发布—订阅模式的方法,经过 Object.defineProperty() 来劫持各个属性的 setter,getter,在数据变更时发布消息给订阅者,触发相应的监听回调。
模拟实现可参考 面试题:你能写一个Vue的双向数据绑定吗?,这里就不贴代码了。
在遍历子节点的时候还有一个列表对比的算法,是针对带有 key
而且新旧列表同时存在的节点作处理的。当子节点仅仅只是发生位置改变的状况,若是按照同层对比,它们就会被替换掉,形成较大的 DOM 开销,而列表对比算法会经过对比新旧节点列表的顺序来移动节点进行 DOM 的更新,在一些场合下就能够起到必定的性能优化的做用。
对于 Virtual Dom 详细的代码实现可参考:深度剖析:如何实现一个 Virtual DOM 算法
本质:监听 URL 的变化,而后匹配路由规则,而且无需刷新页面。
实现方式:
详细内容可参考:面试官: 你了解前端路由吗?
Proxy的优点:
Object.defineProperty的优点以下:
指建立初始化数据、编译模板、挂载 DOM、渲染、更新、卸载一系列的过程。
props
、 $emit
或本组件值的变化来执行回调进行后续操做,无缓存性,页面从新渲染时值不变化也会执行。推荐使用惟一标识做为 key 的缘由:
nextTick
可让咱们在下次 DOM 更新循环结束以后执行延迟回调,用于得到更新后的 DOM。
props
/ $emit
$children
/ $parent
—— 父组件中用 this.$children
获取到子组件实例数组,子组件中可使用 this.$parent
获取父组件实例对象。
#app
上获取 $parent
获得的是 new Vue()
的实例,在这之上再获取 $parent
则是 undefined
;而底层子组件获取 $children
获得的是空数组。provide
/ inject
—— vue2.2 新增的 api ,父组件经过 provide
属性来提供变量,而后在子组件中用 inject
来注入变量。ref
/ $refs
—— ref
在普通 DOM 元素上使用,引用的就是 DOM 元素;若是在子组件上,引用的是组件实例$attrs
/ $listeners
—— vue2.4新增,能够进行跨级的组件通讯eventBus
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
// A.vue
<template>
<div>
<button @click="sendFirstName"></button>
</div>
</template>
<script>
import {EventBus} from './event-bus.js'
export default {
data(){
return{
firstName:'leborn'
}
},
methods:{
sendFirstName(){
EventBus.$emit('getFirstName', {
firstName:this.firstName
})
}
}
}
</script>
// B.vue
<template>
<div>姓名: {{name}}</div>
</template>
<script>
import { EventBus } from './event-bus.js'
export default {
data() {
return {
name: ''
}
},
mounted() {
EventBus.$on('getFirstName', param => {
this.name = param.firstName + 'james';
})
}
}
</script>
复制代码
代码层面:
Webpack 层面优化:
基础的Web技术优化:
详细内容可参考:Vue 项目性能优化 — 实践指南(网上最全 / 详细)
class ExampleComponent extends React.Component {
// 用于初始化 state
constructor() {}
// 用于替换 `componentWillReceiveProps` ,该函数会在初始化和 `update` 时被调用
// 由于该函数是静态函数,因此取不到 `this`
// 若是须要对比 `prevProps` 须要单独在 `state` 中维护
static getDerivedStateFromProps(nextProps, prevState) {}
// 判断是否须要更新组件,多用于组件性能优化
shouldComponentUpdate(nextProps, nextState) {}
// 组件挂载后调用
// 能够在该函数中进行请求或者订阅
componentDidMount() {}
// 用于得到最新的 DOM 数据
getSnapshotBeforeUpdate() {}
// 组件即将销毁
// 能够在此处移除订阅,定时器等等
componentWillUnmount() {}
// 组件销毁后调用
componentDidUnMount() {}
// 组件更新后调用
componentDidUpdate() {}
// 渲染组件函数
render() {}
// 如下函数不建议使用
UNSAFE_componentWillMount() {}
UNSAFE_componentWillUpdate(nextProps, nextState) {}
UNSAFE_componentWillReceiveProps(nextProps) {}
}
复制代码
缘由:在 setState 函数实现中,会根据 isBatchingUpdates 这个变量来决定是否同步更新 this.state。isBatchingUpdates 默认是 false 表示 setState 会同步更新 this.state。但 React 在调用事件处理函数时会调用一个 batchedUpdates 函数,将 isBatchingUpdates 改成 true,此时 setState 不会同步更新 this.state
class Example extends React.Component {
constructor() {
super();
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
render() {
return null;
}
};
复制代码
输出:0 0 2 3
前端的知识点实在是太多太杂了,对于一些没有涉及到的内容,例如 webpack,babel 什么的后续可能会补上。本篇文章若是能帮助到你的话点赞就完事儿了,没能帮到你也敬请见谅哈~
最后建议你们在看一些技术文章的时候能够本身一边作作总结,否则很容易过目即忘的,没错博主就是如此,而后文章若是有不严谨的地方,欢迎你们评论区指出。