MDN

JavaScript 完全手册(2018版)

HTML

<!-- HTML5新增的语义化标签(用于搜索引擎识别)常用的有 -->
<header></header>   <!-- 头部(页眉) -->
<nav></nav>         <!-- 导航 -->
<aside></aside>     <!-- 侧边栏(比如文章分类、友情链接等) -->
<main></main>       <!-- 页面主要内容 -->
<article></article> <!-- 文章,表示一段独立的内容 -->
<section></section> <!-- 其他模块,一个模块是一个section(或者也可以用来给article分段) -->
<footer></footer>   <!-- 底部(页脚) -->

<!-- 自定义属性,要求以"data-"开头,最好不要有英文大写(否则js获取不到)
     JavaScript获取自定义属性:
     var element = document.querySelector("p");
     var name = element.dataset['myName'];     //把横杠改成驼峰法
-->
<p data-my-name="xiaoming">小明</p>

<progress max="100" value="10"></progress> <!-- 进度条 -->
<meter max="100" high="80" low="40" value="50"></meter> <!-- 度量器(类似进度条,不同进度有不同效果) -->

<img alt="无法加载图片" title="鼠标悬浮时的提示信息">

<base target="_blank" /> <!-- 统一设置所有a标签的打开方式 -->
<a href="#id" target="_blank | _self">超链接</a><br />

<dl>
    <dt>标题</dt>
    <dd>数据1</dd>
    <dd>数据2</dd>
</dl>

<br />

<select>
    <option>选项1</option>
    <option selected>选项2</option>
</select>

<br />

<table align="center | left | right" border="2" cellspacing="1" cellpadding="1">
<caption>标题</caption>
<thead>
    <tr>
        <th>类目1</th>
        <th>类目2</th>
        <th>类目3</th>
    </tr>
</thead>
<tbody>
    <tr>
        <td>数据1</td>
        <td>数据2</td>
        <td rowspan="2">数据3</td>
    </tr>
    <tr>
        <td colspan="2">数据4</td>
    </tr>
</tbody>
</table>

<label>姓名:<input type="text" /></label> <!-- 点击文字跳转输入框 -->

<br />

性别:<input type="radio" name="sex" checked />
<input type="radio" name="sex"/>

<br />

<input type="file" multiple /> <!-- 多文件上传 -->

<br />

<!-- 记录上次输入过的数据,alt+s选中输入框 -->
<input type="text" list="data" placeholder="搜索" autofocus required autocomplete accesskey="s"  />
<datalist id="data">
    <option>数据1</option>
    <option selected>数据2</option>
</datalist>

<br />

<fieldset>
    <legend>标题</legend>
</fieldset>

<hr />

<audio src="1.mp3" autoplay controls loop="-1" ></audio><!-- 无限循环用-1 -->

<hr />

<!-- 不同浏览器支持的格式不同,可以配置source兼容多种浏览器,使用浏览器最多支持以下三种格式 -->
<audio autoplay controls loop="-1">
    <source src="1.mp3" />
    <source src="1.wav" />
    <source src="1.ogg" />
</audio>

<hr />

<embed type="application/x-shockwave-flash" src="" quality="high" allowFullScreen="true" width="400" height="400" align="middle" allowScriptAccess="true" />

<hr />

<video autoplay controls loop="0" poster="1.jpg">
    <source src="1.mp4" />
    <source src="1.ogg" />
    <source src="1.webM" />
    您的浏览器不支持视频播放
</video>

CSS基础

使用方式

三种使用方式

<!-- 外部导入 -->
<link href="other.css" type="text/css" rel="stylesheet" />

<!-- html内书写 -->
<style type="text/css"> </style>

<!-- 标签属性设置 -->
<p style="display: inline-block; width: 150px" >xxx</p>

文字

font-style: italic; /* 可以使用normal、italic、oblique(人为地控制字体倾斜,而不是去使用倾斜字体) */
font-weight: 700; /* 100的整数倍(100-900),也可以使用normal、bold、boler、lighter,700等同于bold */
font-size: 14px;
font-family: 'Microsoft Yahei',simsun,'\5B8B\4F53'; /* 中文最好用unicode编码,含空格、中文、unicode编码都要使用引号,英文可以不用引号 */

/*font: italic bold 16px 'Microsoft Yahei'; 一键设置,font: font-style font-weight font-size font-family*/

line-height: normal; /* 行高,使用数字时可选的单位有 像素px、相对值em,百分比% */
letter-spacing: 1px; /* 字间距 */
word-spacing: 1px; /* 单词间距,仅对英文有效 */
word-wrap: break-word; /* 自动换行,主要处理英文单词,可以使用normal、break-word、keep-all(只在半角空格或连字符处换行,连字符即横杠-) */

word-break: normal; /* 自动换行,主要处理英文单词,可以使用normal、break-all(允许在单词内换行)、keep-all(只在半角空格或连字符处换行) */
white-space: normal; /* 可以使用normal、nowrap(强制在一行中显示所有文本,除非遇到br) */
text-overflow: clip; /* 文本内容超出div大小时,是否使用省略标记...显示,可以使用clip(不显示...,显示完整内容,要让超出的内容隐藏,要配合overflow: hidden使用)、ellipsis(超出部分用...替代) */

text-align: center; /* 文字在水平方向的对齐方式,可以使用left、right、center;想要垂直居中,设置line-height等于div的高度 */
text-indent: 2em; /* 首行缩进两个字的距离 */
text-shadow: 1px 2px 3px rgba(0,0,0,0,4); /* text-shadow: 水平位置 垂直位置 模糊距离 阴影颜色 */
text-decoration: none; /* none可以去掉超链接的下划线,可选值有none、underline、overline、line-through */

也可以通过@font-face自定义字体

@font-face{
    font-family: myfont;    /* 创建名字为myfont的字体 */
    font-weight: 400;
    font-style: normal;
    src: url(./myfont.eot) format('embedded-opentype'), url(./myfont.woff) format('woff'), url(./myfont.ttf) format('truetype'), url(./myfont.svg) format('svg');
}

字体图标其实就是添加自定义字体,然后使用before伪元素在指定class的前面添加content为指定Unicode编码的内容

背景颜色、背景图片

/* 背景颜色 */
color: rgba(0,0,0,0.5); /* 普通颜色可以用rgb,透明颜色用rgba */
background-color: pink;

/* 线性渐变 */
background: linear-gradient(red, blue);
background: -webkit-linear-gradient(red, blue);     /* 带私有前缀,兼容不同的浏览器 */
background: -o-linear-gradient(red, blue);
background: -moz-linear-gradient(red, blue);

background: linear-gradient(to right, red,orange,yellow,green,blue,indigo,violet);  /* 可以有多个颜色 */

background: linear-gradient(to right, pink, hotpink, deeppink);     /* 指定方向(不指定时默认从上到下) */
background: linear-gradient(right, pink, hotpink, deeppink);        /* to可以省略 */
background: linear-gradient(bottom right, pink, hotpink, deeppink); /* 对角方向 */
background: linear-gradient(180deg, pink, hotpink, deeppink);       /* 指定角度 */

background: repeating-linear-gradient(pink, hotpink 10%, deeppink 20%); /* 重复线性渐变,同样也有兼容性的写法(略) */

/* 径向渐变(从中心到四周) */
background: radial-gradient(pink, hotpink, deeppink); /* 同样也有兼容性的写法(略) */
background: radial-gradient(pink 5%, hotpink 15%, deeppink 60%);    /* 颜色结点不均匀分布 */
background: radial-gradient(circle, pink, hotpink, deeppink);       /* 设置形状 */

background: repeating-radial-gradient(pink, hotpink 15%, deeppink 20%); /* 重复径向渐变 */


/* 背景图片 */
background-image: url(images/1.jpg);
background-repeat: no-repeat; /* 背景是否平铺,可选值有repeat、no-repeat、repeat-x、repeat-y、round(缩放)、spcae */
background-position: left top; /* 水平、垂直对齐方式,可以使用left top right bottom center或者直接写像素值 */
background-attachment: scroll; /* 背景固定(fixed)还是滚动(scroll) */

/* background: 背景颜色 背景图片地址 背景平铺 背景滚动 背景位置x 背景位置y / 图片缩放目标宽度 图片缩放目标高度; */
background: transparent url('pic.png') no-repeat fixed 50px 50px / 200px 200px;
/* 语义明确的可以跳过 */
background: url('pic.png') no-repeat 0px -50px / 200px 200px;

/* 图片默认从border开始显示,要让图片从padding或content中显示,可以设置background-origin */
background-origin: content-box; /* 可选值有border-box、padding-box、content-box */

background-size: 100px 200px; /* 或者只写宽度,不写高度(如 background-size: 100px;),可以等比例缩放;又或者直接写百分比 */
background-size: cover; /* cover:宽高按长的缩放,溢出部分隐藏 contain:宽高按短的缩放,保证图片完整显示,空白部分显示background-color */

width: 80%;     /* 宽、高,可以写数值值或百分比 */
height: 100px;

精灵图:把所有小图片放到一张背景透明的大图片中,然后通过background-position指定小图片在大图片中的哪个位置(一般是负数,坐标取反)。使用精灵图可以减少请求数,减轻服务器压力,但它的维护成本较高

边框、显示模式

/* 一键设置边框属性:border: border-width border-style border-color */
border: 2px solid gray; /* 边框样式有:none、solid、dashed、dotted、double */
border-radius: 5px; /* 圆角边框,如果设置为宽高的一半或者写百分比50%,则会变成圆,也可以分别指定四个角或两个对角的值 */

/* 盒子阴影效果:box-shadow: 水平位置 垂直位置 模糊距离 阴影尺寸 阴影颜色 内/外阴影(outset、inset,默认outset); 前两个属性必须写,后面可以省略 */
box-shadow: 5px 5px 3px 4px rgba(0, 0, 0, .4) inset;

/*  边框图片
    border-image: source slice width outset repeat;
    border-image: 图片地址 内偏移 边框宽度 外偏移 是否重复(repeat、stretch(拉伸)、round(铺满)) */
border-image: url(border.png) 30 30 20 round;
-webkit-border-image: url(border.png) 30 30 20 round;   /* 为保证兼容性,最好加上私有前缀 */

/* 显示模式,有block、inline、inline-block三种可选值 */
display: block;

显示模式分为:

block

块元素,元素独占一行,宽度是屏幕宽度,高度包裹内容。即使设置了宽度,下一元素也是从下一行开始的

inline

行内元素,元素包裹内容

  1. 像a、span、p这些行内元素不能包含块级元素

  2. 行内元素无法设置宽高

  3. 行内元素无法设置上下外边距(margin-top、margin-bottom,或者margin中上下部分无效),只能设置左右外边距,而且设置内边距(padding)时会有兼容性问题,不同浏览器会有不同效果

inline-block

行内块元素(比如img、input、td、textarea、form),介于前面两者之间,元素包裹内容,可以在一行内显示,且可以设置宽高

inline-block元素之间有空隙,无法紧凑地排列,但可以通过给父元素加上font-size:0;让垂直间隙消失,并给行内块元素加上vertical-align:bottom;,来消除空隙

内外边距

默认html内容无法占满整个浏览器页面,所以一般配合通配符选择器清除所有的内、外边距

* {
    margin: 0px; /* 外边距,margin: 0 auto可以实现div水平居中,但必须指定width才能生效 */

    padding: 0px; /* 内边距 */
    /* padding: 10px 20px 30px 40px; 指定四个时方向为顺时针,即上 右 下 左 */
}

注意事项:

margin

  1. 当有一上一下两个div,上面的div设置了margin-bottom,下面的设置了margin-top,最终它们之间的距离不是相加,而是取margin-bottom和margin-top之间的最大值,这又称为外边距合并

  2. 当两个嵌套的div,内部的div设置了margin-top,此时不是内部的div距离外部的div顶部margin-top的距离,而是内外的div顶部依然保持0px的距离,且外部div离上面的其他元素margin-top的距离,这又叫外边距塌陷。解决方案:给外部的div设置1px的外边框或上内边距,或添加overflow: hidden属性

padding

一般padding会把border撑大(div宽度 = width + padding + border,总宽度还要加上margin,高度同理。如果指定width:100%及padding,会超出屏幕宽度),但如果没有指定宽/高,或只是继承了父标签的宽/高。那么padding不会影响div大小(即不会把border撑大,块元素仍然是占一行,即屏幕宽度,或者仍然是父标签的宽/高)

div宽度计算中,padding和border是在width外面的,在CSS3中,可以通过box-sizing来改变计算方式:content-box表示 宽度 = width + padding + border,即保持原有的计算方式;border-box表示 宽度 = width,即padding和border包含在width里,此时padding不再撑开div了

/* 可选值有content-box、border-box */
box-sizing: content-box;

选择器

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>css</title>
    <style type="text/css">
        /* 通配符选择器 */
        * {}

        /* 标签选择器 */
        div {}

        /* 类选择器 */
        .text1 {}

        /* id选择器 */
        #text3 {}

        /* 链接伪类选择器必须按照该顺序设置,否则不会生效(也可以只使用单个) */
        .a1:link {}
        .a1:visited {}
        .a1:hover {}
        .a1:active {}

        /* 结构伪类选择器(nth-child:获取父元素,然后找指定位置的元素,如果该位置的元素和所需类型不一致,则当前选项无效。nth-of-type:获取父元素,然后获取所有指定类型的子元素,选中指定位置) */
        .ul1 li:first-child {}
        .ul1 li:last-child {}
        .ul1 li:nth-child(2n-1) { color: pink; } /* 可以使用数字、odd、even、字母n,或字母n前加数字表示选中几的倍数 */
        .ul1 li:nth-last-child(4) {} /* 从后面数起 */

        .ul1 li:nth-of-type(2n) {}


        /* 目标伪类选择器,点击超链接后锚点的样式 */
        :target { color: green; }

        /* 复合选择器 */
        /* 并集选择器,多个选择器之间用逗号分隔,重复属性只需写一次 */
        div, span, p { font-size: 14px; }

        /* 交集选择器(标签选择器+类选择器,中间没有空格) */
        div.div1 { font-size: 23px;}

        /* 后代选择器,用空格分隔,把所有后代中对应的元素选中 */
        .ul2 li { font-size: 14px; }

        /* 子元素选择器,只选择亲儿子 */
        .ul2 > li {
            list-style: none; /* 取消小点 */
        }
        
        /* 相邻兄弟选择器,选择紧跟着当前元素的指定元素(最多选择一个) */
        .li1 + li {  }
        /* 如果是 li + li 则可以选择除第一个以外的所有li */

        /* 通用兄弟选择器,选择在当前元素后面的所有指定元素(不包括当前元素),不要求严格相邻 */
        .li1 ~ li {  }


        /* 属性选择器,选中带有某个属性的元素 */
        a[title] { color: red; }
        a[class^=link] { font-style: italic; } /* ^=表示选中class以link开头的元素,$=表示以xxx结尾,*=表示含有xxx字符 */

        /* 伪元素选择器("::"也可以写成":"(可以兼容旧浏览器),但规范建议伪类使用":",伪元素使用"::") */
        p::first-letter { color: red;font-size:23px; }
        p::first-line { color: green; }
        p::selection { color: pink; } /* 鼠标选中时的颜色 */
        /* 伪元素选择器本质上是在指定的标签前/后插入了一个inline元素,该元素可以认为是子元素,里面如果写width:100%是当前元素的宽度,position:absolute;top:0;left:0;是在当前元素的左上角 */
        .text4::before {
            content: "前缀 ";     /* 必须添加content才能生效,如果不需要,可以设置成空字符串"" */
        }
        .text4::after {
            content: " 后缀";
        }

        /* 伪类和伪元素可以混合使用 */
        /* 实现鼠标经过显示border */
        .div2 {
            width: 100px;
            height: 100px;
            position: relative;
            background-color: skyblue;
        }
        .div3:hover::before{
            content: "";
            width: 100%; /* content为空时,100%指标签本身的宽/高 */
            height: 100%;
            border: 5px solid red;
            display: block; /* 伪元素是行内元素,需要更改显示模式 */
            position: absolute; /* 让伪元素不占位置,此时父标签还要设置相对布局 */
            top: -5px;
            left: -5px;
        }
    </style>
</head>
<body>

    <div>
        <!-- class可以有多个,而id只能有一个 -->
        <div class="text1 text2">文本1</div>
        <div id="text3">文本2</div>
    </div>

    <ul class="ul1">
        <li>数据1</li>
        <li>数据2</li>
        <li>数据3</li>
        <div>数据4</div>
        <div>数据5</div>
        <li>数据6</li>
        <li>数据7</li>
    </ul>

    <a href="#title1" class="a1" >超链接</a>
    <h1 id="title1">标题</h1>

    <div class="div1">数据1</div>

    <ul class="ul2">
        <li>一级菜单
            <ul>
                <li class="li1">二级菜单1</li>
                <li>二级菜单2</li>
                <li>二级菜单3</li>
            </ul>
        </li>
    </ul>

    <a href="#" title="我是超链接" class="link1">超链接1</a>
    <a href="#" class="link2">超链接2</a>
    <a href="#" class="other-link">超链接3</a>

    <p style="display: inline-block; width: 150px" >
        我是一段文本我是一段文本我是一段文本我是一段文本
    </p>

    <div class="text4">我是一段文本</div>

    <div class="div2">
        <div class="div3">我是一段文本我是一段文本我是一段文本</div>
    </div>

</body>
</html>

属性继承及优先级

子标签会继承父标签的部分样式

不可继承的样式有:

其他属性都是可继承的

选择器优先级

  1. 优先级:继承的样式 < 标签选择器 < 伪元素选择器 < 类选择器 = 伪类选择器 = 属性选择器 < id选择器 < 标签用style属性

  2. 使用复合选择器的优先级大于只用普通的选择器(比如 标签选择器+后代选择器 > 只使用标签选择器,又如 类选择器+标签选择器+后代选择器 > 标签选择器+标签选择器+后代选择器),如果复合选择器中出现过的选择器类型相同,相同类型的选择器之间使用次数越多,权重越大

  3. 相同优先级按就近原则

  4. 如果使用了!important,表示最高优先级(只适用于当前标签,继承带!important的样式仍然是最低优先级)

/* !important用法 */
color: orange!important;

浮动

浮动

块级元素是独占一行的,虽然inline-block可以使多个块级元素在一行显示,但是元素之间有空隙,不方便处理,这时可以使用浮动(float)

  1. 第一个子元素是浮动元素,则对齐父元素的内边距;如果后面有不浮动的子元素,则从父元素开头开始排列,即浮动元素会挡住不浮动的元素

  2. 如果第一个子元素不是浮动元素,且后面有浮动的子元素

    1. 如果不浮动的元素是块级元素,则浮动的元素会换行再对齐父元素内边距再进行浮动
    2. 如果不浮动的元素是行内元素或行内块元素,则不浮动的元素放到浮动元素后面显示(但是不会换行,如果浮动元素需要换行,则不浮动的元素会放到当前行最后)
  3. 其他元素与上一个浮动的元素顶部对齐;如果浮动元素之间插了一个不浮动块级元素,则换行再对齐父元素内边距再进行浮动

  4. 文本内容是例外,浮动元素可能会挡住不浮动元素,但不浮动元素里面的文本内容永远不会被挡住(如果浮动元素完全挡住了不浮动元素,则文本内容会被挤出不浮动元素,在外面显示)

  5. 浮动在一行排列不下时会自动换行(除非单个浮动元素宽度已经大于父元素宽度),换行没有下界,可以超出父元素下边界

  6. 浮动的元素具有inline-block的特性(后面元素不换行显示、可以指定宽高)

使用浮动的标签最好用一个标准排列方式的父元素包裹,且只要有一个子元素浮动,那么所有子元素都浮动才能对齐,而且这样可以避免意料之外的效果

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>css</title>
    <style type="text/css">
        div {
            width: 200px;
            height: 200px;
            float: left; /* 可选值有left、right、none,右浮动时元素从右往左排列,会和定义的顺序相反*/
            background-color: purple;
            border: 2px solid gray;
        }
        span:nth-child(1) {
            display: block;
            border: 2px solid red;
            padding-left: 50px;
            background-color: lightblue;
        }
        span:nth-child(2) {
            display: block;
            border: 2px solid red;
            height: 500px;
            width: 500px;
            background-color: blue;
        }
        div:nth-child(1) {
            background-color: pink;
            display: inline-block;
            float: none;
        }
        div:nth-child(2) {
            background-color: hotpink;
        }
        div:nth-child(3) {
            background-color: deeppink;
        }
        div:nth-child(4) {
            background-color: skyblue;
            height: 350px;
            float: none; /* 不浮动的元素按照标准的排列方式继续排列,排列在上一个不浮动的元素后面 */
        }

        /* 文字环绕图片 */
        #containerdiv {
            float: none;
            margin: 100px;
            width: 400px;
            height: 400px;
            background-color: transparent;
            border: 2px solid gray;
            word-wrap: break-word;  /* 英文需要加break-word,否则一行装不下一个连续英文(英文单词以空格分隔,没有空格会认为是一个单词)时,会换行,无法实现包裹 */
        }
        #containerdiv img {
            width:200px;
            height:200px;
            background-color: pink;
            float: left;    /* 文字环绕图片只需让图片浮动即可,就是利用了文本不会被挡住的特性 */
        }
        #containerdiv p {
            /* overflow: hidden; 触发BFC后,文字和图片(或者其他两个任意类型的元素)会分开两列显示,无法达到文字环绕图片的效果,但一般会用这个特性做两列的布局 */
        }
    </style>
</head>
<body>
    <span>
        <div>1</div>
        <div>2</div>
        <div>3</div>
        <div>4</div>
        <div>5</div>
        <div>6</div>
    </span>

    <span></span>

    <!-- 文字环绕图片 -->
    <div id="containerdiv">
        <img style="" />
        <p>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本我是一段文本</p>
    </div>
</body>
</html>

清除浮动

由于浮动不占用标准排列的高/宽度(不占内容高/宽度),所以如果父元素不指定高度时,会包裹内容,所以父元素的内容高度为最后一个不浮动的子元素的底部位置,这时如果全部元素都是浮动的话,那么父元素的高度为0,这会导致浮动的子元素会挡住父元素后面的兄弟元素,这时需要清除浮动,使父元素的兄弟元素跟在浮动元素下面

clear可选值有left、right、both,清除浮动只对当前元素生效,所以如果当前元素使用了浮动(包括内部元素),则下一个兄弟节点就要使用clear:both来让自己处于上一元素中所有浮动元素的下方(但是此时上一使用浮动的元素的内容高度仍然是到最后一个不浮动的子元素的底部位置)

为了让当前元素使用浮动后,下一兄弟元素不需要使用clear:both(元素顺序可能随时更改,不知道哪个才是下一元素),而是让自身使用清除浮动,我们一般通过伪元素来清除浮动(此时使用浮动的元素的内容高度到最后一个浮动元素的底部,即浮动元素占据内容高度)

BFC

某些操作会触发BFC(Block Fromatting Context,块级格式化上下文),从而产生自己的块级格式化上下文,块级格式化不同上下文的元素之间完全绝缘,使自己的浮动不影响后面的元素,其他元素的浮动也不会影响自己(按正常顺序放置,不会挡住其他元素,其他元素也不会挡住自己),所以BFC也可以达到清除浮动的效果

触发BFC的操作: 1. float的值不是none 2. position的值不是static或者relative 3. display的值是inline-block、table-cell、flex、table-caption或者inline-flex 4. overflow的值不是visible

触发BFC(除了通过float触发的情况)可能会对元素有一些额外的效果: 1. BFC可以让浮动元素撑开内容高度 2. BFC也可以解决外边距合并的问题,由于在两个块级格式化上下文中,两个div是绝缘的,使用margin就不会合并外边距了 3. BFC会让浮动和文字隔开,这样文字就无法环绕图片,而是分开两列显示 4. 如果我们创建一个占满整个容器宽度的多列布局,在某些浏览器中最后一列有时候会掉到下一行,这是因为浏览器四舍五入了列宽从而所有列的总宽度会超出容器。但如果我们在多列布局中的最后一列里创建一个新的BFC,它将总是占据其他列先占位完毕后剩下的空间

清除浮动的方法

<!-- 方法三:使用after伪元素选择器,其实就是自动在后面添加额外标签 -->
<style>
    /* 用一个冒号是因为要兼容旧版浏览器 */
    .clearfix:after {
        content: "."; /* content随便写,但不能为空,否则旧版浏览器会有空隙 */
        display: block;
        height: 0;
        clear: both;
        visibility: hidden;
    }
    .clearfix { *zoom: 1; } /* 兼容IE6、7 */

    /* 方法四:使用before和after两个伪元素
    .clearfix:before, .clearfix:after {
        display: table; 这里会触发BFC
        content: "";
    }
    .clearfix:after { clear: both; }
    .clearfix { *zoom: 1; }
    */
</style>

<!-- 方法二:父元素添加overflow清除浮动影响(触发BFC),用hidden、scroll、auto都可以 -->
<span style="overflow: hidden;" class="clearfix">
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
    <div>6</div>
</span>

<!-- 方法一:在使用浮动(包括元素本身不浮动,但其内部元素使用了浮动)的元素后面添加额外标签
<div style="clear: both;"></div> -->

<span></span>

定位

定位通过边偏移+定位方式决定位置

/* 边偏移 */
left: 50%;
top: 0px;
/* right: 0px;
bottom: 0px; */

/* 定位方式,可选值有static(默认值)、relative、absolute、fixed */
position: absolute;

定位方式 - static: 自动定位,按照标签顺序排列(即文档流),不适用于边偏移,默认定位方式 - relative: 相对定位,相对于文档流的位置(即原来的位置)定位,且依然占用着原来的位置 - absolute: 绝对定位,相对于上一已定位的父元素定位(不能是static),它随着滚动条滚动,但不占用原来的位置。一般如果子元素使用了绝对定位,父元素要使用相对定位;绝对定位要居中显示,margin: 0,auto不再生效,需自己计算坐标(left为50%,margin-left为自己宽度一半的负值);绝对定位会把元素转换成inline-block - fixed: 固定定位,相当于浏览器窗口固定,不随滚动条滚动,不占用原来的位置。固定定位width: 100%是指内容宽度;固定定位会把元素转换成inline-block

总结

定位方式 边偏移 占用位置 滚动 自动转换
static 不适用 占用 滚动
relative 适用 占用 滚动
absolute 适用 不占用 滚动 inline-block
fixed 适用 不占用 不滚动 inline-block

使用上述定位可能会导致底部元素被遮挡,这时可以通过z-index调整叠放顺序(只有使用relative、absolute或fixed才会生效),z-index取值可以是正、负整数或0,默认值是0,值越大,越往上

z-index: 1;

显示、隐藏

display: none; /* 可选值有block、inline-block、inline、flex、list-item、table、inherit、none等等,隐藏后元素不保留位置 */

visibility: hidden; /* 可选值有visible、hidden,隐藏后元素保留原有位置 */


overflow: auto; /* 可选值有visible、auto、hidden、scroll,指内容超出指定大小时如何显示 */
overflow-y: scroll; /* 可以指定方向,scroll无论是否超出,都会一直显示滚动条,所以最好用auto */


/* 要使滚动条隐藏,但仍然可以滚动,有如下方法 */
/* 方法一:当前div使用-webkit-scrollbar(只适用于Chrome和Safari) */
.container::-webkit-scrollbar { display: none; }

/* 方法二:在外面套一层div,使用overflow: hidden(移动端适配不好) */
.container-wrapper { overflow: hidden; }

/* 方法三:使用padding,让滚动条在显示区域外 */
.container {
    overflow-x: scroll;
    overflow-y: hidden;
    /* 解决ios上滑动不流畅 */
    -webkit-overflow-scrolling: touch;
    padding-right: 25px;
}

其他

cursor: pointer; /* 鼠标移动到元素上方的鼠标样式,可选值有:default、pointer、move、text */

outline: 0; /* outline: outline-color outline-style outline-width; 设置为0可以清除轮廓线(比如input的type=text时会有轮廓线) */

resize: none; /* textarea设置后可以禁止拖拽 */

vertical-align: middle; /* 垂直对齐,对块级元素无效;一般用于控制图片和文字的对齐方式(加在img上),由于字体高度不同,可选值有:baseline(默认)、top、middle、bottom */


/* div里面套img等行内块元素时,img的底部默认对齐div的baseline,导致在底部会有小缝隙(在低版本IE下特别明显),解决方法如下 */
/* 方法一:给img添加vertical-align,值可以是任何除baseline以外的属性 */
vertical-align: middle;

/* 方法二:给img添加display: block,只要不是inline-block,就不会和baseline对齐了 */
display: block

过渡、动画

/* transition: 要过渡的属性(可以使用all过渡所有属性) 花费时间 速度曲线(默认是ease) 何时开始(默认是0s); 前两个属性必须写,后面可以省略;时间的单位必须写(可以是s或ms)
   transition: transition-property transition-duration transition-timing-function transition-delay; */
transition: width 0.5s ease-in 0; /* 速度曲线有:linear、ease(逐渐减速)、ease-in(加速)、ease-out(减速)、ease-in-out(先加速再减速) */

transition: all 0.2s steps(4);  /* 分4步完成,中间没有动画效果 */


/* 变形效果要配合过渡效果使用才能实现动画效果
   变形效果必须写单位,即使是0也要写
   如果有多种变形效果,写到一个transform里,用空格隔开 */

/* 2D效果 */
transform: translate(50px, 50px); /* 平移(水平、垂直) */
transform: translate(50px); /* 只写一个参数,则是水平方向平移 */
transform: translateX(50px); /* 2D效果有translateX、translateY */
transform: translate(50%, 0px); /* 相对于自己的宽度平移50% */
/* 水平、垂直居中的写法
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
*/

transform: scale(0, 0.2); /* 缩放(水平、垂直),也有scaleX、scaleY */
transform: scale(0.2); /* 只写一个参数则水平、垂直都缩放 */

transform: rotate(90deg); /* 旋转,正值为顺时针,负值为逆时针,单位为deg,即度 */
transform-origin: left center; /* 旋转默认是围绕中心点的,transform-origin可以更改旋转点,可以设置像素值 */

transform: skew(45deg, 0deg); /* 倾斜(水平、垂直),单位为deg,度 */


/* 3D效果 */
perspective: 1000px; /* 视距,模拟眼睛到屏幕的距离,一般在子元素使用3D效果时,给父元素添加perspective属性 */
perspective-origin: center center; /* 中心点,可以设四个方向或百分比或像素值 */
-webkit-perspective-origin: center center;

transform-style: preserve-3d; /* 是否保留变换后的状态,默认是flat(恢复原状)、preserve-3d(保留3d状态) */

transform: rotateX(180deg); /* 3D旋转效果,有rotateX、rotateY、rotateZ */
transform: rotate3d(1, 1.5, 0, -20deg); /* x、y、z三个方向的矢量,及度数(每个方向的度数 = 矢量 * 度数) */

transform: translateZ(50px);
transform: translate3d(10px, 10px, 20px); /* translate3d(x,y,z),x、y可以是百分比,z只能是像素值 */

backface-visibility: hidden; /* 当元素正面不可见时(翻转超过90°后背面朝上时)是否显示 */


/* 动画效果
    animation: animation-name animation-duration animation-timing-function animation-delay animation-iteration-count animation-direction; 前两个属性必须写,后面可以省略
    animation: 动画名称 一次动画的持续时间 速度曲线(默认是ease) 开始时间 播放次数(无限循环可用infinite) 是否反方向(normal、reverse、alternate(偶数次反方向)、alternate-reverse、inherit);

    animation-fill-mode: forwards; 动画结束时停留,不恢复原状(默认是none)
    animation-play-state: running; 规定动画是否暂停,running、paused,一般用于鼠标悬浮时暂停动画

    动画需配合@keyframes使用
    @keyframes animation-name {
        from {  }
        to {  }
    }
    或者使用百分比(一次动画时间的百分比)
    @keyframes animation-name {
        0% {  }
        25% {  }
        50% {  }
        100% {  }
    }
*/
div { animation: myanim 5s; }
@keyframes myanim {
    0% { background: red; }
    25% { background: yellow; }
    50% { background: blue; }
    100% { background: green; }
}

比如

<meta charset="UTF-8">
<style>
    .div1 {
        width: 200px;
        height: 100px;
        background-color: pink;
        transition: width 0.5s ease-in 0s, height 0.5s ease-in 0.25s;
    }
    .div1:hover {
        width: 400px;
        height: 200px;
    }

    .div2 {
        width: 200px;
        height: 100px;
        margin-top: 40px;
        background-color: purple;
        transition: all 0.5s;
    }
    .div2:hover {
        transform: rotate(90deg);
    }

    /* 水平方向来回滑动的滑块 */
    .slider1 {
        margin-top: 40px;
        height: 100px;
        position: relative;
        perspective: 1000px;
    }
    .slider1 div {
        width: 200px;
        height: 100px;
        position: absolute;
    }
    .slider1 div:first-child {
        background-color: hotpink;
        animation: sliding1 5s infinite;
        backface-visibility: hidden;
    }
    .slider1 div:last-child {
        background-color: skyblue;
        z-index: -1;
        animation: sliding1 5s infinite;
    }
    @keyframes sliding1 {
        0% {
            transform: translate3d(0, 0, 0);
        }
        40% {
            transform: translate3d(400px, 0, 0);
        }
        50% {
            transform: translate3d(400px, 0, 0) rotateY(180deg); /* 多种变形用空格隔开 */
        }
        90% {
            transform: translate3d(0, 0, 0) rotateY(180deg);
        }
        100% {
            transform: translate3d(0, 0, 0);
        }
    }

    /* 无限滚动广告条,其实就是把广告复制两份,然后滚动 */
    .adv1 {
        width: 300px; /* width设置为一份广告的宽度 */
        height: 100px;
        margin: 40px auto;
        overflow: hidden;
        border: 2px solid red;
    }
    .adv1 ul {
        width: 200%; /* .father2设置了overflow: hidden,需要设置width: 200%,否则float会换行显示 */
        margin: 0;
        padding: 0;
        animation: rolling1 5s linear infinite;
    }
    .adv1 ul:hover { animation-play-state: paused; }
    .adv1 ul li {
        width: 100px;
        height: 100px;
        display: inline-block;
        float: left;
    }
    @keyframes rolling1 {
        from { transform: translateX(0); }
        to { transform: translateX(-300px); }
    }
</style>

<div class="div1"></div>

<div class="div2"></div>

<!-- 水平方向来回滑动的滑块 -->
<div class="slider1">
    <div></div>
    <div></div>
</div>

<!-- 无限滚动广告条,其实就是把广告复制两份,然后滚动 -->
<div class="adv1">
    <ul>
        <li style="background-color: pink;">广告1</li>
        <li style="background-color: hotpink;">广告2</li>
        <li style="background-color: deeppink;">广告3</li>

        
        <li style="background-color: pink;">广告1(复制)</li>
        <li style="background-color: hotpink;">广告2(复制)</li>
        <li style="background-color: deeppink;">广告3(复制)</li>
    </ul>
</div>

伸缩布局

传统方式把div三等分可以使用width: 33.3%加float: left,但这样使用margin时会导致float换行显示;而如果使用伸缩布局,则可以正常使用margin

<style>
    .div1 {
        width: 80%;
        height: 400px;
        border: 1px solid purple;
        margin: 100px auto;

        display: flex; /* 父元素显示模式设为flex */

        justify-content: flex-start; /* 水平对齐方式。当只设置父元素为flex但子元素全部都没有指定份数而是写width时,相当于float(但元素排列顺序不变),这时div可能会没有占满,这时可以指定对齐方向,可选值有flex-start、flex-end、center、space-between、space-around */
        align-items: stretch; /* 侧轴对齐方式(即垂直方向对齐方式),stretch(默认值,子元素不指定高度时拉伸元素以适应容器,子元素指定高度时上对齐)、center、flex-start、flex-end */
        align-content: flex-start; /* aligin-items只适用于一行的情况,如果有多行,需使用aligin-content,它的可选值和aligin-items一样。只有设置了flex-direction: row和flex-wrap: wrap才会生效 */

        flex-direction: row; /* 主轴方向,可以指定为row、column、row-reverse、column-reverse */
        flex-wrap: wrap;     /* 是否自动换行,nowrap(默认值,不换行,不够则收缩显示)、wrap、wrap-reverse */
        flex-flow: row wrap; /* 一键设置flex-flow: flex-direction flex-wrap; */

        flex-grow: 1;       /* 如果所有子元素都设置了width,那么设置了flex-grow的子元素会按份数瓜分父元素剩余空间 */
        flex-basis: 100px;  /* 默认为auto,作用和width一样,但优先级要比width高,先跟父容器预约一个宽度,然后剩下的归入到剩余空间 */
        flex-shink: 1;      /* 当所有子元素的width之和大于父元素的width,默认会收缩显示,而flex-shink可以设置各子元素之间收缩的比例 */
    }
    .div1 div {
        width:200px;
        height: 200px;

        /* flex: 1; 子元素占几份就写几,如果有子元素写了width,那么剩下的宽度再按份数分 */
        min-width: 80px; /* 最小宽度,小于最小宽度不再缩放;同理有max-width */

        margin-right: 5px;
    }
    .div1 div:last-child {
        order: -1; /* flex布局可以通过order调整元素顺序,默认值是0,数字越小越靠前 */
    }
</style>

<div class="div1">
    <div style="background-color: pink;"></div>
    <div style="background-color: hotpink;"></div>
    <div style="background-color: deeppink;"></div>
    <div style="background-color: skyblue;"></div>
</div>

自定义属性

自定义属性必须以两个横杠(--)开头,名字可以任意,但区分大小写,使用时通过var(变量名)获取;html中元素的style中也可以使用自定义的css属性。

选择器有作用域的概念,使用时按照DOM树结构从当前节点往上找。全局属性一般声明在:root中(:root选中的是DOM树的根节点,在这里定义的属性相当于是全局的),比如

:root {
    --mycolor: black; /* 全局作用域 */
}
.div1 {
    --mycolor: blue; /* 局部作用域,只能在".div1"选择器及其子元素内使用 */
}
.div2 {
    color: var(--mycolor); /* 使用 */
}
.div2 {
    color: var(--mycolor, black); /* 如果属性不存在,则使用第二个参数(即设置默认值) */
}

自定义的属性是动态计算的

<style>
    :root { --color: blue; }
    div { --color: green; }
    #div1 { --color: red; }
    * { color: var(--color); }
</style>

<p>blue</p> <!-- 没有指定,使用全局的 -->
<div>green</div> <!-- 使用自定义的 -->
<div id='div1'>
    green
    <p>green</p> <!-- #div1及其子元素都使用#div1的样式 -->
</div>

如果定义的属性不完整,需使用calc来进行计算;而如果只是拼接字符串则可以直接使用

:root {
    --size: 20
}
div {
    font-size: calc(var(--size) * 1px); /* 这里直接使用"var(--size)px"的话会变成"20 px",20和px之间会有一个空格,导致解析失败 */
}

div::after {
    content: '--mycolor: 'var(--mycolor);
}

可以重置属性以消除模块受到的影响

--mycolor: initial; /* 重置成未设置的状态 */

--mycolor: inherit; /* 保持不变 */

在JavaScript中获取自定义的属性

//读
const rootStyles = getComputedStyle(document.documentElement);
const varValue = rootStyles.getPropertyValue('--mycolor').trim();
//写
document.documentElement.style.setProperty('--mycolor', 'black');

JavaScript

变量类型

基本类型有:StringNumberBoolean

复合类型有:对象(ObjectArrayDateRegExp)、Function

另外还有nullundefined两个空类型。其中如果是基本类型,undefined是指变量定义了但没有赋值,如果使用没有定义的变量会报错,而非显示undefined;而如果是复合类型,使用了没有定义的成员变量,会显示undefined,而调用了没有定义的方法,会报错

变量可以用var(根据使用位置不同,可以是局部或全局变量)、let(局部变量,只能在本代码中使用)、const(常量)修饰,如果没有使用任何修饰符,则默认是全局变量(但不建议这样用)

基本类型

//--------------基本类型--------------
var a = "123.456";  //String,可以用单引号或双引号
var b = 123.456;    //Number
var c = true;       //Boolean,其实内部用0和1存储


//显示数据方式
var ret = prompt("请输入xxx");
alert(ret);
console.log(ret);

//打印及查看类型
//==比较值(类型不同会先转换再比较,比如null==undefined为true,值与对象类型比较会先转成prototype类型再比较,对象或函数之间比较还是比较地址),===比较地址(NaN之间是不相等的)
console.log(a == b);    //true
console.log(typeof a);
/* 补充:逻辑或 || 如果左边的值为假,则表达式的值为右边的值,比如
div.onClick = function(e){
    e = e || window.event;  //兼任性写法,由于旧版本的浏览器不会自动添加e参数,所以e可能会是undefined
}
*/

JavaScript可以允许我们以换行作为语句的分隔符,而不需要使用分号,但某些特定情况下还是必须使用分号分隔,比如下一行以([/\+-*%,.开始时

//注意:以下代码可以正常运行但作用可能和想象的不一样
var i1 = 1  //局部
i2=2,       //全局,因为前面没有逗号,按照换行作语句分隔符后,i2没有用var修饰,是全局变量
i3=3;       //全局

相互转换:

//--------------转Number--------------
a = parseInt("123.56");     //向下取整,得到123
a = parseFloat("123.56");   //保留小数
a = Number("123.56");       //保留小数
a = Number(true);           //1

//区别:
a = parseFloat("   123.56abc"); //123.56
a = Number("   123.56abc");     //NaN

//隐式转换
a = +"123.456";         //作数学运算会自动转换成Number,比如添加正号,乘以1等
console.log(typeof a);  //number
a = "123.456abc" * 1;   //NaN


//--------------转String--------------
b = String(123.456);
b = 123.456.toString();
b = String(true);       //"true"

//--------------转Boolean--------------
c = Boolean("false");   //得到true,除了0、""、NaN、undefined、false以外,任何东西转Boolean都是true
c = Boolean(NaN);

复合类型

调试的注意事项

关于浏览器console的问题:如果在后面更改了对象的属性,而之前打印的对象在更改后再展开查看,会显示当前的最新值,而非打印时的值(即看到的是展开时的值)

以下代码在2秒内展开,第一次打印的是123,而如果在2秒后再展开,则第一次打印的就成了456

var a = { num: 123 }

console.log(a);

setTimeout(function() {
    a.num = 456;
    console.log(a);
}, 2000);

所以如果打印结果和预期不符时,需考虑是否在后面更改过

Object
//使用new创建的对象,对象类型是它的构造函数名,而使用{}创建的对象,对象的类型是Object
var a = new Object();
a.id = 1;           //如果属性存在,则是修改,如果不存在,则创建属性并赋值(js动态特性)
a.name = "小明";

console.log(a.name);
console.log(a[name]);   //属性可以用点的方式获取,也可以用中括号获取
console.log(typeof a);

function Person() { this.id = 1; }      //构造函数和普通函数一样
Person.Child = function Child() {  };
var p = new Person();

//typeof只能识别出undefined、object、boolean、number、string、function这6种数据类型,而instanceof可以识别具体对象类型
console.log(p instanceof Object);   //true
console.log(p instanceof Person);   //true

//也可以直接创建对象
var a = {'id': 2, 'name': '小明'};
//或者
var a = {};
a.id = 2;
a.name = '小明';

//或者通过 Object.create(对象) 创建和传入对象有相同方法、属性的新对象
//这种方式会把原对象的属性、方法放到原型(__proto__)中,且__proto__中没有constructor属性
var p = { 'id': 1, 'name': '小明', sayHello: function() { console.log('Hello!!')} }
var p2 = Object.create(p);
数组
var b = new Array();        //相当于var b = [],也是创建数组实例
b[0] = "a";
b[1] = "b";
b[2] = "c";

console.log(b);         //["a", "b", "c"]
delete b[1];
console.log(b.length);  //delete不会改变数组长度
console.log(b);         //["a", 2: "c"]
正则
var str = "Hello World!!"; 
var n = str.search(/world/i);   //6,i是修饰符,表示大小写不敏感,修饰符还有g(查找所有匹配而非在找到第一个匹配后停止)、m(执行多行匹配),修饰符也可以不写
console.log(n);

var patt = /world/i;
patt.test("Hello World!!");

/world/i.exec("Hello World!!");

//或者使用RegExp
new RegExp('world');        // /world/
new RegExp('world', 'i');   // /world/i


/*  ES9(ES2018)带来的正则的增强:
    ?=  表示该字符串后面还跟着另一个字符串
    ?!  表示该字符串后面没有跟着另一个字符串

    ?<= 表示该字符串前面还跟着另一个字符串
    ?<! 表示该字符串前面没有跟着另一个字符串 */
/Roger(?= Waters)/.test('Roger is my dog and Roger Waters is a famous musician');   //true
/Roger(?! Waters)/.test('Roger is my dog');     //true
/(?<=Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician');  //true
/(?<!Roger) Waters/.test('Pink Waters is my dog') //true
函数
function sum(a, b){
    return a+b;
}
console.log(sum(2, 3));

//或者使用Function,可以把字符串当作函数体
new Function('console.log("hello")');               //无参
var sum = new Function('a', 'b', 'return a+b;');     //有参
//如果函数体很长,可以使用+连接字符串,或者使用`xxx`字符串模板
var sum = new Function('a', 'b',
`console.log(a);
console.log(b);` );

//类似的还有eval,可以把字符串当作代码执行
eval('var a = 10;');
//eval('{"id": 1, "name": "小明"}');        //语法错误,eval会把最外层的大括号看作是代码块,而不是Object
eval('( {"id": 1, "name": "小明"} )');      //这时需使用小括号转成表达式(小括号内定义的变量在外部无法直接访问,只能通过返回的形式赋值给变量,然后间接访问)


//函数只定义不被调用就不会执行,但自调用函数可以立即执行自己
//自调用函数:(函数)(参数) ,立即调用自身
var arr = ( new Function( 'return ["a", "b", "c"];' ) )();      //无参
var arr = ( new Function( 'a', 'return [a, "b", "c"];' ) )(1);  //有参


//JavaScript的函数在使用时可以少给或多给参数,不一定要严格和定义的参数个数一致
sum(2);     //NaN,因为2+undefined等于NaN

//多传的参数也可以通过arguments获取,arguments和this类似,时函数的隐含属性
 function sum( /* ... */ ){ //一般如果不写参数会加一个注释告诉使用的人这里其实是有参数的,但参数是通过arguments获取
    var ret = 0;
    for(var k in arguments){
        ret += arguments[k];
    }
    return ret;
}
sum(1,2,3,4,5);    //15

关于函数中的this:如果没有指定调用者,则是window;使用newthis是对象本身;如果有指定调用者,那么this就是调用者

function Person(name){
    console.log(this);
    this.name = name;
    this.getName = function () {
        console.log(this);
        return this.name;
    };
    this.setName = function (name) {
        console.log(this);
        this.name = name;
    };
}

Person("小明");//打印的this是window


var p = new Person("小明");//打印的this是p

p.getName();//打印的this是p


var obj = {};
p.setName.call(obj, "小明");//打印的this是obj(把setName方法当作obj的临时方法调用)


var func1 = p.setName;
func1();//打印的this是window


function func2() {
    function func3() {
        console.log(this);
    }
    func3();
}
func2();//打印的this是window

Function可以把代码逻辑分离开来,实现模块化

<!-- 利用了type类型不对时不会执行的特性(也可以放到div中,但是写的时候没有高亮) -->
<script type="mycode1">
    // 代码
    function sayHello() {
        console.log('hello');
    }
    sayHello();
</script>

<script>
    var scripts = document.querySelector("script[type^=mycode1]").innerText;
    (new Function(scripts))();
</script>

ES6(ES2015)带来的函数改进:

//箭头函数
var f1 = (a, b) => { return a+b; }
var f1 = a => a+10;
var f1 = (a, b) => a+b;
var f1 = () => {}; //空函数
var f1 = () => ({name: "小明", age: 14});

//默认参数
var num = 1;
function getValue(){ return ++num; }
function f2(a, b=getValue(), c=b+10){ return a+b;}
f2(1, 2);    //3
f2(1);       //3
f2(1,undefined); //4
f2(1);       //5


//函数参数使用拓展运算符,用于把剩下的参数装进一个变量中
function f3(a, ...arr) {
    let ret = a;
    for(let v of arr) { ret+=v; }
    return ret;
}
f3(1, 2, 3);
//传参也可以使用拓展运算符,用于把数组展开成函数的每个参数
var arr1 = [1, 2, 3];
f3(...arr1);
//拓展运算符还可以用来实现深拷贝
var arr2 = [...arr1];
arr2 = [...arr1, 4, 5, 6];
//把字符串展开成一个个字符
arr2 = [..."love"]; //['l', 'o', 'v', 'e']


//字符串解构
var [s1, s2, s3, s4] = "love";
//解构可以嵌套(包括数组和对象)
var arr3 = [[1, 2, [3, 4]], 5, 6];
var [[a, b, [c, d]], e, f] = arr3;
//循环和函数参数都可以使用解构
var arr4 = [[11, 12], [21, 22], [31, 32]];
for (let [a, b] of arr4) {
    console.log(a, b);
}
function f4({id, name, age}) {
    console.log(id, name, age);
}
f4({id: 1, age: 14, name: "小明"});
//返回值使用解构
function f5() {
    return {id: 1, name: "小明", age: 14};
}
var {name, id, age} = f5();


//原来的回调代码
setTimeout(function() {
    console.log('I promised to run after 1s');
    setTimeout(function() {
        console.log('I promised to run after 2s');
    }, 1000);
}, 1000)
//使用Promises后
const wait = () => new Promise((resolve, reject) => {
    setTimeout(resolve, 1000)
});
wait()
.then(() => {
    console.log('I promised to run after 1s');
    return wait();
})
.then(() => console.log('I promised to run after 2s'));


//使用yield关键字来实现暂停
function *calculator(input) {
    var doubleThat = 2 * (yield (input / 2));
    var another = yield (doubleThat);
    return (input * doubleThat * another);
}
var calc = calculator(10);

calc.next();        // next函数返回 {value: 5, done: false}
calc.next(7);       // 7被doubleThat接收,next函数返回 {value: 14, done: false}
calc.next(100);     // next函数返回 {value: 14000, done: true}

提升问题

变量提升

JavaScript没有块级作用域,只有全局作用域和函数作用域

console.log(a);     // undefined
var a = 'hello';
console.log(a);     // hello

以上写法相当于

var a;

console.log(a);     // undefined
a = 'hello';
console.log(a);     // hello

于是如果函数中含有和全局变量名字相同的变量,则:

var num = 123;
function a() {
    console.log(num);   //undefined
    var num = 456;      //如果没有var,则是修改全局变量,就不存在提升问题,输出就成了123、456
    console.log(num);   //456
}

以上写法相当于

var num = 123;
function a() {
    var num;

    console.log(num);   //undefined
    num = 456;
    console.log(num);   //456
}
函数提升

如果存在和变量相同名字的函数,则函数优先提升(提升变量名,然后把变量名指向函数,而给变量赋值成基本变量是按代码执行顺序执行的):

由于函数优先提升,函数才能先调用,再声明

console.log(a);     //function a() { console('Hello world!!'); }
console.log(a());   //undefined

var a = 'hello';
function a() { console.log('Hello world!!'); }

console.log(a);         //hello
//console.log(a());     //a is not a function

相当于

var a;
a = function () { console.log('Hello world!!'); }

console.log(a);     //function a() { console('Hello world!!'); }
a();                //输出Hello world!!

a = 'hello';

console.log(a);         //hello
//console.log(a());     //a is not a function

于是:

var a = 123;
function a() { console.log(a); }

a();    //a is not a function

相当于

var a;
a = function () { console.log(a); }
a = 123;

a();    //a is not a function
函数声明和函数表达式

函数声明是指定义了一个函数,且该定义和代码逻辑无关(与循环、条件判断、赋值、表达式等无关),单独存在于一个结构中

函数表达式是指把函数定义在了逻辑判断中,与代码逻辑有关,比如放在了循环或条件判断中,或赋值给了一个变量,或用表达式符号(即小括号)包裹住(函数用小括号包裹住之后就不能在小括号外部通过函数名使用了,相当于函数名的作用域在小括号内,但它仍然可以作为返回值赋值给变量,然后通过变量间接调用)

函数表达式不会被提升

//f1();     //f1 is not a function,因为函数表达式不会被提升(而如果提升了的话,无论怎么调都是false,因为会覆盖)

if(true) {
    function f1(){ console.log('true'); }
} else {
    function f1(){ console.log('false'); }
}

f1();       //true,但是在老版本浏览器中可能会是false(把前面的当作函数声明了)

词法作用域

JavaScript没有块级作用域,只有全局作用域和函数作用域

作用域的规则是:

  1. 只有函数才能限制作用域,代码块不能限制
  2. 函数内可以访问外部变量
  3. 优先使用提升规则

例1

if(false) {
    var num = 123;
}

console.log(num);   //undefined

分析:由于变量提升,存在num变量,但if内的语句没有执行,所以没有赋值(如果是不存在num变量会报错,而不是undefined)

例2

var num = 123;

function f2() {
    var num = 456;      //用var表示覆盖掉了上一级作用域的num属性,函数内就无法访问上一级作用域的num
    function f1() {
        console.log(num);
    }
    f1();
}

f2();       //456

分析:由于函数能限制作用域,先在f1内找num,没找到,然后去上一级作用域,即f2中找,找到后输出456

例3

var num = 123;

function f1() {
    console.log(num);
}
function f2() {
    var num = 456;
    f1();
}

f2();       //123

分析:上一级作用域只和函数定义的位置有关,和调用位置无关(也可以通过作用域链分析,这里只给出结论)

闭包

由于词法作用域的特性,所以可以在返回函数时,在返回的函数中使用当前函数定义的变量

function f1() {
    var num = 1;
    return function() {
        num++;
        console.log(num);
    }
}

var func = f1();
func();     //2
func();     //3

通过闭包,函数可以模仿类的功能

function Person() {
    var __name__;       //这里name相当于私有成员,命名规范建议私有成员使用双下划线命名
    return {
        getName: function() { return __name__; },
        setName: function(name) { __name__ = name; }  //注意这里不能使用this,因为this指的是当前json对象,而不是Person,所以没有name属性
    };
}

var p = new Person();   //这里不写new也可以,返回对象时加new和不加new效果一样
p.setName("小明");
p.getName();

可以结合自调用函数使用

var p = (function() {
    var __name__;
    return {
        getName: function() { return __name__; },
        setName: function(name) { __name__ = name; }
    };
})();

使用了闭包后,因为函数中引用着变量,所以函数在不使用后无法正常销毁,会导致程序占用不必要的内存,所以需要手动释放

p = null;

面向对象

这里指的是基于构造函数的对象

//--------------封装特性--------------
function DivTag() {     //构造函数的定义和普通函数一样,但根据命名规范,首字母应该大写
    this.DOM = document.createElement('div');
    this.appendTo = function (node) {
        node.appendChild(this.DOM);
        return this;
    };

    this.css = function (attrs) {
        for(var attrKey in attrs) {
            this.DOM.style[attrKey] = attrs[attrKey];
        }
        return this;
    }
}
// 使用(需使用new,否则就是获取函数返回值)
new DivTag().appendTo(document.body)
        .css({
            'width': '200px',
            'height': '200px',
            'backgroundColor': 'pink'
        });


//--------------值传递和引用传递问题--------------
var a = { 'id': 1, 'name': '小明'};
a.copy = function(obj) {
    for(var k in this){
        obj[k] = this[k];
    }
};
var b = 1;
a.copy(b);  //由于b是基本类型,使用的是值传递,所以b仍然是1,没有变

ES6(ES2015)后还添加了class关键字,可以实现真正意义上的类

class SimpleDate {
    //构造器
    constructor(year, month, day) {
        this._year = year;
        this._month = month;
        let _day = day;     //可以用let实现属性私有化

        //用let实现的私有属性只能在当前代码块中访问,所以需要在当前代码块中提供访问该属性的方法
        this.addDay = function(n) {
            this._day += n;
        }
    }
 
    //方法
    toString(){
        return this._year + "-" + this._month + "-" + this._day;
    }

    //静态方法
    static setDefaultDate(year, month, day) {
        SimpleDate._defaultDate = new SimpleDate(year, month, day);
    }

    //get、set方法,用get、set关键字修饰,当调用obj.year时会调用get,调用obj.year=1时会调用set
    get year() {
        console.log('get year');
        return this._year;
    }
    set year(year) {
        console.log('set year=' + year);
        this._year = year;
    }
}

//子类
class SubClass extends SimpleDate {
    constructor(name, year, month, day) {
        super(year, month, day);
        this._name = name;
    }
}

原型

JavaScript没有严格意义上的类,所以也没有继承的关键字,而原型(prototype)可以实现类似继承的功能,访问属性或函数时如果在对象中找不到就会到原型中找(如果原型中没有,再搜原型的原型,直至搜索到Object.prototype,这就构成了原型链)

原型属性:构造函数的属性(Object.prototype),所有同一个构造函数创建的对象共享同一个prototype,类似继承中的同一个父类,但不同的是,如果对象更改了原型的属性(obj.__proto__.xxx=XXX),会影响到其他使用该原型的对象(即子类可以更改父类属性、方法,且此更改作用于该父类的所有子类)

原型对象:实例的持有的对原型属性的引用(obj.__proto__)称为原型对象,创建对象时会根据prototype生成__proto__constructor

原型属性和原型对象都可以简称为原型

要注意,所有类型包括构造函数都有__proto__属性,它是继承自Object.prototype中的__proto__,但创建实例时是根据构造函数的prototype属性创建的,和__proto__无关

原型的使用

//这样写的话每个对象的sayHello函数不是同一个,每次创建对象时会重复创建函数
function Foo() {
    this.sayHello = function() { console.log('Hello'); }
}
new Foo().sayHello == new Foo().sayHello;   //false


//一般会把函数定义到外面,或者放到prototype中
function Foo() {  }
Foo.prototype.sayHello = function() { console.log('Hello'); }
new Foo().sayHello == new Foo().sayHello;   //true
//也可以用替换的方式添加prototype属性(但是会把constructor属性替换掉,导致没有constructor属性,需要我们手动添加回去)
Foo.prototype = {
    constructor: Foo,
    'id': 1,
    'name': '小明',
    sayHello: function() {  },
    sayGoodBye: function() {  }
}


//如果找不到对象的属性,就会在原型中找
function Foo() {  }
Foo.prototype.id = 1;
var f = new Foo();
console.log(f.id);

//访问属性时找不到才到prototype中找,而赋值时,如果没有该属性,则会创建属性并赋值(js动态特性),与prototype无关
function Foo() {  }
Foo.prototype.id = 1;
var f1 = new Foo();
var f2 = new Foo();
f1.id = 2;
console.log(f1.id);     //2
console.log(f2.id);     //1


//以前访问原型只能通过构造函数,后来添加了通过实例直接访问的支持(但是一般不要用实例修改原型,仅用于调试时方便查看)
function Foo() {  }
var f = new Foo();
console.log(f.__proto__ === Foo.prototype);     //f.__proto__和Foo.prototype是同一个
//补充:如果像前面那样替换掉了prototype,那么在替换前创建的对象对__proto__的更改不会影响Foo.prototype

f.constructor.prototype;    //也可以先获取构造函数再获取原型
Foo.prototype.constructor;  //constructor和prototype可以相互获取

基于原型的继承

function MyArray() {  }
MyArray.prototype = [];     //[]相当于new Array(),继承了该数组实例的所有方法、属性
//也可以使用MyArray.prototype = Array.prototype,但是如果要往MyArray.prototype添加其他方法,这种写法会把我们的方法也添加到Array.prototype,导致污染了Array.prototype

//添加其他方法
MyArray.prototype.myfunc = function() { console.log('hello'); };

var a = new MyArray();
a.push(123);
a.push(456);
console.log(a);

也可以使用Object.create(对象)的方式创建(但是这种方式创建的实例在__proto__中没有constructor属性,无法实现复用)

var p = { 'id': 1, 'name': '小明', sayHello: function() { console.log('Hello!!')} }
var p2 = Object.create(p);  //p的id、name、sayHello及其原型的方法、属性 都会放在p2.__proto__中

原型链

原型链是指对象的__proto__属性的继承关系,而函数的prototype属性是函数持有的对父类__proto__的引用,如果看prototype属性会少掉当前父类这一层关系

通过调试工具查看__proto__的类型时,应该看的是其constructor属性,比如Perosn.__proto__constructorFunction(),说明Perosn.__proto__指向Function.prototype,而Perosn.__proto__.__proto__constructorObject()说明指向Object.prototype,以此类推

以下分析为方便查看,把Perosn.__proto__.__proto__改成了Function.prototype.__proto__,但在浏览器Source工具中watch的变量依然是Perosn.__proto__.__proto__

函数(function Person(){ })的原型链为:Perosn.__proto__->Function.prototypeFunction.prototype.__proto__->Object.prototypeObject.prototype.__proto__->null

对象(var p=new Person())的原型链为:p.__proto__->Person.prototypePerson.prototype.__proto__->Object.prototypeObject.prototype.__proto__->null

如果箭头表示对象的__proto__属性指向,则可以表示为

函数(普通函数或构造函数):Perosn->Function.prototype->Object.prototype->null

Function的构造函数自身:Function.prototype->Object.prototype->null

对象:p->Person.prototype->Object.prototype->null

Object对象:o->Object.prototype->null

注意事项

例1

考虑以下情况:

function Foo() { this.create = function(){ return new Foo(); } }
var f = new Foo();
Foo = 123;
f.create();     //Foo is not a constructor,因为这时Foo已经是123了,无法new Foo()

所以一般不会直接new Foo(),而是使用new this.constructor()

例2

实例默认根据构造函数的prototype中的属性(包括constructor__proto__及自定义的原型属性,其中constructor在定义构造函数时自动添加,__proto__继承自Object或新的原型(用替换方式添加时))生成实例的__proto__属性,该属性就是构造函数的prototype的引用

而替换时如果没有指定constructor,会把构造函数prototype中的constructor属性替换成Object.constructor(如果原型中没有,则替换成原型的原型中的constructor,直至Object.constructor,而这里原型是{}其实就是Object类型),这就导致了实例无法使用constructor属性创建当前类型的实例

所以需要用到constructor时如果使用替换的方式需在prototype中手动添加constructor属性

function Foo() {
    this.create = function(){ return new this.constructor();}
}
Foo.prototype = { constructor: Foo };
var f = new Foo();
Foo = 123;
f.create();

例3

//原型对象持有的是在创建时原型属性的引用
function Foo() {  }
Foo.prototype.func = function(){ console.log('1111'); };
var f1 = new Foo();
Foo.prototype = {
    func: function(){ console.log('2222'); }
};
var f2 = new Foo();
f1.func();      //1111
f2.func();      //2222

//但是如果只是更改了原型属性中的属性,而没有替换掉原型属性,则用的还是同一个原型属性
function Foo() {  }
Foo.prototype.func = function(){ console.log('1111'); };
var f1 = new Foo();
Foo.prototype.func = function(){ console.log('2222'); };
var f2 = new Foo();
f1.func();      //2222
f2.func();      //2222

注意事项总结

  1. 构造函数内部创建自身实例尽量使用new this.constructor()
  2. 用替换方式添加原型时,如果需要在构造函数内使用constructor,要在prototype中添加constructor属性(Foo.prototype = { constructor: Foo }
  3. 原型对象持有的是在创建时原型属性的引用,后面如果更改了原型属性的指向(注意区分 原型属性 和 原型属性的属性),不会影响之前创建的对象

DOM操作

//--------------查找--------------
var div1 = document.getElementById("div1");

var pTag = document.getElementsByTagName("p");  //全局查找,数组类型
pTag = div1.getElementsByTagName("p");          //找子元素,数组类型

document.getElementsByName("name");             //根据name属性设置的值查找,数组类型
document.getElementsByClassName("class1");      //根据class属性设置的值查找,数组类型

document.querySelector('#div1');                //根据选择器获取单个节点,如果有多个匹配,取第一个
document.querySelectorAll('#div1');             //根据选择器获取,数组类型


//--------------更改DOM节点属性--------------
//在body中添加内容,但是在DOM加载完成之后使用document.write()会覆盖文档原有内容
window.onload = function() { document.write(Date()); }      //错误

document.getElementById("div1").innerHTML="新文本!";        //通过innerHTML获取标签的文本内容时,如果有特殊字符,会被转义,如果想获取没有转义的内容,需使用nodeValue

var img1 = document.getElementById("img1")[0];

img1.getAttribute('src');
img1.getAttributeNode('src');              //获取AttributeNode对象,而不是String类型的属性值

img1.src="pic.jpg";                        //更改属性
img1.style.backgroundColor = "blue";
img1.className = 'class2';                 //class是少数的几个和html属性值名字不一样的属性之一
img1.setAttribute('src', 'pic.jpg');
document.querySelector('#checkbox1').checked = false;   //像checked等特殊属性设置的是布尔值,而非字符串


document.getElementsByTagName("image")[0].removeAttribute('backgroundColor');  //删除属性,对于类似checked等特殊属性,不能使用setAttribute('checked', ''),而必须使用removeAttribute删除


/* 在标签中使用JavaScript更改样式时,this指当前标签本身
<h1 onclick="this.innerHTML='Ooops!'">点击文本!</h1>

使用函数
<h1 onclick="changetext(this)">点击文本!</h1>
function changetext(tag) { tag.innerHTML="Ooops!"; }
*/


//--------------更改DOM结构--------------
var parentTag = document.createElement("p");
var childTag = document.createTextNode("这是一个新的段落。");
parentTag.appendChild(childTag);    //插入

var divChild1 = div1.getElementsByTagName("image")[0];
div1.insertBefore(parentTag, divChild1);

parentTag.replaceChild(parentTag, document.createTextNode("2") );   //替换

div1.removeChild(parentTag);    //删除子元素


console.log(div1.nodeName);     //获取节点名字,输出为大写
div1.nodeValue = '文本';        //获取/更改文本节点
console.log(div1.nodeValue);

div1.parentNode;                //访问父节点,和div1.parentElement是一样的

div1.previousSibling;           //访问上一个兄弟节点,但是如果有换行符等文本节点,会把它们当作前一个节点
div1.previousElementSibling;    //访问上一个兄弟节点,忽略文本节点

div1.nextSibling;              //访问下一个兄弟节点
div1.nextElementSibling;        //访问下一个兄弟节点,忽略文本节点

div1.attributes;                //访问属性,数组类型
div1.childNodes;               //访问子节点,数组类型


//--------------监听事件--------------
window.onload = function() { console.log('Hello world!!'); }
pTag[0].click = function() { console.log('click!!'); }

//也可以通过addEventListener添加监听(如果是动态创建的新标签,需先添加到页面中再设置监听)
function myfunc() { console.log('mousemove!!'); }
pTag[0].addEventListener("mousemove", myfunc);
pTag[0].removeEventListener("mousemove", myfunc);

事件监听

常用事件

常用事件有: - onchange: HTML元素改变 - onclick: 点击 - onmouseover: 用户在一个HTML元素上移动鼠标 - onmouseout: 用户从一个HTML元素上移开鼠标 - onkeydown: 用户按下键盘按键 - onload: 浏览器已完成页面的加载,如果script放在body前面,需把用到DOM元素的内容写到window.onload

window.onload是整个文档包括图片等资源加载完毕后才调用,像JQuery等框架还实现了在当前页面结构加载完成调用的函数(JQuery的入口函数$(function() { xxx })就是它自己实现的ready事件),其实现如下

function ready(fn){
    if(document.addEventListener){      //标准浏览器
        document.addEventListener('DOMContentLoaded', function(){
            //注销事件,避免重复触发
            document.removeEventListener('DOMContentLoaded', arguments.callee, false);
            fn();   //执行函数
        }, false);
    }else if(document.attachEvent){     //IE浏览器
        document.attachEvent('onreadystatechange', function(){
            if(document.readyState=='complete'){
                document.detachEvent('onreadystatechange', arguments.callee);
                fn();   //执行函数
            }
        });
    }
}

根据事件传递方向的不同,可以分为事件捕获和事件冒泡。事件捕获是事件从父元素传到子元素,事件冒泡是事件从子元素回传到父元素;通过事件捕获,父元素可以拦截子元素的事件

element.onclick = function() {  }   //事件冒泡,这种写法只能给一个事件绑定一个响应函数,重复绑定会覆盖之前的绑定


//addEventListener一个事件可以绑定多个不同的函数
//事件捕获
element.addEventListener('click', function(e) {
    e = e || window.event;  //兼容旧浏览器的写法,旧浏览器不会把事件e传进函数,需要使用window.event获取
    var targetElement = e.target || e.srcElement;     //兼容旧浏览器的写法
}, true);

//事件冒泡,第三个参数默认是false,可以省略
element.addEventListener('click', function(e) {  }, false);

网络监听

网络事件有: - ononline: 连上网络 - onoffline: 断开网络

window.addEventListener('online', function() {  });
window.addEventListener('offline', function() {  });

全屏接口

全屏事件有: - requestFullScreen(): 开启全屏 - cancelFullScreen(): 退出全屏 - fullScreenElement: 是否为全屏状态

var div = document.querySelector(".div");
document.querySelector("#full").onclick = function() {
    //div.requestFullScreen();

    //兼容不同浏览器的写法
    if(div.requestFullScreen) {
        div.requestFullScreen();
    }
    else if(div.webkitRequestFullScreen) {
        div.webkitRequestFullScreen();  //chrome
    }
    else if(div.mozRequestFullScreen) {
        div.mozRequestFullScreen();     //firefox
    }
    else if(div.msRequestFullScreen) {
        div.msRequestFullScreen();      //IE
    }
}

document.querySelector("#cancelFull").onclick = function() {
    //退出全屏是document调用,而不是某个元素调用
    if(document.cancelFullScreen) {
        document.cancelFullScreen();
    }
    else if(document.webkitRequestFullScreen) {
        document.webkitCancelFullScreen();  //chrome
    }
    else if(document.mozRequestFullScreen) {
        document.mozCancelFullScreen();     //firefox
    }
    else if(document.msRequestFullScreen) {
        document.msCancelFullScreen();      //IE
    }
}

FileReader

FileReader的事件有: - onload: 读取完毕(仅在成功时触发) - onloadstart: 开始读取 - onprogress: 读取中 - onloadend: 读取完毕(无论是否成功都会触发) - onabort: 读取中断 - onerror: 读取失败

使用FileReader实时预览选中的图片:

文件:<input type="file" name="myFile" multiple onchange="prebview(this);" />
<img id="previewImg" />

<script>
    function prebview(element) {
        if(element.files.length > 0){
            var reader = new FileReader();
            reader.onload = function() {
                document.querySelector("#previewImg").src = reader.result;
            }
            reader.readAsDataURL(element.files[0]);
        }
    }
</script>

拖拽

拖拽元素的事件有: - ondrag: 整个拖拽过程都会调用 - ondragstart: 开始拖拽 - ondragleave: 当鼠标离开拖拽元素时调用 - ondragend: 拖拽结束

目标元素的事件有: - ondragenter: 拖拽元素进入 - ondragover: 停留在目标元素上 - ondrop: 在目标元素上松开鼠标,浏览器默认会阻止该事件,除非在ondragover中调用e.preventDefault(),否则不会触发 - ondragleave: 鼠标带着拖拽元素离开目标元素

需要给元素添加draggable="true"的属性

<div id="div1" style="width: 200px;height: 200px;border: 1px solid red;float: left;">
    <p draggable="true" style="background-color: pink;">我是一段文本</p>
</div>
<div id="div2" style="width: 200px;height: 200px;border: 1px solid blue;float: left;margin-left: 20px;"></div>

<script>
    var p = document.querySelector("p");
    var div2 = document.querySelector("#div2");

    var dragElement;

    //拖拽元素事件
    p.ondragstart = function(e) {
        //可以通过全局变量或e.dataTransfer.setData保存当前拖拽的元素
        //e.dataTransfer.setData('text/html', e.target.id); //e.target就是this
        dragElement = this;
    }
    p.ondragend = function(e) {
        dragElement = null;
    }

    //目标元素事件
    div2.ondragover = function(e) {
        e.preventDefault();     //鼠标从禁止图标变成拖拽图标
    }
    div2.ondrop = function(e) {
        //e.dataTransfer.getData只能在ondrop中使用
        //e.target.appendChild(document.getElementById(e.dataTransfer.getData('text/html')));
        if(dragElement) {
            dragElement.parentNode.removeChild(dragElement);
            this.appendChild(dragElement);
        }
    }
</script>

通过事件传递机制,可以给只父元素添加拖拽监听,然后子元素就可以实现拖拽

<div id="div1" style="width: 200px;height: 200px;border: 1px solid red;float: left;">
    <p draggable="true" style="background-color: pink;">我是一段文本1</p>
    <div draggable="true" style="background-color: pink;">我是一段文本2</div>
    <p draggable="true" style="background-color: pink;">我是一段文本3</p>
</div>
<div id="div2" style="width: 200px;height: 200px;border: 1px solid blue;float: left;margin-left: 20px;"></div>

<script>
    //使用同一个拖拽函数的元素的子元素之间可以相互拖拽
    var createDragFunc = function() {
        var dragElement;    //通过闭包的方式获取拖拽元素

        //通过事件捕获的方式给所有子元素拖拽的功能
        return function(element) {
            //拖拽元素事件,e.target为拖拽元素
            element.ondragstart = function(e) {console.log(e.target);
                //只允许子元素拖拽,不允许当前元素拖拽
                if(e.target == element) { return false; }

                //拖拽元素变透明
                if(e.target.style.opacity) { e.target.style.opacity=0.3; }
                dragElement = e.target;
            }
            element.ondragend = function(e) {
                if(e.target == element) { return false; }

                if(e.target.style.opacity) { e.target.style.opacity=1; }
                dragElement = null;
            }

            //目标元素事件,e.target为目标元素
            element.ondragover = function(e) {
                e.preventDefault();
            }
            element.ondrop = function(e) {
                //不允许自己拖到自己身上
                if(dragElement && dragElement!=e.target) { e.target.appendChild(dragElement); }
            }
        }
    }

    var dragFunc = createDragFunc();
    dragFunc(document.querySelector("#div1"));
    dragFunc(document.querySelector("#div2"));

    //dragFunc(document);   //给所有元素添加拖拽监听
</script>

AJAX

AJAX是用JavaScript异步获取数据的一种方式,它遵循同源策略,不能跨域

GET

function callback(data) { console.log(data); }

var xhr = new XMLHttpRequest();     //在浏览器调试工具中,NetWork有一个叫XHR的Filter,就是指异步请求

//第三个参数是指是否异步,默认是true,同步模式下send需要等待至数据接收完成,同步模式下onreadystatechange必须在send之前设置,否则不会被触发
xhr.open('GET', 'http://mywebsite.com/ajax?id=1&name=xiaoming', true);

//xhr.responseType="json";      //此时responseText和responseXML都为空,response得到的是JSON对象

xhr.onreadystatechange = function() {
    if (this.readyState == 2){
        console.log(this.getResponseHeader('Content-Type'));
        console.log(this.getAllResponseHeader());
    }
    if (this.readyState == 4 && this.status == 200 || this.status == 304) {    //readyState == 4说明请求已完成
        callback(this.responseText);     //responseText是响应体的内容,不包括头信息

        //获取响应的三种方式
        console.log(this.response);      //响应体的类型随xhr.responseType变化。比如xhr.responseType=json则是JSON对象
        console.log(this.responseText);  //文本形式的响应体
        console.log(this.responseXML);   //Response的Content-Type是 application/xml 时才有值,一般为空
    }
};

xhr.send(null);

POST

function callback(data) { console.log(data); }

var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://mywebsite.com/ajax', true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");  //因为提交的是 键=值&键=值 的格式,所以Content-Type要设置为urlencoded
xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200 || this.status == 304) { 
        callback(this.responseText);  
    }
};

xhr.send('id=1&name=xiaoming');

readyState: - 0: 请求未初始化(XHR被创建,但尚未调用open方法) - 1: 服务器连接已建立(open方法已经被调用) - 2: 请求已接收(send方法已经被调用,并且头部和状态已经可获得,但响应体还未拉取) - 3: 请求处理中(下载中,responseText属性已经包含部分数据,但不完整) - 4: 请求已完成,且响应已就绪(下载操作已完成)

通过JQuery使用AJAX

$.ajax({
    url: 'http://mywebsite.com/ajax',
    type: 'GET',
    data: 'id=1&name=xiaoming',
    dataType: 'json',   //dataType是指response返回的数据类型,和data无关
    beforeSend: function(xhr){ console.log(xhr); },
    success: function(res){ console.log(res); },     //成功
    error: function(xhr){ console.log(xhr); },       //失败
    complete: function(xhr){ console.log(xhr); }    //不管成功失败都会执行
});

//多个AJAX统一设置beforeSend和complete事件
$(document).ajaxStart(function() { console.log("开始请求"); });
$(document).ajaxStop(function() { console.log("请求结束"); });


//通过 url加选择器 指定异步加载的部分页面内容,用的其实也是AJAX
$(function($) {
    $('.btn1').on('click', function(){
        //请求页面并把该页面中指定选择器选中的部分添加到当前页面下id为div1的标签下
        $('#div1').load('http://mywebsite.com/index #div1 > *');
        return false;
    });
});

存储

cookie是会话级别的,它存储在浏览器内存中,当浏览器退出后会被删除;如果设置了过期时间expires,则会存储在磁盘上,过期时间到了再删除

document.cookie设置值不会覆盖,而是添加,如果已经有同名cookie,则是修改。expirespath等参数之间用分号分隔加在后面即可

cookie中的expirespath等参数通过document.cookie获取时不会显示,document.cookie获取的只有key-value对,中间用分号分隔

cookie遵循同源策略,像a.mywebsite.comb.mywebsite.com这种基础域名相同的情况,要共用cookie,可以设置window.domain=mywebsite.com;而相同域名但不同子路径下的页面要共享cookie,需设置path=/

function setCookie(cname, cvalue, exptime, path) {    //exptime单位为秒
    var cookiestr = cname + "=" + escape(cvalue);
    if(exptime) {
        var d = new Date();
        d.setTime(d.getTime() + (exptime*1000));
        cookiestr += "; expires=" + d.toGMTString();
    }
    if(path) {
        cookiestr += "; path=" + path;
    }
    document.cookie = cookiestr;
}

function getCookie(cname) {
    var name = cname + "=";
    var carray = document.cookie.split(';');
    for(var i = 0; i < carray.length; i++) {
        var c = carray[i].trim();
        if(c.indexOf(name)==0) {
           return unescape(c.substring(name.length, c.length));
        }
    }
    return "";
}

function delCookie(cname) {
    setCookie(cname, "", -1);
}

sessionStorage

sessionStorage的存储大小限制为5MB(不同的浏览器的限制不一样,大多数浏览器限制为5MB),且只能存储字符串类型的数据(非String类型会自动转成String)

sessionStorage生命周期为当前标签页,关闭当前标签页后sessionStorage会被清除,主要应用于SPA(单页应用程序)

sessionStorage遵循同源策略,页面中的同源iframe可以共享sessionStorage

//三种写入方式是等价的
sessionStorage.setItem("name", "小明");
sessionStorage["name"] = "小明";
sessionStorage.name = "小明";

//同样也有三种获取方式
sessionStorage.getItem("name");
sessionStorage["name"];
sessionStorage.name;

sessionStorage.key(0);      //获取index对应的key名称

sessionStorage.removeItem("name");
sessionStorage.clear();     //清除所有sessionStorage

//sessionStorage只能存储字符串类型,存入json对象时要先转成字符串,取出来再解析成json对象
var obj = {'name': '小明', 'age': 14};
sessionStorage.setItem('user', JSON.stringify(obj));
JSON.parse(sessionStorage.getItem('user'));

localStorage

localStorage的存储大小限制为5MB(不同的浏览器的限制不一样,大多数浏览器限制为5MB),且只能存储字符串类型的数据

localStorage不会自动删除,重启浏览器还会存在(但是在浏览器的隐私模式下面不可读取的),必须手动调用删除方法才能删除

不同浏览器无法共享localStorage,且localStorage遵循同源策略

localStoragesessionStorage的用法是几乎相同的,它们唯一的区别只是生命周期的不同

localStorage.setItem("name", "小明");

localStorage.getItem("name");
localStorage.key(0);

localStorage.removeItem("name");
localStorage.clear();

应用程序缓存

html标签中加入manifest属性

<html manifest="demo.appcache">

写缓存清单文件(使用同一清单文件的不同页面用的是相同的缓存)

# 声明这是一个缓存文件
CACHE MANIFEST

# 缓存文件列表
CACHE:
./js1.js
./js2.js
./pic1.jpg
./pic2.jpg

# 必须实时获取的文件列表
NETWORK:


# 如果文件请求失败,使用的替代方案
FALLBACK:
./pic2.jpg ./error.jpg
*.jpg ./error.jpg

打开浏览器,使用调试工具的Network,选择offline,进行测试在离线状态是否能正常访问

移动端适配

媒体查询

指定在设备的不同屏幕类型使用的样式,格式为@media 设备类型 and|not|only (条件) { css样式 }@media需配合viewport使用才会生效;查询条件都满足时,按就近原则生效

常用的媒体类型有: - all: 所有设备 - print: 打印机和打印预览 - screen: 电脑屏幕,平板电脑,智能手机等(最常用) - speech: 屏幕阅读器等发声设备

常用的查询条件有(有max同理也有min): - max-device-height: 屏幕可见的最大高度 - max-device-width: 屏幕最大可见宽度 - max-height: 页面最大可见区域高度 - max-width: 页面最大可见区域宽度 - max-device-aspect-ratio: 屏幕可见宽度与高度的最大比率 - max-aspect-ratio: 屏幕可见宽度与高度的最大比率 - max-resolution: 设备的最大分辨率 - -webkit-min-device-pixel-ratio: 最小像素比

比如

@media screen and (max-width: 500px) {
    body {
        background-color:lightblue;
    }
}

@media (max-width: 500px) {     /* 默认就是搜索screen,可以省略 */
    body {
        background-color:lightblue;
    }
}

使用媒体查询实现自适应图片:

<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
<style>
    /* 超小屏设备(小于576) */
    @media screen and (max-width: 576px) {
        .hidden-xs { display: none !important; }
        .visible-xs-inline-block { display: inline-block !important; }
    }

    /* 小屏设备(576~768) */
    @media screen and (min-width: 576px) and (max-width: 768px) {
        .hidden-sm { display: none !important; }
        .visible-sm-inline-block { display: inline-block !important; }
    }

    /* 中等屏设备(768~992) */
    @media screen and (min-width: 768px) and (max-width: 992px) {
        .hidden-md { display: none !important; }
        .visible-md-inline-block { display: inline-block !important; }
    }

    /* 大屏设备(992~1200) */
    @media screen and (min-width: 992px) and (max-width: 1200px) {
        .hidden-lg { display: none !important; }
        .visible-lg-inline-block { display: inline-block !important; }
    }

    /* 超大屏设备(大于1200) */
    @media screen and (min-width: 1200px) {
        .hidden-xl { display: none !important; }
        .visible-xl-inline-block { display: inline-block !important; }
    }

    .respondimg, .respondimg > div {
        width: 100%;
    }
    /* PC端高度固定 */
    .respondimg > div > .pc-box {
        display: block;
        height: 200px;
        width: 100%;
        background-size: cover;
        background-position: center;
        background-repeat: no-repeat;
    }
    /* 移动端宽度自适应,高度自动变化(不能直接用a的background,因为a必须指定高度才能显示) */
    .respondimg > div > .moblie-box {
        display: block;
        width: 100%;
    }
    .respondimg > div > .moblie-box > img {
        display: block;
        width: 100%;
    }
</style>

<div class="respondimg">
    <div>
        <!-- PC使用的图片 -->
        <a href="javascript:;" class="pc-box hidden-xs hidden-sm" style="background-image: url(./pic1-pc.jpg);" />
        <!-- 移动端使用的图片 -->
        <a href="javascript:;" class="moblie-box hidden-md hidden-lg hidden-xl">
            <img src="./pic1-moblie.jpg" />
        </a>
    </div>

    <div>
        <a href="javascript:;" class="pc-box hidden-xs hidden-sm" style="background-image: url(./pic2-pc.jpg);" />
        <a href="javascript:;" class="moblie-box hidden-md hidden-lg hidden-xl">
            <img src="./pic2-moblie.jpg" />
        </a>
    </div>
</div>

以上的方式无论电脑还是手机都会加载两套图片,优化的方法是通过JavaScript根据window.screen.width来动态地生成节点(同时还可以监听windowresize事件,方便调试,但注意要缓存,否则在调试时每调整一点页面大小都会重新加载一次图片,会很卡)

缩放

页面缩放

通过viewport设置缩放属性

可以设置的属性:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
        <title>viewport</title>
    </head>
    <body>

    </body>
</html>

元素缩放

元素的缩放策略有:百分比、伸缩布局、媒体查询,但这三种方案只能实现宽度缩放,高度仍然是固定大小(img标签除外,img支持等比缩放)

我们也可以使用JavaScript来给每个元素乘以一个缩放比,但是那样很麻烦,于是我们可以使用rem单位,它可以根据根节点(一般是html节点)的font-size属性按比例缩放,如果所有元素都用rem单位,那么我们只需更改根元素的font-size属性,就可以更改所有元素的大小(一般是设计的时候按一个固定的屏幕宽度设计,然后通过JavaScript动态获取屏幕大小,和原来设计的屏幕大小相除,得到一个比例,然后根节点的font-size等于原来的font-size乘以该比例(影响性能),或者结合less生成一系列的@media(简单,但精确度较低))

<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <title>css</title>
    <style type="text/css">
        html {
            font-size: 50px;
        }
        .div1 {
            width: 1rem;
            height: 1rem;
            background-color: pink;
        }
        .div2 {
            margin-top: 20px;
            width: 1.5rem;
            height: 1.5rem;
            background-color: hotpink;
        }
    </style>
</head>
<body>
    <div class="div1"></div>    <!-- 50px x 50px -->
    <div class="div2"></div>    <!-- 75px x 75px -->
</body>
</html>

less

less是动态生成css的工具。less兼容css的语法,同时也有自己的语法:

@charset "UTF-8";    //在文件开头指定字符集

/* css注释,编译后会生成到css中 */
// less注释,编译后不会生成到css中

@a:blue;   //变量
@arr:1px, 2px, 3px;   //数组

//css,可以使用定义的变量
.div1 {
    background-color: @a;
}

//变量和其他字符串拼接时,需使用@{xxx}
@className:.div2;
.@{className} {
    backgroun-color: blue;
}

//声明一个函数,函数不会生成到css中
.func(@c) {    //每调用一次函数,就生成一次里面的css
    .div2 {
        background-color: @c;
    }
}
.func(@a);  //调用函数

//css可以像函数那样调用
.w50 { width: 50%; }
.f(@direction) { float: @direction; }
.w50-f_left {
    .w50();
    .f(right);
}

在less中写css

.div1 {
    span {
        display: block;
    }
    p {
        font-size: 14px;
        a {
            color: red;
        }
    }
    &:hover {   //&表示当前标签
        background-color: lightblue;
    }
    > div {
        font-size: 14px;
    }
}

生成的结果

.div1 span {
    display: block;
}
.div1 p {
    font-size: 14px;
}
.div1 p a {
    color: red;
}
.div1:hover {
    background-color: lightblue;
}
.div1 > div {
    font-size: 14px;
}

应用

使用less生成不同屏幕尺寸下的font-size

@adapterList:750px, 720px, 640px, 540px, 480px, 424px, 414px, 400px, 384px, 375px, 360px, 320px; // 定义数组,数组下标从1开始
@baseWidth:750px;
@baseFontSize:100px;
@len:length(@adapterList); //通过len函数获取数组长度

// less没有循环,只能通过递归模拟循环
.adapterFunc(@index) when(@index > 0) {      /* 带括号的是函数,没有括号的是css样式。函数可以使用when指定什么条件下调用 */
    @media (min-width: extract(@adapterList, @index)) { //通过extract函数获取序号对应数组中的值
        html {
            font-size: extract(@adapterList, @index) / @baseWidth * @baseFontSize;
        }
    }
    .adapterFunc(@index - 1);
}

.adapterFunc(@len);     //调用函数

此时页面可以使用rem做单位

.div1 {
    width: 100rem / @baseFontSize;  /* 写的还是设计稿中的px的数值,然后除以baseFontSize换算成rem */
    height: 100rem / @baseFontSize;
}

使用less解析(导入到主less文件中,然后编译该文件):

import "func.less";
import "styles.less";

命令行执行

lessc main.less main.css

删除链接点击高亮

设置a标签点击后高亮为透明(即删除点击高亮效果)使用-webkit-tap-highlight-color,点击高亮效果只有手机浏览器才有,电脑上看不到

为了兼容不同浏览器,一般会把部分属性加上-webkit-(safari、chrome)、-moz-(firefox)、-ms-(IE)、-o-(opera)等前缀,而手机以-webkit-为主

<style>
    a {
        display: block;
        background-color: rgb(234, 234, 234);
        width: 500px;
        height: 500px;
        tap-highlight-color: transparent;
        -webkit-tap-highlight-color: transparent;
    }
</style>
<a href="javascript:void(0)"></a>

删除输入框样式

不同手机浏览器有自己的输入框样式,为了统一,需要删掉

<style>
    input, textarea {
        -webkit-appearance: none;
    }
</style>
<input type="text" />

触摸事件

window.onload = function() {
    //它们的事件不是同一个类型
    div1.addEventListener('touchstart', function(e) {
        console.log(e);
        console.log(e.touches[0].clientX, e.touches[0].clientY);    //浏览器窗口坐标
        console.log(e.touches[0].pageX, e.touches[0].pageY);        //页面坐标
        console.log(e.touches[0].screenX, e.touches[0].screenY);    //屏幕坐标
    });
    div1.addEventListener('touchmove', function(e) { console.log(e); });
    div1.addEventListener('touchend', function(e) { console.log(e); });
}

用触摸事件实现轮播图:

<style>
    * {
        padding: 0;
        margin: 0;
        box-sizing: border-box;
    }
    .div1 {
        content: "";
        width: 50px;
        height: 50px;
        border: 2px solid red;
        top: 0;
        left: 50%;
        transform: translate(-50%);
        position: absolute;
    }
    .div2 {
        /* 为实现无缝滑动,轮播图的第一张和最后一张需重复,比如:图片3、图片1、图片2、图片3、图片1 */
        /* 图片[0]其实是图片[3]的重复,所以实际上从图片[1]开始,所以要margin-left一张图片的距离 */
        margin-left: -50px;
        width: 250px;
        height: 50px;
        background-color: gray;
    }
</style>
<script>
window.onload = function() {
    var div1 = document.querySelector(".div1");
    var div2 = document.querySelector(".div2");

    var width = div1.offsetWidth;

    var index = 0;
    function startTimmer() {
        var timer = setInterval(function() {
            index++;
            //过渡,实现自动滑动效果
            div2.style.transition = 'all 0.2s';
            div2.style.webkitTransition = 'all 0.2s';
            //位移
            div2.style.transform = 'translateX(' + (-index*width) + 'px)';
            div2.style.webkitTransform = 'translateX(' + (-index*width) + 'px)';
        }, 2000);
        return timer;
    }
    var timer = startTimmer();

    
    div2.addEventListener('transitionend', function() {
        //自动滑动时,到图片[3]时立即切换回图片[0],实现无缝滚动
        if(index >= 3){
            index = 0;
        }
        //用户反向滑动时,到图片[3]时立即切换回图片[0],实现无缝滚动
        else if(index <= 0) {
            index = 3;
        }
        //清过渡,实现无缝轮播
        div2.style.transition = 'none';
        div2.style.webkitTransition = 'none';
        //位移
        div2.style.transform = 'translateX(' + (-index*width) + 'px)';
        div2.style.webkitTransform = 'translateX(' + (-index*width) + 'px)';

        console.log('当前是第' + (index+1) + '张图片');
    });

    //图片跟随用户滑动而改变位置
    var startX = 0;
    var distanceX = 0;
    div2.addEventListener('touchstart', function(e) {
        //清除定时器
        clearInterval(timer);

        startX = e.touches[0].clientX;
    });
    div2.addEventListener('touchmove', function(e) {
        var moveX = e.touches[0].clientX;
        distanceX = moveX - startX;

        //-index*width就是当前的位置,加上distanceX可以获取要移动到的位置
        var translateX = -index*width + distanceX;

        //清过渡
        div2.style.transition = 'none';
        div2.style.webkitTransition = 'none';
        //位移
        div2.style.transform = 'translateX(' + translateX + 'px)';
        div2.style.webkitTransform = 'translateX(' + translateX + 'px)';
    });
    div2.addEventListener('touchend', function(e) {
        //如果滑动距离小于父元素的1/3,则回弹,否则切换到上一张或下一张图片(不管滑动多远,一次只能切换一张)
        if(Math.abs(distanceX) < width/3 ) {
            //回弹就是index不变,所以什么都不做
        } else {
            if(distanceX > 0) { //右滑,上一张
                index--;
            } else {            //左滑,下一张
                index++;
            }
        }
        div2.style.transition = 'all 0.2s';
        div2.style.webkitTransition = 'all 0.2s';
        div2.style.transform = 'translateX(' + (-index*width) + 'px)';
        div2.style.webkitTransform = 'translateX(' + (-index*width) + 'px)';

        //重置
        startX = 0;
        distanceX = 0;
        clearInterval(timer);
        //开定时
        timer = startTimmer();
    });
}
</script>

<div class="div1">
    <div class="div2">
        <!-- 轮播图的第一张和最后一张需重复,比如
            图片3、图片1、图片2、图片3、图片1
         -->
    </div>
</div>

其他小技巧

2夹1布局

<div><图片> <搜索框> <图片></div>这种形式的布局,一般是给父元素div设置padding-leftpadding-right,然后搜索框设置width: 100%,这样无论屏幕宽度如何,搜索框都能占满

<div style="position:relative;padding-left: 55px;padding-right: 55px;">
    <img style="width: 50px;height: 50px;position: absolute;top: 0;left: 0;background: pink;" />
    <input style="width: 100%;height: 40px;border: 0;border-bottom: 1px solid hotpink;outline: 0;" placeholder="请输入" />
    <img style="width: 50px;height: 50px;position: absolute;top: 0;right: 0;background: pink;" />
</div>

加速click事件

移动设备上的点击事件(click)由于不知道是滑动还是点击,所以会延迟300ms,如果300ms内没有移动,则认为是点击

这就导致了如果用户点击后立即松开,仍然需要延迟300ms才能完成点击事件(只能在真机上测试,电脑模拟手机浏览器无法看到效果),这时可以通过touch事件在按下和松开时判断,如果时间相隔小于一定时间,则去响应事件,以提高点击的响应时间(此时click和我们自己实现的tap事件只能选一个绑定,否则会导致代码执行两次)

var bindTap = function(element, callback) {
    var startTime;
    var isMoved = false;
    element.addEventListener('touchstart', function(e) {
        startTime = Date.now();
    });
    element.addEventListener('touchmove', function(e) {
        isMoved = true;
    });
    element.addEventListener('touchend', function(e) {
        if( (Date.now()-startTime) < 150 && !isMoved ) {
            callback && callback(e);     //如果callback不是undefined,则调用
        }
        startTime = 0;
        isMoved = false;
    });
}

//触摸时间0~150ms执行我们自己实现的tap事件
bindTap(document.querySelector("#div1"), function(e){ console.log("tap事件"); });

//触摸时间0~300ms执行click事件(0~150ms内tap和click都会触发,可能会导致代码重复执行,所以最好只绑定其中一个)
document.querySelector("#div1").addEventListener('click', function(e) { console.log("click事件"); });

多列布局

多列布局可以用百分百或伸缩布局实现

一般两列布局还可以使用左边float,右边通过触发BFC的方式占据剩余空间实现

两列布局且左右分开滚动案例:

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
   <title>js</title>
<style>
   * {
       margin: 0;
       padding: 0;
   }
   html, body, .muticol {    /* 占满屏幕 */
       width:100%;
       height:100%;
   }
   .muticol {      /* 如果上面还有内容,可以通过padding-top把内容向下移(不要设置margin,否则会出现滚动条) */
       overflow: hidden;   /* 不允许整个页面滚动,左边和右边要分开滚动(需要通过js的touch事件实现) */
   }
   .muticol > div:nth-child(1) {
       width: 90px;
       height: 100%;
       float: left;    /* 左边的div浮动 */
   }
   .muticol > div:nth-child(1) ul li{
       width: 100%;
       height: 50px;
       line-height: 50px;
       border: 1px solid gray;
   }

   .muticol > div:nth-child(2) {
       padding: 10px;
       overflow: hidden;   /* 右边的div触发BFC,占据剩余空间 */
   }
   .muticol > div:nth-child(2) div {
       width: 33.333%;
       height: 110px;
       float: left;
       box-sizing: border-box;
       padding: 10px;
   }
   .muticol > div:nth-child(2) div img {
       width: 70px;
       height: 70px;
       display: block;
       margin: 0 auto;
       background-color: pink;
   }
   .muticol > div:nth-child(2) div p {
       font-size: 14px;
       height: 20px;
       margin: 0 auto;
       text-align: center;
       line-height: 20px;
       word-break: break-all;
       overflow: hidden;
       text-overflow: ellipsis;
   }
</style>
<script>
   window.onload = function() {

       // 要求传入的元素必须设置height,否则需传入可视高度visibleHeight
       var bindMoveY = function (element, visibleHeight) {
           // 当滑动超过指定范围,需要回弹
           var contentHeight = element.scrollHeight;   // 元素实际内容高度
           var pageHeight = element.offsetHeight;      // 可视区域高度(需设置height,否则还是内容高度)
           if(visibleHeight){
               pageHeight = visibleHeight;
           }

           var startY = 0;     // 起始位置,用于计算当前滑动距离
           var distanceY = 0;  // 当前这一次滑动的滑动距离
           var offsetY = 0;    // 累计的滑动距离(translateY是按初始位置计算的,所以累计距离需要我们自己记录)

           element.addEventListener('touchstart', function(e) {
               startY = e.touches[0].pageY;
           });
           element.addEventListener('touchmove', function(e) {
               distanceY = e.touches[0].pageY - startY;    // 滑动距离
               var targetDistance = offsetY + distanceY;   // 需要translateY的距离=累计距离+当前距离

               // 跟随手势滑动
               element.style.transition = 'none';
               element.style.webkitTransition = 'none';
               element.style.transform = 'translateY(' + targetDistance + 'px)';
               element.style.webkitTransform = 'translateY(' + targetDistance + 'px)';
           });
           element.addEventListener('touchend', function(e) {
               startY = 0;
               offsetY = offsetY + distanceY;  // 记录累计距离

               // 回弹到元素顶部
               if(offsetY > 0){
                   offsetY = 0;

                   element.style.transition = 'all 0.2s';
                   element.style.webkitTransition = 'all 0.2s';
                   element.style.transform = 'translateY(' + offsetY + 'px)';
                   element.style.webkitTransform = 'translateY(' + offsetY + 'px)';
               }
               // 回弹到元素底部
               else if((Math.abs(offsetY) + pageHeight) > contentHeight){
                   offsetY = -(contentHeight - pageHeight);

                   element.style.transition = 'all 0.2s';
                   element.style.webkitTransition = 'all 0.2s';
                   element.style.transform = 'translateY(' + offsetY + 'px)';
                   element.style.webkitTransform = 'translateY(' + offsetY + 'px)';
               }
           });
       }

       var leftdiv = document.querySelector('.muticol > div:nth-child(1)');
       bindMoveY(leftdiv);
       var rightdiv = document.querySelector('.muticol > div:nth-child(2)');
       // 右边没有设置height属性,所以需要传入可视高度
       bindMoveY(rightdiv, rightdiv.parentNode.offsetHeight);
   }
</script>
</head>
<body>
<div class="muticol" style="">
   <div>
       <ul>
           <li>类目1</li>
           <li>类目2</li>
           <li>类目3</li>
           <li>类目4</li>
           <li>类目5</li>
           <li>类目6</li>
           <li>类目7</li>
           <li>类目8</li>
           <li>类目9</li>
           <li>类目10</li>
           <li>类目11</li>
           <li>类目12</li>
           <li>类目13</li>
           <li>类目14</li>
           <li>类目15</li>
           <li>类目16</li>
           <li>类目17</li>
           <li>类目18</li>
           <li>类目19</li>
       </ul>
   </div>
   <div>
       <div><img /><p>商品介绍xxxxxxxxx</p></div>
       <div><img /><p>商品介绍xx</p></div>
       <div><img /><p>商品介绍xxxxx</p></div>
       <div><img /><p>商品介绍x</p></div>
       <div><img /><p>商品介绍xxxxxxxxxxxxxx</p></div>
       <div><img /><p>商品介绍</p></div>
       <div><img /><p>商品介绍xxxxxx</p></div>
       <div><img /><p>商品介绍xxxxxxx</p></div>
       <div><img /><p>商品介绍xx</p></div>
       <div><img /><p>商品介绍</p></div>
       <div><img /><p>商品介绍xxxx</p></div>
       <div><img /><p>商品介绍xxxxxxxxx</p></div>
       <div><img /><p>商品介绍xxxxxxxxx</p></div>
       <div><img /><p>商品介绍xx</p></div>
       <div><img /><p>商品介绍xxxxx</p></div>
       <div><img /><p>商品介绍x</p></div>
       <div><img /><p>商品介绍xxxxxxxxxxxxxx</p></div>
       <div><img /><p>商品介绍</p></div>
       <div><img /><p>商品介绍xxxxxx</p></div>
       <div><img /><p>商品介绍xxxxxxx</p></div>
       <div><img /><p>商品介绍xx</p></div>
       <div><img /><p>商品介绍</p></div>
       <div><img /><p>商品介绍xxxx</p></div>
       <div><img /><p>商品介绍xxxxxxxxx</p></div>
       <div><img /><p>商品介绍xxxxxxxxx</p></div>
       <div><img /><p>商品介绍xx</p></div>
       <div><img /><p>商品介绍xxxxx</p></div>
       <div><img /><p>商品介绍x</p></div>
       <div><img /><p>商品介绍xxxxxxxxxxxxxx</p></div>
       <div><img /><p>商品介绍</p></div>
       <div><img /><p>商品介绍xxxxxx</p></div>
       <div><img /><p>商品介绍xxxxxxx</p></div>
       <div><img /><p>商品介绍xx</p></div>
       <div><img /><p>商品介绍</p></div>
       <div><img /><p>商品介绍xxxx</p></div>
       <div><img /><p>商品介绍xxxxxxxxx</p></div>
   </div>
</div>

</body>
</html>

base.css

一般会把常用的css属性写在一个css文件中,文件一般包括如下内容

*,
*::before,
*::after {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
    -webkit-box-sizing: border-box;
    tap-highlight-color: transparent;
    -webkit-tap-highlight-color: transparent;
}

body {
    font-size: 14px;
    font-family: 'Microsoft YaHei','sans-serif';
    color: #333;
}

ul, ol {
    list-style: none;
}

a {
    text-decoration: none;
    color: #333;
}

input, textarea {
    border: none;
    outline: none;
    resize: none;
    -webkit-appearance: none;
}

input, img{
    border: 0 none;
    outline-style: none;
    vertical-align: bottom; 
}

table{
    border-collapse: collapse;  /*边框合并*/
    border-spacing: 0;
}

label{
    display: inline-block;
    cursor: pointer;
}

.clearfix::before,
.clearfix::after {
    content: '';
    display: block;
    visibility: hidden;
    height: 0;
    line-height: 0;
    clear: both;
}
.clearfix {
    *zoom: 1;
}

Angular

新建项目

npm install -g @angular/cli

ng new myproject --skip-install
cd myproject
npm -i # 或 cnpm install

# 在components/user中新建组件
ng g component components/user

# 启动
ng serve --open

数据绑定

属性获取:{{js中的变量名}}

DOM获取:在DOM标签中加入#id,然后在ts中使用@ViewChild("id") 变量名获取,其类型为ElementRef,可以通过其nativeElement转成原生DOM对象(如果在一个组件中引入了另一组件,也可以通过这种方式获取,此时获取的类型就是被引入的组件的类型)

属性绑定:[html属性名]="js中的变量名",可以使用表达式(比如条件判断==、>、<,算术运算等)

事件绑定:(事件名)="js中的函数名()",其中括号内还可以使用$event把事件对象传入

双向数据绑定(需在app.module.ts中导入FormModule模块,只能用于表单,表单内容的变化同步更新到属性,属性的变化也会同步到表单):[(ngModel)]="js中的变量名"

class绑定:[ngClass]="{'class1':true,'class2':false}",根据条件动态更改class属性

样式绑定:[ngStyle]="{'color': js中的变量名}",使用字面量需用引号包裹,否则会认为是js中的变量

管道:{{js中的变量名 | 转换函数}},比如:{{ info | json}}把info转成json对象、{{ info.name | uppercase}}转成大写、{{ today | date:'yyyy-MM-dd HH:mm:ss' }}日期格式化

循环:*ngFor="let item of items",使用*ngFor的标签(连同其子标签)会被按循环次数重复生成,其标签内部可以通过{{item}}获取数组内容;同时获取下标的写法*ngFor="let item of items";let key=index;,通过{{key}}获取下标

if:*ngIf="flag",其中flag为boolean类型,如果为false,则当前标签不会被生成;这里没有else语句,只能使用*ngIf="!flag"代替

switch:[ngSwitch]="js中的变量名",其标签内部使用*ngSwitchCase="选项"

components/user/user.component.html

<!-- 属性获取、DOM获取、属性绑定、事件绑定、双向数据绑定 -->
{{name}} <input #nameinput type="text" [placeholder]="hint" (keyup)="check($event)" [(ngModel)]="info.name" />

<!-- radio、select、checkbox的双向数据绑定写法 -->
性别:
<input type="radio" value="1" name="sex" id="sex1" [(ngModel)]="info.sex" /><label for="sex1"></label>
<input type="radio" value="2" name="sex" id="sex2" [(ngModel)]="info.sex" /><label for="sex2"></label>

城市:
<select name="city" [(ngModel)]="info.city">
    <option [value]="item" *ngFor="let item of info.cityList">{{item}}</option>
</select>

爱好:
<span *ngFor="let item of info.hobby;let key=index;">
    <!-- 动态拼接字符串(id属性) -->
    <input type="checkbox" [(ngModel)]="item.checked" [id]="'cb'+key" /><label [for]="'cb'+key">{{item.title}}</label>&nbsp;&nbsp;
</span>


<!-- 管道 -->
<pre>{{info | json}}</pre>

<!-- 循环、if -->
<ul>
    <li *ngFor="let item of info.cityList;let key=index;">
        <span *ngIf="key==1" style="color=red"> {{key}}----{{item}} </span>
        <span *ngIf="key!=1"> {{key}}----{{item}} </span>
    </li>
</ul>

<!-- switch -->
<span [ngSwitch]="info.sex">
    <p *ngSwitchCase="1"></p>
    <p *ngSwitchCase="2"></p>
</span>

在_app.module.ts_中导入FormModule模块:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { UserComponent } from './components/user.component';

@NgModule({
  declarations: [
    AppComponent,
    UserComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})

export class AppModule { }

components/user/user.component.ts

import { Component, ViewChild, ElementRef, Input, OnChanges, OnInit, DoCheck, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-user',     //组件名称
  templateUrl: './user.component.html', //页面位置,也可以使用template然后直接使用字符串模板在这里写html
  styleUrls: ['./user.component.scss']  //样式位置
})

export class UserComponent implements OnChanges, OnInit, DoCheck, OnDestroy {
    @ViewChild("nameinput") nameElement:ElementRef;

    public name:string = '姓名:';

    public hint:string = '请输入...';

    public info:any = {
        name: '',
        sex: 1,
        cityList: ['北京', '上海', '广州'],
        city: '北京',
        hobby: [{
            title: '吃饭',
            checked: false
        },{
            title: '睡觉',
            checked: false
        },{
            title: '打豆豆',
            checked: false
        }],
    };

    constructor(){}     //构造器

    check(event: KeyboardEvent) { console.log(info.name); }


    //生命周期方法(按调用顺序排列)
    //使用生命周期方法不一定要声明接口,因为接口在转成JavaScript后就不见了,只是TypeScript的语法糖,Angular会自动检测含有生命周期方法的组件,并调用
    //带check的是组件状态发生改变时调用,带init的只会调用一次
    ngOnChanges(){}         //父子组件传值时调用,首次调用一定会发生在ngOnInit之前
    ngOnInit(){}            //含有指令的DOM元素加载完成,只调用一次
    ngDoCheck(){}           //在每个Angular变更检测周期中调用
    ngAfterContentInit(){}  //当把内容投影进组件之后调用,只调用一次,到这里可以使用@ContentChild、@ContentChildren
    ngAfterContentChecked(){} //每次完成被投影组件内容的变更检测之后调用
    ngAfterViewInit(){}     //DOM加载完成,相当于window.onload,只调用一次,到这里可以使用@ViewChild、@ViewChildren
    ngAfterViewChecked(){}  //每次做完组件视图和子视图的变更检测之后调用
    ngOnDestroy(){}         //每次销毁 指令/组件 之前调用

}

最后在_app.component.html_中使用该模块:

<app-user></app-user>

服务

不同组件之间不能相互调用对方的方法(除非是父子组件关系),要定义公共方法,需使用服务

服务之间可以相互调用,但服务不能调用组件

创建服务

ng g service services/common

在_app.module.ts_中声明服务

import { CommonService } from './services/common.service';

@NgModule({
  declarations: [
    AppComponent,
    UserComponent
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [
    CommonService
  ],
  bootstrap: [AppComponent]
})

services/common.service.ts

import { Injectable } from '@angular/core';

@Injectable()

export class CommonService {
    constructor(){}

    //写公有方法
    testService(){ console.log("CommonService"); }
}

在组件中导入服务,然后就可以使用了

import { CommonService } from '../../services/common.service';

export class UserComponent implements OnInit {


    constructor(public commonService:CommonService){
        this.commonService.testService();
    }
    ngOnInit(){}
}

父子组件传值

组件关系:

如果在_home.component.html_中引入了header和footer两个组件,那么home组件和header、footer的关系就是父子组件

<app-header></app-header>
<app-footer></app-footer>

父组件给子组件传值:子组件标签中加入[子组件属性名]="父组件属性/方法名",然后子组件中使用@Input修饰器获取,如果把整个父组件传给子组件,可以使用[子组件属性名]="this"

父组件获取子组件:子组件标签中加入#id,父组件typescript中使用@ViewChild("id")修饰器获取

子组件给父组件广播数据:在子组件中创建EventEmitter并用@Output修饰器修饰,并在子组件标签中加入(子组件的emitter)="父组件中的方法",然后在子组件中调用emitter的emit方法广播数据

home.component.html

<app-header #header [title]="title" (emitter)="runParent($event)"></app-header>

home.component.ts

import { Component, ViewChild } from '@angular/core';

export class HomeComponent {
    @ViewChild("header") header:any;

    constructor(){}

    runParent(e) { console.log(e); }    //e就是传过来的字符串
}

header.component.ts

import { Component, Input, Output, EventEmitter } from '@angular/core';   //引入相关模块

export class HeaderComponent {
    @Input() title:any;

    @Ouput() private emitter = new EventEmitter<String>();

    constructor(){}

    sendParent() { this.emitter.emit("message from child"); }
}

路由

路由(routing)就是根据不同的url动态地挂载组件,实现单页面应用

使用

在_app.module.ts_中导入模块

import { RouterModule, Routes } from '@angular/router';

在_app.routing.module.ts_中引入自定义的组件,并配置路由

import { HomeComponent } from './components/home.component';
import { UserComponent } from './components/user.component';
import { ProductComponent } from './components/product.component';
import { ProductDetailComponent } from './components/productdetail.component';
import { PageNotFoundComponent } from './components/pagenotfound.component';

//配置路由
const appRoutes: Routes = [
  { path: 'home',           component: HomeComponent },
  { path: 'user',  component: UserComponent },
  {
    path: 'product',
    component: ProductComponent,
    data: { title: 'Product' }
  },
  { path: 'productdetail/:pid/:pname', component: ProductComponent },
  {
    path: '',
    redirectTo: 'home',
    pathMatch: 'full'
  },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true }   //开启调试模式
    )
  ],
})
export class AppModule { }

在_app.component.html_加入router-outlet标签在根组件中启用路由

页面中的url可以使用routerLink动态生成,使用routerLinkActive链接选中样式

<a [routerLink]="['/home']" routerLinkActive="active" >首页</a>     <!-- 动态生成,点击后加active样式(需在css中配置) -->
<a routerLink="/user" routerLinkActive="active" >用户</a>           <!-- 静态配置 -->
<a [routerLink]="['/product']" routerLinkActive="active" >产品</a>

<router-outlet></router-outlet>

传值

给跳转的组件传值

<!-- <a [routerLink]="['/productdetail/']" [queryParams]="{pid: 1, pname: "aaa"}">产品</a>    get传值 -->
<a [routerLink]="['/productdetail/', pid, pname]">产品</a>    <!-- 动态路由传值,需在路由配置中加:pid和:pname -->

获取参数

import { Component, OnInit } from '@angular/core';
import { ActiveRoute } from '@angular/router';  //导入模块

@Component({
  selector: 'app-productdetail',
  templateUrl: './productdetail.component.html',
  styleUrls: ['./productdetail.component.scss']
})

export class ProductDetailComponent implements OnInit {

    constructor(public route:ActiveRoute){}  //依赖注入

    ngOnInit(){
        /*
        //获取queryParams方式传的值
        this.route.queryParams.subscribe((data)=>{
            console.log(data);
        });
        */
        //获取动态路由方式传的值
        this.route.params.subscribe((data)=>{
            console.log(data);
        });
    }
}

ts跳转

使用typescript跳转到其他组件,而不是通过a标签的方式

import { Component, OnInit } from '@angular/core';
import { Router, NavigationExtras } from '@angular/router';   //导入模块

@Component({
  selector: 'app-product',
  templateUrl: './product.component.html',
  styleUrls: ['./product.component.scss']
})

export class ProductComponent implements OnInit {

    constructor(public router:Router){} //依赖注入

    ngOnInit(){}

    //绑定按钮点击事件跳转
    jumpToDetail(){
        /*
        //get传值方式
        let data: NavigationExtras = {
            queryParams: { "pid": "1", "pname": "aaa" }
        };
        this.router.navigate(["/productdetail/"], data);
        */

        //动态路由方式
        this.router.navigate(["/productdetail/", "1", "aaa"]);
    }
}

路由嵌套

比如我们想把product组件放到home组件下

const appRoutes: Routes = [
  { 
    path: 'home',component: HomeComponent,
    children: [
      { path: 'product', component: ProductComponent },
      { path: 'productdetail/:pid/:pname', component: ProductComponent },
      { path: '**', component: ProductComponent }
    ]
  },
  { path: 'user',  component: UserComponent },
  { path: '**', component: PageNotFoundComponent }
];

然后在home组件中添加router-outlet标签启用路由

此时生成路由地址时需写完整路由路径

<a [routerLink]="['/home/product']" >产品</a>

<router-outlet></router-outlet>