hulin32 / blog Goto Github PK
View Code? Open in Web Editor NEWpersonal blog, new article on https://www.hulin.dev
personal blog, new article on https://www.hulin.dev
当我们在开发中使用自己的字体的时候,由于字体文件一般都会比较大,所以需要对它们做优化。一般来说有这样几种方法。
页面渲染的过程
1.浏览器请求 HTML 文档。
2.浏览器开始解析 HTML 响应和构建 DOM。
3.浏览器发现 CSS、JS 以及其他资源并分派请求。
4.浏览器在收到所有 CSS 内容后构建 CSSOM,然后将其与 DOM 树合并以构建渲染树。
- 在渲染树指示需要哪些字体变体在网页上渲染指定文本后,将分派字体请求。
5.浏览器执行布局并将内容绘制到屏幕上。
- 如果字体尚不可用,浏览器可能不会渲染任何文本像素。
- 字体可用之后,浏览器将绘制文本像素。
字体的加载会阻塞页面的渲染,因而优化字体挺重要的
当在加载自己的字体时,在最前面声明local(),像这样
@font-face {
font-family: 'Awesome Font';
font-style: normal;
font-weight: 400;
src: local('Awesome Font'),
url('/fonts/awesome.woff2') format('woff2'),
url('/fonts/awesome.woff') format('woff'),
url('/fonts/awesome.ttf') format('truetype'),
url('/fonts/awesome.eot') format('embedded-opentype');
}
如果字体文件在本地就不会发起新的请求
用法
@font-face {
.....
unicode-range: U+000-5FF; /* Latin glyphs */
}
这个霸道,你可以通过指定unicode-range
来指定需要应用字体的范围,曾经在一个活动页面内使用过
,当时通过检查页面,发现整个项目就用了几个文字,其他全是图片,我就直接写了那几个字符的unicode,字体文件直接从200多kb降到30kb左右,性能测试字体方面直接从F变到了A.
将字体直接放在css文件中,不用发起二次请求,不过css文件会比较大。
参考
WebGL 入门
这里谈的都是WebGL1.0
3D的东西想上手很久了,最近看了一些计算机图形学方面的资料,感觉国内能找到的资料真的不多,这里是一片宝地,值得探索呀。最近先看了《WebGL编程指南》,预备做下笔记,一是为了巩固知识,二也是为了将来可以回来回忆看看。
WebGL是拿来在网页上做复杂三维图形的渲染的,并允许用户和它交互,WebGL起源于OpenGL的一个子集OpenGL ES 2.0, 它本身有带有一个很重要的特性:可编程着色器方法,也叫着色器语言
,GLSL ES类似其他编程语言一样。WebGL就介绍到这里,更多的去翻一下书吧,这本书入门绝佳,市面上另外一本大家推荐的是《WebGL高级编程》,我也买了,还没看,翻了一下,感觉那本可以作为这本的深入。
先从一个最简单的清空绘图区开始,HelloCanvas(我用的代码都是随书源码,请查看这个项目下的源文件)
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('webgl');
// 获取webgl上下文
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// 指定背景色
gl.clearColor(0.1, 0.5, 0.5, 1.0);
// 填充
gl.clear(gl.COLOR_BUFFER_BIT);
}
gl.clear 可以有的参数有:
COLOR_BUFFER_BIT: 颜色缓冲区
DEPTH_BUFFER_BIT: 深度缓冲区
STENCIL_BUFFER_BIT: 模版缓冲区
接下来让我们看看着色器(shader),这是编写WebGL必不可少的部分,着色器分为顶点着色器(Vertex shader)
和片元着色器(Fragment shader)
// HelloPoint1.js (c) 2012 matsuda
// 顶点着色器程序
var VSHADER_SOURCE =
'void main() {\n' +
' gl_Position = vec4(0.0, 0.5, 0.0, 1.0);\n' + // 设置顶点坐标
' gl_PointSize = 10.0;\n' + // 设置点的尺寸
'}\n';
// 片元着色器程序
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // 设置片元颜色
'}\n';
function main() {
var canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// 初始化着色器
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制一个点 可以是
/*
gl.{
POINTS, LINES, LINE_STRIP, LINE_LOOP,
TRIANGLES, TRIANGLE_STRIP, TRIANGLE_FAN
}
*/
gl.drawArrays(gl.POINTS, 0, 1);
}
WebGL和canvas的坐标系也有所区别,中心是(0,0,0)
既然GLSL ES 作为一门语言肯定也会有变量了,是的,它有,只是和我们平时用的有些许差别,我觉得可能是因为它是嵌入式的吧,不清楚。
ES的变量有两个attribute和uniform,attribute的数据和顶点有关,uniform是那些与顶点无关的数据,来我们看看代码,我决定对着例子敲一遍,走起。
// HelloPoint2.js
// 顶点着色器 这里vec4是一个类型
var VSHADER_SOURCE =
'attribute vec4 a_Position; \n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
' gl_PointSize = 10.0;\n' +
'} \n';
var FSHADER_SOURCE =
'precision mediump float; \n'+
'uniform vec4 u_FragColor; \n'+
'void main() { \n' +
' gl_FragColor = u_FragColor;\n' +
'}\n';
function main() {
var canvas = document.getElementById('webgl');
var gl = getWebGLContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log('Failed to intialize shaders.');
return;
}
// 获取a_Position变量的地址
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if (a_Position < 0) {
console.log('Failed to get the storage location of a_Position');
return;
}
var u_FragColor = gl.getAttribLocation(gl.program, 'u_FragColor');
if (!u_FragColor) {
console.log('Failed to get the storage location of u_FragColor');
return;
}
// 设置顶点位置的值
gl.vertextAttrib3f(a_Position, 0.1, 0.0, 0.0);
// 设置颜色
gl.uniform4f(u_FragColor, 1, 0.0, 0.0, 1.0);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
// 画
gl.drawArrays(gl.POINTS, 0, 1);
}
抄下来其实你会发现ES编程和我们普通的区别不大,无非是有更多的限制。
当然你也可以在canvas上绑定事件canvas.onmousedown
来做一些简单的交互,这里就不抄代码了,有兴趣自己去看看。
好了,这些入门算是可以了。
我们了解了:
书中的例子用到了两个方法getWebGLContext
和initShaders
,书在最后有讲过一个,代码不多,接下来让我们看看这两个方法吧. 这两个方法在这个项目的文件里,可以看一下,主要就是初始化webgl的一些操作,确实是挺繁琐的。想要用起来还是要学很多呀。
typescript 是javascript的超级,在原来的基础上引入了类型系统,让代码更加好维护,正经typescript我也还没怎么写过,但在学习中比较没有类型系统时,明显会感觉typescript在项目大后会更好的维护(还没在项目中验证),希望在今后的代码中也能将起引入。
这里我想通过创建一个类来尽量诠释其全部的概念
// 枚举
enum Gender { Female, Male };
class Human {
// string 类型
static component: string = '碳水化合物';
}
// 接口
interface Character {
kindly: boolean;
}
// 继承和实现
class Female extends Human implements Character {
gender: Gender = Gender.Female;
kindly: boolean;
private years: number;
name: string;
constructor(kindly: boolean, years: number, name: string) {
super();
kindly = kindly;
years = years;
}
// void 表示没有返回值
sayHi(): void {
let sentence = `Hello, I am ${this.years} years old`; // 类型推断
console.log(sentence);
}
}
待补充.....
自己学vim使用的一些命令,大多数在文后的链接能找到
<C-r>
-> redo<path/to/file>
-> open fileN<command>
-> Repeat the command N times.*
-> go to next/previous(#) occurrence of the word under the cursorf<char>
, ;
, ,
-> Find one char on the current line, ;
go to the next same char, ',' go back to the last same char<C-p>
-> Go to the previous line<C-n>
-> GO to the next line<C-d>
-> Go down a half screen<c-u>
-> Go up a half screen<C-v>
-> Visual modehttps://demo.cssworld.cn/4/1-9.php
https://demo.cssworld.cn/4/4-2.php
https://demo.cssworld.cn/5/1-1.php
最近在换电脑,之前的电脑终端一直用着,虽然有些地方不如意,但是一直都没管它,这次换电脑想着一次搞定,到最后发现其实挺快的。
我用的oh-my-zsh, 这次发现真是简单。
sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
一个补全的自定义插件 zsh-autosuggestions
它会根据你的历史技术补全,完美
安装教程
官方插件
oh-my-zsh还有一个插件系统,里面维护有很多的插件,你可以在 .oh-my-zsh/plugins/
目录下找到,
当你需要使用是只需要在 .zshrc
文件中田间:
plugins=(
git
zsh-autosuggestions
docker
)
添加完后命令会有很多补全,以及提示信息
react发现个有趣的问题,在下面四种写法中,只有第一种会渲染demo
显示demo
const Demo = (props) => <div {...props} class="demo" />;
<Demo>demo</Demo>
显示hello
const Demo = (props) => <div children="hello" class="demo" />;
<Demo>demo</Demo>
不显示任何信息
const Demo = (props) => <div class="demo" />;
<Demo>demo</Demo>
不显示任何信息
const Demo = () => <div class="demo" />;
<Demo>demo</Demo>
why?
发生上面的原因是因为createElement
函数
function createElement(type, config, children) {
var propName = void 0;
// Reserved names are extracted
var props = {};
var key = null;
var ref = null;
var self = null;
var source = null;
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// Remaining properties are added to a new props object
for (propName in config) {
if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
// 这里会把parent的children复制过来
props[propName] = config[propName];
}
}
}
// 这里如果自身有children会覆盖来自parent的
var childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);
for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
{
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
if (type && type.defaultProps) {
var defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
{
if (key || ref) {
var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
}
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
最近react-router的一个作者另外写了一个类react-router的组件@reach/router
,尝试后感觉太棒了。如果你的项目只是web端的话我认为可以把你的react-router换掉了。
下面是我到目前看到的所有非常好的点。
小,就4kb,压缩后比react-router小40kb左右。
更少的配置
a. react-router需要3个包(history
, react-router-dom
, react-router-redux
),reach-router只需要一个。
b. 不需要在store配置router相关信息
c. 不需要显示的使用history
// in store config file
//react-router
import { routerMiddleware } from 'react-router-redux';
import createHistory from 'history/createBrowserHistory';
const history = createHistory();
const middleware = routerMiddleware(history);
export { history };
//reach/router, nothing
更好用
a. 当你想跳转页面时
// react-router
import { push } from 'react-router-redux';
import { PropTypes } from 'prop-types';
// use it
this.context.store.dispatch(push('/see-you'));
FooComponent.contextTypes = {
store: PropTypes.object,
};
// reach/router
import { navigate } from "@reach/router";
navigate(`/lol`);
b.当你想取url里面的参数时
// react-router
import { withRouter } from 'react-router-dom';
import { PropTypes } from 'prop-types';
//use it
const { match: { params: { iAmHere } } } = this.props;
FooComponent.propTypes = {
match: PropTypes.object,
};
export default withRouter(FooComponent);
// reach/router
const { iAmHere } = this.props;
基本一样的api,学习成本非常低
源码非常简洁,总共就3个文件,900行,如果你想深入理解单页应用的路由是怎么实现的,reach-router,绝对是绝佳的下手资料。
不说了,去看看吧
https://github.com/reach/router
做网站的时候现在经常都会有国际化的要求,社区里面有很多相关的包可以用,最多被用的估计是react-intl,当然我们公司也不例外了,但是当我看着它出现在代码里面的时候真是不自在,一堆的api,需要很多的配置,而且很多其实平时根本就用不到,想了想根据最近react的apiContext实现一个挺容易的,于是我自己动手试了一下,然后有了react-i18context。
做这个包的初衷是为了保持简单,所以接口就4个,但是我觉得大多数的国际化都已经够用了。
// 在app最外层套一个,messages是翻译的一个数组对象, 然后初始化要使用的语言
<IntlProvider messages={messages} locale="en">
<App />
</IntlProvider>
// id 是你翻译文件里语言对象的key
<FormatMsg id="test" />
// 如果你的源文件的test是test: 'hello {name}',通过inject参数,test的值最终会变成‘hello inject'
<FormatMsg id="test" inject={{ name: 'inject' }} />
// locale的值根据你注入翻译文件时对象的key来定
<LocaleSet locale={lang}>
<button>change language</button>
</LocaleSet>
// FooComponent 的props里就会注入changeLan方法,然后你就可以自己选择调用的时机了。
InjectIntlLangWrapper(FooComponent)
在线demo
npm包
代码react-i18context,欢迎代码*扰
picker-view
不要放在组件中的组件,深层嵌套会导致@change事件bug@touchmove.stop
防止事件穿透该看源码了
https://developers.google.com/web/fundamentals/performance/resource-prioritization
待续。。。
最近需要对这本书做一个展示,所以想在这里将其梳理一下.
主要想讲的有3个点:
今天我想说的最最的核心**就是KISS, Keep it simple, stupid. 这个观点来至 Unix编程艺术, 在不断的编码中越来越认可这个概念.
Hello guys, today i want to talk about a book i already read before, The Pragmatic Programmer
From Journeyman to Master, Its like philosophy of how to write good and maintainable codes,i won't discuss all of them today, just three points, Software entropy
, Refactor
, Code reading
, after this presentation, i hope you guys can remember something, these three points, but three points i also think they are too many, i think one is enough, the key of these points are KISS
, This KISS
is not KISS
, its Keep it simple, stupid(This concept is from another), I love this concept very much as years passed.
软件熵, 熵的概念来至物理领域,谈的是一个系统里无序的量,软件熵是指一个软件系统里无序的的量,对我们来说就是一个项目里无序的量, 熵越大我们的项目就越不好管理,当需求增多的时候,代码量肯定会上升几个level,代码量的上升本来已经使软件的熵开始不可避免的上升,如果我们写的代码还不好的话,将会使本来呈线性上升的熵变成几何级的上升,因而我们应该从代码的书写上去尽量缩小熵的增加量,
书上有说一个大家熟知的概念: 破窗理论,当一扇窗户坏后不及时修好的话其他的窗户也会很快就坏掉,如果能即使修理好,那其他窗户坏的几率就会比较小,写代码也是一样,当我们在代码中闻到坏味道时一定要立马停下来,修好它,然后再做接下来的工作,什么是bad smell, 重复的, 没有遵守最佳实践的,不可阅读的,到处都是bug.
(这个就像人生一样,随着我们的长大,读书,工作,结婚,孩子,父母渐老,一件件的接踵而来,如果我们先前的没处理好,新的麻烦有出现,一件赶着一件,最后我们自己都会乱掉)
Okay, let's go to the first point, software entropy,its the amount of "disorder" in a system, what's this? think about our life, as we grow up, there are many things come to us in every period, study, work, marriage, children, older parents, which will make our life hard. Coding is the same,when the project become bigger and bigger, there will be more pages, more complexity, more codes, then bugs are coming very soon if you can't organize them.
当发现我们的代码有问题的时候怎么做呢?重构,使用有意义的名称,函数的长度尽量控制在一个屏,大于一个屏的时候就需要去检查是不是需要拆分成小函数.就像有 很多方法去达到目的一样,我们要找的是最佳方法,写代码也是一个道理,这对于后期bug的修复以及项目的维护都非常有帮助.
(这个就像处理我们生活中的麻烦事,每当遇到麻烦时我们就要找相应的方法去处理)
when these happened, what should we do? In life, I think everybody has his/her rule to handle this, otherwise we can't stand here, In coding, we can do refactor, which can help us to control the software entropy, let it increase not too fast and in our control, for example, make the code readable and maintainable, something like this.
怎么做好重构呢?懂得一些原则,然后阅读源码,阅读源码,阅读源码,talk is cheep, show me your code, 很多好的开源项目都值得我们去阅读,能在开源社区的众多项目中脱颖而出说明肯定是有很多可取之处,当阅读的源码多了写代码就会有顿悟的感觉,其实就是之前的阅读累计下来的那些好的规范的体现,读吧。
(生活中也是一样,You can find gold and beauty in books, 通过读书你可以发现很多好的方法去解决你以前不知道怎么解决的问题)
In life, how could we make us to handle these problem easier and better. as the saying goes in china, you can find gold and beauty in books, reading is a good way to get methods and thoughts.In coding, we should read other guys codes. we can find what we miss in ourself code and make it better.
回到我们最终的i原则,KISS. 希望大家写代码的时候都可以做到keep it simple, stupid, 控制软件熵,重构有坏味道的代码.并通过阅读代码去提升重构的技巧.
That's all, if you don't understand these content, it's okay, just remember one thing, KISS, keep it simple, stupid, all things should be around this center.
算法算是计算机的一本基本功了,由于自己不是科班出生,所以一直比较注重基础方面的知识。算法也看过好几本书了,最难啃的可能是 算法 第四版 ,当时看到那本书的时候,真是恨不得全部一天啃完,图太美。陆陆续续的看到了第五章,后面的就没有看了。当然你现在如果问我快排、二分查找这些还是印象比较深刻的,我觉得这些属于比较基础的一类。但是如果要说到图,树相关的可能就有点难了,因为这些当初学的时候可能最多就达到刚刚弄清楚的样子,但是后面在工作中用的又极少,所以基本都忘了(不是张无忌那个忘了)。因此想借这一篇文章做一个梳理,把算法第四版上面的算法整理一遍,希望让自己对算法的掌握提高一个段位。
按照 算法 第四版 对算法的分类,有 排序,查找,图,字符串这几个大类,我就按这个来做梳理吧。
最近有看到一篇文章,谈到编程的本质问题.里面的观点是:
Program = Logic + Control + Data Structure
我觉得说得挺对的,如果能够把这几个组成部分的逻辑分开的话程序就会变得非常的清晰并且更好维护。借这次终结算法的机会希望也一并能思考一下这些问题。
准备写一系列前端优化相关的文章。
这边文章先概述一下我接下会写的相关内容。
前端现在越来越酷炫,交付也越来越复杂,饭碗也越来越结实(意淫中)。随之而来的所需要的资源也会越来越多。字体想用自己想用的,放更多的图片,可能还有js写的图表,无限向下滚动,再来点canvas...当用户浏览页面的时候,如果需要好几秒才能下载完并可以交互,那会非常的影响交互。
因此写完代码后会有多地方需要优化,当然我们在写代码的过程中就应该考虑这些优化,提前做,这会给后面节约很多时间。
在之后的文章里我会谈到到现在为止我所学的优化的相关知识,后面学到新的时候在补充相应的。
现在初步准备写以下内容:
1.图片
2.字体
3.css
4.js
5.缓存
写下这些一是在将来好回顾,另外也是对这些知识的加深和巩固
相应的代码我会放在pwa_example的commit中,每一节会提交2个commits,分别以start,end结尾
现有的大多数知识都是来自:
android的生命周期函数(官方api),感觉大的方面各个框架有类似的地方
Adapter需要实现3个方法
@Override
public ForecastAdapterViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return null;
}
@Override
public void onBindViewHolder(ForecastAdapterViewHolder holder, int position) {
}
@Override
public int getItemCount() {
return 0;
}
19年初,期待以久的hooks在16.8上稳定了。在这之前我也尝试过了这些新的特性,真的很好用,完全可以取带现有的class,让有副作用的代码类似请求也能达到复用的效果。以下是我自己的一些感受。
hooks最最重要的两个的api是useState和useEffect, 顾名思义,useState是用与管理状态的,useEffect是与副作用相关的。
上一段官网的代码
import React, { useState } from 'react';
function Example() {
// 这里通过解构语法获取state和改变state的方法
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
看看是不是很很简介,想想如果像以前一样如果要使用state,那必须使用class,而且必须使用constructer申明,也写一下
import React from 'react';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state {
count: 0,
};
}
render() {
const { count } = this.state;
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => this.seState({ count: count + 1})}>
Click me
</button>
</div>
);
}
}
有了useState,我看你觉得不会想回去写class了,并且hooks还可以让你自定义hooks,让你的代码复用,state可能吧,醒醒,不要想了。
另外一个接口是useEffect,就我看教程了解到的最直观的好处是代码更想组件了,还是看官网的例子
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
看到没有订阅和取消订阅再一个地方的,并且相关没有,如果这个函数只有订阅的代码的话它是不是可以复用的,像一个组件一样,如果是之前的class你要怎么做,最多是用高阶函数,但我是感觉那个嵌套太多了,之前我也尝试过去写,但后面还是不了了之了。useEffect大法好。
另外有一点useEffect还可以设置签署去确定它是否需要更新.
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, [props.friend.id]);
上面的代码只有在props.friend.id
变化的时候才会去调用函数,如果你只想这个函数在mount和unmount执行的话,只需要给useEffect的第二个参数设置为空数组就可以了。
官方提供的api还有很多,有兴趣的可以自己去看看,前端无止境呀,学起来
另外react作者还把相关的源码做了整理,方便查看,点我去看看
在一个页面分享时,配置jssdk时,必须以当前页面的url进行配置,其实这个看文档仔细点不会有问题。并且网上说的单页应用ios和android的表现不一样,我只想说他们都是假的!假的,行为是一样的,配置时使用正确的url,正确的url.
配置jssdk时,URL请一定用官方文档推荐的 encodeURIComponent(location.href.split('#')[0])
, 如果你的URL有类似这样的参数?nsukey=WM9rS1D01dlCXs144+
, 不要问为什么会有,当你在微信内访问你的网站时就微信可能就会给你加个这个乱七八糟的参数,扎心。。。
测试的问题,我不知道每个人是不是都像我之前那样傻傻的想要测试比如分享的时候,一定要将代码push到测试环境或哪里,然后在微信后台配置url去测试。只想说真的很恶心这样,我都快被我自己恶心到了,后来发现其实不用这么蛋疼的,本地都可以直接调试。
安装localtunnel/localtunnel, 通过它可以让外网的人访问你本地的服务。并且是固定URL,之前用过ngrok, 不过这个付费版才能固定url, 安装完后,跑
lt -p 80 -s hulin
它会生成一个这样的URL: https://hulin.localtunnel.me
把它放到测试账号里,开始你的调试吧,手动给自己点赞。
重现方法
页面a -> 页面b -快速重定向到->页面b(url完全一致),页面的icon font会不显示。
原因猜想:跳转太快,支援还没有渲染好,当重定向回来时会重用之前的页面,倒置页面icon显示出现问题。
解决方法:
延迟页面的重定向,
setTimout((/*redirection codes*/), 100);
都在代码里了:
https://github.com/flyingalex/react-related-source-code-reading/tree/master/src/redux
整体看下来其实不难,我觉得难的应该是理解里面的**吧,也不知道自己是不是算理解了
这是一篇译文,原文在这里,英文好的可以去阅读原文,已获得作者同意翻译本文。
第一次翻译这么长的,如有错误,请指出,一定修改。
最好的网络请求就是永远都不会发生的请求: 想要获得更快的网站时,避免网络请求会远胜于有网络。因此,拥有一个可靠的缓存策略可以让你的用户感觉非常不一样。
话虽如此,但在我的工作中,越来越多的时候我看到是不考虑甚至完全忽视缓存实践。也许是因为第一次看见时没有过度关注,或者可能是因为缺乏这样的意识或知识?不管怎样,让我们来复习一下。
一个最普遍、有效的管理资源缓存的方法是通过HTTP的头部字段Cache-Control
,这个头部可以应用到每一个网站的资源,这意味着我们页面上的所有内容都可以有一个非常定制和精细的缓存策略。这也使得其非常复杂和强大。
一个Cache-Control
头部可能看起来像这样:
Cache-Control: public, max-age=31536000
Cache-Control
是字段名,public
和max-age=31536000
是指令。Cache-Control
可以接受多个指令,在这篇博客中我会介绍这些指令以及它们的用例
public
意味着任何可以缓存的地方都会缓存一份响应的数据。包括CDN,代理服务器等。public
通常都是多余的,因为其他的指令(比如max-age
)已经隐含它了。
相反,private
指令只允许在相应的接受方(客户端或浏览器)保存一份。虽然private
不是一个安全相关的指令,但是它可以防止公共缓存(比如CDN)缓存一些用户相关的信息
max-age
定义了一段时间(相对于请求时)来告诉浏览器相应是否需要刷新
Cache-Control: max-age=60
这条指令会告诉浏览器,它可以在接下来的60秒内使用缓存中的这个文件,而不必重新验证它。60秒后,浏览器将服务器发请求重新验证文件。
如果服务器有一个新文件供浏览器下载,它会返回状态码200,下载新文件,并替换掉HTTP缓存中旧文件,并按照指令缓存文件。
如果服务器没有需要下载的更新副本,服务器会以304响应,并更新缓存的时间。这意味着,如果Cache-Control: max-age=60
仍然存在,缓存文件还将缓存60秒。总共缓存120秒。
注意: 如果单单使用max-age
会有一个非常大的风险…max-age
可以告诉浏览器资源是否已经过时,但是它不能告诉浏览器它是否可以使用过时的版本。浏览器可能在没有验证的情况下使用过期的版本。这种行为有些不确定性,导致我们很难判断浏览器究竟做了什么。幸好,我们有一系列更明确的指令,我们可以用这些指令来增强max-age
。感谢Andy Davies
的帮助
The s-maxage (note the absence of the - between max and age) will take precedence over the max-age directive but only in the context of shared caches. Using max-age and s-maxage in conjunction allows you to have different fresh durations for private and public caches (e.g. proxies, CDNs) respectively.
s-maxage
(注意没有"-"在max
和age
之间)将优先于max-age
指令,但仅在共享缓存的情况下有效。结合使用max-age
和s-maxage
,你可以分别对私有缓存和公共缓存(例如代理、CDN)拥有不同的缓存刷新时间。
Cache-Control: no-store
如果我们不想缓存文件怎么办?如果文件包含敏感信息怎么办?也许这是一个包含你银行详细信息的HTML页面?又或者信息是实时的?也又可能是一个包含实时股票价格的页面?我们肯定不想缓存或从缓存中获取这样的信息: 我们总是希望清除敏感的信息,然后获取最新的实时信息。现在我们需要用no-store
。
no-store
是一条非常强大的指令,告诉浏览器或其他设备不要缓存任何信息。任何带有这条指令的资源,无论在什么情况下都会发起请求。
Cache-Control: no-cache
这个是让大多数人困惑的。no-cache
并不意味着没有缓存。它的意思是在你向服务器重新验证缓存副本,并且服务器表示你可以使用缓存副本之前,不要使用缓存。是的,这听起来应该叫做必须重新验证!只是听起来也不是这样。
no-cache
实际上是一种非常聪明的方式,可以保证内容始终是最新的,但是如果可能的话,也可以使用更快的缓存策略。no-cache
总是会发起网络请求,因为在返回浏览器的缓存之前,它必须与服务器重新验证(除非服务器有更新的响应内容),但是如果服务器的响应良好,网络传输只是文件的头部:可以从缓存中抓取主体,而不是重新加载。
所以,就像我说的,这是一种将获取最新资源和从缓存中获取文件的可能性结合起来的聪明方法,但是它会发出网络请求,至少会有HTTP头响应。
一个好的no-cache
用例几乎是任何动态HTML页面。想想一个网站的首页:它不是实时的,也不包含任何敏感信息,但是理想情况下,我们希望页面总是显示最新的内容。我们可以使用cache-control: no-cache
来指示浏览器首先检查服务器,如果服务器没有更新的内容( 304 ),我们就使用缓存版本。如果服务器确实有一些更新的内容,它会照原样做出响应( 200 )并发送更新的文件。
提示:在发送no-cache
指令的同时发送max-age
指令是没有用的,因为重新验证的时间限制是零秒。
更令人困惑的是,虽然上面的那条指令听起来应该被称为must-revalidate
,但事实上must-revalidate
是不同的(但相似)。
Cache-Control: must-revalidate, max-age=600
must-revalidate
需要相关的max-age
指令;上面代码中,我们把它设置为十分钟。
其中no-cache
将立即与服务器进行重新验证,并且只有在服务器认为可以的情况下才使用缓存,must-revalidate
就像有宽限期的no-cache
一样。在最初的十分钟里,浏览器不会发请求去服务器重新验证,但是十分钟过后,就会发请求去服务器验证了。如果服务器说没有什么新的,它会以304作为响应,并且新的缓存控制头会应用到缓存中—十分钟后才会再次发送请求。如果十分钟后,服务器上有一个更新的文件,我们会得到一个200的响应和它的内容,并且本地缓存会得到更新。
must-revalidate
的一个很好的例子就是像我这样的博客:有很少改变的静态页面。当然,最新的内容是可取的,但是考虑到我的网站变化的频率,我们不需要像no-cache
一样频繁的检测更新。十分钟验证一次足够了。
与s-maxage类似,proxy-revalidate
是must-revalidate
的一个只对公共缓存起作用的特定版本。
immutable
一个非常新且简洁的指令,它告诉浏览器更多关于我们发送的文件的类型——它的内容是可变的还是不可变的?但是,在我们看immutable
怎么用之前,让我们先看看它正在解决的问题:
用户刷新会导致浏览器重新验证文件,而不管文件是否是最新的,因为用户刷新通常意味着以下两种情况之一:
那么让我们检查一下服务器上是否还有更新的东西。
如果服务器上有更新的文件,我们肯定想下载它。因此,我们将得到200个响应、一个新文件,并且——希望——问题得到解决。然而,如果服务器上没有新文件,我们将返回一个304头部,没有新文件,而是一个完整的往返延迟。如果我们重新验证许多文件,导致很多的304,这可能会增加数百毫秒的不必要的开销。
immutable
是一种告诉浏览器文件永远不会改变的方式,它是不可变的,因此永远不需要重新验证它。我们可以完全减少往返延迟的开销。我们所说的可变或不可变文件是什么意思呢?
- style.css: 当我们改变这个文件的内容时,我们根本不改变它的名字。文件总是存在的,它的内容总是变化的。这个文件是可变的。
- style.ae3f66.css: 这个文件是独一无二的——它是以基于内容的指纹命名的,所以当内容改变的时候,我们得到了一个全新的文件。这个文件是不可变的。
我们将在缓存刷新部分详细讨论这一点。
如果我们能够以某种方式向浏览器传达我们的文件是不可变的——它的内容永远不会改变——那么我们也可以让浏览器知道,它不需要费心去检查更新的版本:永远不会有更新的版本,因为当文件的内容改变时,它就不再存在了。
这正是不可变指令的作用:
Cache-Control: max-age=31536000, immutable
在支持immutable
的浏览器中,用户刷新永远不会在31536000秒�内导致重新验证。这意味着不会花费不必要的往返时间在304响应上,这可能会在关键路径上为我们节省大量延迟( CSS块渲染)。在高延迟连接上,这种节省可能是显而易见的。
注意: 你不应该把immutable
应用到任何不是不可变的文件。您还应该有一个非常强大的缓存刷新策略,这样您就不会无意中主动缓存一个应用了不可变的文件。
我真的,真的希望stale-while-revalidate
能得到有更好的支持。
到目前为止,我们已经谈了很多关于重新验证的事情: 浏览器发请求到服务器检查是否有文件更新。在高延迟连接上,仅重新验证的持续时间就已很明显,并且该时间就是死时间——在我们从服务器得到响应之前,我们既不能释放缓存(304),也不能下载新文件(200)。
stale-while-revalidate
提供了一个宽限期(由我们定义),在宽限期内,当我们检查更新版本时,允许浏览器使用过期资源。
Cache-Control: max-age=31536000, stale-while-revalidate=86400
这条指令告诉浏览器,“这个文件可以使用一年,但是在那一年结束后,你有一个额外的星期可以继续使用这个过时的资源,同时在后台重新验证它”。
对非关键资源来说,stale-while-revalidate
是一条很好的指令,当然,我们想要最新的版本,但是我们知道,如果我们在检查更新时使用过期的资源,不会造成任何损害。
以类似于tale-while-revalidate
的方式,stale-if-error
允许浏览器有一个宽限期,在该宽限期内,如果重新验证的资源返回5xx类错误,你可以返回一个过期的资源。
Cache-Control: max-age=2419200, stale-if-error=86400
这条指令,我们指示缓存文件在28天(2419200秒)内是新的,并且如果在此之后遇到错误,我们将允许额外的一天(86400秒),在此期间我们返回过期资源。
谈论缓存而不谈论缓存刷新是不负责任的。我总是建议在考虑缓存策略之前先解决缓存刷新策略。反之则非常头痛。
缓存刷新是为了解决这样一个问题: 我刚刚告诉浏览器在下一年里使用这个文件,但是我刚刚改变了它,我不想让用户等一整年后才得到新的文件!我该怎么能干预呢?!
这是最不可取的做法: 这是绝对不会刷行缓存的。这是一个可变文件,我们很难缓存它。
您应该非常小心地缓存这样的文件,因为一旦它们出现在用户的设备上,我们几乎就失去了对它们的所有控制。
尽管这个例子是一个样式表,但是HTML页面也完全属于这个阵营。我们不能改变网页的文件名——想象一下这会造成多大的影响!—这就是为什么我们倾向于根本不缓存它们。
这里,我们仍然有一个可变的文件,但是我们在它的文件路径中添加了一个查询字符串。虽然比什么都不加要好,但它仍然不完美。如果在中间的什么步骤去掉了查询字符串,我们就可以回到之前的情况,即完全没有缓存刷新。许多代理服务器和CDN不会缓存带有字符串的任何内容(例如,从Cloudflare自己的文档就说过对"style.css?something"的请求会被替换为"style.css"),可能是配置,也可能是处于防御性地目的(查询字符串可能包含特定于一个特定响应的信息)。
指纹识别是缓存刷新文件的首选方法。通过每次内容改变时都改变文件名,我们在技术上不会缓存任何东西:我们最终会得到一个全新的文件!这非常健壮,并且允许使用不可变的。如果您可以在静态资产上实现这一点,请这样做!一旦您成功实现了这种非常可靠的缓存刷新策略,您就可以使用最激进的缓存形式:
Cache-Control: max-age=31536000, immutable
这个方法的关键是改变文件名,但它不一定是指纹。以下所有示例都具有相同的效果:
然而,最后一个例子意味着我们对每个版本进行版本控制,而不是对每个单独的文件进行版本控制。这反过来意味着,如果我们只需要缓存我们的样式表,我们还必须缓存该版本的所有静态文件。这是潜在浪费,所以最好选择(1)或(2)。
缓存失效很难——众所周知如此——所以目前有一个规范正在帮助开发人员非常明确地一次性清除他们站点的整个缓存:Clear-Site-Data
。
我不想在这篇文章中讲太多细节,因为Clear-Site-Data
不是缓存控制指令,而是一个全新的HTTP头。
Clear-Site-Data: "cache"
将该头应用到您的任何一个源资产将清除整个源的缓存,而不仅仅是它所附加的文件。这意味着,如果你需要从所有访问者的缓存中清除你的整个站点,你可以只将上面的标题应用到你的网页上。
在撰写本文时,浏览器支持仅Chrome, Android Webview, Firefox, 和 Opera。
提示: Clear-Site-Data将接受许多指令:"cookies"、"storage"、"executionContexts"和"*"(表示所有上述内容)。
好吧,让我们来看一些可能使用Cache-Control
头部的场景。
像网上银行应用程序页面,列出你最近的交易,当前的余额,也许还有些敏感的银行账户信息,需要是最新的(想象一下,你收到了一个列出你一周前余额的页面!)并确保不被他人看见(您肯定不希望您的银行详细信息存储在共享缓存(或者任何缓存,真的) )。
为此,我们可以使用:
Request URL: /account/
Cache-Control: no-store
根据规范,这足以防止浏览器在私有和共享缓存中长久的保存响应:
no-store
告诉缓存不准存储请求或响应的任何部分。该指令适用于私有和共享缓存。"不准"在这里意味着缓存不得有意将信息存储在非易失性存储器中,并且必须尽最大努力在转发信息后尽快将其从易失性存储器中移除。
但是如果你想有更强的防御性,也许你可以选择:
Request URL: /account/
Cache-Control: private, no-cache, no-store
这将明确指示不要在公共缓存(例如CDN )中存储任何内容,要始终提供尽可能最新的副本,并且不要将任何内容保存到存储中。
如果我们正在构建一个显示近实时信息的页面,如果这些信息存在的话,我们希望确保用户总是能看到我们能给他们的最好的、最新的信息。我们可以使用:
Request URL: /live-updates/
Cache-Control: no-cache
这个简单的指令意味着浏览器在与服务器确认缓存是否被允许使用前不会直接从缓存中显示响应。这意味着用户永远不会看到过时的列车信息,但是如果服务器要求缓存镜像最新的信息,他们就可以从从缓存中获取。
对于几乎所有网页来说,这通常是一个明智的默认设置:给我们最新的可能内容,但是如果可能的话,让我们使用缓存来加速。
像FAQ这样的页面很可能很少更新,而且其中的内容也不太可能对时间敏感。它当然没有实时运动成绩或飞行状态那么重要。我们可能可以缓存这样的页面一段时间,并强制浏览器定期检查新内容,而不是每次访问。让我们看看:
Request URL: /faqs/
Cache-Control: max-age=604800, must-revalidate
这告诉浏览器将网页缓存一周( 604800秒),一旦这一周结束,我们就需要向服务器查询是否需要更新。
注意:在同一个网站中对不同的页面采用不同的缓存策略可能会导致这样一个问题: 您的 no-cache
主页请求它引用的最新样式,f4fa2b.css,但是您的要缓存三天的FAQ页面仍然指向样式a3f66.css。这可能会有轻微的影响,但这是一个您应该注意的场景。
假设我们的app.[fingerprint].js文件更新非常频繁——可能在我们发布的每一个版本中都是如此——但是我们也在每次文件发生变化时都会根据内容对其重命名(这非常好),然后我们可以这样做:
Request URL: /static/app.1be87a.js
Cache-Control: max-age=31536000, immutable
我们是否非常频繁地更新JS并不重要:因为我们有可靠地缓存刷新,所以我们可以想缓存多久就缓存多久。在这种情况下,我们选择缓存一年。我选择了一年,因为首先,一年是很长的时间,其次,浏览器实际上不太可能将文件保存那么长时间(浏览器有有限的存储空间可以用于HTTP缓存,所以它们自己会定期清空部分文件;用户可能也会清除他们缓存)。任何超过一年的设置不会更有效。
此外,因为这个文件的内容从不改变,我们可以让浏览器知道这个文件是不可变的。即使用户刷新了页面,我们也不需要在一年内对它进行重新验证。我们不仅获得了使用缓存的速度优势,还避免了重新验证的延迟损失。
想象一张纯粹装饰性的照片伴随着一篇文章。它不是信息图或图表,它不包含任何对理解页面其余部分至关重要的内容,用户甚至不会真正注意到它是否完全丢失了。
图像通常是需要下载的非常重的资源,所以我们想缓存它;它对页面并不重要,所以我们不需要获取最新版本;我们甚至有可能在图像有点过时后就不再提供图像了。让我们这样做:
Request URL: /content/masthead.jpg
Cache-Control: max-age=2419200, must-revalidate, stale-while-revalidate=86400
在这里,我们告诉浏览器将图像存储28天( 2419200秒),我们要在28天的时间限制后向服务器查询更新,如果图像过期不到一周( 86400秒),我们在后台获取最新版本时使用该图像。
immutable
和 stale-while-revalidate
不仅给我们带来了缓存的传统优势,还允许我们在重新验证时降低延迟成本。尽量避免网络请求,可以让我们的用户获得更快的体验(同时降低基础设施的吞吐量)。通过对我们的资源有一个好的看法,并对我们可用的东西有一个概述,我们可以开始为我们自己的应用程序设计非常精细、定制和有效的缓存策略。
用缓存管理一切
在别人在Hacker News上喷我虚伪之前,我想说的是我自己的缓存策略非常不好,我甚至都不打算去做。
先看简单的的吧,FixedSizeList在底层调用了createListComponent, createListComponent是一个高阶函数,返回一个PureComponent
export default function createListComponent({
getItemOffset,
getEstimatedTotalSize,
getItemSize,
getOffsetForIndexAndAlignment,
getStartIndexForOffset,
getStopIndexForStartIndex,
initInstanceProps,
shouldResetStyleCacheOnItemSizeChange,
validateProps,
}: {|
getItemOffset: GetItemOffset,
getEstimatedTotalSize: GetEstimatedTotalSize,
getItemSize: GetItemSize,
getOffsetForIndexAndAlignment: GetOffsetForIndexAndAlignment,
getStartIndexForOffset: GetStartIndexForOffset,
getStopIndexForStartIndex: GetStopIndexForStartIndex,
initInstanceProps: InitInstanceProps,
shouldResetStyleCacheOnItemSizeChange: boolean,
validateProps: ValidateProps,
|}) {
return class List<T> extends PureComponent<Props<T>, State> {
.....
别怕,我看着也头晕,作者还使用了flow类型,我也没学过,但是感觉跟ts差不太多,还是可以看得懂。
然后我们根据声明周期来看看这个高阶函数吧,我就挑我认为重要的讲了.
在getDerivedStateFromProps
会对参数做一些验证,因为其会在render方法前调用,放这里验证也比价合理
然后在render
里面,
const [startIndex, stopIndex] = this._getRangeToRender();
const items = [];
if (itemCount > 0) {
for (let index = startIndex; index <= stopIndex; index++) {
items.push(
createElement(children, {
data: itemData,
key: itemKey(index, itemData),
index,
isScrolling: useIsScrolling ? isScrolling : undefined,
style: this._getItemStyle(index),
})
);
}
}
这里生成的items
就是最终渲染在页面上的内容,这里巧的就是通过控制startIndex, stopIndex,只是渲染一部分的内容到页面上,所以即使有再多的数据,渲染的内容都是一样的,然后看看_getRangeToRender
方法
_getRangeToRender(): [number, number, number, number] {
const { itemCount, overscanCount } = this.props;
const { isScrolling, scrollDirection, scrollOffset } = this.state;
if (itemCount === 0) {
return [0, 0, 0, 0];
}
// 该方法是使用时才从父组件传下来的
const startIndex = getStartIndexForOffset(
this.props,
scrollOffset,
this._instanceProps
);
// 该方法是使用时才从父组件传下来的
const stopIndex = getStopIndexForStartIndex(
this.props,
startIndex,
scrollOffset,
this._instanceProps
);
// Overscan by one item in each direction so that tab/focus works.
// If there isn't at least one extra item, tab loops back around.
const overscanBackward =
!isScrolling || scrollDirection === 'backward'
? Math.max(1, overscanCount)
: 1;
const overscanForward =
!isScrolling || scrollDirection === 'forward'
? Math.max(1, overscanCount)
: 1;
return [
Math.max(0, startIndex - overscanBackward), // 往前加一个
Math.max(0, Math.min(itemCount - 1, stopIndex + overscanForward)), // 往后再加一个
startIndex,
stopIndex,
];
}
getStartIndexForOffset: (
{ itemCount, itemSize }: Props<any>,
offset: number
): number =>
Math.max(
0,
// 简单的将移动的个数算出来
Math.min(itemCount - 1, Math.floor(offset / ((itemSize: any): number)))
),
getStopIndexForStartIndex: (
{ direction, height, itemCount, itemSize, layout, width }: Props<any>,
startIndex: number,
scrollOffset: number
): number => {
const isHorizontal = direction === 'horizontal' || layout === 'horizontal';
// itemSize 是每个item的大小的高或宽,根据isHorizontal 来决定
const offset = startIndex * ((itemSize: any): number);
const size = (((isHorizontal ? width : height): any): number);
return Math.max(
0,
Math.min(
itemCount - 1,
startIndex +
Math.floor(
// 这里的算法就是用区域的大小再加上还未完全移出的那一个的部分大小
// 不知道为什么要加那一部分,尝试后没有影响
(size + (scrollOffset - offset)) / ((itemSize: any): number)
)
)
);
},
然后重要的就是监听onScroll来改变scrollOffset的值了
当是垂直滚动时
_onScrollVertical = (event: ScrollEvent): void => {
const { clientHeight, scrollHeight, scrollTop } = event.currentTarget;
this.setState(prevState => {
if (prevState.scrollOffset === scrollTop) {
// 如果移动距离是一样的就不更新了
return null;
}
// Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds.
// scrollTop 就是滚动出去的大小
// clientHeight 是区域的大小,
// 其实这里一直都会等于scrollTop
const scrollOffset = Math.max(
0,
Math.min(scrollTop, scrollHeight - clientHeight)
);
return {
isScrolling: true,
scrollDirection:
prevState.scrollOffset < scrollOffset ? 'forward' : 'backward',
scrollOffset,
scrollUpdateWasRequested: false,
};
}, this._resetIsScrollingDebounced);
};
当是水平滚动时
_onScrollHorizontal = (event: ScrollEvent): void => {
const { clientWidth, scrollLeft, scrollWidth } = event.currentTarget;
this.setState(prevState => {
if (prevState.scrollOffset === scrollLeft) {
// 如果移动距离是一样的就不更新了
return null;
}
const { direction } = this.props;
let scrollOffset = scrollLeft;
if (direction === 'rtl') {
const isNegative = isRTLOffsetNegative();
// 这里是当向右阅读时scrollOffset的不同
// TRICKY According to the spec, scrollLeft should be negative for RTL aligned elements.
// This is not the case for all browsers though (e.g. Chrome reports values as positive, measured relative to the left).
// It's also easier for this component if we convert offsets to the same format as they would be in for ltr.
// So the simplest solution is to determine which browser behavior we're dealing with, and convert based on it.
if (isNegative) {
scrollOffset = -scrollLeft;
} else {
scrollOffset = scrollWidth - clientWidth - scrollLeft;
}
}
// Prevent Safari's elastic scrolling from causing visual shaking when scrolling past bounds.
scrollOffset = Math.max(
0,
Math.min(scrollOffset, scrollWidth - clientWidth)
);
return {
isScrolling: true,
scrollDirection:
prevState.scrollOffset < scrollLeft ? 'forward' : 'backward',
scrollOffset,
scrollUpdateWasRequested: false,
};
}, this._resetIsScrollingDebounced);
};
和前面 #25 一样,最都是调用的createListComponent
,不一样的地方就是调用那个函数时传入的方法,我们来一起看看。
主要的就是getStartIndexForOffset
和getStopIndexForStartIndex
两个函数了
// 主要调用了findNearestItem
getStartIndexForOffset: (
props: Props<any>,
offset: number,
instanceProps: InstanceProps
): number => findNearestItem(props, instanceProps, offset),
const findNearestItem = (
props: Props<any>,
instanceProps: InstanceProps,
offset: number
) => {
const { itemMetadataMap, lastMeasuredIndex } = instanceProps;
console.log('offset', offset);
const lastMeasuredItemOffset =
lastMeasuredIndex > 0 ? itemMetadataMap[lastMeasuredIndex].offset : 0;
if (lastMeasuredItemOffset >= offset) {
// 如果查找过就直接使用二分查找
return findNearestItemBinarySearch(
props,
instanceProps,
lastMeasuredIndex,
0,
offset
);
} else {
// 如果没有就使用指数搜索
return findNearestItemExponentialSearch(
props,
instanceProps,
Math.max(0, lastMeasuredIndex),
offset
);
}
};
二分查找我猜很多人都看过了,通过查找中位数来折半查找,非常的快,来主要看看指数查找
const findNearestItemExponentialSearch = (
props: Props<any>,
instanceProps: InstanceProps,
index: number,
offset: number
): number => {
const { itemCount } = props;
let interval = 1;
// 这里主要就是找到那个偏移大于现在的点
while (
index < itemCount &&
getItemMetadata(props, index, instanceProps).offset < offset
) {
index += interval;
interval *= 2;
}
// 找到那个点后就可以使用二分查找了
return findNearestItemBinarySearch(
props,
instanceProps,
Math.min(index, itemCount - 1),
Math.floor(index / 2),
offset
);
};
接下来再看看getStopIndexForStartIndex
getStopIndexForStartIndex: (
props: Props<any>,
startIndex: number,
scrollOffset: number,
instanceProps: InstanceProps
): number => {
const { direction, height, itemCount, layout, width } = props;
const isHorizontal = direction === 'horizontal' || layout === 'horizontal';
// 可见区域的滑动方向的长度
const size = (((isHorizontal ? width : height): any): number);
const itemMetadata = getItemMetadata(props, startIndex, instanceProps);
// 距离第一个的最大距离
const maxOffset = scrollOffset + size;
// 可见区域里第一个的偏移
let offset = itemMetadata.offset + itemMetadata.size;
let stopIndex = startIndex;
while (stopIndex < itemCount - 1 && offset < maxOffset) {
stopIndex++;
offset += getItemMetadata(props, stopIndex, instanceProps).size;
}
return stopIndex;
},
这章我们来聊聊图片的优化。
图片现在基本每个网站都会有,并且还不少,网站logo,图表,产品展示图,这是我们首先需要优化的,而且我觉得这些优化其实难度不大,大多是体力活。应该图片格式不一样所需要使用的工具也不一样,所以不好统一用工具去生成(其实还是有的,后面会介绍到,只是效果不如针对不同格式的优化工具好),因为生成出来图片有的会糊掉。但是如果多的话还是建议这样去生成,再去筛查。
前面说到图片格式不一样,这里谈谈图片的种类,从大类来分有矢量图和栅格图两种,就我现在的了解,矢量图是有很多线条组成,是可以任意收缩大小的,所以表现复杂的图像就显得有点吃力了,栅格图片就主要展示复杂颜色的图片啦,比如相机图片.所以应该根据你要显示的内容选择相应的图片格式。 矢量图格式一般为SVG, 栅格图片格式有JPEG,PNG, GIF,新的格式有WebP等.
1.SVG优化
压缩工具有如下:
svgo
在线工具
sketch svgo
我找svg图片试过,根据图片的不同,优化的效率也不大一样。我用了两张不同大小的图片进行了压缩(图片在这里),分别压缩了25%和50%,所以感觉这样的工具还是挺好的。
2.JPG优化
有两个imagemin插件
imagemin-mozjpeg
imagemin-guetzli
(我是google官网的例子代码)
3.PNG优化
有两个imagemin插件
imagemin-pngquant
imagemin-advpng
当然如果可以的话可以用更新的格式WebP,更好的压缩率。
记得服务器开启gzip压缩,这个见效很好。
如果使用webpack的话,也有相应的插件(没用过)可以用于图片优化
服务器端开启文件缓存,尽量不重复获取相同的数据。
如果你在img标签中这样使用
<img src="photo.png" srcset="[email protected] 2x">
那么在像素高的设备上就会读取更高清的图片
另外还有就是picture标签
<picture>
<source media="(min-width: 800px)" srcset="head.jpg, head-2x.jpg 2x">
<source media="(min-width: 450px)" srcset="head-small.jpg, head-small-2x.jpg 2x">
<img src="head-fb.jpg" srcset="head-fb-2x.jpg 2x" alt="a head carved out of wood">
</picture>
会使用媒体查询并且根据分辨率的不同来显示图片
添加 alt 语义化,图片命名
添加aria-label等标签(后面会专门写一张)
这里以Ramda举例:
import * as R from 'ramda';
const arrayData = [
{
attributes: {
uri: { url: 'test' },
}
}
];
const DOMAIN = 'http://domain.com/';
// normal way
function addDomainForUrl(included) {
return included.map(item => {
const obj = item;
if (obj.attributes.uri) {
obj.attributes.uri.url = `${DOMAIN}${obj.attributes.uri.url}`;
}
if (obj.attributes.url) {
obj.attributes.url = `${DOMAIN}${obj.attributes.url}`;
}
return obj;
});
}
// addDomainForUrl(arrayData);
// functional way
const addDomain = (url) => `${DOMAIN}${url}`;
const transformations = {
attributes: {
url: addDomain,
uri: {
url: addDomain
}
},
}
//R.map(R.evolve(transformations), arrayData);
在说算法前谈谈数据结构,其实好的数据结构算法可能就相当于写好一半了。最基本的数据结构可能应该是队列和栈了。由于这一系列的算法梳理会通过js来表达。所以我们的队列和栈就用js来实现了。而在js中,语言本身就实现了他们。
队列,先进先出
const queue = [];
queue.push(1);
queue.push(2);
queue.shift(); // 1
queue.shift(); // 2
栈,后进先出
const stack = [];
stack.push(1);
stack.push(2);
stack.pop(); // 2
stack.pop(); // 1
相对一些原生没有实现它们的语言来说会容易一些。
好的,开始今天的排序整理吧。
算法4里面讲了:选择排序,插入排序,希尔排序,归并排序,快速排序。
一些通用方法
function less(a, b) {
return a < b;
}
function exch(arr, i, min) {
const temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
选择排序是最简单的排序。首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置。再次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。
function selectionSort(arr) {
len = arr.length;
for (let i = 0; i < len; i++) {
let min = i;
for (let j = i + 1; j < len; j++) {
if (less(arr[j], arr[i])) {
min = j;
}
}
exch(arr, i, min);
}
return arr;
}
遍历整个数组,拿当前索引位置的值与前一位置的值做比较,然后交换位置。然后缩小索引,继续之前的操作,直到遍历到最后一个索引。当前索引位置的左边都是排好序的,但它们的最终位置还不确定,为了给更小的元素腾出空间,它们可能会被移动。
function insert_sort(arr) {
len = arr.length;
for (let i = 0; i < len; i++) {
for (let j = i; j > 0 && less(arr[j], arr[j - 1]); j--) {
exch(arr, j, j - 1);
}
}
return arr;
}
是对插入排序的改进,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。
function shellSort(arr) {
len = arr.length;
let h = 1;
while (h < len / 3) {
h = 3 * h + 1;
}
while (h >= 1) {
for (let i = h; i < len; i++) {
for (let j = i; j >= h && less(arr[j], arr[j - h]); j -= h) {
exch(arr, j, j - h);
}
}
h = Math.floor(h / 3);
}
return arr;
}
将数组排序,可以先(递归地)将它分成两半分别排序,然后将结果归并起来。
原地归并的方法
function merge(arr, lo, mid, hi) {
let i = lo;
let j = mid + 1;
const aux = [];
for (let k = lo; k <= hi; k++) {
aux[k] = arr[k];
}
for (let k = lo; k <= hi; k++) {
if (i > mid) {
arr[k] = aux[j++];
} else if (j > hi) {
arr[k] = aux[i++];
} else if (less(aux[j], aux[i])) {
arr[k] = aux[j++];
} else {
arr[k] = aux[i++];
}
}
return arr;
}
// 自顶向下
function topToBottomSort(arr, lo, hi) {
if (hi <= lo) return;
const mid = lo + Math.floor((hi - lo) / 2);
topToBottomSort(arr, lo, mid);
topToBottomSort(arr, mid + 1, hi);
return merge(arr, lo, mid, hi);
}
// 自底向上
function bottomToTopSort(arr) {
const len = arr.length;
const aux = [];
for (let sz = 1; sz < arr.length; sz = sz + sz) {
for (let lo = 0; lo < len - sz; lo += sz + sz) {
merge(arr, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, len - 1));
}
}
return arr;
}
快速排序和归并排序比较类似都采用了分治的**。
function partition(arr, lo, hi) {
let i = lo;
let j = hi + 1;
const v = arr[lo];
while (true) {
while (less(arr[++i], v)) {
if (i === hi) {
break;
}
}
while (less(v, arr[--j])) {
if (j == lo) {
break;
}
}
if (i >= j) {
break;
}
exch(arr, i, j);
}
exch(arr, lo, j);
return j;
}
function quickSort(arr, lo, hi) {
if (hi < lo) return;
const j = partition(arr, lo, hi);
quickSort(arr, lo, j - 1);
quickSort(arr, j + 1, hi);
}
在算法4官网上看到一个比较好的算法可视化网站http://sorting.at/, 有兴趣的可以看看
1.使用 react-router v4时,不要在router外面获取routing 变量
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.