HTML5新特性

语义标签

标签描述
<header>定义了文档的头部区域
<footer>定义了文档的尾部区域
<nav>定义文档的导航
<section>定义文档中的节
<article>定义文章
<aside>定义页面以外的内容
<details>定义用户可以看到或者隐藏的额外细节
<summary>标签包含details元素的标题
<dialog>定义对话框
<figure>定义自包含内容,如图表
<main>定义文档主内容
<mark>定义文档的主内容
<time>定义日期/时间

增强表单

html5修改一些新的input输入特性,改善更好的输入控制和验证

输入类型描述
color主要用于选取颜色
date选取日期
datetime选取日期(UTC时间)
datetime-local选取日期(无时区)
month选择一个月份
week选择周和年
time选择一个时间
email包含e-mail地址的输入域
number数值的输入域
urlurl地址的输入域
tel定义输入电话号码和字段
search用于搜索域
range一个范围内数字值的输入域

html5新增了五个表单元素

<datalist>用户会在他们输入数据时看到域定义选项的下拉列表
<progress>进度条,展示连接/下载进度
<meter>刻度值,用于某些计量,例如温度、重量等
<keygen>提供一种验证用户的可靠方法生成一个公钥和私钥
<output>用于不同类型的输出比如尖酸或脚本输出

html5新增表单属性

属性描述
placehoder输入框默认提示文字
required要求输入的内容是否可为空
pattern描述一个正则表达式验证输入的值
min/max设置元素最小/最大值
step为输入域规定合法的数字间隔
height/wdith用于image类型<input>标签图像高度/宽度
autofocus规定在页面加载时,域自动获得焦点
multiple规定<input>元素中可选择多个值

视频和音频

视频播放:

1
<video src=""><video>

查看视频的所有属性、方法、事件:

1
console.log(videoBirds);

音频播放:

1
<audio src=""></audio>

查看视频的所有属性、方法、事件:

1
console.log(bgMusic);

Canvas绘图

SVG绘图

什么是SVG?

SVG指可伸缩矢量图形

SVG用于定义用于网络的基于矢量的图形

SVG使用XML格式定义图形

SVG图像在放大或改变尺寸的情况下其图形质量不会有损失

SVG是万维网联盟的标准

SVG的优势

与其他图像格式相比,是哟个SVG的优势在于:

  • SVG图像可通过文本编译器来创建和修改

  • SVG图像可被搜索、索引、脚本化或压缩

  • SVG是可伸缩的

  • SVG图像可在任何的分辨率下被高质量的打印

  • SVG可在图像质量不下降的情况下被放大

SVG与Canvas区别

  • SVG适用于描述XML中的2D图形的语言

  • Canvas随时随地绘制2D图形(使用javaScript)

  • SVG是基于XML的,意味这可以操作DOM,渲染速度较慢

  • 在SVG中每个形状都被当做是一个对象,如果SVG发生改变,页面就会发生重绘

  • Canvas是一像素一像素地渲染,如果改变某一个位置,整个画布会重绘。

CanvasSVG
依赖分辨率不依赖分辨率
不支持事件处理器支持事件处理器
能够以.png或.jpg格式保存结果图像复杂度会减慢搞渲染速度
文字呈现功能比较简单适合大型渲染区域的应用程序
最合适图像密集的游戏不适合游戏应用

地理定位

使用getCurrentPosition()方法来获取用户的位置。以实现“LBS服务”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var x = document.getElementById("demo");

function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
} else {
x.innerHTML = "Geolocation is not supported by this browser.";
}
}

function showPosition(position) {
x.innerHTML = "Latitude: " + position.coords.latitude +
"<br />Longitude: " + position.coords.longitude;
}

拖放API

拖放是一种常见的特性,即捉取对象以后拖到另一个位置。在html5中,拖放是标准的一部分,任何元素都能够拖放。

1
2
3
4
5
6
7
<div draggable="true" ondragstart="drag(event)"></div>
<script>
function drap(ev){
console.log(ev);
}
</script>

拖动生命周期属性名描述
拖动开始ondragstart在拖动操作开始时执行脚本
拖动过程中ondrag只要脚本在被拖动就运行脚本
拖动过程中ondragenter当元素被拖动到一个合法的防止目标时,执行脚本
拖动过程中ondragover只要元素正在合法的防止目标上拖动时,就执行脚本
拖动过程中ondragleave当元素离开合法的防止目标时
拖动结束ondrop将被拖动元素放在目标元素内时运行脚本
拖动结束ondragend在拖动操作结束时运行脚本

Web Worker

Web Worker可以通过加载一个脚本文件,进而创建一个独立工作的线程,在主线程之外运行。

基本使用:

Web Worker的基本原理就是在当前javascript的主线程中,使用Worker类加载一个javascript文件来开辟一个新的线程,

起到互不阻塞执行的效果,并且提供主线程和新县城之间数据交换的接口:postMessage、onmessage。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<script type="text/javascript">
//WEB页主线程
var worker =new Worker("worker.js"); //创建一个Worker对象并向它传递将在新线程中执行的脚本的URL
worker.postMessage("hello world"); //向worker发送数据
worker.onmessage =function(evt){ //接收worker传过来的数据函数
console.log(evt.data); //输出worker发送来的数据
}
</script>
</head>
<body></body>
</html>

Web Storage

WebStorage是HTML新增的本地存储解决方案之一,但并不是取代cookie而指定的标准,cookie作为HTTP协议的一部分用来处理客户端和服务器的通信是不可或缺的,session正式依赖与实现的客户端状态保持。WebSorage的意图在于解决本来不应该cookie做,却不得不用cookie的本地存储。

websorage拥有5M的存储容量,而cookie却只有4K,这是完全不能比的。

客户端存储数据有两个对象,其用法基本是一致。

localStorage:没有时间限制的数据存储

sessionStorage:在浏览器关闭的时候就会清除。

1
2
3
4
5
localStorage.setItem(key,value);//保存数据
let value = localStorage.getItem(key);//读取数据
localStorage.removeItem(key);//删除单个数据
localStorage.clear();//删除所有数据
let key = localStorage.key(index);//得到某个索引的值

WebSocket

WebSocket协议为web应用程序客户端和服务端之间提供了一种全双工通信机制。

特点:

(1)握手阶段采用HTTP协议,默认端口是80和443

(2)建立在TCP协议基础之上,和http协议同属于应用层

(3)可以发送文本,也可以发送二进制数据。

(4)没有同源限制,客户端可以与任意服务器通信。

(5)协议标识符是ws(如果加密,为wss),如ws://localhost:8023

弹性盒子常用属性

display:flex; *如果用了弹性布局,子元素不需要浮动*

justify-content: 子元素水平排列方式方式。

属性描述
center水平居中
space-between两端对齐
space-around手拉手平均分
flex-start居左对齐
flex-end居右对齐

align-items:   子元素垂直排列

属性描述
center垂直居中
space-between上下两端对齐
space-around手拉手上下平均分
flex-start顶部对齐
flex-end底部对齐

flex-direction:   排列方式

属性描述
row默认值 横向排列
row-reverse横向颠倒排列
column垂直排列
column-reverse垂直颠倒排列

flex-wrap:    默认no-wrap让元素步换行

属性描述
wrap换行
nowrap不换行

align-content:        控制容器内多行在Y轴上的排列方式

属性描述
stretch默认值
center居中对齐
flex-start顶部对齐
flex-end底部对齐
space-between上下对齐
space-around上下手拉手对齐

子级

flex:1     1指的是一个系数 *子元素再划分父元素宽度,先刨除固定宽度

flex-grow:1   定义子元素放大比例

媒体查询

使用 @media 查询,你可以针对不同的媒体类型定义不同的样式。

@media 可以针对不同的屏幕尺寸设置不同的样式,特别是如果你需要设置设计响应式的页面,@media 是非常有用的。

当你重置浏览器大小的过程中,页面也会根据浏览器的宽度和高度重新渲染页面。

语法

1、在CSS里使用:

1
2
3
4
5
6
7
8
9
10
@media 类型 and (条件1) and (条件二)
{
css样式
}
例:
@media screen and (max-width:980px ) {
body{
background-color: red;
}
}

2、使用@import导入

1
@import url("css/css.css") all and (max-width:980px);

3、link连接

1
<link rel="stylesheet" media="mediatype and|not|only (media feature)" href="css.css">

媒体类型

描述
all用于所有设备
aural已废弃。用于语音和声音合成器
braille已废弃。 应用于盲文触摸式反馈设备
embossed已废弃。 用于打印的盲人印刷设备
handheld已废弃。 用于掌上设备或更小的装置,如PDA和小型电话
print用于打印机和打印预览
projection已废弃。 用于投影设备
screen用于电脑屏幕,平板电脑,智能手机等。
speech应用于屏幕阅读器等发声设备
tty已废弃。 用于固定的字符网格,如电报、终端设备和对字符有限制的便携设备
tv已废弃。 用于电视和网络电视

媒体功能

描述
aspect-ratio定义输出设备中的页面可见区域宽度与高度的比率
color定义输出设备每一组彩色原件的个数。如果不是彩色设备,则值等于0
color-index定义在输出设备的彩色查询表中的条目数。如果没有使用彩色查询表,则值等于0
device-aspect-ratio定义输出设备的屏幕可见宽度与高度的比率。
device-height定义输出设备的屏幕可见高度。
device-width定义输出设备的屏幕可见宽度。
grid用来查询输出设备是否使用栅格或点阵。
height定义输出设备中的页面可见区域高度。
max-aspect-ratio定义输出设备的屏幕可见宽度与高度的最大比率。
max-color定义输出设备每一组彩色原件的最大个数。
max-color-index定义在输出设备的彩色查询表中的最大条目数。
max-device-aspect-ratio定义输出设备的屏幕可见宽度与高度的最大比率。
max-device-height定义输出设备的屏幕可见的最大高度。
max-device-width定义输出设备的屏幕最大可见宽度。
max-height定义输出设备中的页面最大可见区域高度。
max-monochrome定义在一个单色框架缓冲区中每像素包含的最大单色原件个数。
max-resolution定义设备的最大分辨率。
max-width定义输出设备中的页面最大可见区域宽度。
min-aspect-ratio定义输出设备中的页面可见区域宽度与高度的最小比率。
min-color定义输出设备每一组彩色原件的最小个数。
min-color-index定义在输出设备的彩色查询表中的最小条目数。
min-device-aspect-ratio定义输出设备的屏幕可见宽度与高度的最小比率。
min-device-width定义输出设备的屏幕最小可见宽度。
min-device-height定义输出设备的屏幕的最小可见高度。
min-height定义输出设备中的页面最小可见区域高度。
min-monochrome定义在一个单色框架缓冲区中每像素包含的最小单色原件个数
min-resolution定义设备的最小分辨率。
min-width定义输出设备中的页面最小可见区域宽度。
monochrome定义在一个单色框架缓冲区中每像素包含的单色原件个数。如果不是单色设备,则值等于0
orientation定义输出设备中的页面可见区域高度是否大于或等于宽度。
resolution定义设备的分辨率。如:96dpi, 300dpi, 118dpcm
scan定义电视类设备的扫描工序。
width定义输出设备中的页面可见区域宽度。

JavaScript 基础语法

变量

变量保存的数据可以在需要时设置,更新或提取。赋值给变量的值都有对应的类型。JavaScript的类型有字符串布尔值函数对象,还有undefinednull,以及数组日期正则表达式

变量作用域

作用域是指,在编写的算法函数中,我们能访问变量(在使用函数作用域时,也可以是一个函数)的地方。有局部变量全局变量两种。

全局变量

1
2
3
4
5
6
7
var a = 'global';
console.log(a); //global
function test(){
console.log(a);
}

test(); //global

局部变量

1
2
3
4
5
6
7
function test(){
var a = 1;
console.log(a) // 1
}
console.log(a); // 报错
test();
console.log(a); // 报错

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var myVariable = 'global';
myOtherVariable = 'global';

function myFunction(){
var myVariable = 'local';
return myVariable;
}

function myOtherFunction(){
myOtherVariable = 'local';
return myOtherVariable;
}

console.log(myVariable); // 输出 global,因为它是一个全局变量
console.log(myFunction()); //输出 local,因为myVariable是在myFunction函数中声明的局部变量,所以作用域仅在myFunction

console.log(myOtherVariable); //输出 global,这里引用的是初始化的全局变量myOtherVariable
console.log(myOtherFunction()); //输出 local,在myOtherFunction里,因为没有使用var 关键字修饰,所以这里引用的是全局变量myOtherVariable,并将其复制为local
console.log(myOtherVariable); //输出 local 因为在myOtherFunction里修改了myOtherVariable的值

运算符

算数运算符

运算符描述
+加法
-减法
*乘法
/除法
%系数
++递加
递减

赋值运算符

运算符描述
=赋值
+=加赋值(x += y) == (x = x + y)
-=减赋值(x -= y) == (x = x - y)
*=乘赋值(x *= y) == (x = x * y)
/=除赋值(x /= y) == (x = x / y)
%=取余赋值(x %= y) == (x = x % y)

比较运算符

运算符描述
运算符描述
==等于
===等值等型
!=不相等
!==不等值或不等型

|大于
<|小于
=|大于或等于
<=|小于或等于
?|三元运算符

逻辑运算符

运算符描述
&&逻辑与
||逻辑或
!逻辑非

位运算符

运算符描述例子等同于结果十进制
&5 & 10101 & 000100011
|510101 | 00010101
~~ 5~0101101010
^异或5 ^ 10101 ^ 000101004
<<零填充左位移5 << 10101 << 1101010

|有符号右位移| 5 >> 1|0101 >> 1|0010|2

|零填充右位移|5 >>> 1|0101 >>> 1|0010|2

类型运算符

运算符描述
typeof返回变量的类型。
instanceof返回 true,如果对象是对象类型的实例。

真值和假值

在JavaScript中,true和false有些复杂。在大多数编程语言中,布尔值true和false仅仅表示true/false结果。在JavaScript中,如abc这样的字符,也可以看作true。

数值类型转换成布尔值
undefinedfalse
nullfalse
布尔值true是true,false是false
+0,-0和NaN都是false,其它都是true
字符串如果字符串长度为零就是false,其它都是true
对象true

让我们用代码来验证上面的总结。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function demo(val){
return val ? console.log('true') : console.log('false');
}

demo(true); // true
demo(false); // false
demo(new Boolean(false)); // true (因为对象始终是true)

demo(''); // false (因为长度为零的字符串为false)
demo('false'); // true (因为长度大于零的字符串都为true)
demo(new String('')); // true (因为对象始终是true)

demo(1); // true
demo(-1); // true
demo(NaN); //false
demo(new Number(NaN));// true (因为对象始终是true)

demo({}); // true (因为对象始终是true)

var obj = { name : '张三' };
demo(obj); // true
demo(obj.name); // true
demo(obj.sex); // false (sex属性不存在为undefined)

这两个相等运算符的使用可能会引起一些困惑。
我们来分析一下两者不同

相等运算符(==和===)

“==” 的比较规则

  1. 先检查两个操作数的数据类型是否相同
  2. 如果相同,则比较两个数是否相等
  3. 如果不同,则先将两个数转换为相同数据类型,再进行比较

我们用表格来分析一下不同类型的值用相等运算符比较后的结果:

类型(x)类型(y)结果
nullundefinedtrue
undefinednulltrue
字符串x == toNumber(y)
字符串toNumber(x) == y
布尔值任何类型toNumber(x) == y
任何类型布尔值x == toNumber(y)
字符串或数对象x == toPrimitive(y)
对象字符串或数toPrimitive(x) == y
  • 如果两个操作数都是对象,则仅当两个操作数都引用同一个对象时才返回true。
  • 如果一个操作数是null,另一个操作数是undefined,则返回true。
  • 如果两个操作数是不同类型的,就会尝试在比较之前将它们转换为相同类型:
    • 当数字与字符串进行比较时,会尝试将字符串转换为数字值。
    • 如果操作数之一是Boolean,则将布尔操作数转换为1或0。
    • 如果操作数之一是对象,另一个是数字或字符串,会尝试使用对象的valueOf()和toString()方法将对象转换为原始值。
  • 如果操作数具有相同的类型,则将它们进行如下比较:
    • String:true仅当两个操作数具有相同顺序的相同字符时才返回。
    • Number:true仅当两个操作数具有相同的值时才返回。+0并被-0视为相同的值。如果任一操作数为NaN,则返回false。
    • Boolean:true仅当操作数为两个true或两个false时才返回true。

toNumber 和 toPrimitive 方法是内部的,并根据以下表格对其估值

toNumber 方法对不同类型返回的结果如下:

值类型结果
undefinedNaN
null+0
布尔值如果是true,返回1;如果是false,返回+0
数对应的值

toPrimitive 方法对不同类型返回的结果如下:

值类型结果
对象如果对象的valueOf方法的结果是返回原始值,返回原始值;如果对象的toString方法返回原始值,就返回这个值;其它情况都返回一个错误

我们可以用例子来验证一下:

例子1:

1
console.log('demo' == true); // false

首先,布尔值会被 toNumber 方法转成数 ,因此得到:demo == 1。
其次,用toNumber 转换字符串值。因为字符串包含字母,所以会被转成NaN,表达式编程了 NaN == 1,结果就是false

例子2

1
console.log('demo' == false); // false

首先,布尔值会被 toNumber 方法转成数 ,因此得到:demo == 0。
其次,用toNumber 转换字符串值。因为字符串包含字母,所以会被转成NaN,表达式编程了 NaN == 0,结果就是false

“===”的比较规则

  1. 先检查两个操作数的数据类型是否相同
  2. 若不同,直接返回false
  3. 若相同,则比较二者是否相等

我们可以用以下表格分析。

类型(x)类型(y)结果
x和y的值相同(但不是NaN)true
字符串x和y是相同字符true
布尔值x和y都是true或falsetrue
对象x和y引用同一个对象true

让我们用代码来验证一下:

1
2
3
4
5
6
7
console.log('张三' === true); // false (两者类型不相同)
console.log('张三' === '张三') // true

var person1 = { name : '张三' };
var person2 = { name : '张三' };

console.log(person1 === person2) // false (两者引用的是不同对象)

条件语句

条件语句用于基于不同条件执行不同的动作。

在您写代码时,经常会需要基于不同判断执行不同的动作。
您可以在代码中使用条件语句来实现这一点。
在 JavaScript 中,我们可使用如下条件语句

  • 使用 if 来规定要执行的代码块,如果指定条件为 true
  • 使用 else 来规定要执行的代码块,如果相同的条件为 false
  • 使用 else if 来规定要测试的新条件,如果第一个条件为 false
  • 使用 switch 来规定多个被执行的备选代码块

if 语句

如果想让一个脚本在仅当条件是true时执行,可以使用if语句
语法:

1
2
3
if (条件) {
//如果条件为 true 时执行的代码
}

示例:

1
2
3
4
var num = 1;
if(num === 1){
console.log('num 真的等于 1');
}

输出

1
num 真的等于 1

else 语句

如果想在条件为true的时候执行脚本A,在条件为false的时候执行脚本B可以使用if…else语句。

语法:

1
2
3
4
5
if (条件) {
// 脚本A,条件为 true 时执行的代码块
} else {
// 脚本B,条件为 false 时执行的代码块
}

示例:

1
2
3
4
5
6
var elseVariable = 0;
if(elseVariable === 1){
console.log('elseVariable 等于 1');
}else{
console.log('elseVariable 不等于 1');
}

输出

1
elseVariable 不等于 1

也可以使用三元运算符:

1
2
var elseVariable = 0;
elseVariable === 1 ? console.log('elseVariable 等于 1') : console.log('elseVariable 不等于 1');

else if 语句

如果我们有多个脚本,可以多次使用if…else,根据不同条件执行不同的语句

语法:

1
2
3
4
5
6
7
if (条件 1) {
条件 1 为 true 时执行的代码块
} else if (条件 2) {
条件 1 为 false 而条件 2 为 true 时执行的代码块
} else {
条件 1 和条件 2 同时为 false 时执行的代码块
}

示例:

1
2
3
4
5
6
7
8
9
10
var month = 5;
if (month === 1){
console.log('一月');
}else if (month === 2){
console.log('二月');
}else if (month === 3){
console.log('三月')
}else {
console.log('月份不是一月,也不是二月或三月');
}

输出:

1
月份不是一月,也不是二月或三月

switch

如果要判断的条件和上面的elseif一样,还可以使用switch语句

语法:

1
2
3
4
5
6
7
8
9
10
switch(表达式) {
case n:
代码块
break;
case n:
代码块
break;
default:
默认代码块
}

代码解释:

  • 计算一次 switch 表达式
  • 把表达式的值与每个 case 的值进行对比
  • 如果存在匹配,则执行关联代码

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var month = 5;
switch (month) {
case 1:
console.log('一月');
break;
case 2:
console.log('二月');
break;
case 3:
console.log('三月')
break;
default:
console.log('月份不是一月,也不是二月或三月');
}

输出:

1
月份不是一月,也不是二月或三月

循环

假如您需要运行代码多次,且每次使用不同的值,那么循环(loop)相当方便使用。
通常我们会遇到使用数组的例子:
不需要这样写:

1
2
3
4
5
6
var arr = ['张三','李四','王五','赵六','杨七'];
console.log(arr[0]);
console.log(arr[1]);
console.log(arr[2]);
console.log(arr[3]);
console.log(arr[4]);

可以使用for循环:

1
2
3
4
var arr = ['张三','李四','王五','赵六','杨七'];
for(let i = 0;i < arr.length;i++){
console.log(arr[i]);
}

不同类型的循环

  • for - 循环代码块一定的次数
  • for/in - 循环遍历对象的属性
  • while - 当指定的条件为 true 时循环指定的代码块
  • do/while - 同样当指定的条件为 true 时循环指定的代码块

for 循环

语法:

1
2
3
4
for (语句 1; 语句 2; 语句 3)
{
被执行的代码块
}

代码解释:
语句 1 (代码块)开始前执行
语句 2 定义运行循环(代码块)的条件
语句 3 在循环(代码块)已被执行之后执行

示例:
打印小于10的正整数:

1
2
3
for(let i = 1;i < 10;i++){
console.log(i);
}

for/in 循环

用for/in 语句遍历对象的属性
语法:

1
2
3
for(声明变量(即遍历数组的键名) in 数组){
访问元素:数组[键名]
}

示例:

1
2
3
4
var studentScore = {chinese: 90, math: 93, english: 88};
for (let subject in studentScore){
console.log(subject + '分数为:' + studentScore[subject]);
}

输出:

1
2
3
chinese分数为:90
math分数为:93
english分数为:88

While 循环

while 循环会在指定条件为真时循环执行代码块。
语法:

1
2
3
while (条件) {
要执行的代码块
}

示例:
打印小于10的正整数:

1
2
3
4
5
var num = 1;
while (num < 10){
console.log(num);
num++;
}

do/while 循环

do/while 循环是 while 循环的变体。该循环会在检查条件是否为真之前执行一次代码块,然后如果条件为真的话,就会重复这个循环。
语法:

1
2
3
4
5
do
{
需要执行的代码
}
while (条件);

示例:
打印小于10的正整数:

1
2
3
4
do {
console.log(num);
num++;
}while (num < 10);

while与do…while的区别:

while
先判断条件是否满足,如果满足就执行循环体内的语句,执行完毕后再回来判断条件是否满足,如此无限重复;直到条件不满足时,执行while循环后边的语句。简单来讲就是说while循环是先判断后循环
do…while
与while循环的不同在于:它先执行循环中的语句,然后再判断表达式是否为真, 如果为真则继续循环;如果为假, 则终止循环。因此, do…while循环至少要执行一次循环语句。 简单来讲就是说while循环是先循环后判断

break 和 continue 语句

break 语句用于跳出循环。
continue 用于跳过循环中的一个迭代。

break 语句

break 语句可用于跳出循环。
break 语句跳出循环后,会继续执行该循环之后的代码(如果有的话)。
break 语句(不带标签引用),只能用在循环或 switch 中。

continue 语句

continue 语句中断循环中的迭代,如果出现了指定的条件,然后继续循环中的下一个迭代。
continue 语句(带有或不带标签引用)只能用在循环中。

函数

函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。JavaScript函数语法,函数就是包裹在花括号中的代码块,前面使用了关键词 function: 当调用该函数时,会执行函数内的代码。可以在某事件发生时直接调用函数(比如当用户点击按钮时),并且可由 JavaScript 在任何位置进行调用。

函数的声明

  1. 定义式
1
2
3
4
5
fun(); // this is a function.
function fun() {
console.log('this is a function.')
}
fun(); // this is a function.
  1. 变量式
1
2
3
4
demo(); // 报错:demo is not a function
var demo = function () {
console.log('this is a function.')
}

为什么这里在声明前调用会报错 demo is not a function 呢?而上面定义式的fun();又能在之前调用?
javascript并不是严格的自上而下执行的语言 。JavaScript有个东西叫变量提升(hoisting),它会将当前作用域的所有变量的声明提升到程序的顶部。

1
2
3
4
num = 1;

console.log(num);
var num;

等价于

1
2
3
4
var num;
num = 1;

console.log(num);

那我们再来看一个例子:

1
2
console.log(num);  // undefined
var num = 1;

那为什么这里会打印 undefined 呢?原因为 JavaScript会将变量的声明提升到顶部,可是赋值语句并不会提升。
所以上面demo()函数的声明,变量demo会被提升到顶部,但是变量的值并没有提升,所以变量式声明函数时,函数的调用应该在该表达式的后面:

1
2
3
4
var demo = function () {
console.log('this is a function.')
}
demo(); // this is a function.

我们再来说一个题外话:

1
2
3
4
5
fun();
function fun(){
console.log('this is a function.');
}
var fun = 2;

你觉得上面会报fun is not a function吗?不,其实它会输出 this is a function. 当函数声明与其他声明一起出现的时候,是以谁为准呢?答案就是,函数声明高于一切

构造器式

1
2
let constructor = new Function('name','age',"console.log(name + '的年龄为' + age);")
constructor('张三',18); // 张三的年龄为18

函数的返回值

当 JavaScript 到达 return 语句,函数将停止执行。
如果函数被某条语句调用,JavaScript 将在调用语句之后“返回”执行代码。

在JavaScript中不管我们指没指定函数的返回值,函数都会返回一个值
指定返回值 -> 返回指定的值
未指定返回值 -> 返回undefined

我们来用代码来展示效果:

1
2
3
4
5
6
7
8
9
10
var fun1 = function () {
return '返回一个字符串';
}

var fun2 = function () {

}

console.log(fun1()); // 打印:返回一个字符串
console.log(fun2()); // undefined

为何使用函数?

您能够对代码进行复用:只要定义一次代码,就可以多次使用它。
您能够多次向同一函数传递不同的参数,以产生不同的结果。

面向对象

什么是类?什么是对象?

类是一个抽象的概念,是对某一类事物的抽象。 我们举个例子,人类可以看作一个类,这个类的共性有:第一、站立行走,第二、有一个很发达的大脑,上面这两点都是静态的,描述的是客观的属性(attributes)。人类还需要吃饭、需要睡觉,上面这两点都是动态的行为,即方法(methods)。类可以包含函数,函数在类中就是动态的行为,即方法。

对象就是类的实例化。人类是一个类,而每一个人就是人类的实例化,即每一个人就是一个对象,对象具有类的属性及方法(每个人都站立行走、有一个发达的大脑,并且需要吃饭睡觉)。

JavaScript中类的声明

1. 普通声明,基于已有对象扩充其属性和方法
1
2
3
4
5
6
7
var obj = new Object();
obj.name = '张三';
obj.age = '18';
obj.run = function(){
console.log(this.name + '开始奔跑');
}
obj.run(); // 张三开始奔跑

这种方式的弊端:这种对象的可复用性不强,如果需要使用多个对象,还需要重新扩展其属性和方法。

2. 键值对声明
1
2
3
4
5
6
7
8
9
var obj = {
name : '张三',
age : 18,
run : function () {
console.log(this.name + '开始奔跑');
}
};

obj.run(); // 张三开始奔跑

可以看得到,键值对中的键就是对象的属性,值就是对应属性的值

3. 构造函数方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person() {
this.name = '张三';
this.age = 18;

this.run = function () {
console.log(this.name + '开始奔跑');
}

console.log(this.name + '的年龄为' + this.age);
}


//实例化对象:
var person = new Person(); // 张三的年龄为18
person.run(); // 张三开始奔跑
4. 原型(“prototype”)方式
1
2
3
4
5
6
7
8
9
10
11
12
function Person() {

}

Person.prototype.name = '张三';
Person.prototype.age = 18;
Person.prototype.run = function () {
console.log(this.name + '开始奔跑');
}

var person = new Person();
person.run(); // 张三的年龄为18

使用原型存在的缺点:

  1. 不能传参数;
  2. 有可能会导致程序错误。如果使用原型方式来定义对象,那么生成的所有对象会共享原型中的属性,这样一个对象改变了该属性也会反映到其他对象当中。单纯使用原型方式定义对象无法在构造函数中为属性赋初值,只能在对象生成后再去改变属性值。

继承

创建一个或多个类的专门版本类方式称为继承(Javascript只支持单继承)。 创建的专门版本的类通常叫做子类,另外的类通常叫做父类。 在Javascript中,继承通过赋予子类一个父类的实例并专门化子类来实现。

让我们来看一下例子。
我们定义了 Student类作为 Person类的子类. 之后我们重定义了sayHello() 方法并添加了 squat() 方法

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
function Person(name) {
this.name = name;
this.sayHello = function () {
console.log('我的名字叫' + name);
}

this.run = function () {
console.log(name + '开始奔跑');
}
}

// 定义学生类
function Student(name,age) {
// 调用父类构造器, 确保(使用Function#call)"this" 在调用过程中设置正确
Person.call(this,name);
//初始化学生年龄
this.age = age;

/**
* 更换Person类的sayHello方法
*/
this.sayHello = function () {
console.log('我的名字叫' + name + ',今年' + age + '岁')
}
this.squat = function () {
console.log('蹲下来')
}

}
// 建立一个由Person.prototype继承而来的Student.prototype对象
Student.prototype = Object.create(Person.prototype);
// 设置"constructor" 属性指向Student
Student.prototype.constructor = Student;

var student = new Student('张三',18);
student.sayHello(); // 我的名字叫张三,今年18岁
student.run(); // 张三开始奔跑
student.squat(); //蹲下来

console.log(student instanceof Person); // true
console.log(student instanceof Student); // true

BOM与DOM

JavaScript的实现包括以下3个部分:

名称描述
ECMAScript描述了JS的语法和基本对象。
文档对象模型 (DOM)处理网页内容的方法和接口
浏览器对象模型(BOM)与浏览器交互的方法和接口

Document Object Model(文档对象模型),就是把「文档」当做一个「对象」来看待。

相应的,Browser Object Model(浏览器对象模型),即把「浏览器」当做一个「对象」来看待。

BOM(Browser Object Model)

BOM 即浏览器对象模型,BOM没有相关标准,BOM的最核心对象是window对象。window对象既为javascript访问浏览器提供API,同时在ECMAScript中充当Global对象。BOM和浏览器关系密切,浏览器很多东西可以通过javascript控制,例如打开窗口、打开选项卡、关闭页面、收藏夹等。这些功能与网页内容无关。由于没有标准,不同的浏览器实现同一功能,可以通过不同的实现方式。例如,加入收藏夹这个功能:

1
2
3
IE浏览器: window.external.AddFavorite(url,title);

FireFox浏览器: window.sidebar.addPanel(title, url, "");

虽然没有统一标准,但是各个浏览器的常用功能的js代码大同小异,对于常用的功能已经有默认的标准了。

DOM(Document Object bom图解Model)

DOM即文档对象模型,DOM是W3C标准,DOM的最根本对象是document(window.document),这个对象实际上是window对象的属性,这个对象的独特之处是这个是唯一一个既属于BOM又属于DOM的对象。DOM和文档有关,这里的文档指的是网页,也就是html文档。DOM和浏览器无关,他关注的是网页本身的内容,由于和浏览器没有多大的关系,所以标准就好定了。

dom图解

JavaScript内置对象

基本对象

Object

**Object** 是 JavaScript 的一种 数据类型 。它用于存储各种键值集合和更复杂的实体。Objects 可以通过 Object() 构造函数或者使用 对象字面量 的方式创建

方法名方法描述
Object.assign()通过复制一个或多个对象来创建一个新的对象。
Object.create()使用指定的原型对象和属性创建一个新对象。
Object.defineProperty()给对象添加一个属性并指定该属性的配置。
Object.defineProperties()给对象添加多个属性并分别指定它们的配置。
Object.entries()返回给定对象自身可枚举属性的 [key, value] 数组。
Object.freeze()冻结对象:其他代码不能删除或更改任何属性。
Object.getOwnPropertyDescriptor()返回对象指定的属性配置。
Object.getOwnPropertyNames()返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性名。
Object.getOwnPropertySymbols()返回一个数组,它包含了指定对象自身所有的符号属性。
Object.getPrototypeOf()返回指定对象的原型对象。
Object.is()比较两个值是否相同。所有 NaN 值都相等(这与==和===不同)。
Object.isExtensible()判断对象是否可扩展。
Object.isFrozen()判断对象是否已经冻结。
Object.isSealed()判断对象是否已经密封。
Object.keys()返回一个包含所有给定对象自身可枚举属性名称的数组。
Object.preventExtensions()防止对象的任何扩展。
Object.seal()防止其他代码删除对象的属性。
Object.setPrototypeOf()设置对象的原型(即内部 [[Prototype]] 属性)。
Object.values()返回给定对象自身可枚举值的数组。
Function

每个 JavaScript 函数实际上都是一个 Function 对象。运行 (function(){}).constructor === Function // true 便可以得到这个结论。

实例属性

属性描述
Function.prototype.arguments对应传递给函数的参数数组,这个 Function 的属性已被弃用,请改用 arguments 对象(仅在函数范围内可用)。
Function.prototype.caller表明调用当前函数执行的函数,此属性已被弃用,且仅对一些不严格的函数可用。
Function.prototype.displayName函数的显示名称。
Function.prototype.length函数期望的参数数量。
Function.prototype.name函数的名称。

** 实例方法 **

方法名描述
Function.prototype.apply(thisArg [, argsArray])调用一个函数并将其 this 的值设置为提供的 thisArg。参数可用以通过数组对象传递。
Function.prototype.bind(thisArg[, arg1[, arg2[, …argN]]])创建一个新的函数,该函数在调用时,会将 this 设置为提供的 thisArg。在调用新绑定的函数时,可选的参数序列([, arg1[, arg2[, …argN]]])会被提前添加到参数序列中(译者注:即调用绑定创建的函数时,传递的参数会接续在可选参数序列后)。
Function.prototype.call(thisArg[, arg1, arg2, …argN])调用一个函数,并将其 this 值设置为提供的值。也可以选择传输新参数。
Function.prototype.toString()返回表示函数源码的字符串。覆盖了 Object.prototype.toString 方法。
Boolean

**Boolean**对象是一个布尔值的对象包装器。

如果需要,作为第一个参数传递的值将转换为布尔值。如果省略或值0-0nullfalseNaNundefined,或空字符串(""),该对象具有的初始值false。所有其他值,包括任何对象,空数组([])或字符串"false",都会创建一个初始值为true的对象。

注意不要将基本类型中的布尔值 truefalse 与值为 truefalseBoolean 对象弄混了。

其值不是undefinednull的任何对象(包括其值为false的布尔对象)在传递给条件语句时都将计算为true。 例如,以下if语句中的条件评估为true

1
2
3
4
var x = new Boolean(false);
if (x) {
// 这里的代码会被执行
}

基本类型的布尔值不受此规则影响。例如下面的 if 语句的条件为假:

1
2
3
4
var x = false;
if (x) {
// 这里的代码不会执行
}
Symbol

symbol 是一种基本数据类型 (primitive data type)。Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:”new Symbol()“。

每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol('foo');

console.log(typeof symbol1);
// expected output: "symbol"

console.log(symbol2 === 42);
// expected output: false

console.log(symbol3.toString());
// expected output: "Symbol(foo)"

console.log(Symbol('foo') === Symbol('foo'));
// expected output: false

数字和日期对象

Number

JavaScript 的 Number 对象是经过封装的能让你处理数字值的对象。Number 对象由 Number() 构造器创建。

JavaScript的Number类型为双精度IEEE 754 64位浮点类型。

属性

属性名描述
Number.EPSILON两个可表示(representable)数之间的最小间隔。
Number.MAX_SAFE_INTEGERJavaScript 中最大的安全整数 (2^53 - 1)。
Number.MAX_VALUE能表示的最大正数。最小的负数是 -MAX_VALUE。
Number.MIN_SAFE_INTEGERJavaScript 中最小的安全整数 (-(2^53 - 1)).
Number.MIN_VALUE能表示的最小正数即最接近 0 的正数 (实际上不会变成 0)。最大的负数是 -MIN_VALUE。
Number.NaN特殊的“非数字”值。
Number.NEGATIVE_INFINITY特殊的负无穷大值,在溢出时返回该值。
Number.POSITIVE_INFINITY特殊的正无穷大值,在溢出时返回该值。
Number.prototype (en-US)Number 对象上允许的额外属性。

方法

方法名描述
Number.isNaN()确定传递的值是否是 NaN。
Number.isFinite()确定传递的值类型及本身是否是有限数。
Number.isInteger()确定传递的值类型是“number”,且是整数。
Number.isSafeInteger()确定传递的值是否为安全整数 ( -(2^53 - 1) 至 2^53 - 1)之间。
Number.toInteger()计算传递的值并将其转换为整数 (或无穷大)。
Number.parseFloat()和全局对象 parseFloat() 一样。
Number.parseInt()和全局对象 parseInt() 一样。
Math

Math 是一个内置对象,它拥有一些数学常数属性和数学函数方法。Math 不是一个函数对象。

与其他全局对象不同的是,Math 不是一个构造器。Math 的所有属性与方法都是静态的。引用圆周率的写法是 Math.PI,调用正余弦函数的写法是 Math.sin(x),x 是要传入的参数。Math 的常量是使用 JavaScript 中的全精度浮点数来定义的。

属性

属性名描述
Math.E欧拉常数,也是自然对数的底数,约等于 2.718。
Math.LN22 的自然对数,约等于 0.693。
Math.LN1010 的自然对数,约等于 2.303。
Math.LOG2E以 2 为底的 E 的对数,约等于 1.443。
Math.LOG10E以 10 为底的 E 的对数,约等于 0.434。
Math.PI圆周率,一个圆的周长和直径之比,约等于 3.14159。
Math.SQRT1_2二分之一 ½ 的平方根,同时也是 2 的平方根的倒数 ,约等于 0.707。
Math.SQRT22 的平方根,约等于 1.414。

方法

方法名描述
Math.abs(x)返回一个数的绝对值。
Math.acos(x)返回一个数的反余弦值。
Math.acosh(x)返回一个数的反双曲余弦值。
Math.asin(x)返回一个数的反正弦值。
Math.asinh(x)返回一个数的反双曲正弦值。
Math.atan(x)返回一个数的反正切值。
Math.atanh(x)返回一个数的反双曲正切值。
Math.atan2(y, x)返回 y/x 的反正切值。
Math.cbrt(x)返回一个数的立方根。
Math.ceil(x)返回大于一个数的最小整数,即一个数向上取整后的值。
Math.clz32(x)返回一个 32 位整数的前导零的数量。
Math.cos(x)返回一个数的余弦值。
Math.cosh(x)返回一个数的双曲余弦值。
Math.exp(x)返回欧拉常数的参数次方,E^x,其中 x 为参数,E 是欧拉常数(2.718…,自然对数的底数)。
Math.expm1(x)返回 exp(x) - 1 的值。
Math.floor(x)返回小于一个数的最大整数,即一个数向下取整后的值。
Math.fround(x)返回最接近一个数的单精度浮点型表示。
Math.hypot([x[, y[, …]]])返回其所有参数平方和的平方根。
Math.imul(x, y)返回 32 位整数乘法的结果。
Math.log(x)返回一个数的自然对数(㏒e,即 ㏑)。
Math.log1p(x)返回一个数加 1 的和的自然对数(㏒e,即 ㏑)。
Math.log10(x)返回一个数以 10 为底数的对数。
Math.log2(x)返回一个数以 2 为底数的对数。
Math.max([x[, y[, …]]])返回零到多个数值中最大值。
Math.min([x[, y[, …]]])返回零到多个数值中最小值。
Math.pow(x, y)返回一个数的 y 次幂。
Math.random()返回一个 0 到 1 之间的伪随机数。
Math.round(x)返回四舍五入后的整数。
Math.sign(x)返回一个数的符号,得知一个数是正数、负数还是 0。
Math.sin(x)返回一个数的正弦值。
Math.sinh(x)返回一个数的双曲正弦值。
Math.sqrt(x)返回一个数的平方根。
Math.tan(x)返回一个数的正切值。
Math.tanh(x)返回一个数的双曲正切值。
Math.toSource()返回字符串 “Math”。
Math.trunc(x)返回一个数的整数部分,直接去除其小数点及之后的部分。
Date

创建一个 JavaScript Date 实例,该实例呈现时间中的某个时刻。Date 对象则基于 Unix Time Stamp,即自1970年1月1日(UTC)起经过的毫秒数。

Date 对象方法
方法

方法名描述
getDate()返回月中的第几天(从 1 到 31)。
getDay()返回星期几(0-6)。
getFullYear()返回年份。
getHours()返回小时(从 0-23)。
getMilliseconds()返回毫秒(0-999)。
getMinutes()返回分钟(从 0-59)。
getMonth()返回月份(从 0-11)。
getSeconds()返回秒数(从 0-59)。
getTime()返回自 1970 年 1 月 1 日午夜以来与指定日期的毫秒数。
getTimezoneOffset()返回 UTC 时间与本地时间之间的时差,以分钟为单位。
getUTCDate()根据世界时,返回月份中的第几天(从 1 到 31)。
getUTCDay()根据世界时,返回星期几(0-6)。
getUTCFullYear()根据世界时,返回年份。
getUTCHours()根据世界时,返回小时(0-23)。
getUTCMilliseconds()根据世界时,返回毫秒数(0-999)。
getUTCMinutes()根据世界时,返回分钟(0-59)。
getUTCMonth()根据世界时,返回月份(0-11)。
getUTCSeconds()根据世界时,返回秒数(0-59)。
getYear()已弃用。请改用 getFullYear() 方法。
now()返回自 1970 年 1 月 1 日午夜以来的毫秒数。
parse()解析日期字符串并返回自 1970 年 1 月 1 日以来的毫秒数。
setDate()设置 Date 对象中月的某一天。
setFullYear()设置日期对象的年份
setHours()设置日期对象的小时。
setMilliseconds()设置日期对象的毫秒数。
setMinutes()设置日期对象的分钟数。
setMonth()设置日期对象的月份。
setSeconds()设置日期对象的秒数。
setTime()将日期设置为 1970 年 1 月 1 日之后/之前的指定毫秒数。
setUTCDate()根据世界时,设置 Date 对象中月份的一天。
setUTCFullYear()根据世界时,设置日期对象的年份。
setUTCHours()根据世界时,设置日期对象的小时。
setUTCMilliseconds()根据世界时,设置日期对象的毫秒数。
setUTCMinutes()根据世界时,设置日期对象的分钟数。
setUTCMonth()根据世界时,设置日期对象的月份。
setUTCSeconds()根据世界时,设置日期对象的秒数。
setYear()已弃用。请改用 setFullYear() 方法。
toDateString()将 Date 对象的日期部分转换为可读字符串。
toGMTString()已弃用。请改用 toUTCString() 方法。
toISOString()使用 ISO 标准将日期作为字符串返回。
toJSON()以字符串形式返回日期,格式为 JSON 日期。
toLocaleDateString()使用区域设置约定将 Date 对象的日期部分作为字符串返回。
toLocaleTimeString()使用区域设置约定将 Date 对象的时间部分作为字符串返回。
toLocaleString()使用区域设置约定将 Date 对象转换为字符串。
toString()将 Date 对象转换为字符串。
toTimeString()将 Date 对象的时间部分转换为字符串。
toUTCString()根据世界时,将 Date 对象转换为字符串。
UTC()根据 UTC 时间,返回自 1970 年 1 月 1 日午夜以来的日期中的毫秒数。
valueOf()返回 Date 对象的原始值。

字符串

String

String 对象用于处理文本(字符串)。
方法

方法名描述
anchor()创建 HTML 锚。
big()用大号字体显示字符串。
blink()显示闪动字符串。
bold()使用粗体显示字符串。
charAt()返回在指定位置的字符。
charCodeAt()返回在指定的位置的字符的 Unicode 编码。
concat()连接字符串。
fixed()以打字机文本显示字符串。
fontcolor()使用指定的颜色来显示字符串。
fontsize()使用指定的尺寸来显示字符串。
fromCharCode()从字符编码创建一个字符串。
indexOf()检索字符串。
italics()使用斜体显示字符串。
lastIndexOf()从后向前搜索字符串。
link()将字符串显示为链接。
localeCompare()用本地特定的顺序来比较两个字符串。
match()找到一个或多个正则表达式的匹配。
replace()替换与正则表达式匹配的子串。
search()检索与正则表达式相匹配的值。
slice()提取字符串的片断,并在新的字符串中返回被提取的部分。
small()使用小字号来显示字符串。
split()把字符串分割为字符串数组。
strike()使用删除线来显示字符串。
sub()把字符串显示为下标。
substr()从起始索引号提取字符串中指定数目的字符。
substring()提取字符串中两个指定的索引号之间的字符。
sup()把字符串显示为上标。
toLocaleLowerCase()把字符串转换为小写。
toLocaleUpperCase()把字符串转换为大写。
toLowerCase()把字符串转换为小写。
toUpperCase()把字符串转换为大写。
toSource()代表对象的源代码。
toString()返回字符串。
valueOf()返回某个字符串对象的原始值。

JaScript DOM事件

HTML DOM 事件允许 JavaScript 在 HTML 文档中的元素上注册不同的事件处理程序。

事件通常与函数结合使用,在事件发生之前函数不会被执行(例如当用户单击按钮时)。

事件描述属于
abort媒体加载中止时发生该事件。UiEvent、Event
afterprint当页面开始打印时,或者关闭打印对话框时,发生此事件。Event
animationendCSS 动画完成时发生此事件。AnimationEvent
animationiteration重复 CSS 动画时发生此事件。AnimationEvent
animationstartCSS 动画开始时发生此事件。AnimationEvent
beforeprint即将打印页面时发生此事件。Event
beforeunload在文档即将被卸载之前发生此事件。UiEvent
blur当元素失去焦点时发生此事件。FocusEvent
canplay当浏览器可以开始播放媒体时,发生此事件。Event
canplaythrough当浏览器可以在不停止缓冲的情况下播放媒体时发生此事件。Event
change当form元素的内容、选择的内容或选中的状态发生改变时,发生此事件Event
click当用户单击元素时发生此事件。MouseEvent
contextmenu当用户右键单击某个元素以打开上下文菜单时,发生此事件。MouseEvent
copy当用户复制元素的内容时发生此事件。ClipboardEvent
cut当用户剪切元素的内容时发生此事件。ClipboardEvent
dblclick当用户双击元素时发生此事件。MouseEvent
drag拖动元素时发生此事件。DragEvent
dragend当用户完成拖动元素后,发生此事件。DragEvent
dragenter当拖动的元素进入放置目标时,发生此事件。DragEvent
dragleave当拖动的元素离开放置目标时,发生此事件。DragEvent
dragover当拖动的元素位于放置目标之上时,发生此事件。DragEvent
dragstart当用户开始拖动元素时发生此事件。DragEvent
drop当将拖动的元素放置在放置目标上时,发生此事件。DragEvent
durationchange媒体时长改变时发生此事件。Event
ended在媒体播放到尽头时发生此事件。Event
error当加载外部文件时发生错误后,发生此事件。ProgressEvent、UiEvent、Event
focus在元素获得焦点时发生此事件。FocusEvent
focusin在元素即将获得焦点时发生此事件。FocusEvent
focusout在元素即将失去焦点时发生此事件。FocusEvent
fullscreenchange当元素以全屏模式显示时,发生此事件。Event
fullscreenerror 当元素无法在全屏模式下显示时,发生此事件。Event
hashchange当 URL 的锚部分发生改变时,发生此事件。HashChangeEvent
input当元素获得用户输入时,发生此事件。InputEvent、vent
invalid 当元素无效时,发生此事件。Event
keydown当用户正在按下键时,发生此事件。KeyboardEvent
keypress当用户按下键时,发生此事件。KeyboardEvent
keyup当用户松开键时,发生此事件。KeyboardEvent
load在对象已加载时,发生此事件。UiEvent、vent
loadeddata媒体数据加载后,发生此事件。Event
loadedmetadata加载元数据(比如尺寸和持续时间)时,发生此事件。Event
loadstart当浏览器开始查找指定的媒体时,发生此事件。ProgressEvent
message在通过此事件源接收消息时,发生此事件。Event
mousedown当用户在元素上按下鼠标按钮时,发生此事件。MouseEvent
mouseenter当指针移动到元素上时,发生此事件。MouseEvent
mouseleave当指针从元素上移出时,发生此事件。MouseEvent
mousemove当指针在元素上方移动时,发生此事件。MouseEvent
mouseout当用户将鼠标指针移出元素或其中的子元素时,发生此事件。MouseEvent
mouseover当指针移动到元素或其中的子元素上时,发生此事件。MouseEvent
mouseup当用户在元素上释放鼠标按钮时,发生此事件。MouseEvent
mousewheel不推荐使用。请改用 wheel 事件。WheelEvent
offline当浏览器开始脱机工作时,发生此事件。Event
online当浏览器开始在线工作时,发生此事件。Event
open当打开与事件源的连接时,发生此事件。Event
pagehide当用户离开某张网页进行导航时,发生此事件。PageTransitionEvent
pageshow在用户导航到某张网页时,发生此事件。PageTransitionEvent
paste当用户将一些内容粘贴到元素中时,发生此事件。ClipboardEvent
pause当媒体被用户暂停或以编程方式暂停时,发生此事件。Event
play当媒体已启动或不再暂停时,发生此事件。Event
playing在媒体被暂停或停止以缓冲后播放时,发生此事件。Event
popstate窗口的历史记录改变时,发生此事件。PopStateEvent
progress当浏览器正处于获得媒体数据的过程中时,发生此事件。Event
ratechange媒体播放速度改变时发生此事件。Event
reset重置表单时发生此事件。Event
resize调整文档视图的大小时发生此事件。UiEvent、vent
scroll滚动元素的滚动条时发生此事件。UiEvent、vent
search当用户在搜索字段中输入内容时,发生此事件。Event
seeked当用户完成移动/跳到媒体中的新位置时,发生该事件。Event
seeking当用户开始移动/跳到媒体中的新位置时,发生该事件。Event
select用户选择文本后(对于<input><textarea>)发生此事件UiEvent、vent
show<menu> 元素显示为上下文菜单时,发生此事件。Event
stalled当浏览器尝试获取媒体数据但数据不可用时,发生此事件。Event
storageWeb 存储区域更新时发生此事件。StorageEvent
submit在提交表单时发生此事件。Event
suspend当浏览器有意不获取媒体数据时,发生此事件。Event
timeupdate当播放位置更改时发生此事件。Event
toggle当用户打开或关闭 <details> 元素时,发生此事件。Event
touchcancel在触摸被中断时,发生此事件。TouchEvent
touchend当手指从触摸屏上移开时,发生此事件。TouchEvent
touchmove当手指在屏幕上拖动时,发生此事件。TouchEvent
touchstart当手指放在触摸屏上时,发生此事件。TouchEvent
transitionendCSS 转换完成时,发生此事件。TransitionEvent
unload页面卸载后(对于 <body>),发生此事件。UiEvent、vent
volumechange当媒体的音量已更改时,发生此事件。Event
waiting当媒体已暂停但预期会恢复时,发生此事件。Event
wheel当鼠标滚轮在元素向上或向下滚动时,发生此事件。WheelEvent

JavaScript Ajax

AJAX = Asynchronous JavaScript And XML.

AJAX 并非编程语言。

AJAX 仅仅组合了:

  • 浏览器内建的 XMLHttpRequest 对象(从 web 服务器请求数据)
  • JavaScript 和 HTML DOM(显示或使用数据)

Ajax 是一个令人误导的名称。Ajax 应用程序可能使用 XML 来传输数据,但将数据作为纯文本或 JSON 文本传输也同样常见。

Ajax 允许通过与场景后面的 Web 服务器交换数据来异步更新网页。这意味着可以更新网页的部分,而不需要重新加载整个页面。

XMLHttpRequest 对象

  XMLHttpRequest对象是ajax的基础,XMLHttpRequest 用于在后台与服务器交换数据。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。目前所有浏览器都支持XMLHttpRequest

方 法描 述
abort()停止当前请求
getAllResponseHeaders()把HTTP请求的所有响应首部作为键/值对返回
getResponseHeader(“header”)返回指定首部的串值
open(“method”,”URL”,[asyncFlag],[“userName”],[“password”])建立对服务器的调用。method参数可以是GET、POST或PUT。url参数可以是相对URL或绝对URL。这个方法还包括3个可选的参数,是否异步,用户名,密码
send(content)向服务器发送请求
setRequestHeader(“header”, “value”)把指定首部设置为所提供的值。在设置任何首部之前必须先调用open()。设置header并和请求一起发送 (‘post’方法一定要 )

五步使用法:

  1.创建XMLHTTPRequest对象

  2.使用open方法设置和服务器的交互信息

  3.设置发送的数据,开始和服务器端交互

  4.注册事件

  5.更新界面

栗子:

1
2
3
4
5
6
7
8
9
10
11
//步骤一:创建异步对象
var ajax = new XMLHttpRequest();
//步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
ajax.open('get','users.php?name='+name);
//步骤三:发送请求
ajax.send();
//步骤四:注册事件 onreadystatechange 状态改变就会调用
ajax.onreadystatechange = function () { if (ajax.readyState==4 &&ajax.status==200) {
//步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的    console.log(ajax.responseText);//输入相应的内容
  }
}

JQuery

jQuery 选择器

元素元素
选择器实例选取
*$(“*”)所有元素
#id$(“#lastname”)id=”lastname” 的元素
.class$(“.intro”)所有 class=”intro” 的元素
element$(“p”)所有

元素

.class.class$(“.intro.demo”)所有 class=”intro” 且 class=”demo” 的元素
:first$(“p:first”)第一个

元素

:last$(“p:last”)最后一个

元素

:even$(“tr:even”)所有偶数
:odd$(“tr:odd”)所有奇数
:eq(index)$(“ul li:eq(3)”)列表中的第四个元素(index 从 0 开始)
:gt(no)$(“ul li:gt(3)”)列出 index 大于 3 的元素
:lt(no)$(“ul li:lt(3)”)列出 index 小于 3 的元素
:not(selector)$(“input:not(:empty)”)所有不为空的 input 元素
:header$(“:header”)所有标题元素

-

:animated所有动画元素
:contains(text)$(“:contains(‘W3School’)”)包含指定字符串的所有元素
:empty$(“:empty”)无子(元素)节点的所有元素
:hidden$(“p:hidden”)所有隐藏的

元素

:visible$(“table:visible”)所有可见的表格
s1,s2,s3$(“th,td,.intro”)所有带有匹配选择的元素
[attribute]$(“[href]”)所有带有 href 属性的元素
[attribute=value]$(“[href=’#’]”)所有 href 属性的值等于 “#” 的元素
[attribute!=value]$(“[href!=’#’]”)所有 href 属性的值不等于 “#” 的元素
[attribute$=value]$(“[href$=’.jpg’]”)所有 href 属性的值包含以 “.jpg” 结尾的元素
:input$(“:input”)所有 元素
:text$(“:text”)所有 type=”text” 的 元素
:password$(“:password”)所有 type=”password” 的 元素
:radio$(“:radio”)所有 type=”radio” 的 元素
:checkbox$(“:checkbox”)所有 type=”checkbox” 的 元素
:submit$(“:submit”)所有 type=”submit” 的 元素
:reset$(“:reset”)所有 type=”reset” 的 元素
:button$(“:button”)所有 type=”button” 的 元素
:image$(“:image”)所有 type=”image” 的 元素
:file$(“:file”)所有 type=”file” 的 元素
:enabled$(“:enabled”)所有激活的 input 元素
:disabled$(“:disabled”)所有禁用的 input 元素
:selected$(“:selected”)所有被选取的 input 元素
:checked$(“:checked”)所有被选中的 input 元素

Query 事件方法

事件方法会触发匹配元素的事件,或将函数绑定到所有匹配元素的某个事件。

触发实例:

1
$("button#demo").click()

上面的例子将触发 id=”demo” 的 button 元素的 click 事件。

绑定实例:

1
$("button#demo").click(function(){$("img").hide()})

上面的例子会在点击 id=”demo” 的按钮时隐藏所有图像。

方法描述
bind()向匹配元素附加一个或更多事件处理器
blur()触发、或将函数绑定到指定元素的 blur 事件
change()触发、或将函数绑定到指定元素的 change 事件
click()触发、或将函数绑定到指定元素的 click 事件
dblclick()触发、或将函数绑定到指定元素的 double click 事件
delegate()向匹配元素的当前或未来的子元素附加一个或多个事件处理器
die()移除所有通过 live() 函数添加的事件处理程序。
error()触发、或将函数绑定到指定元素的 error 事件
event.isDefaultPrevented()返回 event 对象上是否调用了 event.preventDefault()。
event.pageX相对于文档左边缘的鼠标位置。
event.pageY相对于文档上边缘的鼠标位置。
event.preventDefault()阻止事件的默认动作。
event.result包含由被指定事件触发的事件处理器返回的最后一个值。
event.target触发该事件的 DOM 元素。
event.timeStamp该属性返回从 1970 年 1 月 1 日到事件发生时的毫秒数。
event.type描述事件的类型。
event.which指示按了哪个键或按钮。
focus()触发、或将函数绑定到指定元素的 focus 事件
keydown()触发、或将函数绑定到指定元素的 key down 事件
keypress()触发、或将函数绑定到指定元素的 key press 事件
keyup()触发、或将函数绑定到指定元素的 key up 事件
live()为当前或未来的匹配元素添加一个或多个事件处理器
load()触发、或将函数绑定到指定元素的 load 事件
mousedown()触发、或将函数绑定到指定元素的 mouse down 事件
mouseenter()触发、或将函数绑定到指定元素的 mouse enter 事件
mouseleave()触发、或将函数绑定到指定元素的 mouse leave 事件
mousemove()触发、或将函数绑定到指定元素的 mouse move 事件
mouseout()触发、或将函数绑定到指定元素的 mouse out 事件
mouseover()触发、或将函数绑定到指定元素的 mouse over 事件
mouseup()触发、或将函数绑定到指定元素的 mouse up 事件
one()向匹配元素添加事件处理器。每个元素只能触发一次该处理器。
ready()文档就绪事件(当 HTML 文档就绪可用时)
resize()触发、或将函数绑定到指定元素的 resize 事件
scroll()触发、或将函数绑定到指定元素的 scroll 事件
select()触发、或将函数绑定到指定元素的 select 事件
submit()触发、或将函数绑定到指定元素的 submit 事件
toggle()绑定两个或多个事件处理器函数,当发生轮流的 click 事件时执行。
trigger()所有匹配元素的指定事件
triggerHandler()第一个被匹配元素的指定事件
unbind()从匹配元素移除一个被添加的事件处理器
undelegate()从匹配元素移除一个被添加的事件处理器,现在或将来
unload()触发、或将函数绑定到指定元素的 unload 事件

jQuery操作DOM元素

插入节点

append(): 向每个匹配的元素内部追加内容.
appendTo(): 将所有匹配的元素追加到指定的元素中,即$(A).appendTo(B),是将A追加到B中.
prepend(): 向每个匹配的元素内部前置内容.
rependTo() 将所有匹配的元素前置到指定的元素中,即$(A).prependTo(B),是将A前置到B中.

前面几个方法都是插入子元素,后面的这几个方法是插入兄弟元素:

after(): 在每个匹配的元素之后插入内容
insertAfter(): 将所有匹配的元素插入到指定元素的后面.
before(): 在每个匹配的元素之前插入内容.
insertBefore(): 将所有匹配的元素插入到指定元素的前面.

删除节点

 jQuery中删除节点的方法:

remove(): 移除所有匹配的元素.
empty(): 删除匹配的元素集合中所有内容,包括子节点.注意,元素本身没有被删除.

复制节点

jQuery中复制节点的方法:
clone(): 创建匹配元素集合的副本.
clone():方法返回被复制的节点.

替换节点

jQuery中替换节点的方法:
replaceAll(): 用指定的HTML内容或元素替换被选元素.

1
$(content).replaceAll(selector).

replaceWith(): 用新内容替换所匹配到的元素.

1
$(selector).replaceWith(content).

jQuery 效果

show()方法和hide()方法

show():根据hide()方法记住的display属性值来显示元素。

hide() : 将该元素的display样式改为 “none”。

fadeIn()方法和fadeOut()方法

fadeOut() :会在指定的一段时间内降低元素的不透明度,直到元素完全消失(“display:none”)。

fadeIn() :与fadeOut()完全相反。

slideUp()方法和slideDown()方法

slideDown():如果一个元素的display属性值为”none”,当调用slideDown()时,这个元素将由上至下延伸显示。

slideUp() :与slideDown()完全相反。

animate() 方法

语法:

1
$(selector).animate({params},speed,callback);

必需的 params 参数定义形成动画的 CSS 属性。

可选的 speed 参数规定效果的时长。它可以取以下值:”slow”、”fast” 或毫秒。

可选的 callback 参数是动画完成后所执行的函数名称。

下面的例子演示 animate() 方法的简单应用;它把 <div> 元素移动到左边,直到 left 属性等于 250 像素为止:

栗子:

1
2
3
$("button").click(function(){
$("div").animate({left:'250px'});
});

操作多个属性

1
2
3
4
5
6
7
8
$("button").click(function(){
$("div").animate({
left:'250px',
opacity:'0.5',
height:'150px',
width:'150px'
});
});

jQuery 遍历

函数描述
.add()将元素添加到匹配元素的集合中。
.andSelf()把堆栈中之前的元素集添加到当前集合中。
.children()获得匹配元素集合中每个元素的所有子元素。
.closest()从元素本身开始,逐级向上级元素匹配,并返回最先匹配的祖先元素。
.contents()获得匹配元素集合中每个元素的子元素,包括文本和注释节点。
.each()对 jQuery 对象进行迭代,为每个匹配元素执行函数。
.end()结束当前链中最近的一次筛选操作,并将匹配元素集合返回到前一次的状态。
.eq()将匹配元素集合缩减为位于指定索引的新元素。
.filter()将匹配元素集合缩减为匹配选择器或匹配函数返回值的新元素。
.find()获得当前匹配元素集合中每个元素的后代,由选择器进行筛选。
.first()将匹配元素集合缩减为集合中的第一个元素。
.has()将匹配元素集合缩减为包含特定元素的后代的集合。
.is()根据选择器检查当前匹配元素集合,如果存在至少一个匹配元素,则返回 true。
.last()将匹配元素集合缩减为集合中的最后一个元素。
.map()把当前匹配集合中的每个元素传递给函数,产生包含返回值的新 jQuery 对象。
.next()获得匹配元素集合中每个元素紧邻的同辈元素。
.nextAll()获得匹配元素集合中每个元素之后的所有同辈元素,由选择器进行筛选(可选)。
.nextUntil()获得每个元素之后所有的同辈元素,直到遇到匹配选择器的元素为止。
.not()从匹配元素集合中删除元素。
.offsetParent()获得用于定位的第一个父元素。
.parent()获得当前匹配元素集合中每个元素的父元素,由选择器筛选(可选)。
.parents()获得当前匹配元素集合中每个元素的祖先元素,由选择器筛选(可选)。
.parentsUntil()获得当前匹配元素集合中每个元素的祖先元素,直到遇到匹配选择器的元素为止。
.prev()获得匹配元素集合中每个元素紧邻的前一个同辈元素,由选择器筛选(可选)。
.prevAll()获得匹配元素集合中每个元素之前的所有同辈元素,由选择器进行筛选(可选)。
.prevUntil()获得每个元素之前所有的同辈元素,直到遇到匹配选择器的元素为止。
.siblings()获得匹配元素集合中所有元素的同辈元素,由选择器筛选(可选)。
.slice()将匹配元素集合缩减为指定范围的子集。

each

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var arr = [ "one", "two", "three", "four"];     
$.each(arr, function(){
alert(this);
});
//上面这个each输出的结果分别为:one,two,three,four

var arr1 = [[1, 4, 3], [4, 6, 6], [7, 20, 9]]
$.each(arr1, function(i, item){
alert(item[0]);
});
//其实arr1为一个二维数组,item相当于取每一个一维数组,
//item[0]相对于取每一个一维数组里的第一个值
//所以上面这个each输出分别为:1 4 7


var obj = { one:1, two:2, three:3, four:4};
$.each(obj, function(i) {
alert(obj[i]);
});
//这个each就有更厉害了,能循环每一个属性
//输出结果为:1 2 3 4

JQuery Ajax

栗子:

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(){
//请求参数
var list = {};
//
$.ajax({
//请求方式
type : "POST",
//请求的媒体类型
contentType: "application/json;charset=UTF-8",
//请求地址
url : "http://127.0.0.1/admin/list/",
//数据,json字符串
data : JSON.stringify(list),
//请求成功
success : function(result) {
console.log(result);
},
//请求失败,包含具体的错误信息
error : function(e){
console.log(e.status);
console.log(e.responseText);
}
});
});

参数

  • options

    类型:Object可选。AJAX 请求设置。所有选项都是可选的。

  • async

    类型:Boolean默认值: true。默认设置下,所有请求均为异步请求。如果需要发送同步请求,请将此选项设置为 false。注意,同步请求将锁住浏览器,用户其它操作必须等待请求完成才可以执行。

  • beforeSend(XHR)

    类型:Function发送请求前可修改 XMLHttpRequest 对象的函数,如添加自定义 HTTP 头。XMLHttpRequest 对象是唯一的参数。这是一个 Ajax 事件。如果返回 false 可以取消本次 ajax 请求。

  • cache

    类型:Boolean默认值: true,dataType 为 script 和 jsonp 时默认为 false。设置为 false 将不缓存此页面。jQuery 1.2 新功能。

  • complete(XHR, TS)

    类型:Function请求完成后回调函数 (请求成功或失败之后均调用)。参数: XMLHttpRequest 对象和一个描述请求类型的字符串。这是一个 Ajax 事件。

  • contentType

    类型:String默认值: “application/x-www-form-urlencoded”。发送信息至服务器时内容编码类型。默认值适合大多数情况。如果你明确地传递了一个 content-type 给 $.ajax() 那么它必定会发送给服务器(即使没有数据要发送)。

  • context

    类型:Object这个对象用于设置 Ajax 相关回调函数的上下文。也就是说,让回调函数内 this 指向这个对象(如果不设定这个参数,那么 this 就指向调用本次 AJAX 请求时传递的 options 参数)。比如指定一个 DOM 元素作为 context 参数,这样就设置了 success 回调函数的上下文为这个 DOM 元素。就像这样:$.ajax({ url: "test.html", context: document.body, success: function(){ $(this).addClass("done"); }});

  • data

    类型:String发送到服务器的数据。将自动转换为请求字符串格式。GET 请求中将附加在 URL 后。查看 processData 选项说明以禁止此自动转换。必须为 Key/Value 格式。如果为数组,jQuery 将自动为不同值对应同一个名称。如 {foo:[“bar1”, “bar2”]} 转换为 ‘&foo=bar1&foo=bar2’。

  • dataFilter

    类型:Function给 Ajax 返回的原始数据的进行预处理的函数。提供 data 和 type 两个参数:data 是 Ajax 返回的原始数据,type 是调用 jQuery.ajax 时提供的 dataType 参数。函数返回的值将由 jQuery 进一步处理。

  • dataType

    类型:String预期服务器返回的数据类型。如果不指定,jQuery 将自动根据 HTTP 包 MIME 信息来智能判断,比如 XML MIME 类型就被识别为 XML。在 1.4 中,JSON 就会生成一个 JavaScript 对象,而 script 则会执行这个脚本。随后服务器端返回的数据会根据这个值解析后,传递给回调函数。可用值:”xml”: 返回 XML 文档,可用 jQuery 处理。”html”: 返回纯文本 HTML 信息;包含的 script 标签会在插入 dom 时执行。”script”: 返回纯文本 JavaScript 代码。不会自动缓存结果。除非设置了 “cache” 参数。注意:在远程请求时(不在同一个域下),所有 POST 请求都将转为 GET 请求。(因为将使用 DOM 的 script标签来加载)”json”: 返回 JSON 数据 。”jsonp”: JSONP 格式。使用 JSONP 形式调用函数时,如 “myurl?callback=?” jQuery 将自动替换 ? 为正确的函数名,以执行回调函数。”text”: 返回纯文本字符串

  • error

    类型:Function默认值: 自动判断 (xml 或 html)。请求失败时调用此函数。有以下三个参数:XMLHttpRequest 对象、错误信息、(可选)捕获的异常对象。如果发生了错误,错误信息(第二个参数)除了得到 null 之外,还可能是 “timeout”, “error”, “notmodified” 和 “parsererror”。这是一个 Ajax 事件。

  • global

    类型:Boolean是否触发全局 AJAX 事件。默认值: true。设置为 false 将不会触发全局 AJAX 事件,如 ajaxStart 或 ajaxStop 可用于控制不同的 Ajax 事件。

  • ifModified

    类型:Boolean仅在服务器数据改变时获取新数据。默认值: false。使用 HTTP 包 Last-Modified 头信息判断。在 jQuery 1.4 中,它也会检查服务器指定的 ‘etag’ 来确定数据没有被修改过。

  • jsonp

    类型:String在一个 jsonp 请求中重写回调函数的名字。这个值用来替代在 “callback=?” 这种 GET 或 POST 请求中 URL 参数里的 “callback” 部分,比如 {jsonp:’onJsonPLoad’} 会导致将 “onJsonPLoad=?” 传给服务器。

  • jsonpCallback

    类型:String为 jsonp 请求指定一个回调函数名。这个值将用来取代 jQuery 自动生成的随机函数名。这主要用来让 jQuery 生成度独特的函数名,这样管理请求更容易,也能方便地提供回调函数和错误处理。你也可以在想让浏览器缓存 GET 请求的时候,指定这个回调函数名。

  • password

    类型:String用于响应 HTTP 访问认证请求的密码

  • processData

    类型:Boolean默认值: true。默认情况下,通过data选项传递进来的数据,如果是一个对象(技术上讲只要不是字符串),都会处理转化成一个查询字符串,以配合默认内容类型 “application/x-www-form-urlencoded”。如果要发送 DOM 树信息或其它不希望转换的信息,请设置为 false。

  • scriptCharset

    类型:String只有当请求时 dataType 为 “jsonp” 或 “script”,并且 type 是 “GET” 才会用于强制修改 charset。通常只在本地和远程的内容编码不同时使用。

  • success

    类型:Function请求成功后的回调函数。参数:由服务器返回,并根据 dataType 参数进行处理后的数据;描述状态的字符串。这是一个 Ajax 事件。

  • traditional

    类型:Boolean如果你想要用传统的方式来序列化数据,那么就设置为 true。请参考工具分类下面的 jQuery.param 方法。

  • timeout

    类型:Number设置请求超时时间(毫秒)。此设置将覆盖全局设置。

  • type

    类型:String默认值: “GET”)。请求方式 (“POST” 或 “GET”), 默认为 “GET”。注意:其它 HTTP 请求方法,如 PUT 和 DELETE 也可以使用,但仅部分浏览器支持。

  • url

    类型:String默认值: 当前页地址。发送请求的地址。

  • username

    类型:String用于响应 HTTP 访问认证请求的用户名。

  • xhr

    类型:Function需要返回一个 XMLHttpRequest 对象。默认在 IE 下是 ActiveXObject 而其他情况下是 XMLHttpRequest 。用于重写或者提供一个增强的 XMLHttpRequest 对象。这个参数在 jQuery 1.3 以前不可用。

回调函数

如果要处理 $.ajax() 得到的数据,则需要使用回调函数:beforeSend、error、dataFilter、success、complete。

函数名说明
beforeSend发送请求之前调用,并且传入一个 XMLHttpRequest 作为参数。
error在请求出错时调用。传入 XMLHttpRequest 对象,描述错误类型的字符串以及一个异常对象(如果有的话)
dataFilter在请求成功之后调用。传入返回的数据以及 “dataType” 参数的值。并且必须返回新的数据(可能是处理过的)传递给 success 回调函数。
success当请求之后调用。传入返回后的数据,以及包含成功代码的字符串。
complete当请求完成之后调用这个函数,无论成功或失败。传入 XMLHttpRequest 对象,以及一个包含成功或错误代码的字符串。

Bootstrap

响应式布局相关的 标签

Bootstrap 采用的是 移动设备优先(mobile first) 的开发策略,因此,我们首先为移动设备优化代码,然后根据需要并利用 CSS 媒体查询功能来缩放组件。为了确保所有设备都能支持正确的渲染和触屏缩放,请务必在 <head> 标签中 添加让 viewport(视口)支持响应式布局的 标签

1
<meta name="viewport" content="width=device-width, initial-scale=1">

ES6

let 与 const

1、let

它的作用类似于var,用来声明变量,但是所声明的变量,只在let命令所在的代码块内有效。

1
2
3
4
5
6
7
8
9
10
11
if(true){

var a = 1;

let b = 2;

}

document.write(a);

document.write(b); // 报错:ReferenceError: b is not defined

体会下let和var的作用域范围:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function f1() {

var a = 8;

let n = 5;

if (true) {

let n = 10;

var a = 20

}

document.write(n); // 5

document.write(a); // 20

}

f1();

2、let的应用

for循环的计数器,就很合适使用let命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = [];

for (let i = 0; i < 10; i++) {

a[i] = function () {

document.write(i);

};

}

document.write(a[6]()); //6

如果把let换成var ,将输出10。这是由于let的块级作用域。

3、const

a、const 声明的是常量,一旦声明,值将是不可变的。

1
2
3
4
5
6
7
8
9
10
11
const PI = 3.1415;

PI // 3.1415

PI = 3;

PI // 3.1415

const PI = 3.1;

PI // 3.1415

b、const 也具有块级作用域

1
2
3
4
5
6
7
if (true) {

const max = 5;

}

document.write(max); // ReferenceError 常量MAX在此处不可得

c、const 不能变量提升(必须先声明后使用)

1
2
3
4
5
6
7
if (true) {

document.write(MAX); // ReferenceError

const MAX = 5;

}

d、const 不可重复声明

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
var message = "Hello!";

let age = 25;

// 以下两行都会报错

const message = "Goodbye!";

const age = 30;

const 指令指向变量所在的地址,所以对该变量进行属性设置是可行的(未改变变量地址),如果想完全不可变化(包括属性),那么可以使用冻结。

const C1 = {};

C1.a = 1;

document.write(C1.a); // 1

C1 = {}; // 报错 重新赋值,地址改变

//冻结对象,此时前面用不用const都是一个效果

const C2 = Object.freeze({});

C2.a = 1; //Error,对象不可扩展

document.write(C2.a);

Proxy

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

1
2
3
4
5
6
7
8
9
10
var obj = new Proxy({}, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
});

上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为。这里暂时先不解释具体的语法,只看运行结果。对设置了拦截行为的对象obj,去读写它的属性,就会得到下面的结果。

1
2
3
4
5
6
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2

上面代码说明,Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。

ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。

1
var proxy = new Proxy(target, handler);

Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。

下面是另一个拦截读取属性行为的例子。

1
2
3
4
5
6
7
8
9
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

上面代码中,作为构造函数,Proxy接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有Proxy的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35

注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。

如果handler没有设置任何拦截,那就等同于直接通向原对象。

1
2
3
4
5
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"

上面代码中,handler是一个空对象,没有任何拦截效果,访问proxy就等同于访问target

一个技巧是将 Proxy 对象,设置到object.proxy属性,从而可以在object对象上调用。

1
var object = { proxy: new Proxy(target, handler) };

Proxy 实例也可以作为其他对象的原型对象。

1
2
3
4
5
6
7
8
var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});

let obj = Object.create(proxy);
obj.time // 35

上面代码中,proxy对象是obj对象的原型,obj对象本身并没有time属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截。

同一个拦截器函数,可以设置拦截多个操作。

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
var handler = {
get: function(target, name) {
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},

apply: function(target, thisBinding, args) {
return args[0];
},

construct: function(target, args) {
return {value: args[1]};
}
};

var fproxy = new Proxy(function(x, y) {
return x + y;
}, handler);

fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true

对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。

下面是 Proxy 支持的拦截操作一览,一共 13 种。

  • **get(target, propKey, receiver)**:拦截对象属性的读取,比如proxy.fooproxy['foo']
  • **set(target, propKey, value, receiver)**:拦截对象属性的设置,比如proxy.foo = vproxy['foo'] = v,返回一个布尔值。
  • **has(target, propKey)**:拦截propKey in proxy的操作,返回一个布尔值。
  • **deleteProperty(target, propKey)**:拦截delete proxy[propKey]的操作,返回一个布尔值。
  • **ownKeys(target)**:拦截Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
  • **getOwnPropertyDescriptor(target, propKey)**:拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • **defineProperty(target, propKey, propDesc)**:拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • **preventExtensions(target)**:拦截Object.preventExtensions(proxy),返回一个布尔值。
  • **getPrototypeOf(target)**:拦截Object.getPrototypeOf(proxy),返回一个对象。
  • **isExtensible(target)**:拦截Object.isExtensible(proxy),返回一个布尔值。
  • **setPrototypeOf(target, proto)**:拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
  • **apply(target, object, args)**:拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  • **construct(target, args)**:拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

Proxy 实例的方法

下面是上面这些拦截方法的详细介绍。

get()

get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。

get方法的用法,上文已经有一个例子,下面是另一个拦截读取操作的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var person = {
name: "张三"
};

var proxy = new Proxy(person, {
get: function(target, propKey) {
if (propKey in target) {
return target[propKey];
} else {
throw new ReferenceError("Prop name \"" + propKey + "\" does not exist.");
}
}
});

proxy.name // "张三"
proxy.age // 抛出一个错误

上面代码表示,如果访问目标对象不存在的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回undefined

get方法可以继承。

1
2
3
4
5
6
7
8
9
let proto = new Proxy({}, {
get(target, propertyKey, receiver) {
console.log('GET ' + propertyKey);
return target[propertyKey];
}
});

let obj = Object.create(proto);
obj.foo // "GET foo"

上面代码中,拦截操作定义在Prototype对象上面,所以如果读取obj对象继承的属性时,拦截会生效。

下面的例子使用get拦截,实现数组读取负数的索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function createArray(...elements) {
let handler = {
get(target, propKey, receiver) {
let index = Number(propKey);
if (index < 0) {
propKey = String(target.length + index);
}
return Reflect.get(target, propKey, receiver);
}
};

let target = [];
target.push(...elements);
return new Proxy(target, handler);
}

let arr = createArray('a', 'b', 'c');
arr[-1] // c

上面代码中,数组的位置参数是-1,就会输出数组的倒数第一个成员。

利用 Proxy,可以将读取属性的操作(get),转变为执行某个函数,从而实现属性的链式操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var pipe = function (value) {
var funcStack = [];
var oproxy = new Proxy({} , {
get : function (pipeObject, fnName) {
if (fnName === 'get') {
return funcStack.reduce(function (val, fn) {
return fn(val);
},value);
}
funcStack.push(window[fnName]);
return oproxy;
}
});

return oproxy;
}

var double = n => n * 2;
var pow = n => n * n;
var reverseInt = n => n.toString().split("").reverse().join("") | 0;

pipe(3).double.pow.reverseInt.get; // 63

上面代码设置 Proxy 以后,达到了将函数名链式使用的效果。

下面的例子则是利用get拦截,实现一个生成各种 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
29
30
const dom = new Proxy({}, {
get(target, property) {
return function(attrs = {}, ...children) {
const el = document.createElement(property);
for (let prop of Object.keys(attrs)) {
el.setAttribute(prop, attrs[prop]);
}
for (let child of children) {
if (typeof child === 'string') {
child = document.createTextNode(child);
}
el.appendChild(child);
}
return el;
}
}
});

const el = dom.div({},
'Hello, my name is ',
dom.a({href: '//example.com'}, 'Mark'),
'. I like:',
dom.ul({},
dom.li({}, 'The web'),
dom.li({}, 'Food'),
dom.li({}, '…actually that\'s it')
)
);

document.body.appendChild(el);

下面是一个get方法的第三个参数的例子,它总是指向原始的读操作所在的那个对象,一般情况下就是 Proxy 实例。

1
2
3
4
5
6
const proxy = new Proxy({}, {
get: function(target, key, receiver) {
return receiver;
}
});
proxy.getReceiver === proxy // true

上面代码中,proxy对象的getReceiver属性是由proxy对象提供的,所以receiver指向proxy对象。

1
2
3
4
5
6
7
8
const proxy = new Proxy({}, {
get: function(target, key, receiver) {
return receiver;
}
});

const d = Object.create(proxy);
d.a === d // true

上面代码中,d对象本身没有a属性,所以读取d.a的时候,会去d的原型proxy对象找。这时,receiver就指向d,代表原始的读操作所在的那个对象。

如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则通过 Proxy 对象访问该属性会报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const target = Object.defineProperties({}, {
foo: {
value: 123,
writable: false,
configurable: false
},
});

const handler = {
get(target, propKey) {
return 'abc';
}
};

const proxy = new Proxy(target, handler);

proxy.foo
// TypeError: Invariant check failed
set()

set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。

假定Person对象有一个age属性,该属性应该是一个不大于 200 的整数,那么可以使用Proxy保证age的属性值符合要求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}

// 对于满足条件的 age 属性以及其他属性,直接保存
obj[prop] = value;
return true;
}
};

let person = new Proxy({}, validator);

person.age = 100;

person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错

上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新 DOM。

有时,我们会在对象上面设置内部属性,属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用。结合getset方法,就可以做到防止这些内部属性被外部读写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const handler = {
get (target, key) {
invariant(key, 'get');
return target[key];
},
set (target, key, value) {
invariant(key, 'set');
target[key] = value;
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${action} private "${key}" property`);
}
}
const target = {};
const proxy = new Proxy(target, handler);
proxy._prop
// Error: Invalid attempt to get private "_prop" property
proxy._prop = 'c'
// Error: Invalid attempt to set private "_prop" property

上面代码中,只要读写的属性名的第一个字符是下划线,一律抛错,从而达到禁止读写内部属性的目的。

下面是set方法第四个参数的例子。

1
2
3
4
5
6
7
8
9
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = receiver;
return true;
}
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
proxy.foo === proxy // true

上面代码中,set方法的第四个参数receiver,指的是原始的操作行为所在的那个对象,一般情况下是proxy实例本身,请看下面的例子。

1
2
3
4
5
6
7
8
9
10
11
12
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = receiver;
return true;
}
};
const proxy = new Proxy({}, handler);
const myObj = {};
Object.setPrototypeOf(myObj, proxy);

myObj.foo = 'bar';
myObj.foo === myObj // true

上面代码中,设置myObj.foo属性的值时,myObj并没有foo属性,因此引擎会到myObj的原型链去找foo属性。myObj的原型对象proxy是一个 Proxy 实例,设置它的foo属性会触发set方法。这时,第四个参数receiver就指向原始赋值行为所在的对象myObj

注意,如果目标对象自身的某个属性不可写,那么set方法将不起作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const obj = {};
Object.defineProperty(obj, 'foo', {
value: 'bar',
writable: false
});

const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = 'baz';
return true;
}
};

const proxy = new Proxy(obj, handler);
proxy.foo = 'baz';
proxy.foo // "bar"

上面代码中,obj.foo属性不可写,Proxy 对这个属性的set代理将不会生效。

注意,set代理应当返回一个布尔值。严格模式下,set代理如果没有返回true,就会报错。

1
2
3
4
5
6
7
8
9
10
11
'use strict';
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = receiver;
// 无论有没有下面这一行,都会报错
return false;
}
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
// TypeError: 'set' on proxy: trap returned falsish for property 'foo'

上面代码中,严格模式下,set代理返回false或者undefined,都会报错。

apply()

apply方法拦截函数的调用、callapply操作。

apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。

1
2
3
4
5
var handler = {
apply (target, ctx, args) {
return Reflect.apply(...arguments);
}
};

下面是一个例子。

1
2
3
4
5
6
7
8
9
10
11
var target = function () { return 'I am the target'; };
var handler = {
apply: function () {
return 'I am the proxy';
}
};

var p = new Proxy(target, handler);

p()
// "I am the proxy"

上面代码中,变量p是 Proxy 的实例,当它作为函数调用时(p()),就会被apply方法拦截,返回一个字符串。

下面是另外一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
var twice = {
apply (target, ctx, args) {
return Reflect.apply(...arguments) * 2;
}
};
function sum (left, right) {
return left + right;
};
var proxy = new Proxy(sum, twice);
proxy(1, 2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30

上面代码中,每当执行proxy函数(直接调用或callapply调用),就会被apply方法拦截。

另外,直接调用Reflect.apply方法,也会被拦截。

1
Reflect.apply(proxy, null, [9, 10]) // 38
has()

has()方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。

has()方法可以接受两个参数,分别是目标对象、需查询的属性名。

下面的例子使用has()方法隐藏某些属性,不被in运算符发现。

1
2
3
4
5
6
7
8
9
10
11
var handler = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
'_prop' in proxy // false

上面代码中,如果原对象的属性名的第一个字符是下划线,proxy.has()就会返回false,从而不会被in运算符发现。

如果原对象不可配置或者禁止扩展,这时has()拦截会报错。

1
2
3
4
5
6
7
8
9
10
var obj = { a: 10 };
Object.preventExtensions(obj);

var p = new Proxy(obj, {
has: function(target, prop) {
return false;
}
});

'a' in p // TypeError is thrown

上面代码中,obj对象禁止扩展,结果使用has拦截就会报错。也就是说,如果某个属性不可配置(或者目标对象不可扩展),则has()方法就不得“隐藏”(即返回false)目标对象的该属性。

值得注意的是,has()方法拦截的是HasProperty操作,而不是HasOwnProperty操作,即has()方法不判断一个属性是对象自身的属性,还是继承的属性。

另外,虽然for...in循环也用到了in运算符,但是has()拦截对for...in循环不生效。

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
let stu1 = {name: '张三', score: 59};
let stu2 = {name: '李四', score: 99};

let handler = {
has(target, prop) {
if (prop === 'score' && target[prop] < 60) {
console.log(`${target.name} 不及格`);
return false;
}
return prop in target;
}
}

let oproxy1 = new Proxy(stu1, handler);
let oproxy2 = new Proxy(stu2, handler);

'score' in oproxy1
// 张三 不及格
// false

'score' in oproxy2
// true

for (let a in oproxy1) {
console.log(oproxy1[a]);
}
// 张三
// 59

for (let b in oproxy2) {
console.log(oproxy2[b]);
}
// 李四
// 99

上面代码中,has()拦截只对in运算符生效,对for...in循环不生效,导致不符合要求的属性没有被for...in循环所排除。

construct()

construct()方法用于拦截new命令,下面是拦截对象的写法。

1
2
3
4
5
const handler = {
construct (target, args, newTarget) {
return new target(...args);
}
};

construct()方法可以接受三个参数。

  • target:目标对象。
  • args:构造函数的参数数组。
  • newTarget:创造实例对象时,new命令作用的构造函数(下面例子的p)。
1
2
3
4
5
6
7
8
9
10
const p = new Proxy(function () {}, {
construct: function(target, args) {
console.log('called: ' + args.join(', '));
return { value: args[0] * 10 };
}
});

(new p(1)).value
// "called: 1"
// 10

construct()方法返回的必须是一个对象,否则会报错。

1
2
3
4
5
6
7
8
const p = new Proxy(function() {}, {
construct: function(target, argumentsList) {
return 1;
}
});

new p() // 报错
// Uncaught TypeError: 'construct' on proxy: trap returned non-object ('1')

另外,由于construct()拦截的是构造函数,所以它的目标对象必须是函数,否则就会报错。

1
2
3
4
5
6
7
8
const p = new Proxy({}, {
construct: function(target, argumentsList) {
return {};
}
});

new p() // 报错
// Uncaught TypeError: p is not a constructor

上面例子中,拦截的目标对象不是一个函数,而是一个对象(new Proxy()的第一个参数),导致报错。

注意,construct()方法中的this指向的是handler,而不是实例对象。

1
2
3
4
5
6
7
8
9
const handler = {
construct: function(target, args) {
console.log(this === handler);
return new target(...args);
}
}

let p = new Proxy(function () {}, handler);
new p() // true
deleteProperty()

deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var handler = {
deleteProperty (target, key) {
invariant(key, 'delete');
delete target[key];
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`Invalid attempt to ${action} private "${key}" property`);
}
}

var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: Invalid attempt to delete private "_prop" property

上面代码中,deleteProperty方法拦截了delete操作符,删除第一个字符为下划线的属性会报错。

注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。

defineProperty()

defineProperty()方法拦截了Object.defineProperty()操作。

1
2
3
4
5
6
7
8
var handler = {
defineProperty (target, key, descriptor) {
return false;
}
};
var target = {};
var proxy = new Proxy(target, handler);
proxy.foo = 'bar' // 不会生效

上面代码中,defineProperty()方法内部没有任何操作,只返回false,导致添加新属性总是无效。注意,这里的false只是用来提示操作失败,本身并不能阻止添加新属性。

注意,如果目标对象不可扩展(non-extensible),则defineProperty()不能增加目标对象上不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable)或不可配置(configurable),则defineProperty()方法不得改变这两个设置。

getOwnPropertyDescriptor()

getOwnPropertyDescriptor()方法拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var handler = {
getOwnPropertyDescriptor (target, key) {
if (key[0] === '_') {
return;
}
return Object.getOwnPropertyDescriptor(target, key);
}
};
var target = { _foo: 'bar', baz: 'tar' };
var proxy = new Proxy(target, handler);
Object.getOwnPropertyDescriptor(proxy, 'wat')
// undefined
Object.getOwnPropertyDescriptor(proxy, '_foo')
// undefined
Object.getOwnPropertyDescriptor(proxy, 'baz')
// { value: 'tar', writable: true, enumerable: true, configurable: true }

上面代码中,handler.getOwnPropertyDescriptor()方法对于第一个字符为下划线的属性名会返回undefined

getPrototypeOf()

getPrototypeOf()方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作。

  • Object.prototype.__proto__
  • Object.prototype.isPrototypeOf()
  • Object.getPrototypeOf()
  • Reflect.getPrototypeOf()
  • instanceof

下面是一个例子。

1
2
3
4
5
6
7
var proto = {};
var p = new Proxy({}, {
getPrototypeOf(target) {
return proto;
}
});
Object.getPrototypeOf(p) === proto // true

上面代码中,getPrototypeOf()方法拦截Object.getPrototypeOf(),返回proto对象。

注意,getPrototypeOf()方法的返回值必须是对象或者null,否则报错。另外,如果目标对象不可扩展(non-extensible), getPrototypeOf()方法必须返回目标对象的原型对象。

isExtensible()

isExtensible()方法拦截Object.isExtensible()操作。

1
2
3
4
5
6
7
8
9
10
var p = new Proxy({}, {
isExtensible: function(target) {
console.log("called");
return true;
}
});

Object.isExtensible(p)
// "called"
// true

上面代码设置了isExtensible()方法,在调用Object.isExtensible时会输出called

注意,该方法只能返回布尔值,否则返回值会被自动转为布尔值。

这个方法有一个强限制,它的返回值必须与目标对象的isExtensible属性保持一致,否则就会抛出错误。

1
Object.isExtensible(proxy) === Object.isExtensible(target)

下面是一个例子。

1
2
3
4
5
6
7
8
var p = new Proxy({}, {
isExtensible: function(target) {
return false;
}
});

Object.isExtensible(p)
// Uncaught TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true')
ownKeys()

ownKeys()方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()
  • for...in循环

下面是拦截Object.keys()的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let target = {
a: 1,
b: 2,
c: 3
};

let handler = {
ownKeys(target) {
return ['a'];
}
};

let proxy = new Proxy(target, handler);

Object.keys(proxy)
// [ 'a' ]

上面代码拦截了对于target对象的Object.keys()操作,只返回abc三个属性之中的a属性。

下面的例子是拦截第一个字符为下划线的属性名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let target = {
_bar: 'foo',
_prop: 'bar',
prop: 'baz'
};

let handler = {
ownKeys (target) {
return Reflect.ownKeys(target).filter(key => key[0] !== '_');
}
};

let proxy = new Proxy(target, handler);
for (let key of Object.keys(proxy)) {
console.log(target[key]);
}
// "baz"

注意,使用Object.keys()方法时,有三类属性会被ownKeys()方法自动过滤,不会返回。

  • 目标对象上不存在的属性
  • 属性名为 Symbol 值
  • 不可遍历(enumerable)的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let target = {
a: 1,
b: 2,
c: 3,
[Symbol.for('secret')]: '4',
};

Object.defineProperty(target, 'key', {
enumerable: false,
configurable: true,
writable: true,
value: 'static'
});

let handler = {
ownKeys(target) {
return ['a', 'd', Symbol.for('secret'), 'key'];
}
};

let proxy = new Proxy(target, handler);

Object.keys(proxy)
// ['a']

上面代码中,ownKeys()方法之中,显式返回不存在的属性(d)、Symbol 值(Symbol.for('secret'))、不可遍历的属性(key),结果都被自动过滤掉。

ownKeys()方法还可以拦截Object.getOwnPropertyNames()

1
2
3
4
5
6
7
8
var p = new Proxy({}, {
ownKeys: function(target) {
return ['a', 'b', 'c'];
}
});

Object.getOwnPropertyNames(p)
// [ 'a', 'b', 'c' ]

for...in循环也受到ownKeys()方法的拦截。

1
2
3
4
5
6
7
8
9
10
const obj = { hello: 'world' };
const proxy = new Proxy(obj, {
ownKeys: function () {
return ['a', 'b'];
}
});

for (let key in proxy) {
console.log(key); // 没有任何输出
}

上面代码中,ownkeys()指定只返回ab属性,由于obj没有这两个属性,因此for...in循环不会有任何输出。

ownKeys()方法返回的数组成员,只能是字符串或 Symbol 值。如果有其他类型的值,或者返回的根本不是数组,就会报错。

1
2
3
4
5
6
7
8
9
10
var obj = {};

var p = new Proxy(obj, {
ownKeys: function(target) {
return [123, true, undefined, null, {}, []];
}
});

Object.getOwnPropertyNames(p)
// Uncaught TypeError: 123 is not a valid property name

上面代码中,ownKeys()方法虽然返回一个数组,但是每一个数组成员都不是字符串或 Symbol 值,因此就报错了。

如果目标对象自身包含不可配置的属性,则该属性必须被ownKeys()方法返回,否则报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var obj = {};
Object.defineProperty(obj, 'a', {
configurable: false,
enumerable: true,
value: 10 }
);

var p = new Proxy(obj, {
ownKeys: function(target) {
return ['b'];
}
});

Object.getOwnPropertyNames(p)
// Uncaught TypeError: 'ownKeys' on proxy: trap result did not include 'a'

上面代码中,obj对象的a属性是不可配置的,这时ownKeys()方法返回的数组之中,必须包含a,否则会报错。

另外,如果目标对象是不可扩展的(non-extensible),这时ownKeys()方法返回的数组之中,必须包含原对象的所有属性,且不能包含多余的属性,否则报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = {
a: 1
};

Object.preventExtensions(obj);

var p = new Proxy(obj, {
ownKeys: function(target) {
return ['a', 'b'];
}
});

Object.getOwnPropertyNames(p)
// Uncaught TypeError: 'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible

上面代码中,obj对象是不可扩展的,这时ownKeys()方法返回的数组之中,包含了obj对象的多余属性b,所以导致了报错。

preventExtensions()

preventExtensions()方法拦截Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值。

这个方法有一个限制,只有目标对象不可扩展时(即Object.isExtensible(proxy)false),proxy.preventExtensions才能返回true,否则会报错。

1
2
3
4
5
6
7
8
var proxy = new Proxy({}, {
preventExtensions: function(target) {
return true;
}
});

Object.preventExtensions(proxy)
// Uncaught TypeError: 'preventExtensions' on proxy: trap returned truish but the proxy target is extensible

上面代码中,proxy.preventExtensions()方法返回true,但这时Object.isExtensible(proxy)会返回true,因此报错。

为了防止出现这个问题,通常要在proxy.preventExtensions()方法里面,调用一次Object.preventExtensions()

1
2
3
4
5
6
7
8
9
10
11
var proxy = new Proxy({}, {
preventExtensions: function(target) {
console.log('called');
Object.preventExtensions(target);
return true;
}
});

Object.preventExtensions(proxy)
// "called"
// Proxy {}
setPrototypeOf()

setPrototypeOf()方法主要用来拦截Object.setPrototypeOf()方法。

下面是一个例子。

1
2
3
4
5
6
7
8
9
10
var handler = {
setPrototypeOf (target, proto) {
throw new Error('Changing the prototype is forbidden');
}
};
var proto = {};
var target = function () {};
var proxy = new Proxy(target, handler);
Object.setPrototypeOf(proxy, proto);
// Error: Changing the prototype is forbidden

上面代码中,只要修改target的原型对象,就会报错。

注意,该方法只能返回布尔值,否则会被自动转为布尔值。另外,如果目标对象不可扩展(non-extensible),setPrototypeOf()方法不得改变目标对象的原型。

Proxy.revocable()

Proxy.revocable()方法返回一个可取消的 Proxy 实例。

1
2
3
4
5
6
7
8
9
10
let target = {};
let handler = {};

let {proxy, revoke} = Proxy.revocable(target, handler);

proxy.foo = 123;
proxy.foo // 123

revoke();
proxy.foo // TypeError: Revoked

Proxy.revocable()方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。

Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。

this 问题

虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。

1
2
3
4
5
6
7
8
9
10
11
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};

const proxy = new Proxy(target, handler);

target.m() // false
proxy.m() // true

上面代码中,一旦proxy代理targettarget.m()内部的this就是指向proxy,而不是target。所以,虽然proxy没有做任何拦截,target.m()proxy.m()返回不一样的结果。

下面是一个例子,由于this指向的变化,导致 Proxy 无法代理目标对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const _name = new WeakMap();

class Person {
constructor(name) {
_name.set(this, name);
}
get name() {
return _name.get(this);
}
}

const jane = new Person('Jane');
jane.name // 'Jane'

const proxy = new Proxy(jane, {});
proxy.name // undefined

上面代码中,目标对象janename属性,实际保存在外部WeakMap对象_name上面,通过this键区分。由于通过proxy.name访问时,this指向proxy,导致无法取到值,所以返回undefined

此外,有些原生对象的内部属性,只有通过正确的this才能拿到,所以 Proxy 也无法代理这些原生对象的属性。

1
2
3
4
5
6
const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

proxy.getDate();
// TypeError: this is not a Date object.

上面代码中,getDate()方法只能在Date对象实例上面拿到,如果this不是Date对象实例就会报错。这时,this绑定原始对象,就可以解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
const target = new Date('2015-01-01');
const handler = {
get(target, prop) {
if (prop === 'getDate') {
return target.getDate.bind(target);
}
return Reflect.get(target, prop);
}
};
const proxy = new Proxy(target, handler);

proxy.getDate() // 1

另外,Proxy 拦截函数内部的this,指向的是handler对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const handler = {
get: function (target, key, receiver) {
console.log(this === handler);
return 'Hello, ' + key;
},
set: function (target, key, value) {
console.log(this === handler);
target[key] = value;
return true;
}
};

const proxy = new Proxy({}, handler);

proxy.foo
// true
// Hello, foo

proxy.foo = 1
// true

上面例子中,get()set()拦截函数内部的this,指向的都是handler对象。

实例:Web 服务的客户端

Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来写 Web 服务的客户端。

1
2
3
4
5
6
const service = createWebService('http://example.com/data');

service.employees().then(json => {
const employees = JSON.parse(json);
// ···
});

上面代码新建了一个 Web 服务的接口,这个接口返回各种数据。Proxy 可以拦截这个对象的任意属性,所以不用为每一种数据写一个适配方法,只要写一个 Proxy 拦截就可以了。

1
2
3
4
5
6
7
function createWebService(baseUrl) {
return new Proxy({}, {
get(target, propKey, receiver) {
return () => httpGet(baseUrl + '/' + propKey);
}
});
}

同理,Proxy 也可以用来实现数据库的 ORM 层。