JavaScript 复习笔记

什么是 JavaScript ?

  JavaScript(通常缩写为 JS )是一种高级的、解释型的编程语言[5]。JavaScript 是一门基于原型、函数先行的语言[6],是一门多范式的语言,它支持面向对象编程,命令式编程,以及函数式编程。它提供语法来操控文本、数组、日期以及正则表达式等,不支持 I/O ,比如网络、存储和图形等,但这些都可以由它的宿主环境提供支持。它已经由 ECMA(欧洲计算机制造商协会)通过 ECMAScript 实现语言的标准化[5]。它被世界上的绝大多数网站所使用,也被世界主流浏览器( Chrome、IE、Firefox、Safari、Opera )支持。

  虽然 JavaScript 与 Java 这门语言不管是在名字上,或是在语法上都有很多相似性,但这两门编程语言从设计之初就有很大的不同,JavaScript 的语言设计主要受到了 Self(一种基于原型的编程语言)和 Scheme(一门函数式编程语言)的影响[6]。在语法结构上它又与C语言有很多相似(例如 if 条件语句、while 循环、switch 语句、do-while 循环等)[7]。

  在客户端,JavaScript 在传统意义上被实现为一种解释语言,但现在它已经可以被即时编译( JIT )执行。随着最新的 HTML5 和 CSS3 语言标准的推行它还可用于游戏、桌面和移动应用程序的开发和在服务器端网络环境运行,如 Node.js 。

JavaScript 的组成

  一个完整的 JavaScript 应该由 3 部分组成:

  • ECMAScript:核心,描述了该语言的语法和基本对象;
  • DOM:文档对象模型,描述处理网页内容的方法和接口
  • BOM:浏览器对象模型,描述与浏览器进行交互的方法和接口

ECMASctipt

  ECMAScript 是一种由 Ecma 国际(前身为欧洲计算机制造商协会)通过 ECMA-262 标准化的脚本程序设计语言。 这种语言在万维网上应用广泛,它往往被称为 JavaScript 或 JScript ,但实际上后两者是 ECMA-262 标准的实现和扩展。
  ECMA-262 规定了 ECMASctipt 这门语言的下列组成成分:

  • 语法
  • 类型
  • 语句
  • 关键字
  • 保留字
  • 操作符
  • 对象

:ECMAScript 和 JavaScript 到底是什么关系?
:前者是后者的规格,后者是前者的一种实现。日常场合,这两个词是可以互换的。

DOM

什么是 DOM ?

  DOM(Document Object Model),即文档对象模型,是针对 XML 但经过扩展用于 HTML 的 API
  DOM 把整个页面映射为一个多层节点结构。
  HTML 或 XML 页面中的每个组成部分都是某种类型的节点,这些节点又包含着不同类型的数据。如下面的 HTML 页面:

1
2
3
4
5
6
7
8
<html>
<head>
<title>Hello World</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>

  在 DOM 中,这个页面可以表示成下面的分层节点图:

1
2
3
4
5
6
7
8
9
html
├── head
│ └── title
│ └── Hello World


└── body
└── h1
└── Hello World

  通过 DOM 创建的这个表示文档的树形图,让开发人员获得了控制页面内容和结构的主动权,借助 DOM 提供的 API ,开发人员可以轻松自动地添加、删除、替换或修改任何节点
  举个例子,若对上面的 HTML 页面添加如下内容:

1
2
3
4
<script>
var h1 = document.getElementsByTagName("h1")[0];
h1.innerHTML = "Hi!guys";
</script>

  则网页h1标签的内容由Hello World变为Hi!guys,达到了修改节点内容的目的。

DOM 的级别

  DOM 可以分为 3 个级别:DOM 1、DOM 2、DOM 3.
  DOM 1 级由两个模块组成:DOM Core(核心)和 DOM HTML 。
  DOM Core 规定的是如何映射基于 XML 的文档结构,以便简化对文档中任意部分的访问和操作;而 DOM HTML 则在 Core 的基础上加以扩展,添加了针对 HTML 的对象和方法
  DOM 2 级不仅让 DOM 1 级中的 Core 模块扩展开始支持 XML 命名空间,还在原基础上扩充了鼠标和用户界面事件、范围、遍历等细分模块:

  • DOM Views(视图):定义了跟踪不同文档视图的接口;
  • DOM Events(事件):定义了事件和事件处理的接口;
  • DOM Style(样式):定义了基于CSS为元素应用样式的接口;
  • DOM Traversal and Range(遍历和范围):定义了遍历和操作文档树的接口。

  DOM 3 级则再次扩展引入了以统一方式加载和保存文档的方法,还新增了验证文档的方法。其也对 DOM Core 模块进行了扩展,使其开始支持 XML 1.0 规范。

BOM

  BOM(Browser Object Model),浏览器对象模型。开发人员使用 BOM 可以控制浏览器显示的页面以外的部分。
  从根本上讲,BOM 只处理浏览器窗口和框架,但人们习惯上也把针对浏览器的JavaScript 扩展算作 BOM 的一部分,如以下扩展:

  • 弹出新浏览器窗口的功能;
  • 移动、缩放和关闭浏览器窗口的功能;
  • 提供浏览器详细信息的navigator对象;
  • 提供浏览器所加载页面的详细信息的location对象;
  • 提供用户显示器分辨率详细信息的screen对象;
  • cookies的支持
  • XMLHTTPRequest和IE的ActiveXObject这样的自定义对象。

在 HTML 中使用 JavaScript

<script> 元素

  我们使用<script>元素向 HTML 中插入 JavaScript,该元素常见属性:

  • type:可选,表示脚本语言的内容类型,默认为text/javascript,一般不写;
  • src:可选,常用。表示引入要执行的外部脚本文件;
  • async:可选,异步,表示立即下载该脚本,不妨碍页面的其他操作。只对外部脚本文件有效。
  • defer:可选,表示脚本可以延迟到文档完全被解析和显示之后在执行。只对外部脚本文件有效

注意哦:一般认为使用外部脚本文件更好,其具有如下优点:

  • 可维护性更好
  • 可缓存

标签的位置抉择

  按惯例<script>元素放该在<head>元素中,但由于浏览器在遇到<body>标签才开始呈现内容,使得浏览器呈现页面时出现明显的延迟,一般将<script>元素放在<body>元素的后面,结束标签</body>的前面。

JavaScript 的基本概念

语法

  ECMAScript 的语法大量借鉴了 C 及其他类 C 语言(如 Java )的语法。
  比如区分大小写,标识符的定义,注释,这都和 Java 一样,但部分(如分号和关键字)却有所不同。

变量

  ECMAScript 的变量是弱类型的语言,即内存中可以保存任何类型的数据。而 Java 这种强类型语言,一般定义的类型确定下来,如定义了int a,则这块内存只能定义int类型的变量了,不能放入其他类型了。

  ECMAScript 早期版本中使用var来定义一个变量(如 var a
  ECMAScript 新版本中则推荐使用let来代替var,它声明了一个块级作用域的本地变量,并且可选的将其初始化为一个值。

  因此,使用let后,局部变量和全局变量变的非常容易区分,同 Java 一样了。

常量

  ECMAScript 的常量使用const来定义,其定义的变量不能改变,以保证数据的安全性,如:

1
2
3
const num = 100;

alert(num++);// 报错

注意哦:在同一作用域中,常量不能和其他变量或函数名重名,否则报错。

建议:在开发中,优先使用const,只有需要改变某一个标识符的时候采用let

执行环境及作用域

什么是执行环境?

  执行环境(简称环境)是 JavaScript 中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。
  全局执行环境是最外围的一个执行环境。在 Web 浏览器中,全局环境被认为是window对象,因此所有全局变量和函数都是最为window对象的属性和方法创建的。某个环境中的所有代码执行完毕后,该环境即被销毁,保存在其中的所有变量和函数定义也随之销毁。

注意哦:全局执行环境直到应用程序退出——例如关闭网页或浏览器时才会被销毁。

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

什么是作用域链?

  当代码在一个环境中执行时,会创建变量对象的一个作用域链,其保证了对执行环境有权访问的所有变量和函数的有序访问
  作用域链的前端始终都是当前执行的代码所在环境的变量对象。若这个环境是函数,则将其活动对象作为变量对象。活动对象最开始只包含一个变量,即arguments对象(该对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含的外部环境,而在下一个变量对象则来自于下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量始终都是作用域链中的最后一个对象。
  标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端然后逐级地向后回溯,直至找到标识符为止(找不到通常发生错误)。
  示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
let color = "red";
function changeColor(){
if(color == "red"){
color = "blue"
}else{
color = "red";
}
}
changeColor();

alert(color);

  上面函数的作用域链包含两个对象:它自己的变量对象(arguments)和全局环境的变量对象。可以在函数内部访问变量color,就是因为能在这个作用域链中找到它。

老版本没有块级作用域

  在Java中,由{}封闭的代码块都有自己的作用域(若用 ECMAScript 的话来讲,就是它们自己的执行环境)。但在 JavaScript 中不是这样的,举个栗子:

1
2
3
4
if(true){
var color = "red";
}
alert(color);// red

  若是 Java 中,color这个变量会在if语句执行完销毁。但在 JavaScript 中,if 语句中的变量声明会将其添加到当前的执行环境(这里是全局环境)。所以在使用for语句时出现这种结果,如:

1
2
3
4
for(var i = 0; i < 10; i ++){

}
alert(i);// 10

新版本的块级作用域

  为了改正这点,ECMAScript 6 版本中使用let代替var来声明变量,它声明了一个块级作用域的本地变量,并且可选的将其初始化为一个值。

1
2
3
4
for(let i = 0; i < 10; i ++){

}
alert(i);// 报错 i is not defined

数据类型

  JavaScript 包括 6 种数据类型,分为 5 种基本数据和引用类型,包括:

  • underfined:只有一个值,即underfined,声明变量时未初始化时即为此值;
  • null:只有一个值,即null,表示空对象指针。若定义变量用于保存对象,最好初始化为此值;
  • boolean:表示布尔型,只有truefalse,和数字不是一回事。
  • number:表示整数或浮点数的数值;
  • string:表示不可变字符串,使用单引号或双引号修饰均可。
  • object:引用类型,表示对象。

  object引用类型的每个实例都有下面属性和方法:

  • Constructor:保存着用于创建当前对象的函数;
  • hasOwnProperty(propertyName):检查给定属性在当前对象实例中是否存在,参数必须为字符串;
  • isPrototypeOf(object):检查传入的对象是否为另一个对象的原型;
  • propertyIsEnumerable(propertyName):检查给定的属性是否能够使用for-in语句来枚举
  • toLocaleString():返回对象的字符串表示,与当前执行环境的地区对应;
  • toString()(较常用):返回对象的字符串表示;
  • valueOf()(较常用):返回对象的字符串、数值或布尔值表示。通常和toString()方法的返回值相同。

注意哦:ECMAScript 中会使用typeof操作符来检测定义变量的数据类型,如判断某个变量是否为数值类型:typeof a == "number";

引用类型

  引用类型包括:

  • Object类型;
  • Array类型;
  • Date类型;
  • RegExp类型;
  • Function类型;
  • 基本包装类型,包括:
    • Boolean类型;
    • Number类型;
    • String类型;
  • 单体内置对象,包括
    • Global对象;
    • Math对象;

Object 类型

什么是 Object 类型?

  Object类型是一个基础类型,其他所有类型都从Object继承了基本的行为。大多数引用类型都是Object类型的实例,其使用最多。虽然其实例不具备多少功能,但仅在应用程序中存储和传输数据,它是非常理想的选择。

如何创建 Object 实例?

  创建Object实例的方式有两种。
  第一种是使用new操作符后跟Object的构造函数,类似 Java:

1
2
3
4
let person = new Object();
// 设置属性
person.name = "wangwu";
person.age = 18;

  第二种也是较为常用的方式是使用对象字面量表示法,如:

1
2
3
4
let person = {
name:"wangwu",
age:18
};

注意哦let person = new Object();let person ={};等效

Array 类型

什么是 Array 类型?

  Array类型代表有序数组,不过和其他语言不同的是,ECMAScript 数组的每一项的可以保存任何数据的数据,比如该数组可以在第一个位置保存字符串,第二个位置保存数值,第三个位置保存对象,依次类推。而且,其数组大小是可以动态调整的,即可以随着数据的增加自动增长以容纳新数据。

如何创建 Array 实例?

  创建数组的方式也有两种。
  第一种是使用构造函数:

1
2
3
4
5
6
7
let colors = new Array();
// 若预先知道数组要保存的数量,可以传递数量进数组,比如20
let colors = new Array(20);
// 若传递其他类型的参数,如字符串,则创建包含那个值的仅一项的数组
let names = new Array("wangwu");
// 使用构造函数时还可以省略操作符,如
let colors = Array(20);

  第二种常见数组的方式是使用数组字面量表示法

1
2
let names = [];// 创建一个空数组
let colors = ["red","green","blue"];

  访问数组时使用索引即可,和 Java 类似,如colors[0]的值为red
  数组的长度保存在其length属性中,比较特殊的是,通过设置此属性,可以从数组的末尾移除元素或向数组中添加新元素:

1
2
3
4
5
6
7
8
// 移除元素
let colors = ["red","green","blue"];
colors.length = 2;
alert(colors[2]);// undefined
// 增加元素
let colors = ["red","green","blue"];
colors[colors.length] = "black";// 在索引 3 处添加一种颜色
colors[colors.length] = "white";// 在索引 4 处添加一种颜色

Array 类型 API(了解)

  Array 类型包括:

  • 转换方法;
  • 栈方法(push()pop());
  • 队列方法(push()shift()pop());
  • 重排序方法(reverse()sort());
  • 创建删除方法;
  • 查看位置方法;
  • 迭代方法;

Date 类型

  ECMAScprit 的 Date 类型是在早期 Java 中的java.util.Date类基础上构建的,代表时间。
  创建一个日期对象,这么做:

1
let now = new Date();

  无构造参数的情况下,上面代码自动获得当前日期和时间。
  若想根据特定的日期和时间创建日期函数,必须传入表示该日期的毫秒数。为了简化这一计算过程,提供了两个方法:Date.parse()Date.UTC()
  Date.parse()方法接收一个表示日期的字符串参数,然后根据此字符串返回相应日期的毫秒数,如:

1
2
3
let now = new Date(Date.parse("May 25, 2004"));
//与上面等价
let now = new Date("May 25, 2004");

  其他方法常用方法还有两个:

  • toLocaleString():返回当前 Date 对象对应的时间本地字符串格式,如2012/12/12 上午12:12:12
  • getTime():返回当前 Date 对象对应时间到 1970 年 1 月 1 日 0 点的毫秒值差

RegExp 类型

什么是 RegExp 类型?

  ECMAScript 通过 RegExp 类型来支持正则表达式

如何创建 RegExp 实例?

  以下是创建该实例的 2 种方式:

1
2
3
4
5
// 匹配 4~12 位的数字和字母组合
// 第一种方式(需转义)
let r = new RegExp("^\\w{4,12}$");
// 第二种方式(常用)
let reg = /^\w{4,12}$/;

Function 类型

  ECMAScript 的函数实际上是对象,每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。后面将详细介绍函数。

基本包装类型

  基本包装类型及其对应的基本数据类型:

  • Boolean->boolean
  • Number->number
  • String->string

  String类型的方法较多,此处不做介绍,但其实和 Java 差不多哦!

单体内置对象

  你可能比较好奇:什么是内置对象?
  内置对象指的是:由 ECMAScript 实现提供的、不依赖宿主环境的对象,这些对象在 ECMAScript 程序执行之前就已经存在了
  换而言之,不必去显示地实例化内置对象,因为它们已经实例化了。
  ObjectArrayString就属于内置对象。
  而单体内置对象有2个:GlobalMath

Global 对象
什么是 Global 对象?

  该对象可以说是最特别的一个对象,因为它是不存在的。它是一个终极的对象,这代表着:不属于任何其他对象的属性和方法,最终都是它的属性和方法

Global 对象和 window 对象的关系

  虽然 ECMAScript 没有之处如何直接访问Global对象,但 Web 浏览器都是将该全局对象作为window对象的一部分加以实现的。因此,在全局作用域中声明的所有变量和函数,都成了window对象的属性。

Math 对象

  该对象提供和数学计算相关的方法,和 Java 的Math类差不多。

垃圾回收

  JavaScript 具有自动垃圾收集机制。

操作符

  和 Java 中类似,故省略。

语句

  大部分和 Java 类似,但 ECMAScript 新增了几种语句,如for-in语句,with语句,start语句。
  for-in语句是一种精准的迭代语句,可以用来枚举对象的属性,如:

1
2
3
for (let product in products){
document.write(product);
}

函数

函数声明

  ECMAScript 中使用function关键字来声明函数,后跟函数名、一组参数和函数体,语法如下:

1
function functionName(arg0,arg1,arg2...){}

  通过函数名及附加的参数来调用该函数。

注意哦:ECMAScript 中函数的参数不在意传递进来的参数有多少个,也不在意参数是什么类型。也就是说,即使你定义了两个参数,也可以传递一个参数或三个参数甚至更多。这是因为ECMAScript 中的参数内部使用一个数组来表示的,函数接收到的始终是这个数组。我们在函数体中可以通过arguments[0]来获得第一个参数,其他参数获取方法类似。

注意哦:ECMAScript 中的函数没有重载

注意哦:ECMAScript 中函数的参数,也是按值传递的。

函数表达式

  前面介绍了如何声明一个函数,其有一个重要特征就是函数声明提升,即代码执行之前会先读取函数声明,所以可以把函数声明放在调用它的语句后面,比如:

1
2
3
4
helloWorld("Hello World");
function helloWorld(str){
alert(str);
};

  现在介绍第二种常见函数的方式:使用函数表达式,比如:

1
2
3
let hw = function (str){
alert(str);
};

  这种情况下创建的函数叫做匿名函数,没有函数名,所以使用前必须赋值:

1
2
3
4
5
6
7
8
9
10
11
//下面是错误的写法,函数还不存在
hw("Hello World");
let hw = function (str){
alert(str);
};

//下面是正确的写法
let hw = function (str){
alert(str);
};
hw("Hello World");

  理解函数提升的关键在于理解函数声明与函数表达式之间的关系,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//无效的语法
if(condition){
function sayHi(){
alert("Hi!");
}
}else{
function sayHi(){
alert("Good!");
}
}
//正确的语法
let sayHi;
if(condition){
sayHi = function(){
alert("Hi!");
}
}else{
sayHi = function(){
alert("Good!");
}
}

  只有创建函数再赋值给变量后,才能把函数作为其他函数的值返回。

闭包

  闭包是指一个内部函数有权访问另一个函数作用域中的变量的函数。
  创建闭包的常见方式,就是在一个函数内部创建另一个函数,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Compare(name){
return function(obj1,obj2){
let value1 = obj1[name];//
let value2 = obj2[name];//

if(value1 < value2){
return -1;
}else if(value1 > value2){
return 1;
}else{
return 0;
}
};
}

BOM 再介绍

  BOM 提供了很多对象用于访问浏览器的功能,这些功能与任何网页内容无关,它们包括:

  • window对象:表示浏览器的一个实例,是一个Global对象,比较重要。
  • location对象:提供了与当前窗口中加载的文档有关的信息,还提供了一些导航功能,为window对象的一个属性;
  • navigator对象:通常用于检测网页的浏览器类型;
  • screen对象:表明客户端的能力,包括浏览器窗口外部的显示器的信息(如高宽)
  • history对象:保存着用户上网的历史记录

  下面介绍下比较重要的两个对象:window对象、location对象。

window 对象

全局作用域

  该对象既是通过 JavaScript 访问浏览器窗口的一个接口,又是 ECMAScript 规定的Global对象,因此所有在全局作用域中声明的变量、函数都会变为window对象的属性和方法,如:

1
2
3
4
5
6
7
let age = 18;
function getAge(){
alert(this.age);
}
alert(window.age);//18
getAge();//18
window.getAge();//18

窗口相关

  window对象还可以设置窗口位置和大小。

导航和打开窗口

  window对象的window.open()方法可以导航到一个特定的 URL 或打开一个新的浏览器窗口,该方法比较重要。

超时调用和间歇调用

  JavaScript 是单线程语言,但它运行通过设置超时值或间歇时间值来调度代码在特定的时刻执行。前者是在指定的时间过后执行代码,而后者则是每隔指定的时间就执行一个代码。

超时调用:setTimeout

  超时调用使用window对象的setTimeout方法,该方法接受两个参数:第一个参数是要执行的代码或函数,第二个参数是以毫秒表示的等待时间。如:

1
2
3
4
5
6
7
8
9
10
11
//不推荐传递字符串可能导致性能损失
setTimeout("alert('hi')", 3000);
//建议的调用方式
setTimeout(function () {
alert("hi");
}, 1000);
//一般这么写更好
function hi() {
alert("hi");
}
setTimeout(hi, 1000);

  调用setTimeout方法后会返回一个数值ID,表示超时调用,可以通过该ID来取消超时调用,使用clearTimeout()方法即可如:

1
2
3
4
5
let timeout = setTimeout(function () {
alert("hi");
}, 1000);

clearTimeout(timeout);

  只要在指定的时间尚未过去之前调用该方法,就可以完全取消超时调用

间歇调用:setInterval

  间歇调用与超时调用类似,只不过它会按照指定的时间间隔重复执行代码,直到间歇调用被取消或页面关闭。设置间歇调用的方法是setInterval(),接受参数和setTimeout()相同,取消间歇调用的方法为clearInterval()方法。下面为一个例子:

1
2
3
4
5
6
7
8
9
10
let i = 0;
let interval = setInterval(function () {
i++;
//i=3,则取消间歇调用函数,函数执行完本次不再调用
if (i == 3){
clearInterval(interval);
}else{
alert(i);
}
},2000);

两者对比

  间歇调用也可用超时调用实现,如上面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
let i = 0;
function add() {
i++;
//i<3时才执行超时调用
if ((i < 3)) {
setTimeout(add, 2000);
} else {
alert(i);
}
}

setTimeout(add, 2000);

  在使用超时调用时,没有必要跟踪超时调用 ID,因为每次执行代码之后,若不再设置另一次超时调用,调用就会自行停止。
  一般认为,使用超时调用来模拟间歇调用时一种最佳模式
  间歇调用很少使用,原因是后一个间歇调用可能会在前一个间歇结束之前启动,而超时调用则完全可以避免这一点。所以,最好不要使用间歇调用。

系统对话框

  浏览器通过alert()confirm()方法可以调用系统对话框向用户显示消息。因为这几个方法打开的对话框是同步和模态的,所以显示这些对话框时JS代码会停止执行,关掉后代码又恢复执行。

location 对象

  location是最有用的 BOM 对象之一,它不仅保存了与当前窗口中加载的文档有关的信息,还提供了一些导航功能(如将 URL 解析为独立的片段)。该对象很特别,既是window对象的属性,也是document对象的属性;换而言之,window.locationdocument.location引用的是同一个对象。下面其为常见的属性:

  • hostname:返回不带端口号的服务器名称,如www.google.com
  • href:返回当前加载页面完整的 URL ,location对象的toString()方法也返回此值,如https//www.google.com
  • search:返回 URL 的查询字符串,以问号开头,如?id=1

DOM 中的事件

  JavaScript 与 HTML 之间的交互式通过事件实现的。
  事件,就是文档或浏览器窗口中发生的一些特定的交互瞬间。

事件流

  事件流描述的是从页面中接收事件的顺序。可以分为事件冒泡流事件捕获流

事件冒泡

  事件冒泡指事件开始时由最具体的元素(文档中嵌套层次最深的节点)接收,然后逐级向上传播到较为不具体的节点,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件冒泡例子</title>
<script>
function btn() {
alert("被点击了");
}
</script>
</head>
<body>
<div onclick="btn()">点击我</div>
</body>
</html>

  若你点击了页面中的<div>元素,那么这个onclick事件会按如下顺序传播:

  • <div>
  • <body>
  • <html>
  • document

  也就是说,onclick事件首先在<div>元素上发生,这个元素就是我们单机的元素。然后,click事件沿DOM树向上传播,在每一级节点上都会发生,直到传播到document对象。过程如下图:

事件捕获

  事件捕获刚好与其相反,其按下面顺序执行:

  • document
  • <html>
  • <body>
  • <div>

DOM 事件流

  一般而言,页面的事件都为 DOM 事件流,其包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。对上面的 HTML 页面过程如下:

事件处理程序

  事件时用户或浏览器自身执行的某种动作,如clickloadmouseover等都是事件的名字,而响应某个事件的函数就是事件处理程序
  事件处理程序的名字以on开头,因此click事件的事件处理程序就是onclick,为事件指定处理程序的方式有好几种:

  • HTML 事件处理程序:在 HTML 元素指定事件元素如onclick
  • DOM 0 级事件处理程序:通过 JavaScript 方式将一个函数赋值给一个事件处理程序属性,如

    1
    2
    3
    4
    let btn = document.getElementById("btn");
    btn.onclick = function(){
    alert("click");
    };
  • DOM 2 级事件处理程序:定义了两个方法用于处理指定和删除事件处理程序的操作

  • 其他

事件对象

  在触发 DOM 上的某个事件时,会产生一个事件对象event,这个对象中包含着所有与事件有关的信息,如导致事件的元素、事件的类型及其他与特定事件相关的信息(如鼠标操作导致事件中鼠标位置的信息,键盘操作导致事件中按键的信息)
  无论指定事件处理程序时使用什么方法( DOM 0 级或 DOM 2 级),都会传入事件对象event,如:

1
2
3
4
let btn = document.getElementById("btn");
btn.onclick = function(event){
alert(event.type);
};

  eventtype属性表明事件类型,event包含以下(常见)属性:

属性/方法 类型 说明
bubbles Boolean 表明事件是否冒泡
defaultPrevented Boolean 为true表示已经调用了preventDefault()
detail Integer 与事件相关的细节信息
preventDefault() Function 取消事件的默认行为
stopPropagation() Function 取消事件的进一步捕获或冒泡
target Element 事件的目标
type String 被触发的事件类型
view AbstractView 与事件关联的抽象视图

事件类型

  不同的事件具有不同的信息,下面为一些常见的事件:

  • UI 事件
  • 焦点事件
  • 鼠标事件
  • 滚轮事件
  • 文本事件
  • 键盘事件
  • 合成事件
  • 触摸与手势事件

JSON

  JSON 是一种非常常用的轻量级的数据格式,可以简化表示复杂数据结构的工作量。

语法

  JSON 的语法可以表示以下3种类型的值:

  • 简单值:如字符串、数值、布尔值、null
  • 对象
  • 数组

  JSON 表示对象,如:

1
2
3
4
{
"name":"wangwu",
"age":18
}

  更复杂的对象:

1
2
3
4
5
6
7
8
{
"name":"wangwu",
"age":18,
"school":{
"name":"HK",
"location":"Earth"
}
}

  JSON 表示数组,如:

1
[1,3,87]

  JSON 还可以表示数组和对象的复合体:

1
2
3
4
5
{
"name":"网站",
"num":3,
"sites":[ "Google", "bwg", "Taobao" ]
}

解析与序列化

  JSON 之所以流行,是因为 JSON 的数据结构可以被解析为 JavaScript 对象,Java 对象甚至其他语言相关的对象。
  在 JavaScript ,序列化与解析的方法如下:

  • stringify():将对象序列化为 JSON 字符串
  • parse():将 JSON 字符串解析为原生的 JavaScript

Ajax

  Ajax 技术,是对 Asynchronous JavaScript + XML 的简写,异步请求,它能够向服务器请求额外的数据而无需卸载页面,能带来更好的用户体验。

ECMAScript 6

  ECMAScript 6 是 2015 年 6 月发布的 ECMAScript 新版本,提供了许多新的功能特点,比如前面提到的let命令带来的块级作用域,const命令带来的常量声明。不仅如此,ECMAScript 6 还带来了解析表达式,字符串、正则、数值、函数、数组、对象的扩展,SetMap数据结构,新的函数等等。

解构

什么是解构?

  ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。

解构数组

  以前,为变量赋值,只能直接指定值:

1
2
3
let a = 1;
let b = 2;
let c = 3;

  ES6 允许写成下面这样。

1
let [a, b, c] = [1, 2, 3];

  本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

  若解构不成功,变量的值就等于undefined

1
2
3
4
let [foo] = [];
let [bar, foo] = [1];

foo// undefined

  若等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错:

1
2
3
4
5
6
7
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

解构对象

  解构不仅可以用于数组,还可以用于对象。

1
2
3
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

  对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

1
2
3
4
5
6
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined

  对象的解构还能赋值给新的变量,如:

1
2
3
let p = {name:"jack",age:18};
let {name:n} = p;
n // jack

解构字符串

  字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象:

1
2
3
4
5
6
const [a, b, c, d, e] = "hello";
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

  类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值:

1
2
let {length : len} = "hello";
len // 5

  其他如解构数值和布尔值、解构函数略

函数优化

  ES 6 中对函数的优化,使得其有类似于 Java 的 Lambda 表达式一样的简写方式,如:

1
2
3
4
5
function sum(a,b) {
return a + b;
}
// 函数优化
const sum = (a, b) => a + b;

模块导入导出

  ES 6 标准增加了 JavaScript 语言层面的模块体系定义。
  ES 6 模块的设计思想是尽量静态化,在编译时就能确定模块的依赖关系,以及输入和输出相关变量函数(而CommonsJS 和 AMD 模块都只能在运行时确定这些东西)。
  下面为 Vue 中使用 ES 模块化的代码示例:

  目录结构如下:

1
2
3
4
├── components
│   └── HelloWorld.vue
├── App.vue
└── main.js

  HelloWorld.vue文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div class="hello">
...
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>

  App.vue文件:

1
2
3
4
5
6
7
8
import HelloWorld from './components/HelloWorld'

export default {
name: 'app',
components: {
HelloWorld
}
}

  main.js文件:

1
2
3
4
5
6
7
8
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
render: h => h(App),
}).$mount('#app')

Promise

  Promise 是异步编程的一种解决方案。
  什么时候我们会用来处理异步事件呢?
  常见应用是异步处理网络请求。
  对于简单的网络请求,并不需要使用到 Promise。但当网络请求非常复杂时,就会出现回调地狱。
  举个栗子(略夸张):

  • 首先需要通过 URL 1 从服务器加载一些数据 data 1,其包涵了下一个请求的 URL 2
  • 其次需要通过 URL 2 从服务器加载一些数据 data 2,其包涵了下一个请求的 URL 3
  • 之后需要通过 URL 3 从服务器加载一些数据 data 3,其包涵了下一个请求的 URL 4
  • 最后发送网络请求 URL 4,获取最终的数据 data 4

  正常情况下,对于上述栗子的代码,不会出现什么问题。
  但是,这样的代码难看且不易维护,而且,若在加载数据时夹杂着进一步的操作,则更加难以维护

  因此,我们可以使用一种更优雅的方式来进行这种异步操作,那就是 Promise 啦!

Promise 的基本使用

  直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
new Promise((resolve, reject) => {
$.ajax({
success: resolve()
error: reject()
})
}).then(() => {
// 具体成功操作
return new Promise((resolve, reject) =>{
$.ajax({
success: resolve()
error: reject()
}).then(() => {
// 具体成功操作
...
}).catch(() => {
// 失败的操作
})
})
}).catch(() => {
// 失败的操作
})

参考

0%