xccjk / x-blog Goto Github PK
View Code? Open in Web Editor NEW学习笔记
学习笔记
import React, { useState, useEffect } from 'react'
const completionFunc = value => {
if(String(value).length === 1) return `0${value}`
return value
}
function Timer() {
const [remainTime, setTime] = useState(null)
useEffect(() => {
const endTime = 1603165442000
const time = endTime ? endTime > new Date().getTime() ? endTime - new Date().getTime() : 0 : 0
const timer = setTimeout(() => {
setTime(time)
}, 1000)
return () => {
clearInterval(timer)
}
}, [remainTime])
const remainHour = parseInt(remainTime / (60 * 60000)) || '00'
const remainMinutes = parseInt((remainTime % (60 * 60000)) / 60000) || '00'
const remainSeconds = parseInt((remainTime % 60000) / 1000) || '00'
return (
<h3>{completionFunc(remainHour)}:{completionFunc(remainMinutes)}:{completionFunc(remainSeconds)}</h3>
)
}
export default Timer
网络层面
请求过程的优化
减少网络请求
渲染层面
服务端渲染的探索与实践
浏览器的渲染机制解析
DOM优化
首屏渲染提示:懒加载初探
经典面试教学
性能监测
可视化工具
W3C性能API
在package中常常可以看到dependencies与devDependencies,但是大家对于这两个有什么深入了解吗?
当你发布一个项目到npm上时,通常会将node_modules添加到.gitignore文件中,是为了避免文件过大导致上传失败
当别人clone你的项目到本地时,并试图运行你的项目时,会发现没有任何效果,这是因为你本地的依赖安装在了你的node_modules上,但是你没有推送到远程仓库上。
解决问题的唯一方法就是别人npm install
,他会安装package.json中所有依赖到的库
package.json文件主要存储了项目的一些依赖关系及其他信息
dependencies本意为依赖
,devDependencies的本意为开发依赖
dependencies: 应该包含项目的基础框架,比如vue,react等
// 安装项目依赖
npm install xxx -S
npm install xxx --save
npm install xxx
devDependencies: 应该包含在开发期间使用的软件包或用于构建捆绑包的软件包,例如mocha,jsc,grunt-contrib-watch,gulp-jade等。这些软件包仅在开发项目时才是必需的,ESlint用于在构建捆绑包时检查所有内容
// 安装开发依赖
npm install xxx --dev
npm install xxx -D
背景:最近在做一个公司内部使用的内部监控系统,需要使用node写一些api接口,很久没怎么写node了,就把流程都走了一遍
这个主要针对新人来,写一个详细的入门教程。采用react+express+mongodb
// 登录命令
ssh <username>@<hostname or IP address>
// root权限登录服务器
sudo ssh [email protected]
下面这样既代表登录服务器成功
因为我的是node服务器,所以node与npm不需要安装,如果是其他类型的服务器,需要安装node与npm
如果比较熟悉Linux命令,那么通过命令安装相关依赖就可以了,如果不是特别清楚,可以通过宝塔面板来进行服务器的可视化操作
需要安装:nvm,pm2,nginx,git,node,npm
sudo yum install -y nginx git pm2
使用curl安装
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
使用wegt安装
wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
或者
git clone git://github.com/creationix/nvm.git ~/nvm
安装过程中遇到的问题:
Failed connect to raw.githubusercontent.com:443; 拒绝连接
解决方式:
解决GitHub的raw.githubusercontent.com无法连接问题
sudo vi /etc/hosts
199.232.96.133 raw.githubusercontent.com
nvm的作用是用来安装多个不同版本的nodejs,可以随时切换版本。实际工作中可能很多node项目的node版本依赖并不相同,因此需要通过nvm安装多个版本nodejs
设置nvm自动运行
echo "source ~/nvm/nvm.sh" >> ~/.bashrc
source ~/.bashrc
查看node版本列表
nvm list-remote
安装指定版本node
nvm install v12.16.3
切换node版本
nvm use v12.16.3
到了这一步,就把服务器的环境相关给安装好了,下一节介绍通过宝塔面板
来登录及使用服务器
在小程序中获取定位具体信息时,不要配置腾讯地图中的WebServiceAPI中的域名白名单什么的,域名配置直接在小程序后台中配置(就是这个货https://apis.map.qq.com),
千万千万不要在设置域名白名单
正确操作是在小程序的后台设置中设置域名
宝塔面板是一款使用方便、功能强大且终身免费的服务器管理软件,支持 Linux 与 Windows 系统。在宝塔面板中,您可以一键配置 LAMP、LNMP、网站、数据库、FTP、SSL,还可以通过 Web 端轻松管理服务器
按照第一节登录服务器
输入宝塔安装的命令,如果你选择的是centos系统7.x以上的版本输入以下命令👇:
yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh
其他各个版本的则需要输入以下对应版本的命令👇:
// Ubuntu/Deepin安装脚本
wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh
// Debian安装脚本
wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && bash install.sh
// Fedora安装脚本
wget -O install.sh http://download.bt.cn/install/install_6.0.sh && bash install.sh
安装的过程中,中途需要输入一次字母Y来确认安装,只需要输入Y字母回车即可继续自动安装了👇:
大于需要1-2分钟左右安装相关的依赖,出现下面的信息既代表成功👇:
千万要记住登录的用户名密码相关信息
宝塔面板默认端口为8888,需要在服务器的防火墙中放开
LNMP
打开http://120.53.247.128:8888/soft
,输入用户名密码登录
首次进入面板会提示你安装套件,一般选择推荐的LNMP(推荐),这个,nginx具有的特点是并发能力强并且占用内存小,所以一般推荐都是安装左边的这个选项了。php等信息如果不是会用到的话就去掉勾选
你可以学到的:
启动项目的前提是对应的xcode Android Studio环境安装完全
flutter doctor
安装正确会出现下面这样的提示👇:
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 1.22.6, on macOS 11.2.1 20D74 darwin-x64, locale zh-Hans-CN)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
[✓] Xcode - develop for iOS and macOS (Xcode 12.4)
[✓] Android Studio (version 4.1)
[✓] VS Code (version 1.52.0)
[✓] Connected device (1 available)
• No issues found!
如果安装错误,按照环境搭建中的安装flutter
创建项目名为app的flutter项目
flutter create app
初始化创建成功会出现下面提示
All done!
[✓] Flutter: is fully installed. (Channel stable, 1.22.6, on macOS 11.2.1 20D74 darwin-x64, locale zh-Hans-CN)
[✓] Android toolchain - develop for Android devices: is fully installed. (Android SDK version 30.0.2)
[✓] Xcode - develop for iOS and macOS: is fully installed. (Xcode 12.4)
[✓] Android Studio: is fully installed. (version 4.1)
[✓] VS Code: is fully installed. (version 1.52.0)
[✓] Connected device: is fully installed. (1 available)
In order to run your application, type:
$ cd app
$ flutter run
Your application code is in app/lib/main.dart.
cd app
flutter run
会自动打开模拟器,并出现下面的提示信息
Launching lib/main.dart on iPhone 12 Pro Max in debug mode...
Running Xcode build...
└─Compiling, linking and signing... 87.2s
Xcode build done. 114.1s
Waiting for iPhone 12 Pro Max to report its views... 4ms
Syncing files to device iPhone 12 Pro Max... 353ms
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h Repeat this help message.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
An Observatory debugger and profiler on iPhone 12 Pro Max is available at: http://127.0.0.1:55603/9Dz6pZ8WEDY=/
// 启动项目
flutter run
pubspec.yaml
文件中管理依赖,比如新增包english_words
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.0
english_words: ^3.1.5
// 根目录命令行运行下面命令
flutter pub get
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final wordPair = WordPair.random();
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
child: Text(wordPair.asPascalCase),
),
),
);
}
}
在通过flutter run
运行的命令下,输入r
即可完成热重载
几个常见的命令:
Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h Repeat this help message.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
通过Navigator.of来实现路由的具体实现
Navigator.of(context).push(
new MaterialPageRoute<void>(
builder: (BuildContext context) {
},
),
);
通过onTap
来实现页面交互,使用setState改变对应状态
onTap: () {
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
}
通过new ThemeData()
来实现主题的切换
MaterialApp(
title: 'Startup Name Generator',
theme: new ThemeData(
primaryColor: Colors.white,
),
home: RandomWords(),
)
字节流解码
-> 字符 -> 输入流预处理
-> 统一字符 -> 令牌化
-> 令牌 -> 构建DOM树
-> DOM树<html><head>
-> <html><head>
-> 开启标签html,开启标签head -> html元素 - body元素 - 文本节点回流
和重绘
的出现function test1() {
console.log(this)
}
test1() // window
var test2 = function () {
console.log(this)
}
test2() // window
var a = 1
var obj = {
a: 2,
test3: function () {
console.log(this)
console.log(this.a)
}
}
obj.test3() // obj 2
// 循环中
[1, 2, 3].forEach(function () {
console.log(this) // window
})
var obj2 = {
test4: function () {
console.log(this)
},
test5: () => {
console.log(this)
},
test6: function () {
var test7 = () => console.log(this)
test7()
},
test8: () => {
var test9 = () => console.log(this)
test9()
}
}
obj2.test4() // {test4: ƒ, test5: ƒ, test6: ƒ, test8: ƒ}
obj2.test5() // window
obj2.test6() // {test4: ƒ, test5: ƒ, test6: ƒ, test8: ƒ}
obj2.test8() // window
var a = 1
function foo() {
console.log(this)
}
var obj = new foo()
obj.a = 2
console.log(obj) // foo {a: 2}
console.log(obj.a) // 2
var obj1 = {
name: 'xcc',
age: 20,
sex: '男'
}
var obj2 = {
name: 'xcc1',
age: 28,
test1: function () {
console.log(this)
}
}
obj2.test1() // {name: "xcc1", age: 28, test1: ƒ}
obj2.test1.call(obj1, '123') // {name: "xcc", age: 20, sex: "男"}
var obj1 = {
name: 'xcc',
age: 20,
sex: '男'
}
function test2(name, age, sex) {
console.log(name, age, sex, this)
}
test2.apply(obj1, ['xcc2', 18, '女']) // xcc2 18 女 {name: "xcc", age: 20, sex: "男"}
var obj1 = {
name: 'xcc',
age: 20,
sex: '男'
}
function test2(name, age, sex) {
console.log(name, age, sex, this)
}
var test3 = test2.bind(obj1)
test3('xcc3', 30, '男') // xcc3 30 男 {name: "xcc", age: 20, sex: "男"}
公司内容搭建了自己的npm仓库,下载内部npm包时,必须通过内部的npm源来安装依赖,否则安装不成功。当个人要发布npm包时,经常需要来回切换npm源,比较麻烦,特此记录一下操作过程
本质还是安装的npm上的包,依赖包的仓库不会变,所以安装下来的包没有什么区别
yarn.lock
文件来记录安装版本JS包管理平台,但是安装速度不够快,拉取的package包版本可能不一致,同时npm允许安装package时执行代码,存在安全隐患
npm: https://registry.npmjs.org/
cnpm: https://r.cnpmjs.org/
taobao: https://registry.npm.taobao.org/
nj: https://registry.nodejitsu.com/
rednpm: https://registry.mirror.cqupt.edu.cn/
npmMirror: https://skimdb.npmjs.com/registry/
deunpm: http://registry.enpmjs.org/
在命令行工具输入npm config edit
即可对本机镜像进行查看编辑
npm config edit
假设修改为淘宝镜像
// 查看npm当前镜像源
npm config get registry
// 设置npm镜像源为淘宝镜像
npm config set registry https://registry.npm.taobao.org/
// 查看yarn当前镜像源
config get registry
// 设置yarn镜像源为淘宝镜像
yarn config set registry https://registry.npm.taobao.org/
npm install nrm -g
nrm ls
nrm use yarn
nrm add yarn https://registry.yarnpkg.com/
nrm del yarn
nrm test yarn
最近在通过download-git-repo
包来拉取不同的项目模板,download-git-repo npm
看了一下使用文档,发现非常的简单明了,适用于github
,gitlab
等
// 安装
npm install download-git-repo
// 使用
const download = require('download-git-repo')
download('flippidippi/download-git-repo-fixture', 'test/tmp', function (err) {
console.log(err ? 'Error' : 'Success')
})
运行案例的可以正常运行,安装文档配置后一直报错
'git clone' failed with status 128
通过direct:url...来进行克隆
// 正确的使用方式
download('direct:https://github.com/xccjk/app.git', 'app', { clone: true }, function (err) {
console.log(err ? err : 'success')
})
需要兼容老版本浏览器慎用
// app.js
import React from 'react'
import useVisibility from './visibilityChange'
export default function App() {
const videoElement = document.getElementById('video')
// 直接调用即可
useVisibility(videoElement)
return (
<div className='App'>
<h1>visibilityChange api在react的使用</h1>
<video
autoplay
loop
id='video'
src={
'https://s3-ap-northeast-1.amazonaws.com/daniemon/demos/The%2BVillage-Mobile.mp4'
}
controls='controls'
width={500}
height={300}
/>
</div>
);
}
// visibilityChange.js
/**
* 处理切换页面后视频暂停播放
* @param {视频的ele} videoElement
*/
function useVisibility(videoElement) {
var hidden, visibilityChange
if (typeof document.hidden !== 'undefined') { // Opera 12.10 and Firefox 18 and later support
hidden = 'hidden'
visibilityChange = 'visibilitychange'
} else if (typeof document.msHidden !== 'undefined') {
hidden = 'msHidden'
visibilityChange = 'msvisibilitychange'
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = 'webkitHidden'
visibilityChange = 'webkitvisibilitychange'
}
const handleVisibilityChange = () => {
if (document[hidden]) {
videoElement && videoElement.pause()
} else {
videoElement && videoElement.play()
}
}
if (typeof window.document.addEventListener === 'undefined' || typeof document[hidden] === 'undefined') {
console.log('This demo requires a browser, such as Google Chrome or Firefox, that supports the Page Visibility API.')
} else {
// 处理页面可见属性的改变
window.document.addEventListener(visibilityChange, handleVisibilityChange, false)
// 当视频暂停,设置title
// This shows the paused
window.document.addEventListener('pause', function () {
window.document.title = 'pause'
}, false)
// 当视频播放,设置title
window.document.addEventListener('play', function () {
window.document.title = 'play'
}, false)
}
}
export default useVisibility
<p>xcc</p>
上下文切换
,这个操作会比较耗时 // 案例
// 当数组长度比较长时,会比较耗时
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.time('xcc')
let body = document.body
data.map(li => {
body.appendChild(<p>{li}</p>)
})
console.timeEnd('xcc')
// good
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
console.time('xcc')
let body = document.body
let str = ''
data.map(li => {
str += <p>{li}</p>
})
body.appendChild(str)
console.timeEnd('xcc')
元素已经样式变化时会引起再次渲染,在渲染过程中最耗时的步骤为重排(Reflow)
与重绘(Repaint)
浏览器渲染页面时会将HTML和CSS解析为DOM和CSSOM,然后进行排布
重排一定会引起重绘,但是重绘不会导致重排
可能会影响带其他元素排布的操作就会引起重排,继而引发重绘
会引起重绘的操作
// bad
const len = 1000
console.time('bad')
for(let i = 0; i < len; i++) {
document.body === 1 ? console.log(i) : null
}
console.timeEnd('bad')
// good
const len = 1000
console.time('good')
const body = JSON.stringify(document.body)
for(let i = 0; i < len; i++) {
body === 1 ? console.log(i) : null
}
console.timeEnd('good')
// bad
console.time('bad')
const ul = document.createElement('ul')
for(let i = 0; i < 1000; i++) {
ul.appendChild(<li>{i}</li>)
}
console.timeEnd('bad')
// good
console.time('good')
const ul = document.createElement('ul')
let str = ''
for(let i = 0; i < 1000; i++) {
str += `<li>{i}</li>`
}
ul.appendChild(str)
console.timeEnd('good')
// bad
for(let i = 0; i < document.querySelectorAll('div').length; i++) {
document.querySelectorAll(`div`)[i].innerText = i
}
// good
const divs = document.querySelectorAll('div')
for (let i = 0; i < divs.length; i++) {
divs[i].innerText = i
}
不要使用复杂的匹配规则和复杂的样式
,从而减少渲染引擎计算样式规则生成CSSOM树的时间ls -al ~/.ssh
,查看.ssh文件下是否有生成秘钥文件mkdir ~/.ssh
生成.ssh文件,同时chmod 700 ~/.ssh
给权限cd ~/.ssh
到.ssh文件下,输入ls
命令查看秘钥文件 // 生成github秘钥
ssh-keygen -t rsa -C 'github登录的邮箱' -f ~/.ssh/id_rsa_github
// -t指定秘钥类型,默认rsa
// -C 设置注释文字,比如邮箱
// -f 指定秘钥文件名称
// 按enter,不输入用户名与密码
// 会生成文件名为id_rsa_github与id_rsa_github.pub两个文件
// 同理生成gitlab秘钥
ssh-keygen -t rsa -C 'gitlab登录的邮箱' -f ~/.ssh/id_rsa_gitlab
// 会生成文件名为id_rsa_gitlab与id_rsa_gitlab.pub两个文件
// 进入.ssh文件
cd ~/.ssh
// 给config文件添加权限,不然在vim文件保存时会提示权限不足
// 错误提示
// .ssh/config" E212: Can't open file for writing
// Press ENTER or type command to continue
sudo touch config
sudo chown root:root config
sudo chmod 755 config
whoami
vim config
// 在config文件中输入下面内容,注意文件名id_rsa_gitlab与id_rsa_github写为自己的文件名
#gitlab
Host gitlab
HostName gitlab.*.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_rsa_gitlab
#github
Host github
AddKeysToAgent yes
UseKeychain yes
HostName github.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/id_rsa_github
// 输入结束后按esc,退出vim编辑模式,按i进入vim编辑模式
// 输入shift+:,然后输入wq,保存并退出vim
sudo ssh-add ~/.ssh/id_rsa_gitlab
与sudo ssh-add ~/.ssh/id_rsa_github
,一定要执行sudo ssh -T [email protected]
与sudo ssh -T [email protected]
,测试一下是否成功mkdir xcc && cd xcc
npm init
xcc-standrad-eslint
,版本为0.1.0
npm adduser
Logged in as xxx on https://registry.npmjs.org/.
npm login
npm publish
即可npm unpublish [email protected]
// 代码1
redirectWXAuth = () => {
const { goToPage } = this.state
const redirectUrl = encodeURIComponent(
`${process.env.REDIRECT_HOST}/login?goto_page=${encodeURIComponent(goToPage)}&bindCode=1`
)
const wechatAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${process.env.WXAPPID}&redirect_uri=${redirectUrl}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`
window.location.replace(wechatAuthUrl)
}
// 代码2
// 重定向前
https://h5.abc.com/?v=1592878502327#/home
// 经过重定向后
https://h5.abc.com/?v=1592878502327#
// 处理方式 - 对要重定向地址中的#剪切
redirectWXAuth = () => {
const { goToPage } = this.state
const url = (goToPage + '').replace('#', '')
const redirectUrl = encodeURIComponent(
`${process.env.REDIRECT_HOST}/login?goto_page=${encodeURIComponent(url)}&bindCode=1`
)
const wechatAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${process.env.WXAPPID}&redirect_uri=${redirectUrl}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`
window.location.replace(wechatAuthUrl)
}
// 处理方式2 - 不使用hash模式路由,由后端配置nginx来解决路由跳转
redirectWXAuth = () => {
const { goToPage } = this.state
const url = (goToPage + '').replace('#', '')
const redirectUrl = encodeURIComponent(
`${process.env.REDIRECT_HOST}/login?goto_page=${encodeURIComponent(url)}&bindCode=1`
)
const wechatAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${process.env.WXAPPID}&redirect_uri=${redirectUrl}&response_type=code&scope=snsapi_userinfo&state=STATE&connect_redirect=1#wechat_redirect`
window.location.replace(wechatAuthUrl)
}
HTTP协议(超文本传输协议)是浏览器端与服务端之间最主要的通信协议
1991年出现,作用是用来传输超文本内容HTML
传输方式
为客户端发起请求,服务端响应请求的通信方式: 客户端 -> GET/index.html -> 服务端 -> <html>...</html> -> 客户端
HTTP/0.9的不足
只能用来传输文本内容,随着新技术的发展,浏览器希望通过HTTP来传输基本,样式,图片,音视频等其他类型文件
核心改变
1996年出现
1. 新增头部设定,头部内容以键值对的形式设置
2. 请求头部通过Accept字段来告诉服务端可以接收的请求的文件类型
3. 响应头部通过Content-Type字段来告诉浏览器返回的文件类型
4. 很多其他功能也可以依靠头部字段实现,比如缓存,认证
传输方式
客户端 -> GET/index.html HTTP/1.0 accept:text/css -> 服务端 -> HTTP/1.0 200 OK Content-type: text/css... -> 客户端
不足
在HTTP/1.0中,每进行一次通信,都需要建立连接、数据传输、断开连接三个阶段,在连接过多时,不停的建立连接和断开连接会增大网络开销
核心改变
1999年出现,解决了连接问题,及重复的创建和断开网络连接,增加了一个创建持久连接的方法,主要实现为一个连接传输完成时,并不是马上进行关闭,而是继续复用它传输其他的请求数据,这个连接保持到浏览器或者服务器端断开连接为止
传输方式
客户端 -> GET/index.html HTTP/1.0 accept:text/css -> 服务端 -> HTTP/1.0 200 OK Content-type: text/css... -> 客户端 -> 下一次请求 GET/index.html HTTP/1.0 accept:text/css -> 服务端 -> HTTP/1.0 200 OK Content-type: text/css... -> ... ->客户端
背景
因为HTTP是基于TCP实现的,TCP建立连接以及断开的过程,也就是常常说的'三次握手'和'四次挥手'
为什么需要进行三次握手?
客户端和服务端断开连接时要发送四次数据,这个过程称为四次挥手
为什么建立连接只通信了三次,而断开连接却用了四次?
HTTP/1.1的不足
虽然通过长连接减少了大量创建/断开连接造成的性能损耗,但是它的并发能力受到限制,所以传输性能还有很大的提升空间
并发能力受限原因
浏览器为了减缓服务器的压力
,浏览器限制了同一个域名下HTTP连接数为6-8个,所以在HTTP/1.1下很容易看到资源文件等待加载的情况,对应的优化方式就是使用多个域名来加载资源HTTP/1.1本身的问题
,虽然HTTP/1.1使用了持久连接,但是多个请求公用一个TCP连接,一个连接中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态,这种情况称为'队头阻塞'HTTP/2解决问题的方式?
多路复用
二进制帧
HTTP1.1与HTTP2存在的问题?
HTTP/3的解决方式?
协议版本 | 解决的核心问题 | 解决方式 |
---|---|---|
0.9 | HTML 文件传输 | 确立了客户端请求、服务端响应的通信流程 |
1.0 | 不同类型文件传输 | 设立头部字段 |
1.1 | 创建/断开 TCP 连接开销大 | 建立长连接进行复用 |
2 | 并发数有限 | 二进制分帧 |
3 | TCP 丢包阻塞 | 采用 UDP 协议 |
// 代码1
if(true) {
var a = 10
}
console.log(a) // 10
等价于
var a = undefined
if(true) {
a = 10
}
console.log(a)
// 代码2
if(false) {
var b = 20
}
console.log(b) // undefined
等价于
var b = undefined
if(true) {
b = 10
}
console.log(b)
// 代码3
var a = 10
console.log(window.a) // 10
// 代码4 - const同理
if(true) {
let a = 10
}
console.log(a) // a is not defined
// 代码5 - const同理
var a = 10
let a = 20 // Identifier 'a' has already been declared
// 代码6
console.log(a) // Identifier 'a' has already been declared
let a = 10
// 代码7 - const同理
let a = 10
console.log(window.a) // undefined
// 代码8 - const同理
let a = 10
a = 20
console.log(a) // 20
// 代码9
const a = 10
a = 20 // Identifier 'a' has already been declared
// 代码10
const a = []
a.push(1)
console.log(a) // [1]
const obj = { a: 1 }
obj.a = 2
console.log(obj) // { a: 2 }
// 代码11
var a = 10
{
console.log(a) // Cannot access 'a' before initialization
let a = 20
}
var a = 10
{
console.log(a) // 10
var a = 20
}
// 代码12
var a = 10
(function() {
console.log(a) // a is not defined
const a = 20
})()
// 代码13
var a = []
for(var i = 0; i < 10; i++) {
a[i] = function() {
console.log(i)
}
}
a[0]() // 10
a[1]() // 10
// 代码14
var a = []
for(var i = 0; i < 10; i++) {
a[i] = (function(i) {
return function() {
console.log(i)
}
}(i))
}
a[0]() // 0
a[1]() // 1
// 代码15
var a = []
for(let i = 0; i < 10; i++) {
a[i] = function() {
console.log(i)
}
}
a[0]() // 0
a[1]() // 1
// 代码16
for(let i = 0; i < 10; i++) {
let i = 'a'
console.log(i) // a ... a
}
// 代码17
for(var i = 0; i < 10; i++) {
let i = 'a'
console.log(i) // a ... a
}
// 代码18
// console出一个值后就终止了
for(var i = 0; i < 10; i++) {
var i = 'a'
console.log(i) // a
}
等价于
var i = 0
for(i < 10; i++) {
i = 'a'
console.log(a)
}
执行完一次后
var i = 'a'
'a'++ => NaN
// 代码19
// 变量重复定义
for(const i = 0; i < 10; i++) {
console.log(i) // Assignment to constant variable.
}
// 代码20
const a = [], obj = { a: 1, b: 2, c: 3 }
for(var key in object) {
a.push(function() {
console.log(key)
})
}
a[0]() // c
// 代码21
var a = [], obj = { a: 1, b: 2, c: 3 }
for(let key in obj) {
a.push(function() {
console.log(key)
})
}
a[0]() // a
// 代码22
var a = [], obj = { a: 1, b: 2, c: 3 }
for(const key in obj) {
a.push(function() {
console.log(key)
})
}
a[0]() // a
相信大家在求职的过程中,一定遇到过下列的几种情况:
那么在平时的求职过程中,怎么避免出现上述的问题呢
问题1:
简历被查看,但是用人公司一直没有给反馈,当遇到这种情况时,需要从下面几个情况来反思这个问题
问题2:
面试感觉很顺利,但是公司迟迟没有给出offer,有可能是下面几种情况:
问题3:
面试了很多公司,但是一直没有满意的,有可能是下面几种情况:
不造假
@HOC function() {}
export default HOC(HocComponent)
import React from 'react'
function HOC(WrapComponent) {
function ChildComponent(props) {
const add = (a, b) => {
return a + b
}
const newProps = { type: 'hoc', add }
return <WrapComponent {...props} {...newProps} />
}
return ChildComponent
}
// @HOC
function HocComponent(props) {
console.log(props) // { type: 'hoc', add: f(){} }
const { add } = props
console.log(add(1, 2))
return (
<div>HocPage</div>
)
}
const newHocComponent = HOC(HocComponent)
export default newHocComponent
开发平台:mac pro
node版本:v8.11.2
npm版本:6.4.1
react-native版本:0.57.8
native-echarts版本:^0.5.0
目标平台:android端收银机,android7.0+
最近在使用react native进行app的开发工作,rn开发的内容会与原生app混合,在一些使用场景下遇到了一些问题
使用场景:每日收益与每月收益的折线图统计
在pc/h5端的开发工作中,图标类的开发使用echarts/f2等三方工具都是比较常见的了,在react native中,没有了DOM的概念,因此在react native中使用了一些三方的图标库
native-echarts,github地址。
需要更换echarts版本的方法
native-echarts内部使用了react native中的webview进行图表的展示,本质上只是传入数据,通过echarts渲染出静态的HTML文档,然后通过webview展示出来而已。
netive-echarts内部使用的echarts版本为v3.2.3"版本,如果需要更高级的echarts版本,只需要更换src/components/Echarts/echarts.min.js文件以及tpl.html文件里的内容即可。
使用时遇到的问题: 在debug模式下,真机以及测试机器上图标正常显示,打包成android的apk文件后图表都不显示
解决方式:
1:找到native-echarts/src/components/Echarts/tpl.html文件,复制到android/app/src/main/assets这个目录下面,如果文件夹不存在就新建一个即可。
2:找到文件native-echarts/src/components/Echarts/index.js,修改为一下内容
import React, { Component } from 'react';
import { WebView, View, StyleSheet, Platform } from 'react-native';
import renderChart from './renderChart';
import echarts from './echarts.min';
export default class App extends Component {
constructor(props) {
super(props);
}
// 预防过渡渲染
shouldComponentUpdate(nextProps, nextState) {
const thisProps = this.props || {}
nextProps = nextProps || {}
if (Object.keys(thisProps).length !== Object.keys(nextProps).length) {
return true
}
for (const key in nextProps) {
if (JSON.stringify(thisProps[key]) != JSON.stringify(nextProps[key])) {
// console.log('props', key, thisProps[key], nextProps[key])
return true
}
}
return false
}
componentWillReceiveProps(nextProps) {
if (nextProps.option !== this.props.option) {
// 解决数据改变时页面闪烁的问题
this.refs.chart.injectJavaScript(renderChart(nextProps))
}
}
render() {
return (
<View style={{flex: 1, height: this.props.height || 400,}}>
<WebView
ref="chart"
scrollEnabled = {false}
injectedJavaScript = {renderChart(this.props)}
style={{
height: this.props.height || 400,
backgroundColor: this.props.backgroundColor || 'transparent'
}}
scalesPageToFit={Platform.OS !== 'ios'}
originWhitelist={['*']}
source={{uri: 'file:///android_asset/tpl.html'}}
onMessage={event => this.props.onPress ? this.props.onPress(JSON.parse(event.nativeEvent.data)) : null}
/>
</View>
);
}
}
可能存在的问题????
同时,在后续的react native版本中,webview即将从react native内部移除出去,改为三方包安装使用。参考:
https://reactnative.cn/docs/webview/#mixedcontentmode
react-native-community/discussions-and-proposals#6
因此,在后续新版本中使用native-echarts,可能会使用不了,因此建议fork一个稳定的版本到自己的github上,或者在后续自己采用react-native-webview + echarts的方式自由的组合版本,使用起来更加自由。
参考文档:
somonus/react-native-echarts#47
背景:最近准备在使用react-native开发的app中接入友盟,来进行用户行为统计,分享,授权登录等操作。
在使用的过程中,遇到了一些错误信息,在此记录一下。
在修改android目录下的build.gradle等信息后,运行react-native run-android报错,错误信息如下:
com.android.build.api.transform.TransformException: com.android.builder.dexing.DexArchiveBuilderException: com.android.builder.dexing.DexArchiveBuilderException: Failed to process /Users/xxx/.gradle/caches/modules-2/files-2.1/com.umeng.analytics/analytics/6.1.4/c1d30c2bbdce435b775cadca57df2098044a0f5e/analytics-6.1.4.jar
为什么会提示这个错误了,后来经过搜索查找,发现了下面介绍:http://www.cnblogs.com/evilKing/p/4778935.html
注释android/app/build.gradle目录下dependencies中的implementation 'com.umeng.analytics:analytics:latest.integration'
,重新执行编译脚本,启动正常,是什么原因导致的错误呢?
贴出我们引入的资源:
dependencies {
// 代表src目录同级libs目录全部解析
implementation fileTree(include: ['*.jar'], dir: 'libs')
// implementation files('libs/umeng-analytics-8.0.0.jar')
// implementation files('libs/umeng-common-2.0.0.jar')
// implementation 'com.umeng.analytics:analytics:latest.integration'
// implementation 'com.umeng.analytics:analytics:6.1.4'
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
implementation 'com.facebook.react:react-native:+'
}
在使用友盟时,我们会下载jar包存放在src同级的libs目录下面,libs目录包含分享,授权登录等一些三方SDK文件,目录结构
再看看dependencies下的implementation fileTree(include: ['*.jar'], dir: 'libs'),*时匹配libs目录下所有的已jar结尾的文件,等同于用过implementation引入资源,代表解析libs目录下的所有资源到文件中,因此在下面再次引入implementation 'com.umeng.analytics:analytics:latest.integration'时,会导致资源重复加载,导致打包失败。
错误现象:window.deltaUrlToBlobUrl is not a function
最近在调试react-native时,打开浏览器调试时发现报错window.deltaUrlToBlobUrl is not a function,通过搜索查找了一下原因。
参考:https://www.jianshu.com/p/1ead6716e09d
发现是因为http://localhost:8081/debugger-ui/模拟器的远程调试已经打开导致的,为什么会出现这样的情况呢?
当在之前已经打开模拟器的情况下,晚上下班了,电脑直接关机,第二天来开机时其实模拟器窗口还是在开启的状态,因此在再次打开时有可能出现上面的报错信息。
android:command+m/ctrl+m + Stop Remote JS Debugging
ios:command+d + Stop Remote JS Debugging
或者摇一摇手机,点击Stop Remote JS Debugging关闭模拟器远程调试,再次打开模拟器远程调试。
// 代码1
// 每次value改变,就会发出一次请求
<input onChange={handleChange} />
const handleChange = async ({target: { value }}) => {
const res = await search({ keyword: value })
}
// 代码2
// 通过定时器。设置时间间隔500ms执行一次请求
<input onChange={handleChange} />
let timer = null
const handleChange = ({target: { value }}) => {
if(timer) {
clearTimeout(timer)
timer = null
}
timer = setTimeout(async () => {
const res = await search({ keyword: value })
}, 500)
}
// 代码3
// 公共函数抽取
/*
应有的功能
1. 参数和返回值如何传递?
2. 防抖之后函数是否可以立即执行?
3. 防抖的函数是否可以取消?
*/
const debounce = (func, wait = 0) => {
let timer = null
let args
function debounced(...age) {
args = arg
if(timer) {
clearTimeout(timer)
timer = null
}
// 以promise形式返回函数执行结果
return new Promise((res, rej) => {
timer = setTimeout(async () => {
try {
const result = await func.apply(this, args)
res(result)
} catch (e) {
rej(e)
}
}, wait)
})
}
// 取消方法
function cancel() {
clearTimeout(timer)
timer = null
}
// 立即执行
function fulsh() {
cancel()
return func.apply(this, args)
}
debounced.cancel = cancel
debounced.flush = flush
return debounced
}
下面3中监听方式的区别
方式1和方式2属于DOM0标准,通过这种方式进行事件监听会覆盖之前的事件监听函数
方式3属于DOM2标准,同一元素上的事件监听函数互不影响,而且可以独立取消,调用顺序和监听顺序一致
方式1:<input type='text' onclick='click()' />
方式2:document.querySelecter('input').onClick = function(e) {}
方式3:document.querySelecter('input').addEventListener('click', function(e) {})
<ul class='list'>
<li class='item'>item1<span class='edit'>编辑</span><span class='delete'>删除</span></li>
<li class='item'>item1<span class='edit'>编辑</span><span class='delete'>删除</span></li>
<li class='item'>item1<span class='edit'>编辑</span><span class='delete'>删除</span></li>
...
</ul>
const ul = document.querySelector('.list')
ul.addEventListener('click', e => {
const t = e.target || e.srcElement
if (t.classList.contains('item')) {
// 接口请求处理业务逻辑
} else {
id = t.parentElement.id
if (t.classList.contains('edit')) {
edit(id)
} else if (t.classList.contains('delete')) {
del(id)
}
}
})
"webpack": "^5.10.3",
"webpack-cli": "^4.2.0",
"webpack-dev-server": "^3.11.0"
// .babelrc.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
browsers: ['> 5%', 'IE 10', 'iOS 7', 'Firefox > 20']
},
useBuiltIns: 'usage',
corejs: 3
}
],
'@babel/preset-react'
],
plugins: [
'@babel/plugin-transform-runtime',
'@babel/plugin-proposal-class-properties',
[
'import',
{
libraryName: 'antd',
libraryDirectory: 'es'
}
]
]
}
Error: Cannot find module 'webpack-cli/bin/config-yargs'
在webpack-cli 4.x
中,不能过webpack-dev-server
启动项目了,需要通过webpack serve...
或者修改webpack-cli
版本改为3.x
// package.json
// webpack4.x
"script": {
"dev": "webpack-dev-server ...",
}
// webpack5.x
"script": {
"dev": "webpack serve ...",
}
// 降版本
{
[email protected]
}
webpack5
中错误出现错误UnhandledPromiseRejectionWarning: TypeError: webpack.NamedModulesPlugin is not a constructor
// webpack.config.js
// webpack4.x
module.exports = {
...
plugins: [
...
new webpack.NamedModulesPlugin()
]
}
// webpack5.x
// 在webpack5.x中,webpack.NamedModulesPlugin的功能已经内置
@babel/runtime
在webpack5.x中,发现很多关于@babel/runtime/helpers/esm
的文件引入错误,错误提示类似下面,通过锁定@babel/runtime包版本即可
Module not found: Error: Can't resolve './superPropBase' in '/Users/xxx/node_modules/@babel/runtime/helpers/esm'
Did you mean 'superPropBase.js'?
BREAKING CHANGE: The request './superPropBase' failed to resolve only because it was resolved as fully specified
(probably because the origin is a '*.mjs' file or a '*.js' file where the package.json contains '"type": "module"').
The extension in the request is mandatory for it to be fully specified.
Add the extension to the request
npm install @babel/[email protected] -D
webpack < 5 used to include polyfills for node.js core modules by default
在运行过程中出现了很多这样的报错信息,是由于在webpack5中移除了nodejs核心模块的polyfill自动引入,所以需要手动引入,如果打包过程中有使用到nodejs核心模块,webpack会提示进行相应配置
// webpack.config.js
module.exports = {
...
resolve: {
// https://github.com/babel/babel/issues/8462
// https://blog.csdn.net/qq_39807732/article/details/110089893
// 如果确认需要node polyfill,设置resolve.fallback安装对应的依赖
fallback: {
crypto: require.resolve('crypto-browserify'),
path: require.resolve('path-browserify'),
url: require.resolve('url'),
buffer: require.resolve('buffer/'),
util: require.resolve('util/'),
stream: require.resolve('stream-browserify/'),
vm: require.resolve('vm-browserify')
},
// 如果确认不需要node polyfill,设置resolve.alias设置为false
alias: {
crypto: false
}
}
}
为html-webpack-plugin
这个包导致的
npm run html-webpack-plugin@next -D
官方给出的问题原因是webpack-cli
这个包的版本导致的,github issue
// 官方提供的解决方式,修改webpack-cli版本到4.2.0既可
npm install [email protected] --save-dev
不过我在本地创建了一个新的项目,版本信息如下,还是存在上面的那个报错信息,demo地址github issue
"webpack": "^5.10.3",
"webpack-cli": "^4.2.0",
"webpack-dev-server": "^3.11.0"
webpack-merge
包遇到的问题 // 4.x版本
{
"webpack-merge": "^4.2.2"
}
// webpack.config.js
const merge = require('webpack-merge')
const defaultConfig = require('../...')
const config = merge(defaultConfig, {
})
export default config
// 5.x版本
{
"webpack-merge": "^5.7.0"
}
const webpackMerge = require('webpack-merge')
const defaultConfig = require('../...')
const config = webpackMerge.merge(defaultConfig, {
})
export default config
recoil
好像还不支持在webpack中使用,我们项目里有使用recoil,配置了babel后,一直提示You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
官方答复:guthub issue
TypeError: Cannot read property 'tap' of undefined
hard-source-webpack-plugin
这个包在版本升级后出现错误,github issue
// webpack.config.js
// webpack4.x
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
module.exports = {
...
piugins: [
new HardSourceWebpackPlugin({
environmentHash: {
root: process.cwd(),
directories: [],
files: ['package-lock.json', 'yarn.lock'],
},
cachePrune: {
maxAge: 2 * 24 * 60 * 60 * 1000,
sizeThreshold: 50 * 1024 * 1024
}
})
]
}
// webpack5.x
// https://github.com/mzgoddard/hard-source-webpack-plugin/issues/461
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')
module.exports = {
...
piugins: [
new HardSourceWebpackPlugin(),
new HardSourceWebpackPlugin.ExcludeModulePlugin([])
]
}
// 1.js
console.log(a)
// webpack.config.basic.js
module.exports = {
entry: './src/1.js',
mode: 'development'
}
// package.json
script: {
...
"build:basic": "webpack --config webpack.config.basic.js"
}
npm run build:basic
,打包生成dist/main.js
文件,命令行终止 // webpack.config.watch.js
module.exports = {
entry: './src/1.js',
mode: 'development',
watch: true
}
// package.json
"build:watch": "webpack --config webpack.config.watch.js"
npm run build:wathc
,打包生成dist/main.js
文件,命令行继续运行
,修改1.js文件,重新执行打包过程,生成新的dist/main.js
文件 // webpack.config.reload.js
module.exports = {
entry: './src/1.js',
mode: 'development',
watch: true,
devServer: {
// 为dist下静态文件提供本地服务渲染
contentBase: './dist',
open: true
}
}
// package.json
script: {
...
"dev:reload": "webpack-dev-server --config webpack.config.reload.js"
}
webpack-dev-server
npm run dev:reload
启动服务,dist文件新增idnex.html
文件,引入打包后的js文件 // 1.js
import './style.css'
...
// style.css
div {
color: red
}
// webpack.config.hmr.js
// 添加devServer hot与css解析器style-loader与css-loader
module.exports = {
entry: './src/index-0.js',
mode: 'none',
watch: true,
devServer: {
// 为dist下静态文件提供本地服务渲染
contentBase: './dist',
open: true,
hot: true
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
}
package.json
script: {
...
"dev:hmr": "webpack-dev-server --config webpack.config.hmr.js"
}
npm install style-loader css-loader
npm run dev:hmr
,浏览器查看发现head下面多了个style标签.hot-update.json
与.hot-update.js
.hot-update.json
与.hot-update.js
,然后就会重载所有请求一切源代码都可以通过各种loader转换为js模块(module),模块间可以相互引用
webpack通过入口点(entry point)递归处理各模块引用关系,最后输出为一个或多个产物文件(bundle)
没一个入口点都是一个快组(chunk group),在不考虑分包的情况下,一个chunk group中只有一个chunk,该chunk包含递归分析后的所有模块。每一个chunk都有一个打包后的输出问题(asset/bundle)
下面来看一份高级前端工程师招聘的JD
岗位职责:
1.负责xx产品线Web前端的开发和维护工作;
2.参与产品的讨论和UI设计,配合UI团队设计系统交互的流程,实现UI效果;
3.协助团队改进框架、优化前端性能。
岗位要求:
1.本科及以上学历,3年以上前端开发经验;
2.精通JavaScript, HTML, CSS, Ajax, jQuery, Bootstrap, SASS/LESS等前端开发技术;
3.精通Vue, React, Angular, Backbone, 或其它前端MVC框架中的一种或几种;
4.学习能力强,热衷技术,喜欢钻研, 有github或个人技术blog者优先;
5.有前端自动化测试, 单元测试方面经验者优先;
6.具备良好的沟通和团队协作能力,工作积极主动,思路清晰,责任心强
看到上面的招聘JD,可以发现几个关键的信息:
因此,假如你是下面的几种情况,可能需要想一下再进行投递:
还有些工作岗位的信息,也需要在面试之前考虑清楚:
同时也非常推荐在决定去某个公司之前,如果有熟人在里面,最好先问清楚里面的情况再决定。同时也可以去知乎、脉脉等求职平台上看公司的工作情况,比如公司是否有裁员、降薪、福利、工作环境、团队氛围、技术水平等多方面进行考量,来决定自己是否投递已经投递后的面试准备以及面试通过后的入职情况
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
cd ~/development
unzip ~/Downloads/flutter_macos_v0.5.1-beta.zip
export PATH=`pwd`/flutter/bin:$PATH
此时你会发现,你打开一个新的命令行窗口是,运行flutter doctor
,会报flutter不存在的错误,是因为环境变量还没全局生效
flutter doctor
错误详情
Android toolchain - develop for Android devices (Android SDK version 30.0.2)
✗ Android licenses not accepted. To resolve this, run: flutter doctor
--android-licenses
[✗] Xcode - develop for iOS and macOS
✗ Xcode installation is incomplete; a full installation is necessary for iOS
development.
Download at: https://developer.apple.com/xcode/download/
Or install Xcode via the App Store.
Once installed, run:
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch
✗ CocoaPods not installed.
CocoaPods is used to retrieve the iOS and macOS platform side's plugin
code that responds to your plugin usage on the Dart side.
Without CocoaPods, plugins will not work on iOS or macOS.
For more info, see https://flutter.dev/platform-plugins
To install:
sudo gem install cocoapods
[!] Android Studio (version 4.1)
✗ Flutter plugin not installed; this adds Flutter specific functionality.
✗ Dart plugin not installed; this adds Dart specific functionality.
[!] VS Code (version 1.52.0)
✗ Flutter extension not installed; install from
https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter
[!] Connected device
! No devices available
上面的错误信息是告诉你东西未安装完,比如xcode,Android Studio,Android Studio的dart、fultter插件,CocoaPods,VS Code的flutter插件未安装
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch
sudo gem install cocoapods
flutter doctor
,关于Android Studio相关的错误可能还是有,运行下面的命令// 注意AndroidStudio版本号
ln -s ~/Library/Application\ Support/Google/AndroidStudio4.1/plugins ~/Library/Application\ Support/AndroidStudio4.1
flutter doctor
,下面这样就都正常了echo $PATH
// 可以看到flutter的路径为
/Users/name/development/flutter/bin:
open ~/.bash_profile
vim ~/.bash_profile
// 创建bash_profile文件
touch .bash_profile
// 添加国内镜像
export PUB_HOSTED_URL="https://pub.flutter-io.cn"
export FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
// 添加安装路径,上面获取到的
export PATH="/Users/name/development/flutter/bin:$PATH"
source ~/.bash_profile
最终的bash_profile文件
// .bash_profile
# HomeBrew
export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles
export PATH="/usr/local/bin:$PATH"
export PATH="/usr/local/sbin:$PATH"
export PUB_HOSTED_URL="https://pub.flutter-io.cn"
export FLUTTER_STORAGE_BASE_URL="https://storage.flutter-io.cn"
# name为自己电脑的名称
export PATH="/Users/name/development/flutter/bin:$PATH"
source ~/.bashrc
# HomeBrew END
在现在的前端开发中,项目常常由多个人共同维护,但是由于每个人的代码风格、提交规范方面、编辑器设置等各方面的原因,导致提交上去的代码风格没有保持统一,常见的有下列几个问题:
git commit -m 'dddddd'
等commit记录为了保证代码风格统一,便于后期的维护,我们需要使用检查工具来检查自己的代码,常见的几个检查场景:
代码已上传到githubgithub
在之前的业务场景下,平时配置一些代码检查规范,为了拥有对应的检查功能,需要分别安装很多三方包,新建配置文件来达到效果,并且想在其他项目中也配置相同的配置时,需要重复安装众多的包,而且还难以保证包版本一致,导致不同项目间可能存在差异
目的
eslint
相关包 npm install xcc-standard-eslint eslint-plugin-react eslint
prettier
相关包 npm install xcc-standard-prettier
stylelint
相关包 npm install xcc-standard-stylelint
commit
相关包 npm install xcc-commitlint husky lint-staged
changelog
相关包 npm install conventional-changelog-cli
官网解释:eslint为一个插件化的JavaScript工具,目标是保证代码的一致性和避免错误
eslint
相关包 npm install xcc-standard-eslint eslint-plugin-react eslint
.eslintrc.js
,配置👇.eslintrc.js
useTs
是否为typescript编写,默认falseignorePatterns
需要排除的风格转换文件夹rules
自定义配置规则,可以用来覆盖默认规则 // ts中常用配置方式
const { getEslint } = require('xcc-standard-eslint')
module.exports = {
...getEslint({
ignorePatterns: ['.dll', 'build', '.temp'],
useTs: true,
// 需要覆盖默认的配置规则,在有些老项目中可能为了维持稳定,不改变之前的逻辑,就需要兼容之前的语法规则
rules: {
'comma-dangle': 'off',
'function-paren-newline': 'off',
'global-require': 'off',
'import/no-dynamic-require': 'off',
'no-inner-declarations': 'off',
'class-methods-use-this': 'off',
'import/extensions': 'off',
'import/prefer-default-export': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-var-requires': 'off',
/**
* 0: 禁止规则
* 1: 警告
* 2: 必须
*/
// render不规范
'react/display-name': 'off',
// 变量名不正确,比如 style_id
'@typescript-eslint/camelcase': 'off',
// 允许any
'@typescript-eslint/no-explicit-any': 'off',
'react/prop-types': 'off',
// 空函数
'@typescript-eslint/no-empty-function': 0,
// 函数先后顺序
'@typescript-eslint/no-use-before-define': 'off',
// return null
'@typescript-eslint/ban-ts-ignore': 'off',
'require-atomic-updates': 'off',
'@typescript-eslint/triple-slash-reference': 'off',
// 未使用变量
'@typescript-eslint/no-unused-vars': 0,
// 数组统一空格 [1, 2, 3, ...]
'array-bracket-spacing': 2,
// prettier 中默认函数名不加空格,类似 function add() {},而eslint中默认为function add () {}
'space-before-function-paren': 0
}
})
}
// 在js中的常用配置方式
const { getEslint } = require('xcc-standard-eslint')
module.exports = {
...getEslint({
ignorePatterns: ['.dll', 'build', '.temp'],
useTs: false,
rules: {
'comma-dangle': 'off',
'function-paren-newline': 'off',
'global-require': 'off',
'import/no-dynamic-require': 'off',
'no-inner-declarations': 'off',
// New rules
'class-methods-use-this': 'off',
'import/extensions': 'off',
'import/prefer-default-export': 'off',
// render不规范
'react/display-name': 'off',
'react/prop-types': 'off',
'require-atomic-updates': 'off',
// 数组统一空格 [1, 2, 3, ...]
'array-bracket-spacing': 2,
// prettier 中默认函数名不加空格,类似 function add() {},而eslint中默认为function add () {}
'space-before-function-paren': 0
}
})
}
.eslintignore
排除不需要执行检查的文件,一般只有src目录文件需要处理
dist
build
config
scripts
node_modules
public
.babelrc
!.*.js
publish.js
server.js
version.js
tsconfig.json
package.json
prettier是一个流行的代码格式化工具的名称,它能够解析代码,使用你自己设定的规则来重新打印出格式规范的代码
prettier
相关包 npm install xcc-standard-prettier
.prettierrc.js
,配置👇.prettierrc.js
const { getPrettier } = require('xcc-standard-prettier')
module.exports = {
...getPrettier()
}
.prettierignore
排除不需要进行代码美化的文件
.dll
build/
.temp
node_modules
.vscode
stylelint是用来对css文件进行格式化及美化的,再css/less/scss中都可以使用
stylelint
相关包 npm install xcc-standard-stylelint
.stylelintrc.js
,配置👇.stylelintrc.js
const { getStyleLint } = require('xcc-standard-stylelint')
module.exports = {
...getStyleLint({
rules: {
// 字体文件相关
'font-family-no-missing-generic-family-keyword': null,
// 空的样式文件
'no-empty-source': null,
// 计算属性 calc()
'function-calc-no-invalid': null
}
})
}
用来对commit阶段效验的,比如代码,样式,commit提交文案等不符合配置的规范时,都可以终止commit的提交
commit
相关包 npm install xcc-commitlint husky lint-staged
.commitlintrc.js
,配置👇.commitlintrc.js
const { getCommitLint } = require('xcc-commitlint')
module.exports = {
...getCommitLint()
}
该文件用来定义项目的编码规范,编辑器的行为会与.editorconfig 文件中定义的一致,并且其优先级比编辑器自身的设置要高,这在多人合作开发项目时十分有用而且必要
.editorconfig.js
,配置👇.editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
eslint
,prettier
对应的插件打开vscode配置文件setting.json
mac系统下command+,
,打开编辑器设置界面,输入框输入setting,打开下面的setting.json文件
{
"editor.formatOnSave": true
}
.js .ts .jsx .tsx .less .css .scss .json
格式文件都采用prettier插件进行格式化 // setting.json部分相关配置
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript|react]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript|react]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"prettier.semi": false,
"prettier.singleQuote": true,
"editor.formatOnType": true,
// 设置默认使用本地配置文件规则
"prettier.configPath": ".prettierrc.js",
"stylelint.config": ".stylelintrc.js",
"[jsonc]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
新增了如下命令👇
npm run lint
: 检查语法等格式,规范不对的js/ts文件npm run lint-fix
: 检查规范不对的文件并修复(语法问题需要手动修复)npm run lint:style
: 列出样式文件风格问题(lint:style中文件需要根据使用的是less或者scss来换)npm run lint-fix:style
: 格式化样式文件npm run changelog
: 列出每次提交的changelog记录npm run changelog:create
: 生成changelog文件package.json script
"script": {
...
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --quiet",
"lint-fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
"lint:style": "stylelint 'src/**/*.less' --syntax less",
"lint-fix:style": "npm run lint:style -- --fix",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -w -r 0",
"changelog:create": "npm run changelog -- -s -r 0"
}
package.json commit的配置
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write",
"git add"
]
},
changelog文件
在安装了依赖包后,运行命令即可生成对应的changelog文件
安装对应依赖包以及新增相关配置文件和在package.json中添加script命令
npm run lint
会展示不符合规范(standrad)的文件列表
npm run lint-fix
会自动进行格式化及风格美化,一些字段规范等需要手动修改
npm run lint:style
会展示css不对的文件列表
npm run lint-fix:style
自动化格式样式文件,极少部分需要手动修改
npm run changelog:create
生成changelog文件,写入更新记录
// 安装changelog包
npm install conventional-changelog-cli
如果你觉得这篇内容对你有帮助的话:
什么是进程?
什么是线程?
多任务并行执行的方式?
很多的场景中,浏览器的渲染、数据请求与用户响应这些需要并行执行的任务,而单线程在运行时只能执行一个任务
解决当前问题的几种方式
多进程
多线程
多进程更轻量,多线程更安全更稳定
总结
进程之间相互独立,但是进程会占用跟多的资源空间。一个进程下可以有多个线程,相互之间可以进行数据通信,相互之间也有影响
Set-Cookie
指定要存储的Cookie值,默认情况下domain被设置为Cookie页面的主机名,也可以手动设置domaind的值Set-Cookie: name=xxx; domain=xcc.com
setItem()
getItem()
removeItem()
clear()
最近一直在使用react native中,遇到了很多的坑,同时也学习到了一些移动端的开发经验。
今天在做一个打包的测试时,遇到了一个问题,打包过程中报错“Error:Error: Duplicate resources”,什么意思呢,就是打包资源有重复,后来查看了一下,发现打包到android/app/src目录下的静态文件重名了。
重现步骤:
1:通过vscode打开项目,运行打包命令
react-native ram-bundle --entry-file index.js --platform android --dev false --bundle-output ./android/app/src/main/assets/index.android.bundle --assets-dest ./android/app/src/main/res/
2:
cd android && ./gradlew assembleRelease
查看android/app/src/mian/res/drawable目录下静态资源图片文件名重复
解决方案
1:打开node_modules/react-native/react.gradle文件,在doFirst块下添加doLast块
doLast {
def moveFunc = { resSuffix ->
File originalDir = file("$buildDir/generated/res/react/release/drawable-${resSuffix}");
if (originalDir.exists()) {
File destDir = file("$buildDir/../src/main/res/drawable-${resSuffix}");
ant.move(file: originalDir, tofile: destDir);
}
}
moveFunc.curry("ldpi").call()
moveFunc.curry("mdpi").call()
moveFunc.curry("hdpi").call()
moveFunc.curry("xhdpi").call()
moveFunc.curry("xxhdpi").call()
moveFunc.curry("xxxhdpi").call()
}
2:打开node_modules/react-native/local-cli/bundle/assetPathUtils.js文件,修改getAndroidAssetSuffix函数方法如下
function getAndroidAssetSuffix(scale: number): string {
switch (scale) {
case 0.75: return 'ldpi-v4';
case 1: return 'mdpi-v4';
case 1.5: return 'hdpi-v4';
case 2: return 'xhdpi-v4';
case 3: return 'xxhdpi-v4';
case 4: return 'xxxhdpi-v4';
}
throw new Error('no such scale');
}
3:删除android/app/src/main/res/drawable-**目录下面打包进去的静态资源文件(文件名会比较长)
4:如果采用android studio进行打包,点击build下clean project,清除打包缓存
5:重新执行打包命令即可打包成功。
参考资料:
在很多时候,我们在本地写一个npm模块时,经常会遇到的一个问题:
新开发或者修改的npm模块,怎么在本地测试?
为了方便描述,比如我们要在webpack-xcc
这个项目中使用一个npm包npm-link-test
很多人在遇到这个问题时,常见的做法有下面几种:
cd app/webpack-xcc
npm install app/npm-link-test
cd app/webpack-xcc/node_modules
ln -s app/npm-link-test npm-link-test
npm link
npm link
是一种把包链接到包文件夹的方式,简单来说,就是可以让你在不发布npm模块的情况下,调试该模块,并且修改模块后会实时生效,不需要通过npm install
进行安装
相关文档:https://docs.npmjs.com/cli/v6/commands/npm-link
npm link
npm link name
npm-link-test
包修改npm-link-test下的index.js内容,会发现项目目录的文件也一起发生变化
在调试完模块后,需要从项目中移除link进行的模块
npm unlink name
// 代码1
// svg
// svg直接写入HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<circle cx="50" cy="50" r="50" />
</svg>
</body>
</html>
// svg写入独立文件引入HTML
<img src='[name].svg' alt='' />
// 代码2
// Base64
// url地址
https://user-gold-cdn.xitu.io/2018/9/15/165db7e94699824b?w=22&h=22&f=png&s=3680
// 引入img
<img src="https://user-gold-cdn.xitu.io/2018/9/15/165db7e94699824b?w=22&h=22&f=png&s=3680">
// base64编码
data:image/png;base64,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
// 代码3
// chrome 支持WebP
<img src="//img.alicdn.com/tps/i4/TB1CKSgIpXXXXccXXXX07tlTXXX-200-200.png_60x60.jpg_.webp" alt="手机app - 聚划算" class="app-icon">
// safari 不支持WebP
<img src="//img.alicdn.com/tps/i4/TB1CKSgIpXXXXccXXXX07tlTXXX-200-200.png_60x60.jpg" alt="手机app - 聚划算" class="app-icon">
问题1:
简历应该选用什么样的模板:
很多的毕业生或者刚入职场的人,会去很多简历网站找些花里胡哨的简历模板,有着深色背景,动效等。很多人觉得炫酷的简历会吸引面试官的注意,但是你要知道,很多面试官一天会看上百份的简历,每天看这样花里胡哨的很容易扰乱面试官提取重要信息的思路,而且每个人的审美也不相同,如果面试官觉得很土,会不会就导致自己浪费了一次机会?
好的简历,应该保持简介干净,最好采用简洁干净的纯色底简历模版,导出简历时最好导出PDF格式或者文本格式,在文件名上最好写上自己的关键信息。比如姓名-手机号-岗位等信息
问题2:
哪些信息是关键的,怎么写不会显的多余:
一份简历肯定要包含自己的个人信息,但是也不是要什么都往上写。比如以前见过有的人的简历个人描述,包含了名字,性别,年龄,籍贯,民族,身份证号,手机号,邮箱,毕业院校,专业,毕业时间,政治面貌,特长,身体状况等
但是面试官是否会在乎你填写的那么多休息呢,一般是不会的(除非你放上了你漂亮的自拍照)。那么我们需要提取关键的信息出来,包含:姓名,年龄,联系方式,毕业院校,专业即可
千万千万记得个人信息不要填写错了,以前遇到过通过邮箱发送offer邮件,求职者说没收到的问题出现,后来才发现是简历邮箱写错了
问题3:
专业技能应该怎么写:
以前看到过别人的简历,技能描述写了下面的这些:
但是在实际的面试中,问出的许多问题,原理并不是特别清楚,可能只是在熟练使用阶段。因此,在写自己的专业技能描述时,千万不要给自己挖坑,千万千万不要写自己都精通
(当然大佬是肯定有的,不过毕竟还是少数),写的东西要自己hold住,不要罗列专业技能名词,应该像下面这样写:
问题4:
工作经历怎么写比较好:
如果是刚刚进入职场的人,写一下大学期间做过什么项目,实习经历,平时的学习方式等
如果是职场老鸟,突出写出最近2-3份工作的项目经历,保证真实性。详细描述出自己做的项目,在项目中承担的职责,遇到的问题,解决问题的思路等。面试官一般感兴趣的是你做了哪些事,遇到过什么问题,思考的过程是怎么样的,怎么解决的问题,带来了什么样的成绩,逻辑清晰,描述准确即可
同时,千万不要简历造假,现在很多大公司都会做背调,一旦发现简历作假,就是诚信问题,后面估计都没机会进去了
问题5:
工作业绩怎么写比较好:
一般写出自己的年度、季度绩效,或者工作中做出的成绩,比如做了某个优化,导致访问量增加了5%,优化了哪些流程,使公司一年节省了100w等即可
问题6:
教育经历怎么写比较好:
一般而言,写出自己的大学信息,专业学习,毕业时间即可,很多人还会把自己的高中,初中等教育经历写进来,其实面试官一般对这也不会感兴趣,毕竟地方性的学习大家也都不了解
如果大学期间获得了什么重量级的奖项,也是可以写的,比如数学,信息学科竞赛国奖等。普通的比如社团干部,奖学金,班干部之类的就不要写了,一般没什么用,只会增加面试官的思考难度
问题7:
个人评价怎么写比较好:
注意,写个人评价的时候,要实事求是,同时表现出自己的特点与优势,别傻乎乎的把自己的缺点之类的都写上去了。好的个人评价可能会有的几个点:
import React, { memo, useCallback, useState } from 'react'
function PageA (props) {
const { onClick, children } = props
console.log(111, props)
return <div onClick={onClick}>{children}</div>
}
function PageB ({ onClick, name }) {
console.log(222)
return <div onClick={onClick}>{name}</div>
}
function UseCallback() {
const [a, setA] = useState(0)
const [b, setB] = useState(0)
const handleClick1 = () => {
setA(a + 1)
}
const handleClick2 =() => {
setB(b + 1)
}
return (
<>
<PageA onClick={handleClick1}>{a}</PageA>
<PageB onClick={handleClick2} name={b} />
</>
)
}
export default UseCallback
import React, { memo, useCallback, useState } from 'react'
function PageA (props) {
const { onClick, children } = props
console.log(111, props)
return <div onClick={onClick}>{children}</div>
}
function PageB ({ onClick, name }) {
console.log(222)
return <div onClick={onClick}>{name}</div>
}
const PageC = memo(PageB)
function UseCallback() {
const [a, setA] = useState(0)
const [b, setB] = useState(0)
const handleClick1 = () => {
setA(a + 1)
}
const handleClick2 = useCallback(() => {
setB(b + 1)
}, [b])
return (
<>
<PageA onClick={handleClick1}>{a}</PageA>
<PageC onClick={handleClick2} name={b} />
</>
)
}
export default UseCallback
sudo vi /etc/hosts
,需要输入电脑密码i
,下面会出现-- INSERT --后就可以输入内容了 # GitHub Start
192.30.253.112 Build software better, together
192.30.253.119 gist.github.com
151.101.184.133 assets-cdn.github.com
151.101.184.133 raw.githubusercontent.com
151.101.184.133 gist.githubusercontent.com
151.101.184.133 cloud.githubusercontent.com
151.101.184.133 camo.githubusercontent.com
151.101.184.133 avatars0.githubusercontent.com
151.101.184.133 avatars1.githubusercontent.com
151.101.184.133 avatars2.githubusercontent.com
151.101.184.133 avatars3.githubusercontent.com
151.101.184.133 avatars4.githubusercontent.com
151.101.184.133 avatars5.githubusercontent.com
151.101.184.133 avatars6.githubusercontent.com
151.101.184.133 avatars7.githubusercontent.com
151.101.184.133 avatars8.githubusercontent.com
# GitHub End
// bad
function filter(type) {
if (type === 1 || type === 2 || type === 3 || type === 4 || ...) {
console.log('条件成立了...')
}
}
// good
const types = [1, 2, 3, 4, ...]
function filter(type) {
if (types.includes(type)) {
console.log('条件成立了...')
}
}
const data = [
{ name: 'a', age: 20 },
{ name: 'b', age: 28 },
{ name: 'c', age: 18 }
]
// bad
function filter(n) {
let isAll = true
data.forEach(({ age }) => {
if (age > n) {
isAll = false
}
})
return isAll
}
// bad
function filter(n) {
const o = data.find(x => x.age > n)
return o ? true : false
}
// good
function filter(n) {
return data.every(x => x.age > n)
}
const data = [
{ name: 'a', age: 20 },
{ name: 'b', age: 28 },
{ name: 'c', age: 18 }
]
// bad
let isAge = false
data.forEach(({ age }) => {
if (age > 25) {
isAge = true
}
})
// good
const isAge = data.some(({ age }) => age > 25)
const data = [
{ name: 'a', age: 20 },
{ name: 'b', age: 28 },
{ name: 'c', age: 18 }
]
// bad
const { name, age } = data.filter(({ age }) => age > 25)[0] || {}
// good
const { name, age } = data.find(({ age }) => age > 25) || {}
// bad
function f(m) {
return m || 1
}
// good
function f(m = 1) {
return m
}
const obj = { a: { b: { c: 1 } } }
// bad
const { a = {} } = obj
const { b = {} } = a
const { c } = b
// good
// 需要配置babel
const m = a?.b?.c
// bad
<div>
{
(this.props.data || []).map(li => <span>{li}</span>)
}
</div>
// good
<div>
{
this.props?.data?.map(li => <span>{li}</span>)
}
</div>
// bad
function pick(type) {
if (type === 1) {
return [0, 1]
} else if (type === 2) {
return [0, 1, 2]
} else if (type === 3) {
return [0, 3]
} else {
return []
}
}
// bad
function pick(type) {
switch (type) {
case 1:
return [0, 1]
case 2:
return [0, 1, 2]
case 3:
return [0, 3]
default:
return []
}
}
// good
// 枚举法
const fn = {
1: [0, 1],
2: [0, 1, 2],
3: [0, 3]
}
const filter = fn[type] ?? []
// good
// map数据结构
const fn = new Map()
.set('1', [0, 1])
.set('2', [0, 1, 2])
.set('3', [0, 3])
const filter = fn[type] ?? []
// bad
return (
<>
{
isCheck ? (
<div>check all</div>
) : null
}
</div>
)
// good
if (!isCheck) return null
return <div>check all</div>
// bad
function sum(num) {
return +num + 1
}
// bad
function sum(num) {
return Number(num) + 1
}
sum('1.1') // 2.1
sum('1.1abc') // NaN
// good
function sum(num) {
return ~~num + 1
}
sum('1.1') // 2.1
sum('1.1abc') // 1
// bad
function async fetchData() {
const res = await getData({})
const { success, response = [] } = res || {}
if (success) {
setData(response)
}
}
// good
function async fetchData() {
try {
const res = await getData({}) ?? []
const { success, response = [] } = res
if (success) {
setData(response)
}
} catch (err) {
console.log('error', const res = await getData({}))
}
}
方式一:使用include或exclude来避免不必要的转译
方式二:通过选择开启缓存将转译结果缓存到文件系统
方式三:对三方库进行处理(node_modules)
方式四:Happypack - 将loader由单线程转为多线程
方式五:构建结果体积压缩
accept-encoding:gzip
// 代码1
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
// 代码2
loader: 'babel-loader?cacheDirectory=true'
// 代码3 - dll配置文件
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: {
// 依赖的库的数组
verdor: [
'prop-types',
'babel-polyfill',
'react',
'react-dom',
'react-router-dom'
]
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js',
library: '[name]_[hash].js'
},
plugins: [
new webpack.DllPlugin({
// DllPlugin的name属性需要和libary保持一致
name: '[name]_[hash]',
path: path.join(__dirname, 'dist', '[name]-manifest.json'),
// context需要和webpack.config.js保持一致
context: __dirname
})
]
}
// webpack.config.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
mode: 'production',
entry: {
main: './src/index.js'
},
output: {
path: path.join(__dirname, 'dist/'),
filename: '[name],js'
},
// dll相关配置
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
// manifest就是第一步中打包出来的json文件
manifest: require('./dist/vendor-manifest.json')
})
]
}
// 代码4
const HapptPack = require('happypack')
// 手动创建进程池
const happyThreadPool = HappyPack.ThreadPool({ size: os.cuus().length })
module.exports = {
module: {
rules: [
...
{
test: /\.js$/,
// 问号后面的查询参数指定了处理这类文件的HappyPack实例的名字
loader: 'happypack/loader?id=happyBabel',
...
}
]
},
plugins: [
...
new HappyPack({
// 这个HappyPack的‘名字’就叫做happyBabel,和上面的查询参数遥相呼应
id: 'happyBabel',
// 指定进程池
threadPool: happyThreadPool,
loaders: ['babel-loader?cacheDirectory']
})
]
}
// 代码5
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
}
// 代码6
// index.js
import { page1, page2 } from './pages'
show(page1)
// pages/index.js
export const page1 = ''
export const page2 = ''
// 打包阶段,未使用文件会被直接删除(主要针对于模块级别的冗余代码)
export const page2 = ''
// 代码7
// webpack3
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
new UglifyJsPlugin({
// 允许并发
parallel: true,
// 开启缓存
cache: true,
compress: {
// 删除所有console
drop_console: true,
// 把使用多次的静态值自动定义为变量
reduce_vars: true
},
output: {
// 删除注释
comment: true,
// 使输出代码尽可能紧凑
beautify: false
}
})
}
// 代码8
// 不按需加载的组件
import BugComponent from './pages/BugComponent'
...
<Route path='/bug' component={BugComponent} />
// 开启按需加载
// webpack配置修改
output: {
path: path.join(__dirname, '/../dist'),
filename: 'app.js',
publicPath: defaultSetting.publicPath,
// 指定chunkFilename
chunkFilename: '[name].[chunkhash:5].chunk.js'
}
// route.js
const getComponent = (location, cb) => {
require.ensure([], (require) => {
cb(null, require('../pages/BugComponent').defaule)
}, 'bug')
}
...
<Route path='/bug' getComponent={getComponent} />
// route.js import(常用方式)
const getComponent = (resolve) => {
return import ('../pages/BugComponent')
}
...
<Route path='/bug' getComponent={getComponent} />
// 核心方法
require.ensure(dependencies, callback, chunkName)
每次下载字体文件后,将下载的字体文件load到DOM节点
async function loadFonts() {
/*
myfont: @font-face对应使用的字体
url: 字体文件对应url
*/
const font = new FontFace('myfont', 'url(myfont.woff)');
await font.load();
document.fonts.add(font);
document.body.classList.add('fonts-loaded');
}
/*
url: 字体文件url
textName: 需要下载的文字
*/
const font = await getFontApi(url, textName)
document.fonts.add(font)
requestIdleCallback
,通过indexedDB
缓存文件indexedDB
缓存到浏览器本地MD5
来判断文件是否发生更改来更新缓存// index.html
* {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
// 排除input与textarea
[contenteditable="true"], input, textarea {
-webkit-user-select: auto!important;
-khtml-user-select: auto!important;
-moz-user-select: auto!important;
-ms-user-select: auto!important;
-o-user-select: auto!important;
user-select: auto!important;
}
iPhoneX底部样式兼容,包括页面主体内容的在安全区域及fix定位下bottom: 0的兼容。参考:iPhone X兼容
h5中拨号,采用window.open('tel:15000000000'),在iOS中不能使用。原因:iOS中不兼容window.open方法,通过window.location.href = 'tel:15000000000'来实现拨号操作
const isIos = function() {
const isIphone = navigator.userAgent.includes('iPhone')
const isIpad = navigator.userAgent.includes('iPad')
return isIphone || isIpad
}
if (isIos) {
window.location.href = 'tel:15000000000'
} else {
window.open('tel:15000000000')
}
<input
id='phone'
type='tel'
maxLength='11'
placeholder='请输入'
onChange={({ target: { value } }) => handlePhone(value)}
onBlur={() => inputOnBlur()}
/>
const check = () => {
setTimeout(() => {
const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop || 0
window.scrollTo(0, Math.max(scrollHeight - 1, 0))
}, 100)
}
const inputOnBlur = () => {
document.getElementById('phone').setAttribute('onblur', check())
setTimeout(() => {
window.scrollTo(0, document.body.scrollTop + 1)
document.body.scrollTop >= 1 && window.scrollTo(0, document.body.scrollTop - 1)
}, 100)
}
npm install react-live-route --save
// routes.js
export const routes = [
{
path: '/',
exact: true,
component: () => <Redirect to='/list' />
},
/*
{
path: '/list',
exact: true,
component: () => <DirectListPage />
},
*/
{
path: '/create',
exact: true,
component: () => (
<PermissionWrap>
<DirectCreatePage />
</PermissionWrap>
)
}
]
// 需要处理的路由列表
export const keepRouter = [
{
path: '/list',
component: DirectListPage
},
{
path: '/demand/list',
component: DemandListPage
}
]
// APP.js
import NotLiveRoute from 'react-live-route'
import { routes, keepRouter } from './pages/routes'
const LiveRoute = withRouter(NotLiveRoute)
<HashRouter>
<>
<Switch>
{routes.map((item, index) => {
return (
<Route
key={index}
path={item.path}
exact
render={props => {
if (canDirectLogin === true) {
return <item.component {...props} />
} else if (canDirectLogin === false) {
return <ForbiddenPage />
} else {
return <Loading.Full />
}
}}
/>
)
})}
</Switch>
{keepRouter.map((item, index) => {
return (
<LiveRoute
key={index}
path={item.path}
alwaysLive
component={item.component}
name={item.path}
/>
)
}
)}
</>
</HashRouter>
// 最终的重定向方法
// 去除重定向地址中的#,同时添加参数connect_redirect
redirectWXAuth = () => {
const { goToPage } = this.state
const url = (goToPage + '').replace('#', '')
const redirectUrl = encodeURIComponent(
`${process.env.REDIRECT_HOST}/login?goto_page=${encodeURIComponent(url)}&bindCode=1`
)
const wechatAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${process.env.WXAPPID}&redirect_uri=${redirectUrl}&response_type=code&scope=snsapi_userinfo&state=STATE&connect_redirect=1#wechat_redirect`
window.location.replace(wechatAuthUrl)
}
在通过flutter run
启动项目的过程中,遇到提示No supported devices connected
的错误,错误的意思为没有找到连接的设备
flutter doctor
,查看设备情况flutter doctor
flutter emulators
,查看可连接设备flutter emulators
flutter emulators --launch <emulator id>
,连接到指定设备,此时会打开一个模拟器// mac端
flutter emulators --launch iOS Simulator
// windows
flutter emulators --launch Pixel_3a_API_30_x86
flutter run
,启动项目,正常运行flutter run
git log
- 查看之前每次的commit记录列表git show
- 查看最近一次已commit的文件修改信息git show commitid
即可查看指定的文件修改详情缓存分类
Expires
和Cache-Control
两个字段来控制的from disk cache
expires: Wed, 11 Sep 2019 16:12:18 GMT
cache-control: max-age=3600, s-maxage=31536000
Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT
If-Modified-Since
字段,为上次返回的last-modified,服务器接收到时间戳后,会对比时间戳与服务器上资源最后修改时间是否一致,来判断资源是否发生变化ETag: W/"2a3b-1602480f459"
if-None-Match
的字符串供服务端对比,格式If-None-Match: W/"2a3b-1602480f459"
HTTP缓存决策指南
// 注册service worker
window.naviagtor.serviceWorker.register('/test.js').then(
function() {
console.log('注册成功)
}
).catch(err => {
console.error('注册失败')
})
// test.js
// 要缓存文件test.html,test.css,test.js
// Service Worker会监听 install事件,我们在其对应的回调里可以实现初始化的逻辑
self.addEventListener('install', event => {
event.waitUntil(
// 考虑到缓存也需要更新,open内传入的参数为缓存的版本号
caches.open('test-v1').then(cache => {
return cache.addAll([
// 此处传入指定的需缓存的文件名
'/test.html',
'/test.css',
'/test.js'
])
})
)
})
// Service Worker会监听所有的网络请求,网络请求的产生触发的是fetch事件,我们可以在其对应的监听函数中实现对请求的拦截,进而判断是否有对应到该请求的缓存,实现从Service Worker中取到缓存的目的
self.addEventListener('fetch', event => {
event.respondWith(
// 尝试匹配该请求对应的缓存值
caches.match(event.request).then(res => {
// 如果匹配到了,调用Server Worker缓存
if (res) {
return res
}
// 如果没匹配到,向服务端发起这个资源请求
return fetch(event.request).then(response => {
if (!response || response.status !== 200) {
return response
}
// 请求成功的话,将请求缓存起来。
caches.open('test-v1').then(function(cache) {
cache.put(event.request, response)
})
return response.clone()
})
})
)
})
// pageA
import './a.css'
<div className='layout-header'>pageA</div>
// a.css
.layout-header {
color: 'red'
}
// pageB
import './b.css'
<div className='layout-header'>pageB</div>
// b.css
.layout-header {
margin: 10px
}
// 方案一实现
<div class='pageA'>
<h4 class='text'>pageA</h4>
</div>
<div class='pageB'>
<h4 class='text'>pageB</h4>
</div>
// 方案二实现
// style.css
.text {
color: 'red'
}
import styles from './style.css'
<div class={styles.text}>方案2</div>
// 编译之后的结果
<div class="_3zyde4l1yATCOkgn-DBWEL"></div>
<style>
._3zyde4l1yATCOkgn-DBWEL {
color: 'red';
}
</style>
// 伪代码
// styles/index.js
const export FONT_24 = 24
const export COLOR_DARK = '#ccc'
...
...
// index.js
import './styles'
.text {
font-size: FONT_24
}
import React, { Component } from 'react'
import { Modal, Button } from 'antd'
class ClassPage extends Component {
constructor(props) {
super(props)
this.state = {
visible1: false,
visible2: false
}
}
render() {
return (
<div>
<Button type='primary' onClick={() => this.setState({visible1: true})}>按钮1</Button>
<Button type='primary' onClick={() => this.setState({visible2: true})}>按钮2</Button>
<Modal
visible={this.state.visible1}
onCancel={() => this.setState({visible1: false})}
>
modal1
</Modal>
<Modal
visible={this.state.visible2}
onCancel={() => this.setState({visible2: false})}
>
modal2
</Modal>
</div>
)
}
}
export default ClassPage
逻辑进行抽取
,类似一个通用的处理函数 import React, { useCallback, useState } from 'react'
import { Modal, Button } from 'antd'
function useTrigger(initialValue = false) {
const [visible, setVisible] = useState(initialValue)
const open = useCallback(() => {
setVisible(true)
}, [setVisible])
const close = useCallback(() => {
setVisible(false)
}, [setVisible])
return {
visible,
open,
close
}
}
function HooksPage () {
const trigger = useTrigger()
const trigger1 = useTrigger()
return (
<div>
<Button type='primary' onClick={trigger.open}>按钮1</Button>
<Button type='primary' onClick={trigger1.open}>按钮2</Button>
<Modal
visible={trigger.visible}
onCancel={trigger.close}
>
modal1
</Modal>
<Modal
visible={trigger1.visible}
onCancel={trigger1.close}
>
modal2
</Modal>
</div>
)
}
export default HooksPage
输入的代码
var name = 'xcc'
console.log(name)
语法分析
// 令牌
Keyword(var)
Identifier(name)
Punctuator(=)
String('xcc')
Identifier(console)
Punctuator(.)
Identifier(log)
Punctuator(()
Identifier(name)
Punctuator())
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.