Javascript
1. 入门
js是世界上最流行的一门脚本语言 , 后端 , 必须精通js
1.1 历史
ECMAScript可以理解为js的一个标准
最新版本已经到了es6
但是大部分浏览器还只停留在es5代码上!
开发环境 – 线上环境 , 版本不一致
1.2 知识点
- 修改idea的js语法为es6语法
- 在编写js代码时 , 第一行写上 , use strict , 代表严格检查 , 避免js不严格的语法规范而带来的问题
- 特殊的数据类型 , NaN 不是一个number , Infinity 表示无限大
- 比较运算符 , = 赋值 , == 等于(类型不一样 , 值一样 , 也会返回true) , === 绝对等于 (类型一样 , 值一样)
- 使用未定义 underfined , 定义使用 , 但是为空 , null
- 数组下标越界的话 , 会显示undefined
- 使用浏览器的控制台可以查看和调试js代码
2. 数据类型
2.1 字符串类型
- 字符串可以使用单引号或者双引号 , 要在引号内使用引号需要使用转义字符
\'
\n
\t
\u4e2d
- 多行字符串编写 , 使用``
- 模板字符串
let name="caiji";
let age=3;
let msg="你好呀,${name}";
- 字符串长度
str.length
- 字符串的可变性 , 不可变
使用console.log(str.length)
使用str. 可以调用关于这个字符串可以查看的属性
- 大小写转换
//注意,这里是方法不是属性
student.toUpperCase()
student.toLowerCase()
- student.indeOf(“t”) , 可以查看t在这个字符串中第一次出现的位置
- substring
//从第一个字符截取到最后一个字符
student.substring(1)
//从第一个字符截取到第三个字符
student.substring(1,3) ========>tu
2.2 数组
Array可以包含任意的数据类型
var arr=[1,2,3,"aaa",null,undefined]
- 长度
arr.length
注意 : js中的数组大小可以发生变化 , 手动扩大数组长度 , 增加的长度会为空 , 手动减少数组的长度 , 会导致数组数据的丢失
- indexOf , 通过下标获得元素
arr.indexOf(1)
字符串的"1"和数字1是不同的
- slice()类似于substring , 可以截取arry的一部分 , 返回一个新的数组
arr.slice(1)
//从第一个开始到最后
- push() pop() 都是在尾部操作 , 一个是压入 , 一个是弹出
- unshift() shift() 在头部操作 , 一个是压入 , 一个是弹出
- 排序使用arr.sort()
- 元素反转 aar.reverse()
- 元素拼接arr.concat()
concat并没有修改数组,只是会返回一个新的数组
- 连接符 join
打印拼接数组,使用特定的字符串连接
arr.join("-")
"c-b-a"
- 多维数组
数组 : 存储数据(如何存 , 如何取)
2.3 对象
若干个键值对
//定义了一个person对象,有两个属性
//属性的最后一个不用写逗号
var person={
name:"caiji",
age:3
}
js中的对象使用大括号 , 使用冒号 , 多个属性之间使用逗号隔开 , 最后一个属性不加逗号
js中的所有的键都是字符串 , 值是任意对象
- 对象赋值
person.name="aaa"
- 使用一个不存在的对象属性 , 不会报错 , 只会显示一个underfined
person.haha
undefined
- 动态的删减属性
delete person.name
- 动态的添加 , 直接给新的属性添加值即可
person.haha="hahah"
//person的属性中就会增加一个名为haha的属性
- 判断属性值是否在这个对象中
xxx in xxx
"age" in person
//继承
"toString" in person
true
- 判断一个属性是否是这个对象自身拥有的hasOneProperty()
person.hasOneProperty("toString)
false
person.hasOneProperty("age")
true
2.4 流程控制
if判断
for、do-while循环
2.5 Map和Set
es6引入了新的数据类型Map
如果用Map实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢
var names = ['Michael', 'Bob', 'Tracy'];
var scores = [95, 75, 85];
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
m.get('Michael'); // 95
初始化Map
需要一个二维数组,或者直接初始化一个空Map
。Map
具有以下方法:
var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined
由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉:
var m = new Map();
m.set('Adam', 67);
m.set('Adam', 88);
m.get('Adam'); // 88
set
Set
和Map
类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set
中,没有重复的key。
要创建一个Set
,需要提供一个Array
作为输入,或者直接创建一个空Set
:
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
重复元素在Set
中自动被过滤:
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
注意数字3
和字符串'3'
是不同的元素。
通过add(key)
方法可以添加元素到Set
中,可以重复添加,但不会有效果:
s.add(4);
s; // Set {1, 2, 3, 4}
s.add(4);
s; // 仍然是 Set {1, 2, 3, 4}
通过delete(key)
方法可以删除元素:
var s = new Set([1, 2, 3]);
s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}
2.6 iterable
遍历Map和Set无法使用下标来循环遍历 , es6引入了新的语法 , iterable类型 , array\Map\Set都属于ierable类型
具有iterable类型的集合可以通过 for…of循环使用遍历
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
console.log(x);
}
for (var x of s) { // 遍历Set
console.log(x);
}
for (var x of m) { // 遍历Map
console.log(x[0] + '=' + x[1]);
}
forEache()
这个方法实在es5.1引入的 , 使用的话需要检测浏览器是否支持这个方法
Set
var s = new Set([1,2,3])
s.forEache(function(element,sameElement,set){
console.log(element);
})
Map
var m = new Map([['a',1],['b',2]]);
m.foEache(function(value,key,map){
console.log(value);
})
Array
var a = new Array([1,2,3]);
a.forEache(funcation(element){
console.log(element);
})
3. 函数
3.1 函数的调用
定义函数的两种方式
var abs = function(){}
function abs(){}
arguments
最常用于判断参数的个数
var abs = function(a,b,c){
if(arguments === 3){
return true;
}else{
return false;
}
}
还可以通过下标获取对应的参数
var abs = function(a,b,c){
console.log(arguments[0]);
}
rest
以为js的函数允许接收很多的参数 , 对于不是必要参收的参数 , 可以使用rest
传入的参数会先绑定到a b 上面 , 剩下的参数会加到rest里面
var abs = funcation(a,b,...rest){
console.log(a);
cosnole.log(b);
console.log(rest);
}
结果
foo(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]
foo(1);
// 结果:
// a = 1
// b = undefined
// Array []
注意return语句的使用 , 因为js引擎会在每行语句的后面自动增加分号 , 建议使用 ‘use strict’ 模式来编写代码
3.2 变量的作用域和解构赋值
js的变量拥有的作用域分为全局和块级 , 全局定义为 var , 块级定义为 let
最明显的全局变量就是window , 还有一个使用的常量的定义为 const
如果一个变量是定义在函数体内的 , 那么这个变量只能让该函数来使用
var abs = function(){
var a = 123;
}
但是js中的函数是可以嵌套使用的 , 内部函数可以使用外部函数的变量 , 但是外部函数不可以使用内部函数的变量
var abs = function(){
var a=123;
var abc = function(){
var b = 234;
//函数abc可以使用外部函数的变量a
//但是外部函数abs不能使用内部函数的变量b
console.log(a);
}
}
如果函数当中存在重名 , 但是在不同的函数体内 , 不会发生冲突
解构赋值
解构赋值解决的问题
var array = ['hello', 'JavaScript', 'ES6'];
var x = array[0];
var y = array[1];
var z = array[2];
现在 , 在es6中 , 可以使用解构赋值 , 直接对多个变量完成赋值
var [a,b,c] = [1,2,3];
console.log(a);// 1
console.log(b);// 2
console.log(c);// 3
对数组元素也可以使用解构赋值
var [a,[b,c]] = [1,[2,3]];
解构赋值还可以忽略某些元素
var [,,z] = [1,2,3];
console.log(z);
还可以对对象元素解构赋值
'use strict';
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};
var {name, age, passport} = person;
console.log(name+age+passport);
3.3 方法
在一个对象中绑定函数成为方法
var person = {
name: "caiji",
age: 12,
birth:function(){
var y = new Date().getFullYear();
return y-this.age;
}
}
在birth这个方法中 , 使用到了this , this指向当前对象的某个属性 , 使用this才可以使用age属性
也可以将方法写在对象的外部
var getage = function(){
var y = new Date().getFullYear();
return y-this.age;
}
var person = {
name: "caiji",
age: 12,
//属性在调用方法的时候不需要()
birth:getage
}
在属性的方法中定义函数
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var that = this; // 在方法内部一开始就捕获this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth; // 用that而不是this
}
return getAgeFromBirth();
}
};
xiaoming.age(); // 25
var that = this; // 在方法内部一开始就捕获this
否则this就会指向undefined或者window
apply
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 参数为空
可以利用apple()来指定函数使用的对象
另一个与apply()
类似的方法是call()
,唯一区别是:
apply()
把参数打包成Array
再传入;call()
把参数按顺序传入。
比如调用Math.max(3, 5, 4)
,分别用apply()
和call()
实现如下:
Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5
对普通函数调用,我们通常把this
绑定为null
。
4. 高阶函数
4.1 map/readuce
map
将数组中的每个元素执行一个固定的操作
'use strict';
function pow(x) {
return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
console.log(results);//1,4,9,16,25,36,49,64,81
将array中的所有数字转换为字符串
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(String); // ['1', '2', '3', '4', '5', '6', '7', '8', '9']
reduce
这个函数必须接收两个参数,reduce()
把结果继续和序列的下一个元素做累积计算,其效果就是:
var arr = [1, 3, 5, 7, 9];
arr.reduce(function (x, y) {
return x + y;
}); // 25
4.2 filter
filter也是一个常用的操作 , 可以将array的某些元素过滤到 , 然后返回剩下的元素
filter()
把传入的函数依次作用于每个元素,然后根据返回值是true
还是false
决定保留还是丢弃该元素。
var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function (x) {
return x % 2 !== 0;
});
r; // [1, 5, 9, 15]
filter实现的是筛选
4.3 sort
使用默认的sort 可能会将10 排在 2 前面 , 因为sort会将数字转会为字符比较第一位
如果不知道sort()
方法的默认排序规则,直接对数字排序,绝对栽进坑里!
幸运的是,sort()
方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。
//从小到大
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return -1;
}
if (x > y) {
return 1;
}
return 0;
});
console.log(arr); // [1, 2, 10, 20]
//从大到小
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return 1;
}
if (x > y) {
return -1;
}
return 0;
}); // [20, 10, 2, 1]
4.4 Array
every
every()
方法可以判断数组的所有元素是否满足测试条件。
var arr = ['Apple', 'pear', 'orange'];
console.log(arr.every(function (s) {
return s.length > 0;
})); // true, 因为每个元素都满足s.length>0
console.log(arr.every(function (s) {
return s.toLowerCase() === s;
})); // false, 因为不是每个元素都全部是小写
find
find()
方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined
:
var arr = ['Apple', 'pear', 'orange'];
console.log(arr.find(function (s) {
return s.toLowerCase() === s;
})); // 'pear', 因为pear全部是小写
console.log(arr.find(function (s) {
return s.toUpperCase() === s;
})); // undefined, 因为没有全部是大写的元素
findIndex
findIndex()
和find()
类似,也是查找符合条件的第一个元素,不同之处在于findIndex()
会返回这个元素的索引,如果没有找到,返回-1
:
var arr = ['Apple', 'pear', 'orange'];
console.log(arr.findIndex(function (s) {
return s.toLowerCase() === s;
})); // 1, 因为'pear'的索引是1
console.log(arr.findIndex(function (s) {
return s.toUpperCase() === s;
})); // -1
forEach
forEach()
和map()
类似,它也把每个元素依次作用于传入的函数,但不会返回新的数组。forEach()
常用于遍历数组,因此,传入的函数不需要返回值:
var arr = ['Apple', 'pear', 'orange'];
arr.forEach(console.log); // 依次打印每个元素
5. 函数2
5.1 闭包函数
6. 标准对象
6.1 Date
在JavaScript中,Date
对象用来表示日期和时间。
要获取系统当前时间,用:
var now = new Date();
now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
now.getFullYear(); // 2015, 年份
now.getMonth(); // 5, 月份,注意月份范围是0~11,5表示六月
now.getDate(); // 24, 表示24号
now.getDay(); // 3, 表示星期三
now.getHours(); // 19, 24小时制
now.getMinutes(); // 49, 分钟
now.getSeconds(); // 22, 秒
now.getMilliseconds(); // 875, 毫秒数
now.getTime(); // 1435146562875, 以number形式表示的时间戳
注意,当前时间是浏览器从本机操作系统获取的时间,所以不一定准确,因为用户可以把当前时间设定为任何值。
传入date , 返回时间戳
var d = Date.parse('2015-06-24T19:49:22.875+08:00');
d; // 1435146562875
时区
var d = new Date(1435146562875);
d.toLocaleString(); // '2015/6/24 下午7:49:22',本地时间(北京时区+8:00),显示的字符串与操作系统设定的格式有关
d.toUTCString(); // 'Wed, 24 Jun 2015 11:49:22 GMT',UTC时间,与本地时间相差8小时
6.2 json
把对象转换为json格式使用 JSON.stringify(object)
'use strict';
var xiaoming = {
name: '小明',
age: 14,
gender: true,
height: 1.65,
grade: null,
'middle-school': '\"W3C\" Middle School',
skills: ['JavaScript', 'Java', 'Python', 'Lisp']
};
var s = JSON.stringify(xiaoming);
console.log(s);
如果要让输出的好看 , 可以加上参数
JSON.stringify(xiaoming, null, ' ');
如果在输出前对属性做修改 , 可以使用函数
function convert(key, value) {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value;
}
JSON.stringify(xiaoming, convert, ' ');
第二个参数用于控制如何筛选对象的键值,如果我们只想输出指定的属性,可以传入Array
:
JSON.stringify(xiaoming, ['name', 'skills'], ' ');
如果我们还想要精确控制如何序列化小明,可以给xiaoming
定义一个toJSON()
的方法,直接返回JSON应该序列化的数据:
var xiaoming = {
name: '小明',
age: 14,
gender: true,
height: 1.65,
grade: null,
'middle-school': '\"W3C\" Middle School',
skills: ['JavaScript', 'Java', 'Python', 'Lisp'],
toJSON: function () {
return { // 只输出name和age,并且改变了key:
'Name': this.name,
'Age': this.age
};
}
};
JSON.stringify(xiaoming); // '{"Name":"小明","Age":14}'
反序列化
拿到一个JSON格式的字符串,我们直接用JSON.parse()
把它变成一个JavaScript对象:
JSON.parse('[1,2,3,true]'); // [1, 2, 3, true]
JSON.parse('{"name":"小明","age":14}'); // Object {name: '小明', age: 14}
JSON.parse('true'); // true
JSON.parse('123.45'); // 123.45
JSON.parse()
还可以接收一个函数,用来转换解析出的属性:
var obj = JSON.parse('{"name":"小明","age":14}', function (key, value) {
if (key === 'name') {
return value + '同学';
}
return value;
});
console.log(JSON.stringify(obj)); // {name: '小明同学', age: 14}
7. 浏览器
7.1 window
window
对象不但充当全局作用域,而且表示浏览器窗口。
window
对象有innerWidth
和innerHeight
属性,可以获取浏览器窗口的内部宽度和高度。内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。
兼容性:IE<=8不支持。
对应的,还有一个outerWidth
和outerHeight
属性,可以获取浏览器窗口的整个宽高。
7.2 navigtor
navigator
对象表示浏览器的信息,最常用的属性包括:
- navigator.appName:浏览器名称;
- navigator.appVersion:浏览器版本;
- navigator.language:浏览器设置的语言;
- navigator.platform:操作系统类型;
- navigator.userAgent:浏览器设定的
User-Agent
字符串。
7.3 screen
screen
对象表示屏幕的信息,常用的属性有:
- screen.width:屏幕宽度,以像素为单位;
- screen.height:屏幕高度,以像素为单位;
- screen.colorDepth:返回颜色位数,如8、16、24。
7.4 location
location
对象表示当前页面的URL信息。例如,一个完整的URL:
http://www.example.com:8080/path/index.html?a=1&b=2#TOP
可以用location.href
获取。要获得URL各个部分的值,可以这么写:
location.protocol; // 'http'
location.host; // 'www.example.com'
location.port; // '8080'
location.pathname; // '/path/index.html'
location.search; // '?a=1&b=2'
location.hash; // 'TOP'
要加载一个新页面,可以调用location.assign()
。如果要重新加载当前页面,调用location.reload()
方法非常方便。
7.5 document
ookie是由服务器发送的key-value标示符。因为HTTP协议是无状态的,但是服务器要区分到底是哪个用户发过来的请求,就可以用Cookie来区分。当一个用户成功登录后,服务器发送一个Cookie给浏览器,例如user=ABC123XYZ(加密的字符串)...
,此后,浏览器访问该网站时,会在请求头附上这个Cookie,服务器根据Cookie即可区分出用户。
Cookie还可以存储网站的一些设置,例如,页面显示的语言等等。
JavaScript可以通过document.cookie
读取到当前页面的Cookie:
document.cookie; // 'v=123; remember=true; prefer=zh'
由于JavaScript能读取到页面的Cookie,而用户的登录信息通常也存在Cookie中,这就造成了巨大的安全隐患,这是因为在HTML页面中引入第三方的JavaScript代码是允许的:
<!-- 当前页面在wwwexample.com -->
<html>
<head>
<script src="http://www.foo.com/jquery.js"></script>
</head>
...
</html>
如果引入的第三方的JavaScript中存在恶意代码,则www.foo.com
网站将直接获取到www.example.com
网站的用户登录信息。
为了解决这个问题,服务器在设置Cookie时可以使用httpOnly
,设定了httpOnly
的Cookie将不能被JavaScript读取。这个行为由浏览器实现,主流浏览器均支持httpOnly
选项,IE从IE6 SP1开始支持。
为了确保安全,服务器端在设置Cookie时,应该始终坚持使用httpOnly
。
7.6 history
history
对象保存了浏览器的历史记录,JavaScript可以调用history
对象的back()
或forward ()
,相当于用户点击了浏览器的“后退”或“前进”按钮。
这个对象属于历史遗留对象,对于现代Web页面来说,由于大量使用AJAX和页面交互,简单粗暴地调用history.back()
可能会让用户感到非常愤怒。
新手开始设计Web页面时喜欢在登录页登录成功时调用history.back()
,试图回到登录前的页面。这是一种错误的方法。
任何情况,你都不应该使用history
这个对象了。
8. 操作DOM
innerText 只会修改文本内容 , 不会增加html标签 , 原模原样输出
innerHtml 可以增加html元素 , 可以增加style等标签和文本
修改css的办法:
// 获取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 设置CSS:
p.style.color = '#ff0000';
p.style.fontSize = '20px';
p.style.paddingTop = '2em';
子节点.appendChild(父节点)
会将子节点追加在父节点内部的最后
动态创建一个节点然后添加到DOM树中,可以实现很多功能。举个例子,下面的代码动态创建了一个节点,然后把它添加到
节点的末尾,这样就动态地给文档添加了新的CSS定义:
var d = document.createElement('style');
d.setAttribute('type', 'text/css');
d.innerHTML = 'p { color: red }';
document.getElementsByTagName('head')[0].appendChild(d);
parentElement.insertBefore(newElement, referenceElement);
如果我们要把子节点插入到指定的位置怎么办?可以使用parentElement.insertBefore(newElement, referenceElement);
,子节点会插入到referenceElement
之前。
removeChild
要删除一个节点,首先要获得该节点本身以及它的父节点,然后,调用父节点的removeChild
把自己删掉:
// 拿到待删除节点:
var self = document.getElementById('to-be-removed');
// 拿到父节点:
var parent = self.parentElement;
// 删除:
var removed = parent.removeChild(self);
removed === self; // true
当你遍历一个父节点的子节点并进行删除操作时,要注意,children
属性是一个只读属性,并且它在子节点变化时会实时更新。
var parent = document.getElementById('parent');
parent.removeChild(parent.children[0]);
parent.removeChild(parent.children[1]); // <-- 浏览器报错
//正确写法
var parent = document.getElementById('parent');
parent.removeChild(parent.children[0]);
parent.removeChild(parent.children[0]);
9. 操作表单
用JavaScript操作表单和操作DOM是类似的,因为表单本身也是DOM树。
不过表单的输入框、下拉框等可以接收用户输入,所以用JavaScript来操作表单,可以获得用户输入的内容,或者对一个输入框设置新的内容。
HTML表单的输入控件主要有以下几种:
- 文本框,对应的
<input type="text">
,用于输入文本; - 口令框,对应的
<input type="password">
,用于输入口令; - 单选框,对应的
<input type="radio">
,用于选择一项; - 复选框,对应的
<input type="checkbox">
,用于选择多项; - 下拉框,对应的
<select>
,用于选择一项; - 隐藏文本,对应的
<input type="hidden">
,用户不可见,但表单提交时会把隐藏文本发送到服务器。
// <label><input type="radio" name="weekday" id="monday" value="1"> Monday</label>
// <label><input type="radio" name="weekday" id="tuesday" value="2"> Tuesday</label>
var mon = document.getElementById('monday');
var tue = document.getElementById('tuesday');
mon.value; // '1'
tue.value; // '2'
mon.checked; // true或者false
tue.checked; // true或者false
获取表单的值\修改表单的值
document.getElementById("a").value;
document.getElementById("a").value = 123 ;
html5的新控件
HTML5新增了大量标准控件,常用的包括date
、datetime
、datetime-local
、color
等,它们都使用``标签:
<input type="date" value="2015-07-01">
<input type="datetime-local" value="2015-07-01T02:03:04">
<input type="color" value="#ff0000">
不支持HTML5的浏览器无法识别新的控件,会把它们当做type="text"
来显示。支持HTML5的浏览器将获得格式化的字符串。例如,type="date"
类型的input
的value
将保证是一个有效的YYYY-MM-DD
格式的日期,或者空字符串。
表单提交
提交表单有两种方式:
- 为button绑定一个onclick事件 , 这个办法扰乱了浏览器对form的正常提交
<!-- HTML -->
<form id="test-form">
<input type="text" name="test">
<button type="button" onclick="doSubmitForm()">Submit</button>
</form>
<script>
function doSubmitForm() {
var form = document.getElementById('test-form');
// 可以在此修改form的input...
// 提交form:
form.submit();
}
</script>
- 响应form本身的onsubmit事件
<!-- HTML -->
<form id="test-form" onsubmit="return checkForm()">
<input type="text" name="test">
<button type="submit">Submit</button>
</form>
<script>
function checkForm() {
var form = document.getElementById('test-form');
// 可以在此修改form的input...
// 继续下一步:
return true;
}
</script>
注意要return true
来告诉浏览器继续提交,如果return false
,浏览器将不会继续提交form,这种情况通常对应用户输入有误,提示用户错误信息后终止提交form。
在检查和修改input
时,要充分利用type=""hidden
来传递数据。
例如,很多登录表单希望用户输入用户名和口令,但是,安全考虑,提交表单时不传输明文口令,而是口令的MD5。普通JavaScript开发人员会直接修改<input>
:
<!-- HTML -->
<form id="login-form" method="post" onsubmit="return checkForm()">
<input type="text" id="username" name="username">
<input type="password" id="password" name="password">
<button type="submit">Submit</button>
</form>
<script>
function checkForm() {
var pwd = document.getElementById('password');
// 把用户输入的明文变为MD5:
pwd.value = toMD5(pwd.value);
// 继续下一步:
return true;
}
</script>
这个做法看上去没啥问题,但用户输入了口令提交时,口令框的显示会突然从几个*
变成32个*
(因为MD5有32个字符)。
要想不改变用户的输入,可以利用input type="hidden"
实现:
<!-- HTML -->
<form id="login-form" method="post" onsubmit="return checkForm()">
<input type="text" id="username" name="username">
<input type="password" id="input-password">
<input type="hidden" id="md5-password" name="password">
<button type="submit">Submit</button>
</form>
<script>
function checkForm() {
var input_pwd = document.getElementById('input-password');
var md5_pwd = document.getElementById('md5-password');
// 把用户输入的明文变为MD5:
md5_pwd.value = toMD5(input_pwd.value);
// 继续下一步:
return true;
}
</script>
10. 操作文件
在HTML表单中,可以上传文件的唯一控件就是``。
注意:当一个表单包含input type="file"
时,表单的enctype
必须指定为multipart/form-data
,method
必须指定为post
,浏览器才能正确编码并以multipart/form-data
格式发送表单的数据。
出于安全考虑,浏览器只允许用户点击<input type="file">
来选择本地文件,用JavaScript对<input type="file">
的value
赋值是没有任何效果的。当用户选择了上传某个文件后,JavaScript也无法获得该文件的真实路径:
var f = document.getElementById('test-file-upload');
var filename = f.value; // 'C:\fakepath\test.png'
if (!filename || !(filename.endsWith('.jpg') || filename.endsWith('.png') || filename.endsWith('.gif'))) {
alert('Can only upload image file.');
return false;
}
File API
由于JavaScript对用户上传的文件操作非常有限,尤其是无法读取文件内容,使得很多需要操作文件的网页不得不用Flash这样的第三方插件来实现。
随着HTML5的普及,新增的File API允许JavaScript读取文件内容,获得更多的文件信息。
HTML5的File API提供了File
和FileReader
两个主要对象,可以获得文件信息并读取文件。
下面的例子演示了如何读取用户选取的图片文件,并在一个div
中预览图像:
var
fileInput = document.getElementById('test-image-file'),
info = document.getElementById('test-file-info'),
preview = document.getElementById('test-image-preview');
// 监听change事件:
fileInput.addEventListener('change', function () {
// 清除背景图片:
preview.style.backgroundImage = '';
// 检查文件是否选择:
if (!fileInput.value) {
info.innerHTML = '没有选择文件';
return;
}
// 获取File引用:
var file = fileInput.files[0];
// 获取File信息:
info.innerHTML = '文件: ' + file.name + '<br>' +
'大小: ' + file.size + '<br>' +
'修改: ' + file.lastModifiedDate;
if (file.type !== 'image/jpeg' && file.type !== 'image/png' && file.type !== 'image/gif') {
alert('不是有效的图片文件!');
return;
}
// 读取文件:
var reader = new FileReader();
reader.onload = function(e) {
var
data = e.target.result; // 'data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...'
preview.style.backgroundImage = 'url(' + data + ')';
};
// 以DataURL的形式读取文件:
reader.readAsDataURL(file);
});
上面的代码演示了如何通过HTML5的File API读取文件内容。以DataURL的形式读取到的文件是一个字符串,类似于data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...
,常用于设置图像。如果需要服务器端处理,把字符串base64,
后面的字符发送给服务器并用Base64解码就可以得到原始文件的二进制内容。
回调
上面的代码还演示了JavaScript的一个重要的特性就是单线程执行模式。在JavaScript中,浏览器的JavaScript执行引擎在执行JavaScript代码时,总是以单线程模式执行,也就是说,任何时候,JavaScript代码都不可能同时有多于1个线程在执行。
你可能会问,单线程模式执行的JavaScript,如何处理多任务?
在JavaScript中,执行多任务实际上都是异步调用,比如上面的代码:
reader.readAsDataURL(file);
就会发起一个异步操作来读取文件内容。因为是异步操作,所以我们在JavaScript代码中就不知道什么时候操作结束,因此需要先设置一个回调函数:
reader.onload = function(e) {
// 当文件读取完成后,自动调用此函数:
};
当文件读取完成后,JavaScript引擎将自动调用我们设置的回调函数。执行回调函数时,文件已经读取完毕,所以我们可以在回调函数内部安全地获得文件内容。
10. Ajax
用JavaScript写一个完整的AJAX代码并不复杂,但是需要注意:AJAX请求是异步执行的,也就是说,要通过回调函数获得响应。
在现代浏览器上写AJAX主要依靠XMLHttpRequest
对象:
function success(text) {
var textarea = document.getElementById('test-response-text');
textarea.value = text;
}
function fail(code) {
var textarea = document.getElementById('test-response-text');
textarea.value = 'Error code: ' + code;
}
var request = new XMLHttpRequest(); // 新建XMLHttpRequest对象
request.onreadystatechange = function () { // 状态发生变化时,函数被回调
if (request.readyState === 4) { // 成功完成
// 判断响应结果:
if (request.status === 200) {
// 成功,通过responseText拿到响应的文本:
return success(request.responseText);
} else {
// 失败,根据响应码判断失败原因:
return fail(request.status);
}
} else {
// HTTP请求还在继续...
}
}
// 发送请求:
request.open('GET', '/api/categories');
request.send();
alert('请求已发送,请等待响应...');
11. jQuery
jquery实际上就是一个写好的js函数库
$("#id");//按照id查找
$("p")//按照tag查找
$(".class")//按照class查找
11.1 操作DOM
修改text和html
jQuery对象的text()
和html()
方法分别获取节点的文本和原始HTML文本,例如,如下的HTML结构:
<!-- HTML结构 -->
<ul id="test-ul">
<li class="js">JavaScript</li>
<li name="book">Java & JavaScript</li>
</ul>
$('#test-ul li[name=book]').text(); // 'Java & JavaScript'
$('#test-ul li[name=book]').html(); // 'Java & JavaScript'
var j1 = $('#test-ul li.js');
var j2 = $('#test-ul li[name=book]');
j1.html('<span style="color: red">JavaScript</span>');
j2.text('JavaScript & ECMAScript');
修改css
<!-- HTML结构 -->
<ul id="test-css">
<li class="lang dy"><span>JavaScript</span></li>
<li class="lang"><span>Java</span></li>
<li class="lang dy"><span>Python</span></li>
<li class="lang"><span>Swift</span></li>
<li class="lang dy"><span>Scheme</span></li>
</ul>
要高亮显示动态语言,调用jQuery对象的css('name', 'value')
方法,我们用一行语句实现:
$('#test-css li.dy>span').css('background-color', '#ffd351').css('color', 'red');
var div = $('#test-div');
div.css('color'); // '#000033', 获取CSS属性
div.css('color', '#336699'); // 设置CSS属性
div.css('color', ''); // 清除CSS属性
显示和隐藏DOM
var a = $('a[target=_blank]');
a.hide(); // 隐藏
a.show(); // 显示
注意,隐藏DOM节点并未改变DOM树的结构,它只影响DOM节点的显示。这和删除DOM节点是不同的。
attr() and removeAttr()
attr()
和removeAttr()
方法用于操作DOM节点的属性:
// <div id="test-div" name="Test" start="1">...</div>
var div = $('#test-div');
div.attr('data'); // undefined, 属性不存在
div.attr('name'); // 'Test'
div.attr('name', 'Hello'); // div的name属性变为'Hello'
div.removeAttr('name'); // 删除name属性
div.attr('name'); // undefined
prop()
方法和attr()
类似,但是HTML5规定有一种属性在DOM节点中可以没有值,只有出现与不出现两种,例如:
<input id="test-radio" type="radio" name="test" checked value="1">
等价于:
<input id="test-radio" type="radio" name="test" checked="checked" value="1">
attr()
和prop()
对于属性checked
处理有所不同:
var radio = $('#test-radio');
radio.attr('checked'); // 'checked'
radio.prop('checked'); // true
prop()
返回值更合理一些。不过,用is()
方法判断更好:
var radio = $('#test-radio');
radio.is(':checked'); // true
类似的属性还有selected
,处理时最好用is(':selected')
。
操作表单
对于表单元素,jQuery对象统一提供val()
方法获取和设置对应的value
属性:
/*
<input id="test-input" name="email" value="">
<select id="test-select" name="city">
<option value="BJ" selected>Beijing</option>
<option value="SH">Shanghai</option>
<option value="SZ">Shenzhen</option>
</select>
<textarea id="test-textarea">Hello</textarea>
*/
var
input = $('#test-input'),
select = $('#test-select'),
textarea = $('#test-textarea');
input.val(); // 'test'
input.val('abc@example.com'); // 文本框的内容已变为abc@example.com
select.val(); // 'BJ'
select.val('SH'); // 选择框已变为Shanghai
textarea.val(); // 'Hello'
textarea.val('Hi'); // 文本区域已更新为'Hi'
可见,一个val()
就统一了各种输入框的取值和赋值的问题。
11.2 事件
鼠标事件
- click: 鼠标单击时触发;
- dblclick:鼠标双击时触发;
- mouseenter:鼠标进入时触发;
- mouseleave:鼠标移出时触发;
- mousemove:鼠标在DOM内部移动时触发;
- hover:鼠标进入和退出时触发两个函数,相当于mouseenter加上mouseleave。
键盘事件
键盘事件仅作用在当前焦点的DOM上,通常是和
。
- keydown:键盘按下时触发;
- keyup:键盘松开时触发;
- keypress:按一次键后触发。
其他事件
- focus:当DOM获得焦点时触发;
- blur:当DOM失去焦点时触发;
- change:当
、
或``的内容改变时触发; - submit:当``提交时触发;
- ready:当页面被载入并且DOM树完成初始化后触发。
其中,ready
仅作用于document
对象。由于ready
事件在DOM完成初始化后触发,且只触发一次,所以非常适合用来写其他的初始化代码。假设我们想给一个``表单绑定submit
事件,下面的代码没有预期的效果:
<html>
<head>
<script>
// 代码有误:
$('#testForm).on('submit', function () {
alert('submit!');
});
</script>
</head>
<body>
<form id="testForm">
...
</form>
</body>
因为JavaScript在此执行的时候,``尚未载入浏览器,所以$('#testForm)
返回[]
,并没有绑定事件到任何DOM上。
所以我们自己的初始化代码必须放到document
对象的ready
事件中,保证DOM已完成初始化:
<html>
<head>
<script>
$(document).on('ready', function () {
$('#testForm).on('submit', function () {
alert('submit!');
});
});
</script>
</head>
<body>
<form id="testForm">
...
</form>
</body>
这样写就没有问题了。因为相关代码会在DOM树初始化后再执行。
由于ready
事件使用非常普遍,所以可以这样简化:
$(document).ready(function () {
// on('submit', function)也可以简化:
$('#testForm).submit(function () {
alert('submit!');
});
});
甚至还可以再简化为:
$(function () {
// init...
});
11.3 动画
使用jQuery实现动画,代码已经简单得不能再简化了:只需要一行代码!
show / hide
直接以无参数形式调用show()
和hide()
,会显示和隐藏DOM元素。但是,只要传递一个时间参数进去,就变成了动画:
var div = $('#test-show-hide');
div.hide(3000); // 在3秒钟内逐渐消失
时间以毫秒为单位,但也可以是'slow'
,'fast'
这些字符串:
var div = $('#test-show-hide');
div.show('slow'); // 在0.6秒钟内逐渐显示
toggle()
方法则根据当前状态决定是show()
还是hide()
。
slideUp / slideDown
你可能已经看出来了,show()
和hide()
是从左上角逐渐展开或收缩的,而slideUp()
和slideDown()
则是在垂直方向逐渐展开或收缩的。
slideUp()
把一个可见的DOM元素收起来,效果跟拉上窗帘似的,slideDown()
相反,而slideToggle()
则根据元素是否可见来决定下一步动作:
fadeIn/fadeOut
fadeIn()
和fadeOut()
的动画效果是淡入淡出,也就是通过不断设置DOM元素的opacity
属性来实现,而fadeToggle()
则根据元素是否可见来决定下一步动作:
var div = $('#test-fade');
div.fadeOut('slow'); // 在0.6秒内淡出
三种动画特效的总结:
<body>
<div style="width: 200px; height: 300px; background-color: yellow;"></div>
<button onclick="hid()">hide消失</button>
<button onclick="show1()">show出现1</button>
<button onclick="show2()">show出现2</button>
<button onclick="toggle()">toggle</button>
<br/>
<br/>
<button onclick="slideup1()">slideup</button>
<button onclick="slidedown1()">slidedown</button>
<button onclick="slidetoggle1()">slidetoggle</button>
<br/>
<br/>
<button onclick="fadein()">fadein</button>
<button onclick="fadeout()">fadeout</button>
<button onclick="fadetoggle()">fadetoggle</button>
<script type="text/javascript">
var hid = function(){
$("div").hide(3000);
}
var show1 = function(){
$("div").show("fast");
}
var show2 = function(){
$("div").show("slow");
}
var toggle = function(){
$("div").toggle(3000);
}
var slideup1 = function(){
$("div").slideUp("slow");
}
var slidedown1 = function(){
$("div").slideDown(3000);
}
var slidetoggle1 = function(){
$("div").slideToggle(3000);
}
var fadein = function(){
$("div").fadeIn("slow");
}
var fadeout = function(){
$("div").fadeOut("slow");
}
var fadetoggle = function(){
$("div").fadeToggle(1000);
}
</script>
</body>
show/hidde 动画从左上角展开或收缩
slideUp/slideDown/slideToggle 动画垂直方向展开或者收缩
fadeIn/fadeOut/fadeToggle 动画从中心展开或者缩放
自定义动画
如果上述动画效果还不能满足你的要求,那就祭出最后大招:animate()
,它可以实现任意动画效果,我们需要传入的参数就是DOM元素最终的CSS状态和时间,jQuery在时间段内不断调整CSS直到达到我们设定的值:
var div = $('#test-animate');
div.animate({
opacity: 0.25,
width: '256px',
height: '256px'
}, 3000); // 在3秒钟内CSS过渡到设定值
animate()
还可以再传入一个函数,当动画结束时,该函数将被调用:
var div = $('#test-animate');
div.animate({
opacity: 0.25,
width: '256px',
height: '256px'
}, 3000, function () {
console.log('动画已结束');
// 恢复至初始状态:
$(this).css('opacity', '1.0').css('width', '128px').css('height', '128px');
});
串行动画
jQuery的动画效果还可以串行执行,通过delay()
方法还可以实现暂停,这样,我们可以实现更复杂的动画效果,而代码却相当简单:
var div = $('#test-animates');
// 动画效果:slideDown - 暂停 - 放大 - 暂停 - 缩小
div.slideDown(2000)
.delay(1000)
.animate({
width: '256px',
height: '256px'
}, 2000)
.delay(1000)
.animate({
width: '128px',
height: '128px'
}, 2000);
}
</script>
11.4 ajax
用JavaScript写AJAX前面已经介绍过了,主要问题就是不同浏览器需要写不同代码,并且状态和错误处理写起来很麻烦。
用jQuery的相关对象来处理AJAX,不但不需要考虑浏览器问题,代码也能大大简化
ajax
jQuery在全局对象jQuery
(也就是$
)绑定了ajax()
函数,可以处理AJAX请求。ajax(url, settings)
函数需要接收一个URL和一个可选的settings
对象,常用的选项如下:
- async:是否异步执行AJAX请求,默认为
true
,千万不要指定为false
; - method:发送的Method,缺省为
'GET'
,可指定为'POST'
、'PUT'
等; - contentType:发送POST请求的格式,默认值为
'application/x-www-form-urlencoded; charset=UTF-8'
,也可以指定为text/plain
、application/json
; - data:发送的数据,可以是字符串、数组或object。如果是GET请求,data将被转换成query附加到URL上,如果是POST请求,根据contentType把data序列化成合适的格式;
- headers:发送的额外的HTTP头,必须是一个object;
- dataType:接收的数据格式,可以指定为
'html'
、'xml'
、'json'
、'text'
等,缺省情况下根据响应的Content-Type
猜测。
下面的例子发送一个GET请求,并返回一个JSON格式的数据:
var jqxhr = $.ajax('/api/categories', {
dataType: 'json'
});
// 请求已经发送了
不过,如何用回调函数处理返回的数据和出错时的响应呢?
还记得Promise对象吗?jQuery的jqXHR对象类似一个Promise对象,我们可以用链式写法来处理各种回调:
'use strict';
function ajaxLog(s) {
var txt = $('#test-response-text');
txt.val(txt.val() + '\n' + s);
}
$('#test-response-text').val('');
var jqxhr = $.ajax('/api/categories', {
dataType: 'json'
}).done(function (data) {
ajaxLog('成功, 收到的数据: ' + JSON.stringify(data));
}).fail(function (xhr, status) {
ajaxLog('失败: ' + xhr.status + ', 原因: ' + status);
}).always(function () {
ajaxLog('请求完成: 无论成功或失败都会调用');
});
get
对常用的AJAX操作,jQuery提供了一些辅助方法。由于GET请求最常见,所以jQuery提供了get()
方法,可以这么写:
var jqxhr = $.get('/path/to/resource', {
name: 'Bob Lee',
check: 1
});
第二个参数如果是object,jQuery自动把它变成query string然后加到URL后面,实际的URL是:
/path/to/resource?name=Bob%20Lee&check=1
这样我们就不用关心如何用URL编码并构造一个query string了。
post
post()
和get()
类似,但是传入的第二个参数默认被序列化为application/x-www-form-urlencoded
:
var jqxhr = $.post('/path/to/resource', {
name: 'Bob Lee',
check: 1
});
实际构造的数据name=Bob%20Lee&check=1
作为POST的body被发送。
getJson
由于JSON用得越来越普遍,所以jQuery也提供了getJSON()
方法来快速通过GET获取一个JSON对象:
var jqxhr = $.getJSON('/path/to/resource', {
name: 'Bob Lee',
check: 1
}).done(function (data) {
// data已经被解析为JSON对象了
});
11.5 扩展
自己编写jquery插件
给jQuery对象绑定一个新方法是通过扩展$.fn
对象实现的。让我们来编写第一个扩展——highlight1()
//第一个版本
$.fn.highlight1 = function () {
// this已绑定为当前jQuery对象:
this.css('backgroundColor', '#fffceb').css('color', '#d85030');
return this;
}
$('#id').highlight1();
//第二个版本
$.fn.highlight2 = function (options) {
// 要考虑到各种情况:
// options为undefined
// options只有部分key
var bgcolor = options && options.backgroundColor || '#fffceb';
var color = options && options.color || '#d85030';
this.css('backgroundColor', bgcolor).css('color', color);
return this;
}
$('#test-highlight2 span').highlight2({
backgroundColor: '#00a8e6',
color: '#ffffff'
});
//第三个版本
$.fn.highlight = function (options) {
// 合并默认值和用户设定值:
var opts = $.extend({}, $.fn.highlight.defaults, options);
this.css('backgroundColor', opts.backgroundColor).css('color', opts.color);
return this;
}
// 设定默认值:
$.fn.highlight.defaults = {
color: '#d85030',
backgroundColor: '#fff8de'
}
//用户设置默认值
$.fn.highlight.defaults.color = '#fff';
$.fn.highlight.defaults.backgroundColor = '#000';
$.fn.highlight.defaults.color = '#659f13';
$.fn.highlight.defaults.backgroundColor = '#f2fae3';
$('#test-highlight p:first-child span').highlight();
$('#test-highlight p:last-child span').highlight({
color: '#dd1144'
});
注意到函数内部的this
在调用时被绑定为jQuery对象,所以函数内部代码可以正常调用所有jQuery对象的方法。
细心的童鞋可能发现了,为什么最后要return this;
?因为jQuery对象支持链式操作,我们自己写的扩展方法也要能继续链式下去
最终,我们得出编写一个jQuery插件的原则:
- 给
$.fn
绑定函数,实现插件的代码逻辑; - 插件函数最后要
return this;
以支持链式调用; - 插件函数要有默认值,绑定在
$.fn..defaults
上; - 用户在调用时可传入设定值以便覆盖默认值