整个组件的生命周期

  • 组件被实例化的时候
  • 组件属性改变的时候
  • 组件状态被改变的时候
  • 组件被销毁的时候

生命周期流程图

componentWillMount

  • 条件:第一次渲染阶段在调用 render 方法前会被调用
  • 作用:该方法在整个组件生命周期只会被调用一次,所以可以利用该方法做一些组件内部的初始化工作

componentDidMount

  • 条件:第一次渲染成功过后,组件对应的 DOM 已经添加到页面后调用
  • 作用:这个阶段表示组件对应的 DOM 已经存在,我们可以在这个时候做一些依赖 DOM 的操作或者其他的一些如请求数据,和第三方库整合的操作。如果嵌套了子组件,子组件会比父组件优先渲染,所以这个时候可以获取子组件对应的 DOM。

componentWillReceiveProps(newProps)

  • 条件: 当组件获取新属性的时候,第一次渲染不会调用
  • 用处: 这个时候可以根据新的属性来修改组件状态
1
2
3
4
5
componentWillReceiveProps: function(nextProps) {
this.setState({
likesIncreasing: nextProps.likeCount > this.props.likeCount
});
}

shouldComponentUpdate(nextProps, nextState)

  • 条件:接收到新属性或者新状态的时候在 render 前会被调用(除了调用 forceUpdate 和初始化渲染以外)
  • 用处:该方法让我们有机会决定是否重渲染组件,如果返回 false,那么不会重渲染组件,借此可以优化应用性能(在组件很多的情况)。

componentWillUpdate(nextProps, nextState)

  • 条件:当组件确定要更新,在 render 之前调用
  • 用处:这个时候可以确定一定会更新组件,可以执行更新前的操作
  • 注意:方法中不能使用 setState ,setState 的操作应该在 componentWillReceiveProps 方法中调用

componentDidUpdate(nextProps, nextState)

  • 条件:更新被应用到 DOM 之后
  • 用处:可以执行组件更新过后的操作

生命周期与单向数据流

  • React 的核心模式是单向数据流,这不仅仅是对于组件级别的模式,在组件内部 的生命周期中也是应该符合单向数据的模式。数据从组件的属性流入,再结合组件的状态,流入生命周期方法,直到渲染结束这都应该是一个单向的过程,其间不能随意改变组件的状态。

详细原文地址:https://zhuanlan.zhihu.com/p/21246418?refer=leanreact

基本共通配置

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
var config = {
entry: [],
output: {
//绝对路径,用于输出到位置,文件输出目录
path: outputPath,
//服务路径,用于热替换服务器,用于配置文件发布路径,如CDN或本地服务器
publicPath: outputPublicPath,
chunkFilename: "[name].chunk.js", //公用文件打包文件
filename: "[name].js" //根据入口文件输出的对应多个文件名
},
resolve: {
extensions: ['', '.js', '.jsx', '.json', '.es6'], // 以这些后缀结尾的文件可忽略后缀
//root: path.join(__dirname, '../app')
//配置别名,在项目中可缩减引用路径
alias: {
//'react': pathToReact
//moment: "moment/min/moment-with-locales.min.js"
}
},
module: {
//各种加载器,即让各种文件格式可用require引用
loaders: [
//使用babel-loader来解析js,es6文件
{
test: /\.(js|es6|jsx)$/,
loader: ['babel-loader'],
//指定作用范围,这里可不写,但是范围越小速度越快
include: path.resolve(dir, needbabelPath),
//排除目录,exclude后将不匹配
exclude: /node_modules/,
query: babelQueryBase
},
//.scss 文件使用 style-loader、css-loader 和 sass-loader 来编译处理
//对于css文件,默认情况下webpack会把css content内嵌到js里边,运行时会使用style标签内联
{ test: /\.scss$/, loader: 'style!css!sass' },
//图片文件使用 url-loader 来处理,小于8kb的直接转为base64
//{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'},
//图片资源在加载时先压缩,然后当内容size小于~10KB时,会自动转成base64的方式内嵌进去
//当图片大于10KB时,则会在img/下生成压缩后的图片,命名是[hash:8].[name].[ext]的形式
//hash:8的意思是取图片内容hushsum值的前8位,这样做能够保证引用的是图片资源的最新修改版本,保证浏览器端能够即时更新
{
test: /\.(jpe?g|png|gif|svg)$/i,
loaders: [
'image?{bypassOnDebug: true, progressive:true, optimizationLevel: 3, pngquant:{quality: "65-80"}}',
'url?limit=10000&name=img/[hash:8].[name].[ext]'
]
},
{
test: /\.(woff|eot|ttf)$/i,
loader: 'url?limit=10000&name=fonts/[hash:8].[name].[ext]'
}
],
noParse: [] //noParse 是 webpack 的另一个很有用的配置项,如果你 确定一个模块中没有其它新的依赖 就可以配置这项,webpack 将不再扫描这个文件中的依赖。
},
plugins: [
new ProgressBarPlugin(),
//预加载的插件
new webpack.PrefetchPlugin("react"),
new webpack.PrefetchPlugin("react/lib/ReactComponentBrowserEnvironment")
]
};
```


几个主要功能说明:

- entry 是页面入口文件配置
- output 是对应输出项配置(即入口文件最终要生成什么名字的文件、存放到哪里)
- plugins 是插件项
- module.loaders 是最关键的一块配置。它告知 webpack 每一种文件都需要使用什么加载器来处理("-loader"其实是可以省略不写的,多个loader之间用“!”连接起来。)

编译命令

$ webpack –config XXX.js //使用另一份配置文件(比如webpack.config2.js)来打包

$ webpack –watch //监听变动并自动打包

$ webpack -p //压缩混淆脚本,这个非常非常重要!

$ webpack -d //生成map映射文件,告知哪些模块被最终打包到哪里了

1
2
3


### dev版本

{
// 生成sourcemap,便于开发调试,正式打包请去掉此行或改成none
devtool: “eval”,// eval生成 sourcemap 的不同方式
//入口文件,需要处理的文件路径
entry: [
‘webpack/hot/dev-server’,
‘webpack-dev-server/client?’+baseUrl,
//上面2个是开发的时候用的热替换服务器
path.resolve(dir, mainFile)
],
module: {
//各种加载器,即让各种文件格式可用require引用
loaders: [
//使用babel-loader来解析js,es6,jsx文件
{
test: /.(js|es6|jsx)$/,
loader: ‘babel’,
//指定作用范围,这里可不写,但是范围越小速度越快
include: path.resolve(dir, needbabelPath),
//排除目录,exclude后将不匹配
exclude: /node_modules/,
query: babelMerge(babelQueryBase, {
“plugins”: [
//这个插件通过任意转换的方式去封装React组件。可以随心所欲的摆弄组件了
[“react-transform”, {
“transforms”: [
{
//一个React转换装置,该装置通过引用Hot Module Replacement API使热重载 React 的类成为可能
“transform”: “react-transform-hmr”,
// 如果你使用React Native,這裡要改用”react-native”
“imports”: [“react”],
“locals”: [“module”]
},
{
//呈现React组件的错误信息
“transform”: “react-transform-catch-errors”,
//捕获异常时用redbox-react来对错误进行更加友好的反馈
“imports”: [“react”, “redbox-react”]
}
]
}]
]
})
}
]
},
plugins: [
//热替换插件
new webpack.HotModuleReplacementPlugin(),
//允许错误不打断程序
new webpack.NoErrorsPlugin()
]
}

1
2
3


### production版本

{
entry: {
“js/app”: path.join(dir, ‘src/js/app.jsx’),
“js/vendor” : vendor // 第三方库包
},
module: {
loaders: [
{
test: /.(js|es6|jsx)$/,
loader: ‘babel’,
exclude: /node_modules/,
query: babelMerge(babelQueryBase)
},
{//单独打包css文件
test: /.scss$/,
loader: ExtractTextPlugin.extract(‘style’, ‘css!autoprefixer!sass’)
}
],
//noParse: [“react”]
},
resolve: {
alias: {//重定向
//react: “react/dist/react.min.js”,
//moment: “moment/min/moment-with-locales.min.js”
}
},
//声明一个外部依赖,该文件不会打包进去,但是要在html页面引入
externals: {
//‘react’: ‘React’
},
autoprefixer: { //浏览器兼容前缀
browsers: [‘last 2 version’, ‘safari 5’, ‘ie 8’, ‘ie 9’, ‘opera 12.1’, ‘ios 6’, ‘android 4’]
},
plugins: [
//js文件的压缩
new webpack.optimize.UglifyJsPlugin({
compressor: {
warnings: false
}
//except: [‘$super’, ‘$’, ‘exports’, ‘require’] //排除关键字
}),
//将公共代码抽离出来合并为一个文件
new webpack.optimize.CommonsChunkPlugin({
name:”js/vendor”,
filename:”js/vendor.bundle.js”,
minChunks:3 //// 提取至少3个模块共有的部分
}),
//单独打包css
new ExtractTextPlugin(“css/styles.css”),
//HtmlWebpackPlugin,模板生成相关的配置,每个对于一个页面的配置,有几个写几个
new HtmlWebpackPlugin(htmlTemplet),
//减小打包文件大小
new webpack.DefinePlugin({
‘process.env.NODE_ENV’: ‘“production”‘
})
]
}
```

详细配置地址:https://github.com/doubleShip/webpack

三个基本原则

  • 整个应用只有唯一一个可信数据源,也就是只有一个 Store
  • State 只能通过触发 Action 来更改
  • State 的更改必须写成纯函数,也就是每次更改总是返回一个新的 State,在 Redux 里这种函数称为 Reducer

Actions

Action 很简单,就是一个单纯的包含 { type, payload } 的对象,type 是一个常量用来标示动作类型,payload 是这个动作携带的数据。Action 需要通过 store.dispatch() 方法来发送。

1
2
3
4
{
type: 'ADD_TODO',
text: 'Build my first Redux app'
}

Reducer

  • Reducer 也是 pure function,这点非常重要,所以绝对不要在 reducer 里面做一些引入 side-effects 的事情,比如:
  • 直接修改 state 参数对象
  • 请求 API
  • 调用不纯的函数,比如 Data.now() Math.random()

Store

现在有了 Action 和 Reducer,Store 的作用就是连接这两者,Store 的作用有这么几个:

  • Hold 住整个应用的 State 状态树
  • 提供一个 getState() 方法获取 State
  • 提供一个 dispatch() 方法发送 action 更改 State
  • 提供一个 subscribe() 方法注册回调函数监听 State 的更改

Data Flow

redux流程的逻辑非常清晰,数据流是单向循环的,就像一个生产的流水线:

1
store(存放状态) -> container(显示状态) -> reducer (处理动作)-> store
  1. 调用 store.dispatch(action)
1
2
3
4
5
Action 是一个包含 { type, payload } 的对象,它描述了“发生了什么”,比如:
{ type: 'LIKE_ARTICLE', articleID: 42 }
{ type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } }
{ type: 'ADD_TODO', text: 'Read the Redux docs.' }
你可以在任何地方调用 store.dispatch(action),比如组件内部,Ajax 回调函数里面等等。
  1. Action 会触发给 Store 指定的 root reducer
1
2
3
root reducer 会返回一个完整的状态树,State 对象上的各个字段值可以由各自的 reducer 函数处理并返回新的值。
reducer 函数接受 (state, action) 两个参数
reducer 函数判断 action.type 然后处理对应的 action.payload 数据来更新并返回一个新的 state
  1. Store 会保存 root reducer 返回的状态树
1
新的 State 会替代旧的 State,然后所有 store.subscribe(listener) 注册的回调函数会被调用,在回调函数里面可以通过 store.getState() 拿到新的 State。

伪类(pseudo class)

  • 伪类是一个抽象类,本质上还是一个类,因此其主要作用仍然是用来选择元素而后设定具体的样式。
  • 伪类,会对现有的元素进行筛选。
  • 伪类总是以一个冒号开头。
  • 伪类选择元素的依据不是名称、属性或内容,而是根据特征(比如状态或顺序)。(:lang除外)
  • 伪类的选择对象可能会随着用户操作文档而发生变化,比如当用户删除某些节点后,会影响子元素(nth-child)伪类的选择。

伪元素(pseudo element)

  • 伪元素本质上是一个元素,只是它一般需要依附在一个已有元素上,作为这个元素的“部分”或“补充”。比如::first-line或::after。
  • 伪元素,会创造出不存在的新元素。
  • 伪元素通常以两个冒号开头。
1
2
3
4
5
「伪类」的作用为「permit selection」,即允许选择一些无法用其他选择器选取的元素,必须对应某个现有的 HTML 元素。而「伪元素」则「create abstractions about the document tree beyond those specified by the document language」,意思是它并不依赖于 HTML 树的结构,即可以创造新的元素。

例如,:first-child是一个伪类,对应作为第一个子元素的元素;:visited作为一个伪类,对应所有已经被访问过的a元素。

::before作为一个伪元素,对应某元素之前一个(实际不存在的)元素。同时::selection作为一个伪元素,它并不代表被选择的元素,而代表被选择的内容(可能是一个元素的一部分)。其他伪元素的例子还有::first-line,对应第一行,等等。

浏览器兼容性

  • 一些老旧的浏览器不支持双冒号的写法,因此如果必须兼容旧浏览器,则应该使用单冒号写法。IE 从 9 开始支持双冒号写法。
  • 在 CSS2 时代,伪元素和伪类均是以一个冒号开头的;在 CSS2.1 之后,为了对伪元素和伪类加以区分,规定伪类继续以一个冒号开头,而伪元素改为以两个冒号开头。但是为了向前兼容,浏览器同样接受 CSS2 时代已经存在的伪元素(它们包括:before, :after, :first-line, :first-letter)的单冒号写法。但是对于 CSS2 之后所有新增的伪元素(例如::selection),必须采用双冒号写法。

this作用域说明

  • this被赋予function所属的Object,具体来说,当function是定义在global对中时,this指向global;当function作为Object的方法时,this指向该Object:
1
2
3
4
5
6
var object = {
foo: function(){
alert(this === object);
}
};
object.foo(); // true

Call和Apply说明

  • call 函数需要参数列表而 apply 函数允许你传递参数为数组
1
2
3
4
5
6
function user(first, last, age){
// do something
}
user.call(window, 'John', 'Doe', 30);
user.apply(window, ['John', 'Doe', 30]);
//执行的结果是相同的,user 函数在window上下文上被调用,并提供了相同的三个参数。

Execution context

  • 执行上下文(简称上下文)决定了Js执行过程中可以获取哪些变量、函数、数据,一段程序可能被分割成许多不同的上下文,每一个上下文都会绑定一个变量对象(variable object),它就像一个容器,用来存储当前上下文中所有已定义或可获取的变量、函数等。位于最顶端或最外层的上下文称为全局上下文(global context),全局上下文取决于执行环境,如Node中的global和Browser中的window
  • 上下文与作用域(scope)是不同的概念。Js本身是单线程的,每当有function被执行时,就会产生一个新的上下文,这一上下文会被压入Js的上下文堆栈(context stack)中,function执行结束后则被弹出,因此Js解释器总是在栈顶上下文中执行。在生成新的上下文时,首先会绑定该上下文的变量对象,其中包括arguments和该函数中定义的变量;之后会创建属于该上下文的作用域链(scope chain),最后将this赋予这一function所属的Object

Scope chain

  • 在function被执行时生成新的上下文时会先绑定当前上下文的变量对象,再创建作用域链。我们知道function的定义是可以嵌套在其他function所创建的上下文中,也可以并列地定义在同一个上下文中(如global)。作用域链实际上就是自下而上地将所有嵌套定义的上下文所绑定的变量对象串接到一起,使嵌套的function可以“继承”上层上下文的变量,而并列的function之间互不干扰

Closure

  • 如果理解了上文中提到的上下文与作用域链的机制,再来看闭包的概念就很清楚了。每个function在调用时会创建新的上下文及作用域链,而作用域链就是将外层(上层)上下文所绑定的变量对象逐一串连起来,使当前function可以获取外层上下文的变量、数据等。如果我们在function中定义新的function,同时将内层function作为值返回,那么内层function所包含的作用域链将会一起返回,即使内层function在其他上下文中执行,其内部的作用域链仍然保持着原有的数据,而当前的上下文可能无法获取原先外层function中的数据,使得function内部的作用域链被保护起来,从而形成“闭包”。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var x = 100;  
var inc = function(){
var x = 0;
return function(){
console.log(x++);
};
};

var inc1 = inc();
var inc2 = inc();

inc1(); // -> 0
inc1(); // -> 1
inc2(); // -> 0
inc1(); // -> 2
inc2(); // -> 1
x; // -> 100
//inc内部返回的匿名function在创建时生成的作用域链包括了inc中的x,即使后来赋值给inc1和inc2之后,直接在global context下调用,它们的作用域链仍然是由定义中所处的上下文环境决定,而且由于x是在function inc中定义的,无法被外层的global context所改变,从而实现了闭包的效果

this in closure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var name = "global";  
var oo = {
name: "oo",
getNameFunc: function(){
return function(){
return this.name;
};
}
}
oo.getNameFunc()(); // -> "global"

//////此时闭包函数被return后调用相当于:
getName = oo.getNameFunc();
getName(); // -> "global"
//////换个列子
var ooo = {
name: "ooo",
getName: oo.getNameFunc() // 此时闭包函数的this被绑定到新的Object
};
ooo.getName(); // -> "ooo"

float布局

浮动布局的核心就是让元素脱离普通流,然后使用width/height,margin/padding将元素定位。脱离普通流的元素,就像脱离地心引力一样,与普通流不在一个高度上。浮动布局的核心就是让元素脱离普通流,然后使用width/height,margin/padding将元素定位。脱离普通流的元素,就像脱离地心引力一样,与普通流不在一个高度上。

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
.wrap {
background-color: #D66464;
}

.clearfix:after {
content: ""
;

clear: both;
display: block;
}
.left {
float: left;
width: 100px;
background: #00f;
height: 180px;
}

.right {
float: right;
width: 150px;
background: #0f0;
height: 200px;
}

.center {
background: #FFFFFF;
margin-left: 110px;
margin-right: 160px;
height: 150px;
}

普通流布局(inline-block布局)

  • 使用inline-block之前先处理点小障碍:inline-block元素会有4px左右的空隙,这个是因为我们写代码时候的换行符所致。
  • 解决办法很简单:在inline-block的父元素中设置样式font-size:0;letter-spacing: -4px; 然后设置inline-block的所有兄弟元素 font-size:值;letter-spacing: 值px; 恢复正常的显示。
  • 另外还有一点需要注意的是inline-block默认是基线对齐的,而inline-block的基线又跟文本基线一致,所以在内容不同的时候并不能水平对齐。只需要用vertical-align显式声明一下top/bottom/middle对齐即可。
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
.wrap {
background-color: #FBD570;
font-size: 0;
letter-spacing: -4px; /*用于兼容safari,根据不同字体字号或许需要做一定的调整*/
margin-left: 100px;
margin-right: 150px;
}

.wrap * {
font-size: 1rem;
letter-spacing: normal;
}

.left {
display: inline-block;
vertical-align: top;
width: 100px;
background: #00f;
height: 180px;
margin-left: -100px;
}

.right {
display: inline-block;
vertical-align: top;
width: 150px;
background: #0f0;
height: 200px;
margin-right: -150px;
}

.center {
display: inline-block;
vertical-align: top;
background: #B373DA;
height: 150px;
min-width: 150px;
width: 100%;
}

绝对定位

前面的浮动和普通流中其实定位都是靠盒子模型控制的,与我们常说的定位还是有差别的。而绝对定位就是我们平常所说的定位,给定参考坐标系+坐标确定位置。

弹性盒子(flex)

弹性盒布局有如下优势:

  1. 独立的高度控制与对齐。
  2. 独立的元素顺序。
  3. 指定元素之间的关系。
  4. 灵活的尺寸与对齐方式。

弹性框布局算法是方向无关的,与此相对的,块级布局侧重于垂直方向、行内布局侧重于水平方向。

:after
:after的意思就是在此元素后面添加一个元素,里面的样式就是后面元素的样式,相当于在当前元素后面插入一个div;如果不考虑兼容性和美观性问题的话只需要这样写就可以了

1
clearfix:after { clear:both }

其他属性描述:

  • visibility: hidden; 隐藏,为了美观当然要隐藏掉这个元素了
  • display: block; 块级方式,如果不设置的话有的浏览器默认display的属性不是block,比如ie6
  • font-size: 0;content: ” “;height: 0; 这些为了把高度去掉,不然浏览器会给空白字符留高度的
  • height: 1%;zoom:1; 这个主要就是针对ie6了 尤其是zoom:1可以把div变成haslayout属性。
1
2
3
4
5
6
7
8
9
10
.clearfix:after { 
content: "."; /**//*内容为“.”就是一个英文的句号而已。也可以不写。*/
display: block; /**//*加入的这个元素转换为块级元素。*/
clear: both; /**//*清除左右两边浮动。*/
visibility: hidden; /**//*可见度设为隐藏。注意它和 display:none;是有区别的。visibility:hidden;仍然占据空间,只是看不到而已;*/
line-height: 0; /**//*行高为0;*/
height: 0; /**//*高度为0;*/
font-size:0; /**//*字体大小为0;*/
}
.clearfix { *zoom:1;} /**//*这是针对于IE6的,因为IE6不支持:after伪类,这个神奇的zoom:1让IE6的元素可以清除浮动来包裹内部元素。*/

基础知识

  • 它分为强缓存和协商缓存:
  • 浏览器在加载资源时,先根据这个资源的一些http header判断它是否命中强缓存,强缓存如果命中,浏览器直接从自己的缓存中读取资源,不会发请求到服务器。比如某个css文件,如果浏览器在加载它所在的网页时,这个css文件的缓存配置命中了强缓存,浏览器就直接从缓存中加载这个css,连请求都不会发送到网页所在服务器;
  • 当强缓存没有命中的时候,浏览器一定会发送一个请求到服务器,通过服务器端依据资源的另外一些http header验证这个资源是否命中协商缓存,如果协商缓存命中,服务器会将这个请求返回,但是不会返回这个资源的数据,而是告诉客户端可以直接从缓存中加载这个资源,于是浏览器就又会从自己的缓存中去加载这个资源;
  • 强缓存与协商缓存的共同点是:如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据;区别是:强缓存不发请求到服务器,协商缓存会发请求到服务器。
  • 当协商缓存也没有命中的时候,浏览器直接从服务器加载资源数据。

强缓存的原理

  • 当浏览器对某个资源的请求命中了强缓存时,返回的http状态为200,在chrome的开发者工具的network里面size会显示为from cache。
  • 强缓存是利用Expires或者Cache-Control这两个http response header实现的,它们都用来表示资源在客户端缓存的有效期。
  • Expires是http1.0提出的一个表示资源过期时间的header,它描述的是一个绝对时间,由服务器返回,用GMT格式的字符串表示,如:Expires:Thu, 31 Dec 2037 23:55:55 GMT,它的缓存原理是:
1
2
3
4
1)浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在respone的header加上Expires的header
2)浏览器在接收到这个资源后,会把这个资源连同所有response header一起缓存下来(所以缓存命中的请求返回的header并不是来自服务器,而是来自之前缓存的header);
3)浏览器再请求这个资源时,先从缓存中寻找,找到这个资源后,拿出它的Expires跟当前的请求时间比较,如果请求时间在Expires指定的时间之前,就能命中缓存,否则就不行。
4)如果缓存没有命中,浏览器直接从服务器加载资源时,Expires Header在重新加载的时候会被更新。
  • Expires是较老的强缓存管理header,由于它是服务器返回的一个绝对时间,在服务器时间与客户端时间相差较大时,缓存管理容易出现问题,比如随意修改下客户端时间,就能影响缓存命中的结果。所以在http1.1的时候,提出了一个新的header,就是Cache-Control,这是一个相对时间,在配置缓存的时候,以秒为单位,用数值表示,如:Cache-Control:max-age=315360000,它的缓存原理是:
1
2
3
4
1)浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在respone的header加上Cache-Controlheader
2)浏览器在接收到这个资源后,会把这个资源连同所有response header一起缓存下来;
3)浏览器再请求这个资源时,先从缓存中寻找,找到这个资源后,根据它第一次的请求时间和Cache-Control设定的有效期,计算出一个资源过期时间,再拿这个过期时间跟当前的请求时间比较,如果请求时间在过期时间之前,就能命中缓存,否则就不行。
4)如果缓存没有命中,浏览器直接从服务器加载资源时,Cache-Control Header在重新加载的时候会被更新。
  • Cache-Control描述的是一个相对时间,在进行缓存命中的时候,都是利用客户端时间进行判断,所以相比较Expires,Cache-Control的缓存管理更有效,安全一些。
1
这两个header可以只启用一个,也可以同时启用,当response header中,Expires和Cache-Control同时存在时,Cache-Control优先级高于Expires:

强缓存的管理

在实际应用中我们会碰到需要强缓存的场景和不需要强缓存的场景,通常有2种方式来设置是否启用强缓存:

  • 通过代码的方式,在web服务器返回的响应中添加Expires和Cache-Control Header;
  • 通过配置web服务器的方式,让web服务器在响应资源的时候统一添加Expires和Cache-Control Header。
    nginx和apache作为专业的web服务器,都有专门的配置文件,可以配置expires和cache-control
1
如果缓存问题出现在ajax请求中,最有效的解决办法就是ajax的请求地址追加随机数;

强缓存的应用

强缓存是前端性能优化最有力的工具,没有之一,对于有大量静态资源的网页,一定要利用强缓存,提高响应速度。通常的做法是,为这些静态资源全部配置一个超时时间超长的Expires或Cache-Control,这样用户在访问网页时,只会在第一次加载时从服务器请求静态资源,其它时候只要缓存没有失效并且用户没有强制刷新的条件下都会从自己的缓存中加载。

协商缓存的原理

当浏览器对某个资源的请求没有命中强缓存,就会发一个请求到服务器,验证协商缓存是否命中,如果协商缓存命中,请求响应返回的http状态为304并且会显示一个Not Modified的字符串
协商缓存是利用的是【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】这两对Header来管理的。

【Last-Modified,If-Modified-Since】的控制缓存的原理是:

  • 浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在respone的header加上Last-Modified的header,这个header表示这个资源在服务器上的最后修改时间:
  • 浏览器再次跟服务器请求这个资源时,在request的header上加上If-Modified-Since的header,这个header的值就是上一次请求时返回的Last-Modified的值:
  • 服务器再次收到资源请求时,根据浏览器传过来If-Modified-Since和资源在服务器上的最后修改时间判断资源是否有变化,如果没有变化则返回304 Not Modified,但是不会返回资源内容;如果有变化,就正常返回资源内容。当服务器返回304 Not Modified的响应时,response header中不会再添加Last-Modified的header,因为既然资源没有变化,那么Last-Modified也就不会改变,这是服务器返回304时的response header:
  • 浏览器收到304的响应后,就会从缓存中加载资源。
  • 如果协商缓存没有命中,浏览器直接从服务器加载资源时,Last-Modified Header在重新加载的时候会被更新,下次请求时,If-Modified-Since会启用上次返回的Last-Modified值。

【Last-Modified,If-Modified-Since】都是根据服务器时间返回的header,一般来说,在没有调整服务器时间和篡改客户端缓存的情况下,这两个header配合起来管理协商缓存是非常可靠的,但是有时候也会服务器上资源其实有变化,但是最后修改时间却没有变化的情况,而这种问题又很不容易被定位出来,而当这种情况出现的时候,就会影响协商缓存的可靠性。所以就有了另外一对header来管理协商缓存,这对header就是【ETag、If-None-Match】。它们的缓存管理的方式是:

  • 浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在respone的header加上ETag的header,这个header是服务器根据当前请求的资源生成的一个唯一标识,这个唯一标识是一个字符串,只要资源有变化这个串就不同,跟最后修改时间没有关系,所以能很好的补充Last-Modified的问题:
  • 浏览器再次跟服务器请求这个资源时,在request的header上加上If-None-Match的header,这个header的值就是上一次请求时返回的ETag的值:
  • 服务器再次收到资源请求时,根据浏览器传过来If-None-Match和然后再根据资源生成一个新的ETag,如果这两个值相同就说明资源没有变化,否则就是有变化;如果没有变化则返回304 Not Modified,但是不会返回资源内容;如果有变化,就正常返回资源内容。与Last-Modified不一样的是,当服务器返回304 Not Modified的响应时,由于ETag重新生成过,response header中还会把这个ETag返回,即使这个ETag跟之前的没有变化:
  • 浏览器收到304的响应后,就会从缓存中加载资源。

协商缓存的管理

协商缓存跟强缓存不一样,强缓存不发请求到服务器,所以有时候资源更新了浏览器还不知道,但是协商缓存会发请求到服务器,所以资源是否更新,服务器肯定知道。大部分web服务器都默认开启协商缓存,而且是同时启用【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】,比如apache

【Last-Modified,If-Modified-Since】和【ETag、If-None-Match】一般都是同时启用,这是为了处理Last-Modified不可靠的情况。有一种场景需要注意:

  • 分布式系统里多台机器间文件的Last-Modified必须保持一致,以免负载均衡到不同机器导致比对失败;
  • 分布式系统尽量关闭掉ETag(每台机器生成的ETag都会不一样);

浏览器行为对缓存的影响

如果资源已经被浏览器缓存下来,在缓存失效之前,再次请求时,默认会先检查是否命中强缓存,如果强缓存命中则直接读取缓存,如果强缓存没有命中则发请求到服务器检查是否命中协商缓存,如果协商缓存命中,则告诉浏览器还是可以从缓存读取,否则才从服务器返回最新的资源。这是默认的处理方式,这个方式可能被浏览器的行为改变:

  • 当ctrl+f5强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存;
  • 当f5刷新网页时,跳过强缓存,但是会检查协商缓存;

兼容性差异

height:100% IE6+ √
height:inherit IE8+ √

大多数情况作用是一样的

除去兼容性,大多数情况下,两者作用是一样的

① 父容器height: auto,无论height:100%或者height:inherit表现都是auto.
② 父容器定高height: 100px,无论height:100%或者height:inherit表现都是100px高.

绝对定位大不同

当子元素为绝对定位元素,同时,父容器的position值为static的时候,height:100%和height:inherit的差异就可以明显体现出来了!

other

  • width取percentage值时,是以其包含块的width为基准计算的。
  • 取inherit值时,等于其父元素的声明值

  • CommonJS 是javascript模块化编程的一种规范,主要是在服务器端模块化的规范。
    require()用来引入外部模块;exports对象用于导出当前模块的方法或变量,唯一的导出口;module对象就代表模块本身。

  • AMD是”Asynchronous Module Definition”的缩写,意思就是”异步模块定义”,模块的加载不影响它后面语句的执行
    AMD就只有一个接口:define(id,dependencies,factory);

  • CMD是在AMD基础上定义的

  • CMD和AMD的区别有以下几点:

1.对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。

2.CMD推崇依赖就近,AMD推崇依赖前置。

1
define(['./a','./b'], function (a, b) { 

    //依赖一开始就写好 
    a.test(); 
    b.test(); 
}); 

//CMD 
define(function (requie, exports, module) { 
     
    //依赖可以就近书写 
    var a = require('./a'); 
    a.test(); 
     
    ... 
    //软依赖 
    if (status) { 
     
        var b = requie('./b'); 
        b.test(); 
    } 
});