参考网站:https://www.liaoxuefeng.com/wiki/1022910821149312
JavaScript教程通俗易懂
一、JavaScript概述
js大部分是做:网络通信 数据交互 操作DOM
1.1 前言
- JavaScript是世界上最流行的脚本语言,因为你在电脑、手机、平板上浏览的所有的网页,以及无数基于HTML5的手机App,交互逻辑都是由JavaScript驱动的。
- 简单地说,JavaScript是一种运行在浏览器中的解释型的编程语言。
- 那么问题来了,为什么我们要学JavaScript?尤其是当你已经掌握了某些其他编程语言如Java、C++的情况下。简单粗暴的回答就是:因为你没有选择。在Web世界里,只有JavaScript能跨平台、跨浏览器驱动网页, 与用户交互。
- Flash背后的ActionScript曾经流行过一阵子,不过随着移动应用的兴起,没有人用Flash开发手机App, 所以它目前已经边缘化了。相反,随着HTML5在PC和移动端越来越流行,JavaScript变得更加重要了。 并且,新兴的Node.js把JavaScript引入到了服务器端,JavaScript已经变成了全能型选手。
- JavaScript一度被认为是一种玩具编程语言,它有很多缺陷,所以不被大多数后端开发人员所重视。 很多人认为,写JavaScript代码很简单,并且JavaScript只是为了在网页上添加一点交互和动画效果。 但这是完全错误的理解。JavaScript确实很容易上手,但其精髓却不为大多数开发人员所熟知。 编写高质量的JavaScript代码更是难上加难。
- 一个合格的开发人员应该精通JavaScript和其他编程语言。如果你已经掌握了其他编程语言, 或者你还什么都不会,请立刻开始学习JavaScript,不要被Web时代所淘汰。
1.2 JavaScript历史
- 要了解JavaScript,我们首先要回顾一下JavaScript的诞生。
- 在上个世纪的1995年,当时的网景公司正凭借其Navigator浏览器成为Web时代开启时最著名的第一代互联网公司。
- 由于网景公司希望能在静态HTML页面上添加一些动态效果,于是叫Brendan Eich这哥们在两周之内设计出了JavaScript语言。你没看错,这哥们只用了10天时间。
- 为什么起名叫JavaScript?原因是当时Java语言非常红火,所以网景公司希望借Java的名气来推广,但事实上JavaScript除了语法上有点像Java,其他部分基本上没啥关系。
1.3 ECMAScript
- 因为网景开发了JavaScript,一年后微软又模仿JavaScript开发了JScript,为了让JavaScript成为全球标准,几个公司联合ECMA(European Computer Manufacturers Association)组织定制了JavaScript语言的标准,被称为ECMAScript标准。所以简单说来就是,ECMAScript是一种语言标准,而JavaScript是网景公司对ECMAScript标准的一种实现。
- 那为什么不直接把JavaScript定为标准呢?因为JavaScript是网景的注册商标。
- 不过大多数时候,我们还是用JavaScript这个词。如果你遇到ECMAScript这个词,简单把它替换为JavaScript就行了。
1.4 JavaScript版本
- JavaScript语言是在10天时间内设计出来的,虽然语言的设计者水平非常NB,但谁也架不住“时间紧,任务重”,所以,JavaScript有很多设计缺陷,我们后面会慢慢讲到。
- 此外,由于JavaScript的标准——ECMAScript在不断发展,最新版ECMAScript 6标准(简称ES6)已经在2015年6月正式发布了,所以,讲到JavaScript的版本,实际上就是说它实现了ECMAScript标准的哪个版本。
- 由于浏览器在发布时就确定了JavaScript的版本,加上很多用户还在使用IE6这种古老的浏览器,这就导致你在写JavaScript的时候,
要照顾
一下老用户,不能一上来就用最新的ES6标准写,否则,老用户的浏览器是无法运行新版本的JavaScript代码的。 - 不过,JavaScript的核心语法并没有多大变化。我们的教程会先讲JavaScript最核心的用法,然后,针对ES6讲解新增特性。
1.5 IDE推荐
<!--
可以用任何文本编辑器来编写JavaScript代码。这里我们推荐以下几种文本编辑器:
Visual Studio Code
微软出的Visual Studio Code,可以看做迷你版Visual Studio,免费!跨平台!内置 JavaScript 支持,强烈推荐使用!
Sublime Text
Sublime Text是一个好用的文本编辑器,免费,但不注册会不定时弹出提示框。
Notepad++
Notepad++也是免费的文本编辑器,但仅限Windows下使用。
WebStorm
WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉 为“Web前 端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。
IDEA
Java开发人员必备,NB!
HBuilder
HBuilder是DCloud(数字天堂)推出的一款支持HTML5的Web开发IDE。HBuilder的编写用到了 Java、 C、Web和Ruby。HBuilder本身主体是由Java编写。它基于Eclipse,所以顺其自然地兼容了Eclipse的插 件。
-->
1.6 引入JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--内部引入-->
<script>
alert("hello world");
</script>
<!--外部引入-->
<!--(注意 大坑):script一定要成对出现,否则无效 -->
<script src="js/qj.js"></script>
<!--不用显示定义type,type默认就是javascript-->
<script type="text/javascript"></script>
//可以在同一个页面中引入多个 .js 文件
还可以在页面中多次编写 <script> js代码... </script> ,浏览器按照顺序依次执行。
</head>
<body>
</body>
</html>
<!--
(1)细节:
1.可以在同一个页面中引入多个 .js 文件
2.还可以在页面中多次编写 <script> js代码... </script> ,浏览器按照顺序依次执行。
3.JavaScript代码可以直接嵌在网页的任何地方,不过通常我们都把JavaScript代码放到 head 中
-->
1.7 运行JavaScript
要让浏览器运行JavaScript,必须先有一个HTML页面,在HTML页面中引入JavaScript,然后,让浏览器
加载该HTML页面,就可以执行JavaScript代码。
1.8 调试
你需要安装Google Chrome浏览器,Chrome浏览器对开发者非常友好,可以让你方便地调试JavaScript代码。
(1)右键-检查/检查元素
(2)执行js代码
先点击“控制台(Console)“,在这个面板里可以直接输入JavaScript代码,按回车后执行。
要查看一个变量的内容,在Console中输入 console.log(a); ,回车后显示的值就是变量的内容。
(3)断点调试
开发者工具的“源码(Sources)”,断点、单步执行等高级调试
二、JavaScript基础
2.1 语法与注释
<!--
(1)JavaScript的语法和Java语言类似,每个语句以 ; 结束,语句块用 {}
但是,JavaScript并不强制要求在每个语句的结尾加 ;
浏览器中负责执行JavaScript代码的引擎会自动在每个语句的结尾补上 ;
让JavaScript引擎自动加分号在某些情况下会改变程序的语义,导致运行结果与期望不一致。在本教
程中,我们不会省略;,所有语句都会添加;。
(2)注释和Java一致 // 或 /* */
-->
2.2 数据类型、运算符和变量
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--Java 严格区分大小写 ,如果弄错了大小写,程序将报错或者运行不正常-->
<script>
//1.变量:
var score = 71;
//2.条件控制
if(score > 0 && score < 70){
alert("60-70");
} else if(score > 70 && score < 80) {
alert("70-80");
} else {
alert("other");
}
</script>
<script>
//前提:IDEA需要支持ES6语法
// "use strict" 严格检查模式,预防JavaScript的随意性导致产生的一些问题
'use strict'; // 如果浏览器支持strict模式,下面的代码将报ReferenceError错误: abc = 'Hello, world';
// console.log(abc);
//必须写在第一行
//局部变量建议使用let去定义
'use strict'
let i = 9; //ES6:let(局部) const(常量)
//不用 var 申明的变量会被视为全局变量,为了避免这一缺陷,
//所有的JavaScript代码都应该使用strict模式。
//我们在后面编写的JavaScript代码将全部采用strict模式。
</script>
</head>
<body>
</body>
</html>
<!--
(1)数据类型
1.Number : JavaScript不区分整数和浮点数,统一用Number表示,以下都是合法的Number类型
123; // 整数123
0.456; // 浮点数0.456
1.2345e3; // 科学计数法表示1.2345x1000,等同于1234.5
-99; // 负数
NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示
Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时
,就 表示为Infinity
计算机由于使用二进制,所以,有时候用十六进制表示整数比较方便,十六进制用0x前缀和0-9,a-f表
示,例如: 0xff00 , 0xa5b4c3d2 ,等等,它们和十进制表示的数值完全一样。
null和undefined
null 表示一个“空”的值,它和 0 以及空字符串 '' 不同, 0 是一个数值,
'' 表示长度为0的字符串,而 null 表示“空”。
在其他语言中,也有类似JavaScript的 null 的表示,例如Java也用 null ,Swift用 nil Python用 None 表示。
但是,在JavaScript中,还有一个和 null 类似的 undefined ,它表示“未定义”。
JavaScript的设计者希望用 null 表示一个空的值,而 undefined 表示值未定义。
事实证明,这并没有什么卵用,区分两者的意义不大。大多数情况下,我们都应该用 null 。 undefined 仅仅在判断函数参数是否传递的情况下有用。
Number可以直接做四则运算,规则和数学一致:
1 + 2; // 3
(1 + 2) * 5 / 2; // 7.5
2 / 0; // Infinity
0 / 0; // NaN
10 % 3; // 1
10.5 % 3; // 1.5
2.字符串
字符串是以单引号'或双引号"括起来的任意文本,比如 'abc' , "xyz" 等等。
请注意, '' 或 "" 本身只是一种表示方式,不是字符串的一部分,
因此,字符串 'abc' 只有a , b , c 这3个字符。
3.布尔值
布尔值和布尔代数的表示完全一致,一个布尔值只有 true 、 false 两种值,要么是 true ,要
么是 false ,可以直接用 true 、 false 表示布尔值,也可以通过布尔运算计算出来:
true; // 这是一个true值
false; // 这是一个false值
2 > 1; // 这是一个true值
2 >= 3; // 这是一个false值
&& 运算是与运算,只有所有都为 true , && 运算结果才是
|| 运算是或运算,只要其中有一个为 true , || 运算结果就是
! 运算是非运算,它是一个单目运算符,把 true 变成 false , false 变成 true :
4.数组 : 数组是一组按顺序排列的集合,集合的每个值称为元素。
JavaScript的数组可以包括任意数据类型
创建数组两种方式:
[1, 2, 3.14, 'Hello', null, true]; //数组用[]表示,元素之间用 , 分隔。
new Array(1, 2, 3); // 通过 Array()创建 数组[1, 2, 3]
然而,出于代码的可读性考虑,强烈建议直接使用 [] 。
数组的元素可以通过索引来访问。请注意,索引的起始值为 0 :
var arr = [1, 2, 3.14, 'Hello', null, true];
arr[0]; // 返回索引为0的元素,即1
arr[5]; // 返回索引为5的元素,即true
arr[6]; // 索引超出了范围,返回undefined
5.对象 : JavaScript的对象是一组由键-值组成的无序集合
var person = {
name: 'Bob',
age: 20,
tags: ['js', 'web', 'mobile'],
city: 'Beijing',
hasCar: true,
zipcode: null
};
JavaScript对象的键都是字符串类型,值可以是任意数据类型。
上述 person 对象一共定义了6个键值对,其中每个键又称为对象的属性,
例如, person 的 name 属性为 'Bob' , zipcode 属性为null
要获取一个对象的属性,我们用 对象变量.属性名 的方式:
person.name; // 'Bob'
person.zipcode; // null
6.比较运算符 :与java不同
Number做比较时,可以通过比较运算符得到一个布尔值:
2 > 5; // false
5 >= 2; // true
7 == 7; // true
JavaScript允许对任意数据类型做比较:
false == 0; // true
false === 0; // false
要特别注意相等运算符 == 。JavaScript在设计时,有两种比较运算符:
第一种是 == 比较,它会自动转换数据类型再比较,很多时候,会得到非常诡异的结果;
第二种是 === 比较,它不会自动转换数据类型,如果数据类型不一致,返回 false,一致再比较
由于JavaScript这个设计缺陷,不要使用 == 比较,始终坚持使用 === 比较
另一个例外是 NaN 这个特殊的Number与所有其他值都不相等,包括它自己:
NaN === NaN; // false
唯一能判断 NaN 的方法是通过 isNaN() 函数:
isNaN(NaN); // true
最后要注意浮点数的相等比较:
1 / 3 === (1 - 2 / 3); // false
这不是JavaScript的设计缺陷。浮点数在运算过程中会产生误差,因为计算机无法精确表示无限循环小
数。要比较两个浮点数是否相等,只能计算它们之差的绝对值,看是否小于某个阈值:
Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001; // true
(2)变量
变量在JavaScript中就是用一个变量名表示,变量名是大小写英文、数字、 $ 和 _ 的组合,且不能
用数字开头。变量名也不能是JavaScript的关键字,如 if 、 while 等。申明一个变量用 var 语
句,比如:
var a; // 申明了变量a,此时a的值为undefined
var $b = 1; // 申明了变量$b,同时给$b赋值,此时$b的值为1
var s_007 = '007'; // s_007是一个字符串
var Answer = true; // Answer是一个布尔值true
var t = null; // t的值是null
变量名也可以用中文,但是,请不要给自己找麻烦。
在JavaScript中,使用等号 = 对变量进行赋值。可以把任意数据类型赋值给变量,同一个变量可以反
复赋值,而且可以是不同类型的变量,但是要注意只能用 var 申明一次,例如:
var a = 123; // a的值是整数123
a = 'ABC'; // a变为字符串
打印变量X,要显示变量的内容,可以用 console.log(x) ,打开Chrome的控制台就可以看到结果。
var x = 100; console.log(x);
使用 console.log() 代替 alert() 的好处是可以避免弹出烦人的对话框。
(3)条件控制
if(score > 0 && score < 70){
alert("60-70");
} else if(score > 70 && score < 80) {
alert("70-80");
} else {
alert("other");
}
-->
2.3 字符串、数组、对象、流程控制、Map、Set
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
'use strict'
console.log('a\'');
console.log("a");
/*
(1)字符串
1.正常字符串我们使用 单引号,或者双引号
2.注意转义字符\
\u#### unicode字符
\x41 ASCII字符==中文
3.多行字符串编写用 ``tab上面esc下面的键
let = `aaa
bbb
ccc`
要把多个字符串连接起来,可以用 + 号连接:
var name = '小明'; var age = 20;
var message = '你好, ' + name + ', 你今年' + age + '岁了!'; alert(message);
4.模板字符串 :ES6新特性
let name = “qingjiang"
let age = 19;
let msg = `你好,${name}`
5.字符串长度
let str = "student"
console.log(str.length)
6.字符串的不可变性
var s = 'Test';
s[0] = 'X';
alert(s); // s仍然为'Test'
7.JavaScript为字符串提供了一些常用方法,注意,调用这些方法本身不会改变原有字符串的内 容,而是返回一个新字符串:
7.1 大小写转换
这里是方法,不是属性
toUpperCase() 把一个字符串全部变为大写
toLowerCase() 把一个字符串全部变为小写
7.2. charAt(index) 获取指定索引处的字符
substring(start,end) 根据指定索引范围截取字符串(含头不含尾)
indexOf(value) 获取指定字符串出现的索引位置
split(value) 根据指定规则切割字符串,返回数组
replace(old,new) 使用新字符串替换老字符串
(2)数组
Array可以包含任意数据类型
var arr = [1,2,3,4,5,6]
1.长度属性
arr.length
注意:假如给arr.length赋值,数组大小就会发生变化
长度变长后面值 为Undefine
长度变短 元素丢失
var arr = [1, 2, 3]; arr.length; // 3
arr.length = 6; arr; // arr变为[1, 2, 3, undefined,undefined,undefined]
arr.length = 2; arr; // arr变为[1, 2]
请注意,如果通过索引赋值时,索引超过了范围,同样会引起 Array 大小的变化:
var arr = [1, 2, 3];
arr[5] = 'x';
arr; // arr变为[1, 2, 3, undefined, undefined, 'x']
大多数其他编程语言不允许直接改变数组的大小,越界访问索引会报错。
然而,JavaScript的 Array却不会有任何错误。
在编写代码时,不建议直接修改 Array 的大小,访问索引时要确保索引不会越界。
2常用方法:
2.1 arr.indexOf() 注意字符串的"30"和数字30不同
var arr = [10, 20, '30', 'xyz'];
arr.indexOf(10); // 元素10的索引为0
arr.indexOf(20); // 元素20的索引为1
arr.indexOf(30); // 元素30没有找到,返回-1
arr.indexOf('30'); // 元素'30'的索引为2
2.2 arr.slice() 截取Array的一部分,返回数组,== 字符串的arr.subString()
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
arr.slice(0, 3); // [03)= ['A', 'B', 'C']
arr.slice(3); // 从索引3开始到结束: ['D', 'E', 'F', 'G']
如果不给 slice() 传递任何参数,它就会从头到尾截取所有元素。利用这一点,我们 可以很容易地复制一个 Array :
var arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
var aCopy = arr.slice(); aCopy; //['A','B','C','D','E','F', 'G'] aCopy === arr; // false
2.3 arr.push(压入尾部若干元素)、arr.pop(弹出尾部最后一个元素)
var arr = [1, 2];
arr.push('A', 'B'); // 返回Array新的长度: 4
arr; // [1, 2, 'A', 'B']
arr.pop(); // pop()返回'B'
arr; // [1, 2, 'A']
arr.pop(); arr.pop(); arr.pop(); // 连续pop 3次
arr; // []
arr.pop(); // 空数组继续pop不会报错,而是返回undefined
arr; // []
2.4 arr.unshift(压入头部若干元素) arr.shift(弹出头部第一个元素)
var arr = [1, 2];
arr.unshift('A', 'B'); // 返回Array新的长度: 4
arr; // ['A', 'B', 1, 2]
arr.shift(); // 'A'
arr; // ['B', 1, 2]
arr.shift(); arr.shift(); arr.shift(); // 连续shift 3次
arr; // []
arr.shift(); // 空数组继续shift不会报错,而是返回undefined
arr; // []
2.5 arr.sort() 按照默认顺序排序
var arr = ['B', 'C', 'A'];
arr.sort();
arr; // ['A', 'B', 'C']
能否按照我们自己指定的顺序排序呢?完全可以,我们将在后面的函数中讲到。
2.6 arr.reverse() 反转
var arr = ['one', 'two', 'three'];
arr.reverse();
arr; // ['three', 'two', 'one']
2.7 arr.splice() 修改数组的“万能方法”,它可以从指定的索引开始删除若干元素,
然后再从该位置添加若干元素
var arr = ['a', 'b', 'c', 'd', 'e', 'f'];
// 从索引2开始删除3个元素,然后再添加两个元素:
arr.splice(2, 3, 'k', 'k'); // 返回删除的元素 ['c', 'd', 'e']
arr; // ['a', 'b', 'k', 'k', 'f']
// 只删除,不添加:
arr.splice(2, 2); // ['k', 'k']
arr; // ['a', 'b', 'f']
// 只添加,不删除:
arr.splice(2, 0, 'k', 'k'); // 返回[],因为没有删除任何元素
arr; // ['a', 'b', 'k', 'k', 'f']
2.8 arr.concat(元素)
注意:concat并没有修改数组,只是会返回一个新的数组
var arr = ['A', 'B', 'C'];
var added = arr.concat([1, 2, 3]);
added; // ['A', 'B', 'C', 1, 2, 3]
arr; // ['A', 'B', 'C']
实际上, concat() 方法可以接收任意个元素和 Array ,
并且自动把 Array 拆开,然后全部添加到新的 Array 里:
var arr = ['A', 'B', 'C'];
arr.concat(1, 2, [3, 4]); // ['A', 'B', 'C', 1, 2, 3, 4]
2.9 arr.join('-') 打印拼接数组,使用特定的字符串连接
var arr = ['A', 'B', 'C', 1, 2, 3];
arr.join('-'); // 'A-B-C-1-2-3'
3.多维数组
var arr = [[1, 2, 3], [400, 500, 600], '-'];
var x = arr[1][2];
console.log(x); // x应该为500
数组:存取数据
(3)对象
JavaScript 中的所有键都是字符串,值是任意对象
1.若干个键值对
var 对象名={
属性名:属性值,
属性名:属性值,
属性名:属性值 //最后一个不加,
}
var person{
name : "shangXin",
age : 20;
email : "2052712743@qq.com"
score : 0;
}
2.对象属性赋值
person.name = "fenMingGao"
3.使用一个不存在的对象属性,不会报错
person.hh => undefined
4.动态的删减属性
delete person.name;
delete person.school; // 删除一个不存在的school属性也不会报错
5.动态的添加属性,直接给新的属性赋值即可
person.newName = "haha"
6.判断属性值是否在这个属性中! "name" in person
"toString" in person
7.判断一个属性是否是这个对象自身拥有的属性,而不是继承来的 hasOwnProperty
person.hasOwnProperty("name") //true
(4)流程控制
1.if判断
var age = 3;
if (age >= 18) {
alert('adult');
} else if (age >= 6) {
alert('teenager');
} else {
alert('kid');
}
2.循环
while(age < 100) {
age = age + 1;
console.log(age);
}
do{
age = age + 1;
console.log(age);
} while(age < 100)
3.for循环
for(let i = 0; i < 100; i++) {
console.log(i);
}
4.数组增强for循环
var arr = [1,2,3,4,5];
//用函数写 :ES5.1新特性
//更好的方式是直接使用iterable内置的forEach方法,
它接收一个函数,每次迭代就自动回调该函数。
arr.forEach(function(value) {
console.log(value);
})
//for in 写法:
它可以把一个对象的所有属性依次循环出来:
var o = {
name: 'Jack',
age: 20,
city: 'Beijing'
};
for (var key in o) {
if (o.hasOwnProperty(key)) {
console.log(key); // 'name', 'age', 'city'
}
}
由于 Array 也是对象,而它的每个元素的索引被视为对象的属性,所以遍历出来是下标
var a = ['A', 'B', 'C'];
for (var i in a) {
console.log(i); // '0', '1', '2'
console.log(a[i]); // 'A', 'B', 'C'
}
请注意, for ... in 对 Array 的循环得到的是 String 而不是 Number
//早年Bug
arr.name = 123; 数组本身并不包含"name"
for in arr遍历出 0,1,2,3,4,"name" //key,多了name
for of arr只遍历出1,2,3,4,5 //值正确
//for of 写法 ES6引入的新的语法
for(let num of age) {
console.log(num); //遍历取的是值
}
(5)Map、Set和Iterable :ES6的新特性
JavaScript的默认对象表示方式 { } 可以视为其他语言中的 Map 或 Dictionary 的数据结 构,即一组键值对。但是JavaScript的对象有个小问题,就是键必须是字符串。
但实际上Number或者其他数据类型作为键也是非常合理的。
为了解决这个问题,最新的ES6规范引入了新的数据类型 Map 。
学生的名字 学生的成绩
var name = ["tom", "jack", "haha"];
var score = [100, 90, 80];
速度慢
(Map)Map 是一组键值对的结构,具有极快的查找速度:
var map = new Map(['tom', 100], ["jack", 90], ["haha", 80]);
//长度
size属性:长度
//取值
var name = map.get("tom");
console.log(name);
//修改/增加
map.set("admin", 11);
//移除
map.delete("tom");
// entries() 获取迭代器对象
let et = map.entries();
for(let i = 0; i < map.size; i++){
document.write(et.next().value + "<br>");
}
(Set)无序重复的集合
和 Map 类似,也是一组key的集合,但不存储value。由于key不能重复,
所以,在 Set 中,没有重复的key。
var set = new Set([3, 1, 1, 1, 1]); //去重
//长度
size属性:长度
//添加
set.add(1);//已有,不添加
set.add(2);//没有,添加
//移除
set.delete(1);
//判断元素是否包某个值
set.has(3);
// keys() 获取迭代器对象
let st = s.keys();
for(let i = 0; i < s.size; i++){
document.write(st.next().value + "<br>");
}
(遍历Map,Set ):iterator(for of) ES6新特性
遍历 Array 可以采用下标循环,遍历Map 和 Set 就无法使用下标。
为了统一集合类型,ES6标准引入了新的 iterable 类型,Array,Map,Set 属于;
具有 iterable 类型的集合可以通过新的 for ... of 循环来遍历。
var a = ['A', 'B', 'C'];
for (var x of a) { // 遍历Array
console.log(x);
}
var map = new Map(['tom', 100], ["jack", 90], ["haha", 80]);
for(let x of map) {
console.log(x); //输出数组["tom", 100] ["jack", 90] ["haha", 80]
console.log(x[0] + '=' + x[1]); //tom=100 jack=90 haha=80
}
var set = new Set([5, 6, 7]);
for(let x of set) {
console.log(x); //5,6,7
}
更好的方式是直接使用 iterable 内置的 forEach 方法,它接收一个函数,
每次迭代就自动回调该函数。以 Array 为例
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
console.log(element + ', index = ' + index);
});
Map 的回调函数参数依次为 value 、 key 和 map 本身:
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
console.log(value);
});
*/
</script>
</head>
<body>
</body>
</html>
三、函数
3.1 函数
<!--
(1)函数 :和方法不同
1.定义函数 :一旦执行到return代表函数结束,返回结果!
如果没有 return 语句,函数执行完毕后也会返回结果,只是结果为 undefined
//绝对值的函数
//方式1
function abs(x) {
if(x >= 0) {
return x;
} else {
return -x;
}
}
//方式2
var abs = function(x) { //匿名函数 赋值给 abs == 等价
if(x >= 0) {
return x;
} else {
return -x;
}
};
两种定义完全等价,注意第二种方式按照完整语法需要在函数体末尾加一个;
表示赋值语句结束。
2.调用: abs(-10);
abs(); //返回NaN abs(x) 函数的参数 x 将收到 undefined ,计算结果为 NaN 。
//没有return 也返回undefined
//可以传任意个参数
3.参数问题规避,手动抛出异常
要避免收到 undefined ,可以对参数进行检查:
var abs = function(x) { //匿名函数 赋值给 abs == 等价
if(typeof x !== 'number') {
throw 'Not a Number';
}
if(x >= 0) {
return x;
} else {
return -x;
}
}
4.arguments 是js 免费赠送的关键字
它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。是一个数组!
可遍历使用
function foo(x) {
console.log('x = ' + x); // 10
for (var i=0; i<arguments.length; i++) {
console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);
//arguments问题:有时我们只需要用传进来多余的参数,而不用已定义的参数,
rest解决:获取除了已定义参数而多余参数(ES6新特性)
//固定写法 rest只能写在 最后
function aaa(a, b, ...rest) {
for(let i = 0; i < rest.length; i++) {
console.log(rest[i]);
}
}
-->
3.2 变量作用域
<!--
(2)变量作用域:js中,var定义的变量是有作用域的,
1.在函数体定义的var变量,函数体外不可用 (外边也想用需要用闭包)
function qj(){
var x = 1;
x = x + 1;
}
x = x + 2;//未定义报异常uncaught ReferenceError: x is not defined
//ReferenceError! 无法在函数体外引用变量x
定义未赋值undefined
2.函数嵌套作用域
function qj() {
var x = 1;
//内部函数可以访问外部函数变量,反之不行
function() {
var y = x + 1; //2
}
var z = y + 1;
}
内部函数变量和外部函数变量重名,内部函数从内向外找
如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。
3.提升变量作用域
function qj(){
var x = "x " + y; //结果是x undefined(不报错,y=undefined说明y已定义,未赋值)
console.log(x);
var y = "y"; //只要定义,自动扩大作用域,但是值不会
}
养成规范:所以定义写在函数最前面,避免乱
4.全局变量
<script>
//全局变量 :不在任何函数内定义的变量就具有全局作用域。
x = 1;
function(){
console.log(x);
}
fun();
console.log(x);
</script>
5.全局对象window
<script>
var x = "xxx";
alert(x); //alert()也是绑定在window下 等价于 window.alert(x);
alert(window.x); //默认所有的全局变量,都会自动绑定在window对象下
</script>
//可以用window.alert 函数当变量使用
<script>
var x = "xxx";
window.alert(x);
var old_alert = window.alert;
old_alert(x);//和alert一样
//失效了
window.alert = function() {
}
window.alert(x);
//恢复
window.alert = old_alert;
alert(x);
</script>
6.规范:把自己代码的全部放入自己定义的唯一空间中,降低全局命名冲突问题
JQuery就是这样 JQuery = $
作用域冲突问题(当引入多个JavaScript会冲突)
//解决方法
<script>
//唯一全局变量
var kuangApp{};
//定义全局变量
kuangApp.name = "kuangshen";
kuangApp.add = function(a,b) {
return a + b;
}
</script>
7.局部作用域 let (ES6) :建议局部变量都使用let定义
var变量作用域实际上是函数内部,我们在 for 循环等语句块中是无法定义具有局部作用域的变量的:
为了解决块级作用域,ES6引入了新的关键字 let ,用let替代 var 可以申明一个块级作用域的量:
function aaa() {
for(var i = 0; i < 100; i++) {
console.log(i);
}
console.log(i + 1);//101 问题? i出了这个作用域还可以使用
//解决:使用let定义 (ES6新特性)
}
//let报错
function aaa() {
for(let i = 0; i < 100; i++) {
console.log(i);
}
console.log(i + 1);//报异常Uncaught ReferenceError: i is not defined
}
8.常量const (ES6 新特性)
由于 var 和 let 申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写
的变量来表示“这是一个常量,不要修改它的值”:
在ES6 之前: 全部常量使用大写字母命名(本质还是变量)
var PI = 3.14;
PI = 123;//可以改变
在ES6中 : 用关键子const(只读变量不可再改,否则报错)
const PI = 3.14;
PI = 123;//不可以改变,报错TypeError:Assignment to constant variable.
3.3 方法与属性
<!--
(3)方法 : 对象中的方法 在一个对象中绑定函数,称为这个对象的方法。
1.定义对象 : 方法属性 (逗号隔开)
var kuangshen = {
//属性字段
name : '秦疆',
birth : 2000,
//方法
age : function() {
let now = new Date().getFullYear();
//今年 - 出生的年
return now - this.brith;
}
}
//获取属性
kuangshen.name;
//调用方法
kuangshen.age();
2.方法分开写,方法里this代表当前对象
function getAge() {
let now = new Date().getFullYear();
//今年 - 出生的年
return now - this.brith;
}
var kuangshen = {
//属性字段
name : '秦疆',
birth : 2000,
//方法
age : getAge
}
//调用方法
kuangshen.age(); //ok
//全局调用getAge,默认对象window
getAge(); // 等价于window.getAge()
getAge()方法里的: this.brith (window对象里没有,返回NaN) 解决方法apply
//方法中this是不能是指定的,谁调用this就是谁
apply : 可以this可以指定修改
getAge.apply(kuangshen, []);
四、内部对象及面向对象编程
<!--
(4)内部对象
1.标准对象: 在JavaScript的世界里,一切都是对象。
typeof 123 //"number"
typeof NaN //"number"
typeof "123"//"string"
typeof true //"boolean"
typeof Math.abs //"function"
typeof undefined //"undefined"
typeof null; //"object"
typeof [] //"object"
typeof {} //"object"
2.Date
//构造方法
//1. Date() 根据当前时间创建对象
let d1 = new Date();
//2. Date(value) 根据指定毫秒值创建对象
let d2 = new Date(10000);
//3. Date(year,month,[day,hours,minutes,seconds,milliseconds]) 根据指定字段创建对象(月份是0~11)
let d3 = new Date(2222,2,2,20,20,20);
let now = new Date();
now; //Tue Aug 09 2022 14:33:55 GMT+0800 (中国标准时间)
now.getFullYear()//年
now.getMonth();//月 0-11代表月
now.getDate();//日
now.getDay(); // 3, 表示星期三
now.getHours();//时
now.getMinutes();//分
now.getSeconds();//秒
now.getMilliseconds();//毫秒数
now.getTime(); //时间戳全世界统一 1970 1.1 0:00:00 毫秒数
//时间戳转时间
var dt = new Date(1660027287780);
//时间转字符串
Date 对象表示的时间总是按浏览器所在时区显示的,
不过我们既可以显示本地时间,也可以显示调整后的UTC时间:
var dt = new Date();
dt.toGMTString() //'Tue, 09 Aug 2022 06:44:48 GMT'
dt.toLocaleString() //'2022/8/9 14:44:48'
注意,当前时间是浏览器从本机操作系统获取的时间,
所以不一定准确,因为用户可以把当前时间设定为任何值。
3.Number
//1. parseFloat() 将传入的字符串浮点数转为浮点数
document.write(Number.parseFloat("3.14") + "<br>");
//2. parseInt() 将传入的字符串整数转为整数
document.write(Number.parseInt("100") + "<br>");
document.write(Number.parseInt("200abc") + "<br>"); // 从数字开始转换,直到不是数字为止
4.Math
//1. ceil(x) 向上取整
document.write(Math.ceil(4.4) + "<br>"); // 5
//2. floor(x) 向下取整
document.write(Math.floor(4.4) + "<br>"); // 4
//3. round(x) 把数四舍五入为最接近的整数
document.write(Math.round(4.1) + "<br>"); // 4
document.write(Math.round(4.6) + "<br>"); // 5
//4. random() 随机数,返回的是0.0-1.0之间范围(含头不含尾)
document.write(Math.random() + "<br>"); // 随机数
//5. pow(x,y) 幂运算 x的y次方
document.write(Math.pow(2,3) + "<br>"); // 8
5.RegExp:正则表达式:是一种对字符串进行匹配的规则。
构造函数:
let reg2 = new RegExp(规则)
let reg2 = /[1][358][0-9]{9}$/
//1.验证手机号
//规则:第一位1,第二位358,第三到十一位必须是数字。总长度11
let reg1 = /[1][358][0-9]{9}$/;
reg1.test("aa18688888888");
//2.验证用户名
//规则:字母、数字、下划线组成。总长度4~16
let reg2 = /^[a-zA-Z_0-9]{4,16}$/;
reg2.test("zhang_san123");
4.JSON
4.1 什么是JSON
早期,所有数据传输习惯使用XML文件
JSON(JavaScript Object Notation,js对象简谱) 是一种轻量级数据交换格式
简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言
易于人的阅读与编写,同时也易于机器解析和生成,并有效地提升网络传输效率
在JavaScript 一切皆为对象,任何js支持的类型都可以用JSON来表示如 number string;
格式:
对象都用{} map
数组都用[] list
所有的键值对都是用 key:value
4.2 JSON字符串 和 js对象 的转化
ver user {
name : "qinjiang",
age : 3,
sex : '男'
}
//对象转化为JSON字符串 (参数为对象)
var jsonUser = JSON.stringify(uer);
//{"name":"qinjiang","age":3,"sex":"男"}
//JSON 字符串转化为对象 (参数为JSON字符串)
var obj = JSON.parse('{"name":"qinjiang","age":3,"sex":"男"}');
4.3 JSON和js对象的区别(很多人搞不懂)
var obj = {a:'hello', b:'hellob'};
var JSON = '{"a":"hello","b":"hellob"}';
5.Ajax (阿甲克斯)
原生的js写法: xhr异步请求
(鸡可里)
JQuery封装好的方法 $("#name").ajax("") 成功则从(蛇可塞s)取
axios (耳秀啊斯) 请求 后面用这个
-->
五、面向对象编程
<!--
(5)面向对象编程
1。什么是面向对象
JavaScript、java、c#...
JavaScript 和 java 有一些区别
类: 模板
对象: 实例
ES6以前在JavaScript里不用类,而用原型:
var Student = {
name : "qinjiang",
age : 3,
run : function{
console.log(this.name + "run...");
}
};
var xiaoming = {
name : "xiaoming",
};
xiaoming._proto_ = Student;
xiaoming.run();
//再次设置原型
var Bird = {
fly : function(){
console.log(this.name + "fly...")
}
}
xiaoming._proto_=Bird;
xiaoming.fly();
2.class继承 (ES6新特性)
ES6之前定义一个类 (看不懂)
注意:当对象本身找不到属性的时候会先去原型里找就是对象原型prototype,找不到再去__proto__里找.
function Student(name) {
this.name = name; //对象添加属性 直接给属性赋值就行
};
//新增一个方法hello
Student.prototype.hello = function() { //对象添加方法(在对象的原型对象添加方法) 赋值即就行
alert('hello');
};
ES6 定义类
// 1.定义一个学生的类
class Student{
// 构造器
constructor(name){
this.name=name; //对象添加属性 直接 给属性赋值就行 如 对象.新属性 = 值
}
// 方法
hello(){
alert('hello')
}
}
// 2.继承
class smallStudent extends Student{
custructor(name,grage){
super(name)
this.grade = grade;
}
// 小学生类自己的方法
myGrade(){
alert('小学生')
}
}
// 3.使用
var xiaoming = new Student("xiaoming");
var xiaohong = new SmallStudent("xiaohong",1);
六、操作BOM对象(重点)
<!--
(6)操作BOM对象(重点)
JavaScript 和 浏览器关系?
JavaScript 诞生就是为了能够让它在浏览器中运行 操作BOM浏览器对象
所以,浏览器自然是JavaScript开发者必须要关注的。
BOM:浏览器对象模型
目前主流的浏览器分这么几种:
IE 6-11 : 国内用得最多的IE浏览器,历来对W3C标准支持差。从IE10开始支持ES6标准
Chrome : Google出品的基于Webkit内核浏览器,内置了非常强悍的JavaScript引擎——V8。
由于Chrome一经安装就时刻保持自升级,所以不用管它的版本,最新版早就支持ES6了
Safari : Apple的Mac系统自带的基于Webkit内核的浏览器,从OS X 10.7 Lion自带的6.1版本开 始支持ES6,目前最新的OS X 10.11 El Capitan自带的Safari版本是9.x,早已支持ES6
FireFox : Mozilla自己研制的Gecko内核和JavaScript引擎OdinMonkey。
早期的Firefox按版本发布,后来终于聪明地学习Chrome的做法进行自升级,时刻保持最新
移动设备上目前iOS和Android两大阵营分别主要使用Apple的Safari和Google的Chrome,
由于两者都是Webkit核心,结果HTML5首先在手机上全面普及(桌面绝对是Microsoft拖了后腿),
对JavaScript的标准支持也很好,最新版本均支持ES6
其他浏览器如Opera等由于市场份额太小就被自动忽略了。
另外还要注意识别各种国产浏览器,如某某安全浏览器,某某旋风浏览器,它们只是做了一个壳,
其核心调用的是IE,也有号称同时支持IE和Webkit的“双核”浏览器。
不同的浏览器对JavaScript支持的差异主要是,有些API的接口不一样,比如AJAX,File接口。
对于ES6标准,不同的浏览器对各个特性支持也不一样。
在编写JavaScript的时候,就要充分考虑到浏览器的差异,尽量让同一份JavaScript代码能运行在不 同的浏览器中。
JavaScript可以获取浏览器提供的很多对象,并进行操作。
1.window : 对象不但充当全局作用域,代表浏览器窗口
window.alert("hello");
//调整窗口会变 内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。
window.innerHeight //460
window.innerWidth //1707
//可以获取浏览器窗口的整个宽高。
window.outerHeight //760
window.outerWidth //1280
1.1.定时器(重要)
唯一标识 setTimeout(功能,毫秒值):设置一次性定时器。
clearTimeout(标识):取消一次性定时器。
唯一标识 setInterval(功能,毫秒值):设置循环定时器。
clearInterval(标识):取消循环定时器。
//一、定时器
function fun(){
alert("该起床了!");
}
//设置一次性定时器
//let d1 = setTimeout("fun()",3000);
//取消一次性定时器
//clearTimeout(d1);
//设置循环定时器
//let d2 = setInterval("fun()",3000);
//取消循环定时器
//clearInterval(d2);
1.2.加载事件(重要)
window.onload:在页面加载完毕后触发此事件的功能。
//加载事件
window.onload = function(){
let div = document.getElementById("div");
alert(div);
}
2.Navigator : 封装了浏览器的信息
// 请注意, navigator 的信息可以很容易地被用户修改,
不建议使用navigator对象的属性来判断和编写代码,因为会被人为修改。
对象表示浏览器的信息,最常用的属性包括:
navigator.appName //浏览器名称
navigator.appVersion //浏览器版本
navigator.language //浏览器设置的语言
navigator.userAgent //浏览器设定的 User-Agent 字符串
navigator.platform //操作系统类型
3.screen
对象表示屏幕的信息,常用的属性有:
screen.height//800 屏幕宽度,以像素为单位;
screen.width //1280屏幕高度,以像素为单位;
screen.colorDepth:返回颜色位数,如8、16、24。
6.location (重要) : 代表当前页面的URL信息
可以用 location.href 获取。要获得URL各个部分的值,可以这么写:
完整URL:http://www.example.com:8080/path/index.html?a=1&b=2#TOP 1
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.reload() //刷新网站
location.assgin("http://www.chaoxing.com/") //跳转页面 :后面进行重定向用
7.document : 代表当前页面,HTML DOM文档树
由于HTML在浏览器中以DOM形式表示为树形结构, document 对象就是整个DOM树的根节点。
document.title //'百度一下,你就知道'
document.title = '冯明高'
//获取具体的文档节点
<dl id="app">
<dt>Java</dt>
<dd>JavaSE</dd>
<dd>JavaEE</dd>
</dl>
<script>
var dl = document.getElementById("app");
</script>
//document 对象有一个 cookie 属性,可以获取当前页面的Cookie
//读取到当前页面的Cookie:
document.cookie //_m_h5_tk=831c8ec9b7531644eea87c0a19155f7f_1660050406468
//劫持cookie原理:
当你登录一个带有病毒网站,该网站有个js
<script src = "aa.js">
</script>
该js里有语句document.cookie获取cookie,
然后异步请求携带从你的浏览器获取到的cookie信息发送他的服务器,就gg
//防止获取cookie : 服务器端可设置cookie的httpOnly
8.history(不建议使用): 代表浏览历史记录
history.forward() //相当于浏览器上的前进 浏览器上前进后退按键 = js这两个语句
history.back() //相当于浏览器上的后腿
-->
七、操作DOM对象(重点)
<!--
(7)操作DOM对象 (重点) js大部分是做:网络通信 数据交互 操作DOM
DOM: 文档对象模型
由于HTML文档被浏览器解析后就是一棵DOM树,
要改变HTML的结构,就需要通过JavaScript来操作DOM。
浏览器页面就是一个DOM树形结构
更新:更新DOM节点内容
遍历DOM节点:得到DOM节点
删除:删除一个DOM节点内容以及它包含的所有子节点。
添加:添加一个DOM子节点
注意:js代码在前面,html在后面,不能获取元素
解决:1.把js移动到html的后面
2.用延迟加载:window.onload = function(){
里面不能定义函数;
}
1.选择器
方式1:
在操作一个DOM节点前,我们需要通过各种方式先拿到这个DOM节点:原生代码,之后用JQuery
document.getElementById() :定位唯一的一个DOM节点
document.getElementsByTagName() 定位一组DOM节点。
document.getElementsByClassName()定位一组DOM节点。
可以先定位父节点,再从父节点开始选择,以缩小范围:
// 先定位ID为'test-table'的节点,再返回其内部所有tr节点:
var trs = document.getElementById('testtable').getElementsByTagName('tr');
// 先定位ID为'te-div'的节点,再返回其内部所有class包含red的节点:
var reds = document.getElementById('te-div').getElementsByClassName('red');
<div id="father">
<h1>标题一</h1>
<p id="p1">p1</p>
<p class="p2">p2</p>
</div>
<script>
//对应CSS选择器
let h1 = document.getElementsByTagName("h1");
let p1 = document.getElementById("p1");
let p2 = document.getElementsByClassName("p2");
let father = document.getElementById("father");
var childrens = father.children; //获取父节点下的所有直属子节点
var firstChild = father.firstChild;
var lastChild = father.lastChild;
</script>
方式2:
使用 querySelector() 和 querySelectorAll() ,需要了解selector语法,
然后使用条件来获取节点,更加方便:
// 通过querySelector获取ID为q1的节点:
var q1 = document.querySelector('#q1');
// 通过querySelectorAll获取q1节点内的符合条件的所有节点:
var ps = q1.querySelectorAll('div.highlighted > p');
2.更新
let id1 = document.getElementById("id1");
//修改文本内容
id1.innerText="123" //修改文本的值
id1.innerHTML='<strong>123</strong>' //可以解析HTML文本标签
//修改CSS样式
DOM节点的 style 属性对应所有的CSS,可以直接获取或设置。
因为CSS允许 font-size 这样的名称,但它并非JavaScript有效的属性名,
所以需要在JavaScript中改写为驼峰式命名 fontSize :
id1.style.color='blue'; //属性使用string ""包裹
id1.style.fontSize='200px'; //驼峰命名
id。style.padding = '10px';
3.插入节点
如果我们获得节点是空的,我们通过innerHTML就可以新增一个元素
如果节点不是空的,不能这么干,会覆盖这个节点内
追加
<p id="js">JavaScript</p>
<div id="list">
<p id="se">JavaSE</p>
<p id="ee">JavaEE</p>
<p id="me">JavaME</p>
</div>
<script>
let js = document.getElementById("js");
let list = document.getElementById("list");
//改变元素位置,追加到其后面
list.appendChild(js)
//改变元素节点到ee位置前面
list.insertBefore(js, ee); //ee 需要被包含在list
//通过js,创建一个新的节点(标签),再插入
let newP = document.createElement("p");
//万能设置 等价写法 newP.id="newP";
newP.setAttribute("id", "newP")
p.innerText="hhhh";
list.appendChild(newP);
</script>
4.删除节点
删除节点的步骤,先获取父节点,在通过父节点删除自己
<div id="father">
<h1>标题一</h1>
<p id="p1">p1</p>
<p class="p2">p2</p>
</div>
<script>
let self = document.getElementById("p1");
let father = self.parentElement;
//删除
father.removeChild(self)
//删除是个动态过程,每次删除一个节点对象father.children 也更新
father.removeChild(father.children[0])
father.removeChild(father.children[1]) //删除元素0后,子元素1已经动态编程0了
father.removeChild(father.children[2]) //删除元素1后,子元素2已经动态编程0了
</script>
八、操作表单(验证)
<!--
(8.1)操作表单
HTML表单的输入控件主要有以下几种:
文本框 <input type="text">
密码框 <input type="password">
单选框 <input type="radio"> 选择一项
多选框 <input type="checkbox"> 选择多项
下拉框 <select> 选择一项
隐藏域 hidden 用户不可见,但表单提交时会把隐藏文本发送到服务器
......
表单的目的: 提交信息
-->
<form action="post">
<span>用户名:</span><input type="text" id="username">
<p>
<span>性别:</span>
<input type="radio" name="sex" id="boy_radio" value="boy">
<input type="radio" name="sex" id="girl_radio" value="girl">
</p>
</form>
<script>
let userName = document.getElementById("username");
//得到输入框的值
userName.value //111111
//修改输入框的值
userName.value="123456"
//对于单选框,多选框等等固定的值
boy_radio.value; //只能获取当前固定值boy,不可修改
boy_radio.checked;//true代表选中,false代表未选中
//对于单选框和复选框不可修改,设置 checked 为 true 或 false 即可。
</script>
<!--
(8.2)验证控制表单提交
方式一
响应一个 button 的 click 事件,在JavaScript代码中提交表单: -->
<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.submit(); // 提交form:
}
</script>
<!--
方式2
浏览器默认点击 <button type="submit"> 时提交表单,或者用户在最后一个输入框按回车键。
因此,第二种方式是响应 <form> 本身的onsubmit 事件,在提交form时作修改:-->
<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> 时,要充分利用 <input type="hidden"> 来传递数据。
例如,很多登录表单希望用户输入用户名和口令,但是,安全考虑,提交表单时不传输明文口令,
而是口令的MD5。普通JavaScript开发人员会直接修改 <input> : -->
<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 src="https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.min.js">
</script>
<script> function checkForm() {
var pwd = document.getElementById('password');
// 把用户输入的明文变为MD5:
wd.value = md5(pwd.value);
// 继续下一步:
return true;
}
</script>
<!--
这个做法看上去没啥问题,但用户输入了口令提交时,
口令框的显示会突然从几个 * 变成32个 * (因为MD5有32个字符)。
要想不改变用户的输入,可以利用 <input type="hidden"> 实现: -->
<script src="https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.min.js"> </script>
<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 = md5(input_pwd.value);
// 继续下一步: return true;
}
</script>
九、利器JQuery
<!--
(10)JQuery
JQuery库,里面存在大量的javascript函数
好处:
消除浏览器差异:你不需要自己写冗长的代码来针对不同的浏览器来绑定事件,编写AJAX等代码
简洁的操作DOM的方法:写 $('#test') 肯定比 document.getElementById('test')简洁
轻松实现动画、修改CSS等各种操作。
官网:https://jquery.com/
jQuery的理念“Write Less, Do More“,让你写更少的代码,完成更多的工作!
jQuery是 jquery-xxx.js 文件:
但你会看到有compressed(已压缩)和uncompressed(未压缩)两种版本,使用时完全一样,
但如果你想深入研究jQuery源码,那就用uncompressed版本。
使用jQuery只需要在页面的 head 引入jQuery文件即可: -->
<html>
<head>
<script src="//code.jquery.com/jquery-1.11.3.min.js">
</script>
...
</head>
<body>
<a id="test-link" href="#0">点我试试</a>
<script>
// 获取超链接的jQuery对象:
//方式一
var a = $('#test-link');
a.on('click', function () {
alert('Hello!');
});
// 方式二
a.click(function () {
alert('Hello!');
});
</script>
</body>
</html>
<!--
1.简单入门:公式: $(选择器).事件(事件函数)
美元符号定义 jQuery
选择符(selector)"查询"和"查找" HTML 元素
jQuery 的 action() 执行对元素的操作 -->
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/jquery-1.7.2.js">
//公式: $(selector).action()
</script>
</head>
<body>
<a href="" id="test-jquery">点我</a>
<script>
let a = document.getElementById("test-jquery");
//选择器就是css里的选择器
$('#test-jquery').click(function (){
alert('hello jquery');
})
</script>
<!--
2.选择器
原生js : 选择器少,麻烦不好记
标签
document.getElementsByTagName()
id
document.getElementById()
类
document.getElementsByClassName()
JQuery: CSS一样:CSS中的选择器他全都能用!
$('p').click() //标签选择器
$('#id1').click() //id择器
$('.class1').click() //类选择器
按属性查找 : 当属性的值包含空格等特殊字符时,需要用双引号括起来。
$('[name=email]'); // 找出<??? name="email">
$('[type=password]'); // 找出<??? type="password">
$('[items="A B"]'); // 找出<??? items="A B">
按属性查找还可以使用前缀查找或者后缀查找:
$('[name^=icon]'); // 找出所有name属性值以icon开头的DOM
//例如: name="icon-1", name="icon-2"
$('[name$=with]'); // 找出所有name属性值以with结尾的DOM
//例如: name="startswith", name="endswith"
3.操作DOM
3.1 修改Text和HTML -->
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="lib/jquery-1.7.2.js"></script>
</head>
<body>
<ul id="ul">
<li class="js">JavaScript</li>
<li name="python">Python</li>
</ul>
<script>
$('#ul li[name=python]').text();//获得值,HTML也是类似操作
$('#ul li[name=python]').text('设置值'); //设置值,HTML也是类似操作
$('#ul').html();//获得值 '\n <li class="js">JavaScript</li>\n // <li name="python">kkkk</li>\n '
$('#ul').html('<strong>123</strong>');//设置值
//一个jQuery对象可以包含0个或任意个DOM对象,它的方法作用在对应的每个DOM节点上
$('#test-ul li').text('JS'); // 是不是两个节点都变成了JS?
</script>
<!--
3.2 添加DOM
要添加新的DOM节点,.html() 这种暴力方法外,还可以用 append() 方法,例如:-->
<div id="test-div">
<ul>
<li><span>JavaScript</span></li>
<li><span>Python</span></li>
<li><span>Swift</span></li>
</ul>
</div>
<script>
//父级:append() 把DOM添加到最后, prepend() 则把DOM添加到最前。
var ul = $('#test-div>ul');
ul.append('<li><span>Haskell</span></li>');
//把新节点插入到指定位置同级节点:可以用 after() 或者 before() 方法
var js = $('#test-div>ul>li:first-child');
js.after('<li><span>Lua</span></li>');
</script>
<!--
3.3 删除节点
要删除DOM节点,拿到jQuery对象后直接调用 remove() 方法就可以了。
如果jQuery对象包含若干DOM节点,实际上可以一次删除多个DOM节点: -->
var li = $('#test-div>ul>li');
li.remove(); // 所有<li>全被删除
<!--
3.4 CSS
$('#ul li[name=python]').css("color","red") //键值对
注意:
//单个css属性设置 不用{}
//多个css属性设置 需要{} 包含键值对
$('#ul li[name=python]').css({"color","red"},{"background- color","blue"}) //键值对
$('#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属性
css() 方法将作用于DOM节点的 style 属性,具有最高优先级。
如果要修改 class 属性,可以用jQuery提供的下列方法:
var div = $('#test-div');
div.hasClass('highlight'); // false, class是否包含
highlight div.addClass('highlight'); // 添加highlight这个class
div.removeClass('highlight'); // 删除highlight这个class
3.5 元素的显示与隐藏
$('#ul').hide(); //等价display: none
//不过,要显示这个DOM就需要恢复原有的 display 属性,
//这就得先记下来原有的 display属性到底是 block 还是 inline 还是别的值
//考虑到显示和隐藏DOM元素使用非常普遍,jQuery直接提供 show() 和 hide() 方法,
//我们不用关心它是如何修改 display 属性的,总之它能正常工作
$('#ul').css("display","none");
$('#ul').show(); //显示
3.6 获取DOM信息
利用jQuery对象的若干方法,我们直接可以获取DOM的高宽等信息,
而无需针对不同浏览器编写特定代码 -->
<script>
// 浏览器可视窗口大小:
$(window).width(); // 800
$(window).height(); // 600
// HTML文档大小:
$(document).width(); // 800
$(document).height(); // 3500
// 某个div的大小:
var div = $('#test-div');
div.width(); // 600
div.height(); // 300
div.width(400); // 设置CSS属性 width: 400px,是否生效要看CSS是否有效
div.height('200px'); // 设置CSS属性 height: 200px,是否生效要看CSS是否有效
</script>
<!--
3.7 attr() 和 removeAttr() 方法用于操作DOM节点的属性: -->
<script>
// <div id="test-div" name="Test" start="1">...</div>
ar div = $('#test-div');
div.attr('data'); // undefined, 属性不存在
iv.attr('name'); // 'Test'
iv.attr('name', 'Hello'); // div的name属性变为'Hello'
div.removeAttr('name'); // 删除name属性
iv.attr('name'); // undefined
</script>
<!--
3.8 操作表单
对于表单元素,jQuery对象统一提供 val() 方法获取和设置对应的 value 属性 -->
<script>
/* <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'
</script>
<!--
4.事件
jQuery能够绑定的事件主要包括:
鼠标事件
click: 鼠标单击时触发
dblclick:鼠标双击时触发
mousedown([[data],fn]) //事件在鼠标在元素上点击后会触发
mouseenter([[data],fn])
mouseleave([[data],fn]) //被选中的元素离开
mousemove([[data],fn]) //移动
mouseout([[data],fn]) //事件在鼠标从元素上离开后会触发
mouseover([[data],fn]) //事件会在鼠标移入对象时触发
mouseup([[data],fn])
键盘事件:键盘事件仅作用在当前焦点的DOM上,通常是 和 。
keydown:键盘按下时触发;
keyup:键盘松开时触发;
keypress:按一次键后触发。
其他事件
focus:当DOM获得焦点时触发;
blur:当DOM失去焦点时触发;
change:当 、 或 的内容改变时 触发;
submit:当 提交时触发;
ready:当页面被载入并且DOM树完成初始化后触发。
mouse: <span id="mouseMove"></span>
<div id="divMove">
在这里移动鼠标试试
</div>
<script>
//当网页元素加载完成之后,响应事件
// $(document).ready(function (){
//
// })
//简写版
$(function (){
$('#divMove').mousemove(function (e){
$('#mouseMove').text('x:' + e.pageX + 'y:' + e.pageY);
})
});
</script> -->
<!--
4.1 初始化事件: 相关代码会在DOM树初始化后再执行 -->
<html>
<head>
<script>
$(document).on('ready', function () {
$('#testForm).on('submit', function () {
alert('submit!');
});
});
// 由于 ready 事件使用非常普遍,所以可以这样简化:
$(document).ready(function () {
//on('submit', function)也可以简化:
$('#testForm).submit(function () {
alert('submit!');
});
});
//甚至还可以再简化为:
$(function () {
// init...
});
</script>
</head>
<body>
<form id="testForm">
...
</form>
</body>
4.2 事件参数
有些事件,如 mousemove 和 keypress我们需要获取鼠标位置和按键的值,
否则监听这些事件就没什么意义了。
所有事件都会传入 Event 对象作为参数,可以从 Event 对象上获取到更多的信息:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#testMouseMoveDiv{
width: 300px;
height: 300px;
border: 1px solid black;
}
</style>
</head>
<body> mousemove: <span id="testMouseMoveSpan"></span>
<div id="testMouseMoveDiv"> 在此区域移动鼠标试试 </div>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"> </script>
<script>
$(function () { $('#testMouseMoveDiv').mousemove(function (e) {
$('#testMouseMoveSpan').text('pageX = '
+ e.pageX + ', pageY = ' + e.pageY);
});
});
</script>
</body>
</html>
<!--
未来Ajax
$('from').ajax()
$.ajax({
url: "test.html",
context: document.body,
success: function(){
$(this).addClass("done");
}
});
十、AJAX
10.1 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' //接收数据格式
});
// 默认get请求已经发送了
不过,如何用回调函数处理返回的数据和出错时的响应呢?
还记得Promise对象吗?jQuery的jqXHR对象类似一个Promise对象,我们可以用链式写法来处理各种回调:
'use strict';
function ajaxLog(s) { //定义一个日志函数,输出到 '#test-response-text'中
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('请求完成: 无论成功或失败都会调用');
});
10.2 get
对常用的AJAX操作,jQuery提供了一些辅助方法。由于GET请求最常见,所以jQuery提供了==get()==方法,可以这么写:
var jqxhr = $.get('/path/to/resource', {
name: 'Bob Lee',
check: 1
});
- 第二个参数如果是object,jQuery自动把它变成query string然后加到URL后面,实际的URL是:
- 这样我们就不用关心如何用URL编码并构造一个query string了.
/path/to/resource?name=Bob%20Lee&check=1
10.3 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被发送。
10.3 getJSON
由于JSON用得越来越普遍,所以jQuery也提供了 ==getJSON()==方法来快速通过GET获取一个JSON对象:
var jqxhr = $.getJSON('/path/to/resource', {
name: 'Bob Lee',
check: 1
}).done(function (data) {
// data已经被解析为JSON对象了
});
10.4 安全限制
jQuery的AJAX完全封装的是JavaScript的AJAX操作,所以它的安全限制和前面讲的用JavaScript写AJAX完全一样。
如果需要使用JSONP,可以在==ajax()==中设置 jsonp: ‘callback’,让jQuery实现JSONP跨域加载数据。
关于跨域的设置请参考浏览器 - AJAX一节中CORS的设置。
十一、JS 函数式编程
11.1 FP 简介
重要性
函数式编程(FP),不是一个新的概念,它几乎贯穿了整个编程史。直到最近几年,函数式编程才成为整个开发界的主流观念。
函数式编程有完善且清晰的原则,一旦我们知道这些原则,我们将能更加快速地读懂代码,定位问题。这是为什么函数式编程重要的原因!
比如:你可能写过一些命令式的代码,像 if 语句和 for 循环这样的语句。这些语句旨在精确地指导计算机如何完成一件事情。而声明式代码,以及我们努力遵循函数式编程原则所写出的代码,更专注于描述最终的结果。
函数式编程以另一种方式来思考代码应该如何组织才能使数据流更加明显,并能让读者很快理解你的思想。
记住,你编写的每一行代码之后都要有人来维护,这个人可能是你的团队成员,也可能是未来的你。
最初的函数
函数式编程不是仅仅用 function 这个关键词来编程,就像面向对象编程不仅仅是用了对象就算是。
函数的真正意义是什么?
回到最初的起点,我们心中的函数一定是这样的:
f(x) = 2x2 + 3,这是数学上真正的函数。那这和函数式编程有什么关系呢?
-
函数的本质是【映射】。以一个优雅的方式来描述一组值和另一组值的映射关系,即函数的输入值与输出值之间的关联关系。
-
在编程中,它或许有许多个输入值,或许没有。它或许有一个输出值( return 值),或许没有。
-
如果你计划使用函数式编程,你应该尽可能多地使用函数,而不是程序。你所有编写的 function 应该接收输入值,并且返回输出值。
-
这里,输入值就是函数传参,输出值就是return的东西。(如果你没有 return 值,或者你使用 return;,那么则会隐式地返回 undefined 值。)
11.2 函数式编程
函数式编程是一种编程范式,是一种构建计算机程序结构和元素的风格,它把计算看作是对数学函数的评估,避免了状态的变化和数据的可变,与函数式编程相对的是命令式编程。我们有这样一个需求,给数组的每个数字加一:
// 数组每个数字加一, 命令式编程
let arr = [1, 2, 3, 4];
let newArr = [];
for(let i = 0; i < arr.length; i++){
newArr.push(arr[i] + 1);
}
console.log(newArr); // [2, 3, 4, 5]
这段代码结果没有问题,但是没法重用。我们换一个思维,这里面包含的操作其实就两个,一个是遍历数组,一个是成员加一。我们把这两个方法拆出来:
// 先拆加一出来
let add1 = x => x +1;
// 然后拆遍历方法出来,通过遍历返回一个操作后的新数组
// fn是我们需要对每个数组想进行的操作
let createArr = (arr, fn) => {
const newArr = [];
for(let i = 0; i < arr.length; i++){
newArr.push(fn(arr[i]));
}
return newArr;
}
// 用这两个方法来得到我们期望的结果
const arr = [1, 2, 3, 4];
const newArr = createArr(arr, add1);
console.log(newArr); // [2, 3, 4, 5], 结果仍然是对的
这样拆分后,如果我们下次的需求是对数组每个元素乘以2,我们只需要写一个乘法的方法,然后复用之前的代码就行:
let multiply2 = x => x * 2;
// 调用之前的createArr
const arr2 = [1, 2, 3, 4];
const newArr2 = createArr(arr2, multiply2);
console.log(newArr2); // [2, 4, 6, 8], 结果是对的
高阶函数
事实上我们的加一函数只能加一,也不好复用,它还可以继续拆:
// 先写一个通用加法,他接收第一个加数,返回一个方法
// 返回的这个方法接收第二个加数,第一个加数是上层方法的a
// 这样当我们需要计算1+2是,就是add(1)(2)
let add = (a) => {
return (b) => {
return a + b;
}
}
// 我们也可以将返回的函数赋给一个变量,这个变量也就变成一个能特定加a的一个方法
let add1 = add(1);
let res = add1(4);
console.log(res); // 5
所以函数式编程就是将程序分解为一些更可重用、更可靠且更易于理解的部分,然后将他们组合起来,形成一个更易推理的程序整体。
高阶函数
一个函数如果可以接受或返回一个甚至多个函数,它被叫做高阶函数。
其中最强大的就是:【闭包】。
我们将在的后续举例中大量使用闭包。它可能是所有函数式编程中最重要的基础。
此处举一小例:假设你需要将两个值相加,一个你已经知道,另一个还需要后面才能知道,你可以使用闭包来记录第一个输入值:
function makeAdder(x) {
return function sum(y){
return x + y;
};
}
//我们已经分别知道作为第一个输入的 10 和 37
var addTo10 = makeAdder( 10 );
var addTo37 = makeAdder( 37 );
// 紧接着,我们指定第二个参数
addTo10( 3 ); // 13
addTo10( 90 ); // 100
addTo37( 13 ); // 50
这种在连续函数调用中指定输入,是函数式编程中非常普遍的形式。
它可分为两类:偏函数应用和柯里化。
具名函数
我们提倡要用具名函数,而不是匿名函数,这更有利于我们语义化代码,比如getPreferredName(…),操作意图很明确,并且可以很好的回溯问题,防止出现==(anonymous function)== 。
但是 => 箭头函数除外,箭头函数还是得有效利用。
=> 箭头函数令人兴奋的地方在于它几乎完全遵循函数的数学符号,特别是像 Haskell 这样的函数式编程语言。它能简化、优化代码片段中的空间。
丢掉“this"
JavaScript 中的 this 绑定规则是真的难记,好消息是我们将把 this 丢弃掉,不去理会它。
这样做的内核原因是:this 是函数的一个隐式的输入参数。前面我们提到通常,开发人员喜欢显式模式而不是隐式模式。,这样的隐式输入违背了我们的原则。这样做的内核原因是:this 是函数的一个隐式的输入参数。前面我们提到通常,开发人员喜欢显式模式而不是隐式模式。,这样的隐式输入违背了我们的原则。
11.3 纯函数
思考“return”
这个隐式函数输出在函数式编程中有一个特殊的名称:副作用。
没有副作用的函数也有一个特殊的名称:纯函数
纯函数是指一个函数,如果它的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据的变化,只依赖于其输入参数。同时函数的运行也不改变任何外部数据,它只通过它的返回值与外部通讯。下面这个函数就不是纯函数,因为函数内部需要的discount需要从外部获取:
let discount = 0.8;
const calPrice = price => price * discount;
let price = calPrice(200); // 160
// 当discount变了,calPrice传同样额参数,结果不一样,所以不纯
discount = 0.9;
price = calPrice(200); // 180
要改为纯函数也很简单,将discount作为参数传递进去就行了
const calPrice = (price, discount) => price * discount;
纯函数可以保证代码的稳定性,因为相同的输入永远会得到相同结果。不纯的函数可能会带来副作用。
11.4 函数副作用
函数副作用是指调用函数时除了返回函数值之外,还对主调用函数产生附加的影响,比如修改全局变量或者外部变量,或者修改参数。这可能会带来难以查找的问题并降低代码的可读性。下面的foo就有副作用,当后面有其他地方需要使用a,可能就会拿到一个被污染的值
let a = 5;
let foo = () => a = a * 10;
foo();
console.log(a); // 50
除了我们自己写的函数有副作用外,一些原生API也可能有副作用,我们写代码时应该注意:
我们的目标是尽可能的减少副作用,将函数写为纯函数,下面这个不纯的函数使用了new Date,每次运行结果不一样,是不纯的:
要给为纯函数可以将依赖注入进去,所谓依赖注入就是将不纯的部分提取出来作为参数,这样我们可以让副作用代码集中在外部,远离核心代码,保证核心代码的稳定性
// 依赖注入
const foo = (d, log, something) => {
const dt = d.toISOString();
return log(`${dt}: ${something}`);
}
const something = 'log content';
const d = new Date();
const log = console.log.bind(console);
foo(d, log, something);
所以减少副作用一般的方法就是:
- 函数使用参数进行运算,不要修改参数
- 函数内部不修改外部变量
- 运算结果通过返回值返回给外部
11.5 可变性和不可变性
- 可变性:指一个变量创建以后可以任意修改
- 不可变性: 指一个变量被创建后永远不会发生改变,不可变性是函数式编程的核心概念
下面是一个可变的例子:
如果我们一定要修改这个参数,我们应该将这个参数进行深拷贝后再操作,这样就不会修改参数了: