js执行上下文

js的执行上下文

执行上下文是当前js代码被解析和执行时所在的环境的抽象概念,js中的运行任何代码都是在执行上下文中运行。

执行上下文的的三种类型

全局执行上下文:这是默认的,最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文。它做了两件事:

  • 创建一个全局对象,在浏览器中这个全局对象就是window对象。
  • 将this指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。

函数执行上下文:每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在被调用时才会被创建。一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,她都会按照特定的顺序执行一系列步骤。

Eval函数执行上下文:运行在eval函数中的代码也获得自己的执行上下文。

执行上下文的生命周期

执行上下文的生命周期包括三个阶段:创建阶段——执行阶段——回收阶段

创建阶段

当函数被调用时,但未被执行其内部任意代码前,会做以下三件事:

  1. 创建变量对象:首先初始化函数的参数arguments,提升函数声明和变量声明。
  2. 创建作用域链(scope chain):在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,js始终从代码嵌套的最内层开始,如果最内层没有该变量,就往上一层父级作用域中找,直到找到变量。
  3. 确定this指向

在一段 JS 脚本执行之前,要先解析代码(所以说 JS 是解释执行的脚本语言),解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来。变量先暂时赋值为undefined,函数则先声明好可使用。这一步做完了,然后再开始正式执行程序。

另外,一个函数在执行之前,也会创建一个函数执行上下文环境,跟全局上下文差不多,不过 函数执行上下文中会多出this arguments和函数的参数。

以上过程实则为代码的预处理阶段,详细过程如下:

  1. 读取分析整个源代码
  2. 先扫描函数声明,之后扫描变量(var声明)
    • 处理函数声明时有冲突,会覆盖。
    • 处理变量声明有冲突,会忽略。
  3. 将扫描到的函数和变量保存到一个对象中(全局的会保存在window对象中)
  4. 变量的值是undefined,函数的值则是指向该函数(是一个函数字符串)
    • 形式如:window = { x : undefined , f : ‘function(){console.log(1);}’ }

注意:当遇到函数和变量同名且都会被提升的情况,函数声明优先级比较高,因此变量声明会被函数声明所覆盖,但是可以重新赋值。

function声明的优先级比var声明高,也就意味着当两个同名变量同时被function和var声明时,function声明会覆盖var声明

执行阶段

执行变量赋值、代码执行。

该过程为:

  1. 将变量的值从undefined改为给定值。

  2. 调用函数f(),以便函数得到执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    上面函数(f)内,代码的处理过程:

    1、预处理阶段

    a、将函数的参数添加到一个对象(暂定为:词法对象)

    b、扫描函数声明,之后扫描变量(var声明)

    d、将扫描到的函数和方法添加到词法对象里面

    c、变量的值是undefined,函数的值则指向该函数(与全局的一样)

    2、运行阶段

    与全局的的运行原理一样

回收阶段

执行上下文出栈等待虚拟机回收执行上下文

看看一下例子就知道变量提升是怎么回事了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function test(arg){
// 1. 形参 arg 是 "hi"
// 2. 因为函数声明比变量声明优先级高,所以此时 arg 是 function
console.log(arg);
var arg = 'hello'; // 3.var arg 变量声明被忽略, arg = 'hello'被执行
function arg(){
console.log('hello world')
}
console.log(arg);
}
test('hi');
/* 输出:
function arg(){
console.log('hello world')
}
hello
*/

执行上下文栈

函数多了,就有多个函数执行上下文,每次调用函数创建一个新的执行上下文,那如何管理创建的那么多执行上下文呢?

JavaScript 引擎创建了执行上下文栈来管理执行上下文。可以把执行上下文栈认为是一个存储函数调用的栈结构,遵循先进后出的原则

我们需要记住几个关键点:

  • JavaScript执行在单线程上,所有的代码都是排队执行。
  • 一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。
  • 每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收。
  • 浏览器的JS执行引擎总是访问栈顶的执行上下文。
  • 全局上下文只有唯一的一个,它在浏览器关闭时出栈。

推荐:

https://www.cnblogs.com/lark-/p/7954047.html

执行环境及作用域

执行环境定义了变量或函数有权访问的其他函数,决定了各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。

全局执行环境时最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不同,表示执行的对象也不一样,在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境就会被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭网页或浏览器时才会被销毁)。

每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在环境执行之后,栈将其环境弹出,把控制权发回给之前的执行环境。ECMAScript程序中的执行流正是由这个方便的机制控制着。

当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行代码所在环境的变量对象。如果这个环境是函数,则其活动对象作为变量对象。活动对象在最开始时只包含一个变量,即argument对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自外部环境,而在下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。

标识符解析是沿着作用域链一级一级搜索标识符的过程,搜索过程始终从作用域链的前端开始,然后逐级向后回溯,直到找到标识符为止(如果找不到标识符,通常会导致错误发生)。

加长作用域链
  1. try-catch语句的catch块
  2. with语句
1
2
3
4
5
6
7
with(location){
var url = href + qs;
}
// 相当于
function ***(location){
var url = location.href + location.qs;
}
垃圾回收机制

JavaScript具有自动垃圾回收机制,也就是说,执行环境会负责管理代码执行过程中的使用的内存。而在C和C++之类的语言中,开发人员的以像基本任务就是手工跟踪内存的使用情况,这是造成许多问题的一个根源。在编写JavaScript程序时,开发人员不再关心内存使用问题,所需内存的分配以及无用内存的回收完全实现了自动管理。这种垃圾回收机制的原理其实很简单:找出那些不在继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔或代码执行中预定的收集时间周期性的执行这一操作。

局部变量的生命周期

局部变量只在函数执行过程中存在,而在这个过程中,会为局部变量在栈或堆内存上分配相应的空间,以便存储它们的值。然后再函数中使用这些变量,直至函数执行结束。此时,局部变量就没有存在的必要了,因此可以释放他们的内存以供将来使用。在这种情况下,很容易【判断变量是否还有存在的比亚;但并非所有情况下都这么容易就能得出结论。垃圾收集器必须跟踪哪个变量有用哪个没用,对于不再有用的变量打上标记以便将来回收其占用的内存。用于表示无用变量的策略可能也因实现而异,但具体到浏览器中的实现则通常由两个策略。

  1. 标记清除

    垃圾收集器在运行时会给存储在内存中的所有变量加上标记。然后他会去掉环境中的变量以及被环境中的变量引用的变量标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些代表带标记的值并回收他们所占用的内存空间。

  2. 引用计数

    引用计数的含义是跟踪记录每个值被引用的次数。当这个值的引用次数变为0,则说明没法再访问这个值了,就会将其占用的内存空间回收。这样当垃圾收集器下次再运行时,他就会释放那些引用为0的值所占的空间

管理内存

分配给Web浏览器的可用内存数量通常要比分配给桌面应用程序的少。这样做的目的是出于安全方面考虑,目的是防止JavaScript的网页耗尽全部系统内存而导致系统崩溃。内存限制问题不仅会影响给变量分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量。

因此确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式是为执行中的代码只保存必要的数据,一旦数据不再有用,最好通过将其值设置为null来释放其引用——这种做法叫做结束引用。这一做法适用于大多数全局变量和全局变量的属性。局部变量会在它们离开执行环境时自动被解除引用。

不过,解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。

总结

数据类型

基本类型只在内存中占据固定大小的空间,因此会被保存在栈内存中,从一个变量向另一个变量复制,会创建该值的副本。

引用类型的值是对象,保存在堆内存中,包含引用类型值的变量并非对象本身,而是一个指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象。

确定一个值是哪种基本类型可用typeof,而确定是哪种引用用类型使用instanceof。

js中的立即执行函数

js中的立即执行函数

立即执行函数是一种语法模式,可以让你的函数在定义后立即执行。本质是一个表达式,使用运算符将函数声明转换为函数表达式之后就可达到立即执行效果

这样,使得页面加载完成后只执行一次设置的函数。且因将设置函数中的变量包裹在局部变量中,不会泄露成全局变量。

立即执行函数的写法
  1. 匿名函数不能单独定义,必须赋值操作或立即执行,否则会被JS引擎定义为语法错误。

    1
    2
    function(){alert(dada);}
    VM229:1 Uncaught SyntaxError: Unexpected token
  2. 在函数体后面加括号就能立即调用,这个函数形式必须是函数表达式,不能是函数声明。

    1
    2
    function(){alert(123);}();
    VM265:1 Uncaught SyntaxError: Unexpected token (
  3. 可以在函数前面加符号,或者用括号将函数包住来消除函数声明。

    1
    (function(){alert(123);})();
  4. 消除函数声明最安全的做法是加括号,因为运算符号还会和函数的返回值进行运算,造成不必要麻烦。

  5. 包住函数表达式的括号可以括住参数,也可以不括住,效果是一样的。

通常,全局变量被作为一个参数传递给立即执行函数参数,这样她在函数内部不使用window就可以访问到全局变量。

1
2
3
(function (global){
console.log(global);
})(this);

通过定义一个匿名函数,创建了一个新的函数作用域,相当于创建了一个“私有”的命名空间,该命名空间的变量和方法,不会破坏污染全局的命名空间。此时若是想访问全局对象,将全局对象以参数形式传进去即可,如jQuery代码结构。

js中的闭包

谈谈闭包

最开始我以为闭包就是一个函数里内嵌了一个内部函数,内部函数可以访问外部函数的私有变量,并使得其他函数可以访问到该私有变量,其实这只是一小部分,真正的闭包作用不止如此。

一个闭包是一个可以自己拥有独立的环境与变量的表达式(通常是函数,因为到ES6才有块级作用域的概念)。它有权访问另一个函数作用域中的变量的函数。

故主要作用归结为两个:

  1. 可以在函数外部访问到函数内部的函数的局部变量。
  2. 让这些变量始终保存在内存中,不会随着函数的结束而自动销毁。

深层原因

  1. 我们都知道在js执行时一个函数时都会创建一个作用域函数,将函数中的局部变量(函数的形参也是局部变量(保存进去,伴随那些传入的函数变量一起被初始化。当函数执行完毕时,由于返回的函数使用了某个私有属性,,所以返回的函数中保存了一个指向该私有属性的引用,故不会被回收。
1
2
3
4
5
6
7
8
9
10
11
function A(age) {
var name = 'wind';
var sayHello = function() {
console.log('hello, '+name+', you are '+age+' years old!');
};
return sayHello;
}
var wind = A(20);
wind(); // hello, wind, you are 20 years old!

其作用域函数对象为{ age:20, name:'wind'};
  1. 每次调用一次外部函数就产生一个新的闭包,新的作用域,以前的闭包依旧存在互不影响。
  2. 同一个闭包会保留上一次的状态,当它被再次调用时会在上一次的基础上进行。
1
2
3
4
5
6
7
8
9
function add(){
var nnum = 42;
return function(){ console.log(num++); }
}
var a = A();
a(); //42
a(); //43
var b = A(); //重新调用A(),形成新闭包,有了新的作用域对象
b(); //42

4、在外部函数中存在的多个函数 “ 同生共死 ”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var fun1, fun2, fun3;
function A() {
var num = 42;
fun1 = function() { console.log(num); }
fun2 = function() { num++; }
fun3 = function() { num--; }
}

A();
fun1(); // 42
fun2();
fun2();
fun1(); // 44
fun3();
fun1(); //43

var old = fun1;

A();
fun1(); // 42
old(); // 43 上一个闭包的fun1()

三个函数被同时声明并且都可以对作用域对象的属性(局部变量)进行访问与操作。

由于函数不能有多个返回值,所以我用了全局变量。我们再次可以看出在我们第二次调用A()时产生了一个新的闭包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function buildArr(arr){
var result = [];
for(var i = 0; i < arr.length; i++){
var item = 'item' + i;
result.push(function(){
console.log(item + ' ' + arr[i])
});
}
return result;
}
var fnlist = buildArr([1,2,3]);
fnList[0](); //item2 undefined
fnList[1](); //item2 undefined
fnList[2](); //item2 undefined

怎么会这样呢?我们预想的三个输出应该是 item0 1, item1 2, item2 3。为什么结果却是返回的result数组里面存储了三个 item2 undefined ?

我们上文中提到过两点:

  1. 闭包在返回的时候对作用域对象有一个引用。
  2. 在外部函数中存在的内部函数的“同生共死”。

我们的for循环为外部函数创建了多个“同生共死”的内部函数,他们都共享一个环境,而当result数组返回时,所有内部函数都引用了同一个作用域对象:

1
2
3
4
5
var bArr = {
item: 'item2',
i : 3,
arr: [1,2,3]
}

为什么作用域对象是这样?就拿我们上面的例子来说,当循环全部结束时,作用域对象中的属性i正好是i++之后的3,而arr[3]是没有值的,所以为undefined。

注意:在最后一次循环的时候即i = 2时,当i++,i = 3的循环条件不满足循环结束,此时的item的值已经被保存下来了,所以此时的arr[i]为arr[3],而item为item2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function buildArr(arr) {
var result = [];
for (var i = 0; i < arr.length; i++) {
result.push( (function(n) {
return function() {
var item = 'item' + n;
console.log(item + ' ' + arr[n]);
}
})(i));
}
return result;
}

var fnlist = buildArr([1,2,3]);
fnlist[0](); // item0 1
fnlist[1](); // item1 2

我们可以用一个自执行函数将i绑定,这样i的每一个状态都会被存储,答案就和我们预期的一样了。

所以以后在使用闭包的时候遇到循环变量我们要习惯性的想到用自执行函数来绑定它。

1
2
3
4
5
6
7
8
9
10
function buildArr(arr){
var result = [];
for(let i = 0; i <arr.length; i++){
let item = 'item' + i;
result.push(function(){console.log(item + '' + arr[i])});
}
return result;
}
var fnlist = buildArr([1,2,3]);
fnlist[0](); //item0 1

使用let代替,主要是利用let会创建块级作用域的特性。

闭包的用途
  1. 读取函数内部的变量。
  2. 让这些变量的值始终保持在内存中,不会在函数被调用后被自动清除。
  3. 方便调用上下文的局部变量,利于代码的封装。
闭包应用场景
  1. setTimeout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//原生的setTimeout传递的第一个函数不能带参数
setTimeout(function(param){
alert(param)
},1000)


//通过闭包可以实现传参效果
function func(param){
return function(){
alert(param)
}
}
var f1 = func(1);
setTimeout(f1,1000);
  1. 作为回调函数
1
2
3
4
5
function changeSize(size){
return function(){
document.body.style.fontSize = size + 'px';
};
}
  1. 封装变量
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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>闭包模拟私有方法</title>
<link rel="stylesheet" href="">
</head>
<body>
<script>
//用闭包定义能访问私有函数和私有变量的公有函数。
var counter = (function(){
var privateCounter = 0; //私有变量
function change(val){
privateCounter += val;
}
return {
increment:function(){ //三个闭包共享一个词法环境
change(1);
},
decrement:function(){
change(-1);
},
value:function(){
return privateCounter;
}
};
})();

console.log(counter.value());//0
counter.increment();
counter.increment();//2
//共享的环境创建在一个匿名函数体内,立即执行。
//环境中有一个局部变量一个局部函数,通过匿名函数返回的对象的三个公共函数访问。

</script>
</body>
</html>
  1. 为节点循环绑定click事件
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<link rel="stylesheet" href="">
</head>
<body>

<p id="info">123</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>

<script>
function showContent(content){
document.getElementById('info').innerHTML = content;
};

function setContent(){
var infoArr = [
{'id':'email','content':'your email address'},
{'id':'name','content':'your name'},
{'id':'age','content':'your age'}
];
for (var i = 0; i < infoArr.length; i++) {
var item = infoArr[i];
document.getElementById(item.id).onfocus = function(){
showContent(item.content)
}
}
}
setContent()
//循环中创建了三个闭包,他们使用了相同的词法环境item,item.content是变化的变量
//当onfocus执行时,item.content才确定,此时循环已经结束,三个闭包共享的item已经指向数组最后一项。



/**
* 解决方法1 通过函数工厂,则函数为每一个回调都创建一个新的词法环境
*/
function showContent(content){
document.getElementById('info').innerHTML = content;
};

function callBack(content){
return function(){
showContent(content)
}
};

function setContent(){
var infoArr = [
{'id':'email','content':'your email address'},
{'id':'name','content':'your name'},
{'id':'age','content':'your age'}
];
for (var i = 0; i < infoArr.length; i++) {
var item = infoArr[i];
document.getElementById(item.id).onfocus = callBack(item.content)
}
}
setContent()

/**
* 解决方法2 绑定事件放在立即执行函数中
*/
function showContent(content){
document.getElementById('info').innerHTML = content;
};

function setContent(){
var infoArr = [
{'id':'email','content':'your email address'},
{'id':'name','content':'your name'},
{'id':'age','content':'your age'}
];
for (var i = 0; i < infoArr.length; i++) {
(function(){
var item = infoArr[i];
document.getElementById(item.id).onfocus = function(){
showContent(item.content)
}
})()//放立即执行函数,立即绑定,用每次的值绑定到事件上,而不是循环结束的值
}
}
setContent()

/**
* 解决方案3 用ES6声明,避免声明提前,作用域只在当前块内
*/
function showContent(content){
document.getElementById('info').innerHTML = content;
};

function setContent(){
var infoArr = [
{'id':'email','content':'your email address'},
{'id':'name','content':'your name'},
{'id':'age','content':'your age'}
];
for (var i = 0; i < infoArr.length; i++) {
let item = infoArr[i]; //限制作用域只在当前块内
document.getElementById(item.id).onfocus = function(){
showContent(item.content)
}
}
}
setContent()
</script>
</body>
</html>

推荐博文:https://blog.csdn.net/qq_36276528/article/details/70049825

了解浏览器

理解浏览器的工作原理

前言

​ 浏览器是专门用来访问和浏览万维网页面的客户端软件,也是现在最为流行的软件之一,其重要性不言而喻。前端工程师作为负责程序页面显示的工程师,需要直接和浏览器打交道。本文将详细介绍浏览器的工作原理。

浏览器组成

browser

主要组件包括:

  1. 用户界面——包括地址栏,后退/前进按钮,书签,目录等,也就是所看到的除了用来显示所请求的页面窗口之外的其他部分。
  2. 浏览器引擎——用来查询及操作渲染引擎的接口。
  3. 渲染引擎——用来显示请求的内容,如:如果请求内容为html,他负责解析html及css,并将解析后的结果显示在页面。
  4. 网络——用来完成网络调用,如:http请求,它具有平台无关的接口,可以在不同平台上工作。
  5. UI后端——用来绘制类似组合选择框及对话框等基本组件,具有不特定与某个平台的通用接口,顶层使用操作系统的用户接口。
  6. JS解释器——用来执行JS代码。
  7. 数据存储——属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术。

内核

浏览器分为两部分:渲染引擎和js引擎,由于js引擎越来越独立,内核就倾向于只指渲染引擎,负责请求网络页面资源并解析排版并呈现给用户。

默认情况下,渲染引擎可以显示html,xml文档及图片,他可以借助插件显示其他类型数据,例如使用PDF阅读器插件,可以显示PDF格式。

渲染引擎

firefox使用gecko引擎

IE使用Trident引擎,2015年微软推出自己新的浏览器,原名叫斯巴达,后改名edge,使用edge引擎

opera最早使用Presto引擎,后来弃用

chrome\safari\opera使用webkit引擎,13年chrome和opera开始使用Blink引擎

UC使用U3引擎

QQ浏览器和微信内核使用X5引擎,16年开始使用Blink引擎

js引擎

老版本IE使用Jscript引擎,IE9之后使用Chakra引擎,edge浏览器仍然使用Chakra引擎

firefox使用monkey系列引擎

safari使用的SquirrelFish系列引擎

Opera使用Carakan引擎

chrome使用V8引擎。nodeJs其实就是封装了V8引擎

渲染流程

  从资源的下载到最终的页面展现,渲染流程可简单地理解成一个线性串联的变换过程的组合,原始输入为URL地址,最终输出为页面Bitmap,中间依次经过了Loader、Parser、Layout和Paint模块

chrome1

 渲染引擎的核心流程如下所示

chrome

Loader

Loader模块负责处理所有的HTTP请求以及网络资源的缓存,相当于是从URL输入到Page Resource输出的变换过程。HTML页面中通常由JS/CSS/Image资源,为了不阻塞后续解析过程,一般会有两个IO管道同时存在,一个负责主页面下载,一个负责各种外链接资源的下载。

chrome

注意:虽然大部分情况下不同资源都是并发下载异步解析(如图片资源可以在主页面解析显示完成后再被显示),但JS脚本可能会要求改变页面,因此有时保持执行顺序和下载管道后续处理的阻塞是不可避免的。

缓存

缓存在浏览器中也得到广泛的应用,对提高用户体验起到重要的作用。在浏览器中,主要存在三种类型的缓存:Page Cache,Memory Cache,Disk Cacche。这三类Cache的容量都是额可以配置的,比如限制Memory Cache最大不超过30M,Page Cache缓存的页面数量不超过5个等。

Page Cache:即页面缓存。用来缓存用户访问过的网页DOM树、Render树等数据。设计页面缓存的意图在于提供流畅的页面前进、后退浏览体验。几乎所有的现代浏览器都支持页面缓存功能

Memory Cache:浏览器内部的缓存机制,对于相同的url的资源直接从缓存中获取,不需要重新下载。在使用浏览器浏览网页时,尤其是浏览一个大型网站的不同页面时,经常会遇到网页中包含相同资源的情况,应用Memory Cache可以显著提高浏览器的用户体验,减少无谓的内存、时间以及网络带宽开销。

Disk Cache:资源加载缓存和服务器进行交互,服务器端可以通过HTTP头信息设置网页要不要加载;

转自:

https://www.cnblogs.com/itplay/p/10475368.html

我认识的js原型及原型链

JavaScript原型

在讲js原型之前,必须先了解Object和Function。

Object和Function作为js自带的函数,Object继承自己,Function继承自己,Object和Function互相继承对方,也就是说Object和Function都既是函数也是对象。

1
2
3
4
Function instanceof Object;	//true
Object instanceof Function; //true
Function.prototype //f(){ [native code]}
Object.prototype //Object

普通对象和函数对象

在Javascript的世界里,全都是对象,而对象之间也是存在区别,我们首先区分一下普通对象和函数对象,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function f1(){};
var f2 = function(){};
var f3 = new function(){};

var o1 = {};
var o2 = new Object();
var o3 = new f1();

console.log(typeof Object); //function
console.log(typeof Function);//function
console.log(typeof f1) //function
console.log(typeof f2) // function
console.log(typeof f3) //function
console.log(typeof o1) //object
console.log(typeof o2) //object
console.log(typeof o3)// object

在上面的代码中可以看出,f1、f2和f3都是函数对象,而o1,o2和o3都是object对象,也就是普通对象,函数对象本质就是由new function()构造而来,其他的都是普通对象;函数对象可以创建普通对象,普通对象没法创建函数对象。

注意:

1、每一个函数对象都有一个prototype属性,但是普通对象是没有的;

  prototype下面又有个construetor,指向这个函数。

2、每个对象都有一个名为__proto__的内部属性,指向它所对应的构造函数的原型对象,原型链基于__proto__;

1
2
3
4
5
6
7
var o = {};
o.prototype; //undefined
o instanceof Object; //true
o._proto_ === Object.prototype; //true
Object === Object.prototype.constructor; //true
Object.prototype.constructor; //function Object(){ [native code]}
Object.prototype.__proto__; //null

原型

在js中,原型也是一个对象,其作用则是实现对象的继承。

在js的所有函数对象中都存在一个属性prototype,该属性对应当前对象的原型。

而所有js对象都存在一个__proto__属性(由于__proto__是一个非标准属性,因此只有火狐和谷歌两个浏览器支持,标准方法是Object.getPrototypeOf()),__proto__属性指向实例对象的构造函数的原型,理解起来就是如下:

1
2
var p = new Person();
p.__proto__ === Person.prototype; //true

从上面代码中,p是实例对象,Person是p的构造函数,可看出p的__proto__属性指向构造函数Person的原型。

请看一下例子加深理解

1
2
3
4
5
6
7
8
9
var parent = function(name){
this.name = name;
}
parent.prototype.getName = function(){
return this.name;
}
var son = new parent("huahua");

console.log(son.getName());//'huahua'

原型链

除开Object的prototype的原型是null以外,所有的对象和原型都有自己的原型,对象的原型指向原型对象。

在层级多的关系中,多个原型层层相连则构成了原型链。

在查找一个对象的属性时,倘若在当前对象找不到该属性,则会沿着原型链一直往上查找,知道找到为止,如果到了原型链顶端,还没找到,则返回undefined。

img

JS中所有的东西都是对象,所有的东西都由Object衍生而来, 即所有东西原型链的终点指向null

构造器constructor

constructor是构造函数创建的实例的属性,该属性的作用是指向创建当前对象的构造函数。

例如,son.constructor == parent;//true

深入理解js中的this

深入理解js中的this

学过javascript的同学想必都听过对this指向的描述,谁调用就指向谁,但究竟是怎么回事,欢迎往下看。

先来看看以下例子:

1
2
3
4
5
6
7
8
var obj = {
bar:1,
foo:function(){console.log(this.bar);}
};
var foo = obj.foo;
var bar = 2;
console.log(obj.foo()); //1
console.log(foo()); //2

解释:

obj.foo()摆明了是obj调用其属性对象foo,所以里面的this指向的就是obj这个对象。

对于foo()来说,等价于window.foo()。我们习惯性将全局变量window的属性直接以属性名展现。

JavaScript语言之所以有this的设计,跟内存里面的数据结果有关。

内存的数据结构

1
var obj = {foo : 5};

上面的代码将一个对象赋值给变量obj。js引擎会先在内存里面生成一个对象{foo:5},然后把这个对象的内存地址赋值给变量obj

也就是说,变量obj是一个地址(reference)。后面如果要读取obj.foo,引擎先从obj拿到内存地址,然后再从该地址读出原始的对象,返回它的foo属性。

原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的foo属性,实际上是以下面的形式保存的。

img

1
2
3
4
5
6
7
  foo: {
[[value]]: 5
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}

注意foo属性的值保存在属性描述对象value属性里。

若属性的值是一个函数呢
1
var obj = { foo: function () {} };

这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo属性的value属性。

由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。

1
2
3
4
5
6
7
8
var f = function () {console.log(this);};
var obj = { f: f };

// 单独执行,相当于Window.f()
f() //Window

// obj 环境执行
obj.f() //obj

js 允许在函数体内部,引用当前环境的其他变量。

this的设计目的就是在函数体内部,指代函数当前的运行环境。

img

this的实例讲解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a);
}
},
fn:function(){
console.log(this.a);
}
}
o.b.fn(); //12
o.fn(); //10

o.b.fn()因为函数fn()是被b调用,所以this指向b,this.a的值自然是b.a

由此我们可以总结出几种情况:

 情况1:如果一个函数中有this,但是它没有被上一级的对象所调用,那么this指向的就是window,这里需要说明的是在js的严格版中this指向的不是window,但是我们这里不探讨严格版的问题,你想了解可以自行上网查找。

 情况2:如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。

 情况3:如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象

1
2
3
4
5
6
7
8
9
10
var o = {
a:10,
b:{
// a:12,
fn:function(){
console.log(this.a);
}
}
}
o.b.fn(); //undefined

尽管对象b中没有属性a,这个this指向的也是对象b,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西。

还有一种比较特殊的情况

1
2
3
4
5
6
7
8
9
10
11
12
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //undefined
console.log(this); //window
}
}
}
var j = o.b.fn;
j();

这里this指向的是window,是不是有些蒙了?其实是因为你没有理解一句话,这句话同样至关重要。

this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的,例子中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window。

构造函数版this

1
2
3
4
5
function Fn(){
this.user = "nikita";
}
var a = new Fn();
console.log(a.user); //nikita

  这里之所以对象a可以点出函数Fn里面的user是因为new关键字可以改变this的指向,将这个this指向对象a,为什么我说a是对象,因为用了new关键字就是创建一个对象实例,我们这里用变量a创建了一个Fn的实例(相当于复制了一份Fn到对象a里面),此时仅仅只是创建,并没有执行,而调用这个函数Fn的是对象a,那么this指向的自然是对象a,那么为什么对象a中会有user,因为你已经复制了一份Fn函数到对象a中,用了new关键字就等同于复制了一份。

当this遇上return时

先对比一下几个例子:

返回值是一个对象

1
2
3
4
5
6
7
function fn()  
{
this.user = 'nikita';
return {};
}
var a = new fn;
console.log(a.user); //undefined

返回一个函数

1
2
3
4
5
6
7
function fn()  
{
this.user = 'nikita';
return function(){};
}
var a = new fn;
console.log(a.user); //undefined

返回普通值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function fn()  
{
this.user = 'nikita';
return 1;
}
var a = new fn;
console.log(a.user); //nikita

function fn()
{
this.user = 'nikita';
return undefined;
}
var a = new fn;
console.log(a.user); //nikita

由此可知:

 如果返回值是一个对象/函数,那么this指向的就是那个返回的对象/函数,如果返回值不是一个对象/函数那么this还是指向函数的实例。

不过有一点需要注意就是:

虽然null也是对象,但this还是指向那个函数的实例

1
2
3
4
5
6
7
function fn()  
{
this.user = 'nikita';
return null;
}
var a = new fn;
console.log(a.user); //nikita

知识点补充:

  1.在严格版中的默认的this不再是window,而是undefined。

  2.new操作符会改变函数this的指向问题,虽然我们上面讲解过了,但是并没有深入的讨论这个问题,网上也很少说,所以在这里有必要说一下。

1
2
3
4
5
6
var num = 1;
function fn(){
this.num = 2;
}
var a = new fn();
console.log(a.num); //2

 为什么this会指向a?首先new关键字会创建一个空的对象,然后会自动调用将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。

我们来看看new时发生的4个阶段。

1
var Func = function(){};
  1. 创建一个空对象
    1
    var obj = new Func();
  2. 设置原型链
    1
    obj._proto_ = Func.prototype;
  3. 让Func中的this指向obj,并执行Func的函数体。
    1
    var res = Func.call(obj);
  4. 判断Func的返回值类型

    如果是值类型,返回obj。如果是引用类型,就返回这个引用类型的对象。

    1
    2
    3
    4
    5
    6
    if (typeof(result) == "object"){
    func=result;
    }
    else{
    func=obj;;
    }

vue中的this及其作用域

这是vue文档里的原话:

All lifecycle hooks are called with their ‘this’ context pointing to the Vue instance invoking it.

意思是:在Vue所有的生命周期钩子方法(如created,mounted, updated以及destroyed)里使用this,this指向调用它的Vue实例,即(new Vue)。

箭头函数中的this

箭头函数没有自己的this和作用域,它的this是继承来的;默认指向在定义时他所处的对象(宿主环境),而不是执行时的对象,定义时,它的对象环境可能是window;箭头函数可以方便让我们在setTimeout,setInterval中方便的使用this。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var app = new Vue({
el : '#app',
data:{
message : '你好'
},
created : function(){
console.log(this.message); //你好 this指向vue实例
this.showMessage1(); //hello
this.showMessage2(); //你好
},
methods:{
showMessage1:function(){
setTimeout(function(){
document.getElementById('id1').innerText = this.message; //this指向window
},10)
},
showMessage2:function(){
setTimeout(() => {
document.getElementById('id2').innerText = this.message; //this指向vue实例
},10);
}
}
});

第一个输出“hello”,第二个输出“你好”。说明showMessage1()中的this指向的是window,showMessage()指向vue实例组件。

绑定vue实例到this的方法

为防止this指向出现歧义,有两种方法绑定this。

使用bind

1
2
3
4
5
showMessage:function(){
setTimeout(function(){
console.log(this.message);
}.bind(this),10);
}

对setTimeout()里的匿名函数使用bind()绑定到vue实例的this。这样在匿名函数中的this也是vue实例了。

把vue实例的this赋值给另一个变量再使用。

1
2
3
4
5
6
showMessage:function(){
var self = this;
setTimeout(function(){
console.log(self.message); //改为self
}.bind(this),10);
}

总结:

对于普通函数(包括匿名函数),this指向的是直接调用者,在非严格状态下,如果没有直接调用者,this指向window。在严格状态下,如果没有直接调用者,this为undefined。

箭头函数没有自己的作用域,在他内部使用的this是有它的宿主环境决定。

使用call,apply,bind绑定的,this指向绑定的对象。

this绑定的优先级

this绑定的优先级:

new > bind > call(apply) > obj.func() > 默认绑定。

1
2
3
4
var obj = {};
obj.log = console.log;
obj.log.call(console,this);
// 输出window

obj.log = console.log 为obj对象创建了一个函数(即console.log)的引用log,因为这是个函数的引用,所以想要执行有两种方法,一种是直接加上(),另一种如上转变this指向。而obj.log.call(console,this)中的第二个参数是从外部传进去的,和函数内的this对象无任何关系。

相对全面的数组去重

数组去重的方法

  1. 遍历数组法

    最简单的去重方法,实现思路:新建一新数组,遍历传入数组,值不在新数组就加入该新数组;注意:判断值是否在数组的方法“indexOf”是ECMAScript5方法中,IE8以下不支持需多写一些兼容低版本浏览器代码,源码如下:

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
//最简单数组去重法
function unique1(array){
var n = []; //一个新的临时数组
//遍历当前数组
for(var i = 0; i < array.length; i++){
//如果当前数组的第i已经保存进了临时数组,那么跳过
//否则把当前项push到临时数组中
if(n.indexOf(array[i]) == -1){
n.push(array[i]);
}
}
return n;
}
//判断浏览器是否支持indexOf
if(!Array.prototype.indexOf){
Array.prototype.indexOf = function(item){
var result = -1,a_item = null;
if(this.length == 0){
return result;
}
for(var i = 0,len = this.length; i < len;i++){
a_item = this[i];
if(a_item === item){
result = i;
break;
}
}
return result;
}
}
  1. 对象键值对法

    该方法执行的速度比较快,但占用内存比较大;实现思路:新建一js对象以及新数组,遍历传入数组时,判断值是否为js对象的值,不是的话给对象新增该键并放入新数组。注意:判断是否为js对象时,会自动对传入的键执行“toString()”,不同的键可能会被误认为一样;如:a[4] , a[‘4’]。解决方法:调用”indexOf“。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function unique2(array){
var n = {}, r = [], len = array.length, type, val;
for(var i = 0; i < len; i++){
val = array[i];
type = typeof val;
if(!n[val]){
n[val] = [type];
r.push(val);
}else if(n[val].indexOf(type) < 0){
n[val].push(type);
r.push(val);
}
}
return r;
}
  1. 数组下标法

    还是得调用“index Of”性能同方法1差不多,实现思路:如果当前数组的第i项在当前数组中出现的位置不是i,那么表示第i项是重复的。否则存入结果数组。

1
2
3
4
5
6
7
8
9
function unique3(array){
var n = [array[0]];
for(var i = 1; i < array.length; i++){
if(array.indexOf(array[i]) === i){
n.push(array[i]);
}
}
return n;
}
  1. 排序后相邻去除法

    虽然原生数组的“sort”方法排序后不怎么靠谱,但在不重视顺序的去重里该缺点毫无影响。实现思路:给传入的数组排序,排序后相同值相邻,然后遍历新数组只加入不与前一值重复的值。

1
2
3
4
5
6
7
8
9
10
function unique4(array){
array.sort();
var re = array[0];
for(var i = 1; i < array.length; i++){
if(array[i] !== re[re.length-1]){
re.push(array[i]);
}
}
return re;
}
  1. 优化遍历数组法

    实现思路:获取没重复的最右一值放入新数组。(检测到有重复值时终止当前循环同时进入顶层循环的下一轮判断)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function unique5(array){
    var re = [];
    for(var i = 0 , len = array.length; i < len; i++){
    for(var j = i + 1; j < len; j++){
    if(array[i] === array[j]){
    ++i;
    }
    }
    re.push(array[i]);
    }
    return re;
    }
  2. 在Array的原型上实现去重
    1
    2
    3
    4
    Array.prototype.unique6 = function(){
    //this指向要操作的数组
    return [...new Set(this)];
    }
  3. 利用ES6中的Set + Array.from去重

    Set本身是一个构造函数,用来生成Set数据结构。它是一个类数组,成员的值都是唯一的。Array.from()将类数组转为真正的数组。

    1
    2
    3
    function unique7(array){
    return Array.from(new Set(arr));
    }
  4. 双层循环和splice去重
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function unique9(array){
    for(var i = 0; i < array.length; i++){
    for(var j = i + 1; j < array.length; j++){
    if(array[i] === array[j]){
    array.splice(j,1);
    j--;
    console.log(array[i]);
    }
    }
    }
    return array;
    }
  5. 利用includes去重

    不能去掉两个空的对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function unique9(array){
    var arr = [array[0]];
    for(var i = 1; i < array.length; i++){
    if(!arr.includes(array[i])){
    arr.push(array[i]);
    }
    }
    return arr;
    }
  6. 利用hasOwnProperty判断是否存在对象属性。

    不能去掉两个空的对象

    1
    2
    3
    4
    5
    6
    function uniaue10(array){
    var obj = {};
    return arr.filter(function(item,index,array){
    return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
    });
    }
  7. 利用filter去重

    1
    2
    3
    4
    5
    function unique11(array){
    return array.filter(function(item,index,array){
    return array.indexOf(item,0) === index;
    });
    }
  8. 利用map数据结构

    会将空对象看作两个对象,同时NaN与“NaNcy”也是不同的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function unique12(array){
    let map = new Map();
    let arr = new Array();
    for(let i = 0; i < array.length; i++){
    if(map.has(array[i])){
    map.set(array[i],true);
    }else{
    map.set(array[i],false);
    arr.push(array[i]);
    }
    }
    return arr;
    }

css盒模型集锦

第一次写博文,先来说说css中的盒子模型吧!

css盒子模型

所有HTML元素可以看作盒子,在CSS中,”box model”这一术语是用来设计和布局时使用。

CSS盒模型本质上是一个盒子,封装周围的HTML元素,它包括:边距,边框,填充,和实际内容。

盒模型允许我们在其它元素和周围元素边框之间的空间放置元素。

CSS box-model

在页面设计中有4个常见属性:content(内容)、padding(内边距)、border(边框)和margin(外边距),我们把这4部分转化成日常生活的盒子来理解,所以称为盒子模型。

content(内容)就是盒子里装的东西,padding(内边距)就是怕盒子里装的东西损坏而添加的泡沫或者其他抗震防挤压的辅料,border(边框)就是盒子本身了,margin(外边距)则说明盒子摆放的时候不能全部堆在一起,要留一定空隙。

在网页设计中,content常指文字、图片等元素,但是也可以是小盒子(DIV嵌套),padding只有宽度属性,可以理解为真实盒子中抗震辅料的厚度,而border有大小和颜色之分,又可以理解为真实盒子的厚度以及这个盒子的颜色或材料,margin就是该盒子与其他东西要保留多大距离。


了解完盒子模型就不得不说元素的宽度和高度了。

W3C标准盒模型(box-sizing:content-box;)

一般元素默认使用标准盒模型元素实际宽 = content内容宽(设置的width值)+ padding-left(左内边距)+ padding-right(右内边距)+border-left(左边宽)+border-right(右边宽)+margin-left(左边距)+margin-right(右边距);

元素实际高 = content内容高(设置的height值)+ padding-top(上内边距)+ padding-bottom(下内边距)+border-top(上边宽)+border-bottom(下边宽)+margin-top(上边距)+margin-bottom(下边距);

注意:我们通过javascript代码获取某一个元素的大小时,其实指的是content的大小,即我们设置width的值。

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
.box{

width: 200px;

height: 180px;

padding: 10px;

border: 1px solid black;

margin: 5px;

}

盒子实际尺寸

1
2
3
4
5
6
宽度 = margin.left + border.left + padding.left + content.width + padding.right + border.right + margin.right
= 5 + 1 + 10 + 200 + 10 + 1 + 5
= 232
高度 = margin.top + border.top + padding.top + content.height + padding.bottom + border.bottom + margin.bottom
= 5 + 1 + 10 + 180 + 10 + 1 + 5
= 212

IE模型(box-sizing:border-box;)

如果想在非IE浏览器使用该模型,只需设置 box-sizing:border-box;即可

盒子实际宽度 = 内容宽(width 的值)+ margin-left + margin-right;

上面例子中的盒子实际尺寸

1
2
3
4
5
6
宽度 = margin.left + width + margin.right
= 5 + 200 + 5
= 210
高度 = margin.top + height + margin.bottom
= 5 + 180 + 5
= 190

这是因为IE模型中的width默认包括content,padding和border的值。

弹性盒子flexbox

Flexbox布局的最大特性就是让Flex项目可伸缩,也就是让Flex项目的宽度和高度可以自动填充Flex容器剩余的空间或者缩小Flex项目适配Flex容器不足的宽度。而这一切都是依赖于Flexbox属性中的flex属性来完成。

弹性盒子是一种当页面需要适应不同屏幕大小以及设备类型时确保元素拥有恰当的行为的布局方式

引入弹性盒布局模型的目的是提供一种更加有效的方式来对一个容器中的子元素进行排列、对齐和分配空白空间

弹性盒子由弹性容器(Flex container)和弹性子元素(Flex item)组成。弹性容器通过设置 display 属性的值为 flex 或 inline-flex将其定义为弹性容器。弹性容器内包含了一个或多个弹性子元素。

注意: 弹性容器外及弹性子元素内是正常渲染的。弹性盒子只定义了弹性子元素如何在弹性容器内布局。弹性子元素通常在弹性盒子内一行显示。默认情况每个容器只有一行。

img

1.指定弹性子元素在父容器中的位置flex-direction
  • flex-direction 属性规定灵活项目的方向。
  • 当你调转行和列的方向时,flex-start和flex-end对应的方向也被调转
  • 当flex以列为方向时,justify-content控制纵向对齐,align-items控制横向对齐。
  • flex-direction: row|row-reverse|column|column-reverse|initial|inherit;
2.内容(横轴上)对齐justify-content属性
  • 内容对齐justify-content属性应用在弹性容器上,把弹性项沿着弹性容器的主轴线(main axis)对齐。

  • justify-content 用于设置或检索弹性盒子元素在主轴(横轴)方向上的对齐方式。

  • justify-content: flex-start | flex-end | center | space-between | space-around;

    • flex-start:

      弹性项目向行头紧挨着填充。这个是默认值。第一个弹性项的main-start外边距边线被放置在该行的main-start边线,而后续弹性项依次平齐摆放。

    • flex-end:

      弹性项目向行尾紧挨着填充。第一个弹性项的main-end外边距边线被放置在该行的main-end边线,而后续弹性项依次平齐摆放。

    • center:

      弹性项目居中紧挨着填充。(如果剩余的自由空间是负的,则弹性项目将在两个方向上同时溢出)。

    • space-between:

      弹性项目平均分布在该行上。如果剩余空间为负或者只有一个弹性项,则该值等同于flex-start。否则,第1个弹性项的外边距和行的main-start边线对齐,而最后1个弹性项的外边距和行的main-end边线对齐,然后剩余的弹性项分布在该行上,相邻项目的间隔相等。

    • space-around:

      弹性项目平均分布在该行上,两边留有一半的间隔空间。如果剩余空间为负或者只有一个弹性项,则该值等同于center。否则,弹性项目沿该行分布,且彼此间隔相等(比如是20px),同时首尾两边和弹性容器之间留有一半的间隔(1/2*20px=10px)。

img

3.侧轴方向上的对齐方式align-items
  • align-items 设置或检索弹性盒子元素在侧轴(纵轴)方向上的对齐方式。
  • align-items: flex-start | flex-end | center | baseline | stretch
    • flex-start:弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴起始边界。
    • flex-end:弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴结束边界。
    • center:弹性盒子元素在该行的侧轴(纵轴)上居中放置。(如果该行的尺寸小于弹性盒子元素的尺寸,则会向两个方向溢出相同的长度)。
    • baseline:如弹性盒子元素的行内轴与侧轴为同一条,则该值与’flex-start’等效。其它情况下,该值将参与基线对齐。
    • stretch:如果指定侧轴大小的属性值为’auto’,则其值会使项目的边距盒的尺寸尽可能接近所在行的尺寸,但同时会遵照’min/max-width/height’属性的限制。
4.弹性盒子子元素换行方式flex-wrap
  • flex-wrap 属性规定flex容器是单行或者多行,同时横轴的方向决定了新行堆叠的方向。
  • flex-wrap :normal | wrap | wrap-reverse | initial |inherit ;
5.设置各个行(垂直方向)的对齐aign-content
  • 设置行与行之间的距离
  • align-content属性在弹性容器内的各项没有占用交叉轴上所有可用空间时对齐容器内的各项(只在垂直方向上,都是单独占据一行)
  • align-content: flex-start | flex-end | center | space-between | space-around | stretch
    • stretch - 默认。各行将会伸展以占用剩余的空间。
    • flex-start - 各行向弹性盒容器的起始位置堆叠。
    • flex-end - 各行向弹性盒容器的结束位置堆叠。
    • center -各行向弹性盒容器的中间位置堆叠。
    • space-between -各行在弹性盒容器中平均分布。
    • space-around - 各行在弹性盒容器中平均分布,两端保留子元素与子元素之间间距大小的一半。
6.排列弹性子元素order
  • 用整数来定义排列顺序,数值小的排在前面,可为负值。 设置或检索弹性盒模型对象的子元素出现的順序。
  • order: number|initial|inherit;
7.完美居中margin:auto;
  • 使用弹性盒子,只需设置margin:auto;可使得弹性子元素在两上轴方向上完全居中
8.设置弹性元素自身在侧轴方向上的对齐方式align-self
  • align-self:auto | flex-start | flex-end | center | baseline | stretch

    • auto:如果’align-self’的值为’auto’,则其计算值为元素的父元素的’align-items’值,如果其没有父元素,则计算值为’stretch’。

    • flex-start:弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴起始边界。

    • flex-end:弹性盒子元素的侧轴(纵轴)起始位置的边界紧靠住该行的侧轴结束边界。

    • center:弹性盒子元素在该行的侧轴(纵轴)上居中放置。(如果该行的尺寸小于弹性盒子元素的尺寸,则会向两个方向溢出相同的长度)。

    • baseline:如弹性盒子元素的行内轴与侧轴为同一条,则该值与’flex-start’等效。其它情况下,该值将参与基线对齐。

    • stretch:如果指定侧轴大小的属性值为’auto’,则其值会使项目的边距盒的尺寸尽可能接近所在行的尺寸,但同时会遵照’min/max-width/height’属性的限制。
    • align-self 属性可重写灵活容器的 align-items 属性。

9.指定子元素如何分配空间flex
  • flex属性用于设置和检索弹性盒模型对象的子元素如何分配空间
  • flex属性是flex-grow,flex-shrink和flex-basis属性的简写属性
  • 注意:如果元素不是弹性盒模型对象的子元素,则 flex 属性不起作用。
  • flex: flex-grow flex-shrink flex-basis | auto | initial | inherit;
10.设置flex容器的对齐方式align-items
  • 使用每个flex对象元素的 align-self 属性可重写 align-items 属性。
  • align-items: stretch | center | flex-start | flex-end | baseline | initial | inherit;
|