面试知识点总结

- 布局

- 弹性布局(display:flex;)属性详解
- Flexbox 是 flexible box 的简称(注:意思是“灵活的盒子容器”),是 CSS3 引入的新的布局模式。它决定了元素如何在页面上排列,使它们能在不同的屏幕尺寸和设备下可预测地展现出来。
- 它之所以被称为 Flexbox ,是因为它能够扩展和收缩 flex 容器内的元素,以最大限度地填充可用空间。与以前布局方式(如 table 布局和浮动元素内嵌块元素)相比,Flexbox 是一个更强大的方式:
- 在不同方向排列元素
- 重新排列元素的显示顺序
- 更改元素的对齐方式
- 动态地将元素装入容器
- 一、基本概念
- 采用 Flex 布局的元素,称为 Flex 容器(flex container),简称”容器”。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称”项目”。

1. 在 Flexbox 模型中,有三个核心概念:
1. – flex 项(注:也称 flex 子元素),需要布局的元素
1. – flex 容器,其包含 flex 项
1. – 排列方向(direction),这决定了 flex 项的布局方向
1. 二、容器属性

1. 2.1 flex-direction:
1. row(默认值):主轴为水平方向,起点在左端。
1. row-reverse:主轴为水平方向,起点在右端。
1. column:主轴为垂直方向,起点在上沿。
1. column-reverse:主轴为垂直方向,起点在下沿。

1. 2.2 flex-wrap:
1. nowrap(默认):不换行。
1. wrap:换行,第一行在上方。
1. wrap-reverse:换行,第一行在下方。
2. 2.3 justify-content:
1. flex-start(默认值):左对齐
1. flex-end:右对齐
1. center: 居中
1. space-between:两端对齐,项目之间的间隔都相等。
1. space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。

1. 2.4 align-items:
1. flex-start:交叉轴的起点对齐。
1. flex-end:交叉轴的终点对齐。
1. center:交叉轴的中点对齐。
1. baseline: 项目的第一行文字的基线对齐。
1. stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。

1. 2.5 align-content:
1. 定义了多根轴线的对齐方式,如果项目只有一根轴线,那么该属性将不起作用
1. flex-start:与交叉轴的起点对齐。
1. flex-end:与交叉轴的终点对齐。
1. center:与交叉轴的中点对齐。
1. space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
1. space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
1. stretch(默认值):轴线占满整个交叉轴。

1. 结合 justify-content和align-items,看看在 flex-direction 两个不同属性值的作用下,轴心有什么不同:

1. 三、项目属性
1. 3.1 order属性

1. 3.2 flex-grow属性
1. flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
1. 如果所有项目的flex-grow属性都为1,则它们将等分剩余空间(如果有的话)。如果一个项目的flex-grow属性为2,其他项目都为1,则前者占据的剩余空间将比其他项多一倍。

1. 3.3 flex-shrink属性
1. flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。

.item { flex-shrink:
1. 如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。
1. 负值对该属性无效。
1. 3.4 align-self属性
1. align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

1. .item { align-self: auto | flex-start | flex-end | center | baseline | stretch;}
1. **弹性布局默认不改变项目的宽度,但是它默认改变项目的高度。如果项目没有显式指定高度,就将占据容器的所有高度。**
- display:table的用法
- 为什么不用table系表格元素呢?
- 1、用DIV+CSS编写出来的文件k数比用table写出来的要小,不信你在页面中放1000个table和1000个div比比看哪个文件大
- 2、table必须在页面完全加载后才显示,没有加载完毕前,table为一片空白,也就是说,需要页面完毕才显示,而div是逐行显示,不需要页面完全加载完毕,就可以一边加载一边显示
- 3、非表格内容用table来装,不符合标签语义化要求,不利于SEO
- 4、table的嵌套性太多,用DIV代码会比较简洁
- 但是有的项目中又需要类似表格的布局怎么办呢?可以用display:table来解决
- display:table系列几乎是和table系的元素相对应的,请看下表:
- 为什么不用table系表格元素呢?

1. 目前display:table的应用场景也是比较广泛的,Google地图在搜索路线时,左侧的路线详情就是用的display:table来实现的。
1. 1.div模拟表格:
<!DOCTYPE html>
1. 2.**让块级标签实现行内效果,即浮动至同一横轴,并实现等高效果**
1. table表格中的单元格最大的特点之一就是同一行列表元素都等高。所以,很多时候,我们需要等高布局的时候,就可以借助display:table-cell属性。说到table-cell的布局,不得不说一下“匿名表格元素创建规则”:
1. CSS2.1表格模型中的元素,可能不会全部包含在除HTML之外的文档语言中。这时,那些“丢失”的元素会被模拟出来,从而使得表格模型能够正常工作。所有的表格元素将会自动在自身周围生成所需的匿名table对象,使其符合table/inline-table、table-row、table- cell的三层嵌套关系。
1. 举个例子吧,如果我们为元素使用“display:table-cell;”属性,而不将其父容器设置为“display:table-row;”属性,浏览器会默认创建出一个表格行,就好像文档中真的存在一个被声明的表格行一样。
<!DOCTYPE html>
1. 上例中div.row可以不要,效果一样
1. 3.结合vetical-align实现块级元素垂直居中
- Grid 网格布局
- 一、概述
- 网格布局(Grid)是最强大的 CSS 布局方案。
- 它将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局。以前,只能通过复杂的 CSS 框架达到的效果,现在浏览器内置了。
- 一、概述

1. 上图这样的布局,就是 Grid 布局的拿手好戏。
1. Grid 布局与 [Flex 布局](https://www.ruanyifeng.com/blog/2015/07/flex-grammar.html)有一定的相似性,都可以指定容器内部多个项目的位置。但是,它们也存在重大区别。
1. Flex 布局是轴线布局,只能指定"项目"针对轴线的位置,可以看作是**一维布局**。Grid 布局则是将容器划分成"行"和"列",产生单元格,然后指定"项目所在"的单元格,可以看作是**二维布局**。Grid 布局远比 Flex 布局强大。
1. 二、基本概念
1. 学习 Grid 布局之前,需要了解一些基本概念。
1. 2.1 容器和项目
1. 采用网格布局的区域,称为"容器"(container)。容器内部采用网格定位的子元素,称为"项目"(item)。
1
2
3
1. 上面代码中,最外层的<div>元素就是容器,内层的三个<div>元素就是项目。
1. 注意:项目只能是容器的顶层子元素,不包含项目的子元素,比如上面代码的<p>元素就不是项目。Grid 布局只对项目生效。
1. 2.2 行和列
1. 容器里面的水平区域称为"行"(row),垂直区域称为"列"(column)。

1. 上图中,水平的深色区域就是"行",垂直的深色区域就是"列"。
1. 2.3 单元格
1. 行和列的交叉区域,称为"单元格"(cell)。
1. 正常情况下,n行和m列会产生n x m个单元格。比如,3行3列会产生9个单元格。
2. 2.4 网格线
1. 划分网格的线,称为"网格线"(grid line)。水平网格线划分出行,垂直网格线划分出列。
1. 正常情况下,n行有n + 1根水平网格线,m列有m + 1根垂直网格线,比如三行就有四根水平网格线。

1. 上图是一个 4 x 4 的网格,共有5根水平网格线和5根垂直网格线。
1. 三、容器属性
1. Grid 布局的属性分成两类。一类定义在容器上面,称为容器属性;另一类定义在项目上面,称为项目属性。这部分先介绍容器属性。
1. 3.1 display 属性
1. display: grid指定一个容器采用网格布局。

div { display: grid; }
1. 上图是display: grid的[效果](https://jsbin.com/guvivum/edit?html,css,output)。
1. 默认情况下,容器元素都是块级元素,但也可以设成行内元素。
div { display: inline-grid; }
1. 上面代码指定div是一个行内元素,该元素内部采用网格布局。

1. 上图是display: inline-grid的[效果](https://jsbin.com/qatitav/edit?html,css,output)。
1. 注意,设为网格布局以后,容器子元素(项目)的float、display: inline-block、display: table-cell、vertical-align和column-*等设置都将失效。
1. 3.2 grid-template-columns 属性 , grid-template-rows 属性
1. 容器指定了网格布局以后,接着就要划分行和列。grid-template-columns属性定义每一列的列宽,grid-template-rows属性定义每一行的行高。
.container { display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px 100px; }
1. [上面代码](https://jsbin.com/qiginur/edit?css,output)指定了一个三行三列的网格,列宽和行高都是100px。

1. 除了使用绝对单位,也可以使用百分比。
.container { display: grid; grid-template-columns: 33.33% 33.33% 33.33%; grid-template-rows: 33.33% 33.33% 33.33%; }
1. **(1)repeat()**
有时候,重复写同样的值非常麻烦,尤其网格很多时。这时,可以使用repeat()函数,简化重复的值。上面的代码用repeat()改写如下。
.container { display: grid; grid-template-columns: repeat(3, 33.33%); grid-template-rows: repeat(3, 33.33%); }
repeat()接受两个参数,第一个参数是重复的次数(上例是3),第二个参数是所要重复的值。
repeat()重复某种模式也是可以的。
grid-template-columns: repeat(2, 100px 20px 80px);
上面代码定义了6列,第一列和第四列的宽度为100px,第二列和第五列为20px,第三列和第六列为80px。
1. **(2)auto-fill 关键字**
有时,单元格的大小是固定的,但是容器的大小不确定。如果希望每一行(或每一列)容纳尽可能多的单元格,这时可以使用auto-fill关键字表示自动填充。
.container { display: grid; grid-template-columns: repeat(auto-fill, 100px); }
上面代码表示每列宽度100px,然后自动填充,直到容器不能放置更多的列。
1. **(3)fr 关键字**
为了方便表示比例关系,网格布局提供了fr关键字(fraction 的缩写,意为”片段”)。如果两列的宽度分别为1fr和2fr,就表示后者是前者的两倍。
.container { display: grid; grid-template-columns: 1fr 1fr; }
上面代码表示两个相同宽度的列。
fr可以与绝对长度的单位结合使用,这时会非常方便。
.container { display: grid; grid-template-columns: 150px 1fr 2fr; }
上面代码表示,第一列的宽度为150像素,第二列的宽度是第三列的一半。
1. **(4)minmax()**
minmax()函数产生一个长度范围,表示长度就在这个范围之中。它接受两个参数,分别为最小值和最大值。
grid-template-columns: 1fr 1fr minmax(100px, 1fr);
上面代码中,minmax(100px, 1fr)表示列宽不小于100px,不大于1fr。
1. **(5)auto 关键字**
auto关键字表示由浏览器自己决定长度。
grid-template-columns: 100px auto 100px;
上面代码中,第二列的宽度,基本上等于该列单元格的最大宽度,除非单元格内容设置了min-width,且这个值大于最大宽度。
1. **(6)网格线的名称**
grid-template-columns属性和grid-template-rows属性里面,还可以使用方括号,指定每一根网格线的名字,方便以后的引用。
.container { display: grid; grid-template-columns: [c1] 100px [c2] 100px [c3] auto [c4]; grid-template-rows: [r1] 100px [r2] 100px [r3] auto [r4]; }
上面代码指定网格布局为3行 x 3列,因此有4根垂直网格线和4根水平网格线。方括号里面依次是这八根线的名字。
网格布局允许同一根线有多个名字,比如[fifth-line row-5]。
1. **(7)布局实例**
grid-template-columns属性对于网页布局非常有用。两栏式布局只需要一行代码。
.wrapper { display: grid; grid-template-columns: 70% 30%; }
上面代码将左边栏设为70%,右边栏设为30%。
传统的十二网格布局,写起来也很容易。
grid-template-columns: repeat(12, 1fr);
1. 3.3 grid-row-gap 属性 , grid-column-gap 属性, grid-gap 属性
1. grid-row-gap属性设置行与行的间隔(行间距),grid-column-gap属性设置列与列的间隔(列间距)。
.container { grid-row-gap: 20px; grid-column-gap: 20px; }
1. [上面代码](https://jsbin.com/mezufab/edit?css,output)中,grid-row-gap用于设置行间距,grid-column-gap用于设置列间距。

1. grid-gap属性是grid-column-gap和grid-row-gap的合并简写形式,语法如下。
grid-gap:
1. 因此,上面一段 CSS 代码等同于下面的代码。
.container { grid-gap: 20px 20px; }
1. 如果grid-gap省略了第二个值,浏览器认为第二个值等于第一个值。
1. 根据最新标准,上面三个属性名的grid-前缀已经删除,grid-column-gap和grid-row-gap写成column-gap和row-gap,grid-gap写成gap。
1. 3.4 grid-template-areas 属性
1. 网格布局允许指定"区域"(area),一个区域由单个或多个单元格组成。grid-template-areas属性用于定义区域。
.container { display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px 100px; grid-template-areas: ‘a b c’ ‘d e f’ ‘g h i’; }
1. 上面代码先划分出9个单元格,然后将其定名为a到i的九个区域,分别对应这九个单元格。
1. 多个单元格合并成一个区域的写法如下。
grid-template-areas: ‘a a a’ ‘b b b’ ‘c c c’;
1. 上面代码将9个单元格分成a、b、c三个区域。
1. 下面是一个布局实例。
grid-template-areas: “header header header” “main main sidebar” “footer footer footer”;
1. 上面代码中,顶部是页眉区域header,底部是页脚区域footer,中间部分则为main和sidebar。
1. 如果某些区域不需要利用,则使用"点"(.)表示。
grid-template-areas: ‘a . c’ ‘d . f’ ‘g . i’;
1. 上面代码中,中间一列为点,表示没有用到该单元格,或者该单元格不属于任何区域。
1. 注意,区域的命名会影响到网格线。每个区域的起始网格线,会自动命名为区域名-start,终止网格线自动命名为区域名-end。
1. 比如,区域名为header,则起始位置的水平网格线和垂直网格线叫做header-start,终止位置的水平网格线和垂直网格线叫做header-end。
1. 3.5 grid-auto-flow 属性
1. 划分网格以后,容器的子元素会按照顺序,自动放置在每一个网格。默认的放置顺序是"先行后列",即先填满第一行,再开始放入第二行,即下图数字的顺序。

1. ·这个顺序由grid-auto-flow属性决定,默认值是row,即"先行后列"。也可以将它设成column,变成"先列后行"。
grid-auto-flow: column;
1. [上面代码](https://jsbin.com/xutokec/edit?css,output)设置了column以后,放置顺序就变成了下图。

1. grid-auto-flow属性除了设置成row和column,还可以设成row dense和column dense。这两个值主要用于,某些项目指定位置以后,剩下的项目怎么自动放置。
1. [下面的例子](https://jsbin.com/wapejok/edit?css,output)让1号项目和2号项目各占据两个单元格,然后在默认的grid-auto-flow: row情况下,会产生下面这样的布局。

1. 上图中,1号项目后面的位置是空的,这是因为3号项目默认跟着2号项目,所以会排在2号项目后面。
1. 现在修改设置,设为row dense,表示"先行后列",并且尽可能紧密填满,尽量不出现空格。
grid-auto-flow: row dense;
1. [上面代码](https://jsbin.com/helewuy/edit?css,output)的效果如下。

1. 上图会先填满第一行,再填满第二行,所以3号项目就会紧跟在1号项目的后面。8号项目和9号项目就会排到第四行。
1. 如果将设置改为column dense,表示"先列后行",并且尽量填满空格。
grid-auto-flow: column dense;
1. [上面代码](https://jsbin.com/pupoduc/1/edit?html,css,output)的效果如下。

1. 上图会先填满第一列,再填满第2列,所以3号项目在第一列,4号项目在第二列。8号项目和9号项目被挤到了第四列。
1. 3.6 justify-items 属性, align-items 属性,place-items 属性
1. justify-items属性设置单元格内容的水平位置(左中右),align-items属性设置单元格内容的垂直位置(上中下)。
.container { justify-items: start | end | center | stretch; align-items: start | end | center | stretch; }
1. 这两个属性的写法完全相同,都可以取下面这些值。
start:对齐单元格的起始边缘。
end:对齐单元格的结束边缘。
center:单元格内部居中。
stretch:拉伸,占满单元格的整个宽度(默认值)。
1. .container { justify-items: start;}
1. [上面代码](https://jsbin.com/gijeqej/edit?css,output)表示,单元格的内容左对齐,效果如下图。

1. .container { align-items: start;}
1. [上面代码](https://jsbin.com/tecawur/edit?css,output)表示,单元格的内容头部对齐,效果如下图。

1. place-items属性是align-items属性和justify-items属性的合并简写形式。
place-items:
1. 下面是一个例子。
place-items: start end;
1. 如果省略第二个值,则浏览器认为与第一个值相等。
1. 3.7 justify-content 属性,align-content 属性,place-content 属性
1. justify-content属性是整个内容区域在容器里面的水平位置(左中右),align-content属性是整个内容区域的垂直位置(上中下)。
.container { justify-content: start | end | center | stretch | space-around | space-between | space-evenly; align-content: start | end | center | stretch | space-around | space-between | space-evenly; }
1. 这两个属性的写法完全相同,都可以取下面这些值。(下面的图都以justify-content属性为例,align-content属性的图完全一样,只是将水平方向改成垂直方向。)
1. start - 对齐容器的起始边框。

1. end - 对齐容器的结束边框。

1. center - 容器内部居中。

1. stretch - 项目大小没有指定时,拉伸占据整个网格容器。

1. space-around - 每个项目两侧的间隔相等。所以,项目之间的间隔比项目与容器边框的间隔大一倍。

1. space-between - 项目与项目的间隔相等,项目与容器边框之间没有间隔。

1. space-evenly - 项目与项目的间隔相等,项目与容器边框之间也是同样长度的间隔。

1. place-content属性是align-content属性和justify-content属性的合并简写形式。
place-content:
1. 下面是一个例子。
place-content: space-around space-evenly;
1. 如果省略第二个值,浏览器就会假定第二个值等于第一个值。
1. 3.8 grid-auto-columns 属性,grid-auto-rows 属性
1. 有时候,一些项目的指定位置,在现有网格的外部。比如网格只有3列,但是某一个项目指定在第5行。这时,浏览器会自动生成多余的网格,以便放置项目。
1. grid-auto-columns属性和grid-auto-rows属性用来设置,浏览器自动创建的多余网格的列宽和行高。它们的写法与grid-template-columns和grid-template-rows完全相同。如果不指定这两个属性,浏览器完全根据单元格内容的大小,决定新增网格的列宽和行高。
1. [下面的例子](https://jsbin.com/sayuric/edit?css,output)里面,划分好的网格是3行 x 3列,但是,8号项目指定在第4行,9号项目指定在第5行。
.container { display: grid; grid-template-columns: 100px 100px 100px; grid-template-rows: 100px 100px 100px; grid-auto-rows: 50px; }
1. 上面代码指定新增的行高统一为50px(原始的行高为100px)。

1. 3.9 grid-template 属性, grid 属性
1. grid-template属性是grid-template-columns、grid-template-rows和grid-template-areas这三个属性的合并简写形式。
1. grid属性是grid-template-rows、grid-template-columns、grid-template-areas、 grid-auto-rows、grid-auto-columns、grid-auto-flow这六个属性的合并简写形式。
1. 从易读易写的角度考虑,还是建议不要合并属性,所以这里就不详细介绍这两个属性了。
1. 四、项目属性
1. 下面这些属性定义在项目上面。
1. 4.1 grid-column-start 属性,grid-column-end 属性,grid-row-start 属性,grid-row-end 属性
1. 项目的位置是可以指定的,具体方法就是指定项目的四个边框,分别定位在哪根网格线。
grid-column-start属性:左边框所在的垂直网格线
grid-column-end属性:右边框所在的垂直网格线
grid-row-start属性:上边框所在的水平网格线
grid-row-end属性:下边框所在的水平网格线
1. .item-1 { grid-column-start: 2; grid-column-end: 4;}
1. [上面代码](https://jsbin.com/yukobuf/edit?css,output)指定,1号项目的左边框是第二根垂直网格线,右边框是第四根垂直网格线。

1. 上图中,只指定了1号项目的左右边框,没有指定上下边框,所以会采用默认位置,即上边框是第一根水平网格线,下边框是第二根水平网格线。
1. 除了1号项目以外,其他项目都没有指定位置,由浏览器自动布局,这时它们的位置由容器的grid-auto-flow属性决定,这个属性的默认值是row,因此会"先行后列"进行排列。读者可以把这个属性的值分别改成column、row dense和column dense,看看其他项目的位置发生了怎样的变化。
1. [下面的例子](https://jsbin.com/nagobey/edit?html,css,output)是指定四个边框位置的效果。

.item-1 { grid-column-start: 1; grid-column-end: 3; grid-row-start: 2; grid-row-end: 4; }
1. 这四个属性的值,除了指定为第几个网格线,还可以指定为网格线的名字。
.item-1 { grid-column-start: header-start; grid-column-end: header-end; }
1. 上面代码中,左边框和右边框的位置,都指定为网格线的名字。
1. 这四个属性的值还可以使用span关键字,表示"跨越",即左右边框(上下边框)之间跨越多少个网格。
.item-1 { grid-column-start: span 2;}
1. [上面代码](https://jsbin.com/hehumay/edit?html,css,output)表示,1号项目的左边框距离右边框跨越2个网格。

1. 这与[下面的代码](https://jsbin.com/mujihib/edit?html,css,output)效果完全一样。
.item-1 { grid-column-end: span 2;}
1. 使用这四个属性,如果产生了项目的重叠,则使用z-index属性指定项目的重叠顺序。
1. 4.2 grid-column 属性, grid-row 属性
1. grid-column属性是grid-column-start和grid-column-end的合并简写形式,grid-row属性是grid-row-start属性和grid-row-end的合并简写形式。
.item { grid-column:
1. 下面是一个例子。
.item-1 { grid-column: 1 / 3; grid-row: 1 / 2; } / 等同于 / .item-1 { grid-column-start: 1; grid-column-end: 3; grid-row-start: 1; grid-row-end: 2; }
1. 上面代码中,项目item-1占据第一行,从第一根列线到第三根列线。
1. 这两个属性之中,也可以使用span关键字,表示跨越多少个网格。
.item-1 { background: #b03532; grid-column: 1 / 3; grid-row: 1 / 3; } / 等同于 / .item-1 { background: #b03532; grid-column: 1 / span 2; grid-row: 1 / span 2; }
1. [上面代码](https://jsbin.com/volugow/edit?html,css,output)中,项目item-1占据的区域,包括第一行 + 第二行、第一列 + 第二列。

1. 斜杠以及后面的部分可以省略,默认跨越一个网格。
.item-1 { grid-column: 1; grid-row: 1;}
1. 上面代码中,项目item-1占据左上角第一个网格。
1. 4.3 grid-area 属性
1. grid-area属性指定项目放在哪一个区域。
1. .item-1 { grid-area: e;}
1. [上面代码](https://jsbin.com/qokexob/edit?css,output)中,1号项目位于e区域,效果如下图。

1. grid-area属性还可用作grid-row-start、grid-column-start、grid-row-end、grid-column-end的合并简写形式,直接指定项目的位置。
.item { grid-area:
1. 下面是一个[例子](https://jsbin.com/duyafez/edit?css,output)。
.item-1 { grid-area: 1 / 1 / 3 / 3;}
1. 4.4 justify-self 属性, align-self 属性,place-self 属性
1. justify-self属性设置单元格内容的水平位置(左中右),跟justify-items属性的用法完全一致,但只作用于单个项目。
1. align-self属性设置单元格内容的垂直位置(上中下),跟align-items属性的用法完全一致,也是只作用于单个项目。
1. .item { justify-self: start | end | center | stretch; align-self: start | end | center | stretch;}
1. 这两个属性都可以取下面四个值。
start:对齐单元格的起始边缘。
end:对齐单元格的结束边缘。
center:单元格内部居中。
stretch:拉伸,占满单元格的整个宽度(默认值)。
1. 下面是justify-self: start的例子。

.item-1 { justify-self: start;}
1. place-self属性是align-self属性和justify-self属性的合并简写形式。
place-self:
1. 下面是一个例子。
place-self: center center;
1. 如果省略第二个值,place-self属性会认为这两个值相等。
- 常规流
- 常规流布局
- 常规流、文档流、普通文档流、常规文档流
- 所有元素,默认情况下,都属于常规流布局
- 总体规则:块盒独占一行,行盒水平依次排列
- 包含块(containing block):每个盒子都有它的包含块,包含块决定了盒子的排列区域。
- 绝大部分情况下:盒子的包含块,为其父元素的内容盒
- 块盒
- 【水平方向】每个块盒的总宽度,必须刚好等于包含块的宽度
- 宽度的默认值是 auto
- margin 的取值也可以是 auto,默认值 0
- auto:将剩余空间吸收掉
- width 吸收能力强于 margin
- 若宽度、边框、内边距、外边距计算后,仍然有剩余空间,该剩余空间被 margin-right 全部吸收
- 在常规流中,块盒在其包含块中居中,可以定宽、然后左右 margin 设置为 auto。
- 每个块盒垂直方向上的 auto 值
- height:auto, 适应内容的高度
- margin:auto, 表示 0
- 百分比取值
- padding、宽、margin 可以取值为百分比
- 以上的所有百分比相对于包含块的宽度。
- 高度的百分比:
- 【水平方向】每个块盒的总宽度,必须刚好等于包含块的宽度
- 常规流布局
1)包含块的高度(是否)取决于子元素的高度,设置百分比无效
2)包含块的高度不取决于子元素的高度,百分比相对于父元素高度
1. 上下外边距的合并
1. 两个常规流块盒,上下外边距相邻,会进行合并。
1. 两个外边距取最大值。
1. 块级格式化上下文
1. 为使 overflow有效果,块级容器必须有一个指定的高度(height或者max-height)或者将white-space设置为nowrap。
- 浮动
- 浮动的基本特点
- 修改 float 属性值为:
- left:左浮动,元素靠上靠左
- right:右浮动,元素靠上靠右
- 默认值为 none
- 当一个元素浮动后,元素必定为块盒(更改 display 属性为 block)
- 浮动元素的包含块,和常规流一样,为父元素的内容盒
- 修改 float 属性值为:
- 盒子尺寸
- 宽度为 auto 时,适应内容宽度
- 高度为 auto 时,与常规流一致,适应内容的高度
- margin 为 auto,为 0.
- 边框、内边距、百分比设置与常规流一样
- 盒子排列
- 左浮动的盒子靠上靠左排列
- 右浮动的盒子考上靠右排列
- 1)【浮动盒子和常规流盒子】在包含块中排列时,常规流盒子放在前面,浮动盒子会避开常规流块盒(常规流在上)
- 2)常规流块盒放在后面,无视浮动盒子(常规流盒子在浮动盒子底部)
- 行盒在排列时,会避开浮动盒子
- 外边距合并不会发生
- 如果文字没有在行盒中,浏览器会自动生成一个行盒包裹文字,该行盒叫做匿名行盒。
- 高度坍塌
- 高度坍塌的根源:常规流盒子的自动高度,在计算时,不会考虑浮动盒子
- 清除浮动,涉及 css 属性:clear
- 默认值:none
- left:清除左浮动,该元素必须出现在前面所有左浮动盒子的下方
- right:清除右浮动,该元素必须出现在前面所有右浮动盒子的下方
- both:清除左右浮动,该元素必须出现在前面所有浮动盒子的下方
- 浮动(多个)排列原理:
- 浮动的基本特点
- 定位
- 定位:position
- 定位:手动控制元素在包含块中的精准位置
- 涉及的 CSS 属性:position
- position 属性
- 默认值:static,静态定位(不定位)
- relative:相对定位
- absolute:绝对定位
- fixed:固定定位
- 一个元素,只要 position 的取值不是 static,认为该元素是一个定位元素。
- 定位元素会脱离文档流(相对定位除外)
- 一个脱离了文档流的元素:
- 文档流中的元素摆放时,会忽略脱离了文档流的元素
- 文档流中元素计算自动高度时,会忽略脱离了文档流的元素
- 相对定位 (relative)
- 不会导致元素脱离文档流,只是让元素在原来位置上进行偏移。
- 可以通过四个 CSS 属性对设置其位置:
- left
- right
- top
- bottom
- 盒子的偏移不会对其他盒子造成任何影响。
- 绝对定位(absolute)
- 宽高为 auto,适应内容
- 包含块变化:找祖先中第一个定位元素,该元素的填充盒为其包含块。若找不到,则它的包含块为整个网页(初始化包含块)
- 固定定位(fixed)
- 其他情况和绝对定位完全一样。
- 包含块不同:固定为视口(浏览器的可视窗口)
- 定位下的居中
- 某个方向居中:
- 定宽(高)
- 将左右(上下)距离设置为 0
- 将左右(上下)margin 设置为 auto
- 绝对定位和固定定位中,margin 为 auto 时,会自动吸收剩余空间
- 某个方向居中:
- 多个定位元素重叠时
- 堆叠上下文
- 设置 z-index,通常情况下,该值越大,越靠近用户
- 只有定位元素设置 z-index 有效
- z-index 可以是负数,如果是负数,则遇到常规流、浮动元素,则会被其覆盖
- 补充
- 绝对定位、固定定位元素一定是块盒
- 绝对定位、固定定位元素一定不是浮动
- 没有外边距合并
- 定位:position
- 浮动、定位、弹性、table、Grid 网格布局 :优缺点
- 浮动
- 优点:兼容性好
- 缺点:清除浮动
- 绝对定位
- 优点:快捷
- 缺点:可用性差,脱离文档流
- 弹性
- 优点:
- 缺点:
- table
- 优点:兼容性好
- 缺点:单元格超出,每一个都必须调整
- Grid 网格布局
- 优点:代码量简化
- 缺点:
- 浮动
- 页面布局的变通
- 三栏布局
- 左右宽度固定,中间自适应
- 上下高度固定,中间自适应
- 两栏布局
- 左宽度固定,右自适应
- 右宽度固定,左自适应
- 上高度固定,下自适应
- 下高度固定,上自适应
- 三栏布局
- CSS的position定位
- css定位position值分别为:static, relative,absolute,fixed
- relative(相对定位):相对定位的偏移参考元素是元素本身,不会使元素脱离文档流。元素的初始位置占据的空间会被保留。相对定位元素常常作为绝对定位元素的父元素。并且定位元素经常与z-index属性进行层次分级
- 代码实例:
- css定位position值分别为:static, relative,absolute,fixed
<!DOCTYPE html>
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
1. 效果图:

1. 尽管rel元素产生了偏移,但是文字并没有填补它的原来的位置,可以看出相对定位元素没有脱离文档流,原来的位置依然会被保留。
1. 2. absolute(绝对定位)绝对定位元素以父辈元素中最近的定位元素为参考坐标,如果绝对定位元素的父辈元素中没有采用定位的,那么此绝对定位元素的参考对象是html,元素会脱离文档流。就好像文档流中被删除了一样。并且定位元素经常与z-index属性进行层次分级
1. 代码实例:
<!DOCTYPE html>
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
1. 效果图:

1. 在此辟谣一下哈!如果绝对定位元素的父辈元素中没有采用定位的,那么此绝对定位元素的参考对象是谁呢,有的人说是body,有的人会说是document,其实都不是,看了MDN上的介绍,以initial containing block为参考,它的尺寸是和视口是一致的,但不是由Viewport所产生的,而是由根元素<html>所产生的。
1. 代码实例:
<!DOCTYPE html>
1. 实例效果图:

1. 如果参考对象是body或者document的话,div元素肯定要位于页面的最底部,注意到这里有滚动条,元素只是位于视口的最底部。
1. 3. fixed (固定定位)位移的参考坐标是可视窗口,使用fixed的元素脱离文档流。并且定位元素经常与z-index属性进行层次分级
1. 实例代码:
<!DOCTYPE html>
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
阿什顿发斯蒂芬阿斯蒂芬阿斯蒂芬阿斯蒂芬
1. 实例效果图:

1. fixed固定定位和absolute绝对定位比较类似,它们都能够让元素产生位移上面演示了固定定位;如果到目前为止还没有看到与绝对定位的区别,那么我们可以在文中多加些文字是浏览器产生滚动条,拖动滚动条就可以看到两个定位方式的区别,固定定位的元素如其名一样,能够固定在某个位置。而绝对定位就会随着滚动条滚动而移动位置。
1. 4.static (静态定位)默认值,元素框正常生成的,top left bottom right这几个偏移属性不会影响其静态定位的正常显示
1. 不常用的四种
1. 1.inherit
1. 规定应该从父元素继承 position 属性的值
1. inherit 关键字可用于任何 HTML 元素上的任何 CSS 属性
1. 兼容:ie7及以下版本不支持此属性
2. 2.initial
1. 设置positon的值为默认值(static)
1. 兼容:ie不支持此属性
1. 问:有了static为什么还会存在此属性,不是多此一举?
1. 答:initial 关键字可用于任何 HTML 元素上的任何 CSS 属性,不是postion特有的
3. 3.unset
1. 设置positon的值为不设置:
1. 如果该属性的默认属性是 继承属性(例如字体相关的默认属性基本都是继承),该值等同于 inherit
1. 如果该属性的默认属性 不是继承属性(例如pisition的默认属性为static),该值等同于 initial
1. 兼容:ie不支持此属性
4. 4.sticky
1. css3新属性,它的表现就像position:relative和position:fixed的合体:
1.在目标区域在屏幕中可见时,它的行为就像position:relative;
2.页面滚动时
当父元素是body时
a.滚动距离小于屏幕高度或宽度,它会固定在目标位置
b.滚动距离大于屏幕高度或宽度,它的表现就像position:relative和1一样
当父元素不是body,在父元素高度内滚动时它会固定在目标位置,就像fixed
1. 在父元素滚动为不可视时它的表现就像position:relative和1一样
1. 兼容: ie不兼容、google不完全兼容(thead、tr标签不支持)、firefox59以后兼容,之前版本不完全兼容(table标签不支持)
1. HTML5高级之position(定位)
1. position 属性规定元素的定位类型,定义建立元素布局所用的定位机制。任何元素都可以定位,不过绝对或固定元素会生成一个块级框,而不论该元素本身是什么类型。相对定位元素会相对于它在正常流中的默认位置偏移。
1. position一般分为三种,一种是相对定位relative,一种是绝对定位absolute,一种是固定定位fixed,接下来分别说明这三个属性的用法以及相对应的特性,并举例说明。
1. 1、position:relative 相对定位
特点:
1)不影响元素本身的特性;
2)不使元素脱离文档流(元素移动之后原始位置会被保留);
3)如果没有定位偏移量,对元素本身没有任何影响;
4)提升层级。
注:定位元素位置控制:top/right/bottom/left 定位元素偏移量
1. 2、position:absolute 绝对定位
特点:
1)使元素完全脱离文档流;
2)使内嵌支持宽高;
3)块属性标签内容撑开宽度;
4)如果有定位父级相对于定位父级发生偏移,没有定位父级相对于document发生偏移;
5)相对定位一般都是配合绝对定位元素使用;
6)提升层级
注意:
z-index:[number]; 定位层级
a、定位元素默认后者层级高于前者;
b、建议在兄弟标签之间比较层级
接下来用一个例子进行使用说明。
例子要求:如何将左边的三个div变成右边的三个div布局,即将div2的位置移动到如图的位置。
代码如下:
<!DOCTYPE html>
这里做几点解释:
1)为什么body需要添加position:relative?
因为position:absolute这个属性会根据父级进行定位,如果没有定位父级则会相对于document发生偏移。而body在chrome浏览器中带有默认的样式,即带有margin属性,所以需要给body定义定位,这样后面的div就会根据body进行定位。
2)为什么div3中也需要添加position:absolute?
因为div2中添加属性position:absolute之后,就直接完全脱离文档流,那么div3的位置就会往上移动,为了实现效果,也需要在div3中添加同样的属性。
1. 3、position:fixed 固定定位
与绝对定位的特性基本一致,唯一的差别是始终相对整个文档进行定位;
问题:IE6不支持固定定位;
1. 4、其他定位
position:static ; 默认值
position:inherit ; 从父元素继承定位属性的值 (不兼容)、
1. 5、综合例子说明
做一个类似的弹窗效果。
代码:
我用了两种方法实现,一种就是上面总结的,都是利用div和position定位实现的,一种就是直接利用box-shadow属性实现的.
1)利用div和position定位实现
<!DOCTYPE html>
其中用到了三个比较重要的属性:
a】position 定位
b】z-index 定位层级
c】opacity 透明度
标准 不透明度: opacity:0~1;
IE 滤镜: filter:alpha(opacity=0~100);
2)利用box-shadow属性实现
box-shadow 向框添加一个或多个阴影。该属性是由逗号分隔的阴影列表,每个阴影由 2-4 个长度值、可选的颜色值以及可选的 inset 关键词来规定。省略长度的值是 0。
<!DOCTYPE html>
1. css粘性定位position:sticky问题采坑
1. 前言:
1. position:sticky是css定位新增属性;可以说是相对定位relative和固定定位fixed的结合;它主要用在对scroll事件的监听上;简单来说,在滑动过程中,某个元素距离其父元素的距离达到sticky粘性定位的要求时(比如top:100px);position:sticky这时的效果相当于fixed定位,固定到适当位置。
2. **使用:**
sticky-nav { position : sticky ; top : 100px ; }
1. 设置position:sticky同时给一个(top,bottom,right,left)之一即可
1. 使用条件:
1、父元素不能overflow:hidden或者overflow:auto属性。
2、必须指定top、bottom、left、right4个值之一,否则只会处于相对定位
3、父元素的高度不能低于sticky元素的高度
4、sticky元素仅在其父元素内生效
1. 例子:
1. css代码:
{ margin: 0; padding: 0 } html body { height: 100vh; width: 100% } h1 { height: 200px; position: relative; background-color: lightblue; } h1:after { content: ‘’; position: absolute; top: 100px; left: 0; width: 100%; height: 2px; background-color: red; } #sticky-nav { position: sticky; /position: absolute; left: 0;*/ top: 100px; width: 100%; height: 80px; background-color: yellowgreen; } .scroll-container { height: 600px; width: 100%; background-color: lightgrey; }
1. html代码:
高200px;距顶部100px
发生滚动
发生滚动
1. 项目中遇到的坑:
先来看看各大内核对position:sticky的支持情况
1. 问题描述:
在一个小程序开发项目中;tabs组件使用了粘性定位,其中有tab栏的切换;tab栏底部是大段列表内容list-container内容的展示;其中展示内容有click事件(或者说是touch事件);ios以及pc浏览器中对点击的测试是正常的;但在安卓手机中!!!!我的天,点击穿透了!!并且,尝试去掉list-container中的item的点击跳转,发现tab切换的点击没有了反应,事件消失了!!!
设置断点,查看事件流的走向:首先事件捕获—>目标节点tab—>事件冒泡;这个泡居然冒到了container-list中的item。。。简直噩梦
大致的项目结构:
html结构:
解决办法:
1.在使用组件库的tab时,外层套一个div,防止点击穿透和不正常的事件流走向或者(一个治标不治本的方法,具体看业务场景)
2.组件库的样式无法改,sticky作为tab组件的行内样式,因为我使用这个tab时是直接在viewpoint的顶部的,这是完全可以用fixed达到效果。我在调用类的外部设置了position:fixed !import;样式最高优先级去覆盖了组件库中的定位样式,就正常了。
一点想法:
position:sticky对安卓的兼容简直让人想哭,目前手机端的用户非常多,要做到兼顾,由于安卓系统对sticky粘性定位的惨淡支持;如果业务场景可以用其它定位解决,那就还是不要用sticky吧。。。。留下心酸的泪水。。。。
- 布局
- 圣杯布局、双飞翼布局、Flex布局和绝对定位布局
- 圣杯布局与双飞翼布局针对的都是三列左右栏固定中间栏边框自适应的网页布局(想象一下圣杯是主体是加上两个耳朵;鸟儿是身体加上一对翅膀)
- 圣杯布局的出现是来自由 Matthew Levine 在 2006 年写的一篇文章 《In Search of the Holy Grail》。 比起双飞翼布局,它的起源不是源于对页面的形象表达。在西方,圣杯是表达“渴求之物”的意思。
- 而双飞翼布局则是源于淘宝的UED,可以说是灵感来自于页面渲染。
- 布局要求有几点:
- 三列布局,中间宽度自适应,两边定宽;
- 中间栏要在浏览器中优先展示渲染;
- 允许任意列的高度最高;
- 可以看出我们题目的要求跟圣杯布局和双飞翼布局要求一样。
- 圣杯布局
- 效果图
- 圣杯布局、双飞翼布局、Flex布局和绝对定位布局

缩放页面可以发现随着页面的宽度的变化,这三栏布局是中间盒子优先渲染,两边的盒子框子宽度固定不变,即使页面宽度变小,也不影响我们的浏览。注意:为了安全起见,最好还是给body加一个最小宽度!
1. 圣杯布局要求
header和footer各自占领屏幕所有宽度,高度固定。
中间的container是一个三栏布局。
三栏布局两侧宽度固定不变,中间部分自动填充整个区域。
中间部分的高度是三栏中最高的区域的高度。
1. 圣杯布局的三种实现
【1】浮动
<!DOCTYPE html>
先定义好header和footer的样式,使之横向撑满。
在container中的三列设为浮动和相对定位(后面会用到),center要放在最前面,footer清除浮动。
三列的左右两列分别定宽200px和150px,中间部分center设置100%撑满
这样因为浮动的关系,center会占据整个container,左右两块区域被挤下去了
接下来设置left的 margin-left: -100%;,让left回到上一行最左侧
但这会把center给遮住了,所以这时给外层的container设置 padding-left: 200px;padding-right: 150px;,给left和right空出位置
这时left并没有在最左侧,因为之前已经设置过相对定位,所以通过 left: -200px; 把left拉回最左侧
同样的,对于right区域,设置 margin-left: -150px; 把right拉回第一行
这时右侧空出了150px的空间,所以最后设置 right: -150px;把right区域拉到最右侧就行了。
【2】flex弹性盒子
<!DOCTYPE html>
header和footer设置样式,横向撑满。
container中的left、center、right依次排布即可
给container设置弹性布局 display: flex;
left和right区域定宽,center设置 flex: 1; 即可
【3】grid布局

<!DOCTYPE html>
如上图所示,我们把body划分成三行四列的网格,其中有5条列网格线
给body元素添加display: grid;属性变成一个grid(网格)
给header元素设置grid-row: 1; 和 grid-column: 1/5; 意思是占据第一行网格的从第一条列网格线开始到第五条列网格线结束
给footer元素设置grid-row: 1; 和 grid-column: 1/5; 意思是占据第三行网格的从第一条列网格线开始到第五条列网格线结束
给left元素设置grid-row: 2; 和 grid-column: 1/2; 意思是占据第二行网格的从第一条列网格线开始到第二条列网格线结束
给center元素设置grid-row: 2; 和 grid-column: 2/4; 意思是占据第二行网格的从第二条列网格线开始到第四条列网格线结束
给right元素设置grid-row: 2; 和 grid-column: 4/5; 意思是占据第二行网格的从第四条列网格线开始到第五条列网格线结束
1. 题目要求:针对如下DOM结构,编写CSS,实现三栏水平布局,其中left、right分别位于左右两侧,left宽度为200px,right宽度为300px,main处在中间,宽度自适应。 要求:允许增加额外的DOM节点,但不能修改现有节点顺序。
方法一:圣杯布局
1.设置基本样式
/3.圣杯布局法/ .left, .main, .right { min-height: 130px; } .left { background: green; width: 200px; } .main { background-color: blue; } .right { background-color: red; width: 300px; }
为了高度保持一致给left main right都加上min-height:130px。

2.圣杯布局是一种相对布局,首先设置父元素container的位置:
.container { padding: 0 300px 0 200px; }
实现效果是左右分别空出200px和300px区域,效果如图:

3.将主体部分的三个子元素都设置左浮动
.left, .main, .right { min-height: 130px; float: left; }
出现了如下情况,怎么办,别着急慢慢来:

4.设置main宽度为width:100%,让其单独占满一行

.main { background-color: blue; width: 100%; }
5.设置left和right 负的外边距
我们的目标是让left、main、right依次并排,但是上图中left和right都是位于下一行,这里的技巧就是使用负的margin-left:
.left { margin-left: -100%; background-color: green; width: 200px; } .right { margin-left: -300px; background-color: red; width: 300px; }
负的margin-left会让元素沿文档流向左移动,如果负的数值比较大就会一直移动到上一行。关于负的margin的应用也是博大精深,这里肯定是不能详细介绍了。
设置left部分的margin-left为-100%,就会使left向左移动一整个行的宽度,由于left左边是父元素的边框,所以left继续跳到上一行左移,一直移动到上一行的开头,并覆盖了main部分(仔细观察下图,你会发现main里面的字“main”不见了,因为被left遮住了),left上移过后,right就会处于上一行的开头位置,这时再设置right部分margin-left为负的宽度,right就会左移到上一行的末尾。

6.接下来只要把left和right分别移动到这两个留白就可以了。可以使用相对定位移动 left和right部分。

.left, .main, .right { position: relative; min-height: 130px; float: left; } .left { left: -200px; margin-left: -100%; background: green; width: 200px; } .right { right: -300px; margin-left: -300px; background-color: red; width: 300px; }
至此,我们完成了三列中间自适应的布局,也就是传说中的圣杯布局。完整的代码如下:
<!DOCTYPE html>
方法二:双飞翼布局
圣杯布局和双飞翼布局解决问题的方案在前一半是相同的,也就是三栏全部float浮动,但左右两栏加上负margin让其跟中间栏div并排,以形成三栏布局。不同在于解决 “中间栏div内容不被遮挡”问题的思路不一样。
他的HTML结构发生了变化:
直接贴出代码,读者可以自行参透他们的异同:
<!DOCTYPE html>
双飞翼布局比圣杯布局多使用了1个div,少用大致4个css属性(圣杯布局container的 padding-left和padding-right这2个属性,加上左右两个div用相对布局position: relative及对应的right和left共4个属性,;而双飞翼布局子div里用margin-left和margin-right共2个属性,比圣杯布局思路更直接和简洁一点。简单说起来就是:双飞翼布局比圣杯布局多创建了一个div,但不用相对布局了。
方法三:Flex布局
Flex 是 Flexible Box 的缩写,意为”弹性布局”,用来为盒状模型提供最大的灵活性。
任何一个容器都可以指定为 Flex 布局,所以Flex 布局将成为未来布局的首选方案。
阮一峰老师的教程http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
Flex布局制作了 Demo,:http://static.vgee.cn/static/index.html
接下来讲一下此实例的具体实现:
1.首先将container块设置为一个Flex容器
.container{ display: flex; min-height: 130px; }
那么container下属的main、left和right这三个子元素自动成为容器成员,称为 Flex 项目(flex item),简称”项目”。
2.对这三个项目做初始设置
.main{ background-color: blue; } .left{ background-color: green; } .right{ background-color: red; }
项目根据内容进行弹性布局:

3.通过order属性设置排列顺序
可以看出三个项目的排序方式不一样了,main排在了第一个,要让main在中间,left在左边,可以通过Flex容器下的项目的属性“order”属性来设置:
.left{ order: -1; background-color: green; }
对于order属性:定义项目的排列顺序,越小越靠前,默认为0。

4.通过项目属性flex-grow设置main的放大比例,将空余的空间用main来填充,使三个项目不满一整行;默认为0,也就是对剩余空间不做处理。

.main{ flex-grow:1; background-color: blue; }
5.通过项目属性flex-basis 设置left和right的固定宽度
.left{ order: -1; flex-basis: 200px; background-color: green; } .right{ flex-basis: 300px; background-color: red; }
这样就实现了我们的目标,是不是很简单?这就是flex布局的魅力。。。

6.最后,完整的代码如下:
<!DOCTYPE html>
方法四:绝对定位布局
绝对定位使元素的位置与文档流无关,因此不占据空间。这一点与相对定位不同,相对定位实际上被看作普通流定位模型的一部分,因为元素的位置相对于它在普通流中的位置。
提示:因为绝对定位的框与文档流无关,所以它们可以覆盖页面上的其它元素。可以通过设置 z-index 属性来控制这些框的堆放次序。
言归正传:
<!DOCTYPE html>
实现结果当然是一样的啦!
1. 水平居中、 垂直居中、 垂直水平居中
1. 水平居中
1. 方法一:在父容器上定义固定宽度,margin值设成auto

<!DOCTYPE html>

<!DOCTYPE html>
如果不是,则先将其父元素设置为块级元素,再给父元素设置 text-align: center;
我是行内元素
效果:
1. 块级元素
方案一:(分宽度定不定两种情况)定宽度:需要谁居中,给其设置 margin: 0 auto; (作用:使盒子自己居中)
效果:

不定宽度:默认子元素的宽度和父元素一样,这时需要设置子元素为display: inline-block; 或 display: inline;即将其转换成行内块级/行内元素,给父元素设置 text-align: center;
效果:(将#son转换成行内元素,内容的高度撑起了#son的高度,设置高度无用)

方案二:使用定位属性
首先设置父元素为相对定位,再设置子元素为绝对定位,设置子元素的left:50%,即让子元素的左上角水平居中;
定宽度:设置绝对子元素的 margin-left: -元素宽度的一半px; 或者设置transform: translateX(-50%);
不定宽度:利用css3新增属性transform: translateX(-50%);
效果:

方案三:使用flexbox布局实现(宽度定不定都可以)
使用flexbox布局,只需要给待处理的块状元素的父元素添加属性 display: flex; justify-content: center;
效果:
1. 1
居中效果主要分为三大类:水平居中、垂直居中和水平垂直居中。水平居中的实现方案,大家最熟悉的莫过开给元素定一个显示式的宽度,然后加上margin的左右值为auto。如:
.center { width: 960px; margin-left: auto; margin-right: auto; }
这种方法给知道了宽度的元素设置居中是最方便不过的了,但有很多情况之下,我们是无法确定元素容器的宽度。换句话说,未有明确宽度的时候,上面的方法无法让我们实现元素水平居中。那要怎么办呢?这也就是我们今天需要讨论的问题。
为了更好的说明问题,我们来看一个制作分页效果的代码:
HTML
给分页加上样式:
.pagination li { line-height: 25px; } .pagination a { display: block; color: #f2f2f2; text-shadow: 1px 0 0 #101011; padding: 0 10px; border-radius: 2px; box-shadow: 0 1px 0 #5a5b5c inset,0 1px 0 #080808; background: linear-gradient(top,#434345,#2f3032); } .pagination a:hover { text-decoration: none; box-shadow: 0 1px 0 #f9bd71 inset,0 1px 0 #0a0a0a; background: linear-gradient(top,#f48b03,#c87100); }
这是一个极普通的样式代码,初步的效果:

这很显然不是我们需要的效果,接下来我们分几种方案来制作:
一、margin和width实现水平居中
第一种方法是最古老的实现方案,也是大家最常见的方案,在分页容器上定义一个宽度,然后配合margin的左右值为“auto”实现效果:
.pagination { width: 293px; margin-left: auto; margin-right: auto; } .pagination li { line-height: 25px; display: inline; float: left; margin: 0 5px; } .pagination a { display: block; color: #f2f2f2; text-shadow: 1px 0 0 #101011; padding: 0 10px; border-radius: 2px; box-shadow: 0 1px 0 #5a5b5c inset,0 1px 0 #080808; background: linear-gradient(top,#434345,#2f3032); } .pagination a:hover { text-decoration: none; box-shadow: 0 1px 0 #f9bd71 inset,0 1px 0 #0a0a0a; background: linear-gradient(top,#f48b03,#c87100); }
代码中绿色部分是为了实现分页居中效果而添加的代码。(下文中没有特殊声明,绿色部分代码表示新增加的代码。),先来看看效果:

效果是让我们实现了,但其扩展性那就不一定强了。示例中只显示了五页和向前向后的七个显项,但往往我们很多情况下是不知道会有多少个分页项显示出来,而且也无法确定每个分页选项的宽度是多少,也就无法确认容器的宽度。
优点:实现方法简单易懂,浏览器兼容性强;
缺点:扩展性差,无法自适应未知项情况。
二、inline-block实现水平居中方法
这个方法早期在《如何解决inline-block元素的空白间距》和《CSS3制作的分页导航》中都有涉及到,但未单独提取出来。此次,将这种方法拿出来说。
仅inline-block属性是无法让元素水平居中,他的关键之处要在元素的父容器中设置text-align的属性为“center”,这样才能达到效果:
.pagination { text-align: center; font-size: 0; letter-spacing: -4px; word-spacing: -4px; } .pagination li { line-height: 25px; margin: 0 5px; display: inline-block; display: inline; zoom:1; letter-spacing: normal; word-spacing: normal; font-size: 12px; } .pagination a { display: block; color: #f2f2f2; text-shadow: 1px 0 0 #101011; padding: 0 10px; border-radius: 2px; box-shadow: 0 1px 0 #5a5b5c inset,0 1px 0 #080808; background: linear-gradient(top,#434345,#2f3032); } .pagination a:hover { text-decoration: none; box-shadow: 0 1px 0 #f9bd71 inset,0 1px 0 #0a0a0a; background: linear-gradient(top,#f48b03,#c87100); }
效果如下:

这个方法相对来说也是简单易懂,但使用了inline-block解决了水平居中的问题,却又产生了一个新的问题,就是分页项与分页项由回车符带来的空白间距,那么不知情的同学就会不知道如何解决?(而且这个间距并不是所有浏览器都有),所以需要解决下inline-block带来的间距,详细的解决方法可以阅读《如何解决inline-block元素的空白间距》一文。
做点:简单易懂,扩展性强;
缺点:需要额外处理inline-block的浏览器兼容性。
三、浮动实现水平居中的方法
刚看到标题,大家可能会感到很意外,元素都浮动了,他还能水平居中?大家都知道,浮动要么靠左、要么靠右,还真少见有居中的。其实略加处理就有了。
.pagination { float: left; width: 100%; overflow: hidden; position: relative; } .pagination ul { clear: left; float: left; position: relative; left: 50%;/整个分页向右边移动宽度的50%/ text-align: center; } .pagination li { line-height: 25px; margin: 0 5px; display: block; float: left; position: relative; right: 50%;/将每个分页项向左边移动宽度的50%/ } .pagination a { display: block; color: #f2f2f2; text-shadow: 1px 0 0 #101011; padding: 0 10px; border-radius: 2px; box-shadow: 0 1px 0 #5a5b5c inset,0 1px 0 #080808; background: linear-gradient(top,#434345,#2f3032); } .pagination a:hover { text-decoration: none; box-shadow: 0 1px 0 #f9bd71 inset,0 1px 0 #0a0a0a; background: linear-gradient(top,#f48b03,#c87100); }
效果如下所示:

这种方法实现和前面的与众不同,使用了浮动配合position定位实现。下面简单的介绍了一下这种方法实现原理,详细的可以阅读Matthew James Taylor写的《Horizontally Centered Menus with no CSS hacks》一文。
没有浮动的div:大家都知道div是一个块元素,其默认的宽度就是100%,如图所示:

如果div设置了浮动之后,他的内容有多宽度就会撑开有多大的容器(除显式设置元素宽度值之外),这也是我们实现让分页导航居中的关键所在:

接下来使用传统的制作方法,我们会让导航浮动到左边,而且每个分页项也进行浮动,就如下图所示一样:

现在要想的办法是让分页导航居中的效果了,在这里是通过“position:relative”属性实现,首先在列表项“ul”上向右移动50%(left:50%;),看到如下图所示:

如上图所示一样,整个分页向右移动了50%的距离,紧接着我们在“li”上也定义“position:relative”属性,但其移动的方向和列表“ul”移动的方向刚好是反方向,而其移动的值保持一致:

这样一来就实现了float浮动居中的效果。
特别声明:方法三思想来源于Matthew James Taylor写的《Horizontally Centered Menus with no CSS hacks》一文,并且引用其文中演示的示意图。
优点:兼容性强,扩展性强;
缺点:实现原理较复杂。
四、绝对定位实现水平居中
绝对定位实现水平居中,我想大家也非常的熟悉了,并且用得一定不少,早期是这样使用的:
.ele { position: absolute; width: 宽度值; left: 50%; margin-left: -(宽度值/2); }
但这种实现我们有一个难题,我并不知道元素的宽度是多少,这样也就存在如方法一所说的难题,但我们可以借助方法三做一点变通:
.pagination { position: relative; } .pagination ul { position: absolute; left: 50%; } .pagination li { line-height: 25px; margin: 0 5px; float: left; position: relative;/注意,这里不能是absolute,大家懂的/ right: 50%; } .pagination a { display: block; color: #f2f2f2; text-shadow: 1px 0 0 #101011; padding: 0 10px; border-radius: 2px; box-shadow: 0 1px 0 #5a5b5c inset,0 1px 0 #080808; background: linear-gradient(top,#434345,#2f3032); } .pagination a:hover { text-decoration: none; box-shadow: 0 1px 0 #f9bd71 inset,0 1px 0 #0a0a0a; background: linear-gradient(top,#f48b03,#c87100); }
效果如下所示:

优点:扩展性强,兼容性强;
缺点:理解性难。
五、CSS3的flex实现水平居中方法
CSS3的flex是一个很强大的功能,她能让我们的布局变得更加灵活与方便,唯一的就是目前浏览器的兼容性较差。那么第五种方法,我们就使用flex来实现,其实这种方法早在《CSS3实现水平垂直居中》一文有介绍,我们把水平居中的部分代码取出来:
.pagination { display: -webkit-box; -webkit-box-orient: horizontal; -webkit-box-pack: center; display: -moz-box; -moz-box-orient: horizontal; -moz-box-pack: center; display: -o-box; -o-box-orient: horizontal; -o-box-pack: center; display: -ms-box; -ms-box-orient: horizontal; -ms-box-pack: center; display: box; box-orient: horizontal; box-pack: center; } .pagination li { line-height: 25px; margin: 0 5px; float: left; } .pagination a { display: block; color: #f2f2f2; text-shadow: 1px 0 0 #101011; padding: 0 10px; border-radius: 2px; box-shadow: 0 1px 0 #5a5b5c inset,0 1px 0 #080808; background: linear-gradient(top,#434345,#2f3032); } .pagination a:hover { text-decoration: none; box-shadow: 0 1px 0 #f9bd71 inset,0 1px 0 #0a0a0a; background: linear-gradient(top,#f48b03,#c87100); }
效果如下:

优点:实现便捷,扩展性强
缺点:兼容性差。
六、CSS3的fit-content实现水平居中方法
今天看《Horizontal centering using CSS fit-content value》一文,让我体验了一下”fit-content”制作水平居中的方法。我也将这种方法收进来。
“fit-content”是CSS中给“width”属性新加的一个属性值,他配合margin可以让我轻松的实现水平居中的效果:
.pagination ul { width: -moz-fit-content; width:-webkit-fit-content; width: fit-content; margin-left: auto; margin-right: auto; } .pagination li { line-height: 25px; margin: 0 5px; float: left; } .pagination a { display: block; color: #f2f2f2; text-shadow: 1px 0 0 #101011; padding: 0 10px; border-radius: 2px; box-shadow: 0 1px 0 #5a5b5c inset,0 1px 0 #080808; background: linear-gradient(top,#434345,#2f3032); } .pagination a:hover { text-decoration: none; box-shadow: 0 1px 0 #f9bd71 inset,0 1px 0 #0a0a0a; background: linear-gradient(top,#f48b03,#c87100); }
效果如下:

优点:简单易懂,扩展性强;
缺点:浏览器兼容性差 1. 2 一、对于行内元素:
text-align:center;
二、对于确定宽度的块级元素:
(1)margin和width实现水平居中
常用(前提:已设置width值):margin-left:auto; margin-right:auto;
(2)绝对定位和margin-left: -(宽度值/2)实现水平居中
固定宽度块级元素水平居中,通过使用绝对定位,以及设置元素margin-left为其宽度的一半
.content{ width: 200px; position: absolute; left: 50%; margin-left: -100px; // 该元素宽度的一半,即100px background-color: aqua; }
(3)position:absolute + (left=0+top=0+right=0+bottom=0) + margin:auto
.content{ position: absolute; width: 200px; top: 0; right: 0; bottom: 0; left: 0; margin: auto; }
三、对于未知宽度的块级元素:
(1)table标签配合margin左右auto实现水平居中
使用table标签(或直接将块级元素设值为display:table),再通过给该标签添加左右margin为auto
(2)inline-block实现水平居中方法
display:inline-block;(或display:inline)和text-align:center;实现水平居中
存在问题:需额外处理inline-block的浏览器兼容性(解决inline-block元素的空白间距)
(3)绝对定位实现水平居中
绝对定位+transform,translateX可以移动本省元素的50%
.content{ position: absolute; left: 50%; transform: translateX(-50%); / 移动元素本身50% / background: aqua; }
(4)相对定位实现水平居中
用float或者display把父元素变成行内块状元素
.contentParent{ display: inline-block; / 把父元素转化为行内块状元素 / /float: left; 把父元素转化为行内块状元素 / position: relative; left: 50%; } /目标元素/ .content{ position: relative; right: 50%; background-color:aqua; }
(5)CSS3的flex实现水平居中方法,法一
.contentParent{ display: flex; flex-direction: column; } .content{ align-self:center; }
(6)CSS3的flex实现水平居中方法,法二
.contentParent{ display: flex; } .content{ margin: auto; }
(7)CSS3的fit-content配合左右margin为auto实现水平居中方法
.content{ width: fit-content; margin-left: auto; margin-right: auto; } 1. 元素水平居中 当然最好使的是:
margin: 0 auto;
居中不好使的原因:
1、元素没有设置宽度,没有宽度怎么居中嘛!
2、设置了宽度依然不好使,你设置的是行内元素吧,行内元素和块元素的区别以及如何将行内元素转换为块元素
示例 1:

效果:
1. 多行的行内元素
使用给父元素设置display:table-cell;和vertical-align: middle;属即可;效果:
1. 块级元素
方案一:使用定位首先设置父元素为相对定位,再设置子元素为绝对定位,设置子元素的top: 50%,即让子元素的左上角垂直居中;
定高度:设置绝对子元素的 margin-top: -元素高度的一半px; 或者设置transform: translateY(-50%);
不定高度:利用css3新增属性transform: translateY(-50%);
效果:

方案二:使用flexbox布局实现(高度定不定都可以)
使用flexbox布局,只需要给待处理的块状元素的父元素添加属性 display: flex; align-items: center;
效果:
1. 垂直水平居中
1. 方式1:绝对定位

<!DOCTYPE html>

<!DOCTYPE html> 1. 方式3:使用translate实现平移 <!DOCTYPE html>
下面的transform代码可以更换为transform: translate(-50%,-50%); 1. 方式4:通过设置bottom top left right margin来实现

<!DOCTYPE html>
最长使用,设置 display: flex;justify-content: center;align-items: center;三个属性;
方案3:flex布局
示例 4:

示例 5:table-cell布局
因为table-cell相当与表格的td,td为行内元素,无法设置宽和高,所以嵌套一层,嵌套一层必须设置display: inline-block;td的背景覆盖了橘黄色,不推荐使用

效果:

方案二:设置父元素为相对定位,给子元素设置绝对定位,left: 50%; top: 50%; margin-left: —元素宽度的一半px; margin-top: —元素高度的一半px;
效果:

方案1:position 元素已知宽度
父元素设置为:position: relative;子元素设置为:position: absolute;距上50%,据左50%,然后减去元素自身宽度的距离就可以实现
示例 2:

设置父元素为相对定位,给子元素设置绝对定位,left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%);
效果:

方案二:使用flex布局实现
设置父元素为flex定位,justify-content: center; align-items: center;
效果:

方案2:position transform 元素未知宽度
如果元素未知宽度,只需将上面例子中的margin: -50px 0 0 -50px;替换为:transform: translate(-50%,-50%);
效果如上!
示例 3:

1. 平时是这样的,上下排列~

1. float 浮动
div1{ float: left; } #div2 { float: right; } #div3 { float: right; }
1. 然后这样了

1. float 的特点:
多个 div 右浮动时,顺序会颠倒,请注意看 div2 和 div3,可以通过将它们再用一个 div 包起来,然后对它们设置左浮动,对父 div 设置右浮动来解决。
脱离文档流,若父元素高度由内容撑开,那么就撑不开,从图中可以看到 wrap 没了,因为高度变为了 0,可通过清浮动来解决。
文字会环绕在浮动元素周围,图中未表现出来。
不能换行,图中未表现出来。
1. inline-block 行块标签

#div1, #div2, #div3{ display: inline-block; }
1. inline-block 特点:
元素间会有空白。这个空白其实是空白符,因为 inline-block 会使元素在行内排列,也就是跟文字在一起排列,而我们源代码中 div 和 div 之间的空格、Tab、换行符在浏览器里会被合并成一个空白符,所以就会出现缝隙,常见的解决方案有:
1. 通过给父元素设置 font-size: 0; ,使空白符不可见。但会导致子元素中继承的字体大小也为 0,解决方案:
1. 可以明确子元素内字体大小的,为其单独设置文字大小。
2. 可以使用 rem 作为字体大小单位来继承 HTML 根元素的字体大小属性。
2. 在源代码里把前一个 div 的结束标签和后一个 div 的开始标签贴在一起。可读性极差,丑拒。
3. 不用 inline-block,嘿嘿~
可以换行,如下图
1. flex 弹性盒模型
1. 最爱的解决方案,给父元素设置 display: flex; 即可。
wrap{ display: flex; }
1. 效果图:

1. 还可以通过 justify-content 属性调整子元素的水平对齐方式:
wrap{ display: flex; justify-content: flex-start; }
1. **flex-start:**
1. 默认,图同上。
1. **flex-end:**

1. **center:**

1. **space-around:**

1. **space-between:**

1. 不过当父元素宽度不够时, flex 默认是不会换行的,而是会等比例压缩,缩放比例 flex-shrink 属性或复合属性 flex 相关

1. 通过 flex-wrap 属性可以使其换行,该值有三个取值 nowrap、 wrap、 wrap-reverse,默认为 nowrap。
wrap{ display: flex; flex-wrap: nowrap; }
1. **nowrap**
1. 默认,图略。
1. **wrap**

1. **wrap-reverse**

1. flex 使用不再深入探讨,推荐阮一峰写的教程 [http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html?utm_source=tuicool](http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html?utm_source=tuicool)
1. 三栏布局
1. **实现效果:** 左右栏定宽,中间栏自适应

1. 1、绝对定位布局:position + margin
html结构:
css样式:
body,html{ height: 100%; padding: 0; margin: 0; overflow: hidden; } /左右进行绝对定位/ .left,.right{ position: absolute; height:100%; top: 0; background: #ff69b4; } .left{ left: 0; width: 100px; } .right{ right: 0; width: 200px; } /中间用margin空出左右元素所占的空间/ .main{ height:100%; margin: 0 100px 200px 0; background: #659; }
缺点: 如果中间栏含有最小宽度限制,或是含有宽度的内部元素,当浏览器宽度小到一定程度,会发生层重叠的情况。
1. 2、浮动布局: float + margin
html结构:
css样式:
body,html{ height: 100%; padding:0; margin: 0; } /左边栏左浮动/ .left{ float:left; height:100%; width:100px; background:#ff69b4; } /中间栏自适应/ .main{ height:100%; margin:0 200px 0 100px; background: #659; } /右边栏右浮动/ .right{ float:right; height:100%; width:200px; background:#ff69b4; }
1. 3、flex布局
html结构:
css样式:
.container{ display: flex; } .left{ width:200px; background: red; } .main{ flex: 1; background: blue; } .right{ width:200px; background: red; }
这种布局方式,高度由内容决定。
1. 4、table布局
html结构:
css样式:
.container{ display: table; width:100%; } .container>div{ display: table-cell; } .left{ width: 100px; background: red; } .main{ background: blue; } .right{ width: 200px; background: red; }
高度由内容决定。
1. 5、Grid网格布局
html结构:
css样式:
.container{ display: grid; width: 100%; grid-template-rows: 100px; /设置行高/ grid-template-columns: 100px auto 200px; /设置列数属性/ } .left{ background: red; } .main{ background: blue; } .right{ background:red; }
1. 6、圣杯布局
html结构:
css样式:
/ 两边定宽,中间自适用 / body,html,.container{ height: 100%; padding:0; margin: 0; } .col{ float: left; / 三个col都设置float: left,为了把left和right定位到左右部分 / position:relative; } /父元素空出左右栏位子: 因为上一步中,左右栏定位成功了,但是中间栏的内容会被遮盖住/ .container{ padding:0 200px 0 100px; } /左边栏/ .left{ left:-100px; width: 100px; height:100%; margin-left: -100%; background: #ff69b4; } /中间栏/ .main{ width:100%; height: 100%; background: #659; } /右边栏/ .right{ right:-200px; width:200px; height:100%; margin-left: -200px; background: #ff69b4; }
总结:圣杯布局用到了浮动float、负边距、相对定位relative,不添加额外标签
1. 7、双飞翼布局
html结构:
css样式:
body,html,.container{ height: 100%; padding:0; margin: 0; } .col{ float: left; / 把left和right定位到左右部分 / } .main{ width:100%; height:100%; background: #659; } .main_inner{ / 处理中间栏的内容被遮盖问题 / margin:0 200px 0 100px; } .left{ width: 100px; height: 100%; margin-left: -100%; background: #ff69b4; } .right{ height:100%; width:200px; margin-left: -200px; background: #ff69b4; }
双飞翼布局的好处:
(1)主要的内容先加载的优化。
(2)兼容目前所有的主流浏览器,包括IE6在内。
(3)实现不同的布局方式,可以通过调整相关CSS属性即可实现。
1. 8、对比圣杯布局和双飞翼布局:
(1)都是左右栏定宽,中间栏自适应的三栏布局,中间栏都放到文档流前面,保证先行渲染。
(2)解决方案基本相似:都是三栏全部设置左浮动float:left,然后分别结局中间栏内容被覆盖的问题。
(3)解决中间栏内容被覆盖问题时,圣杯布局设置父元素的padding,双飞翼布局在中间栏嵌套一个div,内容放到新的div中,并设置margin,实际上,双飞翼布局就是圣杯布局的改进方案。
1. 假设高度已知,请写出三栏布局,左栏、右栏宽度300px,中间宽度自适应。

1. 样式
1. 1. 浮动布局
三栏布局
浮动解决方案
1.这是三栏布局的浮动解决方案; 2.这是三栏布局的浮动解决方案;浮动布局是有局限性的,浮动元素是脱离文档流,要做清除浮动,这个处理不好的话,会带来很多问题,比如高度塌陷等。
浮动布局的优点就是比较简单,兼容性也比较好。只要清除浮动做的好,是没有什么问题的。
延伸:你知道哪些清除浮动的方案?每种方案的有什么优缺点?
1. 2.绝对定位布局
三栏布局
绝对定位解决方案
1.这是三栏布局的绝对定位解决方案; 2.这是三栏布局的绝对定位解决方案;绝对定位布局优点,很快捷,设置很方便,而且也不容易出问题,你可以很快的就能想出这种布局方式。
缺点就是,绝对定位是脱离文档流的,意味着下面的所有子元素也会脱离文档流,这就导致了这种方法的有效性和可使用性是比较差的。
1. 3.flex布局
三栏布局
flexbox解决方案
1.这是三栏布局的felx解决方案; 2.这是三栏布局的flex解决方案;felxbox布局是css3里新出的一个,它就是为了解决上述两种方式的不足出现的,是比较完美的一个。目前移动端的布局也都是用flexbox。
felxbox的缺点就是不能兼容IE8及以下浏览器
1. 4.表格布局
三栏布局
表格布局解决方案
1.这是三栏布局的表格解决方案; 2.这是三栏布局的表格解决方案;表格布局在历史上遭到很多人的摒弃,说表格布局麻烦,操作比较繁琐,其实这是一种误解,在很多场景中,表格布局还是很适用的,比如这个三栏布局,用表格布局就轻易写出来了。还有表格布局的兼容性很好,在flex布局不兼容的时候,可以尝试表格布局。
表格布局也是有缺陷的,当其中一个单元格高度超出的时候,两侧的单元格也是会跟着一起变高的,而有时候这种效果不是我们想要的。
1. 5.网格布局
三栏布局
网格布局解决方案
1.这是三栏布局的网格布局解决方案; 2.这是三栏布局的网格布局解决方案;网格布局也是新出的一种布局方式,如果你答出这种方式,也就证明了你的实力,证明你对技术热点是有追求的,也说明你有很强的学习能力。
效果图

最后这个问题还有很多延伸问题的,比如,
高度已知换为高度未知呢?
块内内容超出会是怎样的效果?
如果是上下高度已知,中间自适应呢?
如果是两栏布局呢?
如果是上下左右混合布局呢?
1. 已知布局元素的高度,写出三栏布局,要求左栏、右栏宽度各为300px,中间自适应。
1. 一、浮动布局
<!DOCTYPE html>
浮动布局的兼容性比较好,但是浮动带来的影响比较多,页面宽度不够的时候会影响布局
1. 二、绝对定位布局
<!DOCTYPE html>
绝对定位布局快捷,但是有效性比较差,因为脱离了文档流。
1. 三、flex布局
<!DOCTYPE html>
自适应好,高度能够自动撑开
1. 四、table-cell表格布局
<!DOCTYPE html>
兼容性好,但是有时候不能固定高度,因为会被内容撑高。
1. 五、网格布局
<!DOCTYPE html>
比较新的一种布局方式,兼容性没那么好。
- ul、li导航栏居中的两种办法
- 总结了下导航栏的制作方法:一种是用float设计,提前设置好高度与宽度,然后将要显示的元素设置为float::left依次显示。
- 一、float方法
- 界面html
1. css代码
.topbar-container{ background-color: #222222; } .topbar-wrap{ width:1200px; height:60px; margin:0 auto; overflow: hidden; } .logo{ height:50px; width: 50px; float: left; margin:5px 5px; } .nav{ float: left; height: 60px;; } .nav li{ float: left; width: 100px; margin:0 10px; list-style: none; } .nav a{ text-decoration: none; height: 100%; width: 100%; display: block; font-size: 18px; color: #dadada; text-align: center; line-height: 60px; } .nav a:hover{ background: #333; }
1. **二、inline-block居中**
1. 其中左边的img设置为a'bsolute是为了让img不占位置,从而让右侧的nav居中
1. html代码
1. css代码
.topbar-container2{ background-color: #222222; } .topbar-wrap2{ height: 60px; width: 1200px; margin:0 auto; position: relative; } .logo2{ position: absolute; height:50px; width: 50px; margin:5px 5px; left:-0px; top:0; } .nav2{ height: 60px; width:1200px; } .nav2 ul{ text-align: center; height:100%; width: 100%; list-style: none; } .nav2 li{ display:inline-block; } .nav2 ul li a{ text-decoration: none; font-size: 18px; color: #dadada; text-align: center; line-height: 60px; margin:0 20px; }
1. **重点是ul设置text-align:center,这样才会居中**
- 盒模型

- 标准盒模型

1. width和height =content宽度/高度
- IE盒模型

1. width和height =content+padding+border宽度/高度
- 如何设置box-sizing:content-box 【border-box不包含margin】, box-sizing:border-box
- content-box 是默认值。如果你设置一个元素的宽为100px,那么这个元素的内容区会有100px 宽,并且任何边框和内边距的宽度都会被增加到最后绘制出来的元素宽度中。
- width 与 height 只包括内容的宽和高, 不包括边框(border),内边距(padding),外边距(margin)。注意: 内边距、边框和外边距都在这个盒子的外部。 比如说,.box {width: 350px; border: 10px solid black;} 在浏览器中的渲染的实际宽度将是 370px。
- 尺寸计算公式:
- width 与 height 只包括内容的宽和高, 不包括边框(border),内边距(padding),外边距(margin)。注意: 内边距、边框和外边距都在这个盒子的外部。 比如说,.box {width: 350px; border: 10px solid black;} 在浏览器中的渲染的实际宽度将是 370px。
- content-box 是默认值。如果你设置一个元素的宽为100px,那么这个元素的内容区会有100px 宽,并且任何边框和内边距的宽度都会被增加到最后绘制出来的元素宽度中。
width = 内容的宽度
height = 内容的高度
1. 宽度和高度的计算值都不包含内容的边框(border)和内边距(padding)。
1. border-box 告诉浏览器:你想要设置的边框和内边距的值是包含在width内的。也就是说,如果你将一个元素的width设为100px,那么这100px会包含它的border和padding,内容区的实际宽度是width减去(border + padding)的值。大多数情况下,这使得我们更容易地设定一个元素的宽高。
1. [width] 和 [height] 属性包括内容,内边距和边框,但不包括外边距。这是当文档处于 Quirks模式 时Internet Explorer使用的[盒模型](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model)。注意,填充和边框将在盒子内 , 例如, .box {width: 350px; border: 10px solid black;} 导致在浏览器中呈现的宽度为350px的盒子。内容框不能为负,并且被分配到0,使得不可能使用border-box使元素消失。
1. 尺寸计算公式:
1. _width = border + padding + 内容的宽度_
1. _height = border + padding + 内容的高度_
- JS如何设置获取盒模型对应的宽和高
- dom.style.width/height 只能取到内联样式的尺寸
- dom.currentStyle.width/height 得到的是渲染的尺寸 【只有IE支持】
- window.getComputedStyle(dom).width/height 【支持谷歌和safari】
- dom.getBoundingClientRect().width/height 计算元素的绝对位置 【据窗口左上角的位置】

- BFC(边距重叠解决方案)
- BFC概念:块级格式化上下文
- 父子元素、兄弟元素、空元素会出现【边距重叠】
- BFC原理:
- BFC垂直方向的边距会出现重叠
- BFC区域不会与浮动元素重叠
- BFC在页面上是一个独立的容器,外边与里面的元素不会相互影响
- 计算BFC高度时,浮动元素会参与计算
- 如何创建BFC:
- float 的值不是 none
- position 的值不是static或者relative。(absolute,fixed)
- display的值是inline-block、table-cell、flex、table-caption或者inline-flex
- overflow 的值不是 visible(hidden,auto,scroll)
- BFC的使用场景
- 运算符

- 会发生类型转换
- 字符串拼接
- ==运算符
- if语句
- 逻辑运算
- 算术运算符【+ - / % * 幂 ++ —】
- 0除以正数,得到结果 Infinity (正无穷)
- 0除以负数,得到结果 -Infinity (负无穷)
- 0除以0,得到结果 NaN (Not a Number,非数字)
- %,求模
- 余数的符号,与被除数相同。
- 除加号之外的算术运算符
- 将原始类型转换为数字类型(自动完成转换),然后进行运算。
- boolean: true -> 1, false -> 0
- string: 如果字符串内部是正确的数字,直接变为数字,如果是一个非数字,则得到NaN(能识别Infinity,不能把字符串内部的东西当作表达式),如果字符串是一个空字符串(没有任何内容),转换为0. 字符串转换时,会忽略前后空格。
- NaN虽然是数字,与任何数字作任何运算,得到的结果都是NaN
- null:null -> 0
- undefined: undefined -> NaN
- 将对象类型先转换为字符串类型,然后再将该字符串转换为数字类型
- 对象类型 -> “[object Object]” -> NaN
- 加号运算符
- 加号一边有字符串,含义变为字符串拼接
- 将另一边的其他类型,转换为字符串
- 数字 -> 数字字符串
- boolean -> boolean字符串
- null -> “null”
- undefined -> “undefined”
- 对象 -> “[object Object]”
- 加号两边都没有字符串,但一边有对象,将对象转换为字符串,然后按照上面的规则进行
- 自增和自减
- 基本功能
- 一元运算符
- ++:将某个变量的值自增1
- —:将某个变量的值自减1
- 细节
- x++: 将变量x自增1,得到的表达式的值是自增之前的值。
- ++x: 将变量x自增1,得到的表达式的值是自增之后的值。
- x—: 将 变量x自减1,得到的表达式的值是自减之前的值。
- —x: 将变量x自减1,得到的表达式的值是自减之后的值。
- 比较运算符
- 大小比较:> < >= <=
- 相等比较:== != === !==
- 比较运算符的返回类型:boolean
- 算术运算符的优先级高于比较运算符
- NaN与任何数字比较,得到的结果都是false
- Infinity比任何数字都大
- -Infinity比任何数字都小
- 细节
- 两个字符串比较大小,比较的是字符串的字符编码。
- 如果一个不是字符串,并且两个都是原始类型,将它们都转换为数字进行比较
- ‘1’ -> 1
- ‘’ -> 0
- ‘ ‘ -> 0
- ‘ a’ -> NaN
- ‘3.14’ -> 3.14
- null -> 0
- undefined -> NaN
- 如果其中一个是对象,将对象转换为原始类型然后,按照规则1或规则2进行比较
- 目前,对象转换为原始类型后,是字符串 “[object Object]” ->NaN
- == 和 != (相等比较 和 不相等比较)
- ==: 比较两个数据是否相等
- 何时使用===和==|
if (obj.a == null) { //这里相当于obj.a === null || obj.a === undefined,简写形式 //这是jquery源码中推荐的写法 }
1. !=: 比较两个数据是否不相等
1. **细节**
1. 两端的类型相同,直接比较两个数据本身是否相同(两个对象比较的地址)
1. 两端的类型不同
1. 1). null 和 undefined, 它们之间相等, 和其他原始类型比较, 则不相等。
1. 2). 其他原始类型,比较时先转换为数字,再进行比较
1. 3). NaN与任何数字比较,都是false,包括自身
1. 4). Infinity和-Infinity,只能和自身相等
1. 5). 对象比较时,要先转换为原始类型后,再进行比较
3. === 和 !== (严格相等 和 严格不相等)
1. === : 两端的数据和类型必须相同
1. !== : 两端的数据或类型不相同
1. 两端类型相同,规则和相等比较一致。
1. 两端类型不同,为false。
1. 数字规则:
1. 1). NaN与任何数字比较,都是false,包括自身
1. 2). Infinity和-Infinity,自能和自身相等
- 逻辑运算符

1. 与(并且)【&&】
1. 书写方式: 表达式1 && 表达式2
1. 将表达式1 进行 boolean 判定
1. **以下数据均判定为false:**
1. **null**
1. **undefined**
1. **false**
1. **NaN**
1. **''**
1. **0**
4. **其他数据全部为真**
4. 如果表达式1的判定结果为假,则直接返回表达式1,而不执行表达式2;否则,返回表达式2的结果。 (短路规则)
2. 或【||】
1. 写法:表达式1 || 表达式2
1. 将表达式1 进行 boolean 判定
1. 如果表达式1为真,直接返回表达式1,不运行表达式2;否则,返回表达式2
3. 非【!】
1. 写法: !数据
1. **将数据的boolean判定结果直接取反,非运算符一定返回boolean类型。**
4. **三目运算符**
1. 书写方式: 表达式1 ? 表达式2 : 表达式3
1. 对表达式1进行boolean判定
1. 如果判定结果为真,返回表达式2;否则,返回表达式3。
5. **类型转换不会影响原本的数据**
5. void 运算符
1. 普通写法: void 表达式
1. 函数写法: void(表达式)
1. 运行表达式,然后返回undefined
1. 这个运算符能向期望一个表达式的值是[undefined]的地方插入会产生副作用的表达式。
1. void 运算符通常只用于获取 undefined的原始值,一般使用void(0)(等同于void 0)。在上述情况中,也可以使用全局变量[undefined] 来代替(假定其仍是默认值)。
1. [立即调用的函数表达式](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/void#%E7%AB%8B%E5%8D%B3%E8%B0%83%E7%94%A8%E7%9A%84%E5%87%BD%E6%95%B0%E8%A1%A8%E8%BE%BE%E5%BC%8F)
1. 在使用[立即执行的函数表达式](https://developer.mozilla.org/zh-CN/docs/Glossary/IIFE)时,可以利用 void 运算符让 JavaScript 引擎把一个function关键字识别成函数表达式而不是函数声明(语句)。
void function iife() { };
1. [在箭头函数中避免泄漏](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/void#%E5%9C%A8%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0%E4%B8%AD%E9%81%BF%E5%85%8D%E6%B3%84%E6%BC%8F)
1. 箭头函数标准中,允许在函数体不使用括号来直接返回值。 如果右侧调用了一个原本没有返回值的函数,其返回值改变后,则会导致非预期的副作用。 安全起见,当函数返回值是一个不会被使用到的时候,应该使用 void 运算符,来确保返回 [undefined](如下方示例),这样,当 API 改变时,并不会影响箭头函数的行为。
button.onclick = () => void doSomething();
1. 确保了当 doSomething 的返回值从 [undefined] 变为 true 的时候,不会改变函数的行为
1. typeof 运算符
1. 普通写法: typeof 表达式
1. 函数写法: typeof(表达式)
1. typeof运算,返回表达式的类型,是一个字符串。
1. <br />

- 字符串拼接
- 使用加号运算符
- 连接字符串最简便的方法是使用加号运算符。
- 示例1
- 下面代码使用加号运算符连接两个字符串。
- 使用加号运算符
var s1 = “abc” , s2 = “def”; console.log(s1 + s2); //返回字符串“abcdef”
1. 使用concat()方法
1. 使用字符串 concat() 方法可以把多个参数添加到指定字符串的尾部。该方法的参数类型和个数没有限制,它会把所有参数都转换为字符串,然后按顺序连接到当前字符串的尾部最后返回连接后的字符串。
1. 示例2
1. 下面代码使用 concat() 方法把多个字符串连接在一起。
var s1 = “abc”; var s2 = s1.concat(“d” , “e” , “f”); //调用concat()连接字符串 console.log(s2); //返回字符串“abcdef” concat() 方法不会修改原字符串的值,与数组的 concat() 方法操作相似。
1. 使用join()方法
1. 在特定的操作环境中,也可以借助数组的 join() 方法来连接字符串,如 HTML 字符串输出等。
1. 示例3
1. 下面代码演示了如何借助数组的方法来连接字符串。
var s = “JavaScript” , a = []; for (var i = 0; i < 1000; i ++) { a.push(s); var str = a.join(“”); a = null; document.write(str);
1. 在上面示例中,使用 for 语句把 1000 个 “JavaScript”字符串装入数组,然后调用数组的 join() 方法把元素的值连接成一个长长的字符串。使用完毕应该立即清除数组,避免占用系统资源。
1. 在传统浏览器中,使用数组的 join() 方法连接超大字符串时,速度会很快,是推荐的最佳方法。随着现代浏览器优化了加号运算符的算法,使用加号运算符连接字符串速度也非常快,同时使用简单。一般推荐使用加号运算符来连接字符串,而 concat() 和 join() 方法可以用在特定的代码环境中。
- 随机排序
- 一、顺序排序
- 1、按字符编码排序:sort()
- 一、顺序排序
var testArray=[23,500,1000,300,34,-2]; testArray.sort(); alert(testArray); //-2,1000,23,300,34,500
1. 2、将数组元素倒序排:reverse()
var testArray=[-2,53,34,300,500,1000]; testArray.reverse(); alert(testArray); //1000,500,300,34,53,-2
1. 3、在sort()里面加个比较函数(从小到大排)
var testArray=[23,500,1000,300,34,-2]; //传给sort一个比较函数,如果比较函数return值小于0,则表示a必须出现在b前面,否则在b后面。 testArray.sort(function(a,b){return a-b;}); alert(testArray); //-2,23,34,300,500,1000
1. 4、快速排序
1. 效率相比上面的方法最高。
1. 看不懂下面代码的话可以参考:快速排序(Quicksort)的Javascript实现
var testArray=[‘df’, ‘rtr’, ‘wy’, ‘dafd’, ‘dfs’, ‘wefa’, ‘tyr’, ‘rty’, ‘rty’, ‘ryt’, ‘afds’, ‘wer’, ‘te’]; var testArray2=[23,500,1000,300,34,-2]; //快速排序函数 var quickSort = function(arr) { if (arr.length <= 1) { return arr; } var pivotIndex = Math.floor(arr.length / 2); var pivot = arr.splice(pivotIndex, 1);//将基准分离出 var left = []; var right = []; for (var i = 0; i < arr.length; i++){ if (arr[i] < pivot) { left.push(arr[i]); } else { right.push(arr[i]); } } return quickSort(left).concat([pivot], quickSort(right)); }; //调用输出 alert(quickSort(testArray));//afds,dafd,df,dfs,rtr,rty,rty,ryt,te,tyr,wefa,wer,wy alert(quickSort(testArray2)); //-2,23,34,300,500,1000
1. 二、随机打乱排序(乱序)
1. 1、低效版:
1. 原理:和上面数组排序的2原理一样,让比较函数随机传回-1或1即可。
1. 这种方法打乱10000个元素的数组,所用时间大概在35ms上下,比较低效。
var testArray=[-2,23,34,300,500,1000]; testArray.sort(function(){return Math.random()>0.5?-1:1;}); alert(testArray); //结果不唯一
1. 2、高效版:
1. (1)洗牌算法:
这种方法打乱10000个元素的数组来测试仅需要7,8毫秒的时间。
var testArray=[-2,23,34,300,500,1000]; if (!Array.prototype.derangedArray) { Array.prototype.derangedArray = function() { for(var j, x, i = this.length; i; j = parseInt(Math.random() * i), x = this[—i], this[i] = this[j], this[j] = x); return this; }; } alert(testArray.derangedArray());//结果不唯一
1. (2)这个简单明了,且复杂度为 O(n)
function shuffle(arr) { var len = arr.length; for (var i = 0; i < len - 1; i++) { var index = parseInt(Math.random() * (len - i)); var temp = arr[index]; arr[index] = arr[len - i - 1]; arr[len - i - 1] = temp; } return arr; } var arr = [-2,1,3,4,5,6,7,8,9]; console.log(shuffle(arr));//结果不唯一
- JSON 【不过是一个JS对象】
- JSON.stringify({a:10,b:20}) //把对象变为字符串
- JSON.parse(‘{“a”:10,”b”:20}’) //把字符串变为对象
- 构造函数、原型、原型



1. var arr =[]; arr instanceof Array //ture typeof arr //object typeof是无法判断数组的 2、 function Elem(id) { this.elem = document.getElementById(id) } Elem.prototype.html = function (val) { var elem = this.elem; if (val) { elem.innerHTML = val; return this // } else { return elem.innerHTML } } Elem.prototype.on = function (type, fn) { var elem = this.elem; elem.addEventListener(type, fn) } var div1 = new Elem('div') console.log(div1.html(<p>hello imooc</p>).on('click', function () { alert('clicked') })) 3、创建一个新对象 this指向新对象 执行代码,即对this赋值 返回this
- 构造函数




- 原型 prototype
- 所有函数都有一个属性:prototype,称之为函数原型
- 默认情况下,prototype 是一个普通的 Object 对象
- 默认情况下,prototype 中有一个属性,constructor,它也是一个对象,它指向构造函数本身。

1. 函数是通过new Function创建
1. <br />

1. 每个函数都有原型对象型中的constructor指向函数本身
- 隐式原型 proto。
- 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了“null”除外)
- 所有的对象都有一个属性:proto,称之为隐式原型
- 默认情况下,隐式原型指向创建该对象的函数的原型。
- 所有对象的隐式原型指向创建该对象的函数的原型
- 当访问一个对象的成员时:
- 看该对象自身是否拥有该成员,如果有直接使用
- 在原型链中依次查找是否拥有该成员,如果有直接使用

- 原型链



1. 特殊点:
1. Function 的**proto**指向自身的 prototype
1. Object 的 prototype 的**proto**指向 null
2. <br />

1. 原型链的应用
1. 基础方法
1. W3C 不推荐直接使用系统成员**proto**
1. **Object.getPrototypeOf(对象)**
获取对象的隐式原型
1. **Object.prototype.isPrototypeOf(对象)**
判断当前对象(this)是否在指定对象的原型链上
1. **对象 instanceof 函数**
判断函数的原型是否在对象的原型链上
1. **Object.create(对象)**
创建一个新对象,其隐式原型指向指定的对象
1. **Object.prototype.hasOwnProperty(属性名)**
判断一个对象自身是否拥有某个属性
- 执行上下文、this、作用域、作用域链、闭包


- 执行上下文

1. 执行上下文
1. 范围:一段<script>或者一个函数
1. 全局:变量定义、函数声明
1. 函数:变量定义、函数声明、this、arguments
1. PS:注意“函数声明”和“函数表达式”的区别
- this
- this要在执行时才能确认值,定义时无法确认
var a = { name: ‘A’, fn: function () { console.log(this.name) } } a.fn() // this === a a.fn.call({name: ‘B’}) // this === {name: ‘B’) var fnl = a.fn fnl() // this === window
1. 说明this几种不同的使用场景
1. 作为构造函数执行
1. 作为对象属性执行
1. 作为普通函数执行
1. call apply bind
- 作用域

1. 自由变量
1. 作用域链,即自由变量的查找
1. 闭包的两个场景
1. 团数作为返回值(上一个demo)
1. 团数作为参数传递(自己思考)
- 作用域链

- 闭包


- 异步、单线程
- 同步和异步的区别是什么?分别举一个同步和异步的例子
- 同步会阻塞代码执行,而异步不会
- alert是同步的,setTimeout是异步的
- 一个关于setTimeout的笔试题
- 同步和异步的区别是什么?分别举一个同步和异步的例子

- 前端使用异步的场景有哪些
- 定时任务:setTimeout, setlnverval
- JS setTimeout 和 setInterval 的区别
- setTimeout 和 setInterval 都属于 JS 中的定时器,可以规定延迟时间再执行某个操作,不同的是 setTimeout 在规定时间后执行完某个操作就停止了(setTimeout 只执行一次那段代码。),而 setInterval 在执行完一次代码之后,经过了那个固定的时间间隔,它还会自动重复执行代码。
- JS setTimeout 和 setInterval 的区别
- 定时任务:setTimeout, setlnverval
function **fun**() { **alert**("hello"); } setTimeout(fun, 1000); //*参数是函数名* setTimeout("fun()", 1000); //*参数是字符串* setInterval(fun, 1000); setInterval("fun(),1000"); 在上述代码中,无论是 setTimeout 还是 setInterval,在使用函数名作为调用句柄时不能带参数,使用字符串调用时可以带参数。例如:setTimeout(‘fun(name)’,1000);
它们都有两个参数,一个是将要执行的代码字符串,还有一个是以毫秒为单位的时间间隔,当过了那个时间段之后就将执行那段代码。
window.setTimeout(“function”,time);//设置一个超时对象,只执行一次,无周期-
window.setInterval(“function”,time);//设置一个超时对象,周期=’交互时间’
停止定时:
window.clearTimeout(对象) 清除已设置的 setTimeout 对象
window.clearInterval(对象) 清除已设置的 setInterval 对象
1. 不再单独再定义一个函数,直接将函数调用放在一个函数里面,可以使用函数名作为调用调用句柄。
function **fun**(*name*) { **alert**("hello" + " " + name); } setTimeout(function () { **fun**("Tom"); }, 1000); //*参数是函数名* 在上述代码中,setTimeout 和 setInterval 的区别就是 setTimeout 延迟一秒弹出’hello’,之后便不再运行;而 setInterval 则会隔一秒弹出’hello’,直至用 clear 来清除定时器的语法。
1. requestAnimationFrame,与 setTimeout 相比 requestAnimationFrame 最大的优势是由系统来决定回调函数的执行时机,它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象,常用于动画场景。
window.requestAnimationFrame(callback);
1. 实例
实现一个打点计时器,要求
1、从 start 到 end(包含 start 和 end),每隔 100 毫秒 console.log 一个数字,每次数字增幅为 1;
2、返回的对象中需要包含一个 cancel 方法,用于停止定时操作;
3、第一个数需要立即输出.
function **count**(*start*, *end*) { //*立即输出第一个数* console.**log**(start++); //* 重复执行函数* var timer = setInterval(function () { //* 判断首是否小于尾* if (start <= end) { console.**log**(start++); }else { clearInterval(timer); } },100); //* 返回对象* return{ **cancel**: function () { clearInterval(timer); } } } **count**(2,6);
1. 网络请求:ajax请求,动态<img>加载
1. 事件绑定
- 日期、Math
- 获取2017-06-10格式的日期

- 获取随机数,要求是长度一致的字符串格式

- 写一个能遍历对象和数组的通用forEach涵数

- 日期
- Date.now() // 获取当前时间毫秒
- var dt = new Date()
- dt.getTime() //获取豪秒数
- dt.getFullYear() // 年
- dt.getMonth() //月(0-11)
- dt.getDate() // 日(0 - 31)
- dt.getHours() // 小时(0 - 23)
- dt.getMinutes() // 分钟(0 - 59)
- dt.getSeconds() //秒(0-59)
- 数组、对象
- 数组的所有方法
- JavaScript中创建数组有两种方式
- (一)使用 Array 构造函数:
- JavaScript中创建数组有两种方式
var arr1 = new Array(); //创建一个空数组 var arr2 = new Array(20); // 创建一个包含20项的数组 var arr3 = new Array(“lily”,“lucy”,“Tom”); // 创建一个包含3个字符串的数组
1. (二)var 创建数组
var arr4 = []; //创建一个空数组 var arr5 = [20]; // 创建一个包含1项的数组 var arr6 = [“lily”,“lucy”,“Tom”]; // 创建一个包含3个字符串的数组
1. 1、join()
1. 通过join()方法可以实现重复字符串,只需传入字符串以及重复的次数,就能返回重复后的字符串,函数如下:
function repeatString(str, n) { return new Array(n + 1).join(str); } console.log(repeatString(“abc”, 3)); // abcabcabc console.log(repeatString(“Hi”, 5)); // HiHiHiHiHi
1. 2、push()和pop()
1. push(): 可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。
1. pop():数组末尾移除最后一项,减少数组的 length 值,然后返回移除的项。
var arr = [“Lily”,“lucy”,“Tom”]; var count = arr.push(“Jack”,“Sean”); console.log(count); // 5 console.log(arr); // [“Lily”, “lucy”, “Tom”, “Jack”, “Sean”] var item = arr.pop(); console.log(item); // Sean console.log(arr); // [“Lily”, “lucy”, “Tom”, “Jack”]
1. 3、shift() 和 unshift()
1. shift():删除原数组第一项,并返回删除元素的值;如果数组为空则返回undefined 。
1. unshift:将参数添加到原数组开头,并返回数组的长度 。
1. 这组方法和上面的push()和pop()方法正好对应,一个是操作数组的开头,一个是操作数组的结尾。
var arr = [“Lily”,“lucy”,“Tom”]; var count = arr.unshift(“Jack”,“Sean”); console.log(count); // 5 console.log(arr); //[“Jack”, “Sean”, “Lily”, “lucy”, “Tom”] var item = arr.shift(); console.log(item); // Jack console.log(arr); // [“Sean”, “Lily”, “lucy”, “Tom”]
1. 4、sort()
1. sort():按升序排列数组项——即最小的值位于最前面,最大的值排在最后面。
1. 在排序时,sort()方法会调用每个数组项的 toString()转型方法,然后比较得到的字符串,以确定如何排序。即使数组中的每一项都是数值, sort()方法比较的也是字符串,因此会出现以下的这种情况:
var arr1 = [“a”, “d”, “c”, “b”]; console.log(arr1.sort()); // [“a”, “b”, “c”, “d”] arr2 = [13, 24, 51, 3]; console.log(arr2.sort()); // [13, 24, 3, 51] console.log(arr2); // 13, 24, 3, 51
1. 为了解决上述问题,sort()方法可以接收一个比较函数作为参数,以便我们指定哪个值位于哪个值的前面。比较函数接收两个参数,如果第一个参数应该位于第二个之前则返回一个负数,如果两个参数相等则返回 0,如果第一个参数应该位于第二个之后则返回一个正数。以下就是一个简单的比较函数:
function compare(value1, value2) { if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } } arr2 = [13, 24, 51, 3]; console.log(arr2.sort(compare)); // [3, 13, 24, 51]
1. 如果需要通过比较函数产生降序排序的结果,只要交换比较函数返回的值即可:
function compare(value1, value2) { if (value1 < value2) { return 1; } else if (value1 > value2) { return -1; } else { return 0; } } arr2 = [13, 24, 51, 3]; console.log(arr2.sort(compare)); // [51, 24, 13, 3]
1. 5、reverse():反转数组项的顺序。
var arr = [13, 24, 51, 3]; console.log(arr.reverse()); //[3, 51, 24, 13] console.log(arr); //3, 51, 24, 13
1. 6、concat()
1. concat() :将参数添加到原数组中。这个方法会先创建当前数组一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组。在没有给 concat()方法传递参数的情况下,它只是复制当前数组并返回副本。
var arr = [1,3,5,7]; var arrCopy = arr.concat(9,[11,13]); console.log(arrCopy); //[1, 3, 5, 7, 9, 11, 13] console.log(arr); // 1, 3, 5, 7
1. 从上面测试结果可以发现:传入的不是数组,则直接把参数添加到数组后面,如果传入的是数组,则将数组中的各个项添加到数组中。但是如果传入的是一个二维数组呢?
var arrCopy2 = arr.concat([9,[11,13]]); console.log(arrCopy2); //[1, 3, 5, 7, 9, Array[2]] console.log(arrCopy2[5]); //[11, 13]
1. 上述代码中,arrCopy2数组的第五项是一个包含两项的数组,也就是说concat方法只能将传入数组中的每一项添加到数组中,如果传入数组中有些项是数组,那么也会把这一数组项当作一项添加到arrCopy2中。
1. 7、slice()
1. slice():返回从原数组中指定开始下标到结束下标之间的项组成的新数组。slice()方法可以接受一或两个参数,即要返回项的起始和结束位置。在只有一个参数的情况下, slice()方法返回从该参数指定位置开始到当前数组末尾的所有项。如果有两个参数,该方法返回起始和结束位置之间的项——但不包括结束位置的项。
var arr = [1,3,5,7,9,11]; var arrCopy = arr.slice(1); var arrCopy2 = arr.slice(1,4); var arrCopy3 = arr.slice(1,-2); var arrCopy4 = arr.slice(-4,-1); console.log(arr); //1, 3, 5, 7, 9, 11 console.log(arrCopy); //[3, 5, 7, 9, 11] console.log(arrCopy2); //[3, 5, 7] console.log(arrCopy3); //[3, 5, 7] console.log(arrCopy4); //[5, 7, 9]
1. arrCopy只设置了一个参数,也就是起始下标为1,所以返回的数组为下标1(包括下标1)开始到数组最后。
1. arrCopy2设置了两个参数,返回起始下标(包括1)开始到终止下标(不包括4)的子数组。
1. arrCopy3设置了两个参数,终止下标为负数,当出现负数时,将负数加上数组长度的值(6)来替换该位置的数,因此就是从1开始到4(不包括)的子数组。
1. arrCopy4中两个参数都是负数,所以都加上数组长度6转换成正数,因此相当于slice(2,5)。
1. 8、splice()
1. splice():很强大的数组方法,它有很多种用法,可以实现删除、插入和替换。
1. 删除:可以删除任意数量的项,只需指定 2 个参数:要删除的第一项的位置和要删除的项数。例如, splice(0,2)会删除数组中的前两项。
1. 插入:可以向指定位置插入任意数量的项,只需提供 3 个参数:起始位置、 0(要删除的项数)和要插入的项。例如,splice(2,0,4,6)会从当前数组的位置 2 开始插入4和6。
1. 替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定 3 个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。例如,splice (2,1,4,6)会删除当前数组位置 2 的项,然后再从位置 2 开始插入4和6。
1. splice()方法始终都会返回一个新数组,该数组中包含从原始数组中删除的项,如果没有删除任何项,则返回一个空数组。
var arr = [1,3,5,7,9,11]; var arrRemoved = arr.splice(0,2); console.log(arr); //[5, 7, 9, 11] console.log(arrRemoved); //[1, 3] var arrRemoved2 = arr.splice(2,0,4,6); console.log(arr); // [5, 7, 4, 6, 9, 11] console.log(arrRemoved2); // [] var arrRemoved3 = arr.splice(1,1,2,4); console.log(arr); // [5, 2, 4, 4, 6, 9, 11] console.log(arrRemoved3); //[7]
1. 9、indexOf()和 lastIndexOf()
1. indexOf():接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中, 从数组的开头(位置 0)开始向后查找。
1. lastIndexOf:接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中, 从数组的末尾开始向前查找。
1. 这两个方法都返回要查找的项在数组中的位置,或者在没找到的情况下返回1。在比较第一个参数与数组中的每一项时,会使用全等操作符。
var arr = [1,3,5,7,7,5,3,1]; console.log(arr.indexOf(5)); //2 console.log(arr.lastIndexOf(5)); //5 console.log(arr.indexOf(5,2)); //2 console.log(arr.lastIndexOf(5,4)); //2 console.log(arr.indexOf(“5”)); //-1
1. 10、forEach()
1. forEach():对数组进行遍历循环,对数组中的每一项运行给定函数。这个方法没有返回值。参数都是function类型,默认有传参,参数分别为:遍历的数组内容;第对应的数组索引,数组本身。
var arr = [1, 2, 3, 4, 5]; arr.forEach(function(x, index, a){ console.log(x + ‘|’ + index + ‘|’ + (a === arr)); }); // 输出为: // 1|0|true // 2|1|true // 3|2|true // 4|3|true // 5|4|true
1. 11、map()
1. map():指“映射”,对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。
1. 下面代码利用map方法实现数组中每个数求平方。
var arr = [1, 2, 3, 4, 5]; var arr2 = arr.map(function(item){ return item*item; }); console.log(arr2); //[1, 4, 9, 16, 25]
1. 12、filter()
1. filter():“过滤”功能,数组中的每一项运行给定函数,返回满足过滤条件组成的数组。
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; var arr2 = arr.filter(function(x, index) { return index % 3 === 0 || x >= 8; }); console.log(arr2); //[1, 4, 7, 8, 9, 10]
1. 13、every()
1. every():判断数组中每一项都是否满足条件,只有所有项都满足条件,才会返回true。
var arr = [1, 2, 3, 4, 5]; var arr2 = arr.every(function(x) { return x < 10; }); console.log(arr2); //true var arr3 = arr.every(function(x) { return x < 3; }); console.log(arr3); // false
1. 14、some()
1. some():判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true。
var arr = [1, 2, 3, 4, 5]; var arr2 = arr.some(function(x) { return x < 3; }); console.log(arr2); //true var arr3 = arr.some(function(x) { return x < 1; }); console.log(arr3); // false
1. 15、reduce()和 reduceRight()
1. 这两个方法都会实现迭代数组的所有项,然后构建一个最终返回的值。reduce()方法从数组的第一项开始,逐个遍历到最后。而 reduceRight()则从数组的最后一项开始,向前遍历到第一项。
1. 这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)作为归并基础的初始值。
1. 传给 reduce()和 reduceRight()的函数接收 4 个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。
1. 下面代码用reduce()实现数组求和,数组一开始加了一个初始值10。
var values = [1,2,3,4,5]; var sum = values.reduceRight(function(prev, cur, index, array){ return prev + cur; },10); console.log(sum); //25
- 数组去重的方法
- 双重for循环去重
- 原理 两两比较如果相等的话就删除第二个
- 例如: 1 1 1 3 2 1 2 4
- 先让第一个1 即arr[0]与后面的一个个比较 如果后面的值等于arr[0] 删除后面的值
- 第一次结束后的结果是 1 3 2 2 4 删除了后面所有的1
- 同理 第二次 第三会删除与自己相同的元素
function noRepeat1(arr){ // 第一层for用来控制循环的次数 for(var i=0; i //第二层for 用于控制与第一层比较的元素 for(var j=i+1; j //如果相等 if(arr[i] == arr[j]){ //删除后面的 即第 j个位置上的元素 删除个数 1 个 arr.splice(j,1); // j—很关键的一步 如果删除 程序就会出错 //j—的原因是 每次使用splice删除元素时 返回的是一个新的数组 // 这意味这数组下次遍历是 比较市跳过了一个元素 / 例如: 第一次删除后 返回的是 1 1 3 2 1 2 4 但是第二次遍历是 j的值为2 arr[2] = 3 相当于跳过一个元素 因此要 j— */ j—; } } } return arr; }
1. 2. 单层for循环
1. 原理和方法一相似
function norepeat(arr){ arr.sort(); //先排序让大概相同的在一个位置,这里为什么说是大概相同 是因为sort排序是把元素当字符串排序的 它和可能排成 1 1 10 11 2 20 3 … 不是我们想要的从小到大 for(var i = 0; i < arr.length-1;i++){ //还是两两比较 一样删除后面的 if(arr[i]==arr[i+1]){ arr.splice(i,1); //i— 和j—同理 i—; } } return arr; }
1. 3. 原理:用一个空数组去存首次 出现的元素
1. 利用 indexOf 属性 indexOf是返回某个指定的字符在字符串中出现的位置,如果没有就会返回-1
1. 因此我们可以很好的利用这个属性 当返回的是 -1时 就让其存入数组
1. <br />
function noRepeat2(arr){ var newArr = []; for(var i = 0; i < arr.length; i++){ if(newArr.indexOf(arr[i]) == -1){ newArr.push(arr[i]); } } return newArr; }
1. 4. 原理:利用对象的思想 如果对象里没有这个属性的话就会返回undefined
1. 利用这个原理当返回的是undefined时 让其放入数组 然后在给这个属性赋值
function norepeat3(arr) { var obj = {}; var newArr = []; for(var i = 0; i < arr.length; i++) { if(obj[arr[i]] == undefined) { newArr.push(arr[i]); obj[arr[i]] = 1; } } return newArr; }
1. 5. 原理:循环比较如果相等的让后面的元素值为0 最后在输出的时候删除为0的 这个前提是你的数据里不能有0 但是凡事可以变通你可以设置任何值替代这个0
1. 这个方法是我当时想到实现的所以没有进行很好的优化
var newArr = []; //控制外循环 for(var i=0; i //内存循环 只比较后面的 for(j=i+1;j //如果相等就让其值等于0 if(arr[i]==arr[j]){ arr[j]=0; } } //去除值为0的 if(arr[i]==0){ continue; }else{ //放入新的数组 newArr.push(arr[i]); } }
1. 6. for循环嵌套,利用splice去重
1. 此方法是比较常用的方法之一,也是es5中比较实用的方法之一。话不多说,上代码:
function newArr(arr){ for(var i=0;i for(var j=i+1;j if(arr[i]==arr[j]){ //如果第一个等于第二个,splice方法删除第二个 arr.splice(j,1); j—; } } } return arr; } var arr = [1,1,2,5,6,3,5,5,6,8,9,8]; console.log(newArr(arr))
1. 7. 建新数组,利用indexOf去重
1. 此方法也是es5中比较简单的方法之一,基本思路是新建一个数组,原数组遍历传入新数组,判断值是否存在,值不存在就加入该新数组中;值得一提的是,方法“indexOf”是es5的方法,IE8以下不支持。话不多说,上代码:
function newArr(array){ //一个新的数组 var arrs = []; //遍历当前数组 for(var i = 0; i < array.length; i++){ //如果临时数组里没有当前数组的当前值,则把当前值push到新数组里面 if (arrs.indexOf(array[i]) == -1){ arrs.push(array[i]) }; } return arrs; } var arr = [1,1,2,5,5,6,8,9,8]; console.log(newArr(arr))
1. 8. ES6中利用Set去重
1. 此方法是所有去重方法中代码最少的方法,代码如下:
function newArr(arr){ return Array.from(new Set(arr)) } var arr = [1,1,2,9,6,9,6,3,1,4,5]; console.log(newArr(arr))
- 数组遍历的几种方法及用法
- 1、forEach方法
- forEach是最简单、最常用的数组遍历方法,它提供一个回调函数,可用于处理数组的每一个元素,默认没有返回值。
- 1、forEach方法

1. 以上是个简单的例子,计算出数组中大于等于3的元素的个数。
1. 回调函数的参数,第一个是处于当前循环的元素,第二个是该元素下标,第三个是数组本身。三个参数均可选。
1. 2、map方法
1. map,从字面上理解,是映射,即数组元素的映射。它提供一个回调函数,参数依次为处于当前循环的元素、该元素下标、数组本身,三者均可选。默认返回一个数组,这个新数组的每一个元素都是原数组元素执行了回调函数之后的返回值。
1. map方法不改变原数组。

1. <br />

1. 以上是一个简单的例子,把原数组的每一项乘以自身下标+1的数。
1. 3、filter方法
1. filter,过滤,即对数组元素的一个条件筛选。它提供一个回调函数,参数依次为处于当前循环的元素、该元素下标、数组本身,三者均可选。默认返回一个数组,原数组的元素执行了回调函数之后返回值若为true,则会将这个元素放入返回的数组中。
1. filter方法不改变原数组

1. <br />

1. 以上是一个简单的例子,筛选出原数组中,自身乘以下标大于等于3的元素。
1. 4、some、every方法
1. some方法和every的用法非常类似,提供一个回调函数,参数依次为处于当前循环的元素、该元素下标、数组本身,三者均可选。
1. 数组的每一个元素都会执行回调函数,当返回值全部为true时,every方法会返回true,只要有一个为false,every方法返回false。当有一个为true时,some方法返回true,当全部为false时,every方法返回false。
1. some、every方法不改变原数组。

1. <br />

1. 5、reduce方法
1. reduce方法有两个参数,第一个参数是一个回调函数(必须),第二个参数是初始值(可选)。回调函数有四个参数,依次为本轮循环的累计值、当前循环的元素(必须),该元素的下标(可选),数组本身(可选)。
1. reduce方法,会让数组的每一个元素都执行一次回调函数,并将上一次循环时回调函数的返回值作为下一次循环的初始值,最后将这个结果返回。
1. 如果没有初始值,则reduce会将数组的第一个元素作为循环开始的初始值,第二个元素开始执行回调函数。
1. 最常用、最简单的场景,是数组元素的累加、累乘。

1. <br />

1. reduce方法不改变原数组
1. 6、for of方法
1. es6新增了interator接口的概念,目的是对于所有数据结构提供一种统一的访问机制,这种访问机制就是for of。
1. 即:所有有interator接口的数据,都能用for of遍历。常见的包括数组、类数组、Set、Map等都有interator接口。

1. <br />

1. 如果想用for of的方法遍历数组,又想用Index,可以用for of遍历arr.entries()

1. <br />

- javascript中的关联数组
- 基本概念:
- “关联数组”是一种具有特殊索引方式的数组。不仅可以通过整数来索引它,还可以使用字符串或者其他类型的值(除了NULL)来索引它。关联数组的索引值是任意的标量,这些标量称为Keys,可以在以后用于检索数组中的数值。关联数组的元素没有特定的顺序。
- 关联数组长成什么样?
- 基本概念:
var defs = [W3C: “World Wide Web Consortium”, DOM: “Document Object Model”];
1. 如何定义关联数组?
var defs = []; defs[key] = value; 备注:key 和 value 需要分别赋予不同的值。
1. 如何遍历关联数组?
for (key in defs) { // 变量 key 可以直接使用。 var value = defs[key]; //每个key对于的值。 }
- 什么是类数组?
- 数组的特征有:可以通过角标调用,如 array[0];具有长度属性 length;可以通过 for 循环或 forEach 方法,进行遍历。
- 那么,类数组是什么呢?顾名思义,就是具备与数组特征类似的对象。比如,下面的这个对象,就是一个类数组。
let arrayLike = { 0: 1, 1: 2, 2: 3, length: 3, };
1. 类数组 arrayLike 可以通过角标进行调用,具有 length 属性,同时也可以通过 for 循环进行遍历。
1. 类数组,还是比较常用的,只是我们平时可能没注意到。比如,我们获取 DOM 节点的方法,返回的就是一个类数组。再比如,在一个方法中使用 arguments 获取到的所有参数,也是一个类数组。
1. 但是需要注意的是:类数组无法使用 forEach、splice、push 等数组原型链上的方法,毕竟它不是真正的数组。
- forEach 遍历所有元素

- every 判断所有元素是否都符合条件

- some 判断是否有至少一个元素符合条件

- sort 排序

- map 对元素重新组装,生成新数组

- filter 过滤符合条件的元素


- DOM、BOM
- DOM


1、树
1. DOM事件级别

1. DOM事件模型
1. prototype

1. Attribute


1. DOM事件流

1. 描述DOM事件捕获的具体流程

1. Event对象的常见应用
1. event.preventDefault() 阻止默认事件
1. event.stopPropagation() 阻止冒泡
1. event.stopImmediatePropagation() 事件响应
1. event.currentTarget()
1. event.target()
2. 自定义事件

1. 自定义事件 Event 与 CustomEvent
1. window.addEventListener() 添加事件监听
1. window.dispatchEvent() 抛出事件
1. Event算是一个顶级接口,CustomEvent基于Event,增加了部分参数
1. Event
event = new Event(typeArg, eventInit);
typeArg
是DOMString 类型,表示所创建事件的名称。
eventInit可选
是 EventInit 类型的字典,接受以下字段:
“bubbles”,可选,Boolean类型,默认值为 false,表示该事件是否冒泡。
“cancelable”,可选,Boolean类型,默认值为 false, 表示该事件能否被取消。
“composed”,可选,Boolean类型,默认值为 false,指示事件是否会在影子DOM根节点之外触发侦听器。
window.addEventListener(‘custom’, customHandler) function customHandler(params) { // 打印事件对象 在5秒后 出现打印,可以看到我们自定义的参数 console.log(params) } setTimeout(() => { // 创建自定义事件 let event = new Event(‘custom’); // 如果希望事件带参数,可以把参数放在事件对象上 event.name = ‘custom-name’; event.detail = { age: 20 } event.ppp = ‘这是一个锅’ // dispatchEvent 返回一个 boolean 值 let result = window.dispatchEvent(event) console.log(result); }, 5000)
1. CustomEvent
event = new CustomEvent(typeArg, customEventInit);
typeArg
一个表示 event 名字的字符串
customEventInit可选
一个字典类型参数,有如下字段
“detail”, 可选的默认值是 null 的任意类型数据,是一个与 event 相关的值
在展示使用detail作为第二个参数的例子前,要先注意一件事:CustomEventInit字典也可以接受EventInit字典的参数,就像一开始的例子一样,我传递了EventInit字典的bubbles、cancelable、composed。
bubbles 一个布尔值,表示该事件能否冒泡。 来自 EventInit。注意:测试chrome默认为不冒泡。
cancelable 一个布尔值,表示该事件是否可以取消。 来自 EventInitwindow.addEventListener('custom', customHandler) function customHandler(params) { // 打印事件对象 在5秒后 出现打印,可以看到我们自定义的参数 console.log(params) } setTimeout(() => { // 创建事件对象 let event = new CustomEvent('Eric', { // 这里可直接传入 自定义的事件参数 detail: { height: 100, widht: 100, rect: 10000 } }) // 同样 我们也可以直接在事件对象上绑定 参数 [event.name](http://event.name/) = 'custom-event' window.dispatchEvent(event) }, 5000)<script type="text/javascript"> /* 创建一个事件对象,名字为newEvent,类型为build */ var newEvent = new CustomEvent('build', { bubbles:true,cancelable:true,composed:true }); /* 给这个事件对象创建一个属性并赋值,这里绑定的事件要和我们创建的事件类型相同,不然无法触发 */ [newEvent.name](http://newevent.name/) = "新的事件!"; /* 将自定义事件绑定在document对象上 */ document.addEventListener("build",function(){ alert("你触发了使用CustomEvent创建的自定义事件!" + [newEvent.name](http://newevent.name/)); },false) /* 触发自定义事件 */ document.dispatchEvent(newEvent); </script>
下面将展示使用detail参数的例子,使用到detail的部分我会加粗处理(为了看着方便,这回就不传递EventInit字典中的参数了)
- BOM

1. 如何检测浏览器类型

1. 拆解url各部分

1. <br />



- 事件绑定、事件冒泡、代理、事件节流


- 通用事件绑定

- 事件冒泡

- 代理

1. 代理的好处
1. 代码简洁
1. 减少浏览器内存占用
- 事件节流

- Ajax、跨域、Http协议



- 跨域
- 什么是跨域
- 浏览器是有同源策略的,不允许ajax访问其他域接口
- 跨域条件:协议、域名、端口,有一个不同就跨域
- 允许跨域加载资源的标签 ,标签的使用场景
用于打点统计,统计网站可能是其他域
- // b.html window.onmessage = function(e) { console.log(e.data) //我爱你 e.source.postMessage(‘我不爱你’, e.origin) }
1. WebSocket 1. CORS- 安全

- CSRF
- 基本概念和缩写
- CSRF ,通俗称为跨站请求伪造,英文名Cross-site request forgery 缩写CSRF
- 攻击原理
- 网站存在漏洞,并用户登陆过
- 基本概念和缩写

1. 防御措施 1. Token验证 1. Referer验证 1. 隐藏令牌- XSS
- 基本概念和缩写
- XSS(cross-site scripting跨域脚本攻击)
- 攻击原理
- 向页面注入JS
- 防御措施
- 让XSS不可执行
- XSS跨站请求攻击
- 新浪博客写一篇文章,同时偷偷插入一段
- 攻击代码中,获取cookie,发送自己的服务器
- 发布博客,有人查看博客内容
- 会把查看者的cookie发送到攻击者的服务器
- 前端替换关键字,例如替换 < 为 <;> 为>;
- 后端替换
- 基本概念和缩写
- CSRF、XSS区别
- XSS: 向页面注入JS运行,JS函数体内进行执行操作
- CSRF: 利用本身漏洞执行接口
- 渲染机制

- 什么是DOCTYPE及作用
- DTD(document type definition,文档类型定义)是一系列的语法规则,用来定义XML或(X)HTML的文件类型。浏览器会使用它来判断文档类型,决定使用何种协议来解析,以及切换浏览器模式【DTD告诉浏览器我们是什么类型的文档,要用什么解析和渲染】
- DOCTYPE是用来声明文档类型和DTD规范的,一个主要的用途便是文件的合法性验证。【DOCTYPE通知浏览器告诉当前文档包含哪个DTD哪个文档类型】
- 如果文件代码不合法,那么浏览器解析时便会出一些差错
- HTML5 <!DOCTYPE html> HTML 4.01 Strict 该DTD包含所有HTML元素和属性,但不包括展示性的和弃用的元素(比如font) <!DOCTYPE HTML PUBLIC “-/W3C/DTD HTML 4.01//EN” “http://www.w3.org/TR/html4/strict.dtd“> HTML 4.01 Transitional 该DTD包含所有HTML元素和属性,包括展示性的和弃用的元素(比如font) <!DOCTYPE HTML PUBLIC “-/W3C/DTD HTML 4.01 Transitional//EN” “http://www.w3.org/TR/html4/loose.dtd“>
- 浏览器渲染过程





- 重排Reflow
- 定义
- DOM结构中的各个元素都有自己的盒子(模型),这些都需要浏览器根据各种样式来计算并根据计算结果将元素放到它该出现的位置,这个过程称之为reflow
- 触发Reflow
- 当你增加、删除、修改DOM结点时,会导致Reflow或Repaint
- 当你移动DOM的位置,或是搞个动画的时候
- 当你修改CSS样式的时候
- 当你Resize窗口的时候(移动端没有这个问题),或是滚动的时候
- 当你修改网页的默认字体时
- 定义
- 重绘Repaint
- 定义
- 当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来后,浏览器于是便把这些元素都按照各自的特性绘制了一遍,于是页面的内容出现了,这个过程称之为repaint
- 触发Repaint
- DOM改动
- CSS改动
- 定义
- 布局Layout
- JS运行机制
console.log(1); setTimeout(function () { console.log(3); }, 0); console.log(2);
console.log(‘A’); while(true){ } console.log(‘B’)
for (var i = 0; i < 4; i++) { setTimeout(function () { console.log(i); }, 1000); }
- 如何理解JS单线程
- 同一个时间只能做一件事。
- JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。
- 为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完
- 全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
- 什么是任务队列
- 什么是Event Loop
- 那些语句会放入异步任务队列,放入异步任务队列的时机
- setTimeout和setInterval
- DOM事件
- ES6中的Promise

- 页面性能
- 提升页面性能的方法有哪些?
- 1、资源压缩合并,减少HTTP请求
- 2、非核心代码异步加载 一一一> 异步加载的方式 一一一> 异步加载的区别
- 3、利用浏览器缓存 一一一> 缓存的分类 ———> 缓存的原理
- 4、使用CDN|
- 5、预解析DNS
- 异步加载
- 1、异步加载的方式
- 1)动态脚本加载
- 2)defer、
- 3)async
- 2、异步加载的区别,
- 1)defer是在HTML解析完之后才会执行,如果是多个,按照加载的顺序依次执行
- 2)async是在加载完之后立即执行,如果是多个,执行顺序和加载顺序无关
- 浏览器缓存
- 1、缓存的分类
- 1)强缓存
- 1、缓存的分类
- 1、异步加载的方式
- 提升页面性能的方法有哪些?
Expires Expires: Thu, 21 Jan 2017 23:39:02 GMT 【过期时间,绝对时间(服务器)】
Cache-Control Cache-Control: max-age=3600【相对时间(客户端)】1. 2)协商缓存|Last-Modified If-Modified-Since Last-Modified:Wed, 26 Jan 2017 00:35:11 GMT Etag If-None-Match
- 错误监控
- 前端错误的分类
- 即使运行代码:代码错误
- 资源加载错误
- 错误的捕获方式
- 即时运行错误的捕获方式
- 1) try..catch
- 2) window.onerror
- 资源加载错误
- 1) object.onerror
- 2) performance.getEntries()
- 3) Error事件捕获
- 延伸: 跨域的js运行错误可以捕获吗,错误提示什么,应该怎么处理?
- 即时运行错误的捕获方式
- 前端错误的分类

1. 1、在script标签增加crossorigin属性 1. 2、设置js资源响应头Access-Control-Allow-Origin:*- 上报错误的基本原理
- 采用Ajax通信的方式上报
- 利用Image对象上报
- call、apply 以及 bind 的区别和用法
- call 和 apply 的共同点
- 它们的共同点是,都能够改变函数执行时的上下文,将一个对象的方法交给另一个对象来执行,并且是立即执行的。
- 为何要改变执行上下文?举一个生活中的小例子:平时没时间做饭的我,周末想给孩子炖个腌笃鲜尝尝。但是没有适合的锅,而我又不想出去买。所以就问邻居借了一个锅来用,这样既达到了目的,又节省了开支,一举两得。
- 改变执行上下文也是一样的,A 对象有一个方法,而 B 对象因为某种原因,也需要用到同样的方法,那么这时候我们是单独为 B 对象扩展一个方法呢,还是借用一下 A 对象的方法呢?当然是借用 A 对象的啦,既完成了需求,又减少了内存的占用。
- 另外,它们的写法也很类似,调用 call 和 apply 的对象,必须是一个函数 Function。接下来,就会说到具体的写法,那也是它们区别的主要体现。
- 它们的共同点是,都能够改变函数执行时的上下文,将一个对象的方法交给另一个对象来执行,并且是立即执行的。
- call 和 apply 的区别
- 它们的区别,主要体现在参数的写法上。先来看一下它们各自的具体写法。
- call 的写法
Function.call(obj,[param1[,param2[,…[,paramN]]]])
1. 需要注意以下几点: 1. 调用 call 的对象,必须是个函数 Function。 1. call 的第一个参数,是一个对象。 Function 的调用者,将会指向这个对象。如果不传,则默认为全局对象 window。 1. 第二个参数开始,可以接收任意个参数。每个参数会映射到相应位置的 Function 的参数上。但是如果将所有的参数作为数组传入,它们会作为一个整体映射到 Function 对应的第一个参数上,之后参数都为空。 1. <br />function func (a,b,c) {} func.call(obj, 1,2,3) // func 接收到的参数实际上是 1,2,3 func.call(obj, [1,2,3]) //func 接收到的参数实际上是 [1,2,3],undefined,undefined apply 的写法 Function.apply(obj[,argArray])``
1. 需要注意的是: 1. 它的调用者必须是函数 Function,并且只接收两个参数,第一个参数的规则与 call 一致。 1. 第二个参数,必须是数组或者类数组,它们会被转换成类数组,传入 Function 中,并且会被映射到 Function 对应的参数上。这也是 call 和 apply 之间,很重要的一个区别。 1. <br />func.apply(obj, [1, 2, 3]); //func 接收到的参数实际上是 1,2,3 func.apply(obj, { 0: 1, 1: 2, 2: 3, length: 3, }); // func 接收到的参数实际上是 1,2,3- call 和 apply 的用途
- 下面会分别列举 call 和 apply 的一些使用场景。声明:例子中没有哪个场景是必须用 call 或者必须用 apply 的,只是个人习惯这么用而已。
- call 的使用场景
- 1、对象的继承。如下面这个例子:
function superClass() { this.a = 1; this.print = function () { console.log(this.a); }; } function subClass() { superClass.call(this); this.print(); }subClass(); // 1
1. subClass 通过 call 方法,继承了 superClass 的 print 方法和 a 变量。此外,subClass 还可以扩展自己的其他方法。 1. 2、借用方法。还记得刚才的类数组么?如果它想使用 Array 原型链上的方法,可以这样:let domNodes = Array.prototype.slice.call(document.getElementsByTagName(“*“)); 这样,domNodes 就可以应用 Array 下的所有方法了。
1. apply 的一些妙用 1. 1、Math.max。用它来获取数组中最大的一项。let max = Math.max.apply(null, array); 同理,要获取数组中最小的一项,可以这样: let min = Math.min.apply(null, array);
1. 2、实现两个数组合并。在 ES6 的扩展运算符出现之前,我们可以用 Array.prototype.push 来实现。let arr1 = [1, 2, 3]; let arr2 = [4, 5, 6]; Array.prototype.push.apply(arr1, arr2); console.log(arr1); // [1, 2, 3, 4, 5, 6]
1. bind 的使用 1. 最后来说说 bind。在 MDN 上的解释是:bind() 方法创建一个新的函数,在调用时设置 this 关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。 1. 它的语法如下:Function.bind(thisArg[, arg1[, arg2[, …]]])
1. bind 方法 与 apply 和 call 比较类似,也能改变函数体内的 this 指向。不同的是,bind 方法的返回值是函数,并且需要稍后调用,才会执行。而 apply 和 call 则是立即调用。 1. 来看下面这个例子:function add(a, b) { return a + b; } function sub(a, b) { return a - b; } add.bind(sub, 5, 3); // 这时,并不会返回 8 add.bind(sub, 5, 3)(); // 调用后,返回 8
1. 如果 bind 的第一个参数是 null 或者 undefined,this 就指向全局对象 window。- 总结
- call 和 apply 的主要作用,是改变对象的执行上下文,并且是立即执行的。它们在参数上的写法略有区别。
- bind 也能改变对象的执行上下文,它与 call 和 apply 不同的是,返回值是一个函数,并且需要稍后再调用一下,才会执行。
- 关于 call 和 apply 的便捷记忆法:
猫吃鱼,狗吃肉,奥特曼打小怪兽。 有天狗想吃鱼了 猫.吃鱼.call(狗,鱼) 狗就吃到鱼了 猫成精了,想打怪兽 奥特曼.打小怪兽.call(猫,小怪兽) 猫也可以打小怪兽了
- call:call 是函数的正常调用方式,并指定上下文 this。
- apply:apply 的作用和 call 一样,只是在调用的时候,传参数的方式不同。区别是 apply 接受的是数组参数,call 接受的是连续参数。如下代码:
function **add**(*a*,*b*){ return a+b; } add.**call**(add, 5, 3); //*8* add.**apply**(add, [5, 3]); //*8*- 复制代码bind:bind 接受的参数跟 call 一致,只是 bind 不会立即调用,它会生成一个新的函数,你想什么时候调就什么时候调。如下代码:
function **add**(*a*, *b*){ return a+b; } var foo1 = add.**bind**(add, 5,3); **foo1**(); //*8*- call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
- 在一个子构造函数中,你可以通过调用父构造函数的 call 方法来实现继承,类似于 Java 中的写法。下例中,使用 Food 和 Toy 构造函数创建的对象实例都会拥有在 Product 构造函数中添加的 name 属性和 price 属性,但 category 属性是在各自的构造函数中定义的。
function **Product**(*name*, *price*) {[this.name](http://this.name/) = name; this.price = price; } function **Food**(*name*, *price*) { Product.**call**(this, name, price); this.category = "food"; } function **Toy**(*name*, *price*) { Product.**call**(this, name, price); this.category = "toy"; } var cheese = new **Food**("feta", 5); var fun = new **Toy**("robot", 40);- bind()最简单的用法是创建一个函数,不论怎么调用,这个函数都有同样的 this 值。JavaScript 新手经常犯的一个错误是将一个方法从对象中拿出来,然后再调用,期望方法中的 this 是原来的对象(比如在回调中传入这个方法)。如果不做特殊处理的话,一般会丢失原来的对象。基于这个函数,用原始的对象创建一个绑定函数,巧妙地解决了这个问题:
this.x = 9; //* 在浏览器中,this 指向全局的 "window" 对象* var module = { x: 81, **getX**: function () { return this.x; }, }; module.**getX**(); //* 81* var retrieveX = module.getX; **retrieveX**(); //* 返回 9 - 因为函数是在全局作用域中调用的* //* 创建一个新函数,把 'this' 绑定到 module 对象* //* 新手可能会将全局变量 x 与 module 的属性 x 混淆* var boundGetX = retrieveX.**bind**(module); **boundGetX**(); //* 81*- JavaScript apply() 方法
- apply() 方法与 call() 方法非常相似:
- 在本例中,person 的 fullName 方法被应用到 person1:
var person = {fullName: function () { return this.firstName + “ “ + this.lastName; }, }; var person1 = { firstName: “Bill”, lastName: “Gates”, }; person.fullName.apply(person1); // 返回 ‘Bill Gates’
- call() 和 apply() 之间的区别
- 不同之处是:
- call() 方法分别接受参数。
- apply() 方法接受数组形式的参数。
- 如果要使用数组而不是参数列表,则 apply() 方法非常方便。
- 不同之处是:
- 每个函数都包含两个非继承而来的方法:call()方法和 apply()方法。
- 1.相同点: 这两个方法的作用是一样的。
- 都是在特定的作用域中调用函数,等于设置函数体内 this 对象的值,以扩充函数赖以运行的作用域。
- 一般来说,this 总是指向调用某个方法的对象,但是使用 call()和 apply()方法时,就会改变 this 的指向。
- call()方法使用示例:
1. apply()方法使用示例:1. 2. 不同点: 接收参数的方式不同。 1. apply()方法 接收两个参数,一个是函数运行的作用域(this),另一个是参数数组。 1. 语法:apply([thisObj [,argArray] ]);,调用一个对象的一个方法,2 另一个对象替换当前对象。 1. ****说明:****如果 argArray 不是一个有效数组或不是 arguments 对象,那么将导致一个 1. TypeError,如果没有提供 argArray 和 thisObj 任何一个参数,那么 Global 对象将用作 thisObj。 2. call()方法 第一个参数和 apply()方法的一样,但是传递给函数的参数必须列举出来。 1. 语法:call([thisObject[,arg1 [,arg2 [,...,argn]]]]);,应用某一对象的一个方法,用另一个对象替换当前对象。 1. 说明: call 方法可以用来代替另一个对象调用一个方法,call 方法可以将一个函数的对象上下文从初始的上下文改变为 thisObj 指定的新对象,如果没有提供 thisObj 参数,那么 Global 对象被用于 thisObj。 2. 使用示例 1:function add(c,d){ return this.a + this.b + c + d; } var s = {a:1, b:2 }; console.log(add.call(s,3,4)); // 1+2+3+4 = 10 console.log(add.apply(s,[5,6])); // 1+2+5+6 = 14
1. 使用示例 2:- 手动实现 apply
Function.prototype.myCall = function (context) { let obj = context || window obj.fn = this let args = [] for (let i = 1, len = arguments.length; i < len; i++) { args.push(arguments[i]) } const result = obj.fn(…args) return result } // test function add(c, d) { return this.a + this.b + c + d; } const obj = { a: 1, b: 2 }; console.log(add.myCall(obj, 3, 4)); // 10 console.log(add.myCall({ a: 3, b: 9 }, 3, 4)); // 19 console.log(add.myCall({ a: 3, b: 9 }, { xx: 1 }, 4)); // 12[object Object]4
- 手动实现 apply
Function.prototype.myApply = function (object, arr) { let obj = object || window obj.fn = this let result if (!arr) { return obj.fn() } if (!(arr instanceof Array)) { throw new Error(‘params must be array’) } result = obj.fn(…arr) delete obj.fn return result } // test console.log(add.myApply(obj)); // NaN console.log(add.myApply(obj, [3, 4])); // 10 console.log(add.myApply(obj, [1, ‘abc’, ‘2’])); // 4abc
- call 和 apply 的作用与区别
- 共同的作用:call 和 apply 都是用来修改函数中 this 的指向问题;
- 其次就是它们不同的传参方式:注意上一句话中说他们的作用时有两个关键词 ‘函数’和‘this’,想要修改 this 的指向,那么必然有一个 this 修改后的指向,而函数必然后关系到传参问题:call 方法可以传给该函数的参数分别作为自己的多个参数,而 apply 方法必须将传给该函数的参数合并成一个数组作为自己的一个参数:
- eg:
var name = ‘Evan’; var age = 20; var person = { name: ‘Hillary’, age: 19, sayIntroduce: function () { return “Hello, My name is “ + this.name + “ and I’m “ + this.age + ‘ years old.’ }, sayHobby: function (val1, val2) { return “I’m “ + this.name + “, I like “ + val1 + “ and “ + val2 + “.”; } } var person1 = { name: ‘Coy’ } console.log(person.sayIntroduce()); // Hello, My name is Hillary and I’m 19 years old.
- 当我们通过 call 和 apply 来 this 的指向时,不传任何参数,则默认为将 this 指向修改为 windows
- // 当没有参数时,默认将 this 指向 window
console.log(person.sayIntroduce.call()); // Hello, My name is Evan and I’m 20 years old. console.log(person.sayIntroduce.apply()); // Hello, My name is Evan and I’m 20 years old.
- 有参数时,this 指向第一个参数:
- // 将 this 指向 person1,由于 person1 中没有 age 属性,因此为 undefined
console.log(person.sayIntroduce.call(person1)); // Hello, My name is Coy and I'm undefined years old. console.log(person.sayIntroduce.apply(person1)); // Hello, My name is Coy and I'm undefined years old.- 当需要传递参数时,call 可以直接写多个参数,apply 需要用数组方式传递:
console.log(person.sayHobby.call(person1, ‘swimming’, ‘hiking’)); // I’m Coy, I like swimming and hiking. console.log(person.sayHobby.apply(person1, [‘swimming’, ‘hiking’])); // I’m Coy, I like swimming and hiking.
- 下面是一个构造函数的例子:
- //构造函数应用
function Grade(max, min, average) { this.max = max; this.min = min; this.average = average; } function Subject(subjectName,max, min, average) { Grade.call(this, max, min, average); this.subjectName = subjectName; } var math = new Subject('math', 99, 60, 80); console.log(math);- 面向过程编程(pop),面向对象编程(oop),函数式编程(fp)
- 1、概念
- 面向过程编程(procedure oriented Programming)(POP)
- 面向对象编程(object oriented programming)(OOP)
- 函数式编程(functional programming)(FP)
- 2、解释
- 面向过程编程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
- 面向对象编程是把构成问题的事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
- 我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
- 而函数式编程(请注意多了一个“式”字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。
- 我们首先要搞明白计算机(Computer)和计算(Compute)的概念。
- 在计算机的层次上,CPU 执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言是最贴近计算机的语言。
- 而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远。
- 对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如 C 语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如 Lisp 语言。
- 函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
- 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
- 3、举例
- 例子 1
- 例如五子棋,面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤 2,9、输出最后结果。把上面每个步骤用分别的函数来实现,问题就解决了。
- 而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为 1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的 i 变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
- 可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了总多步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。
- 功能上的统一保证了面向对象设计的可扩展性。比如我要加入悔棋的功能,如果要改动面向过程的设计,那么从输入到判断到显示这一连串的步骤都要改动,甚至步骤之间的循序都要进行大规模调整。如果是面向对象的话,只用改动棋盘对象就行了,棋盘系统保存了黑白双方的棋谱,简单回溯就可以了,而显示和规则判断则不用顾及,同时整个对对象功能的调用顺序都没有变化,改动只是局部的。
- 再比如我要把这个五子棋游戏改为围棋游戏,如果你是面向过程设计,那么五子棋的规则就分布在了你的程序的每一个角落,要改动还不如重写。但是如果你当初就是面向对象的设计,那么你只用改动规则对象就可以了,五子棋和围棋的区别不就是规则吗?(当然棋盘大小好像也不一样,但是你会觉得这是一个难题吗?直接在棋盘对象中进行一番小改动就可以了。)而下棋的大致步骤从面向对象的角度来看没有任何变化。
- 当然,要达到改动只是局部的需要设计的人有足够的经验,使用对象不能保证你的程序就是面向对象,初学者或者很蹩脚的程序员很可能以面向对象之虚而行面向过程之实,这样设计出来的所谓面向对象的程序很难有良好的可移植性和可扩展性。
- 例子 2:
- 用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。所谓盖浇饭,北京叫盖饭,东北叫烩饭,广东叫碟头饭,就是在一碗白米饭上面浇上一份盖菜,你喜欢什么菜,你就浇上什么菜。我觉得这个比喻还是比较贴切的。
- 蛋炒饭制作的细节,我不太清楚,因为我没当过厨师,也不会做饭,但最后的一道工序肯定是把米饭和鸡蛋混在一起炒匀。盖浇饭呢,则是把米饭和盖菜分别做好,你如果要一份红烧肉盖饭呢,就给你浇一份红烧肉;如果要一份青椒土豆盖浇饭,就给浇一份青椒土豆丝。
- 蛋炒饭的好处就是入味均匀,吃起来香。如果恰巧你不爱吃鸡蛋,只爱吃青菜的话,那么唯一的办法就是全部倒掉,重新做一份青菜炒饭了。盖浇饭就没这么多麻烦,你只需要把上面的盖菜拨掉,更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。
- 到底是蛋炒饭好还是盖浇饭好呢?其实这类问题都很难回答,非要比个上下高低的话,就必须设定一个场景,否则只能说是各有所长。如果大家都不是美食家,没那么多讲究,那么从饭馆角度来讲的话,做盖浇饭显然比蛋炒饭更有优势,他可以组合出来任意多的组合,而且不会浪费。
- 盖浇饭的好处就是”菜”“饭”分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是”可维护性“比较好,”饭” 和”菜”的耦合度比较低。蛋炒饭将”蛋”“饭”搅和在一起,想换”蛋”“饭”中任何一种都很困难,耦合度很高,以至于”可维护性”比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在 3 个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性。
- 例子 1
- 总结 :
- 面向过程
- 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix 等一般采用面向过程开发,性能是最重要的因素。
- 缺点:没有面向对象易维护、易复用、易扩展
- 面向对象
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
- 缺点:性能比面向过程低
- 面向过程
- 一、面向对象与面向过程的区别
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
- 可以拿生活中的实例来理解面向过程与面向对象,例如五子棋,面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤 2,9、输出最后结果。把上面每个步骤用不同的方法来实现。
- 如果是面向对象的设计思想来解决问题。面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为 1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
- 可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了多个步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。
- 上述的内容是从网上查到的,觉得这个例子非常的生动形象,我就写了下来,现在就应该理解了他俩的区别了吧,其实就是两句话,面向对象就是高度实物抽象化、面向过程就是自顶向下的编程!
- 二、面向对象的特点
- 在了解其特点之前,咱们先谈谈对象,对象就是现实世界存在的任何事务都可以称之为对象,有着自己独特的个性
- 属性用来描述具体某个对象的特征。比如小志身高 180M,体重 70KG,这里身高、体重都是属性。
- 面向对象的思想就是把一切都看成对象,而对象一般都由属性+方法组成!
- 属性属于对象静态的一面,用来形容对象的一些特性,方法属于对象动态的一面,咱们举一个例子,小明会跑,会说话,跑、说话这些行为就是对象的方法!所以为动态的一面, 我们把属性和方法称为这个对象的成员!
- 类:具有同种属性的对象称为类,是个抽象的概念。比如“人”就是一类,期中有一些人名,比如小明、小红、小玲等等这些都是对象,类就相当于一个模具,他定义了它所包含的全体对象的公共特征和功能,对象就是类的一个实例化,小明就是人的一个实例化!我们在做程序的时候,经常要将一个变量实例化,就是这个原理!我们一般在做程序的时候一般都不用类名的,比如我们在叫小明的时候,不会喊“人,你干嘛呢!”而是说的是“小明,你在干嘛呢!”
- 面向对象有三大特性,分别是封装性、继承性和多态性,这里小编不给予太多的解释,因为在后边的博客会专门总结的!
- 三、面向过程与面向对象的优缺点
- 用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。所谓盖浇饭,北京叫盖饭,东北叫烩饭,广东叫碟头饭,就是在一碗白米饭上面浇上一份盖菜,你喜欢什么菜,你就浇上什么菜。我觉得这个比喻还是比较贴切的。
- 蛋炒饭制作的细节,我不太清楚,因为我没当过厨师,也不会做饭,但最后的一道工序肯定是把米饭和鸡蛋混在一起炒匀。盖浇饭呢,则是把米饭和盖菜分别做好,你如果要一份红烧肉盖饭呢,就给你浇一份红烧肉;如果要一份青椒土豆盖浇饭,就给浇一份青椒土豆丝。
- 蛋炒饭的好处就是入味均匀,吃起来香。如果恰巧你不爱吃鸡蛋,只爱吃青菜的话,那么唯一的办法就是全部倒掉,重新做一份青菜炒饭了。盖浇饭就没这么多麻烦,你只需要把上面的盖菜拨掉,更换一份盖菜就可以了。盖浇饭的缺点是入味不均,可能没有蛋炒饭那么香。
- 到底是蛋炒饭好还是盖浇饭好呢?其实这类问题都很难回答,非要比个上下高低的话,就必须设定一个场景,否则只能说是各有所长。如果大家都不是美食家,没那么多讲究,那么从饭馆角度来讲的话,做盖浇饭显然比蛋炒饭更有优势,他可以组合出来任意多的组合,而且不会浪费。
- 盖浇饭的好处就是”菜”“饭”分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用软件工程的专业术语就是”可维护性”比较好,“饭” 和”菜”的耦合度比较低。蛋炒饭将”蛋”“饭”搅和在一起,想换”蛋””饭”中任何一种都很困难,耦合度很高,以至于”可维护性”比较差。软件工程追求的目标之一就是可维护性,可维护性主要表现在 3 个方面:可理解性、可测试性和可修改性。面向对象的好处之一就是显著的改善了软件系统的可维护性。
- 总结:
- 面向过程
- 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix 等一般采用面向过程开发,性能是最重要的因素。
- 缺点:没有面向对象易维护、易复用、易扩展
- 面向对象
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
- 缺点:性能比面向过程低
- 面向过程
- 面向对象与面向过程语言的区别
- 计算机语言可以归为面向过程语言和面向对象语言,那么到底什么是面向对象,什么是面向过程呢?
- 答:面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
- 可以拿生活中的实例来理解面向过程与面向对象,例如五子棋,面向过程的设计思路就是首先分析问题的步骤:1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤 2,9、输出最后结果。把上面每个步骤用不同的方法来实现。
- 如果是面向对象的设计思想来解决问题。面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为 1、黑白双方,这两方的行为是一模一样的,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
- 可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了多个步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。
- 面向过程
- 概念
- 面向过程是一种以过程为中心的 编程思想,它是一种基础的顺序的思维方式,面向对象方法的基础实现中也包含面向过程思想。
- 特性:模块化 流程化
- 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开 发,性能是最重要的因素。
- 缺点:没有面向对象易维护、易复用、易扩展
- 概念
- 面向对象
- 概念
- 面向对象是按人们认识客观世界的 系统思维方式,采用基于对象(实体)的概念建立模型,模拟客观世界分析、设计、实现软件的办法。通过面向对象的理念使计算机软件系统能与现实世界中的系统一一对应。
- 特性:抽象 封装 继承 多态
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
- 缺点:性能比面向过程低
- 概念
- 面向对象与面向过程具体而言,有如下几个方面的不同:
- 1、出发点不同
- 面向对象方法是用符合常规思维的方式来处理客观世界的问题,强调把问题域的要领直接映射到对象之间的接口上。而面向过程的方法则强调的则是过程的抽象化与模块化,它是以过程为中心构造或处理客观世界问题的。
- 2、层次逻辑关系不同
- 面向对象的方法则是用计算机逻辑来模拟客观世界中的物理存在的,以对象的集合类作为处理问题的单位,尽可能地使计算机世界向客观世界靠拢,以使问题的处理更清晰直接,面向对象方法是用类的层次结构来体现类之间的继承与发展。面向过程方法处理问题的基本单位是能清晰准确地表达过程的模块,用模块的层次结构概括模块或模块间的关系与功能,把客观世界的问题抽象成计算机可以处理的过程。
- 3、数据处理方式与控制程序方式不同
- 面向对象方法将数据与对应的代码封装成一个整体,原则上其他对象不能直接修改其数据,即对象的修改只能由自身的成员函数完成,控制程序方式上是通过“事件驱动”来激活和运行程序。而面向过程方法是直接通过程序来处理数据,处理完毕后即可显示处理的结果,在控制方式上是按照设计调用或返回程序,不能自由导航,各模块之间存在着控制与被控制,调动与被调用的关系。
- 4、分析设计与编码转换方式不同
- 面向对象方法贯穿于软件生命周期的分析,设计及编码中,是一种平滑过程,从分析到设计再到编码是采用一致性的模型表示,即实现的是一种无缝连接。而面向过程方法强调分析、设计及编码之间按规则进行转换贯穿于软件生命周期的分析、设计及编码中,实现的是一种有缝的连接
- 什么是面向过程?什么是面向对象?
- 1.什么是面向过程
- 仔细思考一下,我们在学习和工作中,当我们去实现某项功能或
- 完成某项任务时,是不是会不自觉的按部就班的罗列出我们要做的事情?而当我们按着我们罗列的步骤去解决问题时,实质上就是按照面向过程的思想去解决问题。我们罗列的步骤就是过程,按照步骤解决问题就是面向过程。
- 传统的面向过程的编程思想总结起来就八个字——自顶向下,逐步细化!
- 实现步骤如下:
- (1)将要实现的功能描述为一个从开始到结束按部就班的连续的步骤(过程);
- (2)依次逐步完成这些步骤,如果某一步的难度较大,又可以将该步骤再次细化为若干个子步骤,以此类推,一直到结束得到想要的结果;
- (3)程序的主体是函数,一个函数就是一个封装起来的模块,可以实现一定的功能,各个子步骤往往就是通过各个函数来完成的,从而实现代码的重用和模块化编程!
- 实现步骤如下:
- 案例:学生到校报道注册:
- 面向过程
- 就是按照我们分析好了的步骤,按部就班的依次执行就行了!所以当我们用面向过程的思想去编程或解决问题时,首先一定要把详细的实现过程弄清楚。一旦过程设计清楚,代码的实现简直轻而易举。
- 2.面向对象
- 所谓的面向对象,就是在编程的时候尽可能的去模拟真实的现实世界,按照现实世界中的逻辑去处理一个问题,分析问题中参与其中的有哪些实体,这些实体应该有什么属性和方法,我们如何通过调用这些实体的属性和方法去解决问题。现实世界中,任何一个操作或者是业务逻辑的实现都需要一个实体来完成,也就是说,实体就是动作的支配者,没有实体,就肯定没有动作发生!现在让我们思考下,上述注册报名的每一个步骤都有哪些动词?提出 提供 缴 收 获得 分配 增加有动词就一定有实现这个动作的实体!
- 所谓的模拟现实世界,就是使计算机的编程语言在解决相关业务逻辑的方式,与真实的业务逻辑的发生保持一致!需要使每一个动作的背后都一个完成这个动作的实体!(比如三层架构中的 Dao、Service、Action)因为任何功能的实现都是依赖于一个具体的实体的“动作|操作|行动”,可以看作是一个又一个的实体在发挥其各自的“能力”并在内部进行协调有序的调用过程!
- 当采用面向对象的思想解决问题时,可分为下面几步:
- (1)分析哪些动作是由哪些实体发出的;
- (2)定义这些实体,为其增加相应的属性和功能;
- (3)让实体去执行相应的功能或动作。
- 采用面向对象的思想,解决上面的报名问题,应该如下:
- 第一步:分析那些动作是由哪些实体发出的学生提出报名学生缴费机构收费教师分配教室班级增加学生信息
- 于是,在整个过程中,一共有四个实体:学生、机构、教师、班级!
- 在现实中的一个具体的实体,就是计算机编程中的一个对象!
- 第一步:分析那些动作是由哪些实体发出的学生提出报名学生缴费机构收费教师分配教室班级增加学生信息
- 第二步:定义这些实体,为其增加相应的属性和功能属性就是实体在现实世界中的一些特征表现。如:人的属性:姓名、性别、身高、三围、体重、电话号码、家庭住址、籍贯等手机的属性:品牌、价格、颜色、尺寸、待机时间等功能就是能完成的动作,在面向对象的术语中,动作就叫作方法或者函数。如:人的动作(功能):吃饭、睡觉、学习、打游戏、走路、跑步、缴费!手机的动作(功能):打电话、发短信、拍照、打游戏、视频、看电影等
- 下图显示了在上述实例中出现的实体以及相应的属性和功能:
- 第三步:让实体去执行相应的功能或动作
- 学生提出报名
- 学生缴费
- 学校收费
- 教师分配教室
- 班级增加学生信息
- 所以说,面向过程主要是针对功能,而面向对象主要是针对能够实现该功能的背后的实体。面向对象实质上就是面向实体,所以当我们使用面向对象进行编程时,一定要建立这样一个观念:万物皆对象!面向对象当中又会有面向过程,比如“学校收费这一步”,如果比较复杂,就需要细化出多个步骤,这又有点像面向过程
- 面向过程
- 3.面向对象和面向过程的比较
- 在我们将面向过程和面向对象讨论完后,会明显的感觉两者之间有着很大的区别。面向过程简单直接,易于入门理解,模块化程度较低。而面向对象相对于面向过程较为复杂,不易理解,模块化程度较高。可总结为下面三点:
- 都可以实现代码重用和模块化编程,但是面对对象的模块化更深,数据更封闭,也更安全!因为面向对象的封装性更强!面对对象的思维方式更加贴近于现实生活,更容易解决大型的复杂的业务逻辑从前期开发角度上来看,面对对象远比面向过程要复杂,但是从维护和扩展功能的角度上来看,面对对象远比面向过程要简单!
- 如何选择面向对象还是面向过程,对于一个有着丰富开发经验的老手来说,这是个得心应手的过程。而对于一个新手而言,其实从两者的对比就可以看出,当我们的业务逻辑比较简单时,使用面向过程能更快的实现。但是当我们的业务逻辑比较复杂时,为了将来的维护和扩展,还是面向对象更为靠谱点。
- 4.在举例子说明面向过程和面向对象的区别,加深理解
- 五子棋系统
- (1)面向过程的设计思路:
- 五子棋系统
- 1、概念
步骤一:开始游戏 步骤二:黑子先走 步骤三:绘制画面 步骤四:判断输赢 步骤五:轮到白子 步骤六:绘制画面 步骤七:判断输赢, 步骤八:返回步骤 2 步骤九:输出最后结果。
1. (2)面向对象的设计思路:整个五子棋可以分为: 黑白双方,这两方的行为是一模一样的 棋盘系统,负责绘制画面 * 规则系统,负责判定诸如犯规、输赢等。 第一类对象(玩家对象)负责接受用户输入,并告知第二 类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了 棋子的变化就要负责在屏幕上面显示出这种变化,同时利 用第三类对象(规则系统)来对棋局进行判定。
- 5.什么是面向对象、什么是面向过程一段比较抽象的解释,
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;
- 面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
- 面向对象与面向过程的本质的区别
- 巧妙的使用模板字符串生成 dom 元素
- 通常,我们拼接 dom 元素字符串是这样的…
function createDOM(option) { var dom = ‘
‘; return dom; }- 看起来似乎没有什么毛病,挺好的,但是,一旦这个 dom 元素数据庞大复杂一些,这种方式看起来就好比看天书…
- 来看看优化方案
function createDOM(option) { // 使用数组方式拆分标签元素并适当缩进进行格式化,看起来结构非常清晰 var dom = [ ‘
‘ ].join(‘’); // [].join(‘’)将数组进行拼接 // 将 title 替换模板中的{#title#} return formateString(dom, option); } // 将 data 中的数据替换模板中的{#内容#} function formateString(str, data) { return str.replace(/{#(\w+)#}/g, function(match, key) { return typeof data[key] === undefined ? ‘’ : data[key] }) }- 测试
- createDOM({title: ‘标题’, src: ‘http://xx/xx/xx.com’, alt: ‘提示’})
- 输出结果
- 浅谈 JavaScript 模拟$(HTML 字符串)实现创建 DOM 对象
- JavaScript 里动态创建标准 DOM 对象一般使用:document.createElement()方法。
- 但在实际使用过程中,可能会希望直接根据 HTML 字符串创建 DOM 节点,模拟$(HTML 字符串)创建 DOM 对象的方法。
- 1、思路:
① 用 document.createElement()方法创建一个 div 元素; ② 用 innerHTML 来给 div 元素插入 HTML 字符串; ③ 用 div.childNodes[0]即可返回 HTML 字符串对应创建的 DOM 对象。
1. 2、代码:function createNode(htmlStr) { var div = document.createElement(“div”); div.innerHTML = htmlStr; return div.childNodes[0]; }
- 判断函数是否作为构造函数
- 构造函数中 this 指向 new 创建的实例。所以可以通过在函数内判断 this 是否为当前函数的实例进而判断当前函数是否作为构造函数。
function F(){ if(this instanceof F){ console.log(‘我现在是作为构造函数’) } else { console.log(‘我现在是普通函数’) } } F(); // 我现在是普通函数 new F(); // 我现在是作为构造函数
- 彻底理解回调函数
- 1、前奏
- 在谈回调函数之前,先看下下面两段代码:
- 不妨猜测一下代码的结果。
- 1、前奏
function say (value) { alert(value); } alert(say); alert(say(‘hi js.’));
1. 如果你测试了,就会发现: 1. 只写变量名 say 返回的将会是 say 方法本身,以字符串的形式表现出来。 1. 而在变量名后加()如 say()返回的就会使 say 方法调用后的结果,这里是弹出 value 的值。- 2、js 中函数可以作为参数传递
- 再看下面的两段代码:
function say (value) { alert(value); } function execute (someFunction, value) { someFunction(value); } execute(say, 'hi js.'); 与 function execute (someFunction, value) { someFunction(value); } execute(function(value){alert(value);}, 'hi js.');1. 上面第一段代码是将 say 方法作为参数传递给 execute 方法 1. 第二段代码则是直接将匿名函数作为参数传递给 execute 方法 1. 实际上:function say (value) { alert(value); } // 注意看下面,直接写 say 方法的方法名与下面的匿名函数可以认为是一个东西 // 这样再看上面两段代码是不是对函数可以作为参数传递就更加清晰了 say; function (value) { alert(value); }
1. 这里的 say 或者匿名函数就被称为回调函数。- 3、回调函数易混淆点——传参
- 如果回调函数需要传参,如何做到,这里介绍两种解决方案。
- 将回调函数的参数作为与回调函数同等级的参数进行传递

1. 回调函数的参数在调用回调函数内部创建 1. <br />
- JS eval()函数
- eval()函数,这个函数可以把一个字符串当作一个 JavaScript 表达式一样去执行它。
- 举个小例子:
//执行表达式 var the_unevaled_answer = “2 + 3”; var the_evaled_answer = eval(“2 + 3”); alert(“the un-evaled answer is “ + the_unevaled_answer + “ and the evaled answer is “ + the_evaled_answer);
- 如果你运行这段 eval 程序, 你将会看到在 JavaScript 里字符串”2 + 3”实际上被执行了。
- 所以当你把 the_evaled_answer 的值设成 eval(“2 + 3”)时, JavaScript 将会明白并把 2 和 3 的和返回给 the_evaled_answer。
- 这个看起来似乎有点傻,其实可以做出很有趣的事。比如使用 eval 你可以根据用户的输入直接创建函数。
- 这可以使程序根据时间或用户输入的不同而使程序本身发生变化,通过举一反三,你可以获得惊人的效果。
- 在实际中,eval 很少被用到,但也许你见过有人使用 eval 来获取难以索引的对象。
- 文档对象模型(DOM)的问题之一是:有时你要获取你要求的对象简直就是痛苦。
- 例如,这里有一个函数询问用户要变换哪个图象:变换哪个图象你可以用下面这个函数:
function swapOne() { var the_image = prompt(“change parrot or cheese”,””); var the_image_object; if (the_image == “parrot”) { the_image_object = window.document.parrot; } else { the_image_object = window.document.cheese; } the_image_object.src = “ant.gif”; } 连同这些 image 标记: [img src=”http://a.com/a.gif“ name=”parrot”] [img src=”http://b.com/b.gif“ name=”cheese”] [code] 请注意象这样的几行语句: [code] the_image_object = window.document.parrot;
- 它把一个图象对象敷给了一个变量。虽然看起来有点儿奇怪,它在语法上却毫无问题。
- 但当你有 100 个而不是两个图象时怎么办?你只好写上一大堆的 if-then-else 语句,要是能象这样就好了:
function swapTwo() { var the_image = prompt(“change parrot or cheese”,””); window.document.the_image.src = “ant.gif”; }
- 不幸的是, JavaScript 将会寻找名字叫 the_image 而不是你所希望的”cheese”或者”parrot”的图象,
- 于是你得到了错误信息:”没听说过一个名为 the_image 的对象”。
- 还好,eval 能够帮你得到你想要的对象。
function simpleSwap() { var the_image = prompt(“change parrot or cheese”,””); var the_image_name = “window.document.” + the_image; var the_image_object = eval(the_image_name); the_image_object.src = “ant.gif”; }
- 如果用户在提示框里填入”parrot”,在第二行里创建了一个字符串即 window.document.parrot. 然后包含了 eval 的第三
- 行意思是: “给我对象 window.document.parrot” - 也就是你要的那个图象对象。一旦你获取了这个图象对象,你可以把
- 它的 src 属性设为 ant.gif. 有点害怕?用不着。其实这相当有用,人们也经常使用它。
- 我们常常在 Javascript 中间到 Eval 这个函数,
- 有些人觉得这个函数很奇怪,可以把一些字符串变的功能很强大
- 在我们需要将普通的字符串转变成具体的对象的时候,就会用到这个函数
- eval 函数对作为数字表达式的一个字符串进行求值,其语法为:
- eval(expr)
- 此处 expr 是一个被求值的字符串参数。如果该字符串是一个表达式,eval 求该表达式的值;如果该参数代表一个或多个 JavaScript 语句,那么 eval 执行这些语句。eval 函数可以用来把一个日期从一种格式(总是字符串)转换为数值表达式或数字
- js 面向对象之 new
- 对象是什么
- 面向对象编程(Object Oriented Programming,缩写为 OOP)是目前主流的编程范式。它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。
- 每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。对象可以复用,通过继承机制还可以定制。因此,面向对象编程具有灵活、代码可复用、高度模块化等特点,容易维护和开发,比起由一系列函数或指令组成的传统的过程式编程(procedural programming),更适合多人合作的大型软件项目。
- 那么,“对象”(object)到底是什么?我们从两个层次来理解。
- 对象是单个实物的抽象。
- 一本书、一辆汽车、一个人都可以是对象,一个数据库、一张网页、一个与远程服务器的连接也可以是对象。当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就可以模拟现实情况,针对对象进行编程。
- 对象是一个容器,封装了属性(property)和方法(method)。
- 属性是对象的状态,方法是对象的行为(完成某种任务)。比如,我们可以把动物抽象为 animal 对象,使用“属性”记录具体是那一种动物,使用“方法”表示动物的某种行为(奔跑、捕猎、休息等等)。
- 构造函数
- 面向对象编程的第一步,就是要生成对象。前面说过,对象是单个实物的抽象。通常需要一个模板,表示某一类实物的共同特征,然后对象根据这个模板生成。
- 典型的面向对象编程语言(比如 C++ 和 Java),都有“类”(class)这个概念。
- 所谓“类”就是对象的模板,对象就是“类”的实例。但是,JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)。
- JavaScript 语言使用构造函数(constructor)作为对象的模板。所谓”构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。
- 构造函数就是一个普通的函数,但是有自己的特征和用法。
var Vehicle = function () { this.price = 1000; };
1. 上面代码中,Vehicle 就是构造函数。为了与普通函数区别,构造函数名字的第一个字母通常大写。- 构造函数的特点有两个。
- 函数体内部使用了 this 关键字,代表了所要生成的对象实例。
- 生成对象的时候,必须使用 new 命令。
- 下面先介绍 new 命令。
- new 命令
- 基本用法
- new 命令的作用,就是执行构造函数,返回一个实例对象。
var Vehicle = function () { this.price = 1000; }; var v = new Vehicle(); v.price // 1000
1. 上面代码通过 new 命令,让构造函数 Vehicle 生成一个实例对象,保存在变量 v 中。 1. 这个新生成的实例对象,从构造函数 Vehicle 得到了 price 属性。 1. new 命令执行时,构造函数内部的 this,就代表了新生成的实例对象,this.price 表示实例对象有一个 price 属性,值是 1000。 1. 有参数构造器 1. 使用 new 命令时,根据需要,构造函数也可以接受参数。var Vehicle = function (p) { this.price = p; }; var v = new Vehicle(500);
1. 是否带括号 1. new 命令本身就可以执行构造函数,所以后面的构造函数可以带括号,也可以不带括号。 2. 下面两行代码是等价的,但是为了表示这里是函数调用,推荐使用括号。// 推荐的写法 var v = new Vehicle(); // 不推荐的写法 var v = new Vehicle;
1. 忘记使用 new 1. 一个很自然的问题是,如果忘了使用 new 命令,直接调用构造函数会发生什么事? 1. 这种情况下,构造函数就变成了普通函数,并不会生成实例对象。而且由于后面会说到的原因,this 这时代表全局对象,将造成一些意想不到的结果。var Vehicle = function (){ this.price = 1000; }; var v = Vehicle(); v // undefined price // 1000
1. 上面代码中,调用 Vehicle 构造函数时,忘了加上 new 命令。结果,变量 v 变成了 undefined,而 price 属性变成了全局变量。因此,应该非常小心,避免不使用 new 命令、直接调用构造函数。- 保证构造器和 new 一起使用
- 为了保证构造函数必须与 new 命令一起使用,一个解决办法是,构造函数内部使用严格模式,即第一行加上 use strict。
- 这样的话,一旦忘了使用 new 命令,直接调用构造函数就会报错。
function Fubar(foo, bar){ ‘use strict’; this._foo = foo; this._bar = bar; } Fubar() // TypeError: Cannot set property ‘_foo’ of undefined
1. 另一个解决办法,构造函数内部判断是否使用 new 命令,如果发现没有使用,则直接返回一个实例对象。function Fubar(foo, bar) { if (!(this instanceof Fubar)) { return new Fubar(foo, bar); } this._foo = foo; this._bar = bar; } Fubar(1, 2)._foo // 1 (new Fubar(1, 2))._foo // 1
- new 命令的原理
- 使用 new 命令时,它后面的函数依次执行下面的步骤。
- 创建一个空对象,作为将要返回的对象实例。
- 将这个空对象的原型,指向构造函数的 prototype 属性。
- 将这个空对象赋值给函数内部的 this 关键字。
- 开始执行构造函数内部的代码。
- 也就是说,构造函数内部,this 指的是一个新生成的空对象,所有针对 this 的操作,都会发生在这个空对象上。
- 构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即 this 对象),将其“构造”为需要的样子。
- 如果构造函数内部有 return 语句,而且 return 后面跟着一个对象,new 命令会返回 return 语句指定的对象;否则,就会不管 return 语句,返回 this 对象。
- 使用 new 命令时,它后面的函数依次执行下面的步骤。
var Vehicle = function () { this.price = 1000; return 1000; }; (new Vehicle()) === 1000 // false
1. 上面代码中,构造函数 Vehicle 的 return 语句返回一个数值。这时,new 命令就会忽略这个 return 语句,返回“构造”后的 this 对象。 1. 但是,如果 return 语句返回的是一个跟 this 无关的新对象,new 命令会返回这个新对象,而不是 this 对象。这一点需要特别引起注意。var Vehicle = function (){ this.price = 1000; return { price: 2000 }; }; (new Vehicle()).price // 2000
- 另一方面,如果对普通函数(内部没有 this 关键字的函数)使用 new 命令,则会返回一个空对象。
function getMessage() { return ‘this is a message’; } var msg = new getMessage(); msg // {} typeof msg // “object”
1. 上面代码中,getMessage 是一个普通函数,返回一个字符串。- 对它使用 new 命令,会得到一个空对象。这是因为 new 命令总是返回一个对象,要么是实例对象,要么是 return 语句指定的对象。
- 本例中,return 语句返回的是字符串,所以 new 命令就忽略了该语句。
- new 简化的内部流程
function _new(/ 构造函数 / constructor, / 构造函数参数 / params) { // 将 arguments 对象转为数组 var args = [].slice.call(arguments); // 取出构造函数 var constructor = args.shift(); // 创建一个空对象,继承构造函数的 prototype 属性 var context = Object.create(constructor.prototype); // 执行构造函数 var result = constructor.apply(context, args); // 如果返回结果是对象,就直接返回,否则返回 context 对象 return (typeof result === ‘object’ && result != null) ? result : context; }
- // 实例
var actor = _new(Person, ‘张三’, 28); new.target 函数内部可以使用 new.target 属性。如果当前函数是 new 命令调用,new.target 指向当前函数,否则为 undefined。 function f() { console.log(new.target === f); } f() // false new f() // true 使用这个属性,可以判断函数调用的时候,是否使用 new 命令。 function f() { if (!new.target) { throw new Error(‘请使用 new 命令调用!’); } // … } f() // Uncaught Error: 请使用 new 命令调用!
1. 上面代码中,构造函数 f 调用时,没有使用 new 命令,就抛出一个错误。- Object.create() 创建实例对象
- 构造函数作为模板,可以生成实例对象。但是,有时拿不到构造函数,只能拿到一个现有的对象。
- 我们希望以这个现有的对象作为模板,生成新的实例对象,这时就可以使用 Object.create() 方法。
var person1 = { name: ‘张三’, age: 38, greeting: function() { console.log(‘Hi! I’m ‘ + this.name + ‘.’); } }; var person2 = Object.create(person1);person2.name // 张三 person2.greeting() // Hi! I’m 张三.
1. 上面代码中,对象 person1 是 person2 的模板,后者继承了前者的属性和方法。- 浏览器渲染页面的过程,以及重绘和重排
- 浏览器的渲染过程
- 1,浏览器解析html源码,然后创建一个 DOM树。
- 在DOM树中,每一个HTML标签都有一个对应的节点,并且每一个文本也都会有一个对应的文本节点。
- DOM树的根节点就是 documentElement,对应的是html标签。
- 2,浏览器解析CSS代码,计算出最终的样式数据。
- 对CSS代码中非法的语法她会直接忽略掉。
- 解析CSS的时候会按照如下顺序来定义优先级:浏览器默认设置,用户设置,外链样式,内联样式,html中的style。
- 3,构建出DOM树,并且计算出样式数据后,下一步就是构建一个 渲染树(rendering tree)。
- 渲染树和DOM树有点像,但是是有区别的。DOM树完全和html标签一一对应,但是渲染树会忽略掉不需要渲染的元素,比如head、display:none的元素等。
- 而且一大段文本中的每一个行在渲染树中都是独立的一个节点。
- 渲染树中的每一个节点都存储有对应的css属性。
- 4,一旦渲染树创建好了,浏览器就可以根据渲染树直接把页面绘制到屏幕上。
- 1,浏览器解析html源码,然后创建一个 DOM树。
- 一个渲染过程的例子
- 例如有下面这样一段HTML代码:
- 浏览器的渲染过程
< html > < head > < title > Beautiful page </ title > </ head > < body > < p > Once upon a time there was a looong paragraph... </ p > < div style = " display: none " > Secret message </ div > < div >< img src = " ... " /></ div > ... </ body > </ html >1. 那么DOM树是完全和HTML标签一一对应的,如下所示:documentElement (html) head title body p [text node] div [text node] div img …
1. 而渲染树就不同了,她只有哪些需要绘制出来的元素,所以head 以及被隐藏的div都不会出现在渲染树中。root (RenderView) body p line 1 line 2 line 3 … div img …
- 重绘和重排(repaints and reflows)
- 每个页面至少在初始化的时候会有一次重排操作。任何对渲染树的修改都有可能会导致下面两种操作:
- 1,重排
- 就是渲染树的一部分必须要更新 并且节点的尺寸发生了变化。这就会触发重排操作。
- 2,重绘
- 部分节点需要更新,但是没有改变他的集合形状,比如改变了背景颜色,这就会触发重绘。
- 1,重排
- 什么情况下会触发重绘或重排
- 下面任何操作都会触发重绘或者重排:
- 增加或删除DOM节点
- 设置 display: none;(重排并重绘) 或者 visibility: hidden(只有重排)
- 移动页面中的元素
- 增加或者修改样式
- 用户 改变窗口大小,滚动页面等
- 看一个例子:
var bstyle = document . body . style ; // cache bstyle . padding = “ 20px “ ; // reflow, repaint bstyle . border = “ 10px solid red “ ; // another reflow and a repaint bstyle . color = “ blue “ ; // repaint only, no dimensions changed bstyle . backgroundColor = “ #fad “ ; // repaint bstyle . fontSize = “ 2em “ ; // reflow, repaint // new DOM element - reflow, repaint document . body . appendChild ( document . createTextNode ( ‘ dude! ‘ )) ;
- 有些重绘操作会比其他操作昂贵很多。比如你把一个body的子元素做了修改,不一定会导致大量的其他节点更新,但是你把一个元素移动到页面顶部去,可能就会导致全部其他节点进行重排操作,这个代价就非常昂贵。
- 聪明的浏览器
- 因为渲染树的改变导致的重绘或重排操作都可能代价很高,浏览器会对这个改动做很多优化。
- 一个策略就是不要立即做操作,而是批量进行。比如把你的脚本对DOM的修改放入一个队列,在队列所有操作结束后只需要进行一次绘制即可。
- 但是有的时候脚本可能会导致浏览器的批量优化无法进行,可能在清空队列之前就需要重新绘制(绘制意思是重绘或者重排)页面。比如你通过脚本获取这些样式:
offsetTop , offsetLeft , offsetWidth , offsetHeight scrollTop /Left/Width/Height clientTop /Left/Width/Height getComputedStyle() , or currentStyle in IE
- 因为浏览器必须给你最新的值,所以当你进行这些取值操作的时候会立刻触发一次页面的绘制。这样本来可以批量修改样式然后一次性绘制的方法就无法使用了。
- 减少重绘和重排
- 1,不要一个一个地单独修改属性,最好通过一个classname来定义这些修改
// bad var left = 10 , top = 10 ; el . style . left = left + “ px “ ; el . style . top = top + “ px “ ; // better el . className += “ theclassname “ ;
1. 2,把对节点的大量修改操作放在页面之外 1. 用 documentFragment来做修改 1. clone 节点,在clone之后的节点中做修改,然后直接替换掉以前的节点 1. 通过 display: none 来隐藏节点(直接导致一次重排和重绘),做大量的修改,然后显示节点(又一次重排和重绘),总共只会有两次重排。 2. 3,不要频繁获取计算后的样式。如果你需要使用计算后的样式,最好暂存起来而不是直接从DOM上读取。 2. 4,总的来说,总是考虑到渲染树得存在,考虑到你的一次修改会导致多大的绘制操作。 1. 比如绝对定位元素的动画就不会影响其他大部分元素。- 1.document.write和innerHTML的区别
- document.write重排整个页面
- innerHTML可以重绘页面的一部分
- 2.浏览器运行机制
- 1、构建DOM树(parse):渲染引擎解析HTML文档,首先将标签转换成DOM树中的DOM node(包括js生成的标签)生成内容树(Content Tree/DOM Tree);
- 2、构建渲染树(construct):解析对应的CSS样式文件信息(包括js生成的样式和外部css文件),而这些文件信息以及HTML中可见的指令(如),构建渲染树(Rendering Tree/Frame Tree);
- 3、布局渲染树(reflow/layout):从根节点递归调用,计算每一个元素的大小、位置等,给出每个节点所应该在屏幕上出现的精确坐标;
- 4、绘制渲染树(paint/repaint):遍历渲染树,使用UI后端层来绘制每个节点。
- 重绘(repaint或redraw):当盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来之后,浏览器便把这些原色都按照各自的特性绘制一遍,将内容呈现在页面上。重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。
- 触发重绘的条件:改变元素外观属性。如:color,background-color等。
- 重排(重构/回流/reflow):当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建, 这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。
- 重绘和重排的关系:在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘。
- 重排必定会引发重绘,但重绘不一定会引发重排。
- 触发重排的条件:任何页面布局和几何属性的改变都会触发重排,比如:
1、页面渲染初始化;(无法避免) 2、添加或删除可见的DOM元素; 3、元素位置的改变,或者使用动画; 4、元素尺寸的改变——大小,外边距,边框; 5、浏览器窗口尺寸的变化(resize事件发生时); 6、填充内容的改变,比如文本的改变或图片大小改变而引起的计算值宽度和高度的改变; 7、读取某些元素属性:(offsetLeft/Top/Height/Width, clientTop/Left/Width/Height, scrollTop/Left/Width/Height, width/height, getComputedStyle(), currentStyle(IE) )
- 重绘发生的情况:
- 重绘发生在元素的可见的外观被改变,但并没有影响到布局的时候。比如,仅修改DOM元素的字体颜色(只有Repaint,因为不需要调整布局)
- 重绘重排的代价:耗时,导致浏览器卡慢。
- 3.优化:
- 1、浏览器自己的优化:浏览器会维护1个队列,把所有会引起回流、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成一次回流重绘。
- 2、我们要注意的优化:我们要减少重绘和重排就是要减少对渲染树的操作,则我们可以合并多次的DOM和样式的修改。并减少对style样式的请求。
- (1)直接改变元素的className
- (2)display:none;先设置元素为display:none;然后进行页面布局等操作;设置完成后将元素设置为display:block;这样的话就只引发两次重绘和重排;
- (3)不要经常访问浏览器的flush队列属性;如果一定要访问,可以利用缓存。将访问的值存储起来,接下来使用就不会再引发回流;
- //例如myElement元素沿对角线移动,每次移动一个像素。到500*500像素的位置结束。timeout循环体中可以这么做
myElement.style.left = 1 + myElement.offsetLeft + ‘px’;myElement.style.top = 1 + myElement.offsetTop + ‘px’; if(myElement.offsetLeft >= 500){ stopAnimation(); }
1. //显然这种方法低效,每次移动都要查询偏移量,导致浏览器刷新渲染队列而不利于优化。好的办法是获取一次起始位置的值,然后赋值给一个变量。如下var current = myElement.offsetLeft; current++; myElement.style.left = current + ‘px’;myElement.style.top = current + ‘px’; if(myElement.offsetLeft >= 500){ stopAnimation(); }
1. (4)使用cloneNode(true or false) 和 replaceChild 技术,引发一次回流和重绘; 1. (5)将需要多次重排的元素,position属性设为absolute或fixed,元素脱离了文档流,它的变化不会影响到其他元素; 1. (6)如果需要创建多个DOM节点,可以使用DocumentFragment创建完后一次性的加入document;var fragment = document.createDocumentFragment(); var li = document.createElement(‘li’); li.innerHTML = ‘apple’; fragment.appendChild(li); var li = document.createElement(‘li’); li.innerHTML = ‘watermelon’; fragment.appendChild(li); document.getElementById(‘fruit’).appendChild(fragment);
1. (7)尽量不要使用table布局。- 关于作用域、作用域链和闭包的理解
- 作用域
- 函数作用域
- 先看一小段代码:
- 函数作用域
- 作用域
var scope=”global”; function t(){ console.log(scope); var scope=”local” console.log(scope); } t();
1. (PS: console.log()是firebug提供的调试工具,很好用,有兴趣的童鞋可以用下,比浏览器+alert好用多了) 1. 第一句输出的是: "undefined",而不是 "global" 1. 第二讲输出的是:"local" 1. 你可能会认为第一句会输出:"global",因为代码还没执行var scope="local",所以肯定会输出“global"。 1. 我说这想法完全没错,只不过用错了对象。我们首先要区分Javascript的函数作用域与我们熟知的C/C++等的块级作用域。 1. 在C/C++中,花括号内中的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的。而Javascript压根没有块级作用域,而是函数作用域. 1. 所谓函数作用域就是说:-》变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。 1. 所以根据函数作用域的意思,可以将上述代码重写如下:var scope=”global”; function t(){ var scope; console.log(scope); scope=”local” console.log(scope); } t();
1. 我们可以看到,由于函数作用域的特性,局部变量在整个函数体始终是由定义的,我们可以将变量声明”提前“到函数体顶部,同时变量初始化还在原来位置。 1. 为什么说Js没有块级作用域呢,有以下代码为证:var name=”global”; if(true){ var name=”local”; console.log(name) } console.log(name);
1. 都输出是“local",如果有块级作用域,明显if语句将创建局部变量name,并不会修改全局name,可是没有这样,所以Js没有块级作用域。 1. 现在很好理解为什么会得出那样的结果了。scope声明覆盖了全局的scope,但是还没有赋值,所以输出:”undefined“。 1. 所以下面的代码也就很好理解了。function t(flag){ if(flag){ var s=”ifscope”; for(var i=0;i<2;i++) ; } console.log(i); console.log(s); } t(true);
1. 输出:2 ”ifscope" 1. 变量作用域 1. 还是首先看一段代码:function t(flag){ if(flag){ s=”ifscope”; for(var i=0;i<2;i++) ; } console.log(i); } t(true); console.log(s);
1. 就是上面的翻版,知识将声明s中的var去掉。 1. 程序会报错还是输出“ifscope"呢? 1. 让我揭开谜底吧,会输出:”ifscope" 1. 这主要是Js中没有用var声明的变量都是全局变量,而且是顶层对象的属性。 1. 所以你用console.log(window.s)也是会输出“ifconfig" 1. 当使用var声明一个变量时,创建的这个属性是不可配置的,也就是说无法通过delete运算符删除 1. var name=1 ->不可删除 1. sex=”girl“ ->可删除 1. this.age=22 ->可删除 1. 变量的作用域 1. 两种:全局变量和局部变量。 2. 全局作用域: 1. 最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的: var outerVar = "outer"; function fn(){ console.log(outerVar); } fn();//result:outer 1. 局部作用域: 1. 和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,而对于函数外部是无法访问的,最常见的例如函数内部1. 需要注意的是,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量! function fn(){ innerVar = "inner"; } fn(); console.log(innerVar);// result:inner 1. 再来看一个代码: var scope = "global"; function fn(){ console.log(scope);//result:undefined var scope = "local"; console.log(scope);//result:local; } fn(); 1. 很有趣吧,第一个输出居然是undefined,原本以为它会访问外部的全局变量(scope=”global”),但是并没有。这可以算是javascript的一个特点,**只要函数内定义了一个局部变量,函数在解析的时候都会将这个变量“提前声明”**: var scope = "global"; function fn(){ var scope;//提前声明了局部变量 console.log(scope);//result:undefined scope = "local"; console.log(scope);//result:local; } fn(); 1. 然而,也不能因此草率地将局部作用域定义为:用var声明的变量作用范围起止于花括号之间。 1. javascript并没有块级作用域 1. 那什么是块级作用域? 1. 像在C/C++中,花括号内中的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的,比如下面的**c语言代码**:for(int i = 0; i < 10; i++){ //i的作用范围只在这个for循环 } printf(“%d”,&i);//error
1. 但是javascript不同,并没有所谓的块级作用域,javascript的作用域是相对函数而言的,可以称为函数作用域: for(var i = 1; i < 10; i++){ //coding } console.log(i); //10- 作用域链(Scope Chain)
- 先来看一段代码:
name=”lwy”; function t(){ var name=”tlwy”; function s(){ var name=”slwy”; console.log(name); } function ss(){ console.log(name); } s(); ss(); } t();
1. 当执行s时,将创建函数s的执行环境(调用对象),并将该对象置于链表开头,然后将函数t的调用对象链接在之后,最后是全局对象。然后从链表开头寻找变量name,很明显 1. name是"slwy"。 1. 但执行ss()时,作用域链是: ss()->t()->window,所以name是”tlwy" 1. 下面看一个很容易犯错的例子:1. 当文档加载完毕,给几个按钮注册点击事件,当我们点击按钮时,会弹出什么提示框呢? 1. 很容易犯错,对是的,三个按钮都是弹出:"Button4",你答对了吗? 1. 当注册事件结束后,i的值为4,当点击按钮时,事件函数即function(){ alert("Button"+i);}这个匿名函数中没有i,根据作用域链,所以到buttonInit函数中找,此时i的值为4, 1. 所以弹出”button4“。 1. 那什么是作用域链? 1. 我的理解就是,根据在内部函数可以访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问。 1. 想要知道js怎么链式查找,就得先了解js的执行环境 6. 执行环境(execution context) 1. 每个函数运行时都会产生一个执行环境,而这个执行环境怎么表示呢?js为每一个执行环境关联了一个变量对象。环境中定义的所有变量和函数都保存在这个对象中。 1. 全局执行环境是最外围的执行环境,全局执行环境被认为是window对象,因此所有的全局变量和函数都作为window对象的属性和方法创建的。 1. js的执行顺序是根据函数的调用来决定的,当一个函数被调用时,该函数环境的变量对象就被压入一个环境栈中。而在函数执行之后,栈将该函数的变量对象弹出,把控制权交给之前的执行环境变量对象。 1. 举个例子: var scope = "global"; function fn1(){ return scope; } function fn2(){ return scope; } fn1(); fn2(); 1. 上面代码执行情况演示:
1. 作用域链。 1. 当某个函数第一次被调用时,就会创建一个执行环境(execution context)以及相应的作用域链,并把作用域链赋值给一个特殊的内部属性([scope])。然后使用this,arguments(arguments在全局环境中不存在)和其他命名参数的值来初始化函数的活动对象(activation object)。当前执行环境的变量对象始终在作用域链的第0位。 1. 以上面的代码为例,当第一次调用fn1()时的作用域链如下图所示: 1. (因为fn2()还没有被调用,所以没有fn2的执行环境)
1. 可以看到fn1活动对象里并没有scope变量,于是沿着作用域链(scope chain)向后寻找,结果在全局变量对象里找到了scope,所以就返回全局变量对象里的scope值。 1. 标识符解析是沿着作用域链一级一级地搜索标识符地过程。搜索过程始终从作用域链地前端开始,然后逐级向后回溯,直到找到标识符为止(如果找不到标识符,通常会导致错误发生)—-《JavaScript高级程序设计》 1. 那作用域链地作用仅仅只是为了搜索标识符吗? 1. 再来看一段代码: function outer(){ var scope = "outer"; function inner(){ return scope; } return inner; } var fn = outer(); fn(); 1. outer()内部返回了一个inner函数,当调用outer时,inner函数的作用域链就已经被初始化了(复制父函数的作用域链,再在前端插入自己的活动对象),具体如下图:
1. 一般来说,当某个环境中的所有代码执行完毕后,该环境被销毁(弹出环境栈),保存在其中的所有变量和函数也随之销毁(全局执行环境变量直到应用程序退出,如网页关闭才会被销毁) 1. 但是像上面那种有内部函数的又有所不同,当outer()函数执行结束,执行环境被销毁,但是其关联的活动对象并没有随之销毁,而是一直存在于内存中,因为该活动对象被其内部函数的作用域链所引用。 1. 具体如下图: 1. outer执行结束,内部函数开始被调用 1. outer执行环境等待被回收,outer的作用域链对全局变量对象和outer的活动对象引用都断了
1. 像上面这种内部函数的作用域链仍然保持着对父函数活动对象的引用,就是**闭包(closure)** 1. with语句说到作用域链,不得不说with语句。with语句主要用来临时扩展作用域链,将语句中的对象添加到作用域的头部。
看下面代码
person={name:”yhb”,age:22,height:175,wife:{name:”lwy”,age:21}}; with(person.wife){ console.log(name); }
with语句将person.wife添加到当前作用域链的头部,所以输出的就是:“lwy”.
with语句结束后,作用域链恢复正常。1. 闭包 1. 闭包的简介: 1. 闭包就是能够读取其他函数内部变量的函数。只有函数内部的子函数才能读取局部变量,在本质上,闭包是函数内部和函数外部连接起来的桥梁。 1. 当函数可以记住并访问所在词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。 - - 出自《你不知道的JavaScript(上卷)》 2. 闭包的定义: 1. 如果在一个内部函数里,对在外部作用域(但不是全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。 3. 闭包的特点: 1. 可以读取自身函数外部的变量(沿着作用域链寻找)先从自身开始查找,如果自身没有才会继续往上级查找,自身如果拥有将直接调用。(哪个离的最近就用哪一个) 1. 延长内部变量的生命周期 1. 函数b嵌套在函数a内部 1. 函数a返回函数b 4. 闭包的作用: 1. 在函数a执行完并返回后,闭包使得JavaScript的垃圾回收机制不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量,闭包需要循序渐进的过程。 5. 闭包的构成: 1. 闭包由俩个部分构成:函数
以及创建该函数的环境1. 应用场景:保护函数内的变量安全。函数a中只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。
在内存中维持一个变量1. 了解JAVA的同学肯定知道JAVA是有私有方法的。私有方法只能被一个类中的其他方法所调用,但是JavaScript并没有,所以就需要用闭包来模拟。 1. 私有方法有利于限制对代码的访问,可以避免非核心的方法干扰代码的公共接口,减少全局污染。 1. demo:var test = (function() { var a = 1; function add(val){ a += val; } return { add1() { add(1); }, add2() { add(2); }, result() { return a; } } })();
1. 上面这种方式也叫做模块模式(module pattern)。关于设计模式请看:https://blog.csdn.net/weixin_43606158/article/details/90229052
1. 拓展: 1. 回收机制: 1. 在JavaScript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果俩个对象互相引用,而不再被第3者所引用,那么这俩个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。 3. 简单demo:var num = 6; function outer() { var num = 1; function inner() { var n = 2; alert(n + num); } return inner } const test = outer(); test();
1. 结果是 **3** 1. 闭包的缺点: 1. 滥用闭包会造成内存泄露,因为闭包中引用到的包裹函数中定义的变量都永远不会被释放,所以我们应该在必要的时候,及时释放这个闭包函数。 2. 闭包有两个作用: 1. 第一个就是可以读取自身函数外部的变量(沿着作用域链寻找) 1. 第二个就是让这些外部变量始终保存在内存中 1. 关于第二点,来看一下以下的代码: function outer(){ var result = new Array(); for(var i = 0; i < 2; i++){//注:i是outer()的局部变量 result[i] = function(){ return i; } } return result;//返回一个函数对象数组 //这个时候会初始化result.length个关于内部函数的作用域链 } var fn = outer(); console.log(fn[0]());//result:2 console.log(fn[1]());//result:2 1. 返回结果很出乎意料吧,你肯定以为依次返回0,1,但事实并非如此 1. 来看一下调用fn[0]()的作用域链图:
1. 可以看到result[0]函数的活动对象里并没有定义i这个变量,于是沿着作用域链去找i变量,结果在父函数outer的活动对象里找到变量i(值为2),而这个变量i是父函数执行结束后将最终值保存在内存里的结果。 1. 由此也可以得出,js函数内的变量值不是在编译的时候就确定的,而是等在运行时期再去寻找的。 1. 那怎么才能让result数组函数返回我们所期望的值呢? 1. 看一下result的活动对象里有一个arguments,arguments对象是一个参数的集合,是用来保存对象的。 1. 那么我们就可以把i当成参数传进去,这样一调用函数生成的活动对象内的arguments就有当前i的副本。 1. 改进之后: function outer(){ var result = new Array(); for(var i = 0; i < 2; i++){ //定义一个带参函数 function arg(num){ return num; } //把i当成参数传进去 result[i] = arg(i); } return result; } var fn = outer(); console.log(fn[0]);//result:0 console.log(fn[1]);//result:1 1. 虽然的到了期望的结果,但是又有人问这算闭包吗?调用内部函数的时候,父函数的环境变量还没被销毁呢,而且result返回的是一个整型数组,而不是一个函数数组! 1. 确实如此,那就让arg(num)函数内部再定义一个内部函数就好了: 1. 这样result返回的其实是innerarg()函数 function outer(){ var result = new Array(); for(var i = 0; i < 2; i++){ //定义一个带参函数 function arg(num){ function innerarg(){ return num; } return innerarg; } //把i当成参数传进去 result[i] = arg(i); } return result; } var fn = outer(); console.log(fn[0]()); console.log(fn[1]()); 1. 当调用outer,for循环内i=0时的作用域链图如下:
1. 由上图可知,当调用innerarg()时,它会沿作用域链找到父函数arg()活动对象里的arguments参数num=0. 1. 上面代码中,函数arg在outer函数内预先被调用执行了,对于这种方法,js有一种简洁的写法 function outer(){ var result = new Array(); for(var i = 0; i < 2; i++){ //定义一个带参函数 result[i] = function(num){ function innerarg(){ return num; } return innerarg; }(i);//预先执行函数写法 //把i当成参数传进去 } return result; } 1. 关于this对象 1. 关于闭包经常会看到这么一道题:var name = “The Window”; var object = { name : “My Object”, getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()());//result:The Window
1. 《javascript高级程序设计》一书给出的解释是: 1. this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象调用时,this等于那个对象。不过,匿名函数具有全局性,因此this对象同常指向windowjs 运算符
- 要进行各种各样的运算,就要使用不同的运算符号。
1、算术运算符:+、-、*、/、%、++、—
A = 10 + 20; A = 10 – 20; A = 10 * 20; A = 10 / 20;
- (1)“%”取余运算符,两个数相除,取余数。
- A = 10 % 3; // A = 1,如果余数不为0,则两个不能整除
- A = 10 % 2; // A = 0,如果余数为0,则两个数能除尽
- (1)“%”取余运算符,两个数相除,取余数。

1. **(2)“++”加1运算符、自加1** 1. “++”可以作前缀(++i),也可以作后缀(i++)。 1. **如果不赋值的话,i++和++i的结果是一样的。** 1. **如果要赋值的话,i++和++i的结果就不一样了** 1. <br />
1. **(3)“--”减1运算符,自减1** 1. “--”可以作前缀(--i),也可以作后缀(i--)。 1. **如果不赋值的话,i--和--i的结果是一样的。** 1. **如果要赋值的话,i++和++i的结果就不一样了**- 2、赋值运算符:=、+=、-=、*=、/=
- “+=”先加后等。如:a += 10 //展开后 a = a + 10
- “-=”先减后等。如:a -= 10 //展开后 a = a - 10
- “=”先乘后等。如:a = 10 //展开后 a = a * 10
- “/=”先除后等。如:a /= 10 //展开后 a = a / 10
- 3、字符串运算符:+、+=
- 字符串只能进行“连接”运算,不能进行其它运算。
- var a = “abc”;
- var b = a + “def”; // 结果b = a + “def” = “abc” + “def” = “abcdef”
- var a = “abc”;
- a += 10; // 结果a = a + 10 = “abc” + 10 = “abc10”
- 4、比较运算符:>、<、>=、<=、==、!=、===、!==
- 比较运算符的运算结果是布尔值(true或false)。
- A = 10 > 20; // 结果A=false
- A = 20>=20; // 结果A=true
- A = 10%2 == 0; // 结果A=true
- A = 10%2 == “0”; // 结果A=true
- A = 10%3 != 0; // 结果A=true
- A = 10%2 === “0”; //结果A=false
- “=”是赋值号。如:a = 10
- “==”等于。只比较两个变量的值,而不管类型。只要值一样,就返回true,否则返回false。
- “===”全等于。既比较变量,也判断类型。如果类型和值都一样,返回true,否则返回false。
- 比较运算符的运算结果是布尔值(true或false)。
- 5、逻辑运算符:&&、||、!
- 逻辑运算符的运算结果有两个true或false。
- “&&”逻辑与(并且关系)。如果左右两个操作数都为true,则结果为true,否则,结果为false。
- 逻辑与,就是两个条件同时满足时,则结果为true。
- “||”逻辑或。左右两个条件,只要有一个满足,则返回true,否则,返回false。
- “!”取反运算。!true = false 、 !false = true 、 !100 = false
- 逻辑运算符的运算结果有两个true或false。
- 6、三元运算符:?:
- 所谓“三元运算符”就是指三个操作数。
- 语法:条件表达式 ? 结果1 : 结果2
- 语法:操作数1 ? 操作数2 : 操作数3
- 含义:如果条件为true,则执行“结果1”的代码;如果条件为false,则执行“结果2”的代码。
- 其实:三元运算符,就是if else的变形形式。
- JS 位运算
- 1、& 按位与
- &是二元运算符,它以特定的方式的方式组合操作数中对应的位,如果对应的位都为1,那么结果就是1, 如果任意一个位是0 则结果就是0。
- 1 & 3的结果为1
- 那我们来看看他是怎么运行的
- 1的二进制表示为 0 0 0 0 0 0 1
- 3的二进制表示为 0 0 0 0 0 1 1
- 根据 & 的规则 得到的结果为 0 0 0 0 0 0 0 1,十进制表示就是1
- 2、| 按位或
- |运算符跟&的区别在于如果对应的位中任一个操作数为1 那么结果就是1。
- 1的二进制表示为 0 0 0 0 0 0 1
- 3的二进制表示为 0 0 0 0 0 1 1
- 所以 1 | 3的结果为3
- 3、^ 按位异或
- ^运算符跟|类似,但有一点不同的是 如果两个操作位都为1的话,结果产生0。
- 1的二进制表示为 0 0 0 0 0 0 1
- 3的二进制表示为 0 0 0 0 0 1 1
- 所以 1 ^ 3的结果为2
- 4、~ 按位非
- ~运算符是对位求反,1变0,0变1,也就是求二进制的反码
- 1的二进制表示为 0 0 0 0 0 0 1
- 所以 ~1 的结果是-2
- 5、>> 右移
运算符使指定值的二进制所有位都右移规定的次数,对于其移动规则只需记住符号位不变,左边补上符号位即按二进制形式把所有的数字向右移动对应的位数,低位移出(舍弃),高位的空位补符号位,即正数补零,负数补1。
- 1的二进制表示为 0 0 0 0 0 0 1
- 所以 1>>1的结果为0
- 6、<< 左移
- <<运算符使指定值的二进制所有位都左移规定的次数,对于其移动规则只需记住丢弃最高位,0补最低位即按二进制形式把所有的数字向左移动对应的位数,高位移出(舍弃),低位的空位补零。
- 1的二进制表示为 0 0 0 0 0 0 1
- 所以 1<<1的结果为2 7、>>> 无符号右移
运算符忽略了符号位扩展,0补最高位,但是只是对32位和64位的值有意义。
- 位运算符在js中的妙用:
- 1、使用&运算符判断一个数的奇偶
- 1、& 按位与
偶数 & 1 = 0
奇数 & 1 = 1
那么0&1=0,1&1=11. 2、使用~~,>>,<<,>>>,|来取整3.14 = 3-3.14 = -3 其它的一样
3.14 >> 0 = 3
3.14 << 0 = 3 3.14 | 0 = 3 3.14 >>> 0 = 3(>>>不可对负数取整)
注意:1. 3、使用<<,>>来计算乘除乘法:
1*2 = 2
1<>1 = 1(2/2的一次方)1. 4、利用^来完成比较两个数是否相等1 ^ 1 = 0
1 ^ 非1数 !=0
所以同一个数……同一个数等于0,否则不等于01. 5、使用^来完成值交换a = 1
b = 2
a ^= b
b ^= a
a ^= b
结果a=2,b=11. 6、使用&,>>,|来完成rgb值和16进制颜色值之间的转换16进制颜色值转RGB:

RGB转16进制颜色值:
运行hexToRGB(“#ffffff”)返回”rgb(255,255,255)”
运行RGBToHex(“rgb(255,255,255)”)返回”#ffffff”
位运算应用场景:
偷懒简写(|): 取整、断言
标志位判断 (&): 权限控制
去掉高位/低位(&): 比较特定位、子网掩码
数值交换(^): 加密算法、生成随机数/哈希
构造属性集(|): 边界判断
缺省数值类型值(|):默认端口
位填充(>>): HEX2RGB、leftPad
试图提升算术运算性能
位运算示例:
1. 求 2 的 n 次方:
————————————————————————————————————————————
bitwise | ES5 | ES6
+1 << n | Math.pow(2, n) | 2 * n
————————————————————————————————————————————-
原理: 左移一位, 相当于将原数值与 2 相乘一次 , 即 m << n === m Math.pow(2, n)
2. 求中间索引
————————————————————————————————————————————————
bitwise | ES5 | ES6+
arr.length >> 1 | Math.floor(arr.length / 2)) | -
————————————————————————————————————————————————
原理: 右移一位,相当于原数值除 2 后向下取整
3. 判断索引存在
——————————————————————————————————————————————————————-
bitwise | ES5 | ES6+
!!~’abc’.indexOf(‘d’) | ‘abc’.indexOf(‘d’) !== -1 | ‘abc’.includes(‘d’)
——————————————————————————————————————————————————————-
原理: 按位非 ~-1 === 0
4. 变量交换
——————————————————————————————————————————————————————-
bitwise | ES5 | ES6+
a ^= b; b ^= a; a ^= b; | t = a; b = a; b = t; | [b, a] = [a, b]
——————————————————————————————————————————————————————-
原理: 按位异或 1 ^ 1 === 0, 0 ^ 0 === 1
5. 分支开关
——————————————————————————————————————————————————————-
bitwise | ES5 | ES6+
flag ^= 1; | flag = flag ? 0 : 1; | -
——————————————————————————————————————————————————————-
原理: 按位异或 1 ^ 1 === 0, 0 ^ 0 === 1
6. 截取整数位
——————————————————————————————————————————————————————-
bitwise | ES5 | ES6+
~~-9.9; -9.9>>0; | parseInt(-9.9) | Math.trunc(-9.9)
——————————————————————————————————————————————————————-
原理: 位运算会默认将非数字类型转换成数字类型再进行运算
注意:
以上值(还有 -9.9|0 ) 都为 -9, 而 Math.floor(-9.9) 值为 -10
位运算取整方法不适用超过32位整数最大值 2147483647 的数: 2147483649.4 | 0 的值为 -2147483647- ES6 扩展运算符 …的使用
- 1 含义
- 扩展运算符( spread )是三个点(…)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
- 1 含义
console.log(…[1, 2, 3]) // 1 2 3 console.log(1, …[2, 3, 4], 5) // 1 2 3 4 5 […document.querySelectorAll(‘div’)] // [
,,] 该运算符主要用于函数调用。 function push(array, …items) { array.push(…items); } function add(x, y) { return x + y; } var numbers = [4, 38]; add(…numbers) // 421. 上面代码中,array.push(...items)和add(...numbers)这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。 1. 扩展运算符与正常的函数参数可以结合使用,非常灵活。function f(v, w, x, y, z) { } var args = [0, 1]; f(-1, …args, 2, …[3]);
1. 2 替代数组的 apply 方法 1. 由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。// ES5 的写法 function f(x, y, z) { // … } var args = [0, 1, 2]; f.apply(null, args); // ES6 的写法 function f(x, y, z) { // … } var args = [0, 1, 2]; f(…args);
1. 下面是扩展运算符取代apply方法的一个实际的例子,应用Math.max方法,简化求出一个数组最大元素的写法。// ES5 的写法 Math.max.apply(null, [14, 3, 77]) // ES6 的写法 Math.max(…[14, 3, 77]) // 等同于 Math.max(14, 3, 77);
1. 上面代码表示,由于 JavaScript 不提供求数组最大元素的函数,所以只能套用Math.max函数,将数组转为一个参数序列,然后求最大值。有了扩展运算符以后,就可以直接用Math.max了。 1. 另一个例子是通过push函数,将一个数组添加到另一个数组的尾部。// ES5 的写法 var arr1 = [0, 1, 2]; var arr2 = [3, 4, 5]; Array.prototype.push.apply(arr1, arr2); // ES6 的写法 var arr1 = [0, 1, 2]; var arr2 = [3, 4, 5]; arr1.push(…arr2);
1. 上面代码的 ES5 写法中,push方法的参数不能是数组,所以只好通过apply方法变通使用push方法。有了扩展运算符,就可以直接将数组传入push方法。 1. 下面是另外一个例子。// ES5 new (Date.bind.apply(Date, [null, 2015, 1, 1])) // ES6 new Date(…[2015, 1, 1]);
1. 3 扩展运算符的应用 1. ( 1 )合并数组 1. 扩展运算符提供了数组合并的新写法。// ES5 [1, 2].concat(more) // ES6 [1, 2, …more] var arr1 = [‘a’, ‘b’]; var arr2 = [‘c’]; var arr3 = [‘d’, ‘e’]; // ES5 的合并数组 arr1.concat(arr2, arr3); // [ ‘a’, ‘b’, ‘c’, ‘d’, ‘e’ ] // ES6 的合并数组 […arr1, …arr2, …arr3] // [ ‘a’, ‘b’, ‘c’, ‘d’, ‘e’ ]
1. ( 2 )与解构赋值结合 1. 扩展运算符可以与解构赋值结合起来,用于生成数组。// ES5 a = list[0], rest = list.slice(1) // ES6 [a, …rest] = list 下面是另外一些例子。 const [first, …rest] = [1, 2, 3, 4, 5]; first // 1 rest // [2, 3, 4, 5] const [first, …rest] = []; first // undefined rest // []: const [first, …rest] = [“foo”]; first // “foo” rest // [] 如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。 const […butLast, last] = [1, 2, 3, 4, 5]; // 报错 const [first, …middle, last] = [1, 2, 3, 4, 5]; // 报错
1. ( 3 )函数的返回值 1. JavaScript 的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。var dateFields = readDateFields(database); var d = new Date(…dateFields);
1. 上面代码从数据库取出一行数据,通过扩展运算符,直接将其传入构造函数Date。 1. ( 4 )字符串 1. 扩展运算符还可以将字符串转为真正的数组。[…’hello’] // [ “h”, “e”, “l”, “l”, “o” ]
1. 上面的写法,有一个重要的好处,那就是能够正确识别 32 位的 Unicode 字符。‘x\uD83D\uDE80y’.length // 4 […’x\uD83D\uDE80y’].length // 3
1. 上面代码的第一种写法, JavaScript 会将 32 位 Unicode 字符,识别为 2 个字符,采用扩展运算符就没有这个问题。因此,正确返回字符串长度的函数,可以像下面这样写。function length(str) { return […str].length; } length(‘x\uD83D\uDE80y’) // 3
1. 凡是涉及到操作 32 位 Unicode 字符的函数,都有这个问题。因此,最好都用扩展运算符改写。let str = ‘x\uD83D\uDE80y’; str.split(‘’).reverse().join(‘’) // ‘y\uDE80\uD83Dx’ […str].reverse().join(‘’) // ‘y\uD83D\uDE80x’
1. 上面代码中,如果不用扩展运算符,字符串的reverse操作就不正确。 1. ( 5 )实现了 Iterator 接口的对象 1. 任何 Iterator 接口的对象,都可以用扩展运算符转为真正的数组。var nodeList = document.querySelectorAll(‘div’); var array = […nodeList];
1. 上面代码中,querySelectorAll方法返回的是一个nodeList对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了 Iterator 接口。 1. 对于那些没有部署 Iterator 接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。let arrayLike = { ‘0’: ‘a’, ‘1’: ‘b’, ‘2’: ‘c’, length: 3 }; // TypeError: Cannot spread non-iterable object. let arr = […arrayLike];
1. 上面代码中,arrayLike是一个类似数组的对象,但是没有部署 Iterator 接口,扩展运算符就会报错。这时,可以改为使用Array.from方法将arrayLike转为真正的数组。 1. ( 6 ) Map 和 Set 结构, Generator 函数 1. 扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。let map = new Map([ [1, ‘one’], [2, ‘two’], [3, ‘three’], ]); let arr = […map.keys()]; // [1, 2, 3] Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。 var go = function*(){ yield 1; yield 2; yield 3; }; […go()] // [1, 2, 3]
1. 上面代码中,变量go是一个 Generator 函数,执行后返回的是一个遍历器对象,对这个遍历器对象执行扩展运算符,就会将内部遍历得到的值,转为一个数组。 1. 如果对没有iterator接口的对象,使用扩展运算符,将会报错。var obj = {a: 1, b: 2}; let arr = […obj]; // TypeError: Cannot spread non-iterable object
- ES5 和 ES6处理字符串的方法
- ES5
- 检索字符串
- indexOf
- 检索字符串
- ES5
与数组方法类似。
stringObject.indexOf(searchvalue,fromindex)
fromindex规定在字符串中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的首字符开始检索。1. lastIndexOf与indexOf相反。
indexOf和lastIndexOf方法对大小写敏感!1. 截取字符串 1. slice(start, end)start,要抽取的片断的起始下标,如果为负,则从尾部算起。
end,要抽取的片段的结尾的下标,如果为负,则从尾部算起。
String.slice() 与 Array.slice() 相似1. substring(start, stop)start必需。要抽取的片断的起始下标,不能为负。
stop可选。比要提取的子串的最后一个字符的位置多1,不能为负。
如果参数 start 与 stop 相等,那返回一个空串。
如果 start 比 stop 大,那在提取子串之前会先交换这两个参数。
如果同时为负,则返回空串。
如果一个值为负,则转为0,而且如果start 比 stop 大,会交换值。1. substr(start, length) --不推荐start,要抽取的片断的起始下标,如果为负,则从尾部算起。
length,如果为负,则转为0。1. trim(),trimLeft(),trimRight()去除首尾空格。
1. 正则相关 1. split把一个字符串分割成字符串数组。
stringObject.split(separator, howmany) //separator必需,字符串或正则表达式
var str=”How are you doing today?”; str.split(/\s+/); //“How”, “are”, “you”, “doing”, “today?”
如果把空字符串 (“”) 用作 separator,那么 stringObject 中的每个字符之间都会被分割。
String.split() 执行的操作与 Array.join 执行的操作是相反的。1. match可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
stringObject.match(searchvalue | reg)1. **字符串检索**var str=”Hello world!”; str.match(‘w’); //[“w”, index: 6, input: “Hello world!”, groups: undefined]
1. **正则检索**// 全局匹配 var str=”1 plus 2 equal 3”; str.match(/\d+/g); //[“1”, “2”, “3”] var str=”1 plus 2 equal 3”; str.match(/\d+/); //[“1”, index: 0, input: “1 plus 2 equal 3”, groups: undefined]
1. replace 1. 用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。 1. stringObject.replace(reg | substr, replacement) 2. **字符串替换**‘aaaccc’.replace(‘ccc’, ‘b’); //‘aaab’ ‘aaaccc’.replace(‘bbb’, ‘b’); //‘aaaccc’ ‘aaaccc’.replace(‘a’, ‘b’); //“baaccc”
1. **正则替换**‘aaaccc’.replace(/\w/, ‘b’) //“baaccc” //全局匹配 ‘aaaccc’.replace(/\w/g, ‘b’) //“bbbbbb”
1. **replacement** 1. replacement 可以是字符串,也可以是函数。但是 replacement 中的 $ 字符具有特定的含义。 1. ----------------------------------------------------------------------------------------------------------------------------------- 1. 字符 | 替换文本 1. $1、$2、...、$99 | 与 regexp 中的第 1 到第 99 个子表达式相匹配的文本。 1. $& | 与 regexp 相匹配的子串。 1. $` | 位于匹配子串左侧的文本。 1. $' | 位于匹配子串右侧的文本。 1. $$ | 直接量符号。 1. ----------------------------------------------------------------------------------------------------------------------------------- 1. <br />‘aa123BB’.replace(/([a-z]+)(\d+)([A-Z]+)/g, ‘$1’); // “aa” ‘aa123BB’.replace(/([a-z]+)(\d+)([A-Z]+)/g, ‘$4’); // “$4” ‘aa123BB’.replace(/([a-z]+)(\d+)([A-Z]+)/g, ‘$&’); //“aa123BB” ‘aa123BB’.replace(/(\d+)/g, ‘$`’); //“aaaaBB” ‘aa123BB’.replace(/(\d+)/g, “$’”); //“aaBBBB” ‘aa123BB’.replace(/(\d+)/g, ‘$$’); //“aa$BB”
1. ·ECMAScript v3 规定,replace() 方法的参数 replacement 可以是函数而不是字符串。在这种情况下,每个匹配都调用该函数,它返回的字符串将作为替换文本使用。‘aaaccc’.replace(/\w/g, function() { return ‘b’; }); //“bbbbbb” ‘aaaccc’.replace(/\w/, function() { return ‘b’; }); //“baaccc”
1. search 1. stringObject.search(searchvalue | reg)‘aaaccc’.search(‘ccc’); //3 ‘aaaccc’.search(/ccc/); //3 var str=”Visit W3School!”; str.search(/W3School/); //6
1. search() 对大小写敏感 1. 其他 1. big() 用大号字体显示字符串。 1. blink() 显示闪动字符串。 1. bold() 使用粗体显示字符串。 1. sup() 把字符串显示为上标。 1. sub() 把字符串显示为下标。 1. strike() 使用删除线来显示字符串。 1. small() 使用小字号来显示字符串。 1. charAt() 返回在指定位置的字符。 1. charCodeAt() 返回在指定的位置的字符的 Unicode 编码。 1. toLocaleLowerCase() 把字符串转换为小写。 1. toLocaleUpperCase() 把字符串转换为大写。 1. toLowerCase() 把字符串转换为小写。 1. toUpperCase() 把字符串转换为大写。 1. toSource() 代表对象的源代码。 1. toString() 返回字符串。 1. valueOf() 返回某个字符串对象的原始值。 1. ES6 1. includes(), startsWith(), endsWith() 1. includes(searchValue, start):返回布尔值,表示是否找到了参数字符串。 1. startsWith(searchValue, start):返回布尔值,表示参数字符串是否在原字符串的头部。 1. endsWith(searchValue, start):返回布尔值,表示参数字符串是否在原字符串的尾部。let s = ‘Hello world!’; s.startsWith(‘world’, 6); // true s.endsWith(‘Hello’, 5); // true s.includes(‘Hello’, 6); // false s.includes(‘o’); // true
1. repeat(value) 1. 返回一个新字符串,表示将原字符串重复n次。 1. 如果value数是负数或者Infinity,会报错。但是,如果参数是 0 到-1 之间的小数,则等同于 0。 1. 如果value是字符串,则会先转换成数字。 1. value为NaN等同于 0。 1. value如果是小数,会被取整。‘a’.repeat(3); // “aaa” ‘a’.repeat(-1); //VM449:1 Uncaught RangeError: Invalid count value ‘a’.repeat(‘3n’); //“” ‘a’.repeat(‘3’); //“aaa” ‘a’.repeat(NaN); //“” ‘a’.repeat(1.6); //“a”
1. padStart(),padEnd() 1. padStart(length, string)用于头部补全。 1. padEnd(length, string)用于尾部补全。‘aaa’.padStart(2, ‘ab’); //“aaa” ‘aaa’.padStart(10, ‘0123456789’); //“0123456aaa” ‘aaa’.padStart(10) //“ aaa” ‘aaa’.padEnd(6, ‘cd’) //“aaacdc”
1. 模板字符串 1. 模板字符串(template string)是增强版的字符串,用反引号(`)标识。let name = “Bob”, time = “today”;
Hello ${name}, how are you ${time}?//“Hello Bob, how are you today?”1. 模板字符串中嵌入变量,需要将变量名写在${}之中。 1. **JS表达式,可以进行运算,以及引用对象属性**let x = 1; let y = 2;
${x} + ${y} = ${x + y}//“1 + 2 = 3” var obj = { a: ‘eee’}obj ${obj}//“obj [object Object]”1. **调用函数**function fn() { return “Hello World”; }
foo ${fn()} bar//“foo Hello World bar”1. 标签模板 1. 模板字符串可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。alert
123; 等同于alert(123);1. 如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数。let a = 5; let b = 10; function tag(s, v1, v2) { console.log(s); console.log(v1); console.log(v2); return “OK”; } tag
Hello ${ a + b } world ${ a * b}; // 等同于 tag([‘Hello ‘, ‘ world ‘, ‘’], 15, 50); //[“Hello “, “ world “, “”, raw: Array(3)] //15 //50 //“OK”- 深入javascript之原型和原型链
- 1,函数对象
- 所有引用类型(函数,数组,对象)都拥有proto属性(隐式原型)
- 所有函数拥有prototype属性(显式原型)(仅限函数)
- 原型对象:拥有prototype属性的对象,在定义函数时就被创建
- 2,构造函数
- 先复习下构造函数
- 1,函数对象
//创建构造函数 function Word(words){ this.words = words; } Word.prototype = { alert(){ alert(this.words); } } //创建实例 var w = new Word(“hello world”); w.print = function(){ console.log(this.words); console.log(this); //Person对象 } w.print(); //hello world w.alert(); //hello world
1. print()方法是w实例本身具有的方法,所以w.print()打印hello world;alert()不属于w实例的方法,属于构造函数的方法,w.alert()也会打印hello world,因为实例继承构造函数的方法。 1. 实例w的隐式原型指向它构造函数的显式原型,指向的意思是恒等于w.proto === Word.prototype
1. 当调用某种方法或查找某种属性时,首先会在自身调用和查找,如果自身并没有该属性或方法,则会去它的__proto__属性中调用查找,也就是它构造函数的prototype中调用查找。所以很好理解实例继承构造函数的方法和属性: 1. w本身没有alert()方法,所以会去Word()的显式原型中调用alert(),即实例继承构造函数的方法。3,原型和原型链
Function.prototype.a = “a”; Object.prototype.b = “b”; function Person(){} console.log(Person); //function Person() let p = new Person(); console.log(p); //Person {} 对象 console.log(p.a); //undefined console.log(p.b); //b
- 想一想p.a打印结果为undefined,p.b结果为b
解析:
- p是Person()的实例,是一个Person对象,它拥有一个属性值proto,并且proto是一个对象,包含两个属性值constructor和proto
console.log(p.proto.constructor); //function Person(){} console.log(p.proto.proto); //对象{},拥有很多属性值
- 我们会发现p.proto.constructor返回的结果为构造函数本身,p.proto.proto有很多参数

1. 我们调用constructor属性,p.___proto__.__proto__.constructor得到拥有多个参数的Object()函数,Person.prototype的隐式原型的constructor指向Object(),即Person.prototype.__proto__.constructor == Object() 1. 从p.__proto__.constructor返回的结果为构造函数本身得到Person.prototype.constructor == Person()所以p.___proto__.__proto__== Object.prototype 1. 所以p.b打印结果为b,p没有b属性,会一直通过__proto__向上查找,最后当查找到Object.prototype时找到,最后打印出b,向上查找过程中,得到的是Object.prototype,而不是Function.prototype,找不到a属性,所以结果为undefined,这就是原型链,通过__proto__向上进行查找,最终到null结束 console.log(p.__proto__.__proto__.__proto__); //null console.log(Object.prototype.__proto__); //null 1. 大家理解刚才的过程,相信下面这些应该也都明白 //Function function Function(){} console.log(Function); //Function() console.log(Function.prototype.constructor); //Function() console.log(Function.prototype.__proto__); //Object.prototype console.log(Function.prototype.__proto__.__proto__); //NULL console.log(Function.prototype.__proto__.constructor); //Object() console.log(Function.prototype.__proto__ === Object.prototype); //true- 总结:
- 1.查找属性,如果本身没有,则会去proto中查找,也就是构造函数的显式原型中查找,如果构造函数中也没有该属性,因为构造函数也是对象,也有proto,那么会去它的显式原型中查找,一直到null,如果没有则返回undefined
- 2.p.proto.constructor == function Person(){}
- 3.p._proto.proto== Object.prototype
- 4.p._proto.proto.proto== Object.prototype.proto == null
- 5.通过proto形成原型链而非protrotype
- 最后附上一张图,大家阅读完之后,看图应该可以很容易理解

js预编译的四部曲
- 众所周知javascript是解释性语言,主要特点为解释一行执行一行。
- 而在js运行时会进行三件事:1语法分析 2.预编译 3.解释执行
- 语法分析会在代码执行前对代码进行通篇检查,以排除一些低级错误
- 预编译发生在代码执行的前一刻
- 解释执行顾名思义就是执行代码
我先给大家举几个预编译的小例子:
var a = 123; console.log(a);
此时他返回的值会是123;
但如果我们调换位置:
console.log(a); var a = 123;
我们得到的结果便会是undefined。(由于js解释性语言的原因,先执行console.log,而由于预编译的原因浏览器并不会报错)
- 如果我们在次尝试不定义变量直接获取:
console.log(a); 此时我们会发现浏览器会进行报错。//Uncaught ReferenceError: a is not defined 这条语句提示我们a没有定义
- 到这里有些人会有疑问为什么在console.log前,后和不定义a会有如此大的差别,这就是预编译起到的作用
- 在具体讲解预编译之前要先帮大家了解几点小知识,如下:
- 预编译的前奏
- imply global 暗示全局变量:即任何变量,如果变量未经声明就赋值,此变量就为全局对象(window)所有。
- 简单解释起来就是,如果在js中我们在赋值时经常先定义一个变量,如:var a = 123;
- 此时123这个数字便被赋予了a这个变量,此时我们在控制台console.log(a)就会得到a的值123;
- 这时就会我们就会有个疑问,如果我们没有定义变量直接进行赋值还会打印出值么。
- 答案是肯定的,此时如果我们未定义变量直接赋值,如:
- 预编译的前奏
b = 456;
1. 此时a会被认为是全局的一个对象,即window下的一个值,此时我们在控制台下console.log(b)同样可以得到b的值456。 1. 此时我们可以认为 b = 123 ————> window.b =456- 一切声明的全局变量,全是window。
- 简单说明就是我们 var c = 789 ===> window.c = 789(即:c =789)
- 预编译(粗浅版本)
- 下面我们来看一下对预编译最基本的肤浅理解(该理解非常粗浅无法解决复杂问题):
- 函数声明整体提升
- 变量,声明提升
- 个人理解:在函数执行前函数声明(function(){})会整体提升至逻辑的最前方,
- 变量则会把自身的声明提升到程序的最最前方。
- //注释 var a =123;是变量声明+赋值
- var a:变量声明
- a = 123 变量赋值
- 在预编译时我们不看赋值,只把变量的声明提升,因此若在赋值前打印会提示undefined;
- 此时我们就可以理解之前的问题了:
- 先打印再定义:
console.log(a); var a = 123;
- 在执行前var a提升至代码的最最前端 ,提升后我们再打印 a,由于此时a未被赋值因此打印提示undefined。
- console.log(a);
- 不定义:
- console.log(a);
- 此时由于没有a的定义,所以会报错提示a is not defined;即:a未定义。
- 看似很好理解但是如果代码稍微复杂便无法解决,例如:
console.log(a); fun function a(a) { var a = 456; var a =function () { } a(); } var a = 123;
下面我们来看一下真正的预编译:
- 预编译(精装版本):
- 预编译的四部曲:
- 1.创建GO/AO对象
- 2.找形参和变量声明,将变量和形参名作为AO属性名,值为undefined
- 3.将实参值和形参统一
- 4.在函数体里面找函数声明,值赋予函数体
- 预编译的四部曲:

1. 以此为例: 1. 再次函数中我们来进行预编译: 1. 1.创建AO对象:我们隐式的在函数中创建了一个AO的对象来盛放函数中的变量,此时对象中并没有值; 1. 2.找形参和变量声明,将变量和形参名作为AO属性名,值为undefined:我们在第二个过程中需按照变量和形参
1. 3.将实参值和形参统一:此时将实参带入函数中由于在函数外 f(1),因此AO中a = 1
1. 4.在函数体里面找函数声明,值赋予函数体:由于在函数中有 function a() {} ,这一函数因此此时AO中 a = function a() {}
1. 在进行完预编译后此时若执行函数则会以AO为基础对函数中的变量进行赋值:此时函数中有两次打印一次在函数开头,一次在函数为a赋值之后 1. 再赋值前由于AO中值不变因此a所打印出的值为 function a() {} 1. 在赋值后AO中a = 123,所以此时打印出的值为123。
- Dom对象,js对象和jquery对象的区别
- 1、DOM对象
- 文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展置标语言的标准编程接口。
- DOM实际上是以面向对象方式描述的文档模型。DOM定义了表示和修改文档所需的对象、这些对象的行为和属性以及这些对象之间的关系。
- 通过DOM,可以访问所有的 HTML 元素,连同它们所包含的文本和属性。可以对其中的内容进行修改和删除,同时也可以创建新的元素。 HTML
- DOM 独立于平台和编程语言。它可被任何编程语言诸如 Java、JavaScript 和 VBScript 使用。
- DOM对象,即是我们用传统的方法(javascript)获得的对象。
- DOM准确说是对文档对象的一种规范标准(文档对象模型),标准只定义了属性和方法行为。
- 2、JavaScript 对象
- JavaScript 提供多个内建对象,比如 String、Date、Array 等等。
- 对象只是带有属性和方法的特殊数据类型。
- 通过js获取的DOM对象就是js对象
- 当浏览器支持js的dom接口(api)时,这里狭义的dom对象是以js对象的形式出现的,也就是一个js对象
- 3、jQuery对象
- jquery对象其实是一个javascript的数组,这个数组对象包含125个方法和4个属性
- 4个属性分别是
- jquery 当前的jquery框架版本号
- length 指示该数组对象的元素个数 .
- context 一般情况下都是指向HtmlDocument对象 .
- selector 传递进来的选择器内容
- jQuery对象就是通过jQuery包装DOM对象后产生的对象。jQuery对象是jQuery独有的,其可以使用jQuery里的方法,但是不能使用DOM的方法;反过来Dom对象也不能使用jquery的方法
- 4、js对象和jQuery对象之间的区别于转换
- 1、DOM对象
<!DOCTYPE html>
22222222222222222222222222222222222
1. 可以捕获异常:

1. 由于网络请求异常不会事件冒泡,因此必须在捕获阶段将其捕捉到才行,但是这种方式虽然可以捕捉到网络请求的异常,但是无法判断 HTTP 的状态是 404 还是其他比如 500 等等,所以还需要配合服务端日志才进行排查分析才可以。
1. 注意:
1. 不同浏览器下返回的 error 对象可能不同,需要注意兼容处理。
1. 需要注意避免 window.addEventListener 重复监听。
3. 到此为止,我们学到了:在开发的过程中,对于容易出错的地方,可以使用try{}catch(){}来进行错误的捕获,做好兜底处理,避免页面挂掉。而对于全局的错误捕获,在现代浏览器中,我倾向于只使用使用window.addEventListener('error'),window.addEventListener('unhandledrejection')就行了。如果需要考虑兼容性,需要加上window.onerror,三者同时使用,window.addEventListener('error')专门用来捕获资源加载错误。
1. Promise Catch
1. 我们知道,在 promise 中使用 catch 可以非常方便的捕获到异步 error 。
1. 没有写catch的promise中抛出的错误无法被onerror或try...catch捕获到,所以务必在promise中写catch做异常处理。
1. 有没有一个全局捕获promise的异常呢?答案是有的。 Uncaught Promise Error就能做到全局监听,使用方式:
window.addEventListener("unhandledrejection", function(e){ // e.preventDefault(); // 阻止异常向上抛出 console.log('捕获到异常:', e); }); Promise.reject('promise error');
1. 同样可以捕获错误:

1. 所以,正如我们上面所说,为了防止有漏掉的 promise 异常,建议在全局增加一个对 unhandledrejection 的监听,用来全局监听 Uncaught Promise Error。
1. iframe 异常
1. 对于 iframe 的异常捕获,我们还得借力 window.onerror:
window.onerror = function(message, source, lineno, colno, error) { console.log('捕获到异常:',{message, source, lineno, colno, error}); }
1. 下面一个简单的例子:
- Script error
- 在进行错误捕获的过程中,很多时候并不能拿到完整的错误信息,得到的仅仅是一个”Script Error”。
- 产生原因
- 由于12年前这篇文章里提到的安全问题:blog.jeremiahgrossman.com/2006/12/i-k…
- 当加载自不同域的脚本中发生语法错误时,为避免信息泄露,语法错误的细节将不会报告,而是使用简单的”Script error.”代替。
- 一般而言,页面的JS文件都是放在CDN的,和页面自身的URL产生了跨域问题,所以引起了”Script Error”。
- 解决办法
- 一般情况,如果出现 Script error 这样的错误,基本上可以确定是跨域问题。这时候,是不会有其他太多辅助信息的,但是解决思路无非如下:
- 跨源资源共享机制( CORS ):我们为 script 标签添加 crossOrigin 属性。
1. 崩溃和卡顿 1. 卡顿也就是网页暂时响应比较慢, JS可能无法及时执行。但崩溃就不一样了,网页都崩溃了,JS都不运行了,还有什么办法可以监控网页的崩溃,并将网页崩溃上报呢? 1. 1.利用 window 对象的 load 和 beforeunload 事件实现了网页崩溃的监控。window.addEventListener(‘load’, function () { sessionStorage.setItem(‘good_exit’, ‘pending’); setInterval(function () { sessionStorage.setItem(‘time_before_crash’, new Date().toString()); }, 1000); }); window.addEventListener(‘beforeunload’, function () { sessionStorage.setItem(‘good_exit’, ‘true’); }); if(sessionStorage.getItem(‘good_exit’) && sessionStorage.getItem(‘good_exit’) !== ‘true’) { / insert crash logging code here / alert(‘Hey, welcome back from your crash, looks like you crashed on: ‘ + sessionStorage.getItem(‘time_before_crash’)); }
1. 不错的文章,推荐阅读:[Logging Information on Browser Crashes](http://jasonjl.me/blog/2015/06/21/taking-action-on-browser-crashes/)。 1. 2.基于以下原因,我们可以使用 Service Worker 来实现[网页崩溃的监控](https://juejin.im/entry/5be158116fb9a049c6434f4a?utm_source=gold_browser_extension): 1. Service Worker 有自己独立的工作线程,与网页区分开,网页崩溃了,Service Worker一般情况下不会崩溃 1. Service Worker 生命周期一般要比网页还要长,可以用来监控网页的状态 1. 网页可以通过 navigator.serviceWorker.controller.postMessage API 向掌管自己的 SW 发送消息- VUE errorHandler
- 在Vue中,异常可能被Vue自身给try…catch了,不会传到window.onerror事件触发。不过不用担心,Vue提供了特有的异常捕获,比如Vux2.x中我们可以这样用:
Vue.config.errorHandler = function (err, vm, info) { let { message, // 异常信息 name, // 异常名称 script, // 异常脚本url line, // 异常行号 column, // 异常列号 stack // 异常堆栈信息 } = err; // vm为抛出异常的 Vue 实例 // info为 Vue 特定的错误信息,比如错误所在的生命周期钩子 }
- React 异常捕获
- 在React,可以使用ErrorBoundary组件包括业务组件的方式进行异常捕获,配合React 16.0+新出的componentDidCatch API,可以实现统一的异常捕获和日志上报。
- 我们来举一个小例子,在下面这个 componentDIdCatch(error,info) 里的类会变成一个 error boundary:
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } componentDidCatch(error, info) { // Display fallback UI this.setState({ hasError: true }); // You can also log the error to an error reporting service logErrorToMyService(error, info); } render() { if (this.state.hasError) { // You can render any custom fallback UI return Something went wrong. ; } return this.props.children; } }
1. 然后我们像使用普通组件那样使用它:1. componentDidCatch() 方法像JS的 catch{} 模块一样工作,但是对于组件,只有 class 类型的组件(class component )可以成为一个 error boundaries 。 1. 实际上,大多数情况下我们可以在整个程序中定义一个 error boundary 组件,之后就可以一直使用它了! 1. 需要注意的是:error boundaries并不会捕捉下面这些错误: 1. 事件处理器 1. 异步代码 1. 服务端的渲染代码 1. 在 error boundaries 区域内的错误- 总结
- 可疑区域增加 try…catch
- 全局监控JS异常: window.onerror
- 全局监控静态资源异常: window.addEventListener
- 全局捕获没有 catch 的 promise 异常:unhandledrejection
- iframe 异常:window.error
- VUE errorHandler 和 React componentDidCatch
- 监控网页崩溃:window 对象的 load 和 beforeunload
- Script Error跨域 crossOrigin 解决
- js延迟加载的六种方式
- defer 属性
- HTML 4.01 为
让时间为你证明
- // b.html window.onmessage = function(e) { console.log(e.data) //我爱你 e.source.postMessage(‘我不爱你’, e.origin) }
- 什么是跨域

