关于Babel的一二三事

一、why babel ?

babel:JavaScript Compiler 将ES5+的代码转换为浏览器可执行的代码,主要做了以下事情:

  • 语法转换
  • 提供新语法特性的修补,比如promise、iterators、symbols等
  • 源码转换

babel是一个工具链,由很多插件组成,以下是主要用到的几个插件:

1、@babel/core

Babel的核心功能模块

2、@babel/polyfill
3、@babel/preset-env
4、@babel/cli

二、插件和预设

插件 plugins

插件决定了babel转换代码的规则

i.e:

@babel/plugin-transform-arrow-functions 将箭头函数转换为普通函数

@babel/plugin-proposal-class-properties 转换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
class SafeUser {
password = generateStrongPassword() //初始化密码
speak = () => {
console.log('speaking')
}
}

// 经@babel/core、@babel/plugin-transform-arrow-functions、@babel/plugin-proposal-class-properties转换后
function _defineProperty(obj, key, value) {
if (key in obj) { // ? 为什么需要做这个判断 ? Object.defineProperty(属性定义)与属性赋值的差异?
Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });
} else {
obj[key] = value;
} return obj;
}
class SafeUser {
constructor() {
_defineProperty(this, "password", generateStrongPassword());

_defineProperty(this, "speak", function () {
console.log('speaking');
});
}
}
预设 presets

简单理解:一个preset即为一组预先设定的插件

i.e: @babel/preset-env 包含的插件支持所有最新的JavaScript特性(ES2015,ES2016等)

1
2
3
4
5
6
7
8
9
10
11
const presets = [
[
"@babel/preset-env", // 名为env的preset只会为目标浏览器中没有的功能加载转换插件
{
targets: {
edge: "17",
chrome: "67"
}
}
]
]

三、Polyfill

virtualDOM

React中常见名词解释:

  • Reconciliation 通过ReactDOM等类库与 “真实DOM” 同步

virtualDOM是一种编程概念,UI以一种理想化的,虚拟的表现形式存在与内存中,通过virtualDOM这种方式,产生了声明式API的编程风格,相较于jquery,这种声明式的API帮助我们从以往的属性操作、事件处理和手动更新DOM这些必要的操作中解放出来。

所以可以从以下几个点入手看看ReactDOM都做了哪些事情

1、事件处理

提到virtualDOM,就不得不说以下React16中新加入的Fiber协调引擎,以及React fiber这种新的数据结构,他们的主要目的是使VirtualDOM可以进行增量式渲染。想了解更多的可以看这篇 https://github.com/acdlite/react-fiber-architecture

源码分析:

ReactDOM.render( childNode, domContainer )

react16特性

React16是一个重大更新,将React管理Reconciliation的算法全部重构了( group-up rewrite for how React manages reconciliation)。在兼容之前版本的主要API的情况下,还增加了以下新特性:

  • 具备将代码块中可中断的任务分割的能力
  • 能够将进程中的任务划分优先级( prioritize )执行
  • 异步渲染,利用React Fiber,这种新的数据结构(fiber reconciliation),原来是一种堆栈结构(stack reconciliation)
  • 完善了错误处理机制,不会因为一个子组件的崩溃导致整个应用崩溃。componentDidCatch

Reconciliation: 通过ReactDOM等类库 与 真实DOM 同步

http-proxy杂记

changeOrigin

用于在

互联网广告初识

一些专业名词:

ssp(Sell-side Platform): 所谓Sell-side就是卖方,卖方平台,即卖广告位的
dsp(demand-side Platform): 需求方,需要打广告的
Ad Exchange(adx):

Node.js服务模型

关于服务模型的进化史:

一、石器时代:同步

一次只为一个请求服务,􏶣􏲋􏵃假设每次􏶤应服务􏶥用的时􏲈􏵿定为N􏶦,这􏵛服务的QPS为1/N。

二、青铜时代:复制进程

100个请求就要复制100个进程,进程复制和预复制非常消耗内存。

三、白银时代:多线程 (Apache)

多线程上下文切换开销大。高并发下无法支撑 -> C10k问题。

四、黄金时代:事件驱动(Node与Nginx)

单线程的问题是:1、CPU的利用率 -> child_process -> cluster 2、进程的健壮性

影响􏲡􏶤事件驱动服务模型性能的点在于CPU的计算能力,可伸缩性较高。

Google关于TypeScript3.5版本的反馈[译]

故事是从Google团队升级了TypeScript的版本至3.5后开始的:

1、泛型的隐式默认值(Implicit default for generics)

这是3.5版本升级后破坏性的主要变化

主要异常表现:代码中使用到泛型,但是代码并不关注泛型做了什么。

下面是一个🌰

1
2
3
4
5
6
// i.e 下面的Promise中resolve返回一个值,但是Promise本身并不关注这个值是什么
function dontCarePromise() {
return new Promise((resolve)=>{
resolve()
})
}

在3.4版本及以下,泛型是宽松的,这里的Promise实际是Promise<{}>,然而在3.5中被解释成Promise

那么问题来了,如果有个人在自己的项目中大量写了下面的代码:

1
2
3
4
const myPromise: Promise<{}> = dontCarePromise()

∨∨∨∨∨∨
const myPromise: Promise<unknown> = dontCarePromise() // 在3.5版本中

就造成了对使用者没有任何益处的类型报错,此次版本升级中关于泛型的改变所带来的缺失就和上述现象相似。

比如d3的类型声明库中有一个非常复杂的类型d3.Selection<>,使用时需要传入4个泛型参数来明确这个泛型类型,i.e

1
mySel: d3.Selection<HTMLElement, {}, null, undefined>

然而在3.5版本中,第二个参数类型{}会变成unknown

主要的结论是d3的类型声明是不那么完美的,使用时需要加以注意,不过这是和此次升级无关的。

还有一种异常表现是在只将泛型作为返回值的依赖注入模式中,来看一下下面的代码:

1
function getService<T>(service: Ctor<T>): T

当Ctor是某种匹配Class类型时,主要使用场景是以下代码:

1
2
class MyService {...}
const myService = getService(MyService);

React中的竞态

TS-Module-Resolution

TS Module Resolution
  • Classic [default]

    前提:模块引入语句在文件( /root/src/folder/A.ts )中

​ 1、以相对路径的方式引入, import { b } from ‘./moduleB’ ,ts就会按照以下去查找b变量:

​ a. /root/src/folder/moduleB.ts

​ b. /root/src/folder/moduleB.d.ts

​ 2、非相对路径的方式引入,即直接引入模块名( import { b } from ‘moduleB’ ),则模块解析路径为:

​ a./root/src/folder/moduleB.ts

​ b. /root/src/folder/moduleB.d.ts

​ c./root/src/moduleB.ts

​ d… 即一层层向上找,直到根目录

  • Node (TS中Node模式的模块解析方式相较与于Node.js来说是简化了的)

    前提:模块引入语句在文件( /root/src/moduleA.ts )中

​ 1、相对路径方式的引入:var x = require(“./moduleB”),Node解析路径只会找这个相对路径下符合的模块,不会向上递归查找。

​ a. /root/src/moduleB.js 如果这个路径下的moduleB文件存在。

​ b. /root/src/moduleB 查找moduleB这个文件夹下是否存在package.json,并且package.json中包含{“main”: “lib/mainModule.js”},如果存在,Node.js就会从/root/src/moduleB/lib/mainModule.js中解析。

​ 2、非相对路径方式的模块名引入:var x = require(“moduleB”) ,Node会向上递归解析路径,直到找到符合的模块。

​ a. /root/src/node_modules/moduleB.js

​ b. /root/src/node_modules/moduleB/package.json (if it specifies a “main” property)

​ c. /root/src/node_modules/moduleB/index.js

​ d. /root/node_modules/moduleB.js

​ e. /root/node_modules/moduleB/package.json (if it specifies a “main” property)

​ f. /root/node_modules/moduleB/index.js

​ g. /node_modules/moduleB.js

​ h. /node_modules/moduleB/package.json (if it specifies a “main” property)

​ I. /node_modules/moduleB/index.js

TS中与模块解析相关的配置:

一、baseUrl
1、在命令行中配置,可参考http://www.typescriptlang.org/docs/handbook/module-resolution.html#classic
2、在tsconfig.json中配置baseUrl

为什么需要baseUrl ?

某些模块名与模块文件名不一致,baseUrl可以告诉TS Compiler去哪里寻找这些模块。

1
2
3
4
5
6
7
8
{
"compilerOptions":{
"baseUrl": ".", // 如果写了paths配置项,那么baseUrl是必填项
"paths": {
"jquery": ["node_modules/jquery/dist/jquery"] // 相对路径 相对于baseUrl 拼接起来就是./node_modules/jquery/dist/jquery
}
}
}
1
2
3
4
5
6
7
8
9
projectRoot
├── folder1
│ ├── file1.ts (imports 'folder1/file2' and 'folder2/file3')
│ └── file2.ts
├── generated
│ ├── folder1
│ └── folder2
│ └── file3.ts
└── tsconfig.json
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": [
"*", // 1
"generated/*" // 2
]
}
}
}
1、"*": 模块名即路径名 <moduleName> => <baseUrl>/<moduleName>
2、"generated/*": <moduleName> => <baseUrl>/<generated>/<moduleName>
二、rootDirs

RxJS

Key Words: 响应式编程函数式编程、相当于事件处理的lodash库

Why Rx: 处理同步和异步的事件流、复杂问题简单化、数据流的概念

How: 组合不同的操作符,观察者模式迭代器模式的组合observable

前言:数据生产者和数据消费者之间有俩种模式进行数据的交流:

1、Pull 拉取,取决于数据消费者何时去接收数据,数据生产者对数据何时被消费者接收是无意识的。JavaScript中的函数就是y这种拉取的系统,函数本身作为数据生产者,代码通过”拉取”的方式拿到该函数被调用执行后的一个返回值。ES7中的generator生成器函数也是这种模式,调用iterator.next()的代码作为数据消费者,iterator作为数据生产者。

2、Push推送,数据生产者决定何时将数据发送给数据消费者,后者对数据接收这个动作是无意识的。例如ES6中的Promise(数据生产者)。RxJS中的Observable也采用了Push模式来推送数据

What: 主要理解 Observable && Operators

Observable: 可观察的数据流,可以理解为是数据生产者(Data Producer)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1、通过Observable构造函数新建
// Data Producer 数据生产者,只关注如何产出数据
import { observable } from 'rxjs'
const observable = new Observable((subscriber)=>{
try{
subscriber.next('step1')
subscriber.next('step2')
subscriber.next('step3')
// only execute once
subscriber.complete('step complete')
}catch(e){
// only execute once
subscriber.error('step error')
}
})
// Data Consumer 数据消费者,决定如何消费数据,即如果处理数据
const theObserver = (value) => console.log(value)
// 数据生产者 与 数据消费者 俩者是独立的,只有通过以下方式建立关系,数据消费者才能拿到数据生产者的数据。
observable.subscribe(theObserver)
// 控制台按顺序输出:step1 step2 step3 step complete
1
2
3
4
// 2、通过creator operator操作符生成的observable 
import { of } from 'rxjs'
of(1,2,3).subscribe(value => console.log(value))
// 控制台顺序输出:1,2,3

operators: 本质上是函数,主要分为俩类: pipe operators && creation operators

1、pipe operators

可以理解为是一个管道,将流进管道的数据流做处理后再输出新的处理完后的数据流,并且不会改变原数据流,与函数式编程中的Pure Function类似,(如果订阅了输出后的数据流,则对同时订阅输入的数据流Subscribing to the output Observable will also subscribe to the input Observable)?。

1
2
3
4
5
6
7
8
9
import { of } from 'rxjs';
import { map } from 'rxjs/operators';

map(x => x * x)(of(1, 2, 3)).subscribe((v) => console.log(`value: ${v}`));

// Logs:
// value: 1
// value: 4
// value: 9

2、creation operators

React-Diff

建立在以下三个方面的前提成立的React Diff优化策略:

1、跨层级的DOM移动操作较少,可以忽略不计。

2、同一层级的一组子节点可以通过唯一id区分。

3、拥有相同类的两个组件将会生成相似的树结构,拥有不同类的俩个组件则生成不同的树结构。

优化策略分为三部分:

1、Tree Diff 分层求异

只比较同一层级的节点,这样只需要对树进行一次遍历。因此尽量保持稳定的DOM结构,通过隐藏显示的方式,而不是删除和新建节点。

2、Component Diff

如果是同一类型的组件,按照原策略进行Virtual Dom tree Diff,允许用户直接通过shouldComponent来判断是否需要Diff;如果不是同一类型的组件,则直接标记为dirtyComponent,从而替换整个组件下的子节点。

3、Element Diff 设置唯一 key

通过key来判断当前节点是否可复用;尽量减少类似将最后一个节点移动到列表首部的操作

redux异步处理中间件

redux中的reducer是纯函数,即不会产生副作用。所以需要中间件来处理一些有副作用的事件,比如某些ajax请求。

比较具有代表性的俩个中间件:redux-thunk 与 redux-saga,前者主要是扩展了action,使得action从一个对象变成了一个函数,而后者则是将异步任务进行了统一处理。

1
2
3
4
5
6
7
8
// redux-thunk
dispatch((dispatch)=>{
dispatch({type:"FEICH_USERS_START"})
// do something async
axios.get('api/get/users')
.then(()=>{ dispatch({type:"FEICH_USERS_SUCCESS",payload:response.data})} )
.catch(()=>{ dispatch({type:"FEICH_USERS_FAILED",payload:response.data})} )
})

thunk: 无需传入参数,一切都准备就绪, 只需调用就会返回某些值的函数。

1
// redux-sagas

一个sagas包含三个部分,用以执行联合任务:

1、worker saga // 主要调用者,请求接口,返回数据等。

2、watcher saga

3、root saga // 调用sagas的唯一入口

flex布局

flex是一维的布局,一次只能处理一个维度上的元素布局,而Grid布局则是二维的。flex布局有一条主轴和一条交叉轴,交叉轴永远与主轴垂直,flexbox的特性是沿着主轴或者交叉轴对齐其中的元素。

如果将一个容器的display属性设置为flex或者inline-flex,则这个容器的所有直系子元素都会变成flex元素,都将有以下行为:

1、元素排列为一行(flex-direction初始值为row)

2、元素从主轴的起始线开始

3、元素不会在主维度上拉伸,但是可以缩小。

4、元素被拉伸来填充交叉轴大小。

5、flex-basis属性为auto

6、flex-wrap为nowrap

Decorator修饰器

Decorator

配置: decorator是ES7中的提案,所以需要babel转译后才能被现代浏览器解析。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// .babelrc 
/*
* @babel/preset-env 可以理解为预设的浏览器环境,可以在代码中使用最新的JavaScript代码,而不用针对不同兼 容性的代码去做不同的转换。 presets数组的解析顺序是数组的逆序,在此例中先使用@babel/preset-react,再使用@babel/preset-env,而plugins的解析顺序则与presets解析顺序相反,是数组的正序。
* 注意:@babel/plugin-proposal-decorators一定要写在@babel/plugin-proposal-class-properties之前
*/
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [
["import", {
"libraryName": "antd",
// "libraryDirectory": "es",
"style": "css"
}],
["@babel/plugin-proposal-decorators", {
// "decoratorsBeforeExport": false, // 不能和proposal-class-properties一起使用
"legacy": true // 使用es-2015 stage-1 阶段
}],
["@babel/plugin-proposal-class-properties", {
"loose": true
}],
]
}

Decorator语法:

1、修饰类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@fly         
class Person {}

/*
* 由于此时还没有生成类的实例,所以这里的target为Person类
*
*/
function fly(target){
target.canFly = true
return target
}

Person.canFly // true
const p1 = new Person()
p1.canFly // undefined,如果想要类的实例也有这个属性,则可以在fly函数中: target.prototype.canFly = true

2、修饰类的方法

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
// App.js
class App extends Component {
componentDidMount() {
window.addEventListener("scroll", this.scroll, false);
}

@throttle
scroll() {
console.log("scroll");
}

render(){
return <div/>
}
}

// util.js
/*
* 函数节流,
*/
export function throttle(target, name, descriptor) {
const initialValue = descriptor.value;
const interval = 1000;
let timer = undefined;
descriptor.value = function(...args) {
if (timer) return;
timer = setTimeout(() => {
clearTimeout(timer);
timer = undefined;
}, interval);
initialValue.call(this, args); // 执行原函数
};
return descriptor;
}

https://juejin.im/post/5cbeadb95188250aa52c7b1b

ReactFiber初识

React Fiber

主要目标:

1、能够把可中断的任务做切片处理。

2、能够调整优先级重置复用任务。

用于解决stack reconcile中的固有问题,将React中同步的计算过程做拆分,使主线程不会一直处于占用的状态,因而其他IO操作,比如动画、手势等得以执行。

why: 长时间运行的JS会影响动画、布局和手势,原因是JS引擎线程与GUI页面渲染线程是互斥的,所以长时间执行JS可能会造成页面假死、掉帧的现象。

历史原因: 在React15版本中,用到的核心算法是Reconcile,理解为stack Reconcile, 采用自顶向下递归,从根组件或者触发setState的子组件向下递归,更新过程中主要涉及俩步:1、Diff,比较prevInstance和nextInstance,找出差异及VDOM Change,本质上是一些计算(遍历、比较),在这一过程中是无法执行IO操作的;2、Patch,将Diff出来的差异队列更新到DOM节点上。Patch是批量执行的,并不是有差异就会执行。

浏览器中的渲染进程是多线程的,分为GUI渲染线程、JS引擎线程、事件触发线程、定时触发器线程、异步http请求线程。GUI渲染线程与JS引擎线程互斥

Promise实现

基于PromiseA+规范实现简易版的Promise

Promise: 一个具有then方法的对象或函数。

thenable: 一个定义了then方法的对象或函数。

JavaScript中的module

1、CommonJS:运行时确定模块间的依赖关系

用于服务器,对象就是模块,输出的是值的缓存,不存在动态更新。

2、AMD:运行时确定模块间的依赖关系

用于浏览器中。

3、ES6的模块加载方案:设计思想是尽量静态化,使得编译时就能确定模块间的依赖关系,静态优化, 静态分析(例如宏和类型检验只能靠静态分析实现)

浏览器通用解决方案,模块不是对象。

export: 输出的是接口,不是值。

1
2
3
4
5
6
7
8
export 1;  // error
const a = 1;
export a; // error
export const a = 1; // ok

if(...){
export const a = 1; // error 块级作用域内无法做静态优化
}

import: 输入的是只读接口,编译阶段执行,在代码运行之前,所以不允许使用表达式和变量。Singleton 模式,多次加载只会执行一次import语句。

1
2
console.log(a)
import {a} from 'util'

export default: 本质上是输出了一个名字叫default的变量

export 与 import 复合写法

1
export { a, b } from 'util'  // 相当于对外转发了a,b这俩个接口,在当前模块中并没有导入这俩个变量。

UMD模块格式:

静态编译:

process模块

process.argv

@return <string[]>

启动Node进程时传入的命令行参数,返回一个数组

第一个元素是process.execPath,即启动进程的可执行文件的绝对路径 

第二个元素是正在执行的JavaScript文件的路径
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
!#/usr/bin/env node

process.argv.forEach((arg,index) => {
console.log('index:',index,';arg:',arg)
})

input:
$ node process.js one {two:true} three

output:
index: 0; arg: /Users/joeng/.nvm/versions/node/v10.12.0/bin/node
index: 1; arg: /Users/joeng/Training/processor.js
index: 2; arg: one
index: 3; arg: {two:true}
index: 4; arg: three

npm package: yargs 可解析参数
https://github.com/yargs/yargs/blob/master/docs/api.md


some_thing_about_react

一、React Components & React Elements & Instance

components: 输入props,输出element Tree. 单一数据流。
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
// 1、作为functions定义
const Button = ({children, color}) => ({
type: 'button',
props: {
className: 'button button-'+color,
children: {
type: 'b',
props: {
children: children
}
}
}
})

// 2、使用React.createClass()
const Button = React.createClass({
render(){
const { children, color } = this.props
return {
type: 'button',
props: {
className: 'button button-' + color,
children: {
type: 'b',
props: {
children:children
}
}
}
}
}
})

//3、使用ES6的class继承自React.Component
class Button extends React.Component{
render(){
const { children, color } = this.props
return {
type: 'button',
props: {
className: 'button button-' + color
children: {
type: 'b',
props: {
children: children
}
}
}
}
}
}
elements: 一个不可变的描述对象,告诉react要绘制什么样的视图。element本质上只是普通对象,描述了component instance 和 DOM Node,包括这俩者想要得到的属性。
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
{
type: (string | ReactClass),
props: Object
$$typeof: Symbol.for('react.element')
}

i.e.
{
type: 'button',
props: {
className: 'button button-blue',
children: {
type: 'b',
props: {
children: 'ok'
}
}
}
}
得到以下结果:
<button className='button button-blue'>
<b>
ok
</b>
</button>

// 1、通过React.createElement创建React element
React.createElement(
type,
[props],
[...children]
)

// 2、JSX
const name = 'joeng'
const Ele = <div>hello,{name}</div>


// 校验是否是React Element
React.isValidElement(object)
instances: 只有用class声明的React Components才有instance,instance是React创建和管理的,比起components和elements来说,instance显得不是那么重要。

HTTP缓存的使用

当本地没有文件时,浏览器请求相应服务器获取到文件,并将文件存放到浏览器的缓存目录下,当第二次访问该文件时,浏览器会向服务端发起一个条件请求,询问当前缓存中的文件是否可用。这里的条件请求,指的就是GET请求的请求首部字段中带有“if-Modified-Since”字段。这个字段只在Get请求与HEAD请求中有效。

if-Modified-Since : GMT格式的时间字符串,服务端将该字段与文件最后修改时间的时间戳进行对比,若文件有更新,则返回最新的文件资源,浏览器放弃缓存资源并更新最新的文件;若文件没有更新,服务器端返回”304”状态码,表示浏览器端缓存文件可用。

缺点:

  • 由于是GMT格式的时间字符串,有可能在文件没有改动的情况下,时间戳有更新,造成缓存失效。
  • 只能精确到秒级别,对于一些更新频繁的文件资源,将无法生效。

于是HTTP1.1中引入ETag解决这个问题。

ETag: Entity Tag ,实体标签,类似于”指纹”,服务端在客户端请求资源时,返回对应的资源与ETag值(通常为资源的散列值),客户端在下次请求该资源时,会在请求中带上”ETag”与”If-None-Match”字段,服务端将该字段的值与现有文件的ETag值做对比,若有更新,则返回最新资源;若没有更新,则返回304状态码。

虽然问题解决了,但是在判断缓存是否可用时,客户端依然要发起一次请求询问服务端当前缓存是否可用,那么能不能在不发起请求的情况下进行判断呢?Expires字段由此而生。

Expires:GMT格式的时间字符串,,即在此之后,该请求失效。

缺点:由于是GMT格式时间字符串,有可能因为客户端与服务端采用了不同的时间而导致缓存无法生效。

这种情况下,Cache-Control字段在保持相同功能下,提供了更细密度的灵活控制。

使缓存失效

由于浏览器是按照URL进行缓存的,所以使缓存主动失效的方法是使用不同的URL请求,如不同的资源对应不同的版本号。

Iterator

Iterator 遍历器

一种接口,为不同的数据结构提供统一的访问机制,任何数据接口只要部署Interator接口,就可以完成遍历操作

遍历:依次处理该数据结构的所有成员

  • 为各种数据结构,提供一个统一的、简便的访问接口

  • 使得数据结构的成员能够按某种次序排列

  • ES6创造了一种新的遍历命令 for…of循环,Interator接口主要供for…of消费

遍历过程如下:

1、创建一个指针对象,指向当前数据结构的起始位置。遍历器对象本质上就是一个指针对象。

2、第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

3、第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

4、不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。一个包含value和done两个属性的对象。

Iterable Iterator IterationResult

一种数据结构只要部署了Interator接口,就称这种数据结构是“可遍历的( Iterable )”

1
2
3
4
5
6
7
8
9
10
11
12
[Symbol.iterator]:function(){
return {
next: functions(){
return {
value: 1,
done: true
}
}
}
}
// iterator接口部署在数据结构的Symbol.iterator属性上
// 属性名 Symbol.iterator是一个表达式(所以要用方括号表示),返回Symbol对象的iterator属性

原生具备 Iterator接口的数据结构:

类型签名

Hindley Milner 类型签名

  • 代码最好的文档
  • 标明输入和输入
  • Compile time checks

HM系统中,用a,b类似这样的来代表类型,a引用代表的一定是同一个类型,a和b一定是不同的类型。

前端缓存

Disk Cache Vs Memory Cache

一、内存和硬盘

内存:计算机运行过程中会把需要的计算数据调到内存中进行运算。主要分为RAM(随机存储器)、ROM(只读存储器)、Cache(高速缓存)。

硬盘:计算机的“外存”,用来存放暂时不用的信息。硬盘上的数据只有被装入内存后才能被处理。

二、Memory Cache : the lookahead downloader

“Have you heard of the preloader? It has sharp teeth all made of regex and it fetches everything it lays its eyes on” —— https://calendar.perfplanet.com/2013/big-bad-preloader/

Memory Cache相当于内存中的缓存,几乎所有的网络请求资源都会被自动加入到其中。但是由于浏览器占用的内存不能无限扩大,所以一个Tab页面关闭后,本次浏览的Memory Cache即失效(为了给后来的Tab腾出位置)。

作用:保证了一个页面中如果有俩个相同的请求都实际只会被请求最多一次,不造成浪费。

几乎所有的网络请求都能进入Memory Cache主要理解为preloaderpreload俩个内容:

1、Preloader: 当页面中有多个Javascript脚本或是其他CSS外部文件等资源时,浏览器会一直等待当前资源解析完才去下载另一个外部资源,在这个解析的过程中network就是闲置的,而简单来说Preloader的作用就是找到当前页面需要加载的外部资源,在解析资源过程中提前加载后续外部资源。

2、Preload: 显示指定的预加载资源。

三、Disk Cache

实际存储于文件系统中的,允许相同的资源在跨会话、跨站点的情况下使用。会根据HTTP头信息中的各类字段来判定哪些资源可以缓存以及缓存时效。当命中缓存后,会从硬盘中读取资源,速度低于内存,但是高于网络请求。

四、Service Worker Application -> Cache Storage

Memory Cache和Disk Cache都是浏览器内部根据我们设置的某些字段来进行判断和进行的,而Service worker给了我们直接操作缓存的能力,当然这个缓存是指 “Application -> Cache Storage”中的缓存区。

record-for-ts

一、基础类型

数组 :

1
2
3
> const list:number[] = [1,2,3]     // 方式一 
> const list:Array<number> = [1,2,3] // 方式二
>

元祖 tuple

1
2
3
4
5
6
> let x: [string, number]
> x = ['hello',2] // ok
> x = [2, 'hello'] //Error
> x[3] = 'world' // ok
> x[4] = true // Error, 布尔不是string
>

枚举 枚举类型是对javascript标准数据结构的一个补充 为一组数值赋予优雅的名字

1
2
3
4
5
6
7
8
9
10
> enum Color {Red, Green, Blue}
> let x: Color = Color.Green // 1 默认情况下从0为元素编号
>
> // 手动进行编号
> enum Color {Red:1, Green:4, Blue:6}
> let x: Color = Color.Green // 4
>
> // 枚举值提供的便利: 可以由枚举的值得到它的名字
> let colorName: string = Color[4] // Green
>

void 空值 函数返回 只能被赋予undefined null

1
2
3
4
> function warnUser(): void{
> alert("This is a warning message!")
> }
>

never 永远不存在

1
2
3
4
5
6
7
8
9
> // 返回never的函数必须存在无法达到的终点
> function error(message: string): never{
> throw new Error(message)
> }
>
> function infiniteLoop(): never{
> while(true){}
> }
>

类型断言 只在编译阶段起作用,假设已经进行了必须的检查

1
2
3
4
5
6
7
8
> // 写法一  "尖括号"写法
> let someValue: any = "this is a string"
> let strLength: number = (<string>someValue).length
>
> // 写法二 "as" 语法 jsx里只允许 as语法
> let someValue: any = "this is a string"
> let strLength: number = (someValue as string).length
>

二、接口 TS核心原则之一 “鸭式辩型法”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
> function printLabel(labelObj:{label: string}){
> console.log('label:', labelObj.label)
> }
>
> let myObj = {size: 10, label: 'Size 10 Object'}
> printLabel(myObj)
>
> // use interface
> interface labelValue {
> label: string;
> name?: string; // 可选属性
> readonly count: number; // 只读属性 只能在创建对象时修改值 作为属性使用时
> }
>
> // ReadonlyArray
> let ro:ReadonlyArray<number> = [1,3,4,5]
>
> function printLabel(labelObj: labelValue){
> console.log('label:',labelObj.label)
> }
>
额外的属性检查: ts中,对象字面量会经过额外属性检查
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
> interface SquareConfig {
> color?: string;
> width?: number;
> }
>
> function createSquare(config: SquareConfig): {color: string; area:number}{
> // ...
> }
> // error: 'colour' not expected in type 'SquareConfig'
> let mySquare = createSquare({colour:'red', width: 100})
>
> // 绕开额外的类型检查 方法一 使用类型断言
> let mySquare = createSquare({colour:'red',width:100} as SquareConfig)
>
> // 最佳方式 添加字符串索引签名
> interface SquareConfig {
> color?: string;
> number?: number;
> [propName:string]: any;
> }
>
函数类型
1
2
3
4
5
6
7
8
9
> interface SearchFunc {
> (source:string, subString: string):boolean
> }
> // 检查对应位置上的参数类型 并不要求参数名字相同
> let mySearch: SearchFunc
> mySearch = function(source:string, subString:string){
> return true
> }
>
可索引的类型
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
> // 具有索引签名 表示当用number去索引StringArray时会得到string类型的返回值
> /*
> * 索引签名: 支持两种类型 string | number
> */
> interface StringArray {
> [index: number]: string;
> }
>
> let myArray: StringArray
> myArray = ['Bob', 'Fred']
>
> let myStr:string = myArray[0]
>
> /*
> * 可以支持同时使用 string | number两种索引类型 但是要求数字索引的返回值必须是字符串返回值类型的子类型
> */
> class Animal {
> name: string;
> }
>
> class Dog extends Animal {
> breed: string;
> }
> // Error!
> interface NotOkay {
> [x: number]: Animal,
> [x: string]: Dog
> }
>
类类型

类具有两个类型: 静态部分的类型和实例的类型

1
2
3
4
5
6
7
8
9
10
11
12
> // 实现接口
> interface ClockInterface {
> currentTime: Date;
> }
>
> class Clock implements ClockInterface {
> currentTime: Date;
> constructor(h:number, m:number){}
> }
>
> // 类静态部分与实例部分的区别
>
继承接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> interface Shape { 
> color: string;
> }
>
> interface PenStoke {
> penWidth: number;
> }
>
> interface Square extends Shape, PenStoke {
> sideLength: number;
> }
>
> let square = <Square>{}
> square.color = 'red';
> square.penWidth = 10;
>
混合类型 一个对象可以同时作为函数和对象使用,并带有额外属性
1
2
3
4
5
6
7
8
9
10
11
12
13
> interface Counter {
> (start: number): string;
> interval: number;
> reset(): void;
> }
>
> function getCounter(): Counter {
> let counter = <Counter>function(start: number) {};
> counter.interval = 123;
> counter.reset = function() {};
> return counter;
> }
>
接口继承类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
> class Control {
> private state: any; // 私有成员
> }
>
> interface SelectableControl extends Control {
> select(): void;
> }
>
> class Button extends Control implements SelectableControl {
> select() {}
> }
>
> class TextBox extends Control {
> select() {}
> }
>
> // Error state missing
> class Image implements SelectableControl {
> select(){}
> }
>

三、类

Proxy

Proxy ( 语言层面做出修改 对编程语言进行编程 meta programming )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 重新定义了obj的get、set方法
const obj = new Proxy(
{},
{
get: function(target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
}
);

obj.count = 1 // setting count!
++ obj.count // getting count! setting count!
1
2
ES6原生提供的Proxy构造函数:
var proxy = new Proxy()

装饰器

装饰器 1.0.0

foreword:

了解以下概念:

  • Object.defineProperty(obj, prop, descriptor)
1、对象属性描述符 只能是以下两种形式之一
  • 数据描述符
  • 存取描述符

可同时具有的键值:

enumerable writable configurable get set value
数据描述符 YES YES YES NO NO YES
存取描述符 YES NO YES YES YES NO

一、针对属性方法的装饰器

  • 装饰器本意是”装饰”类的实例,但此时实例还未生成,所以只能去修饰原型,这一点去类的修饰不同

  • 装饰器会修改属性的描述对象(descriptor) ,然后被修改的描述对象在用来定义属性。

  • 当一个方法有多个装饰器,先从外到内进入,由内向外执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function readonly(target, key , descriptor){
descriptor.writable = false
return descriptor
}

class Person {
constructor(){
this.first = 'Joeng'
this.last = 'll'
}
@readonly
name(){
return `${this.first}+${this.last}`
}
}

二、针对类的装饰器

装饰器对类的行为的改变,发生在代码编译时,而不是运行时

装饰器本身是编译时执行的函数

1
2
3
4
5
6
7
8
9
10
11
12
/*
* @param {object} target 装饰的类 此处为Person
*/
function testable(target){
// 若要改变实例属性 通过prototype对象操作
target.prototype.isTestbale = true
}

@testable
class Person{

}

webpack+react简单使用

webpack 搭建 react项目 ( 简易版)

webpack : 加载器(Loaders)的概念 pipeline

LESS文件先通过less-load处理成css,然后再通过css-loader加载成css模块,最后由style-loader加载器对其做最后的处理,从而运行时可以通过style标签将其应用到最终的浏览器环境。

webpack is a module bundler for modern JavaScript applications. When webpack processes your application, it recursively builds a dependency graph that includes every module your application needs, then packages all of those modules into a small number of bundles - often only one - to be loaded by the browser.

​ — https://webpack.js.org/concepts/

webpack.config.js : 主要有三部分组成 :entry、output、module、

1、entry

The entry point tells webpack where to start and follows the graph of dependencies to know what to bundle.

1
2
3
4
> module.exports = {
> entry: './path/to/my/entry/file.js'
> };
>

2、output

tells webpack how to treat bundled code.

1
2
3
4
5
6
7
8
9
10
11
> var path = require('path')
> module.exports = {
> entry: './path/to/my/entry/file.js'
> output: {
> path: path.resolve(__dirname,'dist'),
> filename: '[name].js'
> chunkFilename:"[id].chunk.js" //affect output files for on-demand-loaded chunks
> publicPath:
> }
> }
>

3、Loaders

  1. Identify which file or files should be transformed by a certain Loader. (test property)

  2. Transform those files so that they can be added to your dependency graph (and eventually your bundle). (use property)

  3. webpack treats every file (.css, .html, .scss, .jpg, etc.) as a module.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    >    var path = require('path')
    > module.exports = {
    > entry: './path/to/my/entry/file.js'
    > output: {
    > path: path.resolve(__dirname,'dist'),
    > filename: 'bundle.js'
    > },
    > module: {
    > rules:[
    > { test: '/\.txt$/', use: 'raw-loader' }
    > ]
    > }
    > }
    >

“Hey webpack compiler, when you come across a path that resolves to a ‘.txt’ file inside of a require()/import statement, use the raw-loader to transform it before you add it to the bundle.”

There are three ways to use loaders in your application:

  • Configuration (recommended): Specify them in your webpack.config.js file.
  • Inline: Specify them explicitly in each import statement.
  • CLI: Specify them within a shell command.

4、Plugins

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
> const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
> const webpack = require('webpack'); //to access built-in plugins
> const path = require('path');
>
> const config = {
> entry: './path/to/my/entry/file.js',
> output: {
> path: path.resolve(__dirname, 'dist'),
> filename: 'my-first-webpack.bundle.js'
> },
> module: {
> rules: [
> { test: /\.txt$/, use: 'raw-loader' }
> ]
> },
> plugins: [
> new webpack.optimize.UglifyJsPlugin(),
> new HtmlWebpackPlugin({template: './src/index.html'})
> ]
> };
>
> module.exports = config;
>

serve the purpose of doing anything else that a loader cannot do.

Function.prototype.apply this method you can pass any function as plugin (this will point to the compiler). You can use this style to inline custom plugins in your configuration.

context : The base directory (absolute path!) for resolving the entryoption. If output.pathinfo is set, the included pathinfo is shortened to this directory. (Default : process.cwd())

Other Features of webpack-dev-server

WDS provides functionality beyond what was covered above. There are a couple of relevant fields that you should be aware of:

  • devServer.contentBase - Assuming you don’t generate index.html dynamically and prefer to maintain it yourself in a specific directory, you need to point WDS to it. contentBaseaccepts either a path (e.g., 'build') or an array of paths (e.g., ['build', 'images']). This defaults to the project root.
  • devServer.proxy - If you are using multiple servers, you have to proxy WDS to them. The proxy setting accepts an object of proxy mappings (e.g., { '/api': 'http://localhost:3000/api' }) that resolve matching queries to another server. Proxy settings are disabled by default.
  • devServer.headers - Attach custom headers to your requests here.

Enode_modules/.bin/eslint —init

Since inlining CSS isn’t a good idea for production usage, it makes sense to use ExtractTextPlugin to generate a separate CSS file. You will do this in the next chapter.

lerna

Lerna

管理多个独立的包结构

常用命令行

1
$ lerna init         #初始化或更新已有版本的lerna repo
1
$ lerna bootstrap    #根据每个包的package.json来安装当前lerna repo下所有包的依赖
1
2
3
4
5
6
7
$ lerna add <package>[@version] [--dev]    
#安装某个依赖,同时会将安装的依赖信息添加到对应的package.json文件中

i.e:
$ lerna add webpack #给当前lerna repo中所有的包安装webpack依赖
$ lerna add webpack --dev #指定依赖安装到对应包的dev dependencies中
$ lerna add webpack --scope=module-1 #这里的scope相当于是作用域,将webpack依赖添加到到module-1的包中
1
$ lerna clean   #移除所有package下的node_modules,相当于移除所有已安装的依赖
1
2
3
4
5
6
7
8
9
10
11
12
$ lerna ls      #列出当前lerna repo下的包

i.e
$ lerna ls --json
返回如下结构:
[
{
"name": "package",
"version": "1.0.0",
"private": false
}
]
1
2
3
4
$ lerna run <script>     #在所有包下执行该npm script脚本(前提是script存在)
$ lerna run <script> -- [...args] #执行npm script脚本时 带对应参数
i.e
$lerna run build -- env=production
1
2
3
$ lerna exec -- <command> [...args]      # runs the command in all packages

# 不局限于 lerna run <script> 的 npm script 命令。

[ ‘@xindaijia/utils’, ‘crm’ ]

函数式攻城指南.md

第一章 函数式JavaScript

3种常见编程范式 + 1

  • 命令式 (C语言) 由一条条的命令组成
  • 面向对象(JAVA) 对象与对象之间通过消息传递信息
  • 函数式 (Clojure) 更像是套用数学公式 扩展
  • 逻辑式 (SQL的query语句) 通过提问找到答案的编程方式

一等公民 : 函数和值有相同的地位,所有值在的地方都可以被函数替代。

JavaScript 的函数式支持

  • 函数作为参数,典型的是 ES6的Array.Map(()=>{})
  • 函数作为返回值,典制:柯里化(将多个参数的函数变为一次只能接受一个参数的函数)、thunk (?)
  • 越来越函数式的ES6
  • ES6的箭头函数
  • Destructure 解构
  • 尾递归优化 ?

JavaScript 与函数式编程的距离

  • Javascript中除了6种原始类型(Boolean,Undefined,Null,Number,String,Symbol)之外的第7种Object类型数据结构是可变的
  • 缺少惰性求值,比如数组中有1000个元素,但是只需要操作前10个元素
  • 缺少对函数组合的支持
  • 缺少尾递归优化

Clojure :运行在JVM上的Lisp方言

ClojureScript: 能够编译成javascript的Clojure


第二章、集合

集合是 函数式编程中 最常用到的数据结构

向量:带有索引的一组数据,向量拥有俩个特性:
  • 不可变性(immutable): 一旦被创建,就不可改变
  • 持久性(persistent):“改变“数据结构的另一种方式,返回一个建立在旧数据上的新数据结构

节流和防抖动

函数的执行通常有三个阶段,分别为:请求执行响应

当某些场景下,比如监听滚动、鼠标滑动轨迹等触发频率比较高的事件时,若处理函数稍为复杂,很容易使响应速度跟不上请求速度,这时就会造成页面卡顿、假死现象,非常不友好。

在无法提升硬件水平以及资源有限的情况下,解决方法是只响应部分请求。

throttle 和 debounce 是解决请求和响应不匹配问题的两个方案,差异在于不同的策略。

电梯超时

​ 将电梯运送一次比做一次函数的执行与响应,有两种运行策略,设定 超时时间为 15秒

  • Throttle 策略:第一个人进电梯后,开始计时,15秒后,准时运行,不等待。没有人,则待机。
  • Debounce策略:第一个人进电梯后,开始计时,若在15秒内再次有人进电梯,重新开始15秒计时,若超出15秒,没有人进入,则电梯运行。

概括来说,节流策略就是在指定时间间隔内只执行任务一次,防抖策略是指在任务频繁触发的情况下,只有任务触发的间隔超过指定间隔时,才会执行。

https://github.com/stephenLYZ/stephenLYZ.github.io/issues/17 示例图比较直观

POST传送数据方式

url的三个js编码函数escape(),encodeURI(),encodeURIComponent()

网页在提交表单的时候,如果有空格,则会被转化为+字符。服务器处理数据的时候,会把+号处理成空格。escape()不对“+”编码。

escape()不能直接用于URL编码,它的真正作用是返回一个字符的Unicode编码值。比如”春节”的返回结果是%u6625%u8282,,escape()不对”+”编码 主要用于汉字编码,现在已经不提倡使用。

encodeURI()是Javascript中真正用来对URL编码的函数。 编码整个url地址,但对特殊含义的符号”; / ? : @ & = + $ , #”,也不进行编码。对应的解码函数是:decodeURI()。

encodeURIComponent() 能编码”; / ? : @ & = + $ , #”这些特殊字符。对应的解码函数是decodeURIComponent()。

假如要传递带&符号的网址,所以用encodeURIComponent()

四种常见的POST提交方式

Content-Type: application/x-www-form-urlencoded;charset=utf-8

消息主体编码方式

1、 application/x-www-form-urlencoded

// 请求头中服务器端解析数据格式 ,浏览器的原生

表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。

2、multipart/form-data

我们使用表单上传文件时,必须让

表单的 enctype 等于 multipart/form-data。

3、application/json

来告诉服务端消息主体是序列化后的 JSON 字符串

4、text/xml

是一种使用 HTTP 作为传输协议,XML 作为编码方式的远程调用规范

浏览器内核浅谈

1、 什么是浏览器内核

浏览器最重要或者说核心的部分是“Rendering Engine”,可大概译为“渲染引擎”,不过我们一般习惯将之称为“浏览器内核”。内核通常指的是排版引擎(与JavaScript引擎不同)。负责对网页语法的解释(如标准通用标记语言下的一个应用HTML、JavaScript)并渲染(显示)网页。 所以,通常所谓的浏览器内核也就是浏览器所采用的渲染引擎,渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。不同的浏览器内核对网页编写语法的解释也有不同,因此同一网页在不同的内核的浏览器里的渲染(显示)效果也可能不同

2、 浏览器内核(排版引擎)的分类

5类浏览器内核:1、Trident(IE内核) 2、Gecko (FireFox内核) 3、Presto(曾是Opera前内核,已废弃) 4、Webkit(苹果内核) 5、Blik

2.1 、Trident(IE内核)

该内核程序在1997年的IE4中首次被采用,是微软在Mosaic代码的基础之上修改而来的,并沿用到IE11,也被普遍称作”IE内核”。Trident实际上是一款开放的内核,其接口内核设计的相当成熟,因此才有许多采用IE内核而非IE的浏览器(壳浏览器)涌现。
Trident内核的常见浏览器有:
IE6、IE7、IE8(Trident 4.0)、IE9(Trident 5.0)、IE10(Trident 6.0)
360安全浏览器(1.0-5.0为Trident,6.0为Trident+Webkit,7.0为Trident+Blink)
360极速浏览器(7.5之前为Trident+Webkit,7.5为Trident+Blink)
搜狗高速浏览器(1.x为Trident,2.0及以后版本为Trident+Webkit)
UC浏览器(Blink内核+Trident内核)
猎豹极轻浏览器,猎豹安全浏览器(1.0-4.2版本为Trident+Webkit,4.3及以后版本为Trident+Blink)
傲游浏览器(傲游1.x、2.x为IE内核,3.x为IE与Webkit双核)
百度浏览器(早期版本)
世界之窗浏览器(最初为IE内核,2013年采用Chrome+IE内核)
2345浏览器
腾讯TT
淘宝浏览器
采编读浏览器、阿云浏览器(早期版本)、瑞星安全浏览器、Slim Browser、 GreenBrowser、爱帆浏览器(12 之前版本)、115浏览器、155浏览器、闪游浏览器、N氧化碳浏览器、糖果浏览器、彩虹浏览器、瑞影浏览器、勇者无疆浏览器、114浏览器、蚂蚁浏览器、飞腾浏览器、速达浏览器、佐罗浏览器、海豚浏览器(iPhone/iPad/Android)等。
注:⚠️ 其中部分浏览器的新版本是“双核”甚至是“多核”,其中一个内核是Trident,然后再增加一个其他内核。国内的厂商一般把其他内核叫做“高速浏览模式”,而Trident则是“兼容浏览模式”,用户可以来回切换。

2.2、 Gecko(FireFox内核)

Gecko(Firefox内核):Netscape6开始采用的内核,后来的Mozilla FireFox(火狐浏览器) 也采用了该内核,Gecko的特点是代码完全公开,因此,其可开发程度很高,全世界的程序员都可以为其编写代码,增加功能。因为这是个开源内核,因此受到许多人的青睐,Gecko内核的浏览器也很多,这也是Gecko内核虽然年轻但市场占有率能够迅速提高的重要原因。
事实上,Gecko引擎的由来跟IE不无关系,前面说过IE没有使用W3C的标准,这导致了微软内部一些开发人员的不满;他们与当时已经停止更新了的 Netscape的一些员工一起创办了Mozilla,以当时的Mosaic内核为基础重新编写内核,于是开发出了Gecko。不过事实上,Gecko 内核的浏览器仍然还是Firefox (火狐) 用户最多,所以有时也会被称为Firefox内核。此外Gecko也是一个跨平台内核,可以在Windows、 BSD、Linux和Mac OS X中使用。

Gecko内核常见的浏览器Mozilla Firefox、Mozilla SeaMonkey、waterfox(Firefox的64位开源版)、Iceweasel、Epiphany(早期版本)、Flock(早期版本)、K-Meleon

2.3 、Presto内核

Presto(Opera前内核) (已废弃): Opera12.17及更早版本曾经采用的内核,现已停止开发并废弃,该内核在2003年的Opera7中首次被使用,该款引擎的特点就是渲染速度的优化达到了极致,然而代价是牺牲了网页的兼容性。

实际上这是一个动态内核,与前面几个内核的最大的区别就在脚本处理上,Presto有着天生的优势,页面的全部或者部分都能够在回应脚本事件时等情况下被重新解析。此外该内核在执行Javascrīpt的时候有着最快的速度,根据在同等条件下的测试,Presto内核执行同等Javascrīpt所需的时间仅有Trident和Gecko内核的约1/3(Trident内核最慢,不过两者相差没有多大),本文的其中一个修改者认为上述测试信息过于老旧且不完整,因为他曾做过的小测试显示Presto部分快部分慢,各内核总体相当。那次测试的时候因为Apple机的硬件条件和普通PC机不同所以没有测试WebCore内核。只可惜Presto是商业引擎,使用Presto的除开Opera以外,只剩下NDSBrowser、Wii Internet Channle、Nokia 770网络浏览器等,这很大程度上限制了Presto的发展。

Opera现已改用Google Chrome的Blink内核。

2.4、 Webkit(Safire内核,Chrome内核原型,开源)

Webkit(Safari内核,Chrome内核原型,开源):它是苹果公司自己的内核,也是苹果的Safari浏览器使用的内核。 Webkit引擎包含WebCore排版引擎JavaScriptCore解析引擎,均是从KDE的KHTML及KJS引擎衍生而来,它们都是自由软件,在GPL条约下授权,同时支持BSD系统的开发。所以Webkit也是自由软件,同时开放源代码。在安全方面不受IE、Firefox的制约,所以Safari浏览器在国内还是很安全的。

限于Mac OS X的使用不广泛和Safari浏览器曾经只是Mac OS X的专属浏览器,这个内核本身应该说市场范围并不大;但似乎根据最新的浏览器调查表明,该浏览器的市场甚至已经超过了Opera的Presto了——当然这一方面得益于苹果转到x86架构之后的人气暴涨,另外也是因为Safari 3终于推出了Windows版的缘故吧。Mac下还有OmniWeb、Shiira等人气很高的浏览器。

Google Chrome、360极速浏览器以及搜狗高速浏览器高速模式也使用Webkit作为内核(在脚本理解方面,Chrome使用自己研发的V8引擎)。WebKit 内核在手机上的应用也十分广泛,例如 Google 的手机 Gphone、 Apple 的iPhone, Nokia’s Series 60 browser 等所使用的 Browser 内核引擎,都是基于 WebKit。

WebKit内核常见的浏览器: 傲游浏览器3、Apple Safari (Win/Mac/iPhone/iPad)、Symbian手机浏览器、Android 默认浏览器

Blink是一个由Google和Opera Software开发的浏览器排版引擎,Google计划将这个渲染引擎作为Chromium计划的一部分,并且在2013年4月的时候公布了这一消息。这一渲染引擎是开源引擎WebKit中WebCore组件的一个分支,并且在Chrome(28及往后版本)、Opera(15及往后版本)和Yandex浏览器中使用。

3、浏览器JavaScript引擎

JavaScript引擎是一个专门处理JavaScript脚本的虚拟机,一般会附带在网页浏览器之中。一个典型的浏览器有一个图形引擎和一个独立的JavaScript引擎。JavaScript引擎能为程序员提供部分操作浏览器的功能(网络、DOM、外部事件、HTML5视频、canvas和存储)。

开发中的引擎

  • Rhino , 开放源代码,完全以Java编写,由Mozilla基金会管理
  • SpiderMonkey ,第一款JavaScript引擎,早期用于Netscape Navigator,现时用于Mozilla Firefox。
  • V8,开放源代码,由Google丹麦开发,是Google Chrome的一部分。
  • JavaScriptCore,开放源代码,用于Safari
  • Chakra(JScript引擎),用于Internet Explorer
  • Chakra(JavaScript引擎),用于Microsoft Edge
  • KJS,KDE的ECMAScript/JavaScript引擎,最初由哈里·波顿开发,用于KDE项目的Konqueror网页浏览器中。

参考:https://zh.wikipedia.org/zh-hans/JavaScript引擎

css布局

CSS相关

前言:学习前端也有些日子了,越来越发现自己忽视了很多基础的部分。《精通CSS》这本书躺在我的书架上大概有一年多了,我却几乎没有精读过,细想原因大概是我太浮躁,急于求成,却不知基础是最好的垫脚石。现在接触得越来越多,越发现自己的认识有很大的错误。所以想回顾和总结一下样式布局方面的一些感悟。也会在之后补充一些在样式布局方面遇到的坑。

1、浏览器对 CSS 的匹配原理

浏览器CSS匹配不是从左到右进行查找,而是从右到左进行查找。比如:
div#divBox p span.red { color:red }
浏览器的查找顺序如下:先查找类名为red的span元素,找到后再查找其父辈元素中是否含有p元素,再判断p的父元素中是否有id为divBox的div元素,如果都存在,则匹配上。

浏览器从右到左查询的好处是尽早过滤掉一些无关的样式规则和元素。Firefox 称这种查找方式为 keyselector(关键字查询),所谓的关键字就是样式规则中最后(最右边)的规则,上面的 key 就是 span.red。
所以为了优化css的加载速度,就要让浏览器在查找 style 匹配的元素的时候尽量进行少的查找。

html5缓存

1、 浏览器缓存机制

浏览器缓存机制是指通过HTTP协议头里的Cache-Control(或Expires)和Last-Modified(或Etag)等字段来控制文件缓存的机制。属于是Web中比较早的缓存机制,是在HTTP协议中实现的。属于协议层实现的缓存。这与Dom Storage、AppCache等缓存机制有些不同,后者为应用层缓存的实现。

Cache-Control

用于控制文件在本地缓存有效时长。最常见的,比如服务器回包:Cache-Control:max-age=600表示文件在本地应该缓存,且有效时长是600秒(从发出请求算起)。在接下来600秒内,如果有请求这个资源,浏览器不会发出HTTP请求,而是直接使用本地缓存的文件。

Cache-Control还有一个同功能的字段:Expires。Expires的值一个绝对的时间点,如:Expires: Thu, 10 Nov 2015 08:45:11 GMT,表示在这个时间点之前,缓存都是有效的。

Expires是HTTP1.0标准中的字段,Cache-Control是HTTP1.1标准中新加的字段,功能一样,都是控制缓存的有效时间。当这两个字段同时出现时,Cache-Control是高优化级的。

注:Cache-Control常与Last-Modified一起使用一个用于控制缓存有效时间,一个在缓存失效后,向服务查询是否有更新。

Last-Modified

Last-Modified是标识文件在服务器上的最新更新时间。下次请求时,如果文件缓存过期,浏览器通过If-Modified-Since字段带上这个时间,发送给服务器,由服务器比较时间戳来判断文件是否有修改。如果没有修改,服务器返回304告诉浏览器继续使用缓存;如果有修改,则返回200,同时返回最新的文件。

ETag也是和Last-Modified一样,对文件进行标识的字段。不同的是,ETag的取值是一个对文件进行标识的特征字串。在向服务器查询文件是否有更新时,浏览器通过If-None-Match字段把特征字串发送给服务器,由服务器和文件最新特征字串进行匹配,来判断文件是否有更新。没有更新回包304,有更新回包200。

ETag和Last-Modified可根据需求使用一个或两个同时使用。两个同时使用时,只要满足基中一个条件,就认为文件没有更新。

⚠️两种特殊情况
  • 手动刷新页面(F5),浏览器会直接认为缓存已经过期(可能缓存还没有过期),在请求中加上字段:Cache-Control:max-age=0,发包向服务器查询是否有文件是否有更新。
  • 强制刷新页面(Ctrl+F5),浏览器会直接忽略本地的缓存(有缓存也会认为本地没有缓存),在请求头中加上字段:Cache-Control:no-cache(或 Pragma:no-cache),发包向服务重新拉取文件。

分析:

Cache-Control和Last-Modified一般用在Web的静态资源文件上,如JS、CSS 和一些图像文件。通过设置资源文件缓存属性,对提高资源文件加载速度,节省流量很有意义,特别是移动网络环境。但问题是:缓存有效时长该如何设置?如果设置太短,就起不到缓存的使用;如果设置的太长,在资源文件有更新时,浏览器如果有缓存,则不能及时取到最新的文件。

所谓的消灭‘304’

Last-Modified需要向服务器发起查询请求,才能知道资源文件有没有更新。虽然服务器可能返回304告诉没有更新,但也还有一个请求的过程。对于移动网络,这个请求可能是比较耗时的。有一种说法叫“消灭304”,指的就是优化掉304的请求。


抓包发现,带if-Modified-Since字段的请求,如果服务器回包304,回包带有Cache-Control:max-age或Expires字段,文件的缓存有效时间会更新,就是文件的缓存会重新有效。304回包后如果再请求,则又直接使用缓存文件了,不再向服务器查询文件是否更新了,除非新的缓存时间再次过期。


Cache-Control与Last-Modified是浏览器内核的机制,一般都是标准的实现,不能更改或设置。以QQ浏览器的X5为例,Cache-Control与Last-Modified缓存不能禁用。缓存容量是12MB,不分Host,过期的缓存会最先被清除。如果都没过期,应该优先清最早的缓存或最快到期的或文件大小最大的;过期缓存也有可能还是有效的,清除缓存会导致资源文件的重新拉取。

⚠️⚠️:浏览器,如X5,在使用缓存文件时,是没有对缓存文件内容进行校验的,这样缓存文件内容被修改的可能。


完美的缓存机制应该是这样的:

1、缓存文件没更新,尽可能使用缓存,不用和服务器交互;
2、 缓存文件有更新时,第一时间能使用到新的文件;
3 、缓存的文件要保持完整性,不使用被修改过的缓存文件;
4、 缓存的容量大小要能设置或控制,缓存文件不能因为存储空间限制或过期被清除。 以X5为例,第1、2条不能同时满足,第3、4条都不能满足。


为了解决Cache-Control缓存时长不好设置的问题,以及为了“消灭304”,Web前端采用的方式是:

1、 在要缓存的资源文件名中加上版本号或文件MD5值字串,如common.d5d02a02.js、common.v1.js,同时设置Cache-Control:max-age=31536000,也就是一年。在一年时间内,资源文件如果本地有缓存,就会使用缓存;也就不会有304的回包。
2、 如果资源文件有修改,则更新文件内容,同时修改资源文件名,如common.v2.js,html页面也会引用新的资源文件名。


总结

1、 首先强缓存:在服务器返回的cache-control中的过期时间范围内,这时请求资源不会向服务器发送HTTP请求。
2、 如果强缓存没有命中,即已经过了缓存时间,这时浏览器会向服务器发送HTTP请求,包括:

  • Date:Fri, 09 Jun 2017 09:15:50 GMT
  • ETag:W/“6f4-15bf577a270” (对该资源文件名的标识)
  • Last-Modified:Thu, 11 May 2017 03:05:10 GMT(上次修改日期)

这时服务器会与浏览器发送的文件名的标识进行对比,验证资源是否被修改,如果没有被修改,则说明没有命中协商缓存,服务器会告诉客户端可以直接从自己的缓存中获取资源,于是浏览器就直接从自己的缓存中加载资源。


2、DOM Storage

DOM存储被设计为用来提供一个更大存储量、更安全、更便捷的存储方法,从而可以代替掉将一些不需要让服务器知道的信息存储到Cookies里的这种传统方法。

Dom Storage是通过存储字符串的Key/Value对来提供的,并提供5MB(不同浏览器可能不同,分Host)的存储空间(Cookies才4KB)。另外Dom Storage存储的数据在本地,不像 Cookies,每次请求一次页面,Cookies 都会发送给服务器。

DOM Storage分为sessionStorage和localStorage。localStorage对象和sessionStorage对象使用方法基本相同,它们的区别在于作用的范围不同。sessionStorage用来存储与页面相关的数据,它在页面关闭后无法使用。而localStorage则持久存在,在页面关闭后也可以使用。

当浏览器被意外刷新的时候,一些临时数据应当被保存和恢复。sessionStorage对象在处理这种情况的时候是最有用的,比如恢复我们在表单中已经填写的数据。

3、IndexDB

待补充..

4、WebSQL

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×