移动端响应式开发

  1. 什么是响应式布局开发?

    把我们开发完成的产品,能够让其适配不同的设备屏幕:PC、PAD、PHONE

  2. 现在H5已经成为移动端开发(响应式布局)的标准代名词

  3. 移动端发展史

    智能生活(互联网+)离不开各种APP(应用),这些应用离不开一代又一代的IT工程师

    很久以前,APP开发和H5没什么太大的关系,都是由专业的APP开发团队开发的

    安卓系统:JAVA(JAVA-NATIVE)

    IOS系统:C(object-c/swift)

    我们把使用上述语言开发的APP称之为native-app(原生APP)

    1. 本地开发,打包成安装包,上传至应用商店(APP Store是有审核期的【七天】),用户从应用商店下载安装相关的APP;把当前APP的源文件(源代码安装在手机上,类似于在电脑上安装软件)
    2. native-app可以操作手机内部的软件或硬件(例如:通讯录、摄像头、相册、重力感应器等),因为它是直接运行在手机操作系统中的程序
    3. 最一些操作和交互,性能是比较不错的
    4. 手机操作系统不一样,使用的技术也不一样,所以需要两个不同的开发团队,开发不同版本的app(问题:有的版本升级快,有的版本升级慢)
  4. 目前我们开发一款APP需要H5的介入:在native-app中嵌入H5页面(web-app)这种模式称之为Hybrid混合app开发

    • 由H5开发的web-app的特点
      • 其实就是HTML页面,需要基于浏览器运行(PC或者移动端浏览器在或者V8内核的工具也可以)
      • H5能否操作手机内部的软件或者硬件,取决于浏览器是否支持,如果浏览器支持,H5页面可以调取使用,反之不可以。例如:H5中想要调取摄像头,UC浏览器支持的话,是先把调取摄像头的功能实现了,然后放在window全局对象中,供H5中的JS调取使用
      • 相比于native来说,性能不好
      • 所有的请求访问都必须基于联网状态(除了有些nativea-app把H5特殊处理了),虽然H5中支持manifest离线缓存,但是这个技术不是很好
      • 更新比较及时:H5只需要把服务器上的文件更新,用户每次访问看到的都是最新版本,这个比nativea-app好
  5. Hybrid-app:

    • web-view:一款基于V8引擎渲染HTML页面的工具(类似于浏览器)
    • H5页面就是嵌入到web-view中,由其渲染
    • 真实项目中
      • 将H5响应式页面开发完成后部署到服务器上(有一个http/https的访问地址)
      • 此时用户可以手动输入网址访问
      • 我们也可以生成二维码,让用户扫描访问
    • 整个产品的外层框架交给native-app团队开发,他们把壳搭起来,把需要调取手机内部软件或者硬件的功能实现,把一些缓存页面的机制实现…,提供好对应调取的方法
    • 前端把开发生成好的H5地址告诉给native-app,他们会在自己的web-view中通过提供的地址渲染出对应的页面
    • 一个产品:主题内容或者经常更新的内容,都是H5来开发,native-app只开发外壳和调取手机内部的功能
    • H5中如果需要使用手机内部功能,只需要调取自己宿主环境web-view中提供的方法即可
  6. 微信是最为经典的Hybrid混合开发模式,它支持我们在H5页面在微信这个native-app中运行,而且还可以调取微信提供的一些方法实现相关的操作(例如:微信的二次分享)

常见的项目类型

  1. PC端和移动端公用一套项目(用一个网址),我们也要保持良好的展示性,例如,猎豹浏览器、华为官网等简单的展示网站
  2. PC端和移动端用的是不同的项目,例如:京东、淘宝
    • PC端布局固定
    • 移动端需要考虑响应式开发
      • 放在浏览器中运行
      • 放在第三方平台运行
      • 放在自己公司的native-app中运行

手机常用尺寸:

【苹果】:

  • iphone5s及以前:320px
  • iphone4:320*480
  • iphone5:320*568
  • iphone6:375*667
  • iphone6/7/8/plus:414*736

【安卓】

  • 320、360、480、540、640

做H5页面开发之前,需要先从设计师获取ui设计图(PSD格式或者sketch设计稿)

设备像素密度比

响应式布局

只要当前页面需要在移动端访问,一定要加下面这段meta标签,不加的话,我们html页面的宽度都是980px(如果在320px的手机上打开HTML页面,为了保证把页面看全,需要把html缩小大概3倍左右,所有内容都变下了),为了不让页面缩放,我们需要保证手机的宽度和HTML的宽度一直

1
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
  1. REM响应式布局

    • 和px一样,他是一个css样式单位
    • px是一个固定单位,rem是一个相对单位(相对页面的跟元素(HTML)字体大小来设定的单位)
    • 设置为100px,是为了方便后期换算成rem
    1
    2
    3
    html {
    font-size: 100px;
    }
    • 真实项目中如何利用rem的这个特性来实现相应是布局?

      1. 严格按照设计稿中的尺寸进行样式编写(不管是宽高,还是margin,还是字体等),例如:设计稿是750x1336的,设计稿中有一个 300x150的图片,我们的布局的时候依然按照300x150布局…
    1. 我们在编写样式的时候,不要使用px单位,所有的单位都统一换算为rem(此时我们需要让html的font-size:100px

      1. 上述完成后再750px的手机上是没有任何问题的,但是在375px的手机上肯定存在问题了,此时我们需要让页面中的所有样式都整体缩小才能达到响应式适配的目的(此时只需要把HTML的字体大小修改,那么值钱所有以rem为单位的样式会自动重新计算

      rem等比缩放响应式布局:如果单独做移动端项目(项目只在移动端访问,pc端有单独的项目),我们选择响应式适配的最佳方案就是rem布局(当然细节处理可能会用到flex或者@media)

  2. 媒体查询响应式布局

H5中其他新增的内容

H5中其他新增的内容

增加了新的音视频解决方案

音频:audio

视频:video

  1. 传统的是音视频播放时基于flash来完成的,需要浏览器中安装adobe flash player插件
  2. 现在只需要基于audio或者video播放即可,但是对于音视频的格式有限制,对于浏览器也有限制
  3. 移动端对于flash的支持不好,但是基本上都支持audio和video
  4. PC端的IE浏览器(低版本)不支持audio和video,但是支持flash

H5中增加了canvas(绘图)
它是一个画布,允许我们在JS中通过代码绘制图形以及实现一些好玩的动画

百度统计图插件:Echarts就是基于canvas开发的


提供了很多强大的JS API

API: application programming interface(应用编程接口,凡是供别人调取使用的都可以称之为API;例如:从服务器端获取数据,需要一个URL地址,此地址就是一个api,浏览器提供给我们很多常用的方法,每一个方法都可以叫做API)

本地存储

webStorage
localStorage 本地信息存储
sessionStorage 本地会话存储

在没有H5本地存储之前,我们都使用cookie做的本地存储

获取本机地理位置

通过H5可以获取当前用户地理位置,(精度、纬度、精准度…),再结合第三方地图(高德地图、百度地图、腾讯地图…)API接口,实现一些生活服务的推荐等

提供了新的通讯方式-websocket

想要实现实时通讯类产品,基本上现在都是基于socket.io这个框架来完成

提供操作手机硬件功能的API

调取手机的重力感应器,实现摇一摇,获取实现一些小游戏
调取手机的摄像头获取通讯录

不是所有的手机浏览器都支持这些功能,即使支持这些功能的浏览器,在实现效果上也是不理想的(不稳定、卡顿等)

H5离线缓存:manifest

第一次联网请求玩页面,把信息缓存到本地,下一次即使断网情况下,也可以看到上一次的信息

LESS

less作用

css层叠样式表,它是标记语言,不是编程语言;所有的预编译CSS语言(less/sass…)都是赋予了css的面向对象思想

less的编译

LESS叫做预编译CSS:写好的LESS代码浏览器是不能渲染的,需要我们把它编译成为能渲染的CSS才可以

在开发环境下,我们一般都通过导入less插件(less-2.5.3.min.js)来随时编译less

1
2
3
4
5
<!-- rel="stylersheet/less" -->
<link rel="stylersheet/less" href="1.less">
<!-- 导入less -->
<script src="js/less-2.5.3.min.js"></script>
<!-- 由于每一次加载页面,都需要导入less.js,并且把less文件重新编译为css(很耗性能,页面打开速度肯定会变慢),所以在真实项目中,只有开发环境下我们使用这种模式,生产环境下,我们肯定需要事先把写好的less编译为正常的css后,在上线,以后用户访问的都是编译好的css,而不是拿less现编译 -->

生产环境中,我们需要事先把less编译成css:

  1. 使用node编译
    • 下载安装node
    • 在node全局环境下,使用npm包管理器安装一个less模块
    • npm install less -g
    • 在指定的目录中执行less xxx.less yyy.less 把less编译成css,less xxx.less xxx.min.css -x 不仅编译成css,而且还把css压缩
  2. 使用编译工具

less中的基础与发

变量

  1. 设置变量使用@[变量名]: 变量值
    • 传统css支持的值都可以作为变量值
    • 变量名中可以出现- (less中可以支持数学运算,在部分减法运算的时候,我们需要明确的是运算符还是名字中的-@(shadow-px)-10
  2. 不是所有的情况下都要使用变量,只有很多样式都使用了相同的值,而且以后如果改变的话所有元素的样式都跟着改变
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@a: 1;
@b: 30%;
@c: 1000px;
@d: #000;
@shadow-color: red;
@img-url: '../img';
@name: '测试'
@get: 'name';
/* @@get => 测试*/
.box {
opacity: @a;
filter: alpha(opacity=(@a*100));
background: url("@{img-url}/xxx.png");
}

mixin混合应用

在less当中只要设置了一个样式类,我们就可以把它称之为一个方法,其他地方需要用刀这些样式的,直接.[类名]调用即可(原理:把当前样式类中的代码原封不动的copy一份过去)

不加括号,即是普通样式类,也是封装的一个函数,编译的时候,这个样式类中的代码依然跟着编译,加括号仅仅是封装的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.pub {
width: 100px;
height: 100px;
background: green;
}

.box {
.pub;
background: red;
}

/* 编译后 */
.pub {
width: 100px;
height: 100px;
background: green;
}

.box {
width: 100px;
height: 100px;
background: green;
background: red;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.pub() {
width: 100px;
height: 100px;
background: green;
}

.box {
.pub();
background: red;
}

/* 编译后 */

.box {
width: 100px;
height: 100px;
background: green;
background: red;
}

函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.函数名(@参数1:默认值1, @参数2...){
@arguments
}

.transition(@property:all, @duration, @timing:linear, @delay:0s) {
transition: @arguments;
/*transition: @property, @duration, @timining, @delay;*/
}

.box {
.transition(all, 1s, linear, 0s);
.transition(@timing: ease, @duration: 1s);
}

.sum(@n:0, @m:0) {
@result: @n + @m;
}

.box {
.sum(10, 20);
width: unit(@result, px);
/* unit是less提供的方法,unit(value, px)给value值设置单位,但是如果之前已经有单位了,此处是吧原有单位去掉 */
}

命名空间

在less当中每一个大括号都是一个私有作用域,且变量的声明和赋值在作用于中都会提升

1
2
3
4
5
6
7
8
9
10
@a: 10;
.box {
/* @a 20 */
width: unit(@a, px);
@a: 20;
.mark {
/* @a 20 */
width: unit(@a, px);
}
}

extend继承

项目中,如果想要使用extend实现继承,我们一般都把需要继承的样式类写在最外层(而不是里层的私有作用域),如果22想要继承当前私有作用域中的某个样式类,需要把前缀都准备好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
.pub {
width: 100px;
height: 100px;
background: red;
}

.com {
color: #ff6700;
}

/* less中的extend继承并不是copy代码,而是让当前的样式类和集成的样式类共用一套样式(编译为群租选择器的方式) */
.box:extend(.pub) {
background: green;
}

/* 和上面的原理是一样的 */
.box {
&:extend(.pub, .com);
background: green;
}

.box {
.mark {
width: 100px;
height: 100px;
}

.inner {
// 继承要制定好继承的元素
// &:extend(.mark);
/* 继承多个用逗号分隔 */
// &:extend(.box .mark);
.mark;
background: red;
}
}

条件和递归

我们在mixin中设置的条件:常用的条件运算符:>/>=/</<=/=;我们设定的条件还可以使用is函数:iscolor isnumber isstring iskeyword isurl ispixel ispercentage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.pub(@x) when (@x <= 10) {
width: 100px;
height: 200px;
}

.pub(@x) when (@x > 10) {
width: 200px;
height: 400px;
}

.box {
.pub(20);
background: green;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.columns(@i) when (@i <= 4) {
.box@{i} {
width: unit(@i*10, %);
}
.columns(@i + 1);
}

.columns(1);

/* 编译后 */
.box1 {
width: 10%;
}
.box2 {
width: 20%;
}
.box3 {
width: 30%;
}
.box4 {
width: 40%;
}

链接符&和import

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.box {
.mark { // .box .mark
color: red;
}
// &: 在.box后面紧跟着谁
&.pp { // .box.pp
background: red;
}

& > .mm { // .box > .mm
background: green;
}

&:hover { // .box:hover
background: orange;
}
}
1
2
@import "reset"; // 在自己的less文件中把reset导入进来
@import (reference) "public"; // 加了reference只导入进来使用,但是不编译里面的代码

less小测
小米商城

移动端混合开发

HTML5基础知识及项目实战

HTML5基础概述

HTML: 超文本标记语言(页面中不仅又文字,而且可以呈现出图片,音视频等媒体资源)

XHTML: 他是HTML比较规范严谨的一代版本

XML:可扩展的标记语言(HTML中使用的标签都是W3C标准中规定的,XML允许我们自己扩展标签),它的作用给不是用来写页面结构的,而是用来存储一些数据的(以自己扩展的标签作为标识,清晰明了的展示数据的结构…)
ajax:async javascript and xml

HTML5: 是当前HTML最新的一代版本,也是非常成功的版本,目前市场上基本上都是基于H5规范进行开发的(它相对于传统的HTML更多的是增加一些有助于开发的内容,对原有规范的修改和调整很少)

XML

1
2
3
4
5
6
7
8
9
10
<root>
<student>
<name>小李</name>
<age>19</age>
<student>
<student>
<name>小王</name>
<age>20</age>
<student>
</root>

XHTML

文档声明比较复杂,需要特殊强调

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Title</title>
</head>
<body>

</body>
</html>

HTML4

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Title</title>
</head>
<body>

</body>
</html>

HTML5

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<!-- 声明页面的语言模式:english,如果页面中出现了英文单词,浏览器会自动发起是否翻译的功能 -->
<html lang="en">
<head>
<!-- 指定当前页面的编码格式是国际统一编码:UTF-8 (GB2312)中国编码 -->
<meta charset="UTF-8">
<title>标题</title>
</head>
<body></body>
</html>

HTML5提供的新语法规范

对原有语义化标签的升级

语义化标签(标签语义化):每一个html标签都有自己特殊的含义,我么你在搭建页面结构的时候,应该让合理的标签干合理的事情

HTML5中新增加了一些语义化标签(这些语义化标签,在兼容它的浏览器单中都是块级标签·)

1
2
3
4
5
6
7
8
9
10
article: 文章区域
header: 头部区域
footer: 尾部区域
main: 主题内容区域
section: 普通区域,用来做区域划分
figure: 配图区域
figcaption: 配图说明区域
aside: 与主题内容无关的区域(一般用来打广告)
nav: 导航区域
...

HTML5新增加一些标记标签

1
2
mark: 用来标记需要高亮显示的文本
time: 用来标记日期文本

HTML5中对原有标签还有一些调整

1
2
3
4
strong: 之前是加粗,现在是重点朗读(效果还是家族,但是语义不一样了)
small: 之前是变小,现在是附属细则(效果还是变小)
hr: 之前是一条直线,现在是分割线,用来分割两个区域
...

HTML5中删除一些不经常使用的标签,这里的删除不是不让用(用了也不报错),只是按照最新的标准没有语义了

1
2
font: 之前是标记文字修改某些文字样式的,现在我们不建议使用
center: 之前是使某些内容居中,但是目前我们都是基于CSS样式控制居中,不再使用这个标签

目前不管实在PC端开发爱是移动端开发,我们更应该使用H5规范的语义化标签搭建页面的结构

问题:
IE6-8中不能识别这些新增的语义化标签,我们无法为其设置具体的样式

解决:
在当前页面中的HEAD中(CSS后),我们导入一个JS插件:html5.min.js,它就是用来把页面中所有用到的不兼容的H5语义化标签进行兼容处理

1、把页面中所有不兼容的标签进行替换
2、把CSS中使用标签选择器设置的样式(标签是H5标签)也替换成其他方式

在标准浏览器中不需要引入,只要在IE6-8中才需要(使用条件注释来区分浏览器)

1
2
3
4
5
6
<head>
<!--[if lt IE 9]>
<script src="js/html5.min.js"></script>
<![endif]-->
</head>
<!-- 条件注释中的代码要严格区分大小写以及空格等细节问题 -->

H5中对于表单元素的升级

传统表单元素
form
input: text/psaaword(暗文输入)/button/submit/reset/file/hidden/radio
button
select
label
textarea

submit默认行为:点击按钮会跳转到action对应的地址(表单提交)
传统的非前后端分离项目中,我们会在action中指定一个程序处理页面(一般由后台语言完成),我们利用它的默认行为把数据发送给处理页面1,由处理页面完成数据的存储等操作
现有前后端完全分离项目中,我们都是在js当中手动获取到用户输入的内容并且通过ajax等技术发送给服务器存储或处理(此时我们需要阻止submit的默认行为)

H5对于表单的升级
1、给input设置了很多新的类型
search
email
tel
number
range
color
date
time

[优势]
1)、功能强大了
2)、使用合适的类型,在移动端的开发的时候,用户输入,可以调取出最符合输入格式的虚拟键盘,方便用户操作
3)、部分类型提供了表单验证(内置验证机制:和我们自己写的正则验证不太一样,但是可以凑合【CSS中可以验证、JS中也可以验证】)

2、给input新增一个属性:placeholder给表单框做默认的信息提示

3、二级下拉框

1
2
3
4
5
6
7
<input type="text" list="departmentList">
<datalist id="departmentList">
<option>市场部</option>
<option>销售部</option>
<option>技术部</option>
<option>总裁办</option>
</datalist>

H5针对表单元素升级的部分,在IE低版本(有的IE9和IE10都不兼容),而且没有办法处理兼容,所以我们一般移动端使用这些新特性,PC端还是延续传统的操作办法

H5中的表单验证(内置规则不是特别好),所以真实项目中表单验证依然延续传统的正则验证完成

placeholder

整个IE浏览器对placeholder兼容性都不好
1、IE10+虽然兼容,但是文本框获取焦点后,提示就消失了
2、IE9及以下不兼容这个属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
~function () {
//=> 获取页面当中所有具备data-place自定义属性的input
let inputList = document.getElementsByTagName('input'),
inputAry = [];

for (var i = 0; i < inputList.length; i += 1) {
let item = inputList[i];
item.getAttribute('data-place') !== null ? inputAry.push(item) : null;
}

//=> 非IE浏览器中,我们只需要把自定义属性值存放在placeholder属性中即可,浏览器可以自己根据这个属性做好提示工作
if (/(MSIE|Trident)/i.test(navigator.userAgent)) {
for (var k = 0; k < inputAry.length; k += 1) {
var itemInp = inputAry[k];
inputAry[k].placeholder = itemInp.getAttribute('data-place');
}
return;
}
//=> IE浏览器(包含IE EDGE)不用内置的placeholder,采用我们自己设定的方式处理
for (var z = 0; z < inputAry.length; z += 1) {
var inputItem = inputAry[z],
inputText = inputItem.getAttribute('data-place');
inputItem.placeholder = '';

/*
* 1、心创建一个span,把其存放在input元素的末尾(作为input的兄弟元素)
* 2、给span设置一定的样式(相对于父元素定位,和input的基础样式类似)
* 3、input或者span都要绑定相关事件行为:完成和内置placeholer相同的效果
*
*/
var spanTip = document.createElement('span');
spanTip.innerHTML = inputText;
spanTip.className = 'placeLike';
inputItem.parentNode.appendChild(spanTip);

//=> 把每一个input和span的索引,并且把spanTip作为属性值存储在input的自定义属性上,方便后期获取使用
inputItem.index = spanTip.index = z;
inputItem.spanTip = spanTip;

//=> span的点击行为:点击span让input获取对应光标
spanTip.onclick = function () {
inputAry[this.index].focus();
}

//=> 控制input的输入行为(建议使用DOM2事件绑定,防止后期再其他地方也需要通过keyup或者keydown行为处理其他的事情)
inputItem.onkeydown = inputItem.onkeyup = function(){
var value = this.value,
spanTip = this.spanTip;
spanTip.style.display = value.length > 0 ? 'none':'block';
}
}


}()

design-pattern.md

学习设计模式有什么作用?

[开发]
开发效率
利于团队协作

[维护]
有利于代码的升级改版
逻辑清晰/代码严谨/利于后期维护

[通用]
我们依托设计模式可以实现组件化/模块化/插件化/框架化以及一些常用类库方法的编写

插件、组件、类库、框架的区别

类库:提供一些真实项目开发中常用的方法(方法做了完善处理:兼容处理、细节优化),方便我们开发和维护[jQuery/Zepto…]

插件:把项目中某一部分进行插件封装(是具备具体业务逻辑,更加有针对性),以后再有类似的需求,直接导入插件即可,相关业务逻辑代码不需要自己在编写了[jquery.drag.js/jquery.dialog.js/jquery.validate.min.js/datepicker日历插件/echarts统计图插件/iscroll]

组件:类似于插件,但是插件一般只是把js部分封装了,组件不仅仅封装了js部分,而且把css部分也封装了,以后再使用的时候,我们直接按照文档使用说明引入css/js,搭建对应的结构,什么都不用做功能自然就有了[swiper组件/bootstrap组件…]

框架:比上面三个都要庞大,它不仅仅提供了很多常用的方法,而且也可以支持一些插件的扩展(可以把一些插件集成到框架中运行)、更重要的是提供了非常优秀的代码管理设计思想…[React/Vue/Angular/React Native]

JS中的常用设计模式

单例设计模式、构造原型设计模式、发布订阅设计模式、promise设计模式

单例设计模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//=> 单例模式:把实现当前这个模块所有的属性和方法汇总到同一个命名空间下(分组作用,避免了全局变量的污染)
let exampleRender = (function(){
//=> 实现当前模块具体业务逻辑的方法全部存放在闭包中
let fn = function(){
//=> ...
}
return {
init: function(){
//=> 入口方法:控制当前模块的具体业务逻辑顺序
fn();
}
}
})();
exampleRender.init();

真实项目中,我们如果想要实现具体的业务逻辑需求,都可以依托于当理模式构建,我们把项目划分成各大板块或者模块,把实现同一个板块的方法放在一个独立的命名空间下,方便团队写作开发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Tool {
constructor(){
this.isCompatible = 'addEventlistener' in document; //=> 如果不兼容返回false(IE6-8)
}
//=> 挂载到原型上的方法
css(){
//=> ...
}
//=> 挂载到普通对象上的方法
static distinct(){
//=>...
}
}
class Banner extends Tool{
constructor(...arg){
supper();
this.xxx = xxx;
}
//=> 挂载到子类原型上的方法
bindData(){
this.css(); //=> 把父类原型上的方法执行(子类继承了父类,那么子类的实例就可以调取父类原型上的方法)
this.distinct === undefined; //=> 子类的实例只能调取父类原型上的方法,以及父类给实例提供的私有属性方法,但是父类作为普通对象假如的静态方法,子类的实例是无法调取的(只有这样才可以调取使用Tool.distinct())
}
}

有三个类A/B/C,让C继承A和B

1
2
3
class A {}
class B extends A{}
class C extends B{}

发布订阅设计模式:观察者模式

不同于单例和构造,发布订阅是小型设计模式,应用到某一个具体的需求中:凡是当到达某个条件之后执行N多方法,我们都可以依托于发布订阅设计模式管理规划我们的JS代码

我们经常把发布订阅设计模式嵌套到其他的设计模式中

promise设计模式

解决AJAX异步请求层级齐嵌套的问题,它也是小型设计模式,目的是为了解决层级齐嵌套问题,我们也会经常把它嵌套再其他的设计模式运行

1
2
3
4
5
6
7
8
9
10
11
12
13
$.ajax({
url: '',
async: true,
success: function(){
$.ajax({
url: '',
async: true,
success: function(){
//=> 还会有后续嵌套
}
})
}
})

发布订阅设计模式

俗称观察者模式

实现思路
1、我们先创建一个计划表(容器)
2、后期需要做什么事情,我们都一次把需要处理的事情增加到计划表中
3、当符合某个条件的时候,我们只需要通知计划表中的方法按照顺序依次执行即可

JQ中的发布订阅

JQ中提供了实现发布订阅设计模式的方法

1
2
3
4
5
6
let $plan = $.Callbacks(); //=> 创建一个计划表

let fn = function(){};
$plan.add(fn); //=> 向计划表中添加方法
$plan.remove(fn); //=> 从计划表中移除方法
$plan.fire(100, 200); //=> 通知计划表中所有的方法按照顺序执行:100 200分别作为实参传递给每一个需要执行的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
~function(){
//=> Each:遍历数组中每一项的内容
let each = function (ary, callback) {
for (let i = 0; i < ary.length; i += 1) {
let result = callback && callback(ary[i], i);
if (result === false) {
//=> 如果执行的回调函数中返回false,代表结束当前正在便利的操作(仿照JQ中的each语法实现)
break;
}

//=> 如果回调函数中返回的是DEL,代表当前这一项在回调函数中被删除了,为了防止数组塌陷问题
if (result === 'DEL') i--;
}
};

class Plan {
constructor() {
this.planList = []; //=> 存放方法的容器
}

//=> 挂在到plan原型上的方法
add(fn) {
let planList = this.planList,
flag = true;

//=> 去重处理
each(planList, function(item, index){
if (item === fn) {
flag = false;
return false;
}
})
flag ? planList.push(fn) : null;
}

remove(fn) {
let planList = this.planList;
each(planList, function(item, index){
if (item === fn) {
//planList.splice(index, 1); //=> 这样会引起数组塌陷
planLis[index] = null;
return false;
}
})
}

fire(...arg) {
let planList = this.planList;
each(planList, function(item, index){
if (item === null) {
planList.splice(index, 1);
return 'DEL';
}
item(...arg);
})
}


//=> 挂在到Plan这个对象上的属性和方法
static Callbacks() {
return new Plan();
}
}

window.$ = window.Plan = Plan;
}();

let $plan = Plan.Callbacks();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//=> Each:遍历数组中每一项的内容
let each = function (ary, callback) {
if (!(ary instanceof Array)) return;
for (let i = 0; i < ary.length; i += 1) {
let result = callback && callback(ary[i], i);
if (result === false) {
//=> 如果执行的回调函数中返回false,代表结束当前正在便利的操作(仿照JQ中的each语法实现)
break;
}

//=> 如果回调函数中返回的是DEL,代表当前这一项在回调函数中被删除了,为了防止数组塌陷问题
if (result === 'DEL') i--;
}
};

//=> 第一部分 DOM2事件绑定
~function(){
class EventLibrary {
constructor(){

}
on(curEle, type, fn) {
if (typeof curEle['pond' + type] === 'undefined') {
curEle['pond' + type] = [];
let _run = this.run;
'addEventListener' in document ? curEle.addEventListener(type, _run, false) : curEle.attachEvent('on' + type, function(e){
_run.call(curEle, e);
})
}
let ary = curEle['pond' + type],
flag = true;
each(ary, (item, index) => {
if (item === fn) flag = false;
return flag;
})
flag ? ary.push(fn) : null;
}

off(curEle, type, fn) {
let ary = curEle['pond' + type];
each(ary, (item, index) => {
if (item === fn) {
ary[index] = null;
}
});

}

run(e) {
//=> this当前元素
e = e || window.event;
if (!e.target) {
e.target = e.srcElement;
e.pageX = e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft);
e.pageY = e.clientY + (document.documentElement.scrollTop || document.body.scrollTop);
e.which = e.keyCode;
e.preventDefault = function(){
e.returnValue = false;
}
e.stopPropagation = function(){
e.cancelBubble = true;
}
}
let ary = this['pond' + e.type];
each(ary, (item, index) => {
if (item === null) {
ary.splice(index, 1);
return 'DEL';
}
item.call(this, e);
})
}

}
window.EventLibrary = EventLibrary;
}();
on(document.body, 'click', fn1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
~function(){
Function.prototype.myBind = function myBind (context) {
//=> this:fn(我们需要预先处理this的函数)
//=> context:我们需要预先改变的this值,(如果不传递默认值是window)
context = context || window;
var outerArg = Array.prototypr.slice.call(arguments, 1); //=> 除了context之外传递进来的参数值
var _this = this;
if ('bind' in Function.prototype) {
outerArg.unshift(context);
return _this.bind.apply(_this, outerArg);
}

return function(e){
//=> _this:fn
//=> arguments:返回的匿名函数接收到的参数值
var innerArg = Array.prototype.slice.call(arguments, 0);
outerArg = outerArg.concat(innerArg);
_this.apply(context, outerArg);
}
};

return function(e){
//=> _this:fn
//=> arguments:返回的匿名函数接收到的参数值
var innerArg = Array.prototype.slice.call(arguments, 0);
outerArg = outerArg.concat(innerArg);
_this.apply(context, outerArg);
}
};
class Drag extends EventLibrary {
constructor(curEle) {
super();
this.curEle = curEle;

//=> callback
this.planDown = Plan.Callback();
this.planMove = Plan.Callback();
this.planUp = Plan.Callback()

//=> mousedown
this.on(this.curEle, 'mousedown', () => {
this.down.call(this);
});
}

down() {
//=> this:example
let curEle = this.curEle,
{top:t, left: l} = this.offset();
this.mouseX = e.pageX;
this.mouseY = e.pageY;
this.strL = l;
this.srtT = t;

//=> mousemove / mouseup
this._MOVE = this.move.myBind(this);
this._UP = this.ip.myBind(this);
this.on(document, 'mousemove', this._MOVE);
this.on(document, 'mouseup', this._UP);
this.planDown.fire(this);
}

move(e) {
//=> this:example
let curEle = this.curEle;
let curL = e.pageX - this.mouseX + this.strL,
curT = e.pageY - this.mouseY + this.strT;

//=> 边界判断
let {offsetParent:p} = curEle,
W = document.documentElement.clientWidth || documment.body.clientWidth,
H = document.documentElement.clientHieght || document.body.clientHieght;
if (p.tagName !== 'BODY') {
W = p.clientWidth;
H = p.clientHeight;
}
let maxL = W - curEle.offsetWidth,
maxT = H - curEle.offsetHieght;

curL = curL < 0 ? (curL > maxL ? maxL : curL);
curT = curT < 0 ? (curT > maxT ? maxT : curT);

curEle.style.left = curL + 'px';
curEle.style.top = curL + 'px';

this.planMove.fire();
}

up(e) {
//=> this:example
this.off(document, 'mousemove', this._MOVE);
this.off(document, 'mouseup', this._UP);
this.planUp.fire();
}

offset(flag) {
//=> this:example
let curEle = this.curEle;
let {offsetLeft:l, offsetTop:t, offsetParent:p} = curEle;
if (!flag) {
while (p.tagName !== 'BODY') {
let {clientLeft, clientTop, offsetLeft, offsetTop, offsetParent} = p;
{userAgent} = window.navigator;
if (userAgent.indexOf('MSIE 8') === -1) {
//=> 不是IE8需要加上边框
l += clientLeft;
t += clientTop;
}

l += offsetLeft;
t += offsetTop;
p = offsetParent;
}
}
return {top: t, left: l}

}

static init(curEle, {index = 0} = {}) {
return new Drag(curEle);
}
}
window.d rag = Drag;
}();
Drag.init(oBox, {
index: 0
});

dom-event

javascript中的事件基础以及核心原理以及项目实战

1、事件基础:全新认识事件

1、什么是事件?
事件是元素天生具备的行为方式 (和写不写js代码没关系),当我们去操作元素的时候会触发元素很多事件

2、事件绑定
给当前元素的某一个事件绑定方法,目的是为了让当前元素某个事件被触发的时候,可以做一些事情

给一个事件绑定方法,目前有两种方式:
1、DOM0级事件绑定
oBox.onclick=function(){}

2、DOM2级事件绑定
oBox.addEventListener('click', function(){}, false)标准浏览器
oBox.attachEvent('onclick', function()}) IE6-8

3、常用的事件汇总

【PC端】
表单元素常用事件行为
blur: 失去焦点
focus: 获取焦点
change: 内容改变
select: 被选中

键盘常用事件
keydown: 键盘按下
keyup: 键盘抬起
keypress: 键盘按下后有keypress(中文输入法状态下,会触发keydown,但是由于内容没有改变,keypress会一直触发)

鼠标常用事件
click: 点击(不是单机)
dblclick: 双击(300ms内凉席触发量词点击事件,这样为双击事件)
mouseover: 鼠标划过
mouseout: 鼠标划开
mouseenter: 鼠标进入
mouseleave: 鼠标离开
mousemove: 鼠标移动
mousedown: 鼠标左键按下
mouseup: 鼠标左键抬起
mousewheel: 鼠标滚轮滚动

其他事件行为
load: 加载成功
error: 加载失败
scroll: 滚动条滚动时间
resize: 大小改变事件 window.onresize当浏览器的窗口大小发生改变触发这个事件

【移动端】
移动端键盘一般都是虚拟键盘,虽然部分手机存在keydown/keyup但是兼容不好,所以我们想用键盘事件的时候,使用input事件代替inputBox.oninput = function(){}

移动端没有鼠标,所以鼠标类的事件在移动端兼容性都特别差

移动端的大部分操作是靠手指完成,移动端独有手指事件
单手指事件模型: touchstart、touchmove、touchend、touchcancel(由于意外事件导致触摸事件结束)

多手指事件模型: gesturestart、gesturechange、gestureend…

移动端还有很多操作是基于手机硬件完成的,例如手机传感器,手机陀螺仪,手机重力感应器等

在移动端兼容click事件,PC端的click是点击,但是移动端把click事件当做单击: 移动端使用click事件处理点击操作存在300ms

事件对象

1
2
3
4
//=> 事件绑定, 给oBox的click事件,基于DOM0级事件绑定的方式,绑定了一个方法;以后当我们手动触发oBox的click的时候,会把绑定的方法执行;
oBox.onclick = function(e){
//=> arguments[0] === e; 当方法执行的时候,浏览器默认传递方法的参数值(事件对象)
}

当元素的某一个事件被触发,不仅会把之前绑定的方法执行,而且还会给当前绑定的方法传递一个值(浏览器默认传递),我们把传递的这个值成为事件对象

1、因为这个值是个对象类型的值,里面存储了很多的属性和方法
2、这个对象中存储的值都是当前操作的一些基本信息,例如:鼠标的位置、触发的行为类型、触发的事件源等

以上所说的都是针对标准浏览器,IE6-8下不是这样的机制

IE6-8方法被触发执行的时候,浏览器并没有把事件对象当做值传递给函数(e在IE6-8下是undefined);但是IE6-8也有事件对象、事件对象需要我们通过window.event单独获取

1
2
//=> 兼容写法
e = e || window.event;

事件对象是为了记录当前操作基本信息的,所以只和本次操作有关:本次操作,页面中不管通过什么方式获取的e或者window.event(也不关在哪获取),他们存储的基本信息是相同的

鼠标事件对象 MouseEvent

clientX / clientY:当前鼠标触发点距离当前窗口左上角的X/Y轴的坐标
pageX / pageY:当前鼠标触发点距离BODY左上角的X/Y轴坐标(页面第一屏左上角),但是IE6-8中没有这两个属性

type:当前触发的事件类型

target:事件源(当前鼠标操作的是哪一个元素,那么事件源就是谁),IE6-8下没有tarrget属性,它有的是srcElement这个属性代表事件源

preventDefault:此方法是为了阻止事件的默认行为,IE6-8下没有这个方法,需要使用e.returnValue = false处理

stopPropagation:此方法是为了阻止事件的冒泡传播,IE6-8不兼容使用cancelBubble处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//=> pageX/pageY 兼容处理
oBox.onclick = function(e){
if(typeof e === 'undefined'){ //=> IE6-8
e = window.event;

//=> pageX / pageY
e.pageX = e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft);
e.pageY = e.clientY + (document.documentElement.scrollTop || document.body.scrollTop);

//=> target
e.target = e.srcElement;

//=> preventDefault
e.preventDefault = function(){
e.returnValue = false;
}

//=> stopPropagation
e.stopPropagation = function(){
e.cancelBubble = true;
}
//=> 下面再使用属性或者方法的时候,完全按照标准浏览器的语法来实现即可(IE6-8下不兼容的属性和方法我们已经重写为兼容的了)
}
}

上面的兼容处理方式属于比较完整的,但是如果项目中我们指向用到一个不兼容的属性,哦们没有必要写这么多,简单处理一下兼容就可以了

1
2
3
4
5
oBox.onclick = function(e){
e = e || window.event;
e.target = e.target || e.scrElement;
e.preventDefault?e.preventDefault:e.returnValue = false;
}

键盘事件对象 KeyboardEvent

code:当前键盘的按键,例如:按删除键,存储的是Backspace(IE6-8下没有这个属性),还有一个叫key的属性和code一样,存储的也是按键的名称

keyCode:存储的是当前键盘对应的码值(大部分按键都有自己的码值)

which:和keyCode一样对应的也是键盘码的值(不兼容IE6-8)

1
2
3
4
inputBox.onkeyup = function(e){
e = e || window.event;
var code = e.which || e.keyCode;
}

移动端手指事件对象 TouchEvent

touches / changeTouches / targetTouches: 存储的是当前屏幕上每一个手指操作的位置信息(手指离开屏幕没有相关信息,这样touchend事件中我们无法通过touches获取手指信息)

changedTouches:手指再屏幕上的时候,和touches获取的信息一样,但是它可以记录手指离开屏幕一瞬间所在的位置信息(最常用)

1
2
3
4
5
6
7
8
9
10
11
12
13
TouchEvent
type: 'touchstart',
target: 事件源
touches:
0: {
clientX: xxx,
clientY: xxx,
pageX: xxx,
pageY: xxx,
...
}
length: 1
changeTouches以及targetTouches存储的结构和touches相同

我们知道移动端的click是点击事件(不是pc端的点击效果),存在300ms的延迟,项目中我们需要解决这个延迟:
使用touchstart/touchmove/touchend来处理

简单处理

1
2
3
4
5
oBox.ontouchend = function(){
//=> 不管你是怎么操作的,我只需要知道,手指离开就算点击即可,存在一些问题:
//=> 手指按住屏幕不松开,事件超过750ms应该算作长按,不是点击,手机离开不应该按照点击处理
//=> 手指在屏幕上滑动,此时应该算作滑动不是点击,手指离开屏幕不应该算作点击处理
}

详细处理,解决移动端click300ms延迟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
var box = document.querySelector('#box');
box.ontouchstart = function(e){
let point = e.changedTouches[0];
//=> 记录当前手指的起始坐标位置(记录在当前元素的自定义属性上,在其他方法中如果我们想要获取直接通过自定义属性获取即可)
this.strX = point.pageX;
this.strY = point.pageY;
this.isMove = false;

};
box.ontouchmove = function(e){
let point = e.changedTouches[0];
//=> 一般我们手指操作,都会或多或少的发生一些偏移(习惯性偏移),此时不应该算作偏移,只有互动的距离超出一定范围,我们按照滑动处理即可(一般都是把10px作为偏差值)
let changeX = point.pageX - this.strX,
changeY= point.pageY - this.strY;
this.changeX = changeX;
this.changeY = changeY;
if(Math.abs(changeX) > 10 || Math.abs(changeY) > 10){
this.isMove = true;
}
};
box.ontouchend = function(e){
let point = e.changedTouches[0];
//=> 手指离开的时候,验证是否发生滑动
if(!this.isMove){
//=> 点击操作
console.log('这是点击操作');
}

//=> 滑动操作
let dir = null;
if(Math.abs(this.changeX) > Math.abs(this.changeY)){
//=> 左右滑动
dir = this.changeX < 0 ? 'Left' : 'Right';
}else{
//=> 上下滑动
dir = this.changeY < 0 ? 'Top' : 'Down';
}
console.log(`当前手指滑动的方向为:${dir}`);
}

在移动端开发中,我们需要的一些操作(例如:点击,单机,双击,长按,滑动[四个方向]…)都是基于内置原生的touchstart/touchmove/touchend事件一点点模拟出来的效果,没有现成的事件
而多手指操作(例如:旋转、缩放…)都是基于gesture事件模型模拟出来的效果

目前市场上有很多成熟的类库或插件,专门为大家把常用的操作进行了封装,我们直接调取使用即可

1、fastclick.js 目的就是为了解决移动端click事件300ms延迟问题(如果我们的移动端使用了click我们只需要把这个js导入配置一下即可)
2、百度云touch手势事件库
3、hammer.js
4、zepto.js 提供了移动端事件操作的板块,也是目前市场上使用率最高的(小型JQ)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$('.box').tap(function(){
//=> 点击
});
$('.box').singleTap(function(){
//=> 单击
});
$('.box').doubleTap(function(){
//=> 双击
});
$('.box').longTap(function(){
//=> 长按
});
$('.box').swipe(function(){
//=> 滑动 swipeLeft/swipeRight/swipeUp/swipeDown
});
//=> .pinchIn(function(){}) 缩小
//=> .pinchOut(function(){}) 放大

A标签的默认行为以及阻止

A标签都有那些默认行为
1、超链接:点击A标签可以实现页面的跳转
2、锚点定位:通过hash(哈希)值定位到当前页面指定ID元素的位置

真实项目中我们项用A标签做一个普通的按钮(优势:它的hover样式是兼容所有浏览器的)

锚点定位(哈希定位):定位到当前页面中和hash值(#xxx)相同的元素位置
1、首先在URL地址栏末尾追加一个hash值
2、如果地址栏当中有hash值,浏览器除了页面渲染外,在渲染完成后会默认定位到指定hash值元素位置

阻止A标签的默认行为

1
2
3
//=> 在html中阻止默认行为(最常用)
<a href="javascript:;"></a>
<a href="javascript:void 0;"></a>
1
2
3
4
5
6
7
8
9
10
11
12
//=> 结构当中有a标签 <a href="www.baidu.com" id="link"></a>
//=> 1、当点击会触发click事件
//=> 2、其次按照href中的地址进行页面跳转
link.onclick = function(){
return false; //=> 在函数中返回一个false(只能是false)也可以阻止默认行为
}

//=> 通过事件对象阻止默认行为
link.onclick = function(e){
e = e || window.event;
e.preventDefault?e.preventDefault():e.returnValue=false;
}

事件的传播机制

事件传播有三个阶段
Event.prototype

  • 0 NONE: 默认值,不代表任何的意思
  • 1 CAPTURING_PHASE: 捕获阶段
  • 2 AT_TARGET: 目标阶段(当前事件源)
  • 3 BUBBLING_PHASE: 冒泡阶段

点击某个元素,触发click事件
浏览器在执行元素上绑定的click事件之前
1、从整个页面的document开始向内查找,把元素的祖先元素遍历一遍(为冒泡阶段的传播途径做准备)
2、接下来找到目标元素(找到事件源)把事件源上绑定的方法执行(没有绑定方法就不执行)
3、不仅触发了当前事件源的点击行为,而且浏览器会按照第一阶段规划的传播路线,从内向外把祖先元素的click行为依次触发

当前元素的某个事件行为被触发,它所有的祖先元素(一直到document)的相关事件行为也会被触发(顺序是从内向外),如果组件元素的这个行为绑定了方法,绑定的方法也会被触发,我们把事件的这种传播机制叫做**冒泡传播**

1
2
3
xxx.addEventListener('click', function(){}, true); //=> 当前绑定的方法会在捕获阶段触发

xxx.addEventListener('click', function(){}, false); //=> 当前绑定的方法是在目标阶段或者冒泡阶段才会被触发执行(等价于dom0事件绑定)

mouseover和mouseenter的区别

mouseover:鼠标滑到元素上
mouseenter:鼠标进入元素里面

【1】
mouseover存在事件的冒泡传播机制,而mouseenter浏览器把它的事件传播机制阻止了

【2】
鼠标从父元素进入到子元素
mouseover:先触发父元素的mouseout(因为鼠标已经不再父元素上了,mouseover本意是鼠标在元素上菜算触发),在触发子元素的mouseover(由于冒泡传播机制,导致父元素的mouseover也被重新触发了)

mouseenter:进入,从大盒子进入到小盒子,没有触发大盒子的mouseleave事件,但是也出发了小盒子的mouseenter,浏览器阻止了它的冒泡传播,所大盒子的mouseenter不会被触发

事件委托

利用事件的冒泡传播机制完成(mouseenteru存在委托机制,因为它不存在冒泡传播)

一个容器中的很多元素的同一个事件都要绑定方法,此时我们没有必要在获取所有元素,一个个的绑定方法,我们只需要给最外层元素的这个事件绑定一个方法,这样不管里面哪一个元素的这个事件行为被触发都会利用冒泡传播机制,把最外层容器绑定的那个方法执行,在方法执行的时候,我们可以根据事件源判断出操作的是那个元素,从而做不同的事件(使用事件委托这样完成的操作比一个个的单独事件绑定性能提高50%左右)

DOM2级别事件绑定原理以及兼容处理

初步了解JS中事件绑定的方式

DOM0事件绑定

1
2
3
4
5
6
oBox.onclick = function(e){
//=> this:oBox
e = e || window.event;
}
oBox.onmouseenter = function(){}
...

DOM2事件绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
//=> 标准浏览器中
oBox.addEventListener('click', function(e){
//=> this:oBox

}, false);
//=> false 让当前绑定的方法在冒泡阶段执行(一般都用false)
//=> true 让当前绑定的方法在捕获阶段执行(一般不用)

//=> IE6-8
oBox.attachEvent('onclick', function(e){
//=> 此时绑定的方法都是在冒泡传播阶段执行
//=> e:事件对象,不同于DOM0事件绑定,使用attachEvent绑定方法,当事件触发方法执行的时候,浏览器也会把事件对象做实参传递给函数(传递的值和window.event是相同的),所以IE6-8下获取的事件对象对于:pageX/pageY/target...依然没有,还是存在兼容性(事件对象兼容处理在DOM2中依然存在)
});

有DOM0和DOM2事件绑定,那么DOM1事件绑定呢?
因为在DOM第一代升级迭代的时候,DOM元素的事件绑定方式沿用的是DOM0代绑定的方式,在此版本DOM中,事件绑定没有升级

DOM0事件绑定和DOM2事件绑定的区别

DOM0事件绑定的原理
1、给当前元素对象的某一个私有属性(onxxx)赋值的过程(之前属性默认值是null,如果我们给赋值一个函数,相当于绑定了一个方法)

2、当我们赋值成功(赋值一个函数),此时浏览器会把DOM元素和赋值的函数建立关联,以及建立DOM元素行为操作的监听,当某一个行为被用户触发,浏览器会把相关行为赋值的函数执行

特点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//=> 1、只有DOM元素拥有这个私有属性(onxxx事件私有属性),我们赋值的方法才叫事件绑定,否则属于给当前元素设置一个自定义属性而已

document.body.onclick=function(){}; //=> 事件绑定

/*
* 手动点击页面中的body触发方法执行
* document.body.onclick();
*/

document.body.onsmile=function(){}; //=> 自定义属性

/*
* 只能document.body.onsmile() 这样执行
*/

//=> 2、移除事件绑定的时候,我们只需要赋值为null即可
document.body.onclick=null;

//=> 3、在DOM0事件绑定中,只能给当前元素的某一个事件行为(某一个时间私有属性)绑定一个方法,绑定多个方法,最后一次绑定的会把之前绑定的都替换掉
document.body.onclick=function(){
console.log(1);
}

document.body.onclick=function(){
console.log(2);
}

//=> 点击BODY只能输出2,因为第二次赋值的函数把第一个赋值的函数给替换了

DOM2事件绑定的原理
1、我们DOM2事件绑定使用的addEventListener/attachEvent都是在EventTarget这个内置类的原型上定义的,我们调取使用的时候,首先通过原型链找到这个方法,然后执行完成事件绑定的效果

2、浏览器首先会给当前元素的某一个时间行为开辟一个事件池(时间队列)[其实是浏览器有一个统一的事件池,我们每个元素的某个行为绑定的方法都放在这个事件池中,只是通过相关的标识来区分的]

3、当元素的某一个行为触发,浏览器会到事件池中,把当前存放在事件池中的所有方法,依次按照存放的先后顺序执行

特点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//=> 1、所有DOM0支持的事件行为,DOM2都可以使用,不仅如此,DOM2还支持一些DOM0没有的事件行为
DOMContentLoaded...
document.body.DOMContentLoaded === undefined; //=> DOM0中没有这个属性

window.addEventListener('DOMContentLoaded', function(){
//=> 标准浏览器中兼容这个事件;当浏览器中的dom结构加载完成,就会触发这个事件(也会把绑定的方法执行)
}, false);

window.attachEvent('onDOMContentLoaded', function(){
//=> IE6-8中的DOM2也不支持这个事件
});

//=> 2、DOM2中可以个当前元素的某一个事件行为绑定多个不同的方法(因为绑定的所有方法都存放在事件池中)

function fn1(){
console.log(1);
}
function fn2(){
console.log(2);
}
function fn3(){
console.log(3);
}
document.body.addEventListener('click', fn1, false);
document.body.addEventListener('click', fn3, false);
document.body.addEventListener('click', fn2, false);
document.body.addEventListener('click', fn3, false); //=> 本次向事件池中存储的时候,发现fn3已经在事件池中存在了,不在存储了

//=> 3、DOM2事件绑定的移除比较麻烦一些,需要和绑定的时候:事件类型、绑定方法、以及传播阶段,三个完全一致才可以移除掉
document.body.removeEventListener('click', fn2, false);

document.body.addEventListener('click', function(){
console.log(1);
}, false);

document.body.removeEventListener('click', function(){
console.log(1);
}, false);

//=> DOM2事件绑定需要我们养成“未雨绸缪”的习惯,绑定方法的时候尽量不用你明函数,为后期可能会把方法在事件池中移除掉做准备
1
2
3
4
5
document.body.onclick = function fn(){
console.log(fn); //=> 当前匿名函数的函数本身(给匿名函数设置名字只能在函数本身使用)
console.log(1);
}
fn(); //=> Uncaught ReferenceError: fn is not defined
1
2
3
4
5
6
7
8
document.addEventListener('click', function fn(){
console.log(1);
}, false);
document.addEventListener('click', function fn(){
console.log(2);
}, false);

//=> 点击输出结果是1和2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function fn(){
console.log(1);
}
document.addEventListener('click', fn, false);
document.addEventListener('click', fn, false); //=> 第二次这个存储不到事件池中,因为事件池中已经存在fn了

document.onclick = fn; //=> 本次绑定是没有的,被下面的DOM0绑定给替换掉了
document.onclick = function(){
console.log(2);
};
document.addEventListener('click', function(){
console.log(3);
}, false);

//=> 点击输出结果 1 2 3
//=> 1、DOM0和DOM2绑定的方法是毫无关系的(因为是两套不同的处理机制),即使是绑定的方法相同,也是执行两次
//=> 2、谁先绑定的就先执行谁

事件是元素天生自带的行为 click mouseover DOMContentLoaded…
DOM0:浏览器会把一些常用的事件挂载到元素对象的私有属性上,让我们可以实现DOM0事件绑定

DOM2:凡事浏览器给元素天生设置的事件,在DOM2中都可以使用

window.onload和$(document).ready()的区别

window.onload: 当浏览器中所有的资源内容(DOM结构、文本内容、图片…)都加载完成,触发load事件
1、基于DOM0事件绑定完成的,所以在同一个页面中只能给它绑定一个方法(绑定多个以最后一次绑定的为主)

2、想在一个页面中使用多次,我们应该是基于DOM2事件绑定的

1
2
3
4
5
6
7
8
function fn1(){
//=> 第一个事件
}
function fn2(){
//=> 第二个事件
}
window.addEventListener('load', fn1, false);
window.addEventListener('load', fn2, false);

$(function(){})或者$(document).ready(function(){})
当文档中的DOM结构加载完成就会被触发执行,而且在同一个页面当中可以使用多次

1、JQ中提供的方法,JQ是基于DOMContentLoaded这个事件完成这个操作的

2、JQ中的事件绑定都是基于DOM2事件绑定完成的

3、但是DOMContentLoaded在IE6-8下使用attachEvent也是不支持的,JQ在IE6-8下使用的是onreadystatechange这个事件处理的

DOM2事件绑定的兼容处理

语法上的兼容
[标准]
curEle.addEventListener(‘type’, fn, false);
[IE6-8]
curEle.attachEvent(‘ontype’, fn);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/*
* on: 基于DOM2实现事件绑定(兼容所有浏览器)
* @parameter
* curEle: 当前需要操作的元素
* type: 需要绑定方法的事件类型
* fn: 需要绑定的方法
* @return
* 不需要返回值
*/

//=> ON: 给当前元素某个事件绑定方法
let on = function (curEle, type, fn){
if(document.addEventListener) {
//=> 标准浏览器
curEle.addEventListener(type, fn, false);
return;
}

//=> IE6-8
curEle.attachEvent(`on${type}`, fn);
};

//=> OFF: 移除当前元素某个事件绑定的方法
let off = function (curEle, type, fn){
if(document.addEventListener) {
//=> 标准浏览器
curEle.removeEventListener(type, fn, false);
return;
}

//=> IE6-8
curEle.detachEvent(`on${type}`, fn);
}

除了语法上的区别,在执行机制上有一些区别
在IE6-8中使用attachEvent做事件绑定(把方法存放在当前元素指定事件类型的事件池中)

1、顺序问题:当事件行为触发,执行对应事件池中存放的方法的时候,IE低版本浏览器执行方法的顺序是乱序(标准浏览器是按照绑定的先后顺序一次执行的)

2、重复问题:IE低版本浏览器在向事件池中增加方法的时候,没有去重机制,哪怕当前方法已经存放过了,还会重复的添加进去(标准浏览器事件的事件池机制很完善,可以自动去重,已经存放过的方法不允许再添加进来)

3、this问题:IE低版本浏览器中,当事件行为触发,把事件池中方法执行,此时方法中的this指向window,而不是像标准浏览器一样,指向当前元素本身

究其根本:其实都是IE低版本浏览器对其它内置事件池机制的不完善导致的

DOM2事件绑定兼容处理的原理:告别LOW的IE6-8的内置事件池,而是自己创建一个类似标砖浏览器的“自定义事件池”标砖浏览器不需要处理兼容,只有IE6-8才需要处理兼容

自定义兼容事件池

所谓事件池其实就是一个容器,不管是内置的还是自己创建的
IE6-8兼容处理:

1、 on:
没有自定义的事件池,我们需要手动创建一个自定义的事件池把需要绑定的方法,全部存放在自定义的事件池中
在存储的时候我们自己做重复验证,有的话就不在存储了(重复问题就解决了)

2、 off:
把不需要绑定的方法从自定义的事件池中移除掉

3、 run: 我们需要把run放在内置事件池中,当行为触发,需要浏览器把run方法执行,在run中把自定义事件池中存放的所有方法执行
this问题 -> 顺序问题 都解决了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/*
* on: 基于DOM2实现事件绑定(兼容所有浏览器)
* @parameter
* curEle: 当前需要操作的元素
* type: 需要绑定方法的事件类型
* fn: 需要绑定的方法
* @return
* 不需要返回值
*/

//=> ON: 给当前元素某个事件绑定方法
var on = function (curEle, type, fn){
if(document.addEventListener) {
//=> 标准浏览器
curEle.addEventListener(type, fn, false);
return;
}
//=> 创建自定义事件池:没有才去创建(创建在当前元素的自定义属性上,以后再其他方法中需要使用这个事件池,直接获取使用即可)
//=> 每一个事件应该有一个自己独有的事件池,防止事件之间的冲突
if (typeof curEle[type + 'Pond'] === 'undefined') {
curEle[type + 'Pond'] = [];
//=> 只要执行on就说明当前元素的这个时间行为将要被触发,我们需要绑定方法,此时我们应该把run先放在内置事件池中(当行为触发,先执行run在run方法中,再把我们自定义方法执行)
//curEle.attachEvent.('on' + type, //function(){
//run.call(curEle, e);
//}); //=> 把run只能向内置事件池中存放一次

curEle.attachEvent('on' + type, run.bind(curEle))
}
var ary = curEle[type + 'Pond'];
//=> 增加之前首先看一下,当前自定义事件池中是否有这个方法,我们不增加了
for (var i = 0; i < ary.length; i++){
if(ary[i] === fn){
return;
}
}

//=> 向自定义的事件池中增加方法
ary.push(fn);

};

//=> OFF: 移除当前元素某个事件绑定的方法
var off = function (curEle, type, fn){
if(document.removeEventListener) {
//=> 标准浏览器
curEle.removeEventListener(type, fn, false);
return;
}
//=> 从自定义事件池中把某个方法移除
var ary = curEle[type + 'Pond'];
if (ary) {
for (var i = 0; i < ary.length; i++){
if(ary[i] === fn){
//=> 这一项就是想移除的
//=> off方法执行从事件池中移除某个方法,不能使用splice移除,这样导致原始数组中的索引都改变了,此时正在执行的run循环,索引和想要获取的方法偏位了=> “数组塌陷”
ary[i] = null;
break;
}
}
}
}

//=> RUN: 把自定义事件池中存放的方法依次执行(并且处理this等问题)
var run = function(e){
//=> this:curEle
//=> e:window.event
//=> 把时间对象e的兼容处理好,在绑定方法执行的时候,我们把处理好的e传递给方法,以后在绑定的方法中直接使用即可,不用在开率兼容问题(类似于JQ中事件绑定的e)
if (type e.target === 'undefined') {
e.target = e.srcElement;
e.which = e.keyCode;
e.pageX = e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft);
e.pageY = e.clientY + (document.documentElement.scrollTop || document.body.scrollTop);
e.preventDefault = function(){
e.returnValue = false;
}
e.stopPropagation = function(){
e.cancelBubble = true;
}
}

var ary = this[e.type + 'Pond'];
if(ary){
for (var i = 0; i < ary.length; i++){
var item = ary[i];
if (item === null) {
//=> 当前这一项再执行的过程中被off方法移除掉了(null不执行)
ary.splice(i, 1);
i--;
continue;
}
item.call(this, e);
}
}
}

bind方法的兼容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function fn(e){
console.log(e, this.tag.Name);
}

// document.body.onclick = fn; //=> this:body
// document.body.onclick = fn.call(obj); //=> 绑定方法的时候,就把fn执行了,把返回的undefined绑定给事件,点击的时候什么都不处理
// document.body.onclick = function(e){
fn.call(obj, e);
} //=> 这种方式可以
// document.body.onclick = fn.bind(obj); //=> bind:不仅把this预先处理为obj,对于fn原本拥有的一些参数(例如E)也没有忽略掉,执行的时候也会传递给fn,而且我们还可以自己传递一些参数
// document.body.onclick = fn.bind(obj, 100, 200); //=> 自己传递的参数不会覆盖默认的参数(先把自己传递的传递给fn,fn中最后一项参数才是默认的e)

Function.prototype.myBind = function myBind (context) {
//=> this:fn(我们需要预先处理this的函数)
//=> context:我们需要预先改变的this值,(如果不传递默认值是window)
context = context || window;
var outerArg = Array.prototypr.slice.call(arguments, 1); //=> 除了context之外传递进来的参数值
var _this = this;
if ('bind' in Function.prototype) {
outerArg.unshift(context);
return _this.bind.apply(_this, outerArg);
}

return function(e){
//=> _this:fn
//=> arguments:返回的匿名函数接收到的参数值
var innerArg = Array.prototype.slice.call(arguments, 0);
outerArg = outerArg.concat(innerArg);
_this.apply(context, outerArg);
}
};
fn.myBind(obj, 100, 200);
document.body.onclick = fn.myBind(obj, 100, 200);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//=> ES6 封装DOM2兼容处理
~function(){
Function.prototype.myBind = function (context=window, ...outer) {
if ('bind' in this) {
return this.bind(...arguments);
}
return (...inner) => this.apply(context, outer.concat(inner))
};
let on = function (curEle, type, fn) {
if (document.addEventListener) {
curEle.addEventListener(type, fn, false);
return;
}

if (typeof curEle['pond' + type] === 'undefined') {
curEle['pond' + type] = [];
curEle.attachhEvent('on' + type, run.myBind(curEle));
}

let pondAry = curEle['pond' + type];
for (let i = 0; i < pondAry.length; i++) {
if (pondAry[i] === fn) {
return;
}
}
pondAry.push(fn);
};

let off = function (curEle, type, fn) {
if (document.removeEventListener) {
curEle.removeEventListener(type, fn, false);
return;
}

let pondAry = curEle['pond' + type];
if (!pondAry) return;
for (let i = 0; i < pondAry.length; i++) {
let itemFn = pondAry[i];
if (itemFn === fn) {
pondAry[i] = null;
break;
}
}
};

let run = function (e) {
e = e || window.event;
if (!e.target) {
e.target = e.srcElement;
e.pageX = e.clientX + document.documentElement.scrollLeft || document.body.scrollLeft;
e.pageY = e.clientY + document.documentElement.scrollTop || document.body.scrollTop;
e.which = e.keyCode;
e.preventDefault = function () {
e.returnValue = false;
};
e.stopPropagation = function () {
e.cancelBubble = true;
};
}

let pondAry = this['pond' + e.type];
if (!pondAry) return;
for (let i = 0; i < pondAry.length; i++) {
let itemFn = pondAry[i];
if (itemFn === null) {
pondAry.splice(i, 1);
i--;
coontinue;
}
itemFn.call(this, e);
}
};

window.$event = {
on: on,
off: off
}
}()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//=> 复习拖拽
let down = function (e) {
//=> this->oBox
this.strX = e.clientX;
this.strY = e.clientY;
this.strL = this.offsetLeft;
this.strT = this.offsetTop;

//=> 绑定方法的时候,向事件池中存放的是执行myBind返回的匿名函数(问题:移除的时候不知道移除谁)
this._MOVE = move.myBind(this);
this._UP = up.myBind(this);
$event.on(document, 'mousemove', this._MOVE);
$event.on(document, 'mouseup', this._UP);

//=> 结束当前盒子正在运行的动画
clearInterval(this.timerDrop);
clearInterval(this.timerFly);

};

let move = function (e) {
//=> this-> oBox
let curL = e.clientX - this.strX + this.strL,
curT = e.clientY - this.strY + this.strT;
curL = curL < 0 ? 0 : (curL > maxL ? maxL : curL);
curT = curT < 0 ? 0 : (curT > maxT ? maxT : curT);
this.style.left = curL + 'px';
this.style.top = curT + 'px';

//=> 计算水平方向的速度
if (!this.preFly) {
this.preFly = this.offsetLeft;
} else {
this.speedFly = this.offsetLeft - this.preFly;
this.preFly = this.offsetLeft
}

};

let up = function (e) {
//=> this -> oBox
$event.off(document, 'mousemove', this._MOVE);
$event.off(document, 'mouseup', this._UP);

//=> 松开鼠标后,让盒子运动
moveFly.call(this);
moveDrop.call(this);
};

let moveFly = function () {
//=> this:oBox
let speedFly = this.speedFly;
this.timerFly = setInterval(()=>{
//=> 由于JS盒子模型属性获取的结果是证书(会四舍五入),所以速度如果小于0.5,我们本次加的速度值在下一次获取的时候还会被忽略掉(此时盒子应该是不动的)

if (Math.abs(speedFly) < 0.5) {
clearInterval(this.timerFly);
return;
}


//=> 指数衰减的移动(速度乘以小于1的值肯定越来越小)
speedFly *= 0.98;
let curL = this.offsetLeft + speedFly;
if (curL > maxL || curL < 0) {
speedFly *= -1; //=> 控制反方向运动
curL = curL > maxL ? maxL : (curL < 0 ? 0 : null); //=> 控制不管怎么总都不要超过边界
}
this.style.left = curL + 'px';
}, 17);
}

//=> 垂直方向飞
let moveDrop = function () {
let speedDrop = 10,
flagDrop = 0;
this.timerDrop = setInterval(()=>{
if (flagDrop >= 2) {
clearInterval(this.timerDrop);
return;
}
speedDrop *= 0.98;
speedDrop += 9.8;
let curT = this.offsetTop + speedDrop;
if (curT > maxT) {
speedDrop *= -1;
curT = maxT;
flagDrop++;
} else {
flagDrop = 0;
}
this.style.top = curT + 'px';
}, 17);
}

let oBox = document.getElementById('box'),
maxL = (document.documentElement.clientWidth || document.body.clientWidth) - oBox.offsetWidth,
maxT = (document.documentElement.clientHeight || document.body.clientHeight) - oBox.offsetHeight;


$event.on(oBox, 'mousedown', down);

promise

promise是一个类,高版本的浏览器已经实现了promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//=> executor
//=> executor 包含两个参数 分别是resolve(解决) reject(拒绝)
//=> 代码一旦成功就不会走向失败

let fs = require('fs');
let p = new Promise(function(resolve, reject){ //=> 分别是两个函数
resolve('ok'); //=> 我们自己决定是成功还是失败
fs.readFile('test.txt', 'utf8', function(err, data){
if(err) reject(err);
resolve(data);
})
});

//=> p 代表promise实例
p.then(function(data){ //=> 成功的回调
console.log(data);
},function(err){ //=> 失败的回调
console.log(err)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let p = new Promise((resolve, reject)=>{
setTimeout(()=>{
if(Math.random() > 0.5){
resolve('yes');
}else{
reject('no');
}
}, 1000)
});

p.then(function(data){
console.log(data);
},function(err){
console.log(err);
});

Promise 三个状态 pending fulfilled rejected

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//=> promise 简单实现

function Promise(executor){
let self = this;
self.status = 'pending';
self.value = undefined;
self.reason = undefined;
self.onResolvedCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(value){
if(self.status === 'pending'){
self.value = value;
self.status = 'resolved';
self.onResolvedCallbacks.forEach(item=>item(self.value));
}
}
function reject(reason){
if(self.status === 'pending'){
self.reason = reason;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(item=>item(self.reason));
}
}
try{
executor(resolve, reject)
}catch(e){ //=> 有错误会走向失败
reject(e);
}
}
Promise.prototype.then = function(onFulfilled, onRejected){
let self = this;
if(self.status === 'resolved'){
onFulfilled(self.value);
}
if(self.status === 'rejected'){
onRejected(self.reason);
}
if(self.status === 'pending'){
self.onResolvedCallbacks.push(onFulfilled);
self.onRejectedCallbacks.push(onRejected);
}
}
module.exports = Promise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//=> Promise 链式调用

function Promise(executor){
let self = this;
self.status = 'pending';
self.value = undefined;
self.reason = undefined;
self.onResolvedCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(value){
if(self.status === 'pending'){
self.value = value;
self.status = 'resolved';
self.onResolvedCallbacks.forEach(item=>item(self.value));
}
}
function reject(reason){
if(self.status === 'pending'){
self.reason = reason;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(item=>item(self.reason));
}
}
try{
executor(resolve, reject)
}catch(e){ //=> 有错误会走向失败
reject(e);
}
}
Promise.prototype.then = function(onFulfilled, onRejected){
let self = this;
let promise2;
if(self.status === 'resolved'){
return promise2 = new Promise(function(resolve, reject){
let x = onFulfilled(self.value);
if(x instanceof Promise){
x.then(resolve, reject)
}else{
resolve(x);
}
})
}
if(self.status === 'rejected'){
return promise2 = new Promise(function(resolve, reject){
let x = onRejected(self.reason);
if(x instanceof Promise){
x.then(resolve, reject)
}else{
reject(x);
}
})
}
if(self.status === 'pending'){
return promise2 = new Promise(function(resolve, reject){
self.onResolvedCallbacks.push(function(){
let x = onFulfilled(self.value);
if(x instanceof Promise){
x.then(resolve, reject)
}else{
resolve(x);
}
});
self.onRejectedCallbacks.push(function(){
let x = onRejected(self.reason);
if(x instanceof Promise){
x.then(resolve, reject)
}else{
reject(x);
}
});
})

}
}
module.exports = Promise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//=> Promise 异常处理

function Promise(executor){
let self = this;
self.status = 'pending';
self.value = undefined;
self.reason = undefined;
self.onResolvedCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(value){
if(self.status === 'pending'){
self.value = value;
self.status = 'resolved';
self.onResolvedCallbacks.forEach(item=>item(self.value));
}
}
function reject(reason){
if(self.status === 'pending'){
self.reason = reason;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(item=>item(self.reason));
}
}
try{
executor(resolve, reject)
}catch(e){ //=> 有错误会走向失败
reject(e);
}
}
Promise.prototype.then = function(onFulfilled, onRejected){
onFulfilled = typeof oFulfilled === 'function'?onFulfilled:function(data){return data};
onRejected = typeof onRejected === 'function'?onRejected:function(data){return data};
let self = this;
let promise2;
if(self.status === 'resolved'){
return promise2 = new Promise(function(resolve, reject){
try{
let x = onFulfilled(self.value);
if(x instanceof Promise){
x.then(resolve, reject)
}else{
resolve(x);
}
}catch(e){
reject(e);
}

})
}
if(self.status === 'rejected'){
return promise2 = new Promise(function(resolve, reject){
try{
let x = onRejected(self.reason);
if(x instanceof Promise){
x.then(resolve, reject)
}else{
reject(x);
}
}catch(e){
reject(e);
}

})
}
if(self.status === 'pending'){
return promise2 = new Promise(function(resolve, reject){
self.onResolvedCallbacks.push(function(){
let x = onFulfilled(self.value);
if(x instanceof Promise){
x.then(resolve, reject)
}else{
resolve(x);
}
});
self.onRejectedCallbacks.push(function(){
let x = onRejected(self.reason);
if(x instanceof Promise){
x.then(resolve, reject)
}else{
reject(x);
}
});
})

}
}
Promise.prototype.catch = function(func){
return this.then(null, func);
}
module.exports = Promise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
//=> Promise与其他库结合使用
function Promise(executor){
let self = this;
self.status = 'pending';
self.value = undefined;
self.reason = undefined;
self.onResolvedCallbacks = [];
self.onRejectedCallbacks = [];
function resolve(value){
if(self.status === 'pending'){
self.value = value;
self.status = 'resolved';
self.onResolvedCallbacks.forEach(item=>item(self.value));
}
}
function reject(reason){
if(self.status === 'pending'){
self.reason = reason;
self.status = 'rejected';
self.onRejectedCallbacks.forEach(item=>item(self.reason));
}
}
try{
executor(resolve, reject)
}catch(e){ //=> 有错误会走向失败
reject(e);
}
}
Promise.prototype.then = function(onFulfilled, onRejected){
onFulfilled = typeof oFulfilled === 'function'?onFulfilled:function(data){return data};
onRejected = typeof onRejected === 'function'?onRejected:function(data){return data};
let self = this;
let promise2;
if(self.status === 'resolved'){
return promise2 = new Promise(function(resolve, reject){
try{
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e);
}

})
}
if(self.status === 'rejected'){
return promise2 = new Promise(function(resolve, reject){
try{
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
}catch(e){
reject(e);
}

})
}
if(self.status === 'pending'){
return promise2 = new Promise(function(resolve, reject){
self.onResolvedCallbacks.push(function(){
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
});
self.onRejectedCallbacks.push(function(){
let x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
});
})

}
}
function resolvePromise(promise2, x, resolve, reject){
//=> 将返回的promise不停的执行,直到失败或者返回一个普通的数据类型
if(promise2 === x){
throw new TypeError('xxxx');
}
if((x !== null) && (typeof x === 'function') || (typeof x === 'object')){
//=> promise
let x = x.then;
if(typeof x === 'function'){
then.call(x, fuunction(value){
resolvePromise(promise2, value, resolve, reject);
},function(reason){
reject(reason);
})
}
}else{
resolve(x);
}
}
Promise.prototype.catch = function(func){
return this.then(null, func);
}
Promise.all = function(promises){
return new Promise(function(resolve, reject){
let result = [];
let resolved = function(index){
return function(data){
index++;
if(i === promises.length){
resolve(result);
}
}
}
for(let i = 0;i< promises.length; i+=1){
promises[i].then(resolved(i));
}
})
}
module.exports = Promise

git-command

前言

最近再提交项目代码的时候,遇到了一些没见过的报错信息,发现自己对git命令依然停留在会用的阶段,对于其命令之间的区别和具体作用并不清楚,所以在这写一篇关于git命令的文章来加深自己对git的认识。

Git 历史

同生活中的许多伟大事件一样,Git 诞生于一个极富纷争大举创新的年代。Linux 内核开源项目有着为数众广的参与者。绝大多数的 Linux 内核维护工作都花在了提交补丁和保存归档的繁琐事务上(1991-2002年间)。到 2002 年,整个项目组开始启用分布式版本控制系统 BitKeeper 来管理和维护代码。

到了 2005 年,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了免费使用 BitKeeper 的权力。这就迫使 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds )不得不吸取教训,只有开发一套属于自己的版本控制系统才不至于重蹈覆辙。他们对新的系统制订了若干目标:

  1. 速度
  2. 简单的设计
  3. 对非线性开发模式的强力支持(允许上千个并行开发的分支)
  4. 完全分布式
  5. 有能力高效管理类似 Linux 内核一样的超大规模项目(速度和数据量)

git基础操作

一、新建代码库

1
2
3
4
5
6
7
8
# 在当前目录新建一个git代码库
git init

# 新建一个目录,将其初始化为git代码库
git init [project-name]

# 下载一个项目和它的整个代码历史
git clone [url]

二、配置

git的配置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)

1
2
3
4
5
6
7
8
9
# 像是当前git配置
git config --list

# 编辑git配置文件
git config -e [--global]

# 设置提交代码時的用户信息
git config [--global] user.name "[name]"
git config [--global] user.email "[email address]"

三、增加、删除文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 增加制定文件到暂存区
git add [file1] [file2] ...

# 添加指定目录到暂存区,包括子目录
git add [dir]

# 添加当前目录的所有文件到暂存区
git add .

# 添加每个变化前,都会要求确认
# 对于同一个文件的多出变化,可以实现粉刺提交
git add -p

# 删除工作区文件,并且将这次删除放入暂存区
git rm [file1] [file2] ...

# 停止追踪指定文件,但该文件会保留在工作区
git rm --cached [file]

# 改名文件,并且将这个改名文件放入暂存区
git mv [file-original] [file-renamed]

四、代码提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 提交暂存区到仓库
git commit -m [message]

# 提交暂存区的指定文件到仓库区
git commit [file1] [file2] ... -m [message]

# 提交工作去自上次commit之后的变化,直接到仓库区
git commit -a

# 提交時显示所有diff信息
git commit -v

# 使用一次新的commit,替代上一次提交
# 如果代码没有任何变化,则用来改写上一次commit的提交信息
git commit --amend -m [message]

# 重做上一次的commit,并包括指定文件的新变化
git commit --amend [file1] [file2] ...

五、分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 列出所有分支
git branch

# 列出所有远程分支
git branch -r

# 列出所有本地分支和远程分支
git branch -a

# 新建一个分支,但依然停留在当前分支
git branch [branch-name]

# 新建一个分支,并且换到该分支
git checkout -b [branch]

# 新建一个分支,指向指定commit
git branch [branch] [commmit]

# 新建一个分支,于指定的远程分支建立追踪关系
git branch --track [branch] [remote-branch]

# 切换到指定分支,并更新工作区
git checkout [branch-name]

# 切换到上一个分支
git chckout -

# 建立追踪关系,在现有分支与指定的远程分支之间
git branch --set-upstream [branch] [remote-branch]

# 合并指定分支到当前分支
git merge [branch]


# 选择一个commit,合并进当前分支
git cherry-pick [commit]

# 删除分支
git branch -d [branch-name]

# 删除远程分支
git push origin --delete [branch-name]
git branch -dr [remote/branch]

六、标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 列出所有tag
git tag

# 新建一个tag在当前commit
git tag [tag]

# 新建一个tag在指定commit
git tag [tag] [commit]

# 删除本地tag
git tag -d [tag]

# 删除远程tag
git push origin :refs/tags/[tagName]

# 查看tag信息
git show [tag]

# 提交指定tag
git push [remote] [tag]

# 提交所有tag
git push [remote] --tags

# 新建一个分支,指向某个tag
git checkout -b [branch] [tag]

七、查看信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 显示有变更的文件
git status

# 显示当前分支的版本历史
git log

# 显示commit历史,以及每次commit发生变更的文件
git log --stat

# 搜索提交历史,根据关键词
git log -S [keyword]

# 显示某个commit之后的所有变动,每个commit占据一行
git log [tag] HEAD --pretty=format:%s

# 显示某个commit之后的所有变动,其“提交说明”必须符合搜素条件
git log [tag] HEAD --grep feature

# 显示某个文件的历史版本,包括文件改名
git log --follow [file]
git whatchanged [file]

# 显示指定文件相关的每一次diff
git log -p [file]

# 显示过去5次提交
git log -5 --pretty --oneline

# 显示所有提交的用户,按提交次数排序
git shortlog -sn

# 显示指定文件是什么人什么时候修改过
git blame [file]

# 显示暂存区和工作去差异
git diff

# 显示暂存区和上一个commit差异
git diff --cached [file]

# 显示工作区和当前分支最新commit之间的差异
git diff HEAD

# 显示两次提交之间的差异
git diff [first-branch]...[second-branch]

# 显示今天你写了多少行代码
git diff --shoerstat "@[0 day ago]"

# 显示某次提交的元数据和内容变化
git show [commit]

# 显示某次提交发生变化的文件
git show --name-only [commit]

# 显示某次提交,某个文件的内容
git show [commit]:[filename]

# 显示当前分支的最近几次提交
git reflog

### 八、远程同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 下载远程仓库的所有变动
git fetch [remote]

# 显示所有远程仓库
git remote -v

# 显示某个远程仓库的信息
git remote show [remote]

# 增加一个新的远程仓库,并命名
git remote add [shortname] [url]

# 取回远程仓库的变化,并与本地分之合并
git pull [remote] [branch]

# 强行推送当前分支到远程,即使有冲突
git push [remote] --force

# 推送所有分支到远程仓库
git push [remote] --all

九、撤销

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 恢复暂存区的指定文件到工作区
git checkout [file]

# 恢复某个commit的指定文件到暂存区和工作区
git checkout [commit] [file]

# 恢复暂存区的所有文件到工作区
git checout .

# 重置暂存区的指定文件,与上次commit保持一致,但工作区不变
git reset [file]

# 重置暂存区与工作去,与上一次commit保持一致
git reset -hard

# 重置当前分支的指针为指定commit,同时重置暂存区,但工作去不变
git reset [commit]

# 重置当前分支的HEAD为指定的commit,同时重置暂存区和工作区,与指定commit一致
git reset --hard [commit]

# 重置当前HEAD为指定commit,但保持暂存区和工作区不变
git reset --keep [commit]

# 新建一个commit,用来撤销指定commit
# 后者的所有变化都将被前者抵消,并且应用到当前分支
git revert [commit]

# 暂时将未提交的变化移除,稍后再移入
git stash
git stash pop

十、其实

1
2
# 生成一个可供发布的压缩包
git archive

ES6-Review

JS语法规范ECMAScript(ES6)基础知识及核心原理

ES6 发展史

这里推荐阅读阮一峰ECMAScript 6 入门

使用babel对ES6进行编译

1、 下载安装Babel
环境:需要在电脑上安装node(node自带npm包管理器)

1
2
3
npm install babel-cli -g 把模块安装在全局环境下(在任何的项目中,都可以使用命令来变异我们的代码)

npm uninstall babel-cli -g 把全局下安装的babel-cli 卸载掉

安装日志

1
2
3
4
5
6
7
8
9
$ npm install babel-cli -g
C:\Users\xxx\AppData\Roaming\npm\babel-doctor -> C:\Users\yuboyang\AppData\Roaming\npm\node_modules\babel-cli\bin\babel-doctor.js
C:\Users\xxx\AppData\Roaming\npm\babel -> C:\Users\xxx\AppData\Roaming\npm\node_modules\babel-cli\bin\babel.js
C:\Users\xxx\AppData\Roaming\npm\babel-external-helpers -> C:\Users\yuboyang\AppData\Roaming\npm\node_modules\babel-cli\bin\babel-external-helpers.js
C:\Users\xxx\AppData\Roaming\npm\babel-node -> C:\Users\yuboyang\AppData\Roaming\npm\node_modules\babel-cli\bin\babel-node.js
C:\Users\xxx\AppData\Roaming\npm
`-- babel-cli@6.26.0

......

C:\Users\xxx\AppData\Roaming\npm 这个路径很重要

1、在该路径下可以看到,安装babel后声称的xxx.cmd文件(可以在doc窗口执行命令)
babel.cmd以后可以使用babel命令
babel-node.cmd

2、执行babel命令后我们可以完成一些变异或者其他任务,主要原因是执行babel命令后,会自动加在一些处理任务的文件

二、配置.babelrc文件,安装一些语言解析包

1、我们需要把.babelrc文件配置在当前项目的根目录下(这个文件没有文件名,后缀名就是.babelrc)

a:在电脑上不能直接创建没有文件名的文件,我们需要使用WS中的new->file创建,或者使用命令创建dir>.babelrc

b:babelrc这个后缀名在某些ws中是不识别的,它其实是一个json文件,我们需要在ws中配置一下(让它隶属于json文件)

2、在文件中编写一些内容

1
2
3
4
{
"persets": [], //=> 存放的是,我们变异代码的时候需要以来的语言解析包
"plugins": [] //=> 存放的是,我们变异代码时候需要以来的插件信息
}

3、安装以来的语言解析包

在当前项目的根目录下安装(不是安装在全局),需要特别注意的是:需要在当前项目根目录中打开doc命令才可以

npm install babel-preset-latest 安装最新已经发布的语言标准解析模块
npm install babel-preset-stage-2 安装当前还没有发布但是已经进入草案的语言解析模块(如果你的代码中用到了发布非标准的语法,我们需要安装它)

安装成功后,会在当前目录下存在一个node_modules文件夹,在这个文件夹中有我们安装的模块

4、完成最后.babelrc文件的配置

1
2
3
4
5
6
7
{
"persets": [
"latest",
"stage-2"
],
"plugins": []
}

三、使用命令变异js代码

基本上所有支持命令操作的模块都有一个命令
babel --help / babel -h 查看帮助

babel --version / babel -V 查看版本号

babel --out-file / babel -o 把某一个js文件中的es6代码进行编译

babel --out-dir / babel -d 把某一个文件夹中所有的js文件中的es6代码进行编译

babel --watch / babel -w 监听文件中代码的改变,当代码改变后,会自动进行编译

1
2
3
4
5
6
7
babel ES6/1.js -o ES5/1.js //=> 当个文件编译后保存

babel ES6 -d ES5 //=> 将ES6下全部js文件编译后保存到ES5下

babel ES6 -d ES5 -w //=> 监听改变自动编译

//=> 结束监听 ctrl+c 两次即可

ES6中的let和const

let 基础语法

let 变量名 = 变量值

1、 let声明变量不存在变量提升

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
console.log(str); //=> undefined
console.log(fn); //=> fn本身
console.log(avg); //=> undefined
console.log(sum); //=> sum is not defined
console.log(num); //=> num in not defined

var str = 'aa';
let num = 12;

function fn(){}

var avg = function(){};
let sum = function(){};
//=> ES6当中只提供了创建变量的信誉发标准,创建函数还是沿用ES5中的function(还会存在变量提升),如果想让函数也不存在变量提升,偶使用函数表达式赋值的方式:let fn = fuction(){}

//=> 创建变量
let xxx = xxx;

//=> 创建函数
let xxx = function(){}

//=> 自执行函数
;(function(){

})();

2、使用let定义的变量不允许在同一个作用域中重复生命

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var num = 12;
var num = 13;
console.log(num); //=> 13

let str = 'aa';
let str = 'bb';
console.log(str); //=> Identifier 'str' has already been declared 当前报错,上面代码也不会执行(在JS代码执行之前,就已经知道有重复声明的了,也就是浏览器依然存在类似于变量提升的机制:在JS代码执行之前先把所有let声明的变量过一遍,发现有重复的直接报错)

let num = 12,
fn = function(){
let num = 12;
};
console.log(num); //=> 12 当前作用域下别重复声明即可(不同作用域中的变量是自己私有的,名字重复没关系)

var att = 200;
let att = 100; //=> Identifier 'att' has already been declared
//=> 不管之前使用什么方式在当前作用域中声明的变量,再使用let声明的时候都会报错

3、暂时性死区:使用typeof检测一个未被声明过的变量

1
2
3
4
5
"use strict";
// console.log(typeof num); //=> undefined 当前变量不存在,但是使用typeof检测的时候,不会提示错误,而是返回undefined

// console.log(typeof num); //=> Uncaught ReferenceError: num is not defined ES6中检测一个没有被生命过的变量直接报错,不像之前ES5中的值是undefined一样了
// let num;

4、ES6语法创建的变量(let)存在会级作用域,ES5语法创建变量(var/function)没有会级作用域

[ES5]
window全局作用域
函数执行形成的私有作用域

[ES6]
除了ES5中的两个作用域,ES6中新增加块级作用域(我们可以把块级作用域理解为私有作用域:存在私有变量和作用域链的一些机制)

1
2
3
4
5
6
7
8
9
10
11
let num = 12,
str = '';
let fn = function(str){
str = 'hello';
console.log(arguments[0]); //=> hello 当前JS并没有开启严格模式,所以形参变量和arguments存在映射机制(但我们应尽量不要这样处理,因为把ES6编译成ES5之后,会默认的开启严格模式,映射机制会终端,此处的值依然是‘word’)
console.log(num); //=> Uncaught ReferenceError: num is not defined
let num = 13;
console.log(num, str); //=> 13 hello
};
fn('word');
console.log(num, str); //=> 12 ''
1
2
3
4
5
"use strict"
if(10 >= 10){
var total = 100;
}
console.log(total); //=> Uncaught ReferenceError: total is not defined 判断体也是一个块级作用域,在这个作用域中声明的变量时私有变量,再块级作用域之外时无法使用的
1
2
3
4
for(let i = 0; i < 5; i += 1){
console.log(i);
}
console.log(i); //=> Uncaught ReferenceError: i is not defined 循环体也是一个块级作用域(每一次循环都会形成一个新的块级作用域,当前例子形成五个块级作用域,每一个块级作用域中都有一个私有变量i,分别存储的时0-4)
1
2
3
4
5
6
7
8
9
10
let i = 10;
{
let i = 20;
{
{
console.log(i); //=> Uncaught ReferenceError: i is not defined 虽然ES6没有变量提升,但是每一次进入当前作用域都会把let定义的变量找一边(不提升但是找了,找到了说明当前作用域中是有这个变量的,提前用都会报错)
}
let i = 30;
}
}
1
2
3
4
5
6
7
8
try{
let i = 100;
}catch(e){
let k = 200;
}
console.log(i); //=> Uncaught ReferenceError: i is not defined
console.log(k); //=> Uncaught ReferenceError: k is not defined
//=> try catch 中的任何大括号都是块级作用域
1
2
3
4
5
6
switch(10){
case 10:
let i = 10;
break;
}
console.log(i); //=> Uncaught ReferenceError: i is not defined
1
2
3
4
5
let obj = {name: 'a'};
for(let key in obj){

}
console.log(key); //=> Uncaught ReferenceError: k is not defined

增加这些块级作用域的作用

1
2
3
4
5
6
let list = document.getElementsByClassName('item');
for(let i = 0; i < list.length; i += 1){
list[i].onclick = function(){
console.log(i);
}
}

const的基础语法
const的细节知识点和let一样,和let的主要区别在于:let是创建变量,const是创建常量

1
2
3
4
5
6
7
let num = 12;
num = 13;
console.log(num); //=> 13

const str = 'hello';
str = 'world';
console.log(str); //=> Uncaught TypeError:Assignment to constant variable 而且使用babel 遇到了const设置的常量再进行修改就无法进行编译了

JS中创建变量方式汇总

var: ES5中创建变量
function: ES5中创建函数
ES5中创建变量或函数存在:变量提升、重复声明等特征,但是没有块级作用域

let: ES6中创建变量
const: ES6中创建常量
ES6中创建的变量或者常量都不可以变量提升,也不可以重复生命,而且还存在块级作用域

class: ES6中创建类的方式
import: ES6中模块导入的方式

1
2
3
4
5
6
7
8
9
10
11
class Parent {
constructor(){
//=> this.xxx = xxx
}

//=> Parent.prototype
aa(){}

//=> Parent own property
static bb(){}
}

ES6的解构赋值

按照原有的解构,把原有值中的某一部分内容快速获取到(快速复制给一个变量)

数组的解构赋值

解构赋值本身是ES6的语法规范,使用什么关键字来申明这些变量是无所谓的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let ary = [12,23,34];
//=> 传统的取值赋值操作
//=>let a = ary[0];
//=>let b = ary[1];
//=>let c = ary[2];
//=>console.log(a,b,c);

let [a,b,c] = [12,23,34];
console.log(a,b,c); //=> 12 23 34

~function(){
[d,e,f] = [12,23,34];
}()
console.log(d.e.f); //=> 12 23 34

[g,h,i] = [12,23,34]; //=> 12 23 34
//=> 此处相当于给window增加的全局属性
//=> 但是这个操作在JS严格模式下是不允许的,因为严格模式下不允许出现非使用var/let等声明的变量

多位数组的解构赋值,可以让我们快速获取到需要的结果

1
2
3
4
5
6
7
//=> [12,[23,34],[45,56,[67,78]]] 把多维数组中的34 56 78获取到,并分别复制给A B C

let [,[,A],[,B,[,C]]] = [12,[23,34],[45,56,[67,78]]];
console.log(A,B,C); //=> 34 56 78

let [D] = [12,23,34];
console.log(D); //=> 12 如果指向获取数组中前面的某一项,后面的解构不需要补全

在解构赋值中,可以给某一项设置默认值

1
2
3
4
5
let [,,,A] = [12,23,34];
console.log(A); //=> undefined

let [,,,B = 0] = [12,23,34];
console.log(B); //=> 0
1
2
3
4
5
6
7
8
let [A,...B] = [12,23,34,45];
console.log(A,B); //=> 12 [23,34,45]

let [...C] = [12,23,34];
console.log(C); //=> [12,23,34] 数组克隆

let [D,...E,F] = [12,23,34,45,56,67];
console.log(D,E,F);//=> Uncaught SyntaxError: Rest element must be last element 拓展运算符只能出现在解构赋值的末尾

对象的解构赋值

1
2
3
4
5
6
let {name, age} = {name: 'a', age: 12};
console.log(name, age); //=> a 12

let {A,B} = {name: 'a', age: 12}
console.log(A,B); //=> undefined undefined
//=> 在对象的解构赋值中需要注意的是:赋值的变量需要和对象中的属性名吻合,否则无法获取对应的属性值
1
2
let {C=0} = {name: 'a', age: 12};
console.log(C); //=> 0 和数组的结构赋值一样,我们可以把后面不需要赋值的结果省略掉,而且也可以给当前的变量设置默认值
1
2
3
let {name} = {name: 'a', age: 12};
console.log(name); //=> a
//=> let {,age} = {name: 'a', age: 12}; //=> Uncaught SyntaxError: Unexpected token,和数组的解构赋值不一样的地方在于,对象前面不允许出现空来占位(因为对象获取需要通过具体的属性名,写成空的话,浏览器不知道怎么识别)
1
2
let {name, ...arg} = {name: 'a', age: 12, sex: 0};
console.log(name, arg); //=> a {age: 12, sex: 0} 支持拓展运算符
1
2
3
4
5
//=> 把对象进行浅克隆(只把第一级克隆了)
let obj = {name: 'a', age: 12, sex: 0,score: [1, 2, 3]};
let {...arg} = obj;
console.log(arg === obj); //=> false
console.log(arg.score === obj.score); //=> true
1
2
let {name:A, age:B} = {name: 'a', age: 12}
console.log(A, B); //=> a 12 在对象的解构赋值中,我们可以把对象的属性名起一个别名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
let data = [
{
"name": "张三",
"age": 20,
"score": {
"English": [100, 95, 90],
"math": [100, 100, 80],
"Chinese": [98, 99, 100]
}
},
{
"name": "李四",
"age": 18,
"score": {
"English": [10, 5, 9],
"math": [80, 50, 50],
"Chinese": [68, 49, 80]
}
},
{
"name": "王五",
"age": 23,
"score": {
"English": [77, 45, 67],
"math": [67, 89, 90],
"Chinese": [43, 65, 98]
}
}
];

let [{score:{English:[A],math:[,B],Chinese:[,,C]}}] = data;
console.log(A,B,C);

解构赋值在项目中的应用

可以快速接收到传递的多个值(我传递的是一个对象或数组)

1
2
3
4
5
//=> 快速交换两个变量的值
let a = 12;
let b = 13;
[a, b] = [b, a];
console.log(a, b); //=> 13 12
1
2
3
4
5
6
7
8
let fn = function(){
let a = 12,
b = 13,
c = 14;
return [a, b, c];
};
let [a, b, c] = fn();
console.log(a, b, c); //=> 12 13 14
1
2
3
4
5
let fn = function([a, b, c, d = 0]){
console.log(a, b, c, d); //=> 12 23 34 0
};
fn([12, 23, 34]);
console.log(a); //=> Uncaught ReferenceError: a is not defined 函数中的a b c 是私有变量
1
2
3
4
5
6
7
8
9
10
11
//=> 设置参数默认值
let fn1 = function({x = null, y = {}, z = 1000, callback = null}){

};
fn1({x: 10, y: {a:1}});

//=> 不传参数,让其在空对象中结构赋值,避免报错
let fn2 = function({x = null, y = {}, z = 1000, callback = null} = {}){

};
fn2();
1
2
3
4
5
6
let obj = new Map();
obj.set('x', 10);
obj.set('y', 20);
for(let [key, value] in obj){
console.log(key, value);
}

ES6中的箭头函数

箭头函数基础语法

1
2
3
4
let fn = function(x, y){
return x + y;
}
console.log(fn(10, 20)) //=> 30
1
2
//=> 将上面函数改写成箭头函数
let fn = (x, y) => x + y;
1
2
3
4
5
6
let fn = function(n){
let x = 10,
y = 20;
return x + y;
};
fn();
1
2
3
4
5
let fn = n => {
let x = 10,
y = 20;
return x + y;
}

箭头函数中不支持arguments

1
2
3
4
5
let fn = function(){
let arg = Array.prototype.slice.call(arguments);
return eval(arg.join('+'));
}
fn(10, 20, 30, 40); //=> 100
1
2
3
4
5
6
7
let fn = ()=> {
console.log(arguments); //=> Uncaught ReferenceError: arguments is not defined
//=> 不支持arguments,可以使用ES6中的生于运算符`...`来获取传递进来的所有参数值(相对于arguments的优势:使用剩余运算符接收到的结果本身就是一个数组,不需要再转换了)
//console.log(arg instanceof Array); //=> true
return eval(arg.join('+'))
};
fn(10, 20, 30, 40);
1
2
let fn = (...arg) => eval(arg.join('+'));
console.log(fn(10, 20, 30, 40));

箭头函数中的this问题

普通函数中的this指向问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//=> 普通函数执行this的指向:看函数执行前面是否有点,点前面是谁this就是谁,没有点this指向window或者undefined(严格模式下)
let obj = {
name: 'obj',
fn(){
//=> 这样处理和下面sum的处理是一样的
console.log(this);
},
sum: function(){

}
}
obj.fn(); //=> this => obj
document.body.onclick = obj.fn; //=> this => body
setTimeout(obj.fn, 1000); //=> this => window
obj.fn.call(12); //=> this => 12

箭头函数中的this

1
2
3
4
5
6
7
let obj = {
name: 'obj',
fn: () => {
console.log(this);
//=> 不管怎么操作this都指向window,箭头函数中没有this指向,用到的this都是所在宿主环境(它的上级作用域)中的this
}
}

项目中并不是把所有的函数都改成箭头函数,根据自身需要来修改即可

箭头函数的一点扩充

1
2
3
4
5
6
7
8
9
10
let fn = ()=>{
console.log(this);
}
let obj = {
name: 'obj',
sum: function(){
fn(); //=> this -> window 宿主环境:不是执行的环境而是定义的环境,fn虽然是在这执行,但是他是在window下定义的所以他的宿主环境韩式window
}
};
obj.sum();

对于层级桥操的箭头函数

1
2
3
4
5
6
7
8
let fn = function(i){
return function(n){
return n + (++i);
}
}

//=> 改成箭头函数
let fn1 = (i) => (n) => n + (++i);

ES6中创建类的基础语法

ES5中创建类和实例,以及如何禁止用户把类当作普通函数执行: new.target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Person(name, age) {
// console.log(new.target); //=> ES6新增加的语法,如果通过NEW执行,返回的结果是当前创建的实例,如果是当作普通函数执行,返回的是undefined
if(typeof new.target === 'undefined'){
throw new SyntaxError(`当前Person不能作为一个普通函数执行,请使用new Person 来执行~~`);
}

//=> new 执行的时候,this是当前类的实例,this.xxx = xxx是给当前实例增加的私有属性
this.name = name;
this.age = age;
}
//=> 原型上存放的是公有的属性和方法:给创建的实力使用
Person.prototype = {
constructor: Person,
say: function(){
console.log(`my name is ${this.name}, i am ${this.age} years old~`);
}
}
//=> 把person当作一个普通对象,给对象设置的私有属性
Person.study = function(){
console.log('good good study, day day up~');
};
var p1 = new Person('小二', '22');

Person();

ES6中创建类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// console.log(Person); //=> Uncaught ReferenceError Person is not defined 不存在变量提升 
class Person {
constructor(name = '张三', age = '9'){
//=> 给实例设置的私有属性
this.name = name;
this.age = age;
}
//=> 直接再大括号中编写的方法都直接设置在类的原型上,ES6默认把constructor的问题解决了,此时原型上的constructor指向就是Person
say(){
console.log(`my name is ${this.name}, i am ${this.age} years old~`);
}

study(){
console.log('good good study, day day up~');
}
}
let p1 = new Person();
// Person(); //=> Uncaught TypeError: Class constructor Person cannot be invoked without 'new' ES6中使用class创建的类,不允许把创建的类当作普通函数执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Person {
constructor(...arg){
let [x=0,y=0] = arg;
this.x = x;
this.y = y;
}

sum(){
return this.x + this.y;
}
}

class Child extends Person {
//=> 创建Child类,并且让Child类继承了Person类:
1、把Person的私有属性继承过来设置给;额子类的私有属性
2、让字类实例的__proto__上能够找到Person父类的原型(这样子类的实例就可以调用父类的方法)

// constructor(){} //=> Uncaught REeferencerror: Must call super constructor in dervied class before accessing 'this' or returning from derived constructor

//=> 我们不写constructor,浏览器默认会创建它,而且默认就会把父类的属性继承过来(把传给子类的参数值也传给父类了)

//constructor(...arg){ //=> arg:传递给子类的参数(数组)[剩余运算符]
//super(...arg); //=> [展开运算符] 把arg中每一项值展开,分别传递给父类方法 //super(10, 20, 30)
//}
//---------------
//=> 很多时候我们不仅要继承父类私有的,还需要给子类增加一些额外似有的,此时就必须写constructor,但是一定要在constructor中的第一行写上super,否则会报错

constructor(...arg){
super(...arg);
let [,,z] = arg;
this.z = z;
}

fn(){}
}

let c = new Child(10, 20, 30);

jQuerySourceCode

简介

虽然jQuery在近几年已经不再那么火热,伴随着新兴框架VUE/React等框架的崛起,jQuery的作用正在被弱化,传统的jQuery已经被新兴的框架化工程化所代替。但jQuery中的一些优秀的思想、特质,还是非常值得我们学习的。本文主要讲述这些优秀的思想。

jQ的版本和下载

jQuery的版本

1.x:兼容IE6~8浏览器,是目前PC端开发常用的类库

2.x/3.x:不支持IE6~8的兼容,目前市场上应用的特别少(移动端开发一般用zepto.js)

jquery-1.9.3.min.js
jquery-1.11.3.min.js
jquery.min.js

下载

官网下载:http://jquery.com/

Github:https://github.com/jquery/jquery

基于NODE的npm下载jQuery

1
2
3
4
5
6
7
8
//=>查jquery版本信息存放在jquery.version.json文件中
npm view jquery > jquery.version.json

//=> 下载指定版本的jquery
npm install jquery@1.11.3

//=> 下载最新版本
npm install jquery

如何学习jQuery

研究JQ的源码:研究的过程是学习JQ的精华,促进自己的原生JS能力以及插件或者类库的封装能力

看JQ的API手册: http://jquery.cuishifeng.cn/

锋利的JQ第二版:对于JQ的基础知识和实战应用讲解得非常好点击下载.zip

JQ的核心原理解读(分析源代码)

jQuery 是一个常用方法类库,提供了很多真是项目中需要使用的属性和方法(这些方法jQ已经帮我们完善了浏览器兼容处理以及一些细节的优化)

jQuery本身就是一个类(jQ是基于构造函数模式构建的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var jQuery = function(selector, context){
return new jQuery.fn.init( selector, context );
};

jQuery.fn = jQuery.prototype = {
jquery:version,
constructor: jQuery
...
};

...

init = jQuery.fn.init = function( selector, context ) {
if ( typeof selector === "string" ) {
...
} else if ( selector.nodeType ) {
...
} else if ( jQuery.isFunction( selector ) ) {
...
}

return jQuery.makeArray( selector, this ); //=> 返回一个类数组
}

init.prototype=jQuery.fn;

...

window.jQuery=window.$=jQuery;

当我们在js中执行

\$()

jQuery()

都是在创建一个jQ类的实例($===jQuery),这些实例都是一个类数组(我们把这个类数组称之为JQ对象),JQ的实例可以使用JQ原型上提供的共有属性和方法

项目中我们把\$()称之为JQ选择器,因为执行这个方法可以传递一个selector参数进去,通过selector我们可以获取到需要操作的DOM元素集合(JQ类数组集合),传递的第二个参数context它是当前获取元素的上下文(不传递默认是document,如果传递一个JS元素对象即可)

但是把它叫做JQ选择器有点笼统,因为传递的selector支持三种格式

传递的是个字符串,就是我们所谓的选择器,能够通过选择器获取元素

传递的是一个元素对象,它的意思是把JS原生对象转换为JQ对象

传递的是个函数,它代表等DOM结构加载完成在执行对应的JS代码(类似于window.onload)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ === jQuery; // true
$() === jQuery(); // false 不同实例
$() instanceof jQuery; // true
var $example = $(); //=> 我们用JQ选择器获取的值,一般都是用以$开头的变量名来存储(以后看见变量名是以$开头,我们就知道是JQ获取的实例【JQ对象】)

/*
* $example
* xxx:xxx 私有属性
* 0:某一个元素
* ...
* length:类数组长度
* context:document
* selector:传递的选择器内容
* prevObject:当前执行上下文的JQ对象
* __proto__: jQuery.prototype
* [xxx:xxx 共有的属性和方法]
* add
* addClass
* ...
* __proto__:Object.prototype
*/

第一个参数[selector]传递的是一个字符串,就是通过选择器获取到的元素集合(获取的结果都是类类数组集合;获取多个元素也就是索引多点,获取一个元素就只有索引0,一个都没获取到就是一个空的类数组集合【而不是null】)

一般css或者css3中支持的选择器,jq都支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//=> 基本选择器
$('.aa')
$('#aa')
$('div')
$('*')
$('.aa,#bb')

//=> 后代选择器
$('.box a')
$('.box>a')
$('.box~a')
$('.box+a')

//=> 伪类选择器
$('.box:contains(xxx)') //=> 包含xxx内容的
$('a:first')
$('a:last')
$('a:eq(1)') //=> 索引为1的
$('a:gt(1)') //=> 索引大于1
$('a:lt(10)') //=> 索引小于10
$('a:not()') //=> 不包含
$('a:not(:gt(5))')

jQ对象和原生js对象转换

JQ对象:通过$()获取的JQ实例(类数组)
原生JS对象:通过ES中提供的属性或者方法获取的JS元素对象(nodeType === 1)

把原生JS对象转换为JQ对象

1
2
3
4
5
6
var oBox = document.getElementById('box'); //=> 原生JS对象

oBox.addClass(); //=> 报错:oBox不是JQ对象,不能使用JQ原型上的方法

var $box = $(oBox); //=> 把原生JS对象转化为JQ对象
$box.addClass('bg');

把JQ对象转换为原生JS对象

1
2
3
4
var $body = $('body');
$body[0];
$body.get(0); //=> 获取到的是原生JS对象
$body.eq(0); //=> 获取结果还是一个JQ对象

selector是一个方法

1
2
3
4
5
6
7
8
$(function(){
//=> 当页面中的dom结构加载完成就会执行回调函数中的js代码
//=> 类似玉window.onload : 等到页面中的DOM元素以及资源文件都加载完成才会执行对应的js代码
});

$(document).ready(function(){
//=> 这种写法和上面的写法一模一样
});

和window.onload不太一样
1、$(function(){}) 可以在同一个页面使用多次,多次都生效(所以在使用jQuery完成代码的时候,我们一般都会把代码放在回调函数中;首先不仅是等到结构加载完在执行,而且还形成了一个闭包)
原理:利用了DOM二级事件绑定(可执行多次),监听的是DOMContentLoaded时间(DOM结构加载完成就会触发)

2、window.onload 本身就是资源都加载完成才会执行,使用的是DOM零级事件,在同一个页面中只能使用一次

1
2
3
4
window.onload = function(){};
window.onload = function(){};

//=> 只能留最后一个,最后一次赋值替换了原有的赋值

JQ既是一个类也是一个对象

jQuery.prototype上设置了很多的属性和方法,这些是供JQ实例(DOM集合或DOM元素)使用的属性和方法

addClass
css
removeClass
attr

jQuery也是一个普通的对象,在对象上也有一些自己的属性和方法(和实例没有任何关系),这些都是工具类的方法

ajax
isFunction
unique

jQuery.prototype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$('#box').index(); //=> 获取当前元素的索引(是在自己兄弟元素中的索引,它有几个哥哥,索引就是几)

$('body').data(key,value); //=> data方法可以获取到在html结构上设置的data-xxx的自定义属性

$('#box').attr(); //=> 设置/批量修改/获取当前元素的内置/自定义属性 removeAttr

$('#box').prop(); //=> 和attr一样也是操作元素属性的,但是prop一般都是操作表单元素的内置或者自定义属性 removeProp

addClass: 增加样式类
removeClass: 移除样式类
toggleClass: 切换样式类
hasClass: 验证是否存在某个样式类名

$('#box').html([val]); //=> 不传val就是获取内容,传递val就是设置内容,等价于原生的innerHTML
$('input').val([val]); //=> 表单元素value值的操作(设置/获取)

css: 设置或者批量设置元素的样式(获取的结果没有去单位)

offset(); //=> 获取距离body的偏移
position() //=> 获取距离父级参照物的偏移
scrollTop/scrollLeft([val]); //=> 获取/设置当前元素卷去的高度/宽度
height/width([val]);
innerWidth/innerHeight: 等价于clientWidth/clientHieght
outerWidth/outerHeight: 等价于offsetWidth/offsetHieght

$('#box').on('click', function...); //=> jQuery中的事件绑定
...

写在对象上的方法

1
2
3
4
5
var j = $,noConflict(); //=> 如果当前项目中引入了两个类库,都是使用$操作的,为了防止$使用权的冲突,jQ做了一个处理,转让使用权;此处返回的j就是代表原始$的变量,以后可以使用j()执行

var j = $,noConflict(true); //=> 把jQuery和$使用权都转让给j(深度转让)

$.ajax(); 帮助我们放松ajax请求的

筛选方法

filter: 同级过滤

children: 子集过滤

find: 后代过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var $links = $('a');
$links.filter('.bg'); //=> 首先获取所有的a,在所有的a中把具备样式类bg的获取到

$('#box').children('a'); //=> 首先获取id为box的所有子元素,在所有子元素中筛选出标签为a的元素集合

$('#box').find('.bg'); //=> 首先或取代#box后代中所有的元素,在所有的元素中筛选出样式类名具备bg的元素集合 <=> $('#box .bg')

prev: 获取上一个哥哥
prevAll: 所有的哥哥
next: 下一个弟弟
nextAll: 所有的弟弟
siblings: 所有的兄弟
parent: 父元素
parents: 所有的祖先元素(一直到html为止)

each

JQ中的each有两种
1、 写在原型上的each: 遍历JQ对象中的每一项
2、 写在对象上的each: 工具方法,可以用来遍历数组、类书组、对象等
3、 内置的each其实也是调用原型上的each处理的,只不过JQ在处理的时候会内部自己调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$('a').addClass('select'); //=> 我们获取的a可能有很多个,执行一次addClass,相当于给每个获取的a都增加一个叫做select的样式类(JQ中大部分方法在执行的时候,都会把获取的JQ集合中的每一项调用each进行遍历,把需要操作的任务对每一个遍历一边的元素进行操作)

$('a').css('width'); //=> 获取的时候只返回第一个元素的样式(设置走内置的each批量处理,获取之处理第一个)

//=> 原型上的each
$('a').each(function(index, item){
//=> 床底参数的顺序和数组内置的forEach顺序相反,ary.forEach(function(item, index){...});
//=> 获取的a有多少个,回调函数被触发执行多少次;index当前遍历这一项的索引 item是当前遍历这一项的内容
//=> this -> item(原生JS对象)方法中的this是昂钱遍历的这一项
//=> $(this) 也是当前遍历这一项,当时属于JQ对象
})

//=> JQ对象上提供的工具方法: each
$.each([数组/类数组],function(index, item){
//=> this:item
});

$.each([对象],function(key, value){
//=> this:value
//=> jQ也是采用for in 循环来遍历对象的,这样的话就存在吧共有属性和方法遍历到的问题
if([对象].hasOwnProperty(key)){
//=> 私有
}
});

animate

JQ中提供了元素运动的动画库

stop: 结束当前元素正在运行的动画,继续执行下一个新的动画(一般我们实现动画,stop方法基本必然执行)

finish: 和stop类似,finish需要让元素立即运动到上一个动画的目标位置,从目标位置执行下一个动画,而stop是从上一个动画停止的位置执行下一个动画

animate([target],[duration],[effect],[callBack]):
[target] 对象
[duration] 时间
[effect] linear/ease/ease-in/ease-out/ease-in-out
[callBack] 回调函数,动画结束做的事情

快捷动画

show(1000/fast/slow)
hide
toggle

fadeIn
fadeOut
fadeToggle

slideUp
slideDown
slideToggle