一、什么是 JavaScript
1. 概述
- JavaScript 是世界上最流行的脚本语言;
- JavaScript 是运行在浏览器中的,解释型的编程语言;
- JavaScript ≠ Java;
- 只用了 10 天设计完成;
- 后端人员,必须要精通 JavaScript;
- 在 Web 世界里,只有 JavaScript 能跨平台、跨浏览器,驱动网页,与用户交互;
2. JavaScript 历史
- 1995年,网景公司凭借 Navigator 浏览器,成为 Web 时代最著名的第一代互联网公司。
- 该公司希望能在静态 HTML 页面上,添加一些动态效果,于是找 Brendan Eich 在两周之内(10天)设计出了 JavaScript 语言。
- 为什么起名叫 JavaScript ?原因是,当时 Java 语言非常红火,所以网景公司希望借 Java 的名气来推广,但事 实上 JavaScript 除了语法上有点像Java,其他部分基本上没啥关系。
3. ECMAScript
- ECMAScirpt:可以理解为是 JavaScript 的一个标准;
- 版本已经到 ES6 及更高版本,但是大部分浏览器,还只停留在支持 ES5 代码上;
- 开发环境与线上环境,版本不一致。
二、快速入门
1. 引入 JavaScript
-
内部标签:JS 代码可以直接嵌在网页的任何地方,通常放到 head 中;
-
外部引入:把 JS 代码放到一个单独的 .js 文件,然后引入;
-
测试代码:
第一个JavaScript -
调试
- 安装 Google Chrome 浏览器;
- 打开
开发者工具
:快捷键F12
、Ctrl + Shift + I
,或鼠标右键检查
先点击
控制台(Console)
,在这里可以直接输入 JS 代码,按回车后执行;要查看一个变量的内容,在 Console 中输入
console.log(a);
,回车后显示的值,就是变量的内容。-
关闭 Console 请点击右上角的
×
按钮。
2. 基本语法入门
JavaScript 的语法和 Java 语言类似,每个语句以
;
结束,语句块用 {...};JavaScript 不强制要求在每个语句的结尾加
;
,浏览器中负责执行JavaScript 代码的引擎,会自动在每个语句的结尾补上;
。-
完整的赋值语句:
// 常规 var x = 1; // 仍然可以视为一个完整的语句 'Hello, world'; // 不建议一行写多个语句 var x = 1; var y = 2;
-
实例:
基本语法
3. 数据类型简介
数值、文本、图形、音频、视频、网页等各种各样的数据,在 JavaScript 中定义了以下几种数据类型:
-
变量:
- 大小写英文、数字、 $ 和 _ 的组合,且 不能用数字开头;
- 变量名也不能是 JavaScript 的关键字,如 if 、 while 等;
- 申明一个变量用 var 语句,比如:
var 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
- 变量名也可以用中文,一般不这样用。
-
Number:JavaScript 不区分整数和浮点数,统一用 Number 表示,以下都是合法的 Number 类型:
123; // 整数 0.456; // 浮点数 1.2345e3; // 科学计数法 1.2345x1000 -99; // 负数 NaN; // NaN表示Not a Number,当无法计算结果时用NaN表示 Infinity; // Infinity表示无限大,当数值超过了JavaScript的Number所能表示的最大值时,就表示为Infinity
字符串:‘abc’ “abc”
布尔值:true false
-
逻辑运算符:
&& // 两个都为真,结果为真 || // 一个为真,结果为真 ! // 真即假,假即真(取反)
-
比较运算符:用
===
进行比较;= // 赋值预算法 == // 等于(类型不一致,值一样,也会判断为true,即判断1=='1') === // 绝对等于(类型一样,值一样,结果为true,JS 的缺陷)
须知:
- NaN:与所有的数值都不相等,包括自己;
- 只能通过 isNaN() 来判断这个数是不是 NaN。
-
浮点数:
console.log(1/3)===(1-2/3); // 结果为false // 尽量避免使用浮点数进行运算,存在精度问题 Math.abs(1/3-(1-2/3))<0.00000001; // 结果为true
-
null 和 undefined
- null:空;
- undefined:不存在。
-
数组:Java 的数组必须是相同类型的对象,JS 中不需要这样。
//保证代码的可读性,尽量使用[] var arr = [1,2,3,'hello',null,true]; //取数组下标:如果越界了,就会报undefined
-
对象:对象是一组由
键-值
组成的无序集合- 对象是大括号,数组是中括号;
- 每一个属性之间使用逗号隔开,最后一个不需要加逗号。
// java对象 Person person = new Pperson(); var person = { name: 'Bob', age: 20, tags: ['js', 'web', 'mobile'], city: 'Beijing', hasCar: true, zipcode: null };
- 获取对象的属性,用
对象变量.属性名
的方式person.name < 'Bob' person.zipcode < null
4. 严格检查模式:'use strict'
预防 JavaScript 随意性导致产生的一些问题;
-
必须写在 script 标签内的第一行。
IDEA 设置支持 ES6 语法
三、数据类型
1. 字符串
-
正常字符串:使用单引号、双引号包裹
console.log('a'); console.log("a");
-
转义字符:
\
其它转义符\'; // ' \\; // \ \n; // 换行 \t; // 制表位 \u4e2d; // \u#### Unicode 字符 \x41; // Ascll字符
-
多行字符串:ES6 新增,反引号 ``表示
// tab 和 ESC 键中间的字符 `` 包裹 let msg = `这是 一个多行 字符串`;
-
模板字符串
- ES6 新增
let name = '小明'; let age = 20; // let message = '你好, ' + name + ', 你今年' + age + '岁了!'; // ES6 新增 let message = `你好, ${name}, 你今年${age}岁了!`; console.log(message);
-
字符串长度
let s = 'Hello, world!'; s.length; // 13
-
获取字符串指定位置字符:
- 使用类似 Array 的下标操作,索引号从0开始:
let s = 'Hello, world!'; s[0]; // 'H' s[6]; // ' ' s[7]; // 'w' s[12]; // '!' s[13]; // undefined 超出范围的索引不会报错,但一律返回undefined
-
字符串 不可变:
- 对字符串的某个索引赋值,不会有任何错误,但是,也没有任何效果:
let s = 'Test'; s[0] = 'X'; console.log(s); // s仍然为'Test'
-
大小写转换
// 注意:这里是方法,不是属性 s.toUpperCase(); // 转大写 s.toLowerCase(); // 转小写
-
查找指定字符串出现的位置:indexOf()
let s = 'hello, world'; s.indexOf('world'); // 返回7 s.indexOf('Worldd'); // 没有找到指定的子串,返回-1
-
返回指定索引区间的字符串:substring()
let s = 'hello, world' s.substring(0, 5); // [0,5)从索引0开始到5,返回'hello' s.substring(7); // 从索引7开始到结束,返回'world'
2. 数组
-
Array 可以包含任意的数据类型:(下标索引从0开始)
var arr = [1, 2, 3.14, 'Hello', null, true]; arr.length; // 6
-
注意:直接给 Array 的 length 赋一个新的值会导致 Array 大小的变化
let 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]
如果数组索引赋值变小,元素就会丢失;
-
通过索引修改为新的值:
let arr = ['A', 'B', 'C']; arr[1] = 99; arr; // arr现在变为['A', 99, 'C']
-
通过索引赋值时,索引超过了范围,同样会引起 Array 大小的变化:
let arr = [1, 2, 3]; arr[5] = 'x'; arr; // arr变为[1, 2, 3, undefined, undefined, 'x']
不建议直接修改 Array 的大小,访问索引时要确保索引不会越界。
数组常用方法
- indexOf():通过元素获得下标索引
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
- 注意:数字 30 和字符串 '30' 是不同的元素。
-
slice() :类似于 String 中的substring()
- 截取 Array 的一部分,返回一个新的数组。
let arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G']; arr.slice(0, 3); // 从索引0开始,到索引3结束,但不包括索引3: ['A', 'B', 'C'] arr.slice(3); // 从索引3开始到结束: ['D', 'E', 'F', 'G'] // 不传递任何参数,它就会从头到尾截取所有元素(相当于复制一个新数组) let aCopy = arr.slice(); aCopy; // ['A', 'B', 'C', 'D', 'E', 'F', 'G'] aCopy === arr; // false
-
push()和pop()
- push():向 Array 的末尾添加若干元素;
- pop():把 Array 的最后一个元素删除掉。
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; // []
-
unshift()和shift()
- unshift():往 Array 的头部添加若干元素;
- shift():把 Array 的第一个元素删掉。
let 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; // []
-
sort() 排序
- 直接修改当前 Array 的元素位置,直接调用时,按照默认顺序排序。
let arr = ['B', 'C', 'A']; arr.sort(); arr; // ['A', 'B', 'C']
-
reverse() 元素反转
let arr = ['one', 'two', 'three']; arr.reverse(); arr; // ['three', 'two', 'one']
-
splice()
- 是修改 Array 的 万能方法;
- 可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素。
let arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle']; // 从索引2开始删除3个元素,然后再添加两个元素: arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL','Excite'] arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle'] // 只删除,不添加: arr.splice(2, 2); // ['Google', 'Facebook'] arr; // ['Microsoft', 'Apple', 'Oracle'] // 只添加,不删除: arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素 arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
-
concat()
- 当前的 Array 和另一个 Array 连接起来,并返回一个 新的 Array。
let arr = ['A', 'B', 'C']; let added = arr.concat([1, 2, 3]); added; // ['A', 'B', 'C', 1, 2, 3] arr; // ['A', 'B', 'C']
-
join()
- 把当前 Array 的每个元素,用指定的字符串连接起来,返回连接后的字符串。
// 如果 Array 的元素不是字符串,将自动转换为字符串后再连接 let arr = ['A', 'B', 'C', 1, 2, 3]; arr.join('-'); // 'A-B-C-1-2-3'
-
多维数组
let arr = [[1, 2, 3], [400, 500, 600], '-']; let x = arr[1][1]; console.log(x); // 500
3. 对象
-
无序的集合数据类型,由若干键值对组成。
var 对象名 = { key: 'value', key: 'value', key: 'value' }; // 多个属性之间使用逗号隔开,最后一个属性不加逗号!
JavaScript 中的所有键都是字符串,值是任意对象!
-
定义一个对象
var person = { name: '小明', birth: 1990, school: 'No.1 Middle School', height: 1.70, weight: 65, score: null };
-
获取对象的属性:
对象.属性
或对象["属性"]
- 使用一个不存在的对象属性,不会报错!undefined
var person = { name: '小明' }; person["name"]; // '小明' person.age; // undefined
-
动态的给对象删除属性(delete)
var person = { name: '小明' }; delete person['name']; // 删除name属性 person.name; // undefined delete person.id; // 删除一个不存在的id属性也不会报错
-
动态给新的属性添加值
var person = { name: '小明' }; person.age = 18; // 新增一个age属性 person.age; // 18
-
判断对象是否拥有某一属性:in 操作符
var person = { name: '小明', birth: 1990, school: 'No.1 Middle School', height: 1.70, weight: 65, score: null }; 'name' in person; // true 'id' in person; // false // 继承的属性也为true 'toString' in person; // true
-
hasOwnProperty():判断是否是对象自身拥有的属性
var person = { name: '小明' }; person.hasOwnProperty("toString"); // false 继承属性非自身 person.hasOwnProperty("name"); // true
4. 流程控制
-
if 判断
let age = 8; if (age >= 18) { alert('adult'); } else if (age >= 13) { alert('teenager'); } else { alert('kid'); }
-
while 循环:避免程序死循环
while(true)
let x = 0; let n = 99; while (n > 0) { x += n; // x = x + n; n -= 2; // n = n - 2; } console.log(x); // 2500 计算奇数和
-
do...while:无论条件是否为真,都先执行一遍
let n = 0; do { n += 1; // n = n + 1; } while (n < 100); console.log(n); // 100
-
for 循环
// 基础for let x = 0; let i; for (i = 1; i <= 100; i++) { x += i; // x = x + i; } console.log(x); // 5050
// 遍历数组 let arr = ['Apple', 'Google', 'Microsoft']; let i, x; for (i = 0; i < arr.length; i++) { x = arr[i]; console.log(x); }
编写循环代码时,务必小心编写 初始条件 和 判断条件,尤其是 边界值。
特别注意
i < 100
和i <= 100
是不同的判断逻辑。-
无限循环
let x = 0; for (; ;) { // 未设置判断条件,将无限循环 if (x > 100) { break; // 通过if判断来退出循环 } x++; }
-
for ... in:遍历属性 (数组为索引)
let person = { name: 'Jack', age: 20, city: 'Beijing' }; // 遍历属性 for (let key in person) { // 判断属性是否为自身属性 if (person.hasOwnProperty(key)) console.log(`${key}:${person[key]}`); }
// Array也是对象,元素的索引视为对象的属性,遍历出来是下标 let a = ['A', 'B', 'C']; for (let i in a) { console.log(i); // '0', '1', '2' console.log(a[i]); // 'A', 'B', 'C' }
注意:for ... in 对 Array 的循环得到的是 String 而不是 Number
-
for ... of(ES6):遍历属性值 (数组为元素)
const cars = ["BMW", "Volvo", "Mini"]; for (let x of cars) { console.log(x); // "BMW", "Volvo", "Mini" }
// 遍历字符串 let language = "JavaScript"; for (let x of language) { console.log(x); // 'J','a'...'t' }
// 遍历数组(获取为元素,非下标) let a = ['A', 'B', 'C']; for (let i of a) { console.log(i); // 'A', 'B', 'C' }
-
forEach 循环(ES5.1):遍历数组元素及索引,不能遍历对象
- 对象转数组:
Object.keys(person);
// 遍历元素及索引 let age = [12, 3, 4, 22, 23, 55]; // 传递函数(lambda简化) age.forEach((value, index) => { console.log(`${index}:${value}`); }); // age.forEach(function (value, index) { // console.log(`${index}:${value}`); // });
- 对象转数组:
5. Map 和 Set
- 默认对象表示方式 { } ,类似其他语言中的 Map 或 Dictionary 的数据结构,即一组键值对;
- JavaScript 的对象,键必须是字符串,无法用 Number 或者其他数据类型作为键;
- ES6 引入了新的数据类型 Map。
Map:
K-V
(K不能重复)
// 学生姓名,学生成绩
// let names = ['Michael', 'Bob', 'Tracy'];
// let scores = [95, 75, 85];
// Map 实现
let map = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]);
let name = map.get('Michael'); // 95 通过key获取value
map.set('admin', 100); // 添加元素
map.delete('Bob'); // 删除元素
Set:无序不重复的集合
- Set 和 Map 类似,也是一组 key 的集合,但不存储 value;
- 由于 key 不能重复,所以,在 Set 中,没有重复的 key。
let set = new Set([3, 2, 1]);
set.add(5); // 增加元素
set.delete(3); // 删除元素
set.has(1); // 判断是否包含元素
set.size; // 长度
6. iterator
遍历 Array 可以采用下标循环,遍历 Map 和 Set 就无法使用下标;
为了统一集合类型,ES6 标准引入了新的 iterable 类型(Array、Map、Set);
具有 iterable 类型的集合可以通过新的 for ... of 循环来遍历。
-
遍历数组
// 通过for...of遍历值 / for...in 遍历下标 let array = [1, 2, 3]; for (let x of array) { console.log(x); }
-
遍历 Map
let map = new Map([['tom', 100], ['jack', 90], ['tim', 80]]); for (let x of map) { console.log(x); }
-
遍历 Set
let set = new Set([3, 2, 1]); for (let x of set) { console.log(x); }
-
forEach 方法:
- ES5.1 同时获得
K-V
; - 它接收一个函数,每次迭代就自动回调该函数。
// Array let a = ['A', 'B', 'C']; // element: 当前元素值 index: 当前索引 array: Array对象本身 a.forEach((element, index, array) => { console.log(`index = ${index}:${element}`); }); // Set let s = new Set(['set1', 'set2', 'set3']); // 没有索引,前两个参数都是元素本身 s.forEach((element, sameElement, set) => { console.log(element); }); // Map let m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]); m.forEach((value, key, map) => { console.log(`${key}:${value}`); });
- ES5.1 同时获得
四、函数
1. 定义函数
定义方式一 :推荐
- function 函数名(参数){...}
// 求一个数的绝对值
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
- 一旦执行到 return 时,函数就执行完毕,并将结果返回;
- 如果没有 return 语句,函数执行完毕后也会返回结果,只是结果为 undefined 。
定义方式二:
- var 函数名 = function (参数){...};
let abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
};
- function(x){….} 这是一个匿名函数,把结果赋值给 abs,通过 abs 调用函数;
- 两种定义完全等价;
- 注意:第二种方式,按照完整语法需要在函数体末尾加一个
;
,表示赋值语句结束。
2. 调用函数
-
调用函数时,按顺序传入参数即可:
abs(10); // 返回10 abs(-9); // 返回9
-
参数问题:JavaScript 可以传任意多个参数;
abs(10, 'blablabla'); // 返回10 abs(-9, 'haha', 'hehe', null); // 返回9
-
传入的参数比定义的少也没有问题,也可以不传:
abs(); // 返回NaN
此时 abs(x) 函数的参数 x 将收到 undefined ,计算结果为 NaN。
要避免收到 undefined ,可以对参数进行检查:
function abs(x) {
// 判断参数是否为 number 类型
if (typeof x !== 'number') {
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
arguments
arguments 是关键字,只在函数内部起作用;
永远指向当前函数的调用者传入的所有参数;
-
是一个数组。
function foo(x) { console.log(`x = ${x}`); // 10 for (let i = 0; i < arguments.length; i++) { console.log(`arg[${i}]=${arguments[i]}`); } } foo(10, 20, 30);
-
利用 arguments ,即使函数不定义任何参数,还是可以拿到参数的值:
function abs() { if (arguments.length === 0) { return 0; } let x = arguments[0]; return x >= 0 ? x : -x; } abs(); // 0 abs(10); // 10 abs(-9); // 9
-
arguments 最常用于判断传入参数的个数:
// foo(a[, b], c) // 接收2~3个参数,b是可选参数,如果只传2个参数,b默认为null: function foo(a, b, c) { if (arguments.length === 2) { // 实际拿到的参数是a和b,c为undefined c = b; // 把b赋给c b = null; // b变为默认值 } // ... }
arguments 包含所有的参数,想使用多余的参数操作,需要排除已有参数。
rest 参数
-
ES6 以前,获取参数:
function foo(a, b) { let i, rest = []; if (arguments.length > 2) { // 将多出来的参数,放入数组 for (i = 2; i < arguments.length; i++) { rest.push(arguments[i]); } } console.log('a = ' + a); console.log('b = ' + b); console.log(rest); }
-
ES6 标准引入了rest 参数,获取除了已定义的参数之外所有的参数
…
function foo(a, b, ...rest) { console.log('a = ' + a); console.log('b = ' + 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 []
注意:rest 参数只能写在最后,前面必须用 ... 标识
从运行结果可知,传入的参数先绑定 a 、 b ,多余的参数以数组形式交给变量 rest ,不再需要 arguments 就获取全部参数。
3. 变量的作用域
- 变量作用域
在 JavaScript 中,
var
定义的变量,是有作用域的;-
如果在 函数体内 声明,则在 函数体外 不可以使用。
function foo() { var x = 1; } x = x + 2; // Uncaught ReferenceError: x is not defined
-
不同函数内部的同名变量,互不影响:
function foo() { var x = 1; x = x + 1; } function bar() { var x = 'A'; x = x + 'B'; }
-
内部函数可以访问外部函数的成员,反之则不行:
function foo() { var x = 1; function bar() { var y = x + 1; // bar可以访问foo的变量x! } var z = y + 1; // ReferenceError! foo不可以访问bar的变量y! }
-
内部函数和外部函数的变量名重名
function foo() { var x = 1; function bar() { var x = 'A'; console.log('x in bar() = ' + x); // 'A' } console.log('x in foo() = ' + x); // 1 bar(); } foo();
总结:
- JavaScript 的函数,查找变量从自身函数开始,从 内 向 外 查找。
- 如果内部函数、外部函数有 重名的变量,则内部函数的变量将 屏蔽 外部函数的变量。
- 提升变量作用域
-
JavaScript 的函数定义特点,先扫描整个函数体的语句,把所有声明的变量 自动提升到函数顶部:
function foo() { var x = 'Hello, ' + y; console.log(x); var y = 'Bob'; } foo();
结果:Hello, undefined , 说明 y 的值为 undefined;
自动提升了变量 y 的声明,但不提升变量 y 的赋值;
-
JavaScript 引擎看到的代码相当于:
function foo() { var y; // 提升变量y的声明,此时y为undefined var x = 'Hello, ' + y; console.log(x); y = 'Bob'; }
在函数内部定义变量时,要严格遵守 在函数内部,首先申明所有变量;
-
常见做法:在函数头部,先声明,再使用:
function foo() { var x = 1, // x初始化为1 y = x + 1, // y初始化为2 z, i; // z和i为undefined // 其他语句: }
- 全局函数
- 不在任何函数内定义的变量,就具有全局作用域。
// 全局变量 var a = 1; function f() { console.log(a); } f(); console.log(a);
全局对象 window
-
JavaScript 默认有一个全局对象 window ,全局作用域的变量,实际上是被绑定到 window 的一个属性:
`use strict` var course = 'JavaScript'; alert(course); // 'JavaScript' alert(window.course); // 'JavaScript
直接访问全局变量 course 和访问 window.course 是完全一样;
-
顶层函数的定义,也被视为一个全局变量,并绑定到 window 对象:
function foo() { alert('foo'); } foo(); // 直接调用foo() window.foo(); // 通过window.foo()调用
alert() 函数其实也是 window 的一个变量:
window.alert('调用window.alert()');
// 把alert保存到另一个变量:
var old_alert = window.alert;
// 给alert赋一个新函数:
window.alert = function () {
}
alert('无法用alert()显示了!');
// 恢复alert:
window.alert = old_alert;
alert('又可以用alert()了!');
说明:
- JavaScript 实际上只有一个全局作用域。
- 任何变量(函数也视为变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后如果在全局作用域中也没有找到,则报
ReferenceError
错误。
规范:
- 不同的 JavaScript 文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现;
- 减少冲突:把所有变量和函数,全部绑定到一个自定义全局变量中。
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
- 把自己的代码全部放入唯一的名字空间 MYAPP 中,会大大减少全局变量冲突的可能。
- 很多 JavaScript 库都也是这样的:jQuery,YUI,underscore等等。
- 局部作用域:let
-
在 for 循环等语句块中,是无法定义具有局部作用域的变量的:
function foo() { for (var i = 0; i < 100; i++) { // ... } i += 100; // 问题:出了作用域,仍然可以引用变量i console.log(i); // 200 }
-
ES6 用
let
替代var
,解决局部作用域冲突问题function foo() { for (let i = 0; i < 100; i++) { // ... } i += 100; // Uncaught ReferenceError: i is not defined console.log(i); }
建议:用 let 定义局部作用域变量。
- 常量 const
-
ES6 之前:用全部大写的变量来表示 常量,建议不要修改它的值:
var PI = 3.14 console.log(PI); PI = 123; // 值可以修改 console.log(PI);
-
ES6 引入了关键字
const
定义常量,const
与let
都具有块级作用域:const PI = 3.14; // 只读变量 console.log(PI); // TypeError: Assignment to constant variable PI = 123; // 某些浏览器不报错,但是无效果 console.log(PI); // 3.14
4. 方法
定义方法
方法:对象中的函数,称为这个对象的方法;
对象只包含:属性、方法;
方法调用:
对象.方法名();
,方法名后,一定要带()
;-
实例:
var person = { name: '小明', birth: 1990, age: function () { // 获取当前年份 let y = new Date().getFullYear(); // 返回年龄 // this 当前调用它的对象 return y - this.birth; } }; person.age; // 返回值:方法 person.age() person.age(); // 返回具体数值
在方法内部,this 始终指向当前调用它对象,如:person 调用,this.birth 则指向 person 的 birth 属性。
-
把上述代码拆分:
// 把年龄方法拆分出来(函数) function getAge() { let y = new Date().getFullYear(); return y - this.birth; } var person = { name: '小明', birth: 1990, age: getAge }; person.age(); // 正常结果 getAge(); // NaN
分析:
-
person.age();
:以对象的方法形式调用,this 指向对象 person,所以返回 person 的属性; -
getAge();
:单独调用函数,this 指向全局对象(window),没有对应的属性,所以返回 NaN。
-
apply:控制 this 的指向
-
函数名.apply(对象,[]);
; - JavaScript 的所有函数都可以调用;
- 接收两个参数:
- this 指向的变量;
- Array :数组对象,表示函数本身。
function getAge() { let y = new Date().getFullYear(); return y - this.birth; } var person = { name: '小明', birth: 1990, age: getAge }; // person:this指向的对象; []:空参,数组对象 getAge.apply(person,[]);
五、内部对象
1. 标准对象
- JavaScript 里,一切都是对象;
- typeof:获取对象的类型,返回字符串:
typeof 123; // 'number'
typeof NaN; // 'number'
typeof 'str'; // 'string'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof Math.abs; // 'function'
typeof null; // 'object'
typeof []; // 'object'
typeof {}; // 'object'
2. Date
- Date:表示日期和时间
var now = new Date();
now; // Mon Feb 07 2022 10:44:36 GMT+0800 (中国标准时间)
now.getFullYear(); // 年份
now.getMonth(); // 月份:范围是0~11,值需要 +1
now.getDate(); // 日
now.getDay(); // 星期
now.getHours(); // 小时, 24小时制
now.getMinutes(); // 分钟
now.getSeconds(); // 秒
now.getMilliseconds(); // 毫秒数
now.getTime(); // 时间戳 全世界统一 1970 1.1 00:00:00 毫秒数
console.log(new Date(1644204579172)); // 时间戳转为时间
注意:当前时间是浏览器从本机操作系统获取的时间,不一定准确,当前时间设定为任何值。
-
转换
var d = new Date(1644204579172); d.toLocaleString; // 注意:调用的是一个方法,不是一个属性 d.toLocaleString(); // 显示的字符串与操作系统设定的格式有关 d.toUTCString(); // UTC时间,与本地时间相差8小时
时区转换:只需要传递时间戳,或者从数据库里读时间戳,再让 JavaScript 自动转换为当地时间。
-
获取当前时间戳:
if (Date.now) { // 老版本IE没有now()方法 console.log(Date.now()); } else { console.log(new Date().getTime()); }
3. JSON
- 早期,所有的数据传输习惯都使用 XML 文件
- JSON(JavaScript Object Notation,JS 对象标记)轻量级数据交换格式;
- 简洁、结构清晰,使 JSON 成为理想的数据交换语言;
- 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率;
- 在 JavaScript 中,一切皆为对象,任何 JS 支持的类型,都可以用 JSON 来表示。
格式:
- 对象:
{}
- 数组:
[]
- 键值对:
key:value
- 对象:
-
JSON 键值对:
{"name":"Tom"} {"age":"20"} {"sex":"男"}
-
JSON 是 JavaScript 对象的字符串表示法,使用文本表示一个 JS 对象的信息,本质是一个字符串。
// 对象:键名也可以使用引号包裹 var obj = {a: 'Hello', b: 'World'}; // JSON:本质是一个字符串 var json = '{"a": "Hello", "b": "World"}';
-
JSON 和 JavaScript 对象互转:
-
JSON.parse();
:JSON 转 JavaScript 对象
// JSON 转 JavaScript 对象 var obj = JSON.parse('{"a": "Hello", "b": "World"}'); // 结果是 {a: 'Hello', b: 'World'}
-
JSON.stringify();
:JavaScript 对象转 JSON
// JavaScript 对象转 JSON var json = JSON.stringify({a: 'Hello', b: 'World'}); // 结果是 '{"a": "Hello", "b": "World"}'
-
实例:
JSON
4. Ajax
- 原生的 JS 写法,xhr 异步请求;
- jQuery 封装好的方法
$(“#name”).ajax(“”)
; - axios 请求。
六、面向对象编程
1. 原型对象
-
面向对象的两个基本概念:
- 类:对象的类型模板,原型对象;
- 对象:根据类创建的具体实例。
-
JavaScript 的面向对象,和其他语言,如:Java、C# 的面向对象,有区别:
- JavaScript 不区分 类 和 实例 的概念;
- 通过 原型 (prototype)来实现面向对象编程。
-
原型:可以理解为父类(但不同),B 对象,通过指向,使用 A 对象的属性和方法,A 就是 B 的原型对象;
// user 的原型对象 var student = { name: 'Robot', age: 20, run: function () { console.log(this.name + ' is running...'); } }; var user = { name: '小明' }; // user的原型对象是student user.__proto__ = student;
2. class
JavaScript 的对象模型是基于原型实现的;
优点:简单;
-
缺点:
- 比传统的
类-实例
模型困难; - 继承的实现需要编写大量代码;
- 需要正确实现原型链。
// 了解:函数实现 Student 的方法 function Student(name) { this.name = name; } // 通过原型,给Student新增一个方法 Student.prototype.hello = function () { alert('Hello, ' + this.name + '!'); }; var user = new Student("user").hello();
- 比传统的
-
ES6 引入 class
// ES6 定义一个学生类 class Student { // constructor 构造函数 constructor(name) { this.name = name; } hello() { alert(`Hello,${this.name}`); } } // 创建实例 let user1 = new Student("user1"); let user2 = new Student("user2"); // 调用 user1.hello(); user2.hello();
3. 继承 extends
// 学生类
class Student {
constructor(name) {
this.name = name;
}
hello() {
alert('hello')
}
}
// 继承学生类
class PrimaryStudent extends Student {
// 本类的构造函数
constructor(name, grade) {
// 用super调用父类的构造方法!
super(name);
this.grade = grade;
}
// 本类的方法
myGrade() {
alert(`I am at grade ${this.grade}`);
}
}
// 调用父类的方法
let student1 = new PrimaryStudent("小明", 5).hello();
// 调用本类的方法
let student2 = new PrimaryStudent("小明", 5).myGrade();
- 本质:对象原型
4. 原型链
七、操作 BOM 对象(重点)
1. 浏览器介绍
- JavaScript 和浏览器的关系:为了能在浏览器中运行;
- BOM:浏览器对象模型;
- IE 6~11;
- Chrome;
- Safari;
- FireFox;
- Opera;
第三方
- QQ 浏览器;
- 360;
2. window(重要)
- window 对象不但充当全局作用域,而且表示浏览器窗口;
- innerWidth、innerHeight:浏览器窗口的 内部 宽度和高度;
- 内部宽高:除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高;
- outerWidth、outerHeight:浏览器窗口的整个宽高;
- 兼容性:IE<= 8 不支持。
window.alert(1); window.innerHeight; // 内部高度 window.innerwidth; // 内部宽度 window.outerHeight; // 外部高度 window.outerWidth; // 外部宽度 //调整游览器窗口,值会改变
3. navigator 浏览器信息(不建议使用)
-
navigator 对象表示浏览器的信息,最常用的属性包括:
- navigator.appName:浏览器名称;
- navigator.appVersion:浏览器版本;
- navigator.language:浏览器设置的语言;
- navigator.platform:操作系统类型;
- navigator.userAgent:浏览器设定的 User-Agent 字符串。
-
注意:navigator 的信息可以被用户修改,所以 JavaScript 读取的值不一定是正确的。不建议直接使用,例如:
let width; if (getIEVersion(navigator.userAgent) < 9) { width = document.body.clientWidth; } else { width = window.innerWidth; }
-
建议:利用 JavaScript 对不存在属性返回 undefined 的特性,直接用短路运算符 || 计算:
let width = window.innerWidth || document.body.clientWidth;
4. screen 屏幕信息
- screen 对象表示屏幕的信息;
screen.width; // 屏幕宽度,以像素为单位 screen.height; // 屏幕高度,以像素为单位
5. location(重要)
- location 对象表示当前页面的 URL 信息
location.protocol; // 'http' location.host; // "www.baidu.com" location.port; // '8080' location.href; // "https://www.baidu.com/" location.pathname; // '/path/index.html' location.reload(); //刷新网页 location.search; // '?a=1&b=2' location.assign('新的网站'); // 设置新的地址
6. document(内容 DOM)
-
document 当前的页面,HTML DOM文档树;
document.title='test'; // 改变页面标题
-
获取具体的文档树节点:
- Java
- Java SE
- Java EE
-
获取 cookie:
document.cookie; // 'v=123; remember=true; prefer=zh'
-
劫持 cookie 原理:
为了确保安全,服务器端可以设置 cookie:httpOnly。
7. history(不建议使用)
- 浏览器的历史记录
// 不建议使用 history.back(); // 后退 history.forward(); // 前进
八、操作 DOM 对象(重点)
1. 核心(选择器)
- 网页 HTML 就是 DOM 树形结构,JavaScript 常用操作:
- 更新:更新 DOM 节点表示的 HTML 内容;
- 遍历:遍历 DOM 节点下的子节点,以便进行进一步操作;
- 添加:在 DOM 节点下新增一个子节点,相当于动态增加了一个 HTML 节点;
- 删除:将该节点从 HTML 中删除,相当于删除该 DOM 节点,以及它所有的子节点。(先获取该节点的父节点,通过父节点删除)
获得 DOM 节点
- 操作一个 DOM 节点前,需要先获取该 DOM 节点:
- id 选择器(唯一 DOM):
document.getElementById();
- class 选择器:
document.getElementsByClassName();
- 标签选择器:
document.getElementsByTagName();
- id 选择器(唯一 DOM):
- 除 id 选择器外,其它是返回一组 DOM 节点,要精确地选择 DOM,可以先定位父节点,再从父节点开始选择,以缩小范围。
// 对应 css选择器
var h1 = document.getElementsByTagName('h1');
var p1 = document.getElementById('p1');
var p2 = document.getElementsByClassName('p2');
var father = document.getElementById('father');
//获取父节点下所有的子节点
var childrens = father.children;
var childrens = father.children[index]; // 某个节点
// father.firstChild;
// father.lastChild;
- CSS 选择器:使用条件来获取节点
-
document.querySelector();
:符合条件的第一个 DOM; -
document.querySelectorAll();
:符合条件的所有 DOM;
-
// 通过querySelector获取ID为q1的节点:
var q1 = document.querySelector('#q1');
// 通过querySelectorAll获取q1节点内的符合条件的所有节点:
var ps = q1.querySelectorAll('div.highlighted > p');
// 获取第一个带target属性的a元素
var node = document.querySelector("a[target]");
- 注意:低版本的IE<8不支持 querySelector 和 querySelectorAll , IE8仅有限支持。
2. 更新 DOM
-
修改节点的文本:覆盖
- innerHTML:可以解析 HTML 文本标签;
- innerText:只是文本。
// 获取
...
var p = document.getElementById('p-id'); // 设置文本为ABC: p.innerText = 'ABC'; //ABC
p.innerText = 'ABC'; // ABC // 设置HTML: p.innerHTML = 'ABC RED XYZ'; //...
的内部结构已修改 用 innerHTML 时要注意,对字符编码,避免 XSS 攻击;
-
修改 CSS:DOM 节点的 style 属性对应所有的 CSS,可以直接获取或设置:
// 获取
...
var p = document.getElementById('p-id'); // 设置CSS: p.style.color = '#ff0000'; // 属性使用''包裹 p.style.fontSize = '20px'; // font-size 转驼峰命名 p.style.paddingTop = '2em';
3. 插入 DOM:
插入节点
- DOM 节点为空:
- 通过 innerHTML 可以直接修改,相当于增加一个元素了;
- DOM节点已存在元素:直接替换原来的所有子节点。
追加(末尾):appendChild
JavaScript
Java
Python
Scheme
创建新的标签,实现插入(末尾追加)
var
list = document.getElementById('list'),
haskell = document.createElement('p'); // 创建新的节点
haskell.id = 'haskell'; // 新标签添加 id
haskell.innerText = 'Haskell'; // 新标签添加内容
list.appendChild(haskell); // 追加
- 动态创建一个节点,添加到 DOM 树中:
// 可以创建style、script标签
var d = document.createElement('style');
var a = document.createElement('script');
a.innerHTML = 'alert("hi!!");'
// 设置标签属性:键值对
a.setAttribute('src','js/test.js'); // 动态加载js文件
d.setAttribute('type', 'text/css');
d.innerHTML = 'body { background: red }';
// head 头部标签
document.getElementsByTagName('head')[0].appendChild(d);
document.getElementsByTagName('head')[0].appendChild(a);
节点前插入:insertBefore
var ee = document.getElementById('ee');
var py = document.getElementById('py');
var list = document.getElementById('list');
// list 插入位置的父节点,insertBefore(new,target)
list.insertBefore(a, py);
4. 删除 DOM
-
删除步骤:先获取父节点,再调用父节点的
removeChild
把自己删掉:// 拿到待删除节点: var self = document.getElementById('to-be-removed'); // 拿到父节点: var parent = self.parentElement; // 删除: var removed = parent.removeChild(self); removed === self; // true // 删除父元素的第一个子元素 parent.removeChild(parent.children[0]);
-
注意:
- 删除后的节点虽然不在文档树中了,但其实它还在内存中,可以随时再次被添加到别的位置。
- 遍历删除多个节点时,children 值是实时更新的,一定要注意!
var parent = document.getElementById('parent'); // 删除:是动态过程 parent.removeChild(parent.children[0]); // 浏览器报错:节点数从2变为1,索引 [1] 已不存在 parent.removeChild(parent.children[1]);
九、操作表单(验证)
1. 回顾
- JavaScript 操作表单和操作 DOM 是类似的,表单本身也是 DOM 树;
- 表单输入框、下拉框等可以接收用户输入, JavaScript 可以获得用户输入的内容,或者设置新的内容。
- HTML 表单的输入控件:
- 文本框:
,用于输入文本;
- 密码框:
,用于输入密码;
- 单选框:
,用于选择一项;
- 复选框:
,用于选择多项;
- 下拉框:
,用于选择一项;
- 隐藏文本:
,用户不可见,但表单提交时,会把隐藏文本发送到服务器。
- 文本框:
2. 获取值
-
通过获取
input
节点,直接调用 value 获得对应的用户输入值:// let input = document.getElementById('email'); input.value; // 用户输入的值
以上方式可以应用于 text 、password 、hidden 以及 select;
-
单选框、复选框 value 属性返回的是 HTML 预设的值,需要用 checked 判断:
3. 设置值
-
设置值和获取值类似,对于 text 、password 、hidden 以及 select ,直接设置 value 就可以:
// let input = document.getElementById('email'); input.value = '[email protected]';// 文本框的内容已更新
单选框和复选框,设置 checked 为 true 或 false 即可。
4. 提交表单
JavaScript 提交表单的方式:
-
方式 1(不推荐):onclick 事件响应;
-
按钮提交表单:
type="button"
(会绕过onsubmit())// 1.submit() // 2.this.form.submit() // 3.document.表单名.submit()
上述方式的缺点:扰乱了浏览器对 form 的正常提交,浏览器默认是点击提交按钮
type="submit"
,或在最后一个输入框按回车键;-
方式 2(推荐):表单绑定提交事件
-
注意:
- return true 浏览器继续提交,如果 return false ,通常对应用户输入有误(表单验证),提示用户错误信息后,终止提交 form;
- 在检查和修改时,为保证安全,要利用隐藏文本
来传递数据;
- 用户名和密码,提交表单时不要传输明文口令,而是口令的 MD5;
- 表单元素,只有设置了 name 属性,提交表单时才能传值。
十、jQuery (了解)
1. 什么是 jquery
- jquery 是 JavaScript 中的一个库;
- 理念:写更少的代码,完成更多的工作。
- jQuery 只是一个 jquery-xxx.js 文件,有 compressed(已压缩)和 uncompressed(未压缩)两种版本,使用时完全一样。
获取 jquery
- 官网
- jQuery 中文文档
- jQuery文档2
使用 jQuery
Title
点我试试
- 公式:
$(selector).action()
- 美元符号定义 jQuery;
- 选择符(selector)"查询" 和 "查找" HTML 元素;
- jQuery 的 action() 执行对元素的操作。
2. 选择器
// 原生态js,选择器少,麻烦不好记
// 标签
document.getElementsByTagName();
// id
document.getElementById();
// 类
document.getElementsByClassName();
// jquery css中的选择器通用
$('p').click(); // 标签选择器
$('#id').click(); // id选择器
$('.class1').click(); // class选择器
// 公式:
$(selector).action()
3. 操作 DOM
- 节点文本操作
$('#test-ul li[name=python]').text(); // 获得值
$('#test-ul li[name=python]').text('设置值');// 设置值
$('#test-ul').html(); // 获得值
$('#test-ul').html('123'); // 设置值
- css 的操作
$('#test-ul li[name=python]').css("color","red")
- 元素的显示和隐藏,本质display:none;
$('#test-ul li[name=python]').show();
$('#test-ul li[name=python]').hide();
- 娱乐测试
$(window).width();
$(window).height();
// 显示、隐藏切换
$('#test-ul li[name=python]').toggle();
4. 事件
- 鼠标事件、键盘事件、其他事件
Title
mouse:
在这里移动鼠标试试
小技巧
- 巩固 JS(看游戏源码)
- 巩固 HTML,CSS(扒网站,全部 down 下来,然后对应修改看效果)
- Element-ui等