前端事件流

事件流程如下:

  1. 捕获阶段:事件从根元素开始向触发事件的目标元素进行传递,传递过程中,如果中间有元素注册了事件处理函数,并且 useCapture 参数值为 true ,那么此事件处理函数就会执行,IE9+和其他标准浏览器支持。

  2. 目标阶段:触发目标元素对应事件,并执行注册的事件处理函数。

  3. 冒泡阶段:从目标元素开始向根元素传递,传递过程中,如果中间有元素注册了事件处理函数,且 useCapture 值为 false,此事件处理函数就会执行。

什么是闭包?这就是闭包!

有权访问另一个函数作用域内变量的函数都是闭包。

HTTP缓存机制和原理

  1. 强制缓存

    https://jangdelong.github.io/blog_img/images/1.png

    (1) Expires

    Expires 的值为服务端返回的到期时间,即下一次请求时,请求时间小于服务端返回的到期时间,直接使用缓存数据。

    不过 Expires 是HTTP 1.0的东西,现在默认浏览器均默认使用 HTTP 1.1,所以它的作用基本忽略。

    另一个问题是,到期时间是由服务端生成的,但是客户端时间可能跟服务端时间有误差,这就会导致缓存命中的误差。

    所以HTTP 1.1 的版本,使用Cache-Control替代。

    (2) Cache-Control

    Cache-Control 是最重要的规则。常见的取值有 private、public、no-cache、max-age,no-store,默认为 private。

    • private: 客户端可以缓存
    • public: 客户端和代理服务器都可缓存(前端的同学,可以认为 public 和 private 是一样的)
    • max-age=xxx: 缓存的内容将在 xxx 秒后失效
    • no-cache: 需要使用对比缓存来验证缓存数据(后面介绍)
    • no-store: 所有内容都不会缓存,强制缓存,对比缓存都不会触发(对于前端开发来说,缓存越多越好,so…基本上和它说886)
  2. 对比缓存

    https://jangdelong.github.io/blog_img/images/2.png

    (1) Last-Modified / If-Modified-Since

    • Last-Modified:

      服务器在响应请求时,告诉浏览器资源的最后修改时间。

      https://jangdelong.github.io/blog_img/images/3.png

    • If-Modified-Since:

      再次请求服务器时,通过此字段通知服务器上次请求时,服务器返回的资源最后修改时间。
      服务器收到请求后发现有头 If-Modified-Since 则与被请求资源的最后修改时间进行比对。
      若资源的最后修改时间大于 If-Modified-Since,说明资源又被改动过,则响应整片资源内容,返回状态码 200;

      若资源的最后修改时间小于或等于 If-Modified-Since,说明资源无新修改,则响应HTTP 304,告知浏览器继续使用所保存的cache。

      https://jangdelong.github.io/blog_img/images/4.png

      (2) Etag / If-None-Match (优先级高于Last-Modified / If-Modified-Since)

    • Etag:

      服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。

      https://jangdelong.github.io/blog_img/images/5.png

    • If-None-Match:

      再次请求服务器时,通过此字段通知服务器客户段缓存数据的唯一标识。

      服务器收到请求后发现有头 If-None-Match 则与被请求资源的唯一标识进行比对,
      不同,说明资源又被改动过,则响应整片资源内容,返回状态码 200;

      相同,说明资源无新修改,则响应 HTTP 304,告知浏览器继续使用所保存的 cache。

      https://jangdelong.github.io/blog_img/images/6.png

defer 和 async

1、defer

如果 script 标签设置了该属性,则浏览器会异步的下载该文件并且不会影响到后续 DOM 的渲染;

如果有多个设置了 defer 的 script 标签存在,则会按照顺序执行所有的 script;

defer 脚本会在文档渲染完毕后,DOMContentLoaded 事件调用前执行。

2、async

async 的设置,会使得 script 脚本异步的加载并在允许的情况下执行;
async 的执行,并不会按着 script 在页面中的顺序来执行,而是谁先加载完谁执行。

使用 js 的 FileReader对象实现上传图片时的图片预览功能

废话不多说线上代码

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1, user-scalable=no">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="format-detection" content="telephone=no">
<title>test</title>
<script>
  // 选择图片时预览功能
function imageshow(source) {
var file = source.files[0];
var imageid = source.id;
if (window.FileReader) {
var fr = new FileReader();
fr.onloadend = function (e) {
document.getElementById("portrait"+imageid).src = e.target.result;
};
fr.readAsDataURL(file);
}
    document.getElementById("image"+imageid).style.display="none";
document.getElementById("show"+imageid).style.display="block";
   }
</script>
</head>

<body>
<div>
<div id="image1" >
<p>上传截图</p>
<input type="file" name="screenshot1" id="1" onchange="imageshow(this)"/>
</div>

 <div id="show1" style="display:none;">
  <img src="" id="portrait1" width="100" height="70">
 </div>

 <div id="image2">
<p>上传截图</p>
<input type="file" name="screenshot2" id="2" onchange="imageshow(this)"/>
</div>

 <div id="show2" style="display:none;">
  <img src="" id="portrait2" width="100" height="70">
 </div>

<div id="image3">
<p>上传截图</p>
<input type="file" name="screenshot3" id="3" onchange="imageshow(this)"/>
</div>

<div id="show3" style="display:none;">
  <img src="" id="portrait3" width="100" height="70" >
 </div>
</div>
</body>
</html>

HTTPS 验证原理

https 在真正请求数据前,先会与服务有几次握手验证,以证明相互的身份,以下图为例

https://jangdelong.github.io/blog_img/images/front-end-notes/7.png

性能优化

  • 减少请求数量(sprite、combo)

  • 善用缓存(application cache、http缓存、CDN、localstorage、sessionstorage,备忘录模式)

  • 减少选择器消耗(从右到左),减少DOM操作(DOM和JavaScript解释器的分离)

  • CSS的回流与重绘

    reflow(回流)

    说到页面为什么会慢?那是因为浏览器要花时间、花精力去渲染,尤其是当它发现某个部分发生了点变化影响了布局,需要倒回去重新渲染, 该过程称为reflow(回流)。

    reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。

    repaint(重绘)

    如果只是改变某个元素的背景色、文 字颜色、边框颜色等等不影响它周围或内部布局的属性,将只会引起浏览器 repaint(重绘)。

    repaint 的速度明显快于 reflow(在IE下需要换一下说法,reflow 要比 repaint 更缓慢)。

对称加密及非对称加密

  • 对称加密

发送方和接收方需要持有同一把密钥,发送消息和接收消息均使用该密钥。

相对于非对称加密,对称加密具有更高的加解密速度,但双方都需要事先知道密钥,密钥在传输过程中可能会被窃取,因此安全性没有非对称加密高。

  • 非对称加密算法

接收方在发送消息前需要事先生成公钥和私钥,然后将公钥发送给发送方。发送放收到公钥后,将待发送数据用公钥加密,发送给接收方。接收到收到数据后,用私钥解密。
在这个过程中,公钥负责加密,私钥负责解密,数据在传输过程中即使被截获,攻击者由于没有私钥,因此也无法破解。

非对称加密算法的加解密速度低于对称加密算法,但是安全性更高。

—- 以下更新于 2018-12-11 —-

  • 从属关系区别

@import是 CSS 提供的语法规则,只有导入样式表的作用;link是HTML提供的标签,不仅可以加载 CSS 文件,还可以定义 RSS、rel 连接属性等。

  • 加载顺序区别

加载页面时,link标签引入的 CSS 被同时加载;@import引入的 CSS 将在页面加载完毕后被加载。

  • 兼容性区别

@import是 CSS2.1 才有的语法,故只可在 IE5+ 才能识别;link标签作为 HTML 元素,不存在兼容性问题。

  • DOM可控性区别

可以通过 JS 操作 DOM ,插入link标签来改变样式;由于 DOM 方法是基于文档的,无法使用@import的方式插入样式。

CSS 权重优先级顺序

!important > 行内样式 > ID > 类、伪类、属性 > 标签名 > 继承 > 通配符

观察者模式

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
39
40
41
42
/* Pubsub */
function Pubsub () {
//存放事件和对应的处理方法
this.handles = {};
}
Pubsub.prototype = {
//传入事件类型type和事件处理handle
on: function (type, handle) {
if(!this.handles[type]){
this.handles[type] = [];
}
this.handles[type].push(handle);
},
emit: function () {
// 通过传入参数获取事件类型
var type = Array.prototype.shift.call(arguments);
if (!this.handles[type]) {
return false;
}
for (var i = 0; i < this.handles[type].length; i++) {
var handle = this.handles[type][i];
// 执行事件
handle.apply(this, arguments);
}
},
off: function (type, handle) {
handles = this.handles[type];
if (handles) {
if (!handle) {
handles.length = 0; // 清空数组
} else {
for (var i = 0; i < handles.length; i++) {
var _handle = handles[i];
// 有点问题
if (_handle === handle) {
handles.splice(i,1);
}
}
}
}
}
}

两大数相加

1
2
3
4
5
6
7
8
9
10
11
12
function sumStrings (a, b) {
var res = '',
c = 0;
a = a.split('');
b = b.split('');
while (a.length || b.length || c) {
c += ~~a.pop() + ~~b.pop(); // ~~a字符串转数字
res = c % 10 + res;
c = c > 9; // 进1
}
return res.replace(/^0+/,'');
}

设备像素比

物理像素(physical pixel)

一个物理像素是显示器(手机屏幕)上最小的物理显示单元,在操作系统的调度下,每一个设备像素都有自己的颜色值和亮度值。

设备独立像素(density-independent pixel)

设备独立像素(也叫密度无关像素),可以认为是计算机坐标系统中得一个点,这个点代表一个可以由程序使用的虚拟像素(比如: css像素),然后由相关系统转换为物理像素。 所以说,物理像素和设备独立像素之间存在着一定的对应关系,这就是接下来要说的设备像素比。

设备像素比(device pixel ratio )

设备像素比(简称dpr)定义了物理像素和设备独立像素的对应关系,它的值可以按如下的公式的得到:

1
设备像素比 = 物理像素 / 设备独立像素 // 在某一方向上,x方向或者y方向

—- 以下更新于 2018-12-13 —-

双向绑定原理(简单思路)

  1. 实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

  2. 实现一个订阅者Watcher,每一个Watcher都绑定一个更新函数,watcher可以收到属性的变化通知并执行相应的函数,从而更新视图。

  3. 实现一个解析器Compile,可以扫描和解析每个节点的相关指令(v-model,v-on等指令),如果节点存在v-model,v-on等指令,则解析器Compile初始化这类节点的模板数据,使之可以显示在视图上,然后初始化相应的订阅者(Watcher)。

—- 以下更新于 2019-2-12 —-

面向对象的三个基本特征

  1. 封装
  2. 继承
  3. 多态

—- 以下更新于 2019-2-24 —-

JS继承 · 类

ES5 和 ES6 子类 this 生成顺序不同。ES5 的继承先生成了子类实例,再调用父类的构造函数修饰子类实例,ES6 的继承先生成父类实例,再调用子类的构造函数修饰父类实例。这个差别使得 ES6 可以继承内置对象。

1
2
3
4
5
6
7
8
9
10
11
function MyES5Array() {
Array.call(this, arguments);
}

// it's useless
const arrayES5 = new MyES5Array(3); // arrayES5: MyES5Array {}

class MyES6Array extends Array {}

// it's ok
const arrayES6 = new MyES6Array(3); // arrayES6: MyES6Array(3) []

ES5/ES6 的继承除了写法以外还有什么区别?

来源:Understanding ECMAScript 6

1、class 声明会提升,但不会初始化赋值。Foo 进入暂时性死区,类似于 letconst 声明变量。

1
2
3
4
5
6
7
8
9
10
11
const bar = new Bar(); // it's ok
function Bar() {
this.bar = 42;
}

const foo = new Foo(); // ReferenceError: Foo is not defined
class Foo {
constructor() {
this.foo = 42;
}
}

2、class 声明内部会启用严格模式。

1
2
3
4
5
6
7
8
9
10
11
12
// 引用一个未声明的变量
function Bar() {
baz = 42; // it's ok
}
const bar = new Bar();

class Foo {
constructor() {
fol = 42; // ReferenceError: fol is not defined
}
}
const foo = new Foo();

3、class 的所有方法(包括静态方法和实例方法)都是不可枚举的。

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
// 引用一个未声明的变量
function Bar() {
this.bar = 42;
}
Bar.answer = function() {
return 42;
};
Bar.prototype.print = function() {
console.log(this.bar);
};
const barKeys = Object.keys(Bar); // ['answer']
const barProtoKeys = Object.keys(Bar.prototype); // ['print']

class Foo {
constructor() {
this.foo = 42;
}
static answer() {
return 42;
}
print() {
console.log(this.foo);
}
}
const fooKeys = Object.keys(Foo); // []
const fooProtoKeys = Object.keys(Foo.prototype); // []

4、class 的所有方法(包括静态方法和实例方法)都没有原型对象 prototype,所以也没有[[construct]],不能使用 new 来调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Bar() {
this.bar = 42;
}
Bar.prototype.print = function() {
console.log(this.bar);
};

const bar = new Bar();
const barPrint = new bar.print(); // it's ok

class Foo {
constructor() {
this.foo = 42;
}
print() {
console.log(this.foo);
}
}
const foo = new Foo();
const fooPrint = new foo.print(); // TypeError: foo.print is not a constructor

5、必须使用 new 调用 class

1
2
3
4
5
6
7
8
9
10
11
function Bar() {
this.bar = 42;
}
const bar = Bar(); // it's ok

class Foo {
constructor() {
this.foo = 42;
}
}
const foo = Foo(); // TypeError: Class constructor Foo cannot be invoked without 'new'

6、class 内部无法重写类名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Bar() {
Bar = 'Baz'; // it's ok
this.bar = 42;
}
const bar = new Bar();
// Bar: 'Baz'
// bar: Bar {bar: 42}

class Foo {
constructor() {
this.foo = 42;
Foo = 'Fol'; // TypeError: Assignment to constant variable
}
}
const foo = new Foo();
Foo = 'Fol'; // it's ok

Vue 组件的 data 必须是一个函数

一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。

1
2
3
4
5
data: function () {
return {
count: 0
}
}

如果 Vue 没有这条规则,点击某个按钮组件就可能影响到其它实例。

防抖及节流

  1. 防抖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * 防抖函数
    * TODO: 防止多次提交按钮,只执行最后提交的一次
    * 原理: 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
    * 适用场景: 按钮多次点击等
    */
    export const debounce = (fn, delay = 500) => {
    let timer = null
    return function (...args) {
    clearTimeout(timer)
    timer = setTimeout(() => {
    fn.apply(this, args)
    }, delay)
    }
    }
  2. 节流

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * 节流函数
    * TODO: 固定时间内只执行一次,防止超高频次触发位置变动
    * 原理: 规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
    * 适用场景: 滚动事件等
    */
    export const throttle = (fn, delay = 500) => {
    let flag = true
    return function (...args) {
    if (!flag) return
    flag = false
    setTimeout(() => {
    fn.apply(this, args)
    flag = true
    }, delay)
    }
    }

    更多内容,请移步↓

    https://github.com/front-end-pigs/blog/issues/4