Blog

Yoki


  • 首页

  • 归档

  • 搜索

简单介绍Typescript

发表于 2018-03-30

简单介绍

以下来自维基百科

  • TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个严格超集,并添加了可选的静态类型和基于类的面向对象编程。C#的首席架构师以及 Delphi 和 Turbo Pascal 的创始人安德斯·海尔斯伯格参与了 TypeScript 的开发。

  • TypeScript 设计目标是开发大型应用,然后转译成 JavaScript。[7]由于 TypeScript 是 JavaScript 的严格超集,任何现有的 JavaScript 程序都是合法的 TypeScript 程序。

为什么需要 ts

普通的 js:假设我一个函数里面只能对字符串进行操作,要是我传了数字,直接就报错了,由此可见

  • 增加了代码的可读性和可维护性
  • 在编译阶段就能发现错误
  • 类型推论

同时与 JavaScript 兼容良好,把.js 改为.ts 就能用

基本使用

基础类型

1
2
3
4
5
6
7
8
9
10
11
12
13
//布尔值
const isDone = (boolean = false);
// 数值
const num: number = 6;
//字符串
const str: string = "yoki";
//空值返回
function say(): void {
console.log("hi");
}
//null,undefined
const u: undefined = undefined;
const n: null = null;

数组

1
2
3
4
5
6
//全是数字的数组
const numArr: number[] = [1, 2, 3];
//数字和字符串都有的数组
const tmp: (number | string)[] = [1, "2", 3];
//一个可能什么都有类型都有的数组
const anyArr: any[] = [1, "1", true];

函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//传入参数是数字,返回的也是数字
function sum(x: number, y: number): number {
return x + y;
}
//可选型,用?,sum2(1)可以,sum(1,2)也可以
function sum2(x:number,y?:number):number{
return y?:x+y:x
}
//参数默认值,sum3(1,'5'),y会被自动推导为number
function sum3(x?:number,y=0){
return x+y
}
//剩余参数
function concat(arr:any[],...items:any[]){
return arr.concat(items)
}
concat([],1,2,3)=[1,2,3]

接口

  • 在面向对象,接口是一个很重要的概念,他是对行为的抽象,而具体如何行动需要由 class 去实现

  • ts 的接口是非常灵活的,既可以对类的一部分行为进行抽象,也可以用于对对象的形状进行描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//对对象形状的描述
interface Person{
name:string
age?:number,
say?:()=>void
}
const yoki:Person={
name:'yoki',
age:18
}
//只读属性,不可修改
interface Person{
readonly id:number
}

类

  • public 修饰的属性或者方法都是公有的,可以在任何地方被访问到,默认所有的属性或方法都是 public
  • private 修饰的属性或者方法都是私有的,不能在声明它的类的外部访问
  • protected 修饰的属性或者方法都是受保护的,它与 pravate 类似,区别是它在子类是允许访问的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Animal{
publick name;
public constructor(name){
this.name=name
}
}
let a=new Animal('rabbit')
a.name//rabbit
a.name='tom'//tom
class Animal2{
private name;
public constructor(name){
this.name=name
}
}
let b=new Animal('rabbit')
b.name='tom'//报错
class Animal3{
protected name;
public constructor(name){
this.name=name
}
}
class Cat extends Animal{
constructor(name){
super(name)
console.log(this.name)//可以的
}
}

泛型

是指在定义函数/接口或者类的时候,不预先指定具体的类型,而在使用的时候再指定类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//T是运行时候的类型
function createArray<T>(value: T, len: number): Array<T> {
return Array(len).fill(value);
}
//多个类型
function swap<T1,T2>(tuple:[T1,T2]):[T1,T2]「
return [tuple[1],tuple[0]]
swap([7,'six'])//['six',7]

//泛型约束
function getlen<T>(a:T):number{
return a.length
}// length属性不存在type‘T’的里面,error

interface lengthwise{
length:number
}
function getlen2<T extends lengthwise>(a:T):number{
return a.length
}
getlen2([])//ok
getlen2(123)//error
//泛型类
interface Props{size:'big'|'small'}
interface State{visibily:boolean}
class App extends React.Component<Props,State>{
state={visibily:true}
constructor(props,context){
super(props,context)
}
handleClick=()=>this.setState({show:false})
//'error',show不在state接口
render(){
return this.state.visibily&& <button> type={this.props.size} onClick={this.handleClick} />
}
}
ReactDom.render(<App size='middle' />,document.body)
// error,middle不在props的size里

我理解的前端进阶

发表于 2018-03-13

简单介绍

  • 前端是一个需要广度的职位,感觉如果多了解上下游各部门的相关技术会对工作有极大的帮助。单从比起技术来,眼界跟逻辑思维其实更重要。
  • 从技术上来说,走出前端那一亩三分地,多接触自己不熟悉的技术。当回头看前端的时候往往会有一种豁然开朗的感觉。
  • 现阶段感觉是要接触不同的技术,换种不一样的思维。因为之前也写过 php(用的是国人 tp 框架),初次接触了 mvc 的思想,后来为学校的公众号也写过后台,用的是 python(Django),然后公司现在用的 java 的 spring 那一套(在疯狂补 java 核心卷 I 中…),后端的路已经很成熟的,前端还在探索属于自己的方式。这样看来是豁然开朗了~
  • 萌新 -> 页面重构 -> 前端开发工程师 -> 大前端 -> 前端打杂 -> 团队打杂

练级之路

可以写出方便维护的代码

  • commonjs/cmd/amd(node/sea.js/require.js)
  • es6 module(babel)
  • webpack
  • mvvm 解决 jq 意大利面条
  • 使用 async/await,promise

可以写出不容易出错的代码

  • 类型检查,不能相信传进来的参数(当然现在有 ts)
  • try-catch 捕获错误
  • window.onerror

可以写出性能比较好的代码

  • 大量 dom 操作的时候可以批量读,批量写,会用缓存
  • 会使用递归分而治之,将大问题分解成相似的小问题

可以灵活使用工具

  • charles/whistle 替换本地文件,模拟数据,代理 host
  • 会用 ps 切图,markman 测量工具
  • 各种带语法提示的小插件
  • sourcemap 定位代码

知道怎么定位到问题

  • 会有 chrome devtool 里面的 network/performance 等,断点调试
  • 网络不通,ping 一下百度

常见问题

跨域

  • jsonp(get)
  • 服务器代理

安全问题

  • xss(不能信任用户的输入,过滤一下输入,转义带有脚本的标签)
  • csrf(所有请求都需要带有 token)

性能问题

  • 小文件,快展现
  • 图片资源无损压缩(webp 了解一下)
  • 服务端渲染
  • 预加载
  • dns 预解析
  • web workers 进行大计算
  • 知道浏览器的内部工作原理

关注新技术,并能快速化为己用

  • webaseembly 将 js 转为二进制的规范
  • 小程序
  • pwa

有感而发于公司内部分享 ppt

2017展望与2018总结

发表于 2018-02-25

2017 总结

工作上

  • 官网方面:17 年 1 月进入到公司,当时正值官网重构,组长为了锻炼我,便让我参与到了官网的重构当中。
  • 产品方面:官网完成之后,雷达刚好升级 2.0,我去负责其中两个模块的迁移。之后一直负责雷达的开发,维护过立方,聚合,接触了很多公司的产品。
  • 项目方面:期间维护 MIG 系统,并且重构了整个 DBA,能更快地迁移类似的模块。后来参与研发 ipsos 雷达。

学习上

  • 关于 vue:这一年,也算写了大量的 vue 代码,但 vue 封装了大量的 js 代码,屏蔽底层实现细节,所以我很清楚会写 vue 并不算什么,在前端框架日新月异的今天,只有深入地了解并掌握其中原理,才能以不变应万变。
  • 关于可视化方向:由于 echarts 底层封装实在是过于复杂,也没有仔细去研究,只能去应用它的 api,这是我所遗憾的地方。
  • 关于构建工具:先是接触了 gulp,但并不是了解得很清楚。而对于 webpack 这一块,配置得也算还可以,也大概知晓构建工具在前端方面的应用,但是我觉得不要满足于只做一个配置工程师,知道思想最为重要,要知道为什么会有压缩代码,合并请求等操作。
  • 关于读书:喜欢看人物传记,比如爱因斯坦。物理学和哲学上有趣的书也看了不少,极大的开阔了我的眼界。关于 JavaScript 的书,把《你不知道的 JavaScript》也翻了一遍,对于算法(图解算法)也大概翻了一下。
  • 关于印象笔记:只是自己选择的一种记录的工具,因为之前请教别人的时候发现,别人说了一遍方法,但是会很快忘记,所以这时候最好把它记录下来。笔记里记录了今年工作上遇到的问题,方便自己快速复查。

总结

  • 总的来说,这一年,觉得自己是”开眼看世界”的一年,知道自己是热爱前端的,也知道一个好的前端工程师,他首先得是一个工程师,势必要了解算法,http 等基础知识。而纵向学习是提升学习深度的结果,而不是追求学习广度的结果。

2018 展望

工作上

  • 前端系统搭建:yoda 系统搭建。
  • 后端:学习一下 java,希望能接触组内一些后端的项目,简单写些接口。
  • 业务组件库:业务上建立一个属于自己乃至于公司的组件库,因为做产品发现,一个好用的业务组件能提升很多开发效率。
  • 数据分析:由于我们团队比较偏向分析,可以多向分析师取经。眼光应该放宽一点,学习数据方面的分析,才能对公司的产品和项目有更好的感知度。

学习上

  • MVVM 框架:希望能熟练使用 react 和 angular,最终目标希望能自己造一个小巧的 mvvm 框架,当然造轮子的意义是为了自己学习。
  • Node 开发:能编写一个 express 或者 koa 的中间件。熟练掌握 CRUD 应用。
  • 构建工具:梳理前端构建工具,把常用的大概研究一遍。
  • Typescript:学习 typescript,相信强类型一定会在将来被纳入 ECMAScript 的标准。
  • 关于笔记:希望能把每天要做的事,坚持写在 oneNote,养成良好习惯。而工作上要记下来的写在印象笔记。
  • Java:能熟练进行 web 开发。
  • 关于可视化方向:学习 d3.js 和 three.js。
  • 关于读书:坚持读书,温故知新更为重要。
  • 关于英语:英语实在是太重要了,需要加强学习。

生活习惯

  • 保持精力充沛的前提是要有足够的锻炼,如果每天实在是没有忙到要加班的情况,可以九点走,放好书包后出来跑半小时的步。
  • 坚持八点起床,注意自己的仪容仪表。

总结

  • 多增加了一个维度,同时希望自己能真正对得起工程师这个 title。

前端错误收集

发表于 2018-02-12

为什么要进行前端的错误进行监控

  • 现在网页的要求已经趋近于原生的应用,几乎都是有着大量交互的。面对各种用户,不同的浏览器等等出现的不同问题,有必要进行及时的监控,毕竟有些问题复现也是挺困难的。

捕获错误的方法

  • try..catch 之前说过,但是它只能在 try 的块里运行才可以捕捉错误,无法捕捉全局的错误事件
  • window.onerror 可以用来捕捉全局的错误,但是它无法捕捉异步错误,我们在 ajax 模块统一埋点

window.onerror

1
2
3
4
5
6
window.onerror=function(message,url,linNo,columnNo,error)
message:错误信息
url:发生错误对应的脚本路径,比如是bundle.js
lineNo:错误发生的行号
colunmnNo:错误发生的列号
error:具体的error对象,包含更加详细的错误调用堆栈信息

常见问题

Script Error

现在我们一般都把资源放在 cdn 上,其他资源在本页面相当于跨域为什么会有这种设置,这是避免数据泄露到不安全的域中。如果我是银行页面,随便引入一个 js 资源,读取了账户密码,那可如何是好。

解决手段

添加信任的域

  • 客户端的 script 添加 crossorigin,他的作用就是告诉浏览器,要加载一个其他域的资源,并且信任他
  • 服务端设置Acess-Control-Allow-Origin的响应头,可以直接设置为*,信任全部资源。cdn 资源应该全部加上 CORS 响应头。

    如果我们使用 nginx 的话,可以像下面那样简单配置

1
2
3
4
5
location / {
root /Users/**;
index index.html index.htm;
add_header "Access-Control-Allow-Origin" "*";
}

代码压缩

现代 web 工程都会直接压缩 js 代码,所以线上一般都是只有几行代码但是我们有 sourcemap,可以定位到源代码的位置。但是线上是没有这个东西,我们可以通过sourcemap这个工具来将压缩后的代码生成 sourcemap

  • 这里简单介绍一下生成 sourcemap 文件的方式
1
uglifyjs --source-map 最终生成的map名称 --output 压缩文件名称 原文件名称
  • 简单通过 soucemap 定位到源文件的真正行数
1
2
3
4
5
let sourceMap = require("source-map");
let mapData = require("./test.json");
let consumer = new sourceMap.SourceMapConsumer(mapData);
let info = consumer.originalPositionFor({ line: 1, column: 102 });
console.log(info); //{ source: "test.js", line: 11, column: 6, name: "yoki" }

推荐工具

由以上可以简单知道前端收集的原理了,但真正到线上肯定是不够的,这里有一些成熟的工具

  • sentry
  • fundebug

分享一篇干货如何设计一个前端监控系统

nginx简单使用

发表于 2018-01-20

前情提要:由于公司也是使用 nignx 解决前后端分离跨域问题,这里简单学习一下

设置简单的代理服务器

设置一个代理服务器,它即是一个用来接收请求,并传递它们到代理服务器,取回响应并发送响应给客户端的服务器。

我们将会配置一个简单的代理服务器,它将用本地文件来提供图片请求,而把其他请求转发到代理服务器。这个例子中,这两个服务器都将被定义在一个 nginx 实例中。

首先,在上面配置的基础上再添加一个 server 指令块到 nginx 的配置文件中:

1
2
3
4
5
6
7
8
server {
listen 8080;
root /data/upl;

location / {

}
}

这是一个监听在 8080 端口(location 指令没有指定,默认会使用 80 端口),并且会映射所有请求到本地路径/data/upl 的简单服务器。创建此路径并在里面创建 index.html 文件。注意 root 指令要放在 server 上下文中。这种 root 指令将会在没有自己 root 指令的 location 指令块被选中来处理请求时应用。

下一步,使用上一节的服务器配置,并且修改其为一个代理服务器配置。在第一个 location 指令块,放置 proxy_pass 指令,将代理服务器的地址作为参数(包括代理服务器的协议,域名和端口号)。例子中,它是http://localhost:8080:

1
2
3
4
5
6
7
8
9
server {
location / {
proxy_pass http://localhost:8080;
}

location /images/ {
root /data;
}
}

第二个 location 指令块目前指定的是/images/前缀到/data/images 路径的映射,为了使其能根据文件后缀匹配相应图片的请求,我们修改之:

1
2
3
location ~ \.(gif|jpg|png)$ {
root /data/images;
}

这里参数是一个匹配哪些以.gif,.jpg 或者.png 结尾的 URIs 的正则表达式。正则表达式前面需要放置~。相应的请求将会被影射到/data/images 路径。

当 nginx 选择一个 location 指令块去服务一个请求,它首先检查 location 指令指定的前缀(并记住此最长前缀),然后检查正则表达式。如果有一个正则表达式匹配了,nginx 选中此 location,否则,它将会应用前一个记住的 location。

最后,代理服务器的配置将会是这样:

1
2
3
4
5
6
7
8
9
server {
location / {
proxy_pass http://localhost:8080;
}

location ~ \.(gif|jpg|png)$ {
root /data/images;
}
}

此服务器将会过滤那些以.gif,.jpg 或.png 结尾的请求,并且影射它们到/data/images 目录。传递其他所有请求到上面配置的代理服务上去。

location

  • = 开头表示精确匹配
  • ^~ 开头表示 uri 以某个常规字符串开头,不是正则匹配
  • ~ 开头表示区分大小写的正则匹配;
  • ~* 开头表示不区分大小写的正则匹配
  • / 通用匹配, 如果没有其它匹配,任何请求都会匹配到

顺序 no 优先级:

(location =) > (location 完整路径) > (location ^~ 路径) > (location ~,~* 正则顺序) > (location 部分起始路径) > (/)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#直接匹配网站根,通过域名访问网站首页比较频繁,使用这个会加速处理
#这里是直接转发给后端应用服务器了,也可以是一个静态首页
# 第一个必选规则
location = / {
proxy_pass http://tomcat:8080/index
}
# 第二个必选规则是处理静态文件请求,这是nginx作为http服务器的强项
# 有两种配置模式,目录匹配或后缀匹配,任选其一或搭配使用
location ^~ /static/ {
root /webroot/static/;
}
location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ {
root /webroot/res/;
}
#第三个规则就是通用规则,用来转发动态请求到后端应用服务器
#非静态文件请求就默认是动态请求,自己根据实际把握
#毕竟目前的一些框架的流行,带.php,.jsp后缀的情况很少了
location / {
proxy_pass http://tomcat:8080/
}

nginx官方文档译文

发表于 2018-01-14

简介

nginx 有一个 master 进程和几个 worker 进程。master 进程的主要作用是读取和操作配置文件,同时维护 worker 进程。而真正处理请求的是 worker 进程。nginx 采用基于事件模型和系统依赖的机制最终将请求分配给 worker 进程。worker 进程的数量被定义在配置文件中,或者有可能被自动调整为 CPU 的核心数。

默认情况下,配置文件命名为 nginx.conf,放在/usr/local/nginx/conf,/etc/nginx,或者/usr/local/etc/nginx 目录下

启动,停止,reloading 配置

运行可执行文件即可开启 nginx。一旦 nginx 开启之后,它可以使用可执行的-s 参数进行控制。下面是其语法:

1
nginx -s signal

信号(signal)可以是下面的其中一个:

  • stop —— 快速关闭
  • quit —— 优雅地关闭
  • reload —— 重新加载配置文件
  • reopen —— 重新打开日志文件

比如,等待所有 worker 进程服务完当前请求,然后关闭 nginx 进程:

1
nginx -s quit//这条命令,必须由开启nginx的同一个用户执行。

如果改变了配置文件,除非是 nginx 重启,否则可以执行下面的命令进行应用新的配置:

1
nginx -s reload

一旦 master 进程收到信号重新加载配置文件,它会检查新的配置文件的语法,并尝试应用配置。如果成功,master 进程开启新的 worker 进程,并且给旧的 worker 进程发送信息,请求它们关闭。否则,master 进程将会回滚修改并继续使用旧配置。

旧的 worker 进程,接受到命令关闭,将会停止接收新的请求,并继续服务完当前请求。自此之后,worker 进程将正式退出。

信号可以通过 unix 工具发送给 nginx 进程,例如 kill 命令。这种情况下,信号会直接发送给指定 pid 的进程。默认情况下,nginx 的 master 进程 ID 被写在/usr/local/nginx/logs 或/var/run 目录下的 nginx.pid 文件中。

比如,假如 master 进程的 ID 是 1628,那么发送 QUIT 信号给 nginx 会导致其优雅地退出:

1
kill -s QUIT 1628

获取所有正在运行的 nginx 进程列表信息,可以执行:

1
ps -ax | grep nginx

配置文件结构

nginx 由模块组成。这些模块都被配置文件中的指令所控制。指令包括简单指令和指令块。一条简单的指令由名字和参数组成,参数由空格隔开,以分号(;)结束。一个指令块的结构和简单指令的一样,但是,它以额外的指令集合结束,指令集用大括弧({和})包围。

如果一个指令块里面有其他的指令块,那么它被称为一个上下文(例如,events,http,server 和 location)。

配置文件中,那些位于任何上下文之外的指令都认为是在 main 上下文中。events 和 http 指令位于 main 上下文,server 位于 http 上下文之中,location 位于 server 之中。

一行位于#后面的被认为是注释。

静态内容

web 服务器一个重要的任务就是提供静态资源服务(例如图片等)。我们会实现一个例子,这个例子会根据请求,从不同的本地目录提供静态文件:/data/www(包含 html 文件),/data/images(包含图片)。这时候我们需要在 http 指令块里面创建一个 server 指令块,里面又包含两个 location 指令块。

首先,创建/data/www/index.html 文件,并放置一些图片于/data/images 目录下。

打开配置文件,默认的配置文件已经包含几个实例 server 指令块,大部分都被注释了。我们取消注释,并且开始一个新的 server 指令块:

1
2
3
4
5
http {
server {

}
}

通常来说,配置文件会包含几个 server 指令块,它们由它们监听的端口号和服务器名称来区分。一旦 nginx 决定由哪一个 server 处理一个请求,它会把在请求 header 中的 URI 与在 location 指令块中定义的参数进行测试比较。

向 server 指令块添加 location 指令块:

1
2
3
location / {
root /data/www;
}

location 指令块指定了 URI”/”前缀。为了匹配请求,URI 将会被添加到 root 指令指定的路径,即是/data/www,实现了从路径到本地文件系统的转变。如果有多个匹配的 location 指令块,那么 nginx 将会选择那个拥有最长匹配前缀的。上面的 location 指令块提供了最短的前缀,所以如果其他所有 location 指令块都匹配失败,此指令块才生效。

下一步,添加第二个 location 指令块:

1
2
3
location /images/ {
root /data;
}

它会匹配以/images/开头的请求(虽然 location /也同时匹配此请求,但是却是更短的前缀匹配)。

最后 server 指令块的配置应该是这样的:

1
2
3
4
5
6
7
8
9
server {
location / {
root /data/www;
}

location /images/ {
root /data;
}
}

这已经是一个服务器的配置文件了,这个服务器将会在本地机器http://localhost/监听80端口,并且是可访问状态。服务器将从路径/data/images发送文件去响应URIs以/images/开头的请求。比如,客户请求http://localhost/images/example.png,nginx将发送/data/images/example.png文件。如果该文件不存在,nginx将返回包含404 error 的响应。URIs 不是以/images/开头的请求将会被影射到/data/www 路径。例如,请求http://localhost/some/example.html,nginx将会发送/data/www/some/example.html文件。

为了应用新的配置文件,如果 nginx 还没有开启,开启即可;否则可以发送 reload 信号给 nginx 的 master 进程:

1
nginx -s reload

浅谈浏览器渲染

发表于 2018-01-10
  • 作为一个前端工程师,了解浏览器的渲染过程,可以掌握优化的指导原则。
  • 现在有那么多的优化方案,预编译,预加载,资源合并,按需加载等等都是针对浏览器渲染的优化。

关键渲染路径

  • 关键渲染路径(Critical Rendering Path)是指与当前用户操作有关的内容
  • 比如用户刚刚打开一个页面,首屏的显示就是当前用户操作的内容,具体就是浏览器收到 html/css/js 等资源并对其进行处理从而渲染出页面
  • 了解浏览器渲染的过程与原理,很大程度上是为了优化关键渲染路径
  • 为了保障首屏内容的最快速显示,通常会提到渐进式页面渲染,但是为了渐进式页面渲染,就要做资源的拆分,怎么拆分,这是按场景考虑的

浏览器渲染页面过程

  • 1.dns 查询
  • 2.tcp 连接
  • 3.http 请求即响应
  • 4.服务端响应
  • 5.客户端渲染

客户端渲染

以下步骤不一定一次性顺序完成,如果 dom 或者 cssom 被修改,则以下过程需要重复执行,这样才能计算哪些像素在屏幕上需要重新渲染

  • 1.处理 html 标记并构建 dom 树
  • 2.处理 css 并构建 cssom 树
  • 3.将 dom 树和 cssom 树合并成一个渲染树
  • 4.根据渲染树来布局,以计算每个节点的几何信息
  • 5.将各个节点绘制到屏幕上

阻塞渲染

现代浏览器是并行加载资源的。当 HTML 解析器(html parser)被脚本阻塞的时候,解析器虽然会停止构建 dom,但仍然会识别该脚本后面的资源,并进行预加载。

  • 默认情况下,css 被视为阻塞渲染的资源,这意味着浏览器将不会渲染任何已处理的内容,直到 cssdom 构建完毕
  • js 不仅可以读取和修改 dom 属性,还可以读取和修改 cssom 属性

存在阻塞的 css 资源的时候,浏览器就会延迟 js 的执行和 dom 构建

  • 当浏览器遇到一个 script 标记的时候,dom 构建将暂停,直到脚本完成执行
  • cssom 构建的时候,js 执行将暂停,直到 cssdom 就绪

所以 script 标签的位置很重要,实际使用的时候,可以遵循下面两个原则

  • css 优先:引入顺序上,css 资源先于 js 资源
  • js 应该尽量少影响 dom 的构建

CSS

渲染树(Render-Tree)的关键渲染路径中,要求同时具有 dom 和 cssom,之后才会构建渲染树。所以 html 和 css 都是被阻塞的资源。html 一定是需要的,那么可以从 css 上想办法。

  • 精简 css 并尽快提供它,例如将多个 css 合并成一个,并进行压缩

JavaScript

  • 实际工程,常常将 js 资源放到文档底部
  • defer 和 async 可以改变阻塞模式

defer

  • defer 属性表示延迟执行引入的 js,这段 js 加载时候 html 也并未停止解析,这两个过程是并行的
  • 整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件。
1
2
3
<script src="app1.js" defer></script>
<script src="app2.js" defer></script>
<script src="app3.js" defer></script>
  • defer 不会改变 script 中代码的执行顺序,示例代码会按照 1,2,3 的顺序执行。

async

1
2
3
<script src="app.js" async></script>
<script src="ad.js" async></script>
<script src="statistics.js" async></script>
  • async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行——无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发之后。需要注意的是,这种方式加载的 JavaScript 依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。
  • 多个 async-script 的执行顺序是不确定的。所以 app.js 和 ad.js 和 statistics.js 不一定是顺序执行,谁先加载完谁先执行。

动画了解一下(二)

发表于 2017-12-13

fps

  • fps 是 frame per second,一秒能够重新渲染多少个画面
  • 网页的每一帧都是一次重新渲染,一帧(frame)就是一个画面。
  • 我们平时看电视,电视上的画面连续播放,其实一个画面接着一个画面的,如果一秒有 60 个画面播放,我们看着就很流畅了。如果一秒 10 个画面,可能就会感觉像在跳机械舞一样,一顿一顿的
  • 现在大多数显示器的刷新频率都是 60Hz,浏览器一般也会自动按照这个频率,刷新动画。
  • 所以网页动画能够做到每秒 60 帧,就可以和显示器同步刷新,视觉效果达到最佳。
  • 一秒之内进行 60 次渲染,每次不能超过 16.66 毫秒

如果想要到 60 帧的刷新率,那么 js 线程每个任务的耗时,必须少于 16ms。一个解决办法是用 web worker,主线程只用于 ui 渲染,其他和 ui 渲染不想干的任务,都放在 worker 线程。

调节渲染

有一些 js 方法可以调节重新渲染,大幅度提高网页性能

requestAnimationFrame

  • 他可以将代码放到下次重新渲染执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function doubleHeight(element) {
var currentHeight = element.clientHeight;
element.style.height = currentHeight * 2 + "px";
}
elements.forEach(doubleHeight);
//读了马上又去写,这就会马上重排,导致重新渲染,这就对网页性能很不利
//可以使用window.requestAnimation把读写操作分离,把所有写操作放到下一次浏览器正常的重新渲染
function doubleHeight(element) {
var currentHeight = element.clientHeight;
window.requestAnimationFrame(function() {
element.style.height = currentHeight * 2 + "px";
});
}
elements.forEach(doubleHeight);
  • 适用于页面滚动事件,推迟到下一次重新渲染
  • 适用于网页动画,比如
1
2
3
4
5
6
7
8
9
10
11
//元素一帧旋转一度
var rAF = window.requestAnimationFrame;

var degrees = 0;
function update() {
div.style.transform = "rotate(" + degrees + "deg)";
console.log("updated to degrees " + degrees);
degrees = degrees + 1;
rAF(update);
}
rAF(update);

requestIdleCallback

只有当一帧的末尾有空闲时间,才会执行回调

  • requestIdleCallback(fn)
  • 因为一秒 60 帧,一帧就 16.66ms,只有当前帧的运行时间小于 16.66ms 的时候,fn 才会执行。如果当前没有空闲时间,就推迟到下一帧,直到有空闲时间为止。
  • requestIdleCallback(fn,5000)可以制定第二个参数,表示指定的 ms,表示这段时间内,如果每一帧都没有空间时间,fn 将强制执行

网页性能管理详解

javascript装逼指南

发表于 2017-12-03

1.javascript 代码错误处理方式

1
2
3
4
5
try {
something;
} catch (e) {
window.location.href = "http://stackoverflow.com/search?q=[js]+" + e.message;
}

2.如何优雅的取随机字符串

1
2
3
4
5
6
Math.random()
.toString(16)
.substring(2);
Math.random()
.toString(36)
.substring(2);

3.如何优雅的取整

1
2
3
4
5
let a = ~~2.33; //2

let b = 2.33 | 0; //2

let c = 2.33 >> 0; //2

4.金钱数字取千分位的非正则优雅实现

1
2
3
4
5
6
7
8
9
10
11
12
13
//用reduce
function formatCash(str) {
return str
.split("")
.reverse()
.reduce((prev, next, index) => {
return (index % 3 ? next : next + ",") + prev;
});
}
console.log(formatCash("1234567890")); // 1,234,567,890
//toLocaleString
(23333333).toLocaleString("en-US");
("23,333,333");

5.最短代码实现数组去重

1
2
[...new Set([1, "1", 2, 1, 1, 3])];
//[1,'1',2,3]

6.最短代码实现一个长度为 m(6)且值都为 n(8)的数组

1
2
Array(6).fill(8);
//[8,8,8,8,8,8]

7.短路表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var a = b && 1;
// 相当于
if (b) {
a = 1;
} else {
a = b;
}

var a = b || 1;
// 相当于
if (b) {
a = b;
} else {
a = 1;
}

8.颜色 rgb 和 hex 的相互转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
}
: null;
}

alert(hexToRgb("#0033ff").g); // "51";

初识vps

发表于 2017-11-29

vps 是什么

以下来自维基百科

  • 虚拟专用服务器(英语:Virtual private server,缩写为 VPS),是将一台服务器分区成多个虚拟专享服务器的服务。
  • 实现 VPS 的技术分为容器技术和虚拟机技术 。在容器或虚拟机中,每个 VPS 都可分配独立公网 IP 地址、独立操作系统、实现不同 VPS 间磁盘空间、内存、CPU 资源、进程和系统配置的隔离,为用户和应用程序模拟出“独占”使用计算资源的体验。
  • VPS 可以像独立服务器一样,重装操作系统,安装程序,单独重启服务器。

vps 能做什么

  • 可以进行科学上网,参见搭建 ss 服务器
  • vps 可以配置一个 ip 或者多个 ip。既然有了 ip 那么干的事情就多了。
  • 同时想了解一下 linux 服务器或者部署自己的小作品,这个时候一个配置较低的 vps 就可以发挥作用了。比如托管静态页面,爬虫,起 node 服务等等。

一些 vps 推荐

  • vultr(我就是用这个的)
  • 搬瓦工
  • hostmybytes
  • linode
1…6789
Yoki

Yoki

云在青天水在瓶

82 日志
21 标签
© 2019 Yoki
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4