Code Monkey home page Code Monkey logo

blog's People

Watchers

 avatar

blog's Issues

书单

JavaScript 设计模式与开发实践

author: 曾探
published: 2015-05-01
progress: 正在阅读...
rating: 5
postTitle: JS 设计模式与开发实践
postLink: https://chanshiyu.com/#/post/8
cover: https://cdn.jsdelivr.net/gh/chanshiyucx/yoi@latest/book/JavaScript-设计模式与开发实践.jpg
link: https://www.duokan.com/book/120447
description: 设计模式的定义:在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。书中介绍了 JavaScript 常用的 16 种设计模式,受益良多。

HTML5秘籍(第2版)

author: Matthew MacDonald
published: 2015-05
progress: 完成阅读
rating: 3
postTitle: HTML5秘籍
postLink: https://www.dmrlu.com/blog/#/category
cover: https://wfqqreader-1252317822.image.myqcloud.com/cover/805/26211805/t6_26211805.jpg
link: https://weread.qq.com/web/reader/ef232a80718ff5ddef28298kc81322c012c81e728d9d180
description: 比较适合入门, 不过即使是老手也能学习到一些有用的知识

实现图片懒加载(throttle, debounce)

通过监听scroll实现图片懒加载, 使用throttle, debounce方法优化

懒加载

/**
 * 图片懒加载
 * @parma imgItems {node[]} DOM节点
 * @returns {boolean} 可改进为promise方法
 */
function lazyLoad(imgItems) {
    let _fnScrollTop =  window.scrollY;
    /**
     * @parma arr {node[]}  
     * @parma item {node}
     */
    function delItem(arr, item) {
    	if(arr.indexOf(item) != -1) {
    		item.setAttribute('src',item.dataset.src)
    		// arr.indexOf(item)寻找对应位置
    		arr.splice(arr.indexOf(item), 1)
    	}
    }
    imgItems.forEach((item, index, arr) => {
    	// 获取img的底部高度
    	let itemClientY = item.offsetTop +item.offsetHeight;
    	// 判断img高度(底部)是否在屏幕中
    	if(item.offsetTop < window.innerHeight + _fnScrollTop && item.offsetTop > _fnScrollTop) {
    		delItem(arr, item)
    	} else if (itemClientY < window.innerHeight + _fnScrollTop && itemClientY > _fnScrollTop)  {
    		delItem(arr, item)
    		
    	}
    })
}
防止传入的参数非数组
function lazyLoad(imgItems) {
    ...
    if(imgItems.length === 0) {
    	return true
    }
    ...
}
全部图片加载完成后return
function lazyLoad(imgItems) {
    ...
    if(!Array.isArray(imgItems)) {
    	return false
    }
    ...
}

imgItems其中arr.splice(arr.indexOf(item), 1)删除对应的item

使用
// html
<ul>
    <li><img src="img/10.gif" data-src="img/1.jpg"></li>
    <li><img src="img/10.gif" data-src="img/2.jpg"></li>
    <li><img src="img/10.gif" data-src="img/3.jpg"></li>
    <li><img src="img/10.gif" data-src="img/4.jpg"></li>
    <li><img src="img/10.gif" data-src="img/5.jpg"></li>
    <li><img src="img/10.gif" data-src="img/6.jpg"></li>
    <li><img src="img/10.gif" data-src="img/7.jpg"></li>
</ul>
// js
let imgNodes = [...document.querySelectorAll('img')];
lazyLoad(imgNodes);

debounce

去抖的概念的意思是如电梯一样,当你重复按的时候,总是以最后一下为计时的开始

/**
 * 去抖
 * @parma fn {function} 执行的方法
 * @parma delay {nubmer} 去抖的时间
 * @parma args {any | object} fn的参数
 */
function debounce(fn = function(){}, delay, args) {
    let timer;
    return () => {
        if(timer) {
            clearTimeout(timer)
        }
        timer = setTimeout(() => {
            fn(args);
    	}, delay)
    }
}

throttle

节流的概念: 计时开始后过程中你重复按都没用

/**
 * 节流
 * @parma fn {function} 执行的方法
 * @parma delay {nubmer} 节流的时间
 * @parma args {any | object} fn的参数
 */
function throttle(fn = function(){}, delay, args) {
    let times = true;
    return () => {
    	if(times) {
            setTimeout(() => {
            	fn(args);
            	times = true;
            }, delay)
    	}
    	times = false;
    }
}
使用
<ul>
    <li><img src="img/10.gif" data-src="img/1.jpg"></li>
    <li><img src="img/10.gif" data-src="img/2.jpg"></li>
    <li><img src="img/10.gif" data-src="img/3.jpg"></li>
    <li><img src="img/10.gif" data-src="img/4.jpg"></li>
    <li><img src="img/10.gif" data-src="img/5.jpg"></li>
    <li><img src="img/10.gif" data-src="img/6.jpg"></li>
    <li><img src="img/10.gif" data-src="img/7.jpg"></li>
</ul>
<script>
    let imgNodes = [...document.querySelectorAll('img')];
    lazyLoad(imgNodes)
    // debounce
    window.addEventListener('scroll', debounce(lazyLoad, 100, imgNodes))
    // throttle
    window.addEventListener('scroll', throttle(lazyLoad, 100, imgNodes))
</script>

JavaScript正则表达式

正则表达式使用和相关基础

符号

数字.字母等
符号 意义 []中使用
\d 数字 [0-9]
\D 非数字 [^0-9]
\s 空白符 [ \f\n\r\t\v]
\S 非空白符 [^ \f\n\r\t\v]
\w 字母和数字和下划线 [A-Za-z0-9_]
\W 非字母和数字和下划线 [^A-Za-z0-9_]
[\u4e00-\u9fa5] 中文
[\u4E00-\u9FA5A-Za-z0-9_] 中文、英文、数字包括下划线
定位符
符号 意义
\b 单词边界
\B 非单词边界
$ 结尾
^ 开头
代表类
符号 意义 正则使用(//g)
. 任意字符 .+(任意字符一个或者多个)
| 或者 a|b(a或者b都可以匹配到)
[] 范围类 [0-9](代表数字)
[^] 非范围类 [^0-9](非数字)
匹配使用的符号
符号 意义
/g 全局搜索
/i 区分大小写
/m 多行搜索

/[0-9]/gmi

非打印字符
符号 含义
\t 水平制表符
\v 垂直制表符
\n 换行符
\r 回车符
\f 换页符
限定符
符号 使用 含义
? \d? 数字0到1个
+ \d+ 数字一个或者多个
{1} \d{1} 数字1个
{1, 5} \d{1, 5} 数字1到5个
{n, } \d{n, } 数字至少出现一次
* \d* 0个或者任意一个
特殊字符
字符 说明
^ 匹配输入字符串的开始位置。要匹配 "^" 字符本身,请使用 "^"
$ 匹配输入字符串的结尾位置。要匹配 "$" 字符本身,请使用 "$"
( ) 标记一个子表达式的开始和结束位置。要匹配小括号,请使用 "\(" 和 "\)"
[ ] 用来自定义能够匹配 '多种字符' 的表达式。要匹配中括号,请使用 "\[" 和 "\]"
{ } 修饰匹配次数的符号。要匹配大括号,请使用 "\{" 和 "\}"
. 匹配除了换行符(\n)以外的任意一个字符。要匹配小数点本身,请使用 "\."
? 修饰匹配次数为 0 次或 1 次。要匹配 "?" 字符本身,请使用 "?"
+ 修饰匹配次数为至少 1 次。要匹配 "+" 字符本身,请使用 "\+"
* 修饰匹配次数为 0 次或任意次。要匹配 "*" 字符本身,请使用 "\*"
| 左右两边表达式之间 "或" 关系。匹配 "|" 本身,请使用 "\|"
断言
字符 含义
x(?=y) 先行断言: y紧跟x的情况下匹配x。例如,对于/Jack(?=Sprat)/,“Jack”在跟有“Sprat”的情况下才会得到匹配./Jack(?=Sprat)/ “Jack”后跟有“Sprat”或“Frost”的情况下才会得到匹配。不过, 匹配结果不包括“Sprat”或“Frost”。
x(?!y) 负向先行断言: x后无y紧随的情况下匹配x。例如,对于/\d+(?!\。)/,数字后没有跟随小数点的情况下才会得到匹配。对于/\d+(?!.)/.exec(3.141),“3”得到匹配,“141”则无。
(?<=y)x 后行断言: x紧随y的情况下匹配x。例如,对于/(?<=Jack)Sprat/,“Sprat”紧随“Jack”时才会得到匹配。对于/(?<=Jack)Sprat,“Sprat”在紧随“Jack”或“Tom”的情况下才会得到匹配。不过,匹配结果中不包括“Jack”或“Tom”。
(?<!y)x 负向后行断言: x不紧随y的情况下匹配x。例如,对于/(?<!-)\d+/,数字紧随-符号的情况下才会得到匹配。对于/(?<!-)\d+/.exec(3) ,“3”得到匹配。 而/(?<!-)\d+/.exec(-3)的结果无匹配,这是由于数字之前有-符号。

示例

先行断言

// JS Lookahead assertion x(?=y)

let regex = /First(?= test)/g;

console.log('First test'.match(regex)); // [ 'First' ]
console.log('First peach'.match(regex)); // null
console.log('This is a First test in a year.'.match(regex)); // [ 'First' ]
console.log('This is a First peach in a month.'.match(regex)); // null

负向先行断言

console.log(/\d+(?!\.)/g.exec('3.141')); // [ '141', index: 2, input: '3.141' ]

后行断言

let oranges = ['ripe orange A ', 'green orange B', 'ripe orange C',];

let ripe_oranges = oranges.filter( fruit => fruit.match(/(?<=ripe )orange/));
console.log(ripe_oranges); // [ 'ripe orange A ', 'ripe orange C' ]
?: 或者捕获符
// [00:00.000] 作曲 : 接个吻,开一枪
// [00:01] 作词 : 接个吻,开一枪/王东旭/Lambert

let time = elem.match(/\[(\d{2,}):(\d{2})(?:\.(\d{2,3}))?]/g)
console.log(time)
// [00:00.000]
// [00:01]

JavaScript 正则表达式

分组

正则中用()来表示分组$1,$2 $n

replace中的分组
// ([a-z]\d){3} 3组
'a1b2c3d4'.replace(/([a-z]\d){3}/g, 'X')
// Xd4
match中的分组
var str = 'For more information, see Chapter 3.4.5.1';
var re = /see (chapter \d+(\.\d)*)/i;
var found = str.match(re);

console.log(found);

// logs [ 'see Chapter 3.4.5.1',
//        'Chapter 3.4.5.1',
//        '.1',
//        index: 22,
//        input: 'For more information, see Chapter 3.4.5.1' ]

$1,$2 $n符号
'a1b2c3d4'.replace(/(a1)(b2).+/g, '$1$2')
// a1b2
// $1='a1' $2=b2
方法

示例

replace(替换)
const p = 'The quick brown fox jumps over the lazy dog. If the dog reacted, was it really lazy?';

const regex = /dog/gi;

console.log(p.replace(regex, 'ferret'));
// expected output: "The quick brown fox jumps over the lazy ferret. If the ferret reacted, was it really lazy?"

console.log(p.replace('dog', 'monkey'));
// expected output: "The quick brown fox jumps over the lazy monkey. If the dog reacted, was it really lazy?"
match(多行返回数组)
var str = 'For more information, see Chapter 3.4.5.1';
var re = /see (chapter \d+(\.\d)*)/i;
var found = str.match(re);

console.log(found);

// logs [ 'see Chapter 3.4.5.1',
//        'Chapter 3.4.5.1',
//        '.1',
//        index: 22,
//        input: 'For more information, see Chapter 3.4.5.1' ]

// 'see Chapter 3.4.5.1' 是整个匹配。
// 'Chapter 3.4.5.1' 被'(chapter \d+(\.\d)*)'捕获。
// '.1' 是被'(\.\d)'捕获的最后一个值。
// 'index' 属性(22) 是整个匹配从零开始的索引。
// 'input' 属性是被解析的原始字符串。

match多行搜索的时候分组会出现无法获取分组信息

let lrc = `[00:00.000] 作曲 : 接个吻,开一枪
            [00:01] 作词 : 接个吻,开一枪/王东旭/Lambert
            [00:05.260]编曲:接个吻,开一枪
            [00:11.570]早上光照在房间 穿上新衬衫
            [00:14.460]镜子里我又长高了些
            [00:16.960]和同学吃火锅然后开黑带妹
            [00:19.280]一起狼人杀不用上学就不累
            [00:21.630]发条祝福传给你 请你别忘记`
let res = lrc.match(/\[(.+)\]/gm)
console.log(res)
// 0: "[00:00.000]"
// 1: "[00:01]"
// 2: "[00:05.260]"
// 3: "[00:11.570]"
// 4: "[00:14.460]"
// 5: "[00:16.960]"
// 6: "[00:19.280]"
// 7: "[00:21.630]"]
matchAll(针对match多行搜索)

返回一个包含所有匹配正则表达式的结果及分组捕获组的迭代器。

let lrc = `[00:00.000] 作曲 : 接个吻,开一枪
            [00:01] 作词 : 接个吻,开一枪/王东旭/Lambert
            [00:05.260]编曲:接个吻,开一枪
            [00:11.570]早上光照在房间 穿上新衬衫
            [00:14.460]镜子里我又长高了些
            [00:16.960]和同学吃火锅然后开黑带妹
            [00:19.280]一起狼人杀不用上学就不累
            [00:21.630]发条祝福传给你 请你别忘记`
let res = lrc.matchAll(/\[(.+)\]/gm)
console.log([...res])

// 0: (2) ["[00:00.000]", "00:00.000", index: 0, input: ..., groups: undefined]
// 1: (2) ["[00:01]", "00:01", index: 25, input:...
...
exec
var re = /quick\s(brown).+?(jumps)/ig;
var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog');
// result log: 
/**
0: "Quick Brown Fox Jumps"
1: "Brown"
2: "Jumps"
groups: undefined
index: 4
input: "The Quick Brown Fox Jumps Over The Lazy Dog"
**/
test(返回 true 或 false)
let str = 'hello world!';
let result = /^hello/.test(str);
console.log(result); 
// true
search()

方法执行正则表达式和 String 对象之间的一个搜索匹配。

const paragraph = 'The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?';

// any character that is not a word character or whitespace
const regex = /[^\w\s]/g;

console.log(paragraph.search(regex));
// expected output: 43

console.log(paragraph[paragraph.search(regex)]);
// expected output: "."

HTTP系列——首部字段

今天, 学习HTTP首部字段

HTTP协议——HTTP通用首部(常用)

通用首部字段名

通用首部字段名 说明
Cache-Control 控制缓存的行为
Connection 允许客户单和服务器指定与请求/响应链接有关的选项
Date 创建报文的日期时间

Cache-Control

Cache-Control请求指令:
指令 参数 说明
no-cache 强制向源服务器再次验证
no-store 规定缓存不能在本地存储请求的任一部分
max-age=[秒] 必须 比如max-age=31536000,缓存一年
Cache-Control缓存响应指令:
指令 说明
public 可向任意方提供响应的缓存
private 仅向特定用户返回响应
no-cache 缓存前必须先确认其有效性
no-store 规定缓存不能在本地存储响应的任一部分
max-age=[秒] 响应的最大Age值
补充

no-cache:并不意味着不缓存。它的意思是在使用缓存资源之前,它必须经过服务器的检查(revalidate也可以实现这个功能)。
no-store:才是告诉浏览器不要缓存它。
max-age
资源的内容非常稳定,长时间内都不会发生变更,那么我们就可以声明浏览器/CDN可以长时间缓存该资源(31536000秒,即一年),只要用户不手动清理浏览器缓存,一年内源服务器都不再会收到(当前浏览器/CDN)对该资源的请求。
推荐前端静态资源缓存最优解以及max-age的陷阱

Connection

字段 说明
keep-alive 维持长链接
close 关闭链接
keep-alive说明补充

Connection: Keep-Alive 是用于 HTTP持久连接 的字段。

close模式和keep-alive模式的请求对比:

image

Keep-Alive的优缺

优点:keep-alive 模式更加高效,因为避免了连接建立和释放的开销
缺点:长时间的 tcp 连接容易导致系统资源无效占用,浪费系统资源

HTTP协议——HTTP请求首部(常用)

请求首部字段名

请求首部字段名 说明
Host 给出了接收请求的服务器的主机名和端口号
Referer 提供了包含当前请求URL的文档的URL
User-Agent 将发起请求的应用程序名称告知服务器
Accept 服务器可以处理的内容类型(MIME_type)
Accept-Encoding 编码方式(gzip:LZ77压缩算法;compress:LZW 压缩算法;identity:自身)
If-Modified-Since 搭配Last-Modified实现协商缓存
If-None-Match 搭配ETag实现缓存
Authorization 用户凭证;比如(Bearer xxxx)
Cookie 浏览器每次发送请求都会携带

Referer

我在www.google.com 里有一个www.baidu.com 链接,那么点击这个www.baidu.com ,它的header 信息里就有:

Referer=http://www.google.com
Referer的作用:
  • 防盗链

我只允许我自己的网站访问我自己的图片服务器,那我的域名是www.google.com,那么图片服务器每次取到Referer来判断一下是不是我自己的域名www.google.com,如果是就继续访问,不是就拦截。

  • 防止恶意请求。

动态请求是时候,必须 Referer 为我自己的网站。

Accept

请求头用来告知(服务器)客户端可以处理的内容类型,这种内容类型用MIME类型来表示。服务器可以从诸多备选项中选择一项进行应用,并使用Content-Type应答头通知客户端它的选择。

Accept字段
Accept字段 信息
<MIME_type>/<MIME_subtype> 单一精确的 MIME 类型, 例如text/html.
<MIME_type>/* 一类 MIME 类型, 但是没有指明子类。 image/* 可以用来指代 image/png, image/svg, image/gif 以及任何其他的图片类型。
*/* 任意类型的 MIME 类型
;q= (q因子权重) 值代表优先顺序,用相对质量价值表示,又称作权重。
Accept: text/html

Accept: image/*

Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8

HTTP协议——HTTP响应首部(常用)

响应首部字段名

字段 信息
Age 我也没弄懂,干啥的
Server 服务器应用程序软件的名称和版本
Vary 决定了对于未来的一个请求头
Set-Cookie 服务器端向客户端发送 cookie

Vary

Vary实现动态服务

Vary: User-Agent
例如你提供给移动端的内容是不同的,可用防止你客户端误使用了用于桌面端的缓存。 并可帮助Google和其他搜索引擎来发现你的移动端版本的页面,同时告知他们不需要Cloaking。

Vary: Accept-Encoding不同的客户端压缩编码的方式可能不同,可能有的客户端不支持压缩,那么服务器端返回的数据就不能压缩,需要服务器端做不同的数据返回。对于这个问题的解决通过添加Vary的Accept-Encoding告诉服务器支持的类型,来返回特定数据

HTTP协议——HTTP实体首部(常用)

实体首部字段名

实体首部字段 信息
Allow 枚举资源所支持的 HTTP 方法的集合
Content-Encoding 对主体执行的任意编码方式
Content-Length 主体的长度或者尺寸
Content-Type 这个主体的对象类型
ETag 与此实体相关的实体标记
Last-Modified 这个实体最后一次被修改的日期和时间

Allow

当服务器接收到不支持的 HTTP 方法时,会以状态码405 Method Not Allowed 作为响应返回。与此同时,还会把所有能支持的 HTTP 方法写入首部字段 Allow 后返回。

Expires

缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。

Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。

Expires: Wed, 04 Jul 2012 08:26:05 GMT
#通过HTTP的META设置expires和cache-control
<meta http-equiv="Expires"  content="Wed, 04 Jul 2012 08:26:05 GMT">//仅对该网页有效,对网页中的图片或其他请求无效
ps

如果在Cache-Control响应头设置了 "max-age" 或者 "s-max-age" 指令,那么 Expires 头会被忽略

Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。

补充

Content-Encoding和Content-Type及服务器和客户端处理流程

# Response Headers(响应头部)
Content-Encoding: gzip
Content-Type:text/plain;charset=iso-8859-1

以返回hello信息为例:
服务端浏览器发送了一条信息:hello
首先,服务端要告诉浏览器,我给你发的这条数据的类型,不同类型的数据,接收方的处理方式不一样则需要设置Content-Type:text/plain;charset=iso-8859-1告诉浏览器怎么处理;

因为计算机只知道0和1,这时候浏览器收到的应该是:
01101000(h) 01100101(e) 01101100(l) 01101100(l) 01101111(o)

如果我们对'hello'进行gzip算法压缩;那么二进制串已经发生了改变;所以我们还需要告诉浏览器Content-Encoding: gzip

服务器-->Content-Type:text/plain和Content-Encoding:gzip
-->浏览器-->先解析压缩算法Content-Encoding:gzip-->
先解析压缩算法Content-Encoding:gzip-->后解析Content-Type

Last-Modified和If-Modified-Since的关系

浏览器在第一次访问资源时,服务器返回资源的同时,在Response Headers中添加 Last-Modified,值是这个资源在服务器上的最后修改时间:

Last-Modified: Fri, 23 Oct 2020 07:33:48 GMT

浏览器再次请求该资源,则Request Headers添加

If-Modified-Since: Fri, 23 Oct 2020 07:33:48 GMT

服务器再次收到这个资源请求,会根据If-Modified-Since值与服务器中这个资源的最后修改时间对比。如果没有变化,返回304和空的响应体,直接从缓存读取;如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200;

ETag和If-None-Match

浏览器在第一次访问资源时,服务器返回资源的同时当前资源文件的一个唯一标识在Response Headers中添加 ETag(只要资源有变化,Etag就会重新生成):

ETag: "5f92875c-6fa"

浏览器再次请求该资源,则Request Headers添加

If-None-Match: "5f92875c-6fa"

服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了

MIME 类型

MDN MIME 类型

相关博文

https 加密、http2.0、keep-alive

Accept

http的Content-Encoding和Content-Type及服务器和客户端处理流程

浏览器缓存机制

HTML5秘籍--Css3

HTML5秘籍书籍Css3部分进行要点归纳

开发商前缀的特定于浏览器的样式

image
如果需要使用前缀,“Can I use”网站会用一个黄色的文本小气泡表示。比如,CSS3 transforms特性在最新的Firefox或IE中不需要前缀,但在其他浏览器中都需要

效果

透明

# rgba实现
.semitransparentBox {
  background: rgba(170,240,0,0.5);
}

# opacity实现
.semitransparentBox {
  background: rgb(170,240,0);
  opacity: 0.5;
}

opacity属性会将box中的元素变为透明状态, background的rgba只针对背景

圆角

把圆拉伸成椭圆,即让某一个轴向的圆弧更长。要实现这个效果,就要单独设定每一个角(使用border-top-left-radius这样的属性),然后提供两个值:一个是水平半径,另一个是垂直半径

.roundedBox {
  background: yellow;
  border-top-left-radius: 150px 30px;
  border-top-right-radius: 150px 30px;
}

image

阴影

推荐直接网址实现效果
Box Shadow(阴影)-Css3演示

渐变

线性渐变
从bottm到top渐变
background: linear-gradient(to top, white, blue);
从left到right渐变
background: linear-gradient(top left, white, lightblue)
按照百分比渐变
background: linear-gradient(from top, red 0%, orange 20%, yellow 80%,
  violet 100%);
放射性渐变

同线性渐变, 使用radial-gradient

循环渐变
background-image: repeating-linear-gradient(190deg,red,yellow 7%,green 10%);

动画和变形

这一部分我将总结多方面的知识, 单独发布一篇文章进行讲解

字体

@font-face

@font-face功能并且支持大部分浏览器,需要将字体制作成多种格式。最佳实践方案是包含一个WOFF文件(在现代浏览器上性能最好)、一个EOT文件(兼容旧版IE)以及一个TTF或者OTF文件(兼容Android和旧版的非IE浏览器)

1   @font-face {
2     font-family: 'ChantelliAntiquaRegular';
3     src: url('Chantelli_Antiqua-webfont.eot');
4     src: local('Chantelli Antiqua'),
5       url('Chantelli_Antiqua-webfont.woff') format('woff'),
6       url('Chantelli_Antiqua-webfont.ttf') format('truetype'),
7       url('Chantelli_Antiqua-webfont.svg') format('svg');
8   }
  • ❑ ==第1行==:@font-face是正式注册字体的工具,注册之后才能在样式表的其他地方使用该字体。
  • ❑ ==第2行==:给这款字体起个名字,具体叫什么取决于你;将来使用该字体时要用到这个名字。
  • ❑ ==第3行==:必须首先注册EOT格式的字体文件,这样IE不理解其他规则也没有关系,只要忽略其他格式就行了。这里的url()函数用于告诉浏览器在当前位置下载另一个文件。如果把字体放在了网页所在的文件夹中,那么在此只要给出字体文件名即可。
  • ❑ ==第4行==:接下来就要使用local()函数,这个函数告诉浏览器这种字体的名字,如果恰好访客的电脑中安装了这种字体,浏览器就会使用它。不过,个别情况下这个函数也会导致问题(比如,在Mac OS X中,根据访客字体的安装位置不同,可能显示一个安全对话框;另外,也可能会加载另一个只是同名的字体。)为此,Web设计人员有时候会在此传入一个明显瞎编的字体名,让浏览器在本地找不到该字体。比如,有人会使用一个没有意义的笑脸符号,如local('☺')。
  • ❑ ==第5行~第7行==:最后一步是告诉浏览器可以使用的其他文件格式。如果字体包里有WOFF字体文件,建议把它放在第一位,因为这种格式的字体质量最高。然后,再注册TTF或OTF文件,最后注册SVG文件

使用

body {
  font-family: 'ChantelliAntiquaRegular';
}

多栏布局

# column-count实现
text-align: justify;
column-count: 5;
# column-width实现
text-align: justify;
column-width: 10em

添加一条红色的1像素宽的分隔线

-webkit-column-rule: 1px solid red;
-moz-column-rule: 1px solid red;
column-rule: 1px solid red;

column-span属性让图片或其他元素横跨多列

-moz-column-span: all;
-webkit-column-span: all;
column-span: all;

image

响应式设计

media媒体查询

/* Normal styles here */
...

@media (media-feature-name:value) {
  /*符合条件时应用的样式*/
}

@media (min-width: 600px) and (max-width: 700px) {
  /* Override the styles for 600-700 pixel windows. */
}

image
根据这些信息,可以分别应用不同的样式甚至替换整个样式表。

替换整个样式表
<link rel="stylesheet" media="(min-width: 568.01px)" href="standard.css">
<link rel="stylesheet" media="(max-width: 568px)" href="small_styles.css">
<! --[if lt IE 9]>
  <link rel="stylesheet" href="standard.css">
<! [endif]-->
视频替换
<video controls width="400" height="300">
  <source src="butterfly_mobile.mp4" type="video/mp4" media="(max-device-width:480px)">
  <source src="butterfly.mp4" type="video/mp4">
  <source src="butterfly.ogv" type="video/ogg">
</video>

移动端适配(实现为主)

这次文章主要是已实现为主,原理已经有许多大佬的博文了

移动端开发标签

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">

rem原理

通过设置html的fontSzie来实现动态rem,其实就是将页面百分比化
比如:

<html font-size="50px">
    <div class="box" width="1.25rem"></div>
</html>

html为50px; 可以得到1rem为50px(html被分为100rem)

那么:box的1.25rem宽度就可以得到为(50*1.25)px

@media实现html的fontSize设置
@charset "UTF-8";

@mixin queryWidth($min, $max) {
 @if $min == -1 {
   @media screen and (max-width: $max+px) {
     html {
       font-size: ( ($max+1) / 375 ) * 100px;
     }
   }
 } @else if $max == -1 {
   @media screen and (min-width: $min+px) {
     html {
       font-size: ( $min / 375 ) * 100px;
     }
   }
 } @else {
   @media screen and (min-width: $min+px) and (max-width: $max+px) {
     html {
       font-size: ( $min / 375 ) * 100px;
     }
   }
 }
}

@include queryWidth(-1, 319);    // for iphone 4
@include queryWidth(320, 359);   // for iphone 5
@include queryWidth(360, 374);
@include queryWidth(375, 383);   // for iphone 6
@include queryWidth(384, 399);
@include queryWidth(400, 413);
@include queryWidth(414, -1);    // for iphone 6 plus
纯css实现html的fontSize设置

移动端使用rem布局需要通过JS设置不同屏幕宽高比的font-size,结合vw单位和calc()可脱离JS的控制

/* 基于UI width=750px DPR=2的页面 */
html {
    font-size: calc(100vw / 7.5);
}
jq实现html的fontSize设置(保存大屏幕为100px)
$(window).resize(infinite);
             
infinite();
function infinite() {
var htmlWidth = $('html').width();
    if (htmlWidth >750) {
        $("html").css({
            "font-size" : "100px"
        });
    } else {
        $("html").css({
            "font-size" :  100 / 750 * htmlWidth + "px"
        });
    }
}

#body标签
width: 7.5rem;
阿里flexible实现html的fontSize设置
npm i -S amfe-flexible

vw适配(百分比适配)

配置后直接css写px就会自动解析为vw了

# 安装postcss-px-to-viewport:
npm install postcss-px-to-viewport --save

# postcss.config.js配置:
module.exports = {
  plugins: {
    autoprefixer: {},
    "postcss-px-to-viewport": {
        viewportWidth: 375,   // 视窗的宽度,对应的是我们设计稿的宽度,Iphone6的一般是375 (xx/375*100vw)
        viewportHeight: 667, // 视窗的高度,Iphone6的一般是667
        unitPrecision: 3,     // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)
        viewportUnit: "vw",   // 指定需要转换成的视窗单位,建议使用vw
        selectorBlackList: ['.ignore', '.hairlines'],// 指定不转换为视窗单位的类,可以自定义,可以无限添加,建议定义一至两个通用的类名
        minPixelValue: 1,     // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值
        mediaQuery: false     // 允许在媒体查询中转换`px`
    }
  }
}

相关博文

https://www.cnblogs.com/azhai-biubiubiu/p/6003597.html

关于

Origin

蝉鸣如雨,花宵道中,一如少年。

Theme

畅所欲言

Just enjoy it! smiley

Timeline

2021-03-31:上传掘金和sf的文章
2021-03-30:搭建博客网站

JavaScript 排序算法

许久之前学习的JavaScript算法, 后期将更新新的知识点

常见算法方法

/* 转换位置 */
const swap = (array, i, j) => {
	[arr[i], arr[j]] = [arr[j], arr[i]]
}
/* 拷贝数组 */
const copyArray = (arr) => {
	return [...arr];
}
测试方法
/**
* @description 生成指定期间的数组
* @param {Num} arrayNumber 数组数量
* @param {Num} min 最小数
* @param {Num} max 最大数
* @return {number[]} return 数组
*/
const getRandomArray = (arrayNumber, min, max) => {
	const array = [],
		  range = max - min;
	for(let i=0; i<arrayNumber; i++) {
		let rand = Math.random();
		array[i] = min + Math.round(rand * range); 
	}
	return array;
}
// 获取50000个数组的方法
const arr = getRandomArray(50000, 0, 50000)

算法

原生排序(JavaScriptSort: 34.169921875ms)
jsArray.sort((a,b) => a-b)
插入排序(insertSort: 9178.899169921875ms)
let insertArray = copyArray(arr);
function insertSort(arr) {
	for(let i=1; i<arr.length; i++) {
		for(let j=i; j>0 && arr[j] < arr[j-1]; j--) {
			swap(arr, j-1, j);
		}
	}
	return arr
}
插入排序优化版(intensifyInsertSort: 1308.403076171875ms)
let intensifyInsertArray = copyArray(arr);
function intensifyInsertSort(arr) {
	for(let i=1; i<arr.length; i++) {
		let temp = arr[i], j;
		for(j=i; j>0 && arr[j-1] > temp; j--) {
			arr[j] = arr[j-1]
		}
		arr[j] = temp;
	}
	return arr
}
选择排序(selectSort: 2222.005126953125ms)
let selectArray = copyArray(arr);
function selectSort(arr) {
	let minIndex;
	for(let i=0; i<arr.length-1; i++){
		minIndex = i;
		for(let j=i+1; j<arr.length; j++) {
			if(arr[minIndex] > arr[j]) {
				minIndex = j
			}
		}
		swap(arr, i, minIndex);
	}
	return arr;
}
冒泡排序(bubbleSort: 10552.420166015625ms)
let bubbleArray = copyArray(arr);
function bubbleSort(arr) {
	for(let i=0; i<arr.length-1; i++) {
		for(let j=i+1;j<arr.length;j++) {
			if(arr[i] > arr[j]) {
				swap(arr, i, j)
			}
		}
	}
	return arr
}
归并排序(mergeSort: 28.73095703125ms)
let mergeSortArray = copyArray(arr);
function merge(arr, l, mid, r) {
	let i = l, j = mid+1, newArr = arr.slice(l, r+1);
	for(let m = l; m<=r; m++) {
		if(i > mid) {
			arr[m] = newArr[j-l]
			j++;
		} else if(j > r) {
			arr[m] = newArr[i-l]
			i++;
		} else if(newArr[i-l] < newArr[j-l]) {
			arr[m] = newArr[i-l]
			i++;
		} else {
			arr[m] = newArr[j-l]
			j++;
		}
	}
}

function mergeSortEvent(arr, l, r) {
	if(l >= r) return;
	
	let mid = Math.floor((l + r) / 2);
	mergeSortEvent(arr, l, mid);
	mergeSortEvent(arr, mid+1, r);
	merge(arr, l, mid, r);
	
}

function mergeSort(arr) {
	mergeSortEvent(arr, 0, arr.length-1)
}
归并排序优化版(intensifyMergeSort: 25.60986328125ms)
let intensifyMergeArray = copyArray(arr);
function intensifyMerge(arr, l, mid, r) {
	let i = l, j = mid+1, newArr = arr.slice(l, r+1);
	for (let m = l; m<=r; m++) {
		if (i > mid) {
			arr[m] = newArr[j-l]
			j++;
		} else if (j > r) {
			arr[m] = newArr[i-l]
			i++;
		} else if (newArr[i-l] < newArr[j-l]) {
			arr[m] = newArr[i-l]
			i++;
		} else {
			arr[m] = newArr[j-l]
			j++;
		}
	}
}

function intensifyMergeEvent(arr, l, r) {
	if (l >= r) return;
	
	let mid = Math.floor((l + r) / 2);
	intensifyMergeEvent(arr, l, mid);
	intensifyMergeEvent(arr, mid+1, r);
	
	if (arr[mid] > arr[mid+1]) {
		intensifyMerge(arr, l, mid, r);
	}
}

function intensifyMergeSort(arr) {
	intensifyMergeEvent(arr, 0, arr.length-1)
}

HTML5秘籍--表单

HTML5秘籍书籍中表单部分进行要点归纳

表单

检测浏览器对表单验证支持情况的属性

placeholder、autofocus、required、max、min和step。

autofocus

自动对焦, 刷新页面会自动对焦到当前input

<label for="name">Name <em>*</em></label>
<input id="name" placeholder="Jane Smith" autofocus><br>
step

step 属性规定输入字段的合法数字间隔(假如 step="3",则合法数字应该是 -3、0、3、6,以此类推)。

关闭验证

# 表单关闭
<form id="zooKeeperForm" action="processApplication.cgi" novalidate>
# 绕过验证
<input type="submit" value="Save for Later" formnovalidate>

验证挂钩

# required必填
input:required {
  background-color: lightyellow;
}
# invalid没通过验证样式
input:required:invalid {
  background-color: lightyellow;
}

# valid没通过验证样式
input:required:valid {
  background-color: red;
}

使用正则表达式

不必使用^和$字符表示要匹配字段值的开头和结尾。HTML5会自动确保这一点。实际上,这就是说正则表达式匹配的是字段中完整的值,验证的也是整个值的有效性

<label for="promoCode">Promotion Code</label>
<input id="promoCode" placeholder="QRB-001" title=
 "Your promotion code is three uppercase letters, a dash, then three numbers"
 pattern="[A-Z]{3}-[0-9]{3}">

自定义验证

<label for="comments">When did you first know you wanted to be a
 zookeeper? </label>
<textarea id="comments" oninput="validateComments(this)" ></textarea>

这里,onInput事件会触发一个名为validateComments()的函数。这个函数的代码是你自己写的,主要是检测元素的值,然后调用setCustomValidity()方法。

如果当前值有问题,那么在调用setCustomValidity()方法时就需要提供一条错误消息。否则,如果当前值没有问题,调用setCustomValidity()方法时只要传入空字符串即可;这样会清除以前设置过的自定义错误消息。

function validateComments(input) {
  if (input.value.length < 20) {
    input.setCustomValidity("You need to comment in more detail.");
  }
  else {
    //没有错误。清除任何错误消息
    input.setCustomValidity("");
  }
}

提交表单检测

需要为表单的onSubmit事件定义处理函数,根据情况返回true(表示验证通过,可以提交表单)或false(表示验证未通过,浏览器应该取消提交操作)

<form id="zooKeeperForm" action="processApplication.cgi"
 onsubmit="return validateForm()">

# js 以下是一个简单的示例,演示了针对一个必填字段的自定义验证代码:
function validateForm() {
  if (!Modernizr.input.required) {
    // The required attribute is not supported, so you need to check the
    // required fields yourself.

    // First, get an array that holds all the elements.
    var inputElements = document.getElementById("zooKeeperForm").elements;
	console.log(inputElements)
    // Next, move through that array, checking eaching element.
    for(var i = 0; i < inputElements.length; i++) {

      // Check if this element is required.
      if (inputElements[i].hasAttribute("required")) {
        // If this elemnent is required, check if it has a value.
        // If not, the form fails validation, and this function returns false.
        if (inputElements[i].value == "") {
          alert("Custom required-field validation failed. The form will not be submitted.");
          return false;
        }
      }
    }

    // If you reach this point, everything worked out and the browser
    // can submit the form.
    return true;
  }
}

表单节点

滑动条

HTML5的另一个数值类型的控件是range。

image

<input id="weight" type="range" min="50" max="1000" value="160"><br>

可以使用JavaScript响应滑动条的变化事件(即处理onChange事件)

date时间日期

http://prosetech.com/html5/Chapter%2004/DateTypes.html?datetime-local=2021-06-06T10%3A06&date=2021-06-06&time=10%3A09&month=2021-08&week=2021-W16
通过该网址进行查看即可

使用显示输入建议

通过datalist的id属性对应input中的list

image

<legend>What's Your Favorite Animal? </legend>
<datalist id="animalChoices">
  <span class="Label">Pick an option:</span>
  <select id="favoriteAnimalPreset">
  <option label="Alpaca" value="alpaca">
    <option label="Zebra" value="zebra">
    <option label="Cat" value="cat">
    <option label="Caribou" value="caribou">
    <option label="Caterpillar" value="caterpillar">
    <option label="Anaconda" value="anaconda">
    <option label="Human" value="human">
    <option label="Elephant" value="elephant">
    <option label="Wildebeest" value="wildebeest">
    <option label="Pigeon" value="pigeon">
    <option label="Crab" value="crab">
  </select>
  <br>
  <span class="Label">Or type it in:</span>
</datalist>
<input list="animalChoices" name="list">
进度条和计量条

新图形微件是<progress><meter>,这两个元素外观相似,作用不同

progress

适用于百分比定位

image

# 可以用0.25来表示完成了进度的25%:
<progress value="0.25"></progress>

# 等价于
<progress value="50" max="200"></progress>

## 不确定的滚动条
<progress>Task in progress ...</progress>
meter

被称为计量器。一般来说,给<meter>元素设置的值都会对应现实中的某个值(比如,钱数、天数或重量)。为了控制<meter>元素显示这些数据的方式,需要设置一个最大值和一个最小值(使用max和min属性)

<meter min="5" max="70" value="28">28 pounds</meter>

为了让<meter>元素能够表示那些“过高”或“过低”的值,而且还能表示得恰如其分,就需要用到low和high属性

Your suitcase weighs:
<meter min="5" max="100" high="70" value="79">79 pounds</meter>*
<p><small>* A surcharge applies to suitcases heavier than 70 pounds.
</small></p>

Chrome对于过高的值会显示黄条

image

HTML编辑器

使用contenteditable编辑元素
<div id="editableElement" contenteditable="true">You can edit this text, if
you'd like.</div>
使用designMode编辑页面

与contenteditable属性类似,但designMode属性能够让用户编辑整个页面。
把要编辑的文档放在一个<iframe>元素中,而这个元素就充当了一个超级的编辑框

<iframe id="pageEditor" src="ApocalypsePage_Revised.html"></iframe>
<div>
  <button onclick="startEdit()">Start Editing</button>
  <button onclick="stopEdit()">Stop Editing</button>
  
# js
function startEdit() {
  //把<iframe>转换为设计模式
  var editor = document.getElementById("pageEditor");
  editor.contentWindow.document.designMode = "on";
}


function stopEdit() {
  //关闭<iframe>的设计模式
  var editor = document.getElementById("pageEditor");
  editor.contentWindow.document.designMode = "off";
  //显示编码后的HTML(仅为验证确实可以编辑)
  var htmlDisplay = document.getElementById("editedHTML");
  htmlDisplay.textContent = editor.contentWindow.document.body.innerHTML;
}

帮你彻底搞懂JS中的prototype、__proto__与constructor

本文为CSDN博主码飞_CC原创文章,未经博主允许不得转载。
https://blog.csdn.net/cc18868876837/article/details/81211729

 作为一名前端工程师,必须搞懂JS中的prototype、_proto_与constructor属性,相信很多初学者对这些属性存在许多困惑,容易把它们混淆,本文旨在帮助大家理清它们之间的关系并彻底搞懂它们。这里说明一点,__proto__属性的两边是各由两个下划线构成(这里为了方便大家看清,在两下划线之间加入了一个空格:_ _ proto _ _),本文基于谷歌浏览器(版本 72.0.3626.121)的实验结果所得。

原型链

现在正式开始! 让我们从如下一个简单的例子展开讨论,并配以相关的图帮助理解:

function Foo() {...};
let f1 = new Foo();

以上代码表示创建一个构造函数Foo(),并用new关键字实例化该构造函数得到一个实例化对象f1。虽然是简简单单的两行代码,然而它们背后的关系却是错综复杂的,如下图所示:
image

看到这图别怕,让我们一步步剖析,彻底搞懂它们!
  图的说明:右下角为图例,红色箭头表示__proto__属性指向、绿色箭头表示prototype属性的指向、棕色实线箭头表示本身具有的constructor属性的指向,棕色虚线箭头表示继承而来的constructor属性的指向;蓝色方块表示对象,浅绿色方块表示函数(这里为了更好看清,Foo()仅代表是函数,并不是指执行函数Foo后得到的结果,图中的其他函数同理)。图的中间部分即为它们之间的联系,图的最左边即为例子代码。
  首先,我们需要牢记两点:①__proto__constructor属性是对象所独有的;② prototype属性是函数所独有的。但是由于JS中函数也是一种对象,所以函数也拥有__proto__constructor属性,这点是致使我们产生困惑的很大原因之一。

__proto__

  上图有点复杂,我们把它按照属性分别拆开,然后进行分析:
  image

第一,这里我们仅留下 __proto__ 属性,它是对象所独有的,可以看到__proto__属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象),那么这个属性的作用是什么呢?它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往父对象的__proto__属性所指向的那个对象(可以理解为爷爷对象)里找,如果还没找到,则继续往上找…直到原型链顶端null(可以理解为原始人。。。),此时若还没找到,则返回undefined(可以理解为,再往上就已经不是“人”的范畴了,找不到了,到此结束),由以上这种通过__proto__属性来连接对象直到null的一条链即为我们所谓的原型链

prototype

第二,接下来我们看 prototype 属性:
image
  prototype属性,别忘了一点,就是我们前面提到要牢记的两点中的第二点,它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象,由此可知:f1.__proto__ === Foo.prototype,它们两个完全一样。那prototype属性的作用又是什么呢?它的作用就是包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法。任何函数在创建的时候,其实会默认同时创建该函数的prototype对象

constructor

最后,我们来看一下 constructor 属性:
image
  constructor属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数指向该对象的构造函数,每个对象都有构造函数(本身拥有或继承而来,继承而来的要结合__proto__属性查看会更清楚点,如下图所示),从上图中可以看出Function这个对象比较特殊,它的构造函数就是它自己(因为Function可以看成是一个函数,也可以是一个对象),所有函数和对象最终都是由Function构造函数得来,所以constructor属性的终点就是Function这个函数。
image
  感谢网友的指出,这里解释一下上段中“每个对象都有构造函数”这句话。这里的意思是每个对象都可以找到其对应的constructor,因为创建对象的前提是需要有constructor,而这个constructor可能是对象自己本身显式定义的或者通过__proto__在原型链中找到的。而单从constructor这个属性来讲,只有prototype对象才有。每个函数在创建的时候,JS会同时创建一个该函数对应的prototype对象,而函数创建的对象.proto === 该函数.prototype,该函数.prototype.constructor===该函数本身,故通过函数创建的对象即使自己没有constructor属性,它也能通过__proto__找到对应的constructor,所以任何对象最终都可以找到其构造函数(null如果当成对象的话,将null除外)。如下:
image

总结

  1. 我们需要牢记两点:①__proto__constructor属性是对象所独有的;② prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__constructor属性。
  2. __proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,然后返回undefined,通过__proto__属性将对象连接起来的这条链路即我们所谓的原型链
  3. prototype属性的作用,即f1.__proto__ === Foo.prototype
  4. constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function
      本文就此结束了,希望对那些对JS中的prototype__proto__constructor属性有困惑的同学有所帮助。

最后,感谢这两篇博文,本文中的部分内容参考自这两篇博文:

https://www.cnblogs.com/xiaohuochai/p/5721552.html

https://www.cnblogs.com/Narcotic/p/6899088.html

HTML5秘籍--音频与视频

HTML5秘籍书籍音频与视频部分进行要点归纳

音频

<audio src="rubberduckies.mp3" controls></audio>

预加载媒体文件

preload是一个有用的属性,它告诉浏览器应该怎样加载一个媒体文件。指定preload的值为auto,让浏览器下载整个文件,以便用户单击播放按钮时就能播放。当然,下载过程是后台进行的,网页访客不必等待下载完成,而且仍然可以随意查看网页。

除了auto之外,preload属性还支持另外两个值:

metadata和none
  • metadata: 告诉浏览器先获取音频文件开头的数据块,从而足以确定一些基本信息(比如音频的总时长)
  • none: 告诉浏览器不必预先下载。恰当地利用这些值,可以节省带宽。
<audio src="rubberduckies.mp3" controls preload="metadata"></audio>

js创建

# createElement
var audio = document.createElement("audio");
audio.src = "newsound.mp3";

# Audio
var audio = new Audio("newsound.mp3");

补充属性

  • autoplay: 自动播放
  • loop: 循环播放

视频

<video src="butterfly.mp4" controls></video>

视频封面

poster属性用于设置替换视频的图片。浏览器在三种情况下会使用这个图片:(1) 视频第一帧未加载完毕;(2) 把preload属性设置为none; (3) 没有找到指定的视频文件。

<video src="butterfly.mp4" controls poster="swiss_alps.jpg"></video>

基础实现

<video id="videoPlayer" ontimeupdate="progressUpdate()">
    <source src="butterfly.mp4" type="video/mp4">
    <source src="butterfly.webm" type="video/webm">
</video>
<!--进度条-->
<div class="barContainer">
  <div id="durationBar">
    <div id="positionBar" style="width: 100%;"><span id="displayStatus">Paused</span></div>
  </div>
</div>
<!--控制器-->
<div>
    <button onclick="play()">Play</button>
    <button onclick="pause()">Pause</button>
    <button onclick="stop()">Stop</button>
    <button onclick="speedUp()">Fast</button>
    <button onclick="slowDown()">Slow</button>
    <button onclick="normalSpeed()">Normal Speed</button>
</div>

### JavaScript
var video;
var display;
window.onload = function() {
  video = document.getElementById("videoPlayer");
  display = document.getElementById("displayStatus");
  video.onplaying = function() {
    display.innerHTML = "Playing ...";
  }
  video.onpause = function() {
    display.innerHTML = "Paused";
  }
}
// 进度条
function progressUpdate() {
    var positionBar = document.getElementById("positionBar");
    positionBar.style.width = (video.currentTime / video.duration * 100)  + "%";
displayStatus.innerHTML = (Math.round(video.currentTime*100)/100) + " sec";
  }
// 控制部分
function play() {
  video.play();
}
function pause() {
  video.pause();
}
function stop() {
  video.pause();
  video.currentTime = 0;
}
function speedUp() {
  video.play();
  video.playbackRate = 2;
}
function slowDown() {
  video.play();
  video.playbackRate = 0.5;
}
function normalSpeed() {
  video.play();
  video.playbackRate = 1;
}

视频字幕

标记时间的文本轨道和WebVTT

WebVTT(Web Video Text Tracks)格式写的时间标记文本轨道,这是HTML5偏好的格式

WEBVTT


00:00:05.000 --&gt; 00:00:10.000
This caption appears 5 seconds in and lingers until the 10 second mark.


00:01:00.000 --&gt; 00:01:10.000
Now 1 minute has passed. Think about that for 10 seconds.


00:01:10.000 --&gt; 00:01:15.000
This caption appears immediately after the second caption disappears.


00:01:30.000 --&gt; 00:01:35.000
Captions can use &lt;i&gt;line breaks&lt;/i&gt; and &lt;b&gt;simple&lt;/b&gt; HTML markup.

使用添加字幕

default属性为默认字幕

<video controls width="700" height="400">
  <source src="butterfly.mp4" type="video/mp4">
  <source src="butterfly.webm" type="video/webm">
  <track src="butterfly.vtt" srclang="en" kind="subtitles" label="English"
  default>
  <track src="butterfly_fr.vtt" srclang="fr" kind="subtitles" label="French">
</video>

兼容性

HTML支持多种格式

<video><audio>元素有一个内置的格式后备系统。要使用它,就要从<video><audio>元素中删除src属性,然后嵌套一组<source>元素。以下是一个<audio>元素嵌套<source>元素的例子:

<audio controls>
  <source src="rubberduckies.mp3" type="audio/mp3">
  <source src="rubberduckies.ogg" type="audio/ogg">
  <p>We like disco dancing.</p> // 都不支持的情况下
</audio>

在此,一个元素嵌套了两个元素,每个元素都指向一个不同的音频文件。浏览器会选择播放第一个它所支持的文件。Firefox和Opera会播放rubberduckies.ogg,而IE、Safari和Chrome会播放rubberduckies.mp3。

js兼容模式

只要canPlayType()不返回空字符串,就说明浏览器支持相应的格式

if (audio.canPlayType("audio/ogg")) {
  audio.src = "newsound.ogg";
}
else if (audio.canPlayType("audio/mp3")) {
  audio.src = "newsound.mp3";
}

实践带你了解--http缓存

这次文章为了了解http缓存的机制,自己搭建nginx和设置nginx配置
网上的文章其实有很多,但是大部分都是文字表达,缓存这块又比较偏向理论,导致我一开始也是云里雾里的

所以这次我通过截图和步骤图来说明,并且进行文字补充, 不喜欢看文字的话其实看图片就行啦

以下每次截图都是无痕模式的

强缓存和协商缓存

在这之前我们想要先了解强缓存和协商缓存:
强缓存: 不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。通常cache-controlExpires的属性进行配置

协商缓存: 强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程; 通常Last-ModifiedETag进行验证, 返回304 not modified 状态码

强缓存(cache-control)

max-age

我在nginx中关闭了协商缓存, 并且nginx的sever配置中添加Expires @15h13m; (@15h13m为每天15点13分缓存刷新)
image
在nginx中设置了Expires, 而服务器返回的response headers携带了cache-control: max-age的字段和Expires的字段, 其实两个缓存指向同一时间点刷新

我们可以看到在规定的时间内浏览器刷新页面会触发缓存,过期后则重新请求

no-cache(关闭etag和Last-Modified)

image

no-cache: 缓存需要向服务器验证;

当我们开启了no-cache, 浏览器没有缓存的情况下, 向服务器进行协商缓存, 但是我们关闭了协商缓存的etagLast-Modified, 所以请求重新发送

no-store(开启Etag和Last-Modified)

image
no-store: 这个字段就是告诉浏览器不进行缓存

协商缓存

Last-Modified和f-Modified-Since(关闭Etag)

浏览器在第一次访问资源时,服务器返回资源的同时,在response header中添加 Last-Modified的header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和header;
比如:

Last-Modified: Fri, 23 Oct 2020 07:33:48 GMT

那么下一次请求, 浏览器将携带Last-Modified的值到If-Modified-Since, 并且放在request headers 中进行缓存验证
image
服务器会根据If-Modified-Since值与服务器中这个资源的最后修改时间对比
如果没有变化,返回304和空的响应体,直接从缓存读取
如果If-Modified-Since小于服务器的最后修改时间,说明文件有更新,于是返回新的资源文件和200

Last-Modified存在的弊端:
  • 即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
  • Last-Modified 只能以秒计时, 秒以下的时间内进行修改无法感知, 返回错误的资源

协商缓存ETag和If-None-Match(关闭Last-Modified)

协商缓存ETagLast-Modified类似, 不过etag是算法生成的字符串

image

etag解决Last-Modified存在的问题, 那么为什么服务器不直接全部使用etag而淘汰Last-Modified, 而是二者同存呢?

强缓存和协商缓存的优先级验证

image

图中我们可以看出第一次刷新进行了强缓存, 强缓存过期后, 浏览器携带字段向服务器进行协商缓存

缓存流程图

image

喜欢该文章的话可以点个赞哦, 这是我创作的动力哈

相关文章

浏览器缓存机制

arrayBuffer转audioBuffer实现播放和控制音量

搜索了很久文章,感觉网上很多将audioBuffer实现播放和可视化混在一起
如果只是实现简单的播放那未免太过复杂,audio相关的api又过多很容易搞不清楚

遇到的场景

我们通过ajax的方法,从路径或者后台获取
也可以input的flie中FileReader解析mp3文件获取

哪些是arrayBuffer
  • FileReader获取arrayBuffer
var file = this.files[0];
var fr = new FileReader();

fr.onload = function(e){
	// 这边e.target.result已经是arraybuffer对象类型
	console.log(e.target.result);
}
  • ajax方法获取

ajax获取路径时,需要标识responseType

axios({
	method: 'get',
	url: 'assets/music/test.mp3',
	responseType: 'arraybuffer'
}).then((res) => {
    console.log(res.data) //这个是arraybuffer
})
arrayBuffer转为audioBuffer

通过AudioContext对象方法

const audioContext = new AudioContext();
const audioSource = audioContext.createBufferSource()

audioContext.decodeAudioData(arrayBuffer, (buffer) => {
	audioSource.buffer = buffer;
	audioSource.connect(audioContext.destination);
})
实现播放音频
// 控制播放等操作
const audioSource = audioContext.createBufferSource()

audioContext.decodeAudioData(arrayBuffer, (buffer) => {
	audioSource.buffer = buffer;
	audioSource.connect(audioContext.destination);
	// 播放音乐
	audioSource.start()
})
实现音频时转换过程

image

实现控制音量
const audioContext = new AudioContext();
// 控制音量
const gainNode = audioContext.createGain();
// 控制音频
const audioSource = audioContext.createBufferSource()

audioContext.decodeAudioData(arrayBuffer, (buffer) => {
	audioSource.buffer = buffer;
	// 设置音量为0.5
	gainNode.gain.value = 0.5
	gainNode.connect(audioContext.destination);
	// 播放
	audioSource.start()
})

额外补充

AudioContext

是一个音频上下文,像一个大工厂,所有的音频在这个音频上下文中处理。

let audioContext = new(window.AudioContext || window.webkitAudioContext)();

AudioContext 音频上下文提供了很多属性和方法,用于创建各种音频源和音频处理模块等,这里只介绍一部分,更多属性和方法可到MDN查阅文档。

AudioContext.destination

返回 AudioDestinationNode 对象,表示当前 AudioContext 中所有节点的最终节点,一般表示音频渲染设备。

主要对象和方法
  • AudioContext.createBufferSource()

创建一个 AudioBufferSourceNode 对象, 他可以通过 AudioBuffer 对象来播放和处理包含在内的音频数据。

  • AudioContext.createGain()

创建一个 GainNode,它可以控制音频的总音量。

  • 对象: AudioNode

音频节点接口是一个音频处理模块。包括音频源,音频输出,中间处理模块。

  • AudioNode.connect()

链接两个 AudioNode 节点,把音频从一个 AudioNode 节点输出到另一个 AudioNode 节点,形成一个音频通道。

源码

let audioContext = new AudioContext();
let gainNode = audioContext.createGain();
let audioSource = audioContext.createBufferSource()

function decodeAudioData(audioContext, url) {
	return new Promise((resolve) => {
		axios({
			method: 'get',
			url: url,
			responseType: 'arraybuffer'
		}).then((res) => {
			audioContext.decodeAudioData(res.data, (buffer) => {
				resolve(buffer);
			})
		})
	})
}

let buffer = decodeAudioData(audioContext, 'assets/music/test.mp3');

buffer.then((res) => {
	audioSource.buffer = res;
	
	audioSource.connect(gainNode)
	gainNode.gain.value = 0.5
	gainNode.connect(audioContext.destination);
	
	audioSource.start()
})
参考

HTML5音频API Web Audio

通过Web Audio API可视化输出MP3音乐频率波形

图片懒加载(IntersectionObserver)

基于JavaScript的API(IntersectionObserver)实现图片懒加载

scroll实现

上次我们聊到的是throttle和debounce去实现
实现图片懒加载(throttle, debounce)

不过,就算throttle和debounce是实现图片懒加载,但是scroll事件密集发生,计算量很大,容易造成性能问题。

IntersectionObserver

这api主要负责监听元素和"视口"(viewport)的关系,我就不多讲述了

  • API

Intersection Observer - Web API 接口参考 | MDN

IntersectionObserver API 使用教程 - 阮一峰

实现

下方附带github源码

class LazyLoad{
    constructor(images, options = {}) {
		this.setting = Object.assign({}, {src: 'data-src', srcset: 'data-srcset'}, options)
		this.images = images
		this.observer = null
		this.init()
	}
	init() {
		let observerConfig = {
			root: null,
			rootMargin: '0px',
			threshold: [0]
		}
		this.observer = this.intersectionObserver(observerConfig)
		this.images.forEach(image => this.observer.observe(image))
	}
}
  • observerConfig中的属性
  1. root: 视图节点,null时默认为body(应该是)
  2. rootMargin 根元素的margin
  3. threshold属性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0],即交叉比例(intersectionRatio)达到0时触发回调函数
生成IntersectionObserver实例
intersectionObserver(config) {
    return new IntersectionObserver(entries => {
    	entries.forEach(entry => {
    	  const target = entry.target
    	  // 到元素出现在视图中
    	  if(entry.intersectionRatio > 0) {
    			this.observer.unobserve(target)
    			// 设置img真实src路径
    			this.setImgsrc(target)
    	  }
    	})
    }, config)
}
设置img真实src路径
setImgsrc(target){
	const src = target.getAttribute(this.setting.src)
	const srcset = target.getAttribute(this.setting.srcset)
	// 判断是否为img节点
	if('img' === target.tagName.toLowerCase()) {
		if(src) {
		    target.src = src
		}
		if(srcset) {
		    target.srcset = srcset
		}
	}else {
		target.style.backgroundImage = `url(${src})`
	}
}
添加全局设置

这一个步骤可能多余了,但是在实现组件的时候经常会有全局设置,方面更改

const glabolConfig = {
	src: 'data-src',
	srcset: 'data-srcset'
}
class LazyLoad{
	constructor(images, options = {}, glabolConfig) {
		this.setting = Object.assign({}, glabolConfig, options)
	}
}
保护局部变量
;(function(window, glabolConfig) {
	class LazyLoad{
	    ...
	}
})(window, glabolConfig);	
无new生成实例
window.lazyLoad = (images, options = {}) =>{
	return new LazyLoad(images, options, glabolConfig)
}
使用
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<style>
			img, body, ul, li, html {
				margin: 0;
				padding: 0;
			}
			img{
				height: 700px;
				width: 100vw;
			}
		</style>
		
	</head>
	<body>
		<ul>
			<li><img src="img/10.gif" data-src="img/1.jpg"></li>
			<li><img src="img/10.gif" data-src="img/2.jpg"></li>
			<li><img src="img/10.gif" data-src="img/3.jpg"></li>
			<li><img src="img/10.gif" data-src="img/4.jpg"></li>
			<li><img src="img/10.gif" data-src="img/5.jpg"></li>
			<li><img src="img/10.gif" data-src="img/6.jpg"></li>
			<li><img src="img/10.gif" data-src="img/7.jpg"></li>
		</ul>
		<script src="js/intersectionObserver-lazyload.js"></script>
		<script>
			let imgItems = [...document.querySelectorAll('img')];
			lazyLoad(imgItems)
		</script>
	</body>
</html>
源码

github地址

参考

图片懒加载的前世今生

前端优化(webpack, js, html)

前端优化的使用, 以及解析, 分为webpack设置优化, js方面优化, html优化

HTTP优化

gzip压缩

资源的压缩与合并

HTTP协议上的gzip编码是一种用来改进web应用程序性能的技术,web服务器和客户端(浏览器)必须共同支持gzip。

浏览器请求url,并在request header中设置属性accept-encoding:gzip。表明浏览器支持gzip。

该不该用 Gzip

压缩 Gzip,服务端要花时间;解压 Gzip,浏览器要花时间。中间节省出来的传输时间,真的那么可观吗?建议较大文件进行gizp

webpack4中启动gzip压缩
npm i -D compression-webpack-plugin

plugins: [
  new CompressionPlugin({
    filename: "[path].gz[query]",
    algorithm: "gzip",
    test: /\.js$|\.html$|\.css/,
    threshold: 10240, // 只处理比这个值大的资源。按字节计算
    minRatio: 0.8 // 	只有压缩率比这个值小的资源才会被处理
    deleteOriginalAssets: false, //是否删除原资源
  }),
];

详细配置 CompressionWebpackPlugin

CDN加速

现在大部分云服务商都是提供cdn服务

image

简单的来说: 原服务器上数据复制到其他服务器上,用户访问时,那台服务器近访问到的就是那台服务器上的数据。

CDN加速优点是成本低,速度快。可以用CDN best的CDN进行加速,免费,可部署私有,公有CDN系统。可以实现宕机检测,自动切换ip,分线路,分组解析。也就是CDN加速的主要作用就是保证网站的正常访问,及加快网站访问速度和响应速度,防止网站因黑客攻击,DNS解析劫持故障等导致的网站服务器的宕机状况的出现。

图片(图标)方面

使用字体图标

推荐: Iconfont-阿里巴巴矢量图标库

雪碧图

将多个图标集成在一起

雪碧图制作起来麻烦,我还是推荐Iconfont的字体图标啦

图片使用Base64编码减少页面请求数(建议小图片)

Base64编码图片可以在浏览器自己显示出来

采用Base64的编码方式将图片直接嵌入到网页中,而不是从外部载入,如<img src="data:image/gif;base64,/9j/2SDFG... >,这样下载HTML文档的时间就会增长了。在CSS背景图中也是可以这么做的

使用webpack处理图片成 base64
npm install --save-dev url-loader
// webpack.config.js
module.exports = {
  module: {
    rules: [{
      test: /\.(png|svg|jpg|gif|jpeg|ico|woff|woff2|eot|ttf|otf)$/,
      use: [{
          loader: "url-loader", // 根据图片大小,把图片优化成base64
          options: {
            limit: 10000, //小于10000字节的图片都进行base64操作
          }
        }
      ]
    }]
  }
};
webpack配置 JPG、PNG、GIF和SVG图像的压缩
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        loader: "image-webpack-loader", // 先进行图片优化
        options: {
          mozjpeg: {
            progressive: true,
            quality: 65,
          },
          optipng: {
            enabled: false,
          },
          pngquant: {
            quality: "65-90",
            speed: 4,
          },
          gifsicle: {
            interlaced: false,
          },
          webp: {
            quality: 75,
          },
        },
      },
    ],
  },
};

html,css优化

<script>标签和<style>标签(看注释)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  /**
  * 放头部: 页面先解析<style>后得到css后解析html
  */
  <style>...<style>
  /**
  * 放头部: 页面的解析在遇到js的时候需要运行完js之后才能继续
  * 缺点:js一旦加载或者运算很久,会导致用户白屏
  */
  <script>
    for (let i=0; i < 1000000000000000; i++) {
        console.log("年轻人不讲码德! 你就慢慢等吧")
    }
  </script>
</head>
<body>
  <div id="container"></div>
  /**
  * 放尾部: 页面的解析完毕开始加载js
  * 优点:用户无需等待js加载就可以看到界面
  */
   <script>
    var container = document.getElementById("container")
    console.log('container', container) // '<div id="container"></div>'
  </script>
</body>
  /**
  * 放尾部: 页面先解析html后得到解析css
  * 缺点:导致html节点出来没样式后才有
  */
  <style>...<style>
</html>
回流和重绘

需要浏览器渲染机制,该文章就不展开讲了

回流(重排)

回流又名重排,指几何属性需改变的渲染。触发浏览器回流并重新生成渲染树

重绘

重绘指更改外观属性而不影响几何属性的渲染。渲染树的节点发生改变,但是不影响该节点的几何属性

  • 几何属性:包括布局、尺寸等可用数学几何衡量的属性
布局:display、float、position、list、table、flex、columns、grid
尺寸:margin、padding、border、width、height
  • 外观属性:包括界面、文字等可用状态向量描述的属性
界面:appearance、outline、background、mask、box-shadow、box-reflect、filter、opacity、clip
文字:text、font、word

js优化

减少 DOM 操作
  • 减少DOM 更改等操作
// bad
let box = document.getElementById('box')

box.innerHTML = '1'
box.innerHTML += '2'
box.innerHTML += '3'
...

// good
let content = ''
content = '1'
content += '2'
content += '3'

box.innerHTML = content // 一次性插入
  • DocumentFragment

不是真实 DOM 树,它的变化不会引起 DOM 树的重新渲染

let box = document.getElementById('box')

let content = document.createDocumentFragment()

let Fdiv = document.createElement('div')

Fdiv.innerHTML = '1'
Fdiv.innerHTML += '2'
Fdiv.innerHTML += '3'

content.appendChild(Fdiv)
// 只产生一次dom操作
box.appendChild(content) 
图片懒加载
<li><img src="img/loading.gif" data-src="img/1.jpg"></li>

最开始每一个图片使用loading.gif,首屏加载的时候可以节省多张图片请求

原理:滚动条设置到图片的时候加载data-src内的正常图片

具体实现可以看我之前发的文章实现图片懒加载

防抖和节流
防抖: 在一定时间内,只能触发一次
/**
 * @param {Function} func 要执行的回调函数 
 * @param {Number} wait 延时的时间
 * @param {Boolean} immediate 是否立即执行
 * @return null
 */
let timer, flag;
function throttle(func, wait = 500, immediate = true) {
	if (immediate) {
		if (!flag) {
			flag = true;
			// 如果是立即执行,则在wait毫秒内开始时执行
			typeof func === 'function' && func();
			timer = setTimeout(() => {
				flag = false;
			}, wait);
		}
	} else {
		if (!flag) {
			flag = true
			// 如果是非立即执行,则在wait毫秒内的结束处执行
			timer = setTimeout(() => {
				flag = false
				typeof func === 'function' && func();
			}, wait);
		}
		
	}
};
export default throttle
节流: 一定时间内,只有最后一次操作,再过wait毫秒后才执行函数
let timeout = null;
/**
 * @param {Function} func 要执行的回调函数 
 * @param {Number} wait 延时的时间
 * @param {Boolean} immediate 是否立即执行 
 * @return null
 */
function debounce(func, wait = 500, immediate = false) {
	// 清除定时器
	if (timeout !== null) clearTimeout(timeout);
	// 立即执行,此类情况一般用不到
	if (immediate) {
		var callNow = !timeout;
		timeout = setTimeout(function() {
			timeout = null;
		}, wait);
		if (callNow) typeof func === 'function' && func();
	} else {
		// 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
		timeout = setTimeout(function() {
			typeof func === 'function' && func();
		}, wait);
	}
}

export default debounce

webpack4优化(打包过大等优化)

webpack 设置cdn优化

vuecli生成的项目进行配置

运行yarn build 发现vendors.js之中包含vue,vuex, vue-router
现在我们可以通过webpack的externals进行vue,vuex抽离掉使用cdn

# vue.config.js
module.exports = {
  configureWebpack:{
    externals: {
       'vue': 'Vue',
       'vuex': 'Vuex'
    }
  }
}

# store/index.js
import Vuex from "vuex";

// Vue.use(Vuex); 注释掉
// index.html
<body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/vuex/3.1.3/vuex.min.js"></script>
  </body>
splitChunks(打包分割)

运行yarn build 发现vendors.js之中还有vue-router需要分离
可以利用webpack中的splitChunks分割出来

splitChunks的配置挺复杂的,需要大家自己学习,我只是提供一个方案

# vue.config.js
module.exports = {
  configureWebpack: {
    optimization: {
      splitChunks: {
        cacheGroups: {
          vendors: {
            test: /node_modules/,
            chunks: "initial",
            minChunks: 1,
            priority: -10,
          },
          router: {
            name: 'chunk-router',
            test: /[\\/]node_modules[\\/]vue-router[\\/]/,
            chunks: "all",
            minChunks: 1,
            priority: -5,
          }
        },
      },
    },
  },
};

相关博文

玩转CSS的艺术之美

VUE3 源码解析Reactivity(Reflect and Proxy)

最近研究vue3源码, 知道使用了Proxy和Reflect, 解析原理

这篇文章主要是让大家了解vue3为什么使用Proxy和Reflect以及响应式的部分原理

为什么使用Proxy(Proxy和Object.defineproperty)

Object.defineproperty实现对象监听

先解析一下vue2中使用的Object.defineproperty

let obj = {
    a: 10
}
Object.keys(obj).forEach(key => {
    let value = obj[key]
    Object.defineProperty(obj, key, {
        set(newValue) {
            console.log(`监听${key}改变: ${newValue}`);
            value = newValue
        },
        get() {
            console.log(`获取${key}对应的值: ${value}`);
            return value
        }
    })
})
obj.a = 100 // 监听a改变: 100
obj.b = 10 // 不会被监听到

通过上面的例子我们可以看到obj新添加的属性b, 并不会被监听到
vue2中使用中我们也会遇到这样的问题

# template
<p @click="adda(obj)">{{ obj.a }}</p>
<p @click="addb(obj)">{{ obj.b }}</p>

# srcript
data () {
    return {
        obj:{
            a:1
        }
    }
},
mounted () {
    this.obj.b = 1;
},
methods: {
    addb(item){
        item.b += 1;
        console.log(this.obj.b)
    },
    adda(item){
        item.a += 1;
    }
}

我们发现点击obj.a是响应式, 页面也会更新
而新增的obj.b点击则不会

因为vue2使用的Object.defineproperty无法监听到新增的对象属性

针对这个问题vue2提供了$set方法来解决

mounted () {
    this.$set(this.obj, "b", 1)
}
Proxy实现对象监听
let obj = {
    a: 10
}
const handler = {
    get(target, prop) {
        console.log(`获取${prop}对应的值: ${target[prop]}`);
        return target[prop];
    },
    set(target, prop, val) {
        target[prop] = val;
        console.log(`监听${prop}改变: ${val}`);
        return true
    }
}

let obj2 = new Proxy(obj, handler)
obj2.b = 100 // 监听b改变: 100

我们可以看到通过Proxy实例可以对新添加的属性进行监听

当然Proxy还可以做许多的其他功能, 这里就不多介绍了

我查看Vue3的源码的时候一直对Proxy中使用的Reflect感到不解,为什么要使用Reflect.get和Reflect.set, 我查询了一些文章, 大概了一下思路

Reflect

我将通过一些问题, 来指明Reflect中Proxy中的用处

我们有一个user带有_name属性的对象和一个吸气剂。

这是围绕它的代理:

let user = {
  _name: "Guest",
  get name() {
    return this._name;
  }
};

let userProxy = new Proxy(user, {
  get(target, prop, receiver) {
    return target[prop];
  }
});

console.log(userProxy.name); // Guest

对于我们的示例而言,这就足够了。

一切似乎都还好。但是,让我们将示例变得更加复杂。

继承另一个对象后admin从user,我们可以观察到不正确的行为:

let user = {
  _name: "Guest",
  get name() {
    return this._name;
  }
};

let userProxy = new Proxy(user, {
  get(target, prop, receiver) {
    console.log(target) // user对象{_name: "Guest"}
    return target[prop];
  }
});

let admin = {
  __proto__: userProxy,
  _name: "Admin"
};

console.log(admin.name); // Guest

阅读admin.name应该返回"Admin",而不是"Guest"!

怎么了?也许我们在继承方面做错了什么?

问题实际上出在代理所在的行中:

  1. 当我们阅读时admin.name,由于admin对象没有自己的属性,搜索将转到其原型。
  2. 原型是userProxy
  3. name从代理读取属性时,其get将触发并从原始对象中返回该属性,它在上下文中运行其代码this=target。因此,结果this._name来自原始对象target,即:from user

而这个时候就是Reflect.get就派上用场了

如果我们使用它,一切都会正常运行。

let user = {
  _name: "Guest",
  get name() {
    return this._name;
  }
};

let userProxy = new Proxy(user, {
  get(target, prop, receiver) { // receiver = admin
    return Reflect.get(target, prop, receiver);
  }
});


let admin = {
  __proto__: userProxy,
  _name: "Admin"
};

console.log(admin.name); // Admin

Reflect.get中receiver参数,保留了对正确引用this(即admin)的引用,该引用将Reflect.get中正确的对象使用传递给get。

相关文章

https://javascript.info/proxy

前端常用插件utils汇总

收集了一些热门的github开源插件

工具库 || 数据处理

underscore - JavaScript的实用程序带库

lodash - 是一个一致性、模块化、高性能的 JavaScript 实用工具库。

表单验证

async-validator – 验证表单异步

---jquery

jquery-validation - 您现有的表单提供了插入式验证,同时使各种自定义适合您的应用程序变得非常容易

图片懒加载

---JavaScript

lazyload - 用于延迟加载图像

lazyload - LazyLoad是一种快速,轻巧和灵活的脚本,通过仅在内容图像,视频和iframe进入视口时加载它们来加快Web应用程序的速度

---vue

vue-lazyload - 一个Vue.js插件,用于将图像或组件延迟加载到应用程序中。

---react

react-lazyload - 延迟加载组件,图像或任何与性能有关的内容

图片预览

类似朋友圈

PhotoSwipe- 适用于移动和pc,模块化,框架独立的JavaScript图像库

满足聊天递增图片的需求

viewerjs - JavaScript图像查看器

fancybox - jQuery lightbox脚本,用于显示图像,视频等。触摸启用,响应迅速且可完全自定义

---vue

vue-picture-preview - 移动端、PC 端 Vue.js 图片预览插件

文件上传

---JavaScript

DropzoneJS - 提供带有图像预览的拖放文件上传

Web Uploader - 以HTML5为主的现代文件上传组件(百度)

jQuery-File-Upload - 具有多个文件选择,拖放支持,进度条,验证和预览图像,jQuery的音频和视频。

---vue

vue-dropzone - Dropzone.js的Vue.js组件-带有图像预览的拖放文件上传实用程序

单选框/复选框相关

---jquery

iCheck – 增强复选框和单选按钮

选择框

Choices - 轻量级库,用于制作高度可自定义的选择框、文本区域和其他输入表单。类似于select2和selectize,但没有依赖jquery。

Chosen - 一个使较长的笨拙的选择框更友好的库。

Select2 - 基于jQuery的选择框的替代品。它支持搜索,远程数据集和结果的无限滚动。

bootstrap-select - jQuery插件通过直观的多选,搜索等功能将选择元素带入21世纪

tree树形

---jquery

Bootstrap Tree View - Tree View for Twitter Bootstrap

zTree - 一个依靠 jQuery 实现的多功能 “树插件”

无限滚动

Infinite Scroll - 自动添加下一页

---vue

vue-infinite-scroll - vue.js的无限滚动指令

列表拖拽

Sortable - 是一个JavaScript库,用于在现代浏览器和触摸设备上对拖放列表进行重新排序

MDN拖拽文档

---vue

Vue.Draggable - 基于Sortable.js的Vue拖放组件

---react

react-sortablejs - 在成熟的拖放库Sortable之上构建的React组件

元素拖曳

draggabilly - 使该shiz可拖动

自定义滚动条

jScrollPane-跨浏览器自定义滚动条

perfect-scrollbar - 简约但完美的自定义滚动条插件

进度条

nprogress - 对于细长的进度条,例如YouTube,Medium等上的进度条

---github类似页面进度条

vue-progressbar - vue的轻量级进度条

cookie管理

js-cookie - 一个简单,轻巧的JavaScript API,用于处理浏览器cookie

WebSocket

ws - 简单易用,为Node.js开创了经过快速且经过全面测试的WebSocket客户端和服务器

socket.io - 实时应用程序框架

---vue

Vue-Socket.io - Vuejs和Vuex的Socket.io实现

JavaScript 动画效果库

Vue官网推荐

Velocity - 是一个简单易用、高性能、功能丰富的轻量级JS动画库

Animate.css - 一款强大的预设css3动画库

tween.js - JavaScript补间引擎可简化动画

额外效果

transformjs - 腾讯使CSS3转换超级容易

scenejs

midnight.js - 文字颜色随着背景变

vue-countTo - 在指定的持续时间内计入目标数量

CSS shake - 抖动动画

轮播效果

swiper5 - 纯javascript打造的滑动特效插件,面向手机、平板电脑等移动终端

iSlider - 适用于Mobile WebApp,HTML5 App,Hybrid App的平滑移动触摸滑块

---vue

vue-awesome-swiper - 基于Swiper4,适用于Vue的轮播组件

瀑布流展示

scrollreveal - 在元素滚动到视图时对其进行动画处理

scrollreveal 的演示地址

pc桌面级通知

push.js - 世界上最通用的桌面通知框架

响应式媒体查询

react中antd框架grid组件使用

enquire.js - JavaScript中的媒体查询

操作引导js工具

Intro.js - 为您的网站和项目提供新功能介绍和逐步用户指南的更好方法

driver.js —— 前端操作引导js工具

编辑器工具

editor.js - 具有干净JSON输出的块样式编辑器

UEditor - 由百度web前端研发部开发所见即所得富文本web编辑器

Markdown编辑器

SimpleMDE-Markdown - 一个简单,美观,可嵌入的JavaScript Markdown编辑器

simditor - 方便快捷的所见即所得编辑器

---想掘金一样的markdown解析器(双栏)

marked – markdown解析器

视频

flv.js - 用纯JavaScript编写的HTML5 Flash Video(FLV)播放器(bilibili)

video.js - Video.js是专为HTML5世界打造的网络视频播放器。它支持HTML5和Flash视频

html5media - 简单的h5player,轻量级

jwplayer - 被大量网站使用

日期选择 || 日历

Pikaday - 令人耳目一新的JavaScript Datepicker —轻巧,没有依赖关系,模块化CSS

bootstrap-datepicker - Twitter引导程序(@twbs)的 日期选择器

时间选择

有vue的版本,也支持日期选择

Flatpickr - 轻巧,功能强大的javascript datetimepicker,无依赖项

时间处理

Moment.js - 在JavaScript中解析,验证,操作和显示日期和时间。

模拟数据

Mock - 模拟数据生成器

代码注释

JSDoc - 是JavaScript的API文档生成器

触摸方面 || 手势

PhyTouch - 腾讯丝般顺滑的触摸运动方案

hammer.js - 一个用于多点触摸手势的javascript库

better-scroll - 受iscroll的启发,它支持更多功能并具有更好的滚动性能

loading加载动画

loaders.css - 令人愉悦且注重性能的纯CSS加载动画

css-spinners - 使用CSS和最少的HTML标记制作的简单CSS旋转器和控件

progressbar.js - 反应灵敏的进度条

通知组件

notie - 干净,简单的javascript通知,输入和选择套件,没有依赖项

全屏滚动

fullPage.js - 轻松创建全屏滚动网站

pagePiling.js - Alvaro Trigo的pagePiling插件。创建滚动显示的部分

fullPage.js - 专注于移动端

---vue

Vue-fullpage.js - fullPage.js的官方Vue.js包装器

模板引擎

sodajs - 腾讯重量轻但功能强大的JavaScript模板引擎

list || table组件

vue-easytable - vue table 组件,支持 单元格合并、单元格编辑、多表头固定、多列固定、列拖动、排序、自定义列、条件过滤、分页

list.js - 完美的库,可为表,列表和各种HTML元素添加搜索,排序,过滤器和灵活性

浏览器属性

bowser - 浏览器属性,例如名称,版本,渲染引擎

excel处理

sheetjs - 电子表格数据工具包

exceljs - 读取,操作并将电子表格数据和样式写入XLSX和JSON。

网格布局库

Masonry - 级联网格布局库

FFmpeg(c模块 视频帧预览)

ffmpeg

WebAssembly | MDN

地图

Leaflet - 适用于移动设备的交互式地图的JavaScript库

表情包扩展

twemoji - 一个简单的库,可在所有平台上提供标准Unicode 表情符号支持。

浏览器测试

cypress - 对浏览器中运行的所有内容进行快速,轻松和可靠的测试

浏览器代码编辑器

[]monaco-editor - 基于浏览器的代码编辑器](https://github.com/microsoft/monaco-editor?utm_source=gold_browser_extension)

滑动拼图验证

vue-puzzle-verification
vue-puzzle-vcode

值得学习库

weui

额外推荐

turn.js - 做一本书,带漂亮的翻页的效果

HTML5秘籍--文档类型和语义化以及离线缓存

HTML5秘籍书籍中文档类型和语义化, 离线缓存部分进行要点归纳

HTML5文档类型

<!DOCTYPE html>

好处:

  • 简约, 相比XHTML和HTML4说明更加简单
  • 声明的情况下. 进入标准模式, 所有现代浏览器都会以一致的格式和布局来显示网页
  • 没有声明的情况下. ,进入混杂模式, 浏览器将按照自己的标准就进行布局(导致浏览器呈现效果不同)

语义元素

# 新增
<time>, <nav>, <footer>, <header>, <figure>, <figcaption>, <main>, <output>

优点:

  • 容易修改和维护
  • 无障碍性(屏幕阅读器识别)
  • 搜索引擎优化

语义使用

header

只有在内容标题还附带了其他信息的情况下,才有必要考虑<header>。也就是说,其中包含标题、概要、发表日期、作者署名、图片或子主题链接等很多内容

main

<main>包装了<article>
用于标识网页的主要内容。比如,在前面的启示录网站上,主要内容就是整篇文章,不包含站点页眉、页脚和侧边栏。强烈建议大家在自己的网页中使用这个元素。

标题标签

在站点的页眉中使用了<h1>元素后,你就应该给页面中其他的区块(如“Articles”和“About Us”)选用<h2>元素作标题

侧边栏aside

可以用它来标注一段与正文相关的内容(2.2.5节正是这么做的),也可以用它表示页面中一个完全独立的区块——作为页面主要内容的陪衬。

导航栏nav

常只用于页面中最大最主要的导航区
image

使用<output>标注JavaScript返回值
使用<mark>标注突显文本

ie9及以下执行

<! --[if lt IE 9]>
  <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<! [endif]-->

离线缓存

通过描述文件缓存资源

离线应用的一项基本技术就是缓存,即下载文件(如网页)并在用户计算机上保存一份副本。有了这份副本,即使计算机不能上网,浏览器也可以使用缓存的文件。

创建描述文件(PersonalityTest.manifest)

描述文件就是一个文本文件,其中列出了需要缓存的文件。

CACHE MANIFEST
# version 1.00.001 // 下方更新描述文件中有描述
# pages
PersonalityTest.html
PersonalityTest_Score.html


# styles & scripts
PersonalityTest.css
PersonalityTest.js


# pictures & fonts
Images/emotional_bear.jpg
Fonts/museo_slab_500-webfont.eot
Fonts/museo_slab_500-webfont.woff
Fonts/museo_slab_500-webfont.ttf
Fonts/museo_slab_500-webfont.svg
使用描述文件
<! DOCTYPE html>
<html lang="en" manifest="PersonalityTest.manifest">
...
更新描述文件

它缓存了两个页面。如果你更新了PersonalityTest.html,打开浏览器,重新加载这个页面,你看见的仍然是原先缓存的那个页面。无论你的计算机目前能否上网,都是如此。

问题在于,只要浏览器缓存了应用,那么它就不会向Web服务器请求新内容。浏览器不管你是否更新了服务器上的页面,它只管用自己已经缓存的那个。由于离线应用没有过期一说,所以无论你过多长时间以后再看,就算是几个月以后再看,浏览器照旧还会忽略更新后的页面。

最好的办法就是添加注释,比如:

...
# version 1.00.001
...
NETWORK
CACHE MANIFEST
PersonalityTest.html
Images/emotional_bear.jpg

NETWORK:
Images/logo.png

在联网时,浏览器才会尝试从Web服务器下载logo.png文件,而在离线时,则会忽略它。

FALLBACK
CACHE MANIFEST
PersonalityTest_Score.html

FALLBACK:
PersonalityScore.html PersonalityScore_offline.html

浏览器会把后备文件(即这里的PersonalityScore_offline.html)下载并缓存起来。不过,只有在不能上网的时候浏览器才会使用这个后备文件。而在能上网的时候,浏览器会照常向Web服务器请求另一个文件(即这里的PersonalityScore.html)。

SETTINGS: 在线时绕开缓存
SETTINGS:
prefer-online

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.