Coder Social home page Coder Social logo

x-blog's People

Contributors

dependabot[bot] avatar xccjk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Forkers

gggcom-jep mhhard

x-blog's Issues

react hooks 实现一个计时器

react hooks 实现一个计时器

  • endTime对应服务端返回的时间
  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

网络层面和渲染层面层面来解决前端性能优化问题?

网络层面和渲染层面层面来解决前端性能优化问题

从输入URL到页面加载完成,发生了什么?

  1. 首先我们需要通过DNS(域名解析系统)将URL解析为对应的IP地址
  2. 然后与这个IP地址确定的那台服务器建立起TCP连接
  3. 随后向服务器抛出HTTP请求
  4. 服务端处理完请求后,将目标数据放在HTTP响应里返回给客户端
  5. 拿到数据的浏览器对数据进行渲染

切分过程

  1. DNS解析
  2. TCP连接(三次握手)
  3. HTTP请求抛出
  4. 服务端处理请求,HTTP响应返回
  5. 浏览器拿到响应数据,解析响应内容,把解析结果展示给用户
DNS解析
  1. 能不能减少解析次数或者解析前置?浏览器DNS缓存和DNS prefetch
TCP三次握手
  1. 通过长连接,预连接,接入spdy协议
HTTP连接
  1. 减少请求次数和减小请求体积
  2. 服务器越远,一次请求就越慢,部署时把静态资源放在离我们更近的CDN上
浏览器端渲染
  1. 资源加载优化
  2. 服务端渲染
  3. 浏览器缓存机制DOM树的构建
  4. 网页排版和渲染过程
  5. 回流与重绘的考量
  6. DOM操作的合理规避

前端性能优化思维导图

  1. 网络层面

    • 请求过程的优化

      • HTTP请求优化
        • 构建工具性能调优
        • Gzip压缩原理
        • 图片优化
    • 减少网络请求

      • 本地存储
        • 浏览器缓存技术
        • 离线存储技术
  2. 渲染层面

    • 服务端渲染的探索与实践

    • 浏览器的渲染机制解析

      • CSS性能方案
      • JS性能方案
    • DOM优化

      • 原理与基本思路
      • 事件循环与异步更新
      • 回流与重绘
    • 首屏渲染提示:懒加载初探

  3. 经典面试教学

    • 懒加载初探
    • 事件防抖与节流
  4. 性能监测

    • 可视化工具

      • Performance
      • LightHouse
    • W3C性能API

dependencies vs devDependencies?

dependencies vs devDependencies

dependencies与devDependencies是什么

在package中常常可以看到dependencies与devDependencies,但是大家对于这两个有什么深入了解吗?
当你发布一个项目到npm上时,通常会将node_modules添加到.gitignore文件中,是为了避免文件过大导致上传失败
当别人clone你的项目到本地时,并试图运行你的项目时,会发现没有任何效果,这是因为你本地的依赖安装在了你的node_modules上,但是你没有推送到远程仓库上。
解决问题的唯一方法就是别人npm install,他会安装package.json中所有依赖到的库

package.json文件主要存储了项目的一些依赖关系及其他信息

dependencies本意为依赖,devDependencies的本意为开发依赖

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接口服务01-环境搭建

怎么搞一个node接口服务01-环境搭建

背景:最近在做一个公司内部使用的内部监控系统,需要使用node写一些api接口,很久没怎么写node了,就把流程都走了一遍

这个主要针对新人来,写一个详细的入门教程。采用react+express+mongodb

服务器

  1. 选购一台服务器,我自己购买的腾讯云nodejs服务器(没抢到其他的),centos环境,配置信息如下👇:

  1. 配置服务器登录信息,设置登录密码

  1. 采用ssh登录Linux实例

腾讯ssh登录实例文档

// 登录命令
ssh <username>@<hostname or IP address>

// root权限登录服务器
sudo ssh [email protected]

下面这样既代表登录服务器成功

环境安装

因为我的是node服务器,所以node与npm不需要安装,如果是其他类型的服务器,需要安装node与npm

如果比较熟悉Linux命令,那么通过命令安装相关依赖就可以了,如果不是特别清楚,可以通过宝塔面板来进行服务器的可视化操作

需要安装:nvm,pm2,nginx,git,node,npm

安装nginx,pm2,git

sudo yum install -y nginx git pm2

nvm安装

使用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无法连接问题

  1. 通过IPAddress.com查询raw.githubusercontent.com对应的IP地址
  2. 修改hosts文件,在Linux系统通过sudo vi /etc/hosts
  3. 在hosts文件结尾加上查询出来的199.232.96.133 raw.githubusercontent.com

nvm使用

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

到了这一步,就把服务器的环境相关给安装好了,下一节介绍通过宝塔面板来登录及使用服务器

微信小程序中通过腾讯地图进行逆地址解析报错message: "请求来源未被授权, 此次请求来源域名:servicewechat.com"

微信小程序中通过腾讯地图进行逆地址解析报错message: "请求来源未被授权, 此次请求来源域名:servicewechat.com"

在小程序中获取定位具体信息时,不要配置腾讯地图中的WebServiceAPI中的域名白名单什么的,域名配置直接在小程序后台中配置(就是这个货https://apis.map.qq.com),

千万千万不要在设置域名白名单

正确操作是在小程序的后台设置中设置域名

怎么搞一个node接口服务02-手把手教学宝塔面板的安装和Lnmp环境搭建

怎么搞一个node接口服务02-手把手教学宝塔面板的安装和Lnmp环境搭建

什么是宝塔面板

宝塔面板是一款使用方便、功能强大且终身免费的服务器管理软件,支持 Linux 与 Windows 系统。在宝塔面板中,您可以一键配置 LAMP、LNMP、网站、数据库、FTP、SSL,还可以通过 Web 端轻松管理服务器

服务器安装宝塔面板

  1. 按照第一节登录服务器

  2. 输入宝塔安装的命令,如果你选择的是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等信息如果不是会用到的话就去掉勾选

  • 然后点击一键安装就会自动的执行安装的过程了,安装过程非常的简单只需要等待大概几分钟左右我们的网站运行环境就会自动的安装好了,这个时候宝塔面板的安装和LNMP环境的安装就完成了
  • 安装以及登录成功后,还可以进入软件商店安装自己需要用到的包

宝塔面板官方文档

02-编写第一个flutter应用

编写第一个flutter应用

你可以学到的:

  • 怎么初始化一个flutter项目
  • 学习怎么调用外部package
  • 怎么使用热重载
  • 怎么实现有状态的widget
  • 怎么做一个页面路由(router)
  • 怎么实现页面交互
  • 怎么修改应用主题

启动项目的前提是对应的xcode Android Studio环境安装完全

初始化一个flutter项目

通过flutter 命令创建

  1. 查看flutter安装是否成功
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

  1. 初始化创建项目

创建项目名为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.
  1. 运行flutter项目
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=/

通过vscode flutter插件创建项目

  1. 打开vscode,点击扩展,输入flutter,点击install安装flutter依赖
  2. 点击管理->命令面板,输入flutter,选择Flutter: New Application Project
  3. 指定项目的创建文件,然后输入项目名称
  4. 等待一下后,会发现项目创建成功,在项目根目录运行启动命令运行flutter项目
// 启动项目
flutter run

使用外部package

  1. 在项目根目录pubspec.yaml文件中管理依赖,比如新增包english_words
dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.0
  english_words: ^3.1.5
  1. 安装依赖
// 根目录命令行运行下面命令
flutter pub get
  1. 使用依赖
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相关依赖

热重载

在通过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).

有状态的widget

页面路由(router)

通过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(),
)

浏览器如何渲染页面?

浏览器如何渲染页面

从HTML到DOM

  • 浏览器通过HTTP协议请求到的文档内容是字节数据,类似0a 43 6f...
  • 浏览器把得到的字节数据通过‘编码嗅探算法’来确定字符编码(即16进制字节码对应什么字符),然后根据字符编码将字节数据流进行解码,生成字符数据
  • 把字节流数据解码成字符数据的过程称为‘字节流解码’

输入预处理

  • 将解码得到的字符流数据解析为规范化的字符流数据,这个把字符数据进行统一格式化的过程称为‘输入流预处理’

令牌化

  • 第一步:将字符串数据转化成令牌token
    • 遇到script标签的处理方式
      • 如果是script种直接编写代码
        • 解析过程会暂停,JavaScript脚本引擎开始工作,执行结束后再由渲染引擎继续解析
          • JavaScript引擎执行时,渲染引擎会停止工作,由此会导致页面,动画卡顿的性能问题出现
          • 优化点
            • script标签内容放在文档底部渲染
            • 当代码中含有改变文档DOM的操作,比如document.write(),append()等时,会先重新生成字节流,再进行解析
      • 如果是script外部链接
        • 会根据标签属性执行对应的操作
  • 第二步:解析HTML生成DOM树

构建DOM树

  • 浏览器的解析器会创建一个document对象,树的构建阶段,document会作为根节点被修改与扩充
  • 在HTML5标准中,没类令牌对应DOM元素,树的构建器接收到某个令牌时根据令牌对应的DOM元素并将该元素插入到DOM树中

构建流程图

  • 字节 -> 字节流解码 -> 字符 -> 输入流预处理 -> 统一字符 -> 令牌化 -> 令牌 -> 构建DOM树 -> DOM树
  • 3c 86 .. -> <html><head> -> <html><head> -> 开启标签html,开启标签head -> html元素 - body元素 - 文本节点

从CSS到CSSOM

  • CSSOM树 = 自定义样式解析的CSSOM + 浏览器默认样式(User Agent样式)的CSSOM
  • CSSOM树的节点具有继承的特性,就是会先继承父节点样式作为当前样式,然后再进行补充或者覆盖

从DOM到渲染

  • 渲染引擎会先生成两颗树,DOM和CSSOM

构建渲染树

  • 由于DOM树和CSSOM树是相互独立的,在渲染之前会合并为一颗渲染树
  • 合并过程为从DOM树根节点开始遍历,然后在CSSOM树上找到对应的每个节点的样式(CSSOM中是自下而上的进行查找匹配)
    • 某些节点会自动忽略,比如脚本标记,元标记,设置了display: none的不可见节点
    • 需要显示的伪类元素会加到渲染树中
    • 浏览器进行CSS选择器匹配时,是从右到左进行的(自下而上)

布局

  • 渲染树之后,会来计算元素的大小和位置,简称布局
    • 某些操作会导致重复的计算元素的大小和位置,导致回流重绘的出现
  • 布局完成后会输出对应的盒模型,会显示出每个元素的确切位置和大小

绘制

  • 绘制:将渲染树中的每个节点转换成屏幕上实际像素的过程
  • 绘制是有顺序的
    • 对于使用z-index属性的元素,如果没有按照正确的顺序绘制,将导致渲染结果与预期不符
  • 绘制过程第一步会先遍历布局树,生成绘制记录,然后渲染引擎会根据绘制记录去绘制相应的内容

浅谈JavaScript中的this

先说结论

  • 函数被谁调用,this就是谁,如果没有被对象调用,this就是window
  • 通过new声明的构造函数,函数内容的this永远指向函数本身
  • 箭头函数没有this,箭头函数内部的this取决于外部定义时的环境
  • 箭头函数的this指向外层函数的this,如果外层也不存在this,会查找到window

常见的几种this使用场景

声明式

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

new

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

几种改变this指向的方式

call()

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: "男"}

apply()

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: "男"}

bind()

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包时,经常需要来回切换npm源,比较麻烦,特此记录一下操作过程

npm与yarn的区别

yarn

本质还是安装的npm上的包,依赖包的仓库不会变,所以安装下来的包没有什么区别

  • 是新出的一个JS包管理工具,是为了弥补npm的一些缺陷而出现的
  • 相比npm,yarn运行速度更快
  • 离线模式:再次安装某个包会从缓存中获取
  • 安装版本统一:通过yarn.lock文件来记录安装版本
  • 更简洁的输出
  • 并行安装

npm

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当前镜像源
  npm config get registry
  // 设置npm镜像源为淘宝镜像
  npm config set registry https://registry.npm.taobao.org/
  • yarn
  // 查看yarn当前镜像源
  config get registry
  // 设置yarn镜像源为淘宝镜像
  yarn config set registry https://registry.npm.taobao.org/

通过nrm管理镜像

  • 全局安装nrm
  npm install nrm -g
  • 查看镜像列表
  nrm ls

  • 切换镜像
  nrm use yarn
  • 添加镜像
  nrm add yarn https://registry.yarnpkg.com/
  • 删除镜像
  nrm del yarn
  • 镜像测速
  nrm test yarn
  • 查看更多nrm命令

nrm文档

npm包download-git-repo 报 'git clone' failed with status 128

npm包download-git-repo 报 'git clone' failed with status 128

最近在通过download-git-repo包来拉取不同的项目模板,download-git-repo npm

看了一下使用文档,发现非常的简单明了,适用于githubgitlab

// 安装
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')
})

visibilityChange api的使用场景及在react中的使用

visibilityChange api的使用场景

场景

  • 最近在做网页版视频编辑器相关的工作,页面视频会自动重复的播放,但是发现在页面切换后,音视频还是在自动播放,感觉非常不合理
  • 在没有在当前页面停留时,页面还是不停的请求资源,造成了很大的浪费
  • 后面就发现visibilityChange能够很好的解决这个问题,是用来判断是否在当前页面

visibilityChange api 文档

  • mdn文档
  • 提供了您可以观察的事件,以便了解文档何时可见或隐藏,以及查看页面当前可见性状态的功能
  • HTML5中的新特性,如果需要兼容老版本浏览器慎用

demo

  // 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

其他可预测的使用场景

  • 音视频的播放
  • 页面中的轮询操作,比如常见的打包通过轮询接口实时展示打包报文,切换页面后也还在执行,造成资源浪费
  • 轮播图是否切换
  • 统计页面停留时长、在线时长及浏览量
  • 聊天状态及任务完成的通知

怎么高效的操作DOM元素?

如何高效操作DOM元素

什么是DOM

  • DOM,文档对象模型,比如下面的前端功能
    • 动态渲染列表,表格表单数据
    • 监听点击,提交事件
    • 懒加载脚本或样式文件
    • 实现动态展开树组件,表单级联等复杂操作
  • DOM组成
    • DOM节点
      • 标签是HTML的基本单位
      • 节点是DOM树的基本单位,有多种类型,比如注释节点,文本节点
      • 元素是节点的一种,与HTML标签相对应
      • <p>xcc</p>
        • p是标签,生成DOM树时会产生两个节点,一个是元素节点p,一个是文本节点xcc
    • DOM事件
    • 选择区域
      • 一般用于富文本编辑类业务

为什么说DOM操作耗时

浏览器的工作原理

  • 浏览器包含渲染引擎+JavaScript引擎,两者都是单线程的方式运行,单线程优势为开发方便,避免多线程下死锁,竞争,缺点为失去了并发能力
  • 浏览器为了避免两个引擎同时修改一个内容造成渲染结果不一致的情况,增加了一个机制,及两个引擎具有互斥性,在某个时刻只会有一个引擎在运行,另外一个在阻塞状态。操作系统在进行线程切换时,需要先保存上一个线程的状态信息然后去读取下一个线程的状态信息,俗称上下文切换,这个操作会比较耗时
  • 每次DOM切换就会引发线程的上下文切换,及从JavaScript引擎切换到渲染引擎并执行对应操作,然后继续切换回JavaScript引擎,这样就会带来性能损耗
   // 案例
   // 当数组长度比较长时,会比较耗时
   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,然后进行排布

    • 如果在操作DOM时,对元素,样式进行了修改,会引起渲染引擎的重新计算样式生成CSSOM树,可能会触发对元素的重新排布和重新绘制
  • 重排一定会引起重绘,但是重绘不会导致重排

  • 可能会影响带其他元素排布的操作就会引起重排,继而引发重绘

    • 修改元素边距,大小
    • 添加,删除元素
    • 改变窗口大小
  • 会引起重绘的操作

    • 设置背景图片
    • 修改字体颜色
    • 改变visibility属性值

如何高效操作DOM

循环外操作元素

   // 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')

批量操作元素

  • 场景:在ul下创建1000个li
    • bad: 在循环中创建元素,直接往父元素中添加
    • 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')

缓存元素集合

  • 对上面创建的1000个元素进行修改,使用选择器获取元素时,缓存获取元素的方法
   // 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
   }

总结

  1. DOM渲染流程
  2. DOM耗时元素
  3. 常见解决渲染耗时的方法

其他提升渲染性能的方式

  • 尽量不要使用复杂的匹配规则和复杂的样式,从而减少渲染引擎计算样式规则生成CSSOM树的时间
  • 尽量减少重排和重绘的影响区域
  • 使用CSS3特性来实现动画效果

git同时在github与gitlab中使用?

  1. git bash 运行命令ls -al ~/.ssh,查看.ssh文件下是否有生成秘钥文件.ssh文件列表
  2. 如果目录下没有.ssh文件,使用mkdir ~/.ssh生成.ssh文件,同时chmod 700 ~/.ssh给权限
  3. 如果有.ssh文件,运行cd ~/.ssh到.ssh文件下,输入ls命令查看秘钥文件
  4. 生成秘钥文件
  // 生成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两个文件
  1. 运行ls命令应该就可以看到生成的秘钥文件了,把id_rsa_github.pub与id_rsa_gitlab.pub文件分别存放到github与gitlab的Settings中的SSH Keys中即可
  2. 配置.ssh文件下的config文件
  // 进入.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
  1. 运行命令sudo ssh-add ~/.ssh/id_rsa_gitlabsudo ssh-add ~/.ssh/id_rsa_github,一定要执行
  2. 运行命令sudo ssh -T [email protected]sudo ssh -T [email protected],测试一下是否成功

怎么发布一个npm包?

怎么发布一个npm包

  • 创建一个npm账号,一般用邮箱与密码注册登录即可。npm注册
  • 创建一个文件夹,比如名称为xcc
    • mkdir xcc && cd xcc
    • npm init
    • 填入你希望的包的名称与版本号即可,假如包的名称为xcc-standrad-eslint,版本为0.1.0
  • xcc文件夹创建index.js文件,随便写点内容即可
  • 如果本机第一次发布npm包
    • npm adduser
    • 会要求填入用户名与密码,还有邮箱等信息,填写注册npm时的账号即可
    • 信息正确后会出现Logged in as xxx on https://registry.npmjs.org/.
  • 如果不是第一次发布npm包
    • npm login
    • 登录过程同上
  • 发布npm包
    • npm publish即可
    • 每次更新内容记得改版本号,不然会发布失败的
  • 撤销已发布的npm包

参考文章

微信H5出现重复授权窗口跳转问题?

微信H5出现重复授权窗口跳转问题

场景与问题描述

  1. Android机上,主要在低版本Android机上,客户打开微信H5,页面出现多次弹出授权窗口,需要点击多次确认才会消失
  2. 微信授权主要用于获取code码,用于微信支付时使用

原因分析

  1. 原因分析:多次重定向导致出现多次授权
  2. 重定向方法
  // 代码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)
  }
  1. 经过查阅资料,发现有两种情况下可能会出现重复授权的问题
    • hash模式路由导致的参数丢失
      • (微信中hash模式路由中#字符后面参数丢失)[https://developers.weixin.qq.com/community/develop/article/doc/000aec57ac4e086c893af49145b813]
    • 授权链接参数错误
      • (微信官网授权文档)[https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#0]
      • (connect_redirect参数的影响)[https://blog.csdn.net/jiangguilong2000/article/details/79416615]
  // 代码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来解决路由跳转
  1. 最终结果,去除重定向地址中的#,同时添加参数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)
  }

HTTP协议的那些事?

HTTP协议的那些事

什么是HTTP协议?

HTTP协议(超文本传输协议)是浏览器端与服务端之间最主要的通信协议

HTTP/0.9版本

  1991年出现,作用是用来传输超文本内容HTML

传输方式

  为客户端发起请求,服务端响应请求的通信方式: 客户端 -> GET/index.html -> 服务端 -> <html>...</html> -> 客户端

HTTP/0.9的不足

  只能用来传输文本内容,随着新技术的发展,浏览器希望通过HTTP来传输基本,样式,图片,音视频等其他类型文件

HTTP/1.0

核心改变

  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中,每进行一次通信,都需要建立连接、数据传输、断开连接三个阶段,在连接过多时,不停的建立连接和断开连接会增大网络开销

HTTP/1.1

核心改变

  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... -> ... ->客户端

TCP协议

背景

  因为HTTP是基于TCP实现的,TCP建立连接以及断开的过程,也就是常常说的'三次握手'和'四次挥手'

三次握手

  • 第一次握手: 客户端处于CLOSED状态,服务端处于LISTEN状态,客户端给服务端发送一个SYN报文,并指明客户端的初始序列号ISN,此时客户端处于SYN_SEND状态
  • 第二次握手: 服务端接收到客户端SYN报文,然后以自己的SYN报文作为应答,并指定自己的初始化序列号ISN。同时会把客户端ISN + 1作为ACK值,表示自己已经收到了客户端的SYN,此时服务器处于SYN_REVD状态
  • 第三次握手: 客户端收到SYN报文后,会发送一个ACK报文,同时把服务器的ISN + 1作为CK,表示已经收到服务端SYN报文,此时客户端状态为ESTABLISHED状态。服务端收到ACK报文后,也处于ESTABLISHED状态,此时双方成功建立起了连接

为什么需要进行三次握手?

  • 第一次握手: 握手成功让服务端知道客户端具有发送能力
  • 第二次握手: 让客户端知道服务端具有接受和发送能力
  • 第三次握手: 让服务端确认客户端收到了自己发送的消息

四次挥手

客户端和服务端断开连接时要发送四次数据,这个过程称为四次挥手

  • 第一次挥手: 挥手之前确认客户端与服务端都处于ESTABLISHED状态,客户端发送一个FIN报文,用来关闭客户端到服务器的数据传输,此时客户端状态为FIN_WAIT_1状态
  • 第二次挥手: 当服务端收到FIN后,会发送ACK报文,并且把客户端的序列号值加1作为ACK报文的序列号值,表示已经收到客户端的报文,此时服务端的状态为CLOSE_WAIT
  • 第三次挥手: 如果服务端同意关闭连接,会向客户端发送一个FIN报文,并且指定一个序列号,此时服务端处于LAST_ACK状态
  • 第四次挥手: 客户端收到ACK后,出FIN_WAIT_2状态,待收到FIN报文时发送一个ACK报文作为答应,并且把服务端的序列号值加1作为自己ACK报文的序列号值,此时客户端处于TIME_WAIT状态,等待一段时间后会进入CLOSED状态,当服务端收到ACK报文后,也会变为CLOSED状态,此时连接正式关闭

为什么建立连接只通信了三次,而断开连接却用了四次?

  • 服务端收到客户端FIN报文后,发送的ACK报文只是用来应答的,并不表示服务端也希望立即关闭
  • 只有服务端把所有报文都发送完,才会发送FIN报文,告诉客户端可以断开连接

HTTP/1.1的不足

  虽然通过长连接减少了大量创建/断开连接造成的性能损耗,但是它的并发能力受到限制,所以传输性能还有很大的提升空间

HTTP/2

并发能力受限原因

  • 浏览器为了减缓服务器的压力,浏览器限制了同一个域名下HTTP连接数为6-8个,所以在HTTP/1.1下很容易看到资源文件等待加载的情况,对应的优化方式就是使用多个域名来加载资源
  • HTTP/1.1本身的问题,虽然HTTP/1.1使用了持久连接,但是多个请求公用一个TCP连接,一个连接中同一时刻只能处理一个请求,在当前的请求没有结束之前,其他的请求只能处于阻塞状态,这种情况称为'队头阻塞'

HTTP/2解决问题的方式?

  • 多路复用
    • 收益于二进制帧,对于同一个域,客户端只需要与服务端建立一个连接即可完成通信需求,也就不受限制于浏览器的连接数了
  • 二进制帧
    • HTTP/2中不在使用ASCII编码传输,而是改为了二进制数据
    • 客户端发送请求时会将美国请求的内容封装成不同带有编号的二进制帧,然后将这些帧同时发送给服务器
    • 服务器接收到数据后,会将相同编号的帧合并为完整的请求信息
    • 服务端返回的结果,客户端接收的结果也遵循这个帧的拆分与组合的过程
  • 压缩头部信息来减少传输体积
  • 通过服务推送来减少客户端请求

HTTPS原理

  • HTTP满足了通信的要求,但是这种使用明文发送数据的方式存在着一定的安全隐患,通信内容很容易被三方截取甚至篡改
  • HTTPS = HTTP + 加密 + 证书机制

对称加密

  • 对通信数据进行加密
  • 对称加密在加密解密过程中使用同一个秘钥
  • 性能更好
  • 每次通信秘钥会随机生成

非对称加密

  • 不能保证客户端与服务端同时生成一个相同的秘钥,所以生成的随机秘钥要被传输,对秘钥进行加密
  • 客户端通过公钥来加密,服务端利用私钥来解密
  • 性能相对差些

证书机制

  • 一个服务端会对应多个客户端,所以应该有服务端生成秘钥,发送公钥到客户端建立连接
  • 发送公钥到客户端的过程也会有安全问题
  • 把公钥放入一个证书中,该证书包含服务端的信息,比如颁发者、域名、有效期,为了保证证书是可信的,需要由一个可信的第三方来对证书进行签名。这个第三方一般是证书的颁发机构,也称CA

HTTP/3

HTTP1.1与HTTP2存在的问题?

  • 在客户端与服务端通信出现丢包时,会阻塞TCP的通信
  • HTTP2中,采用二进制帧进行多路复用,一般只有一个TCP连接进行传输,在丢包或者网络问题时,后面的数据都会被阻塞
  • HTTP1.1中,可以开启多个TCP连接,任何一个TCP出现问题都不会影响其他的TCP

HTTP/3的解决方式?

  • 底层依赖由TCP改为UDP
  • UDP传输时不需要建立连接,可以同时发送多个数据包,效率很高
  • 缺点是没有确认机制来保证对方一定能收到数据

总结

协议版本 解决的核心问题 解决方式
0.9 HTML 文件传输 确立了客户端请求、服务端响应的通信流程
1.0 不同类型文件传输 设立头部字段
1.1 创建/断开 TCP 连接开销大 建立长连接进行复用
2 并发数有限 二进制分帧
3 TCP 丢包阻塞 采用 UDP 协议

ES6之let和const?

ES6之let和const

var

  • es6之前通过var声明变量,会出现变量提升的情况出现
  • 通过var声明的变量会绑定到全局的window上
  // 代码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

块级作用域

  • 没有作用域的提升
  • 无法重复声明
  • 无法在定义前使用
  • 不会绑定到全局window
  • let声明的变量可以进行修改,const声明的变量不能修改,但是当const声明的为复杂数据结构(obj, array等)的数据时,可以修改内部引用

let

  // 代码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

const

  • const用来声明常量时,值不可以被修改,值类型为number,string,boolen,undefined,null...
  • const声明不允许修改绑定,但是允许修改值,值类型object,array...
  // 代码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 }

临时性死区

  • 临时死区(Temporal Dead Zone), 简称TDZ
  • 通过let和const声明的变量,会产生一个块级作用域,变量不会被提升到作用域顶部,在声明之前就访问变量,会导致报错
  • 原因:JavaScript引擎在扫描到变量声明时,要么将变量提升到作用域顶部,要不就会将声明放在TDZ中,访问TDZ中的变量会导致运行错误,只有执行过变量声明语句,变量才会从TDZ中移除
  // 代码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
  })()

循环中的作用域

  • for循环语句中,()层为一层作用域,{}层为一层作用域
  • for..in循环中,每次迭代不会修改已有绑定,而是会生成一个新的绑定(代码21)
  // 代码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

通关面试求职01-怎么从海选中脱颖而出

通关面试求职01-怎么从海选中脱颖而出

面试的那些事儿

相信大家在求职的过程中,一定遇到过下列的几种情况:

  1. 简历被用人公司查看,但是没有反馈
  2. 面试很顺利但是用人公司迟迟没给结果
  3. 面试了很多公司,但是一直没有找到满意的工作

那么在平时的求职过程中,怎么避免出现上述的问题呢

问题1:

简历被查看,但是用人公司一直没有给反馈,当遇到这种情况时,需要从下面几个情况来反思这个问题

  1. 自己投递岗位的时间段是否正确。有的大型公司岗位每天会有很多人投递,当简历一直没有被查看时,想拉钩与boss这种平台都可以给别人发送信息提醒HR来查看简历
  2. 自己与岗位的契合度高不高。比如别人要招聘java开发,但是你主要是使用的其他语言,别人通过你的在线简历介绍,感觉岗位匹配度不高,也有可能出现上述情况
  3. 公司只是挂着岗位,但是实际没有招人(我确实遇到过这种情况,岗位常年挂着)

问题2:

面试感觉很顺利,但是公司迟迟没有给出offer,有可能是下面几种情况:

  1. 很多人自己面试的时候感觉良好,但是实际情况一般
  2. 岗位匹配度不是特别高,用人公司希望招聘专业性更强的。比如我现在所在的公司,做视频软件相关,前端岗位就要求对动画相关特别熟悉
  3. 用人公司还在挑选性价比最高的人选。一般公司的岗位同时会有很多人来面试,会发出多个意向offer,需要在几个人里面选择一个性价比最高的人选
  4. 面试时间没通过,用人公司也懒得给反馈(大部分小公司都是这个情况)

问题3:

面试了很多公司,但是一直没有满意的,有可能是下面几种情况:

  1. 个人能力是否达到了公司要求的水平,面试之前准备是否充分,面试过程表现是否比较好
  2. 需要对自己的能力有一个比较清晰的认知,对当前能力水平已经感兴趣的行业及薪资水平要有个清楚的了解。比如面试的一个小公司web高级工程师的岗位,薪资范围y一般只能给到15-25k左右,这时你的期望薪资在30+时,肯定是不会合适的

怎么提高求职成功率,从海选中脱颖而出

  1. 个人专业能力
  2. 举止谈吐,沟通交流
  3. 找熟人内推或者猎头内推
  4. 写一份好的简历,突出自己的优势,不造假
  5. 怎么优雅的和前司和平分手

高阶函数的常见使用方式?

高阶函数

使用方式

  • 在导出函数上面使用@hoc@HOC function() {}
    • @hoc语法通过es7中decorator来实现的,需要配置babel来兼容才可以使用
  • 导出时使用HOC进行包裹,export default HOC(HocComponent)

高阶函数使应用场景

  • 目的:把常用的逻辑独出来进行多次复用
  • 业务场景:在一个多页H5中,部分页面有查看权限,比如新闻列表,详情,不需要进行任何操作就可以看。但是比如活动抽奖页,如果想参加就需要验证手机号登录,当一些新用户通过分享出去的链接进入活动页时,需要弹出手机号登录验证消息弹窗,同理也有其他的页面需要弹出这个弹窗
  • 常规方式:每个需要弹出弹窗的页面都写一遍逻辑,但是当页面数比较多时,就比较麻烦了,而且代码有很多重复的
  • 采用HOC:编写一个携带弹窗逻辑的高阶函数,在每个需要出现弹窗的页面通过高阶函数包裹一下需要导出的页面组件,即可在页面组件中调用弹窗出现的方法

原理

  • 属性代理方式原理
    • 向被处理的组件上添加独立出来的属性与方法,并返回一个包含原组件的新组件

高阶组件使用注意事项

  • 不要在render方法中使用HOC
  • 复制静态方法
  • refs不会被传递

示例

  • 当前示例中,高阶函数定义了属性与方法,并传入到了新导出的组件中,可以在被高阶函数包装的组件中直接调用高阶函数内部的属性与方法,比如在HocComponent组件中调用高阶函数HOC中的add方法
  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

react native中使用echarts

开发平台: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

somonus/react-native-echarts#32

somonus/react-native-echarts#122

react native报错处理com.android.build.api.transform.TransformException: com.android.builder.dexing.DexArchiveBuilderException: com.android.builder.dexing.DexArchiveBuilderException: Failed to process

背景:最近准备在使用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'时,会导致资源重复加载,导致打包失败。

react native错误排查-TypeError: window.deltaUrlToBlobUrl is not a function

react native错误排查-TypeError: window.deltaUrlToBlobUrl is not a function

错误现象: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关闭模拟器远程调试,再次打开模拟器远程调试。

几个DOM元素常见的优化场景?

DOM事件

  • 常见的三种常见,防抖,节流,代理

防抖

  • 场景:输入框输入信息,对输入信息做实时检索,通过接口与后端进行交互,不需要通过点击搜索按钮完成检索
  • 问题:在input的onchange事件中进行实时请求,当输入框输入发生改变时就会发送一次请求。比如输入react
    • 在输入r时,接口发出请求,输入re时,发出请求,输入rea时,发出请求,输入reac时,发出请求,输入react时,发出请求
    • 可是这个输入中,只有输入react时发出的一次请求是有效的
  • 解决方式:添加防抖功能,设置一个合理的时间间隔,避免时间在时间间隔内频繁触发,同时又能保证输入后可以看到结果
  // 代码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
  }

节流

  • 原理:设置在指定的一段时间内,只调用一次函数,重而降低函数的调用频率
  • 场景1:改变浏览器窗口大小事件监听
  • 场景2:左右布局页面,左侧为目录,右侧为内容,内容滚动时左侧目录当前项高亮

代理

  • 对具有相同父节点,事件监听函数逻辑一致,只是参数不同时,通常会采用事件代理和时间委托来优化
  • 场景:业务中常见的列表,编辑或者删除操作,除了传入的每条数据的id不同,一般其他的都一致

事件触发流程

  1. 捕获阶段:事件对象window传播到目标的父对象,红色过程
  2. 目标阶段:事件对象到达事件对象的时间目标,蓝色过程
  3. 冒泡阶段:时间对象从目标对象的父节点开始回到window,绿色过程

DOM事件标准

  • 下面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)
      }
    }
  })

webpack5升级过程遇到的一些坑?

webpack5升级过程遇到的一些坑

版本相关信息

  • node: v14.15.0
  • npm: 6.14.8
  • mac: 10.14.6
  • webpack: 5.10.3
  • webpack-cli: 4.2.0
  • webpack-dev-server: 3.11.0
  "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'
        }
      ]
    ]
  }

问题

  1. 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]
  }
  1. 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的功能已经内置
  1. babel配置导致的文件引入错误,@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
  1. 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
      }
    }
  }
  1. DeprecationWarning: Compilation.assets will be frozen in future, all modifications are deprecated. BREAKING CHANGE: No more changes should happen to Compilation.assets after sealing the Compilation.
    Do changes to assets earlier, e. g. in Compilation.hooks.processAssets.
    Mak`e sure to select an appropriate stage from Compilation.PROCESS_ASSETS_STAGE_*.

html-webpack-plugin这个包导致的⚠️信息,github issue

  npm run html-webpack-plugin@next -D
  1. DeprecationWarning: A 'callback' argument need to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback

官方给出的问题原因是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"
  1. 业务插件遇到的问题,webpack-merge包遇到的问题

csdn资料

  // 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
  1. 数据流工具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

  1. 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([])
    ]
  }

webpack升级日志

webpack模块热更新?

webpack热更新

什么是浏览器的热更新

  • webpack和webpack-dev-server
  • 热更新包含什么
    • 保存之后自动编译(Auto Compile)
    • 自动刷新浏览器(Live Reload)
    • 模块热替换HMR(Hot Module Replacement)
  • 什么是浏览器的热更新
    • 指在本地开发过程中同时打开浏览器进行效果预览,在修改代码后,浏览器自动更新页面内容。
    • 自动更新在表现上也分为:自动刷新整个页面,以及整体无刷新而只更新页面内容

手动模式

  // 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文件,命令行终止

watch

  // 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文件
  • 存在的问题:虽然在修改代码后实时编译了,但是浏览器中的内容并没有发生改变

Live Reload

  // 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文件
  • 通过设置devServer open,浏览器自动打开localhost:8080/index.html
  • 修改1.js,浏览器页面内容发生改变

原理

  • 通过websocket服务,对打开的网页和本地服务间建立持久化的通信

问题

  • 场景:调试位于某个靠后的流程,保存了修改后页面刷新了,需要重新操作一遍

Hot Module Replacement - 模块热替换

  • 为了解决页面刷新导致的状态丢失问题
  // 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"
  }
  • 安装css解析npm install style-loader css-loader
  • css-loader是将文件转换为模块,style-loader是将css内容添加到页面的style标签
  • 运行npm run dev:hmr,浏览器查看发现head下面多了个style标签
  • 修改style.css内容,浏览器中也发生变化
    • 浏览器network中新增两个请求:.hot-update.json.hot-update.js
    • 不会刷新页面重载所有请求

问题

  1. 为什么修改js不能模拟出热替换的效果?
  • 修改js文件时,也会生成.hot-update.json.hot-update.js,然后就会重载所有请求

webpack热更新原理

  • watch: 对本地代码内容改变的监听
  • reload: 浏览器端与本地服务器端的websocket通信
  • hmr: 模块解析与替换功能

webpack的打包流程

  • module: 模块化编程中把程序分割成独立的功能与模块
  • chunk: 按模块间引用关系组合成的代码块,一个chunk可以包含多个module
  • chunk group: 配置的入口点(entry point)区分的快组,一个chunk group包含一个到多个chunk
  • buildling: webpack打包过程
  • asset/bundle: 打包产物

打包**

  • 一切源代码都可以通过各种loader转换为js模块(module),模块间可以相互引用
  • webpack通过入口点(entry point)递归处理各模块引用关系,最后输出为一个或多个产物文件(bundle)
  • 没一个入口点都是一个快组(chunk group),在不考虑分包的情况下,一个chunk group中只有一个chunk,该chunk包含递归分析后的所有模块。每一个chunk都有一个打包后的输出问题(asset/bundle)

通关面试求职03-读懂JD,精准投递

通关面试求职03-读懂JD,精准投递

求职工作四部曲

  1. 先找自己喜欢或者比较了解的行业,根据自身水平找到不同层级的公司
  2. 找到理想公司的岗位JD,清晰了解用人部门的招聘要求,并且做针对性的面试前准备
  3. 公司规模、发展前景、福利待遇、团队氛围、技术水平是否达到自己的预期
  4. 不浪费每一次机会,因为很多公司有人才简历库,以后的面试会参考之前的面试记录。同时也不浪费自己的精力,把目标放在自己感兴趣的公司上面

怎么读懂一份企业的JD

下面来看一份高级前端工程师招聘的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,可以发现几个关键的信息:

  1. 学历要求-本科
  2. 工作年限-3年左右
  3. 精通前端开发的常用技能,对于框架层面没有强制要求
  4. 良好的沟通交流能力
  5. 加分项-有维护的github或者博客,有测试相关经验

因此,假如你是下面的几种情况,可能需要想一下再进行投递:

  1. 学历没有达到本科水平,工作年限未达到2年+
  2. 基础知识一般,还停留在框架的使用层面
  3. 平时一般不会注重总结归纳遇到的问题,技术热情不足

还有些工作岗位的信息,也需要在面试之前考虑清楚:

  1. 有的JD上会有独立负责某项产品,表示需要独立完成项目,并且可能还需要带着别人,因此假如你还需要别人带着工作时,要先考虑清楚是否投递
  2. 有的JD上会有自驱动,抗压能力强,表示工作的压力比较大,比如常见的996福报等,需要自己在找工作时,想清楚自己是否愿意过这样的生活

同时也非常推荐在决定去某个公司之前,如果有熟人在里面,最好先问清楚里面的情况再决定。同时也可以去知乎脉脉等求职平台上看公司的工作情况,比如公司是否有裁员、降薪、福利、工作环境、团队氛围、技术水平等多方面进行考量,来决定自己是否投递已经投递后的面试准备以及面试通过后的入职情况

01-flutter环境搭建

mac flutter环境搭建

安装国内镜像

export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

下载flutter

  1. 下载flutter
  1. 进入你想放置flutter的目录
cd ~/development
  1. 解压下载文件,记住改为你自己下载的版本号,按Y确认即可
unzip ~/Downloads/flutter_macos_v0.5.1-beta.zip

  1. 将flutter添加到path中
export PATH=`pwd`/flutter/bin:$PATH

此时你会发现,你打开一个新的命令行窗口是,运行flutter doctor,会报flutter不存在的错误,是因为环境变量还没全局生效

运行flutter

  1. 运行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插件未安装

  1. xcode安装
  • 在app store搜索xcode,安装即可
  • 运行下面命令
  • 再次运行flutter doctor会发现关于xcode的错误消失了
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch
sudo gem install cocoapods
  1. Android Studio安装

  1. VS Code中flutter插件安装
  • 打开vscode,点击扩展,输入flutter,install安装,安装之后重新打开vscode即可

  1. 再次运行flutter doctor,关于Android Studio相关的错误可能还是有,运行下面的命令
// 注意AndroidStudio版本号
ln -s ~/Library/Application\ Support/Google/AndroidStudio4.1/plugins ~/Library/Application\ Support/AndroidStudio4.1
  1. 再次运行flutter doctor,下面这样就都正常了

环境变量配置

  • 查看flutter的环境配置路径path
echo $PATH
// 可以看到flutter的路径为
/Users/name/development/flutter/bin:

  • 打开环境配置文件
open ~/.bash_profile
  • 或者vim编辑
vim ~/.bash_profile
  • 如果在编辑bash_profile时发现文件不存在
// 创建bash_profile文件
touch .bash_profile
  • 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

怎么打造适合前端团队的代码规范?

前言

在现在的前端开发中,项目常常由多个人共同维护,但是由于每个人的代码风格、提交规范方面、编辑器设置等各方面的原因,导致提交上去的代码风格没有保持统一,常见的有下列几个问题:

  • 缩进、换行、命名、结尾逗号等规则...
  • 样式文件属性的先后顺序规则
  • commit时文案,比如曾经见过git commit -m 'dddddd'等commit记录
  • 每次迭代的changelog记录
  • ...

为了保证代码风格统一,便于后期的维护,我们需要使用检查工具来检查自己的代码,常见的几个检查场景:

  • 代码规范:包含编写时的命名,缩进规则,换行规则等...
  • 样式规范:包含css/less/scss等样式文件属性先后规则,命名
  • 文档规范:包含迭代的changelog记录,当前迭代是新增功能,还是bug修复,还是文档更新
  • 提交规范:包含提交代码时commit的规范,是什么类型的提交,文案
  • 语言配置:js/ts的环境配置
  • 编辑器配置:编辑器环境设置
  • ...

代码已上传到githubgithub

项目背景

在之前的业务场景下,平时配置一些代码检查规范,为了拥有对应的检查功能,需要分别安装很多三方包,新建配置文件来达到效果,并且想在其他项目中也配置相同的配置时,需要重复安装众多的包,而且还难以保证包版本一致,导致不同项目间可能存在差异

打造统一规范代码插件

目的

  • 简化配置:只需安装几个指定的包即可(只是对相关三方包做了统一处理配置)
  • 方便接入:对于老项目,添加对应配置后,值需要运行几个命令就可以达到格式化代码的要求
  • 使用方便:每次编辑保存时会自动格式化代码(需要编辑器配合,后面会将vscode中的配置)

使用方式

安装相关依赖

  • 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相关

官网解释:eslint为一个插件化的JavaScript工具,目标是保证代码的一致性和避免错误

  • 安装eslint相关包
  npm install xcc-standard-eslint eslint-plugin-react eslint
  • 项目根目录新建.eslintrc.js,配置👇

.eslintrc.js

  • useTs是否为typescript编写,默认false
  • ignorePatterns需要排除的风格转换文件夹
  • 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是一个流行的代码格式化工具的名称,它能够解析代码,使用你自己设定的规则来重新打印出格式规范的代码

  • 安装prettier相关包
  npm install xcc-standard-prettier
  • prettier格式化后的效果

【prettier官网使用案例】

  • 项目根目录新建.prettierrc.js,配置👇

.prettierrc.js

  const { getPrettier } = require('xcc-standard-prettier')

  module.exports = {
    ...getPrettier()
  }

.prettierignore

排除不需要进行代码美化的文件

  .dll
  build/
  .temp
  node_modules
  .vscode

stylelint相关

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的提交

  • 安装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

vscode配置

  • 在vscode中安装eslintprettier对应的插件

  • 打开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"
  }

package.json中相关配置

新增了如下命令👇

  • 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文件

  • build:主要目的是修改项目构建系统(例如 glup,webpack,rollup 的配置等)的提交
  • ci:主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle等)的提交
  • docs:文档更新
  • feat:新增功能
  • feature:新增功能
  • fix:bug 修复
  • bugfix:bug 修复
  • perf:性能优化
  • refactor:重构代码(既没有新增功能,也没有修复 bug)
  • style:不影响程序逻辑的代码修改(修改空白字符,补全缺失的分号等)
  • test:新增测试用例或是更新现有测试
  • revert:回滚某个更早之前的提交
  • chore:不属于以上类型的其他类型(日常事务)

项目接入

老项目接入

安装对应依赖包以及新增相关配置文件和在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

  • 没有commit类型

  • commit类型错误

  • commit类型正确

感谢大家

如果你觉得这篇内容对你有帮助的话:

  1. 点赞支持下吧,让更多的人也能看到这篇内容
  2. 觉得不错的话,也可以移步查看更多文章github issues

参考文档

eslint官网

prettier

editorconfig

vscode配置文档

浏览器中的进程与线程?

区分浏览器中的进程与线程

什么是进程?

  • 进程(Process),进程是操作系统进行资源分配和调度的基本单位

什么是线程?

  • 线程(Thread),线程是操作系统进行运算的最小单位

多任务并行执行的方式?

很多的场景中,浏览器的渲染、数据请求与用户响应这些需要并行执行的任务,而单线程在运行时只能执行一个任务

解决当前问题的几种方式

  1. 多进程
  2. 多线程(同一进程)
  3. 多进程 + 多线程

多进程

  • 不同进程之间的资源是独立的,不可以相互访问,这个特征的好处是建立了进程之间的隔离型,避免了多个进程同时操作同一份数据而产生问题

多线程

  • 多线程没有独立的资源,线程之间的数据都是共享的。创建成本更小,不需要分配额外的存储空间
  • 带来的问题
    • 稳定性
      • 进程中任意线程崩溃会导致整个进程崩溃
    • 安全性
      • 恶意的线程启动,可以访问当前进程中的任意资源

多进程更轻量,多线程更安全更稳定

总结

  • 多进程在稳定性和安全性上有优势,但是占用的资源比较多
  • 对于复杂的应用我们可以采用服务化的设计方式,将功能模块单独拆分成进程来提供服务
  • 合理利用GPU进程可以加速绘制

进程之间相互独立,但是进程会占用跟多的资源空间。一个进程下可以有多个线程,相互之间可以进行数据通信,相互之间也有影响

本地存储 - 从Cookie到Web Storage,IndexedDB?

本地存储 - 从Cookie到Web Storage,IndexedDB

cookie

  1. cookie本质工作并非本地存储,而是'维持状态',是一个存储在浏览器里小的文本文件
  2. cookie是为了解决HTTP无状态协议怎么区分请求,通过把文本文件添加到HTTP请求上,在浏览器与服务器间携带用户数据,服务器通过cookie来获取客户端的状态
  3. cookie是以键值对的形式存在的
  4. 缺点
    • cookie不够大
      • 体积只有4kb
    • 过量的cookie会带来巨大的性能浪费
      • cookie是紧跟域名的,通过响应头里的Set-Cookie指定要存储的Cookie值,默认情况下domain被设置为Cookie页面的主机名,也可以手动设置domaind的值
      • Set-Cookie: name=xxx; domain=xcc.com
      • 同一域名下的所有请求,都会携带cookie

Web Storage

  1. HTML5专门为浏览器存储而提供的数据存储机制,分为Local Storage与Session Storage
  2. 两者区别: 生命周期与作用域不同
    • 生命周期
      • Local Storage为持久化存储,存储数据永远不会过期,唯一方式就是手动删除
      • Session Storage为临时性本地存储,是会话级别的存储,当会话结束(页面关闭)时,存储内容也会被释放
    • 作用域
      • Local Storage、Session Storage和Cookie都遵循同源策略,当Session Storage特别的一点在于,即使是相同域名下的两个页面,只要不是在同一个浏览器窗口中打开,那么他们间的内容就无法共享
  3. 特性
    • 存储容量大,根据浏览器的不同,可以达到5-10M之间
    • 不与服务器端通信,只在浏览器端作用
  4. 使用
    • 存储数据 setItem()
      • localStorage.setItem('key', value)
    • 读取数据 getItem()
      • localStorage.getItem('key')
    • 删除某一键值数据 removeItem()
      • localStorage.removeItem('key')
    • 清空数据 clear()
      • localStorage.clear()
Local Storage
  1. 持久化存储的数据,用来存储一些内容稳定的资源,比如base64的图片字符串
  2. 不长更新的css,js等静态资源
Session Storage
  1. 更适合存储生命周期和它同步的会话级别的信息

react native中一次错误排查 Error:Error: Duplicate resources

最近一直在使用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:重新执行打包命令即可打包成功。

参考资料:

1:facebook/react-native#22234

2:https://blog.csdn.net/wyw223/article/details/84311733

npm link 高效的模块调试方式

npm link 高效的模块调试方式

背景

在很多时候,我们在本地写一个npm模块时,经常会遇到的一个问题:

新开发或者修改的npm模块,怎么在本地测试?

为了方便描述,比如我们要在webpack-xcc这个项目中使用一个npm包npm-link-test

很多人在遇到这个问题时,常见的做法有下面几种:

  1. 发布npm包为测试版本,然后安装到项目中进行调试
  • 优点:无
  • 缺点:发布繁琐,有问题不能实时修改生效,非常低效
  1. 直接使用相对路径进行安装
cd app/webpack-xcc
npm install app/npm-link-test
  • 优点:操作方便了不少
  • 缺点:还是需要在每次修改后进行install,并且做不到修改实时生效
  1. 使用软链
cd app/webpack-xcc/node_modules
ln -s app/npm-link-test npm-link-test
  • 优点:修改npm包后,在项目中实时生效
  • 缺点:操作还是比较繁琐

什么是npm link

npm link是一种把包链接到包文件夹的方式,简单来说,就是可以让你在不发布npm模块的情况下,调试该模块,并且修改模块后会实时生效,不需要通过npm install进行安装

相关文档:https://docs.npmjs.com/cli/v6/commands/npm-link

npm link的使用技巧

  • 在模块根目录运行
npm link

  • 在项目根目录下运行
npm link name

  • 检索项目node_modules目录,就可以看到npm-link-test

  • 修改npm-link-test下的index.js内容,会发现项目目录的文件也一起发生变化

  • 在调试完模块后,需要从项目中移除link进行的模块

npm unlink name

图片优化-重量与性能的博弈?

图片优化-重量与性能的博弈

为什么图片优化这么重要

  1. 优化js,css用户是并不可见的,用户最直观看到的就是图片与页面内容是否看得到
  2. 现代的网站图片资源占比大,优化效果比较明显

图片优化的重点

  1. 在图片大小与图片质量之间做出权衡
  2. 优化的方式,通过压缩图片体积来实现,这个操作是以牺牲图片的质量为代价的

图片等网站资源统计查看

  1. HTTP-Archive

不同业务场景下的图片方案选型

二进制数与色彩的关系

  1. 计算机中,像素使用二进制数来表示
  2. 不同图片格式中像素与二进制位数之间的对应关系不同
  3. 二进制位数越多,可以表示的颜色总类也越多,效果越好,文件体积也越大
  4. 一个二进制为表示两种颜色(0|1 对应黑|白),如果一种图片对应的二进制位数有n个,那么就可以呈现2^n中颜色

现有的几种常用图片格式

  1. JPEG/JPG
    • 特点
      • 有损压缩,体积小,加载快,不支持透明
    • 使用场景
      • JPG适用于呈现色彩丰富的图片,通常用来做打的背景图,轮播图或banner图等
    • 优点
      • 图片体积缩小到原体积的50%以下时,JPG的还可以保持住60%的品质
      • 既可以保住图片的质量,又可以减少体积
    • 缺点
      • 处理矢量图形和logo等线条感比较强,颜色对比强烈的图像时,人为压缩会导致图片模糊会特别明显,同时也不支持透明度处理
  2. PNG
    • 特点
      • 无损压缩,质量高,体积大,支持透明
    • 使用场景
      • 主要用来呈现小的logo,颜色简单且对比强烈的图片或背景
    • 优点
      • 无损压缩的高保真图片格式,分为8位和24位,即png8和png24,8位支持256种颜色,24位支持1600万种颜色
      • 具有更强的色彩表现力,对线条的处理更加细腻
    • 缺点
      • 体积过大
    • png8与png24的选择
      • 使用png8时,如果确定这种质量损耗是否在可接受的范围,来确定使用png8或者png24
  3. WebP
  • 特点
    • 年轻的全能型选手
  • 使用场景
    • 不同浏览器端采用降级兼容
    • 代码3
  • 优点
    • 丰富的图片细节,支持透明,可以像gif一样显示动态图片
    • 与PNG相比,WebP无损图像尺寸缩小26%,同时有更小的体积
  • 缺点
    • 2010年被google提出,兼容性很差,使用过程中需要准备降级方案
    • WebP还会增加服务器的负担
  1. Base64
  • 特点
    • 文本文件,依赖编码,小图标解决方案
  • 什么是base64
    • Base64是一种用于传输8Bit字节码的编码方式,通过对图片进行Base64编码,我们可以直接将编码结果写入HTML或者CSS,从而减少HTTP请求次数
  • 雪碧图
    • 雪碧图,CSS精灵,CSS Sprites,图像精灵
    • 原理
      • 一种将小图标和背景图像合并到一张图片上,然后利用CSS的背景定位来显示其中每一部分
    • 优点
      • 减少HTTP请求,对内存和宽带更加友好
  • 应用场景
    • 图片的实际尺寸很小
    • 图片无法以雪碧图的形式与其他小图结合(合成雪碧图主要是为了减少HTTP请求,base64是雪碧图的补充)
    • 图片的更新频率非常低
  • 优点
    • 为了减少加载网页图片时对服务器的请求次数,从而提升网页性能
  • 缺点
    • Base64编码后,图片大小会膨胀为原文件的4/3,大图编码到HTML或者CSS中,后者体积会明显增加,即使减少了HTTP请求,也无法弥补庞大的体积带来的性能开销(结合使用场景)
  • Base64编码工具
    • webpack中,使用url-loader,可以结合文件大小来决定是否进行Base64编码
  • 代码2
  1. SVG
    • 特点
      • 文本文件,体积小,不失真,兼容性好
    • 使用场景
      • 把svg文件写入HTML文件
      • 将SVG写入独立文件后引入HTML
    • 优点
      • 文件体积更小(相比png和jpg),可压缩性更强
      • 作为矢量图,具有图片可无限放大而不失真
      • SVG是文本文件,可以像写代码一样定义SVG,放到HTML里成为DOM的一部分,也可以写入.svg为后缀的独立文件,具有很高的灵活性
    • 缺点
      • 渲染成本比较高
      • SVG存在其他图片格式没有的学习成本(可编程)
    • 代码1
  2. Gif

扩展

  1. http2的多路复用对雪碧图的影响
  // 代码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">

通关面试求职02-打造一份好的简历

通关面试求职02-打造一份好的简历

一份简历应该有的信息

  1. 个人信息:包含姓名,年龄,联系方式,籍贯等
  2. 专业技能描述:熟悉的写上,别往上堆砌专业名词
  3. 工作经历:包含最近3份工作的详细信息
  4. 业绩成果:做出了什么成绩,要保证真实性
  5. 教育经历
  6. 个人评价

好的简历有哪几个必备要素

  1. 清爽、整洁的排版
  2. 突出重点信息,不要罗列
  3. 简历的描述逻辑清晰准确,不要有错字等低级错误

让人看到了烦心的简历有的东西

  1. 花里胡哨的简历模板
  2. 常用信息填写过多或者不完整
  3. 工作经历应该真实完整,说的有逻辑性

问题1:

简历应该选用什么样的模板:

很多的毕业生或者刚入职场的人,会去很多简历网站找些花里胡哨的简历模板,有着深色背景,动效等。很多人觉得炫酷的简历会吸引面试官的注意,但是你要知道,很多面试官一天会看上百份的简历,每天看这样花里胡哨的很容易扰乱面试官提取重要信息的思路,而且每个人的审美也不相同,如果面试官觉得很土,会不会就导致自己浪费了一次机会?

好的简历,应该保持简介干净,最好采用简洁干净的纯色底简历模版,导出简历时最好导出PDF格式或者文本格式,在文件名上最好写上自己的关键信息。比如姓名-手机号-岗位等信息

问题2:

哪些信息是关键的,怎么写不会显的多余:

一份简历肯定要包含自己的个人信息,但是也不是要什么都往上写。比如以前见过有的人的简历个人描述,包含了名字,性别,年龄,籍贯,民族,身份证号,手机号,邮箱,毕业院校,专业,毕业时间,政治面貌,特长,身体状况等

但是面试官是否会在乎你填写的那么多休息呢,一般是不会的(除非你放上了你漂亮的自拍照)。那么我们需要提取关键的信息出来,包含:姓名,年龄,联系方式,毕业院校,专业即可

千万千万记得个人信息不要填写错了,以前遇到过通过邮箱发送offer邮件,求职者说没收到的问题出现,后来才发现是简历邮箱写错了

问题3:

专业技能应该怎么写:

以前看到过别人的简历,技能描述写了下面的这些:

  1. 精通html、css、javascript
  2. 熟悉后端开发
  3. 精通react全家桶的原理、
  4. 精通canvas,lottie动画,熟悉数据库,MySQL...
  5. ...

但是在实际的面试中,问出的许多问题,原理并不是特别清楚,可能只是在熟练使用阶段。因此,在写自己的专业技能描述时,千万不要给自己挖坑,千万千万不要写自己都精通(当然大佬是肯定有的,不过毕竟还是少数),写的东西要自己hold住,不要罗列专业技能名词,应该像下面这样写:

  1. 熟悉html、css及原生javascript
  2. 熟练使用react全家桶,对源码有基本了解,知道实现原理,比如更新机制,hooks,redux等
  3. 熟悉前后端交互流程,使用过node
  4. 熟练使用canvas动画
  5. ...

问题4:

工作经历怎么写比较好:

如果是刚刚进入职场的人,写一下大学期间做过什么项目,实习经历,平时的学习方式等

如果是职场老鸟,突出写出最近2-3份工作的项目经历,保证真实性。详细描述出自己做的项目,在项目中承担的职责,遇到的问题,解决问题的思路等。面试官一般感兴趣的是你做了哪些事,遇到过什么问题,思考的过程是怎么样的,怎么解决的问题,带来了什么样的成绩,逻辑清晰,描述准确即可

同时,千万不要简历造假,现在很多大公司都会做背调,一旦发现简历作假,就是诚信问题,后面估计都没机会进去了

问题5:

工作业绩怎么写比较好:

一般写出自己的年度、季度绩效,或者工作中做出的成绩,比如做了某个优化,导致访问量增加了5%,优化了哪些流程,使公司一年节省了100w等即可

问题6:

教育经历怎么写比较好:

一般而言,写出自己的大学信息,专业学习,毕业时间即可,很多人还会把自己的高中,初中等教育经历写进来,其实面试官一般对这也不会感兴趣,毕竟地方性的学习大家也都不了解

如果大学期间获得了什么重量级的奖项,也是可以写的,比如数学,信息学科竞赛国奖等。普通的比如社团干部,奖学金,班干部之类的就不要写了,一般没什么用,只会增加面试官的思考难度

问题7:

个人评价怎么写比较好:

注意,写个人评价的时候,要实事求是,同时表现出自己的特点与优势,别傻乎乎的把自己的缺点之类的都写上去了。好的个人评价可能会有的几个点:

  1. 平时比较喜欢运动,比如打篮球,羽毛球等(表现出自己的健康,阳光,有活力)
  2. 平时喜欢看书籍,学习视频,写博客(表现出自己是真心喜欢这件事,对技术有追求,善于总结归纳)
  3. 个性以及工作的一些总结

useCallback的常规使用方式?

怎么使用好useCallback,来达到减少render次数的效果

react优化方式

  1. 减少render次数
  2. 减少计算量
  • 下面的代码,当handleClick1时间触发时,PageB组件也会重新渲染
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
  • 使用useCallback进行处理
  1. 点击事件handleClick1触发时,PageB组件也会重新渲染,当PageB组件比较耗时时,就会造成新能问题
  2. PageB组件重新渲染的原因在于每次重新渲染,onClick都会重新定义,即上次的与这次的不一致
  3. 思路:通过useCallback包裹onClick来达到缓存的效果,即useCallback的依赖项不变时不重新生成
  4. 用过memo方法包裹PageB组件,并且通过useCallback包裹PageB组件的onClick方法,memo与PureComponent比较类似,前者是对Function Component的优化,后者是对Class Component的优化,都会对传入组件的数据进行浅比较,useCallback则会保证handleClick2不会发生变化
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

github中的图片不显示?

github上图片不显示

  1. 运行环境mac pro
  2. 命令行输入sudo vi /etc/hosts,需要输入电脑密码
  3. 命令行打开hosts文件后,输入i,下面会出现-- INSERT --后就可以输入内容了
  4. 将下面内容拷贝到hosts文件最下面
  # 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
  1. 按esc,wq保存即可

常见的代码逻辑优化处理?

常见的代码逻辑优化处理

多条件判断

  // 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

错误处理 - try...catch...

  // 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({}))
    }
  }

文本文件及其构建过程的优化?

文本文件及其构建过程的优化

webpack性能调优与Gzip原理

网络层面三个过程

  1. DNS解析
  2. TCP连接
  3. HTTP请求与响应

HTTP优化两大方向

  1. 减少请求次数
  2. 减少单次请求花费的时间
  • 解决方法:资源的压缩与合并

webpack性能瓶颈

  1. webpack的构建过程太花时间
  2. webpack的打包结果体积太大

webpack优化方案

  1. 构建过程提速策略
  2. 不要让loader做太多事情,以babel-loader为例
  • 方式一:使用include或exclude来避免不必要的转译

    • 代码1通过规避对node_modules文件夹或者bower_components文件夹进行处理
  • 方式二:通过选择开启缓存将转译结果缓存到文件系统

    • 代码2通过添加缓存参数来提高编译效率
  • 方式三:对三方库进行处理(node_modules)

    • Externals: 一些情况下会引发重复打包的问题
    • CommonsChunkPlugin: 每次构建都会重新构建一次verdor
    • DllPlugin: 这个插件会把第三方库单独打包到一个文件中,这个文件是一个单纯的依赖库。这个依赖库不会跟着你的业务代码一起被重新打包,只有当依赖自身发生版本变化时才会重新打包
      • DllPlugin处理文件流程
        • 基于dll专属的配置文件,打包dll库
        • 基于webpack.config.js文件,打包业务代码
  • 方式四:Happypack - 将loader由单线程转为多线程

    • 问题:webpack是单线程的,在多个任务的情况下也是排队一个接一个地等待处理
    • 解决方式:使用HappyPack来把任务分解为多个子进程去并发执行,大大提高打包效率
    • 使用方式:把对loader的配置转移到HappyPack中去,手动告诉HappyPack需要多少个并发的进程
    • 配置方式-代码4
  • 方式五:构建结果体积压缩

    • 文件结构可视化,找出体积过大原因,通过配置webpack-bundle-analyzer来查看
      • 配置方式-代码5
    • 拆分资源
    • 删除冗余代码
      • Tree-Shaking
        • 在基于import/export的语法,Tree-Shaking可以在编译的过程中获悉哪些模块未被真正的使用,这些没使用到的代码,在最后打包时会被去除
        • 主要用来处理模块级的代码冗余,更细小的粒度就做不到了,比如未使用的函数,css等
        • 实现方式-代码6
      • UglifyJsPlugin
        • webpack3实现-代码7
        • webpack4中,已经默认使用uglifyjs-webapck-plugin对代码压缩,通过配置optimization.minimize与optimization.minimizer来自定义压缩相关配置
    • 按需加载
      • 按需加载**
        • 一次不加载玩所有的文件内容,只加载此刻需要用到的部分(需要提前做好功能拆分)
        • 当需要更多内容时,再对用到的内容进行即时加载
      • 拆分粒度
        • 细化到更小的组件,细化到某个功能点,代码片段等
      • require.ensure方法
        • 语法:require.ensure(dependencies, callback, chunkName)
        • 这个异步方法,webpack打包时,BugComponent组件会被单独打成一个文件,只有在跳转到bug这个路由时,这个异步方法的回调才会生效,才会去加载组件的内容
        • 使用方式-代码8
      • import
        • require.ensure为webpack提出的异步依赖方法,为非标准方法
        • import为ecma标准语法,返回一个promise
      • Code-Splitting方法
        • react-router4按需加载的实现方式Code-Splitting
        • react-router4内部使用了bundle-loader,内部也是采用require.ensure实现的
    • Gzip压缩
      • 使用方式
        • 在request headers中添加accept-encoding:gzip
          • 问题:chrome会报错Refused to set unsafe header "accept-encoding"
          • 原因:chrome安装w3c标准,w3c标准禁止了该行为。服务端确认有Content-Encoding: gzip就可以了,浏览器端识别出Gzip就会解码
        • 视文件大小来决定是否开启Gzip
          • 对于小文件,开启后可能返回会更慢
          • 对于文本类型文件(js,css,txt,ttf...)等开启压缩会比较明显,对于图片,视频等多媒体文件,多媒体文件本身就采用了有损压缩,开启Gzip可能效果不明显
      • HTTP压缩
        • 目的:HTTP压缩是以缩小体积为目的,对HTTP内容进行重新编码的过程
        • HTTP压缩是一种内置到网页服务器和网页客户端中以改进传输速度和宽带利用率的方式
        • 在使用HTTP压缩的情况下,HTTP数据再从服务器发送前就已经压缩,兼容的浏览器将下载所需的格式钱宣告支持何种方法给服务器,不知道压缩方法的浏览器会下载未压缩的数据
        • 常见压缩方案:Gzip和Deflate
      • Gzip压缩原理
        • 是在一个文本文件中找出一些重复出现的字符串,临时的替换它们,从而使整个文件变小
      • 该不该使用Gzip
        • 具备一定规模的项目文件,都应该使用压缩
        • Gzip是高效的,压缩后通常能帮我们减少70%左右的大小
      • webpack中的Gzip与服务器中的Gzip
        • 压缩文件本身是耗时的,可以理解为以服务器压缩的时间开销和CPU开销以及浏览器解析压缩文件的开销来减少传输过程中的时间开销
        • webpack中的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)

react中dom的渲染流程

  1. react中的虚拟dom,本质上是一种对象形式到dom的描述,虚拟dom的操作是在js层面的计算,如果把js映射生成真实的dom
  2. 调用生命周期中的render方法,生成虚拟dom,然后再调用reactDom.render方法,实现虚拟dom的真实dom转换
  3. 组件的再次更新,会再次调用render方法生成新的虚拟dom,然后借助diff定位出两次虚拟dom的差异,从而针对变化的真实dom做出定向更新
  4. react的diff算法,本质通过分层算法,节点前后位置的替换,会导致替换的节点全部被删除,然后重新生成新的节点挂载在上面。key的作用是确定唯一的ID,来让diff比较时确定是否需要重新生成
  5. react的setState为异步操作,在内部通过添加一个锁机制,来实现值的批量更新,在setTimeout(fu, 0)中,fn中的setState为同步更新
  6. 在使用ts时,js对应ts结尾文件,jsx对应tsx结尾文件
  7. 什么是jsx
    1. jsx是JavaScript的一种语法扩展,和模板语法很接近,当时充分具备JavaScript的能力
  8. jsx是怎么在JavaScript中生效的
    1. jsx是通过babel转换为JavaScript的
    2. babel会吧jsx代码转化为react.createElement调用
    3. jsx本质是React.createElement这个JavaScript调用的语法糖
    4. jsx语法糖允许前端开发者使用我们最熟悉的HTML标签语法来创建虚拟dom
  9. 页面tree = dom tree + css tree
  10. 页面的回流与重绘
  11. 代码编译流程 => 词法解析 => 语法解析 => 生成AST语法树

HTML标签在使用中,如何使用性能最好?

HTML标签

  1. 合理的标签放置顺序,比如link标签文件一般放在header中,script标签文件一般放在body标签后面加载
  2. 合理的标签使用,有利于SEO,比如title标签,文本内容一般在p标签中,标题标签一般放在h1-h6标签中

script标签

  1. async属性
    1. 立即请求文件,不阻止渲染引擎,而是文件加载完毕后阻塞渲染引擎并立即执行文件内容
  2. defer属性
    1. 立即请求文件,但不阻塞渲染引擎,等到解析完 HTML 之后再执行文件内容
  3. HTML标准type属性
    1. 对应值为“module”。让浏览器按照 ECMA Script 6 标准将文件当作模块进行解析,默认阻塞效果同 defer,也可以配合 async 在请求完成后立即执行

link标签

  1. dns-prefetch属性
    1. 当 link 标签的 rel 属性值为“dns-prefetch”时,浏览器会对某个域名预先进行 DNS 解析并缓存。这样,当浏览器在请求同域名资源的时候,能省去从域名查询 IP 的过程,从而减少时间损耗
  2. preconnect属性
    1. 让浏览器在一个 HTTP 请求正式发给服务器前预先执行一些操作,这包括 DNS 解析、TLS 协商、TCP 握手,通过消除往返延迟来为用户节省时间
  3. prefetch/preload属性
    1. 两个值都是让浏览器预先下载并缓存某个资源,但不同的是,prefetch 可能会在浏览器忙时被忽略,而 preload 则是一定会被预先下载
  4. prerender属性
    1. 浏览器不仅会加载资源,还会解析执行页面,进行预渲染

待补充...

字体子集化

字体子集化

  • 在很多时候,中问字体包提交会很大,在一下交互场景下,需要等待指定字体文件下载完成再渲染字体,造成的体验非常差

问题常见

  • 场景1:对固定的几个文字,渲染不同的字体样式
  • 场景2:输入不同的文字,渲染不同的字体样式

本地加载

每次下载字体文件后,将下载的字体文件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)

究极方案

  • 对于场景1,把一系列的字体文件合并为一个字体文件进行下载,减少文件请求次数
  • 对于场景2,下载大的字体文件时,利用浏览器空闲时间下载requestIdleCallback,通过indexedDB缓存文件

业务场景

  • 下载指定字体文件,加载到document上
  • 对于全量下载的文件,通用字体时,通过indexedDB缓存到浏览器本地
  • 对于设计师的自定义字体,通过对文件生成MD5来判断文件是否发生更改来更新缓存

资料

H5开发中遇到的一些常见问题

  1. 在设置页面禁止复制文本是,设置了-webkit-user-select: none导致iOS手机上输入框类失效
// 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;
}
  1. iPhoneX底部样式兼容,包括页面主体内容的在安全区域及fix定位下bottom: 0的兼容。参考:iPhone X兼容

  2. 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')
}
  1. 键盘遮挡输入框,onBlur方法监听处理
<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)
}
  1. 在react中使用类keep-alive组件在pc/h5中的使用 - react-live-route
  • 场景:长列表中滚动到很多页后查看了某条数据,进入列表详情,返回到列表页是,会回到顶部
  • 原因:react中进行路由跳转时,state等数据会丢失,也不回记录滚动等信息
  • 解决方式:
    - 采用redux等数据流工具,在列表页面跳转的时候在componentWillUnmount生命周期记录state中的数据与滚动位置信息,在componentDidMount生命周期对数据进行恢复、
    - 在路由跳转的时候隐藏列表页,在回到列表页的时候再重新渲染出来
  • 遇到的问题
    - Switch渲染的是匹配到的第一个路由,而LiveRoute是为了让组件强制渲染不匹配的路由
    - Switch与LiveRoute应包裹在同一个div中,不然会报错A may have only one child element
    - 采用position: absolute进行定位的元素会有影响
    - routes中的路由应该与keepRouter中的路由不重复,重复的情况下会在同一个路由下渲染页面两次
  • 参考链接
    - facebook/react#12039
    - https://github.com/fi3ework/react-live-route
    - https://codesandbox.io/s/yj9j33pw4j?file=/src/index.js:399-409
    - https://zhuanlan.zhihu.com/p/59637392
 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>
  1. 微信H5重复授权
  • 问题描述
    - android手机上,主要在低版本的android机上,客户打开微信H5,页面出现多次弹出授权窗口,需要点击多次确认才会消失
  • 问题原因
    - 多次重定向导致出现多次授权窗口
    - hash模式路由导致的参数丢失
    - 授权链接中参数不完整
// 最终的重定向方法
// 去除重定向地址中的#,同时添加参数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 run出现错误:No supported devices connected

在通过flutter run启动项目的过程中,遇到提示No supported devices connected的错误,错误的意思为没有找到连接的设备

解决方案

  1. 运行flutter doctor,查看设备情况
flutter doctor
  1. 运行flutter emulators,查看可连接设备
flutter emulators
  1. 运行flutter emulators --launch <emulator id>,连接到指定设备,此时会打开一个模拟器
// mac端
flutter emulators --launch iOS Simulator
// windows
flutter emulators --launch Pixel_3a_API_30_x86
  1. 运行flutter run,启动项目,正常运行
flutter run

git查看commit提交的内容

git查看commit提交的内容

有时候在对文件进行了commit操作后,想看一下修改的文件的具体信息,应该怎么做呢

  1. git log - 查看之前每次的commit记录列表
  2. git show - 查看最近一次已commit的文件修改信息
  3. 如果需要查看指定的某次commit的文件修改信息,git log获取那次的commit id,然后git show commitid即可查看指定的文件修改详情

浏览器缓存机制介绍与缓存策略剖析?

浏览器缓存机制介绍与缓存策略剖析

浏览器缓存的好处

  1. 缓存可以减少网络IO消耗,提高访问速度
  2. 通过网络获取内容速度缓慢又开销巨大,较大的响应需要在客户端与服务器之间多次往返通信,会延迟浏览器获得和处理内容的时间

浏览器缓存的分类和优先级

  1. Memory Cache
  2. Service Worker Cache
  3. HTTP Cache
  4. Push Cache

文件缓存查看方式

  •  通过Network面版
  • 类似于(from xxx)这样对应的资源,就是通过在缓存中获取到的
  • from memory cache对应Memory Cache
  • from ServiceWorker对应Service Worker Cache

HTTP缓存机制探秘

  1. 缓存分类

    • 强缓存(优先级高)
      • 强缓存原理
        • 强缓存利用HTTP头中的ExpiresCache-Control两个字段来控制的
        • 强缓存中,请求再次发出时,会根据这两个字段判断目标资源十分命中强缓存,命中就直接从缓存中获取资源,不会再与服务端发生通信
        • 强缓存命中后,HTTP状态码为200,from disk cache
      • 强缓存实现
        • expires和cache-control
          • expires
            • 在服务器返回响应是,在Response Headers中会写入过期时间expires
            • expires格式expires: Wed, 11 Sep 2019 16:12:18 GMT
            • 原理:当我们准备再次向服务器请求资源,浏览器会先对比本地时间和expires时间戳,本地时间小于expires,就直接取缓存中的这个资源
            • 问题:依赖本地时间,当本地时间被修改,达不到预期效果
          • Cache-Control
            • HTTP1.1新增Cache-Control字段来完成expires的任务
            • Cache-Control作为expires的完全替代方案,expires继续使用的目的在于向下兼容
            • Cache-Control格式cache-control: max-age=3600, s-maxage=31536000
            • max-age为一个时间长度,在这个时间范围内都有效
            • Cache-Control相比expires更加准确,优先级更高
            • s-maxage优先级高于max-age,两者同时出现时,优先考虑s-maxage,如果s-maxage为过期,则向代理服务器请求其缓存内容
            • s-maxage仅在代理服务器中生效,客户端中我们只考虑max-age
        • public和private
          • public和private是针对资源能否被代理服务器缓存而存在的一组对立概念,private为默认值
          • 资源设置public,那么既可以被浏览器缓存,又可以被代理服务器缓存
          • 资源设置private,则资源只能被浏览器缓存
        • no-store和no-cache
          • no-cache
            • 资源设置no-cache后,每一次发起请求都不会去询问浏览器的缓存情况,而是直接向服务端去确认该资源是否过期
          • no-store
            • 不使用任何缓存策略,也不与服务端确认缓存信息,而是直接向服务端发送请求
    • 协商缓存
      • 原理
        • 浏览器与服务器合作之下的缓存策略
        • 协商缓存依赖于服务端与浏览器端的通信
        • 协商缓存机制下,浏览器需要向服务器去询问缓存相关信息,进而判断是否重新发起请求,还是从本地获取缓存的资源
        • 如果提示缓存资源为改动,资源会被重定向到浏览器缓存,网络状态码为304(Not Modified)
      • 实现
        • Last-Modified到Etag
          • Last-Modified
            • 如果启用了协商缓存,会在首次请求的Response Headers中返回Last-Modified,为一个时间戳格式
            • Last-Modified格式Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT
            • 后面我们每次请求,都会带一个If-Modified-Since字段,为上次返回的last-modified,服务器接收到时间戳后,会对比时间戳与服务器上资源最后修改时间是否一致,来判断资源是否发生变化
            • 缺点
              • 服务器没有正确感知文件的变化,包括修改时间与内容等
              • 编辑了文件,但是文件内容没发生改变,服务端不清楚是否真正改变了文件,但是修改时间发生改变,会重新请求资源
              • 修改文件速度过快(1s内),If-Modified-Since只能检查以秒为最小单位的时间差,服务端感知不到资源改动,应该重新请求却没发起资源请求
          • Etag
            • 由服务器为每个资源生成的唯一标识字符串,服务器生成会有性能损耗
            • Etag格式ETag: W/"2a3b-1602480f459"
            • 下次请求会带一个if-None-Match的字符串供服务端对比,格式If-None-Match: W/"2a3b-1602480f459"
            • Etag是作为Last-Modified的补充和强化,优先级也更高
            • 缺点
              • 生成唯一标识会造成服务器性能损耗
  2. HTTP缓存决策指南

    • HTTP流程图
    • 当我们的资源内容不可复用时,直接为 Cache-Control 设置 no-store,拒绝一切形式的缓存;否则考虑是否每次都需要向服务器进行缓存有效确认,如果需要,那么设 Cache-Control 的值为 no-cache;否则考虑该资源是否可以被代理服务器缓存,根据其结果决定是设置为 private 还是 public;然后考虑该资源的过期时间,设置对应的 max-age 和 s-maxage 值;最后,配置协商缓存需要用到的 Etag、Last-Modified 等参数

MemoryCache

  1. 指内存中的缓存,优先级上为浏览器最先去命中的一种缓存,效率上也是响应速度最快的缓存
  2. 与渲染进程有关,当进程结算后(tab关闭),内存数据被销毁
  3. 内存资源有限,大体积的文件肯定不会写入内存中

Service Worker Cache

  1. 独立于主线程外的JavaScript线程,脱离于浏览器窗口,无法直接范文DOM
  2. 独立使得Service Worker可以帮助我们实现离线缓存,消息推送和网络代理等功能
  3. https协议中使用
  4. 生命周期,被install后,会始终存在,只会在active与working之间切换,只能手动终止,这个为实现离线存储的先决条件
    • install
    • active
    • working
  // 注册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()
        })
      })
    )
  })

Push Cache

  1. Push Cache是指HTTP2在server push阶段存在的缓存
  2. Push Cache是缓存的最后一道防线,浏览器只有在Memory Cache,HTTP Cache和Service Worker Cache均未命中的情况下才会询问Push Cache
  3. Push Cache是一种存在于会话阶段的缓存,当session终止是,缓存也会释放
  4. 不同页面共享同一个HTTP2连接,那么就可以共享同一个Push Cache

CSS常见组织方式?

css代码管理

  • 现状:现阶段web标准提倡结构,样式,行为分离,通常css文件会独立为一个文件,怎么更好的组织分类提高复用与代码编辑

常见的几种样式文件组织方式

  1. css文件与组件,页面放同一个文件夹,类似antd组件库的组织方式
  2. css文件放在一个统一的文件夹内,组件,页面中通过import的方式引入

如何避免样式冲突

  • 场景:平时使用框架开发的过程中,经常会遇到这个问题,pageA页面定义了类名layout-header,pageB页面也定义了同样的类名,会发现样式之间相互有影响
  • 解决方案
      1. 为每个页面设置不同的父级类名,类似:.pagea .layout-header与.pageb .layout-header
      • 问题:不同开发人员也可能设置了相同的父级类名,没有代码检测手段可以检测出命名重复的问题
      1. CSS Modules
      • 问题:通过CSS Modules设置后,类名会被编译为一个随机名称,但是这样会导致在想覆盖样式时,不知道该改哪个元素
  // 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
  }

react hooks逻辑抽取及场景?

hooks的逻辑复用

场景

  • 一个页面中,存在多个modal弹窗时,需要定义多个类似visible,onCancel,onOk这样的变量与方法
  • 某几个页面的权限验证

Component实现

  • Component是针对组件的抽取
  • hoc方式也可以实现组件逻辑的抽取
  • 逻辑部分的处理还是需要多次实现,在多个modal的案例中,需要多次实现和定义visible,onCancel,onOk这样的变量和方法
  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

hooks实现

  • hooks方法可以对逻辑进行抽取,类似一个通用的处理函数
  • 在当前场景下,变量与方法只需要写一次,后续使用直接调用既可
  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

总结

  • hooks非常适用与对一些通用逻辑,重复度高的场景中使用

javascript在浏览器中的执行过程?

浏览器是如何执行JavaScript代码的

编译过程

  • 解析
    • 词法分析
      • 将JavaScript代码解析为一个个的令牌
      • 令牌类型
        • 关键字 - Keyword
        • 标识符 - Identifier
        • 符号 - Punctuator
        • 字符串 - String
    • 语法分析
      • 将令牌组装成一颗抽象的语法树
  • 解释
    • JavaScript引擎的解释器Lgnition将AST转换为字节码
  • 优化
    • 解释器会将重复的操作进行优化,生成分析数据,然后将字节码+分析数据传给编译器TurboFan

语法分析过程

输入的代码

  var name = 'xcc'
  console.log(name)

语法分析

  // 令牌
  Keyword(var)
  Identifier(name)
  Punctuator(=)
  String('xcc')
  Identifier(console)
  Punctuator(.)
  Identifier(log)
  Punctuator(()
  Identifier(name)
  Punctuator())


内存管理

特点

  • 先进后出的规则
  • 临时性的存储空间,主要用来存储局部变量和函数调用
  • 基本数据类型String,Number,Undefined,Null,BigInt,Symbol,Boolean直接在栈中创建,而复杂数据类型会存储在堆中,栈中存储的是堆的引用地址
  • 全局变量以及闭包变量也是存储的引用地址
    • 闭包的变量不会销毁

栈的查看方式

  • console.trace()
  • 浏览器控制台的Source下的Call Stack

特点

  • 存储大的数据结构
  • 分为5个区域
    • 代码区 - Code Space
    • Map区 - Map Space
    • 大对象区 - Large Object Space
    • 新生代 - New Space
    • 老生代 - Old Space
  • 回收算法
    • 标记清除
    • 标记整理
    • V8内存一分为二,小空间用于存储新生代对象(32M|16M)用于回收存活时间比较短的对象,回收过程采用复制算法+标记整理算法,新生代内存区分为二个大小空间,使用空间为From,空间时间为To,活动对象的存储于From空间,标记整理后将活动对象拷贝到To,交换空间后完成释放

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.