Skip to content

JavaScript( ECMA Script ) ​

JavaScript 这门语言属于一系列 Web 标准套件中的一部分,这些标准定义出了可互操作的浏览器平台。此部分的内容主要是 ES6 以前的语法,ES6 及其更后的版本详见《ECMAScript6 基础笔记》

作用 ​

一种高级编程语言,运行在浏览器上的一种脚本语言。原名 livescript,为了提高 HTML 网页与用户之间的交互功能,提高用户的体验。其它的脚本语言如:vbscript,jscript, tcl。

运行在浏览器上的语言,是可以看到源代码。

特点 ​

  • 安全性:因为 JavaScript 只能运行在浏览器端,不能直接访问本地客户端硬盘上的文件。最多让浏览器崩溃。
  • 跨平台:无论电脑,手机,平板,只要设备上有浏览器,就可以运行 JS 代码

JavaScript 与 Java 区别 ​

JavaJavaScript
出品公司Oracle网景公司
面向对象完全面向对象:封装、继承、多态基于对象语言,有一些面向对象特性
运行方式编译型语言,会产生字节码中间文件。最终是运行字节码文件。解释型,不会产生中间文件 浏览器解析一行运行一句。
数据类型强类型,字符串与整数不能互用弱类型,不同的数据类型之间会自动转换,可以相互赋值。

JavaScript三大组成部分 ​

取值作用
ECMAScript也是一种脚本语言的标准,构成了JS的脚本语法基础
BOMBrowser Object Model 浏览器对象模型,用来操作浏览器中的对象。如:window对象
DOMDocument Object Model 文档对象模型,用来操作网页中的元素。如:<h1>、<span>

JavaScript 基础语法( ECMA Script ) ​

注释 ​

js
// 单行注释

/*
 多行注释
*/

引入方式 ​

  1. JS 脚本放在 HTML 文件中内部,写在 HTML 内部时需要写在 <script> 标签中
  2. JS 脚本以文件方式存在于 HTML 外部,扩展名叫 js。写在 HTML 外部 js 文件中,直接写脚本内容即可。使用的时候需要导入。(常用)

script 标签 ​

<script> 标签的主要属性如下:

  • src:要导入外部 js 文件
  • type:指定文件类型 text/javascript
  • charset:设置字符集(可选属性)。

在 html 中使用示例:

html
<!-- 导入外部的文件 -->
<script src="js/out.js" type="text/javascript" charset="utf-8"></script>

<script> 标签的特点与使用注意事项:

  • 标签个数:在一个网页中可以使用多个 script 标签,并且每个标签会由上往下依次执行
  • 标签出现的位置:可以出现在 HTML 中任意位置,甚至可以出现在 HTML 的标签之外。
  • 关于语句后面的分号:如果每条语句写一行,则可以省略分号。如果一行出现多条语句,分号就不能省略。建议以后都不要省略。
  • 大小写敏感性:与 Java 相同,JS 中的大小写也是敏感的,区分大小写。
  • 所有 HTML 中 JS 代码都要写在<script>这个标签中,如果是写在 JS 文件中,则不需要<script>标签,但在HTML引用时也需要
html
<script type="text/javascript">
	for (i=0; i<5; i++){
		document.writeln("Hello World<br/>");
	}
</script>

var (变量) ​

变量的定义 ​

定义变量的关键字 var (variable)。不区分类型,统一使用 var 表示变量

变量类型(对应Java类型)JS变量定义格式
整数(int)var i = 10;
双精度(double)var d = 3.14;
字符串(String)var str = "abc";
字符(char)var c = 'a';
布尔(boolean)var b = true;

JS中关于字符和字符串类型 ​

在 JS 中没有字符和字符串区分,只有字符串类型,没有字符类型。字符串即可以使用双引号,也可以使用单引号

js
var str = "abc";
var c = 'a';

弱类型 ​

不同的数据类型之间可以相互转换,可以相互赋值

变量定义的注意问题 ​

  1. var 关键字不是必须存在,可以省略,但建议写上
  2. 一个变量名在 JS 中是可以重复定义的(但不推荐)
  3. 大括号在 JS 中不会对变量造成隔离的作用

NaN 的概念 ​

NaN: Not a Number,不是一个数,在字符串转整数中,如果不能进行转换,则返回 NaN

JavaScript 数据类型 ​

5 种基本数据类型 ​

关键字数据类型
Number数值型(整数和浮点数)
String字符串类型(字符和字符串),可以使用单引号或双引号
Boolean布尔类型(true/false)
Object对象类型,对象类型格式:(定义对象,称为JSON对象
(JavaScript Object Notation, JS对象标记)是一种轻量级的数据交换格式
undefined未初始化的类型,如果一个变量没有赋值它的类型是不确定的
  • Object 示例
json
const obj = {
    name: "张三",
    age: 18
}

null与undefined的区别

  • null:其实是Object对象类型,这个对象只是没有值,为空
  • undefined:没有初始化的类型,并不知道是什么数据类型

typeof 操作符 ​

typeof 操作符作用:用于判断变量是什么数据类型,返回值是这个变量的数据类型。语法格式如下:

js
typeof(变量名)
// 或
typeof 变量名

示例:

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title></title>
	</head>
	<body>
		<!--引用外部的js文件,文件代码以下面的代码一样,在引用的时候执行js中的代码-->
		<script src="js/test.js" type="text/javascript" charset="utf-8"></script>

		<hr />

		<script type="text/javascript">
			//整数
			var i = 10;

			//双精度
			var d = 3.14;

			//字符串和字符
			var str = "abc";
			var c = 'a';

			//布尔类型
			var b = true;

			//js创建对象方法
			var o = new Object();
			var o = {name:"xx", age:18};
			var u;
			var n = null;

			document.write("i的类型:" + typeof(i) + "<br/>");
			document.write("d的类型:" + typeof(d) + "<br/>");
			document.write("s的类型:" + typeof(str) + "<br/>");
			document.write("c的类型:" + typeof(c) + "<br/>");
			document.write("b的类型:" + typeof(b) + "<br/>");
			document.write("o的姓名:" + o.name + "年龄:" + o.age + "<br/>");
			document.write("o的类型:" + typeof(o) + "<br/>");
			document.write("u的类型:" + typeof(u) + "<br/>");
			document.write("n的类型:" + typeof(n) + "<br/>");
		</script>
	</body>
</html>

类型强制转换 ​

String 强制转换为数字 ​

  • 可以用 *1 来转化为数字(实际上是调用.valueOf方法)。然后使用 Number.isNaN 来判断是否为NaN,或者使用 a!==a 来判断是否为 NaN,因为 NaN !== NaN
js
"32" * 1;  // 32
"ds" * 1;  // NaN
null * 1;  // 0
undefined * 1;  // NaN
1 * { valueOf: () => "3" };  // 3
  • 常用:也可以使用 + 来转化字符串为数字
js
+ "123"  // 123
+ "ds"  // NaN
+ ""    // 0
+ null  // 0
+ undefined  // NaN
+ { valueOf: () => "3" }  // 3

取整 |0 ​

对一个数字 |0 可以取整,负数也同样适用,num|0

js
const a = 1.3 | 0;  // 1
const b = -1.9 | 0; // -1

判断奇偶数 &1 ​

对一个数字 &1 可以判断奇偶数,负数也同样适用,num&1

js
const num = 3;
const b1 = !!(num & 1);  // true
const b2 = !!(num % 2);  // true

运算符 ​

双位运算符 ~~ ​

可以使用双位操作符来替代 Math.floor()。双否定位操作符的优势在于它执行相同的操作运行速度更快。

js
Math.floor(4.9) === 4;  // true

// 简写为:
~~4.9 === 4;  // true

不过要注意,对整数来说 ~~ 运算结果与 Math.floor() 运算结果相同,而对于负数来说不相同:

js
~~4.5;  // 4
Math.floor(4.5);  // 4

~~-4.5;  // -4
Math.floor(-4.5);  // -5

短路运算符 ​

常规用法 - 逻辑判断 ​

逻辑与 && 与逻辑或 || 是短路运算符。短路运算符就是从左到右的运算中前者满足要求,就不再执行后者了

  • && 为取假运算,从左到右依次判断,如果遇到一个假值,就返回假值,以后不再执行,否则返回最后一个真值
  • || 为取真运算,从左到右依次判断,如果遇到一个真值,就返回真值,以后不再执行,否则返回最后一个假值
js
let param1 = expr1 && expr2;
let param2 = expr1 || expr2;

其他用法 ​

  • 变量赋初值。如下例:如果variable1是真值就直接返回了,后面短路就不会被返回了,如果为假值,则会返回后面的 foo
js
let variable1;
let variable2 = variable1 || "foo";
  • 也可以用来进行简单的判断,取代冗长的if语句。如下例:如果 param 如果为真值则返回 param.prop 属性,否则返回 param 这个假值,这样在某些地方防止 param 为 undefined 的时候还取其属性造成报错。
js
let variable = param && param.prop;

流程控制语句 ​

if-else 判断 ​

语法格式:

js
if(条件) {
    需要执行的代码;
} else if(条件) {
	需要执行的代码;
} else {
	需要执行的代码;
}

不是布尔类型的条件,直接使用该类型的值用作条件

数据类型为真为假
number非00
string非空串空串
undefined假
NaN(Not a Number)假
Object非nullnull

switch 多分支 ​

case 使用变量名语法格式:

js
switch(变量名){
	case 常量1:
		需要执行的代码;
		break;
	case 常量1:
		需要执行的代码;
		break;
	...
	default:
		需要执行的代码;
		break;
}

case 使用表达式语法格式:

js
switch(true){
	case 表达式1:
		需要执行的代码;
		break;
	case 表达式2:
		需要执行的代码;
		break;
	...
	default:
		需要执行的代码;
		break;
}

循环 ​

while 语句 ​

语法格式

js
while(条件) {
	需要执行的代码;
}

do-while 语句 ​

语法格式

js
do {
	需要执行的代码;
} while(条件);

注:最少执行1次循环

for 语句 ​

语法格式

js
for(初始化语句; 判断条件语句; 控制条件语句){
	需要执行的代码;
}

break 和 continue ​

此两个关键字需要在循环语句中使用

  • break: 结束整个循环
  • continue: 结束本次循环,继续执行下一次循环

示例 ​

使用循环制作乘法表:

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>乘法表</title>
		<style type="text/css">
			table{
				/*使表格边框塌陷*/
				border-collapse: collapse;
				text-align: center;
			}
			
			td{
				padding: 5px;
			}
		</style>
	</head>
	<body>
			<script type="text/javascript">
				var num = window.prompt("请输入一个整数:","9");
				document.write("<table border='1px' align='center'>");
				document.write("<caption>乘法表</caption>");
				for (var i = 1; i <= num; i++) {
					document.write("<tr>");
						for (var j = 1; j <= i; j++) {
							document.write("<td>");
							document.write(j + "×" + i + "=" + (i*j));
							document.write("</td>");
						}
					document.write("</tr>");
				}
				document.write("</table>");
			</script>
	</body>
</html>

函数 ​

函数的定义与语法格式 ​

函数的定义 ​

  • 第1种方式:全名函数
js
function 函数名(参数列表) {
    代码块;
    // 如果有返回值就加return
    [return 返回值;]
}
  • 第2种方式:匿名函数
js
var 变量名 = function (参数列表) {
    代码块;
    // 如果有返回值就加return
    [return 返回值;]
}

示例

js
// 方式一 定义函数
function sayHi(name){
    // 弹出个新窗口显示 window可能省略
    window.alert("和 " + name + " 打个鸟招呼!");
}

// 定义一个name变量
var realName = "birdMan";
// 调用函数 或者直接sayHi("birdMan");
sayHi(realName);

// 方式二 匿名函数
var sayHi2 = function (name){
    window.alert("和 " + name + " 打第2个鸟招呼!");
}

// 调用函数,输入形式变量
sayHi2("鸟人");

参数类型与返回值 ​

  1. 形参的类型。在定义函数时,形参的类型是不需要指定的,因是可以变化的类型
  2. 函数的返回值。function前面没有返回类型,如果有返回值,使用return,如果没有返回值则不用写。

函数的重载 ​

在JS中没有函数的重载,后面定义的同名函数会覆盖前面的函数,只有最后一个函数起作用。

形参的个数与实参个数没有关系

arguments 隐式数组对象 ​

在每一个函数的内部都有一个隐式的数组对象,名字叫arguments,用来保存用户提交的实际参数。函数运行的时候形参从数组中去取值,再进行运算。

实参个数与形参个数无关

  • 调用函数时,隐式数组的执行流程
  1. 调用的时候,先会将形参的值赋值给arguments数组
  2. 执行的时候,实参从arguments数组中按顺序取出值,再进行计算

结论:函数的形参的个数与调用时实参的个数不用相等,可以不同。建议还是写成相同的个数。

Code Demo: 输出 arguments 的长度和数组中的每个元素

js
// 输出隐式数组
function innerArray(n1,n2){
    // 输出函数中的数组
    document.write("数组的长度是:" + arguments.length + "<br/>");
    // 输出数组中每个元素
    for (var i = 0; i<arguments.length; i++) {
        document.write(arguments[i] + "&nbsp;");
    }

    document.write("<br/>");
    document.write("n1 = " + n1 + "<br/>");
    document.write("n2 = " + n2 + "<br/>");
}

// 调用函数
innerArray(10);
innerArray(10,20);
innerArray(10,20,30);

输出结果:

数组的长度是:1
10
n1 = 10
n2 = undefined
数组的长度是:2
10 20
n1 = 10
n2 = 20
数组的长度是:3
10 20 30
n1 = 10
n2 = 20

函数内 this 的指向 ​

函数内 this 的指向,是当调用函数的时候确定的。调用方式的不同决定了this 的指向不同。一般指向调用者

js
// 函数的不同调用方式决定了this 的指向不同
// 1. 普通函数 this 指向window
function fn() {
  console.log("普通函数的this" + this)
}
window.fn()
// 2. 对象的方法 this指向的是对象 o
var o = {
  sayHi: function () {
    console.log("对象方法的this:" + this)
  },
}
o.sayHi()
// 3. 构造函数 this 指向 moon 这个实例对象原型对象里面的this 指向的也是moon这个实例对象
function Star() {}
Star.prototype.sing = function () {}
var moon = new Star()
// 4. 绑定事件函数 this 指向的是函数的调用者 btn这个按钮对象
var btn = document.querySelector("button")
btn.onclick = function () {
  console.log("绑定时间函数的this:" + this)
}
// 5. 定时器函数 this 指向的也是window
window.setTimeout(function () {
  console.log("定时器的this:" + this)
}, 1000)
// 6. 立即执行函数 this还是指向window
;(function () {
  console.log("立即执行函数的this" + this)
})()

JavaScript 专门提供了一些函数方法来处理函数内部 this 的指向问题,常用的有 bind()、call()、apply() 三种方法

call 方法 ​

语法:

js
fun.call(thisArg, arg1, arg2, ...)
  • thisArg:在 fun 函数运行时指定的 this 值
  • arg1,arg2:传递的其他参数
  • 返回值就是函数的返回值,因为它就是调用函数
js
// 改变函数内this指向,js提供了三种方法:call()  apply()  bind()
// 1. call()方法
var o = {
  name: "MooN",
}
function fn(a, b) {
  console.log(this)
  console.log(a + b)
}
// call 第一个可以调用函数 第二个可以改变函数内的this 指向
fn.call(o, 1, 2)
// call 的主要作用可以实现继承
function Father(uname, age, sex) {
  this.uname = uname
  this.age = age
  this.sex = sex
}
function Son(uname, age, sex) {
  Father.call(this, uname, age, sex)
}
var son = new Son("路飞", 18, "男")
console.log(son)

apply 方法 ​

apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。语法如下:

js
fun.apply(thisArg, [argsArray])
  • thisArg:在fun函数运行时指定的 this 值
  • argsArray:传递的值,必须包含在数组里面
  • 返回值就是函数的返回值,因为它就是调用函数
  • apply() 函数主要跟数组有关系,比如使用 Math.max() 求数组的最大值
js
// 改变函数内this指向  js提供了三种方法  call()  apply()  bind()
// 2. apply()  应用 运用的意思
var o = {
  name: "MooN",
}
function fn(arr) {
  console.log(this)
  console.log(arr) // 'pink'
}
fn.apply(o, ["pink"])
// 1. 也是调用函数 第二个可以改变函数内部的this指向
// 2. 但是他的参数必须是数组(伪数组)
// 3. apply 的主要应用 比如说可以利用 apply 借助于数学内置对象求数组最大值
// Math.max();
var arr = [1, 66, 3, 99, 4]
var arr1 = ["red", "pink"]
// var max = Math.max.apply(null, arr);
var max = Math.max.apply(Math, arr)
var min = Math.min.apply(Math, arr)
console.log(max, min)

bind 方法 ​

bind() 方法不会调用函数。但是能改变函数内部 this 指向。语法如下:

js
fun.bind(thisArg, arg1, arg2, ...)
  • thisArg:在 fun 函数运行时指定的 this 值
  • arg1,arg2:传递的其他参数
  • 返回由指定的 this 值和初始化参数改造的原函数拷贝
  • 因此当只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind()
js
// 改变函数内this指向  js提供了三种方法  call()  apply()  bind()
// 3. bind()方法 绑定
var o = {
  name: "MooN",
}
function fn(a, b) {
  console.log(this)
  console.log(a + b)
}
var f = fn.bind(o, 1, 2)
f()
// 1. 不会调用原来的函数,可以改变原来函数内部的this 指向
// 2. 返回的是原函数改变this之后产生的新函数
// 3. 如果有的函数不需要立即调用,但是又想改变这个函数内部的this指向此时用bind
// 示例:有一个按钮,当点击了之后,就禁用这个按钮,3秒钟之后开启这个按钮
/* var btn1 = document.querySelector("button")
btn1.onclick = function () {
  this.disabled = true // 这个this 指向的是 btn 这个按钮
  // var that = this;
  setTimeout(
    function () {
      // that.disabled = false; // 定时器函数里面的this 指向的是window
      this.disabled = false // 此时定时器函数里面的this 指向的是btn
    }.bind(this),
    3000
  ) // 这个this 指向的是btn 这个对象
} */
// 多个按钮
var btns = document.querySelectorAll("button")
for (var i = 0; i < btns.length; i++) {
  btns[i].onclick = function () {
    this.disabled = true
    setTimeout(
      function () {
        this.disabled = false
      }.bind(this),
      2000
    )
  }
}

改变函数内部 this 指向三种方式总结 ​

  • 相同点:
    • 都可以改变函数内部的this指向
  • 区别点:
    • call 和 apply 会调用函数,并且改变函数内部this指向
    • call 和 apply 传递的参数不一样,call 传递参数 arg1, arg2..形式。apply 必须数组形式[arg]
    • bind 不会调用函数,可以改变函数内部this指向
  • 主要应用场景:
    • call:经常做继承
    • apply:经常跟数组有关系。比如借助于数学对象实现数组最大值最小值
    • bind:不调用函数,但是还想改变this指向/比如改变定时器内部的this指向

闭包函数 ​

给函数使用()包裹,表示闭包函数,作用就是在页面打开的时候就执行地函数

js
(function(){
	xxx;
})();

全局函数 ​

什么是全局函数 ​

在 JS 中任何地方都可以直接调用的函数,不属于任何一个对象。

常用的全局函数 ​

Notes: window.alert() 这是 window 对象的方法,不是全局函数。只是因为 window 可以省略而已。

以下是部分全局函数

js
eval()
  • 执行函数。基本作用如下:
    1. 可以执行一段字符串中的JS代码;
    2. 可以对字符串的表达式进行运算,返回计算的结果;
    3. 可以调用字符串中函数
js
parseInt(String s)
  • 将一个字符串转成整数类型。如果不能转换,返回 NaN (Not a Number)
js
parseFloat(String s)
  • 将一个字符串转成小数类型。如果不能转换,返回 NaN (Not a Number)
js
isNaN(String s)
  • 一般用在转换数字之前,用来判断当前这个字符串是否能转成一个数字。非数字为 true,数字为 false
js
encodeURI(uri)
  • 对 URI 地址栏上的参数进行编码,可以避免提交给服务器出现乱码,返回值是编码后的uri对象。uri 可以理解为地址栏元素
js
decodeURI(uri)
  • 对使用encodeURI编码以后的字符串进行解码,相当于还原。

Notes: 如果参数中有特殊字符,需要使用 encodeURI 对地址进行编码,可以避免乱码的问题。再使用 decodeURI 对编码以后的字符串进行解码。

eval() 函数 ​

在 js 中 eval() 函数的基本使用以及一些特殊的使用方式如下:

  • eval 函数只能在非严格模式中进行使用,在 use strict 中是不允许使用这个方法的。
  • eval 函数接收一个参数 s,如果 s 不是字符串,则直接返回 s。否则执行 s 语句。
js
var code1='"a" + 2'; // 表达式 
var code2='{a:2}'; // 语句 
alert(eval(code1)); // 结果:'a2' 
alert(eval(code2)); // 结果:2
alert(eval('(' + code2 + ')')); // 结果:[object Object]

当 eval 中的字符串内是对象时加上括号则可以将原对象原样返回,如果定义 code2={a:2,b:3} 后直接使用 eval(code2) 时会报错,加上括号就会将 code2 原样返回。

  • eval 直接在函数内部使用则返回的是局部变量
js
function te (){
	eval('var a=1;')
}
te();
alert(a); // 这样会报错,因为a是局部变量,只能在te方法内使用
  • 有两种的处理方式可以使在函数内部使用的 eval 成为全局变量
  1. 利用 window.eval() 使其成为全局的
js
function te (){
	window.eval('var a=1')
}
te();

a 变量也是全局的

  1. 这种方式下变量b 也是全局的。
js
function te (){
	var a=eval;
	a('var b=1');
}
te();

总结:

在非严格的模式下,通过 eval 函数相当于又多了一种方法将 JSON 字符串形式转换为对象的形式。其功能和 JSON.parse() 是相似的,但是当已经为通过 JSON.parse() 转换为对象后的 JSON 不能再调用该函数继续进行转换,这样会报错,但是eval()方法不会当传入字符串是对象使继续使用上述的方式,当然会返回原对象。

js
var m = eval('('+ data + ')')

其中 m 是 JSON 对象

eval 方法是将字符串形式的类转化为类。也可以让类对象作为参数也行,只不过也是直接转化为类

  • 将字符串形式的类转化为类:在参数前后添加(和)
  • 将类转化为类:直接将类作为参数即可

JavaScript 的对象 ​

类就是对象的模板,对象就是类的实例。在 ES6之前,JS 中并没用引入类的概念。

在 ES6之前,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和它们的特征。

创建对象可以通过以下三种方式:

  1. 对象字面量。例如:var obj2 = {}
  2. new Object()。例如:var obj1 = new Object()
  3. 自定义构造函数

构造函数 ​

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 关键字一起使用。可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面

js
// ES6之前,利用构造函数创建对象
function Star(uname, age) {
  this.uname = uname
  this.age = age
  this.sing = function () {
    console.log(this.uname + "会唱歌")
  }
}
var shiyuan = new Star("石原里美", 18)
var jieye = new Star("新垣结衣", 20)
console.log(shiyuan)
shiyuan.sing()
jieye.sing()

在 JS 中,使用构造函数时要注意以下两点:

  • 构造函数用于创建某一类对象,其首字母要大写
  • 构造函数要和new一起使用才有意义

new 对象在执行时会做四件事情:

  1. 在内存中创建一个新的空对象。
  2. 让 this 指向这个新的对象。
  3. 执行构造函数里面的代码,给这个新对象添加属性和方法。
  4. 返回这个新对象(所以构造函数里面不需要 return )。

静态成员和实例成员 ​

JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的 this 上添加。通过这两种方式添加的成员,就分别称为静态成员和实例成员。

  • 静态成员:在构造函数本上添加的成员称为静态成员,只能由构造函数本身来访问
  • 实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问
js
// 构造函数中的属性和方法称为成员, 成员可以添加
function Star(uname, age) {
  this.uname = uname
  this.age = age
  this.sing = function () {
    console.log("我会唱歌")
  }
}
var shiyuan = new Star("石原里美", 18)
// 1.实例成员就是构造函数内部通过this添加的成员 uname age sing 就是实例成员
// 实例成员只能通过实例化的对象来访问
console.log(shiyuan.uname)
shiyuan.sing()
// console.log(Star.uname); // 不可以通过构造函数来访问实例成员
// 2. 静态成员在构造函数本身直接添加的成员,就是静态成员。比较这个sex属性
Star.sex = "女"
console.log(Star.sex) // 静态成员只能通过构造函数来访问
console.log(ldh.sex) // 不能通过对象来访问

原型 ​

构造函数原型 prototype ​

JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。注意这个 prototype 就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有,所有通过此构造函数生成的对象都共享此prototype对象。

所以可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。

js
// 1. 构造函数的问题.
function Star(uname, age) {
  this.uname = uname
  this.age = age
  // 直接定义到构造函数上的函数,会造成浪费内存空间的问题
  /* this.sing = function () {
    console.log("我会唱歌")
  } */
}
/*
  2. 一般情况下,的公共属性定义到构造函数里面, 公共的方法放到原型对象身上
      这样可以使所有实例都共享这些公共的方法,节省内存空间
*/
Star.prototype.sing = function () {
  console.log("我会唱歌")
}
var shiyuan = new Star("石原里美", 18)
var jieye = new Star("新垣结衣", 20)
console.log(shiyuan.sing === jieye.sing)
console.dir(Star); // 通过输入到控制台可以看到构造函数中的prototype属性对象
shiyuan.sing()
jieye.sing()

对象原型 proto ​

对象都会有一个属性 __proto__ 指向构造函数的 prototype 原型对象

  • __proto__对象原型和原型对象 prototype 是等价的
  • __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
js
function Star(uname, age) {
  this.uname = uname
  this.age = age
}
Star.prototype.sing = function () {
  console.log("我会唱歌")
}
var shiyuan = new Star("石原里美", 18)
shiyuan.sing()
console.log(shiyuan) // 对象身上系统自己添加一个 __proto__ 指向构造函数的原型对象 prototype
console.log(shiyuan.__proto__ === Star.prototype)
/*
  方法的查找规则: 首先先看实例对象身上是否有 sing 方法,如果有就执行这个对象上的sing
  如果么有sing 这个方法,因为有__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法
 */

构造函数 constructor ​

对象原型(__proto__)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 称为构造函数,因为它指回构造函数本身。

constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数

一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。

js
function Star(uname, age) {
  this.uname = uname
  this.age = age
}
// 很多情况下,需要手动的利用constructor 这个属性指回 原来的构造函数
/* Star.prototype.sing = function() {
    console.log('我会唱歌');
};
Star.prototype.movie = function() {
    console.log('我会演电影');
} */
Star.prototype = {
  // 如果修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
  constructor: Star,
  sing: function () {
    console.log("我会唱歌")
  },
  movie: function () {
    console.log("我会演电影")
  },
}
var shiyuan = new Star("石原里美", 18)
console.log(Star.prototype)
console.log(shiyuan.__proto__)
console.log(Star.prototype.constructor)
console.log(shiyuan.__proto__.constructor)

构造函数、实例、原型对象三者之间的关系 ​

原型链 ​

js
function Star(uname, age) {
  this.uname = uname
  this.age = age
}
Star.prototype.sing = function () {
  console.log("我会唱歌")
}
var shiyuan = new Star("石原里美", 18)
// 1. 只要是对象就有__proto__ 原型, 指向原型对象
console.log(Star.prototype)
console.log(Star.prototype.__proto__ === Object.prototype)
// 2.我们Star原型对象里面的__proto__原型指向的是 Object.prototype
console.log(Object.prototype.__proto__)
// 3. 我们Object.prototype原型对象里面的__proto__原型  指向为 null

JavaScript 的成员查找机制(规则) ​

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
  2. 如果没有就查找它的原型(也就是 __proto__ 指向的 prototype 原型对象)。
  3. 如果还没有就查找原型对象的原型(Object的原型对象)。
  4. 依此类推一直找到 Object 为止(null)。
  5. __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
js
function Star(uname, age) {
  this.uname = uname
  this.age = age
}
Star.prototype.sing = function () {
  console.log("我会唱歌")
}
// Object.prototype.sex = '男';
Star.prototype.sex = "男"
var shiyuan = new Star("石原里美", 18)
shiyuan.sex = "女"
console.log(shiyuan.sex)
console.log(Object.prototype)
console.log(shiyuan)
console.log(Star.prototype)
console.log(shiyuan.toString())

原型对象this指向 ​

  • 构造函数中的 this 指向实例对象
  • 原型对象里面放的是方法,这个方法里面的 this 指向的是这个方法的调用者,也就是这个实例对象
js
function Star(uname, age) {
  this.uname = uname
  this.age = age
}
var that
Star.prototype.sing = function () {
  console.log("我会唱歌")
  that = this
}
var shiyuan = new Star("石原里美", 18)
// 1. 在构造函数中,里面this指向的是对象实例
shiyuan.sing()
// 2.原型对象函数里面的 this 指向的是实例对象
console.log(that === shiyuan)

扩展 JS 内置对象 ​

可以通过原型对象,对JS原来的内置对象进行扩展自定义的方法。

js
// 原型对象的应用 扩展内置对象方法
Array.prototype.sum = function () {
  var sum = 0
  for (var i = 0; i < this.length; i++) {
    sum += this[i]
  }
  return sum
}
// 使用此方式扩展内置对象,会覆盖原来其他方法。
/* Array.prototype = {
  sum: function () {
    var sum = 0
    for (var i = 0; i < this.length; i++) {
      sum += this[i]
    }
    return sum
  },
} */
var arr = [1, 2, 3]
console.log(arr.sum())
console.log(Array.prototype)
var arr1 = new Array(11, 22, 33)
console.log(arr1.sum())

注意:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式。

对象常用方法 ​

Object.assign() 对象属性复制,浅拷贝 ​

  • 使用Object.assign方法可以浅克隆一个对象:
js
let cloneObj = Object.assign({}, targetObj)
  • 简单的深克隆可以使用JSON.parse()和JSON.stringify(),这两个api是解析json数据的,所以只能解析除symbol外的原始类型及数组和对象
js
let cloneObj = JSON.parse(JSON.stringify(targetObj))

Object.keys() 获取对象全部属性 ​

Object.keys() 用于获取对象自身所有的属性名,返回对象所有属性名的数组

js
Object.keys(obj);

示例:

js
// 用于获取对象自身所有的属性
var obj = {
  id: 1,
  pname: "小米",
  price: 1999,
  num: 2000,
}
var arr = Object.keys(obj)
console.log(arr)
arr.forEach(function (value) {
  console.log(value)
})

Object.values() 获取对象全部(属性)值 ​

Object.values(对象)方法作用是,获取对象内的所有值,返回值的数组。

js
const user = {
  name: "Alex",
  age: 23,
  isOnline: false
}

console.log(Object.values(user));
// output: ["Alex", 23, false]

Object.entries() 获取对象的键值 ​

Object.entries(对象)方法的作用是同时获取对象的键和值,返回一个键和值的多维数组。

js
const user = {
  name: "Alex",
  age: 23,
  isOnline: false
}

console.log(Object.entries(user));
// output: [["name", "Alex"], ["age", 23], ["isOnline", false]]

Object.defineProperty() 定义属性 ​

Object.defineProperty() 定义对象中新属性或修改原有的属性。语法如下:

js
Object.defineProperty(obj, prop, descriptor)
  • obj:必需。目标对象
  • prop:必需。需定义或修改的属性的名字
  • descriptor:必需。目标属性所拥有的特性。以对象形式{}书写
    • value:设置属性的值,默认为undefined
    • writable:值是否可以重写。取值:true|false,默认为false
    • enumerable:目标属性是否可以被枚举。取值:true|false,默认为 false
    • configurable:目标属性是否可以被删除或是否可以再次修改特性。取值:true|false,默认为false
js
// Object.defineProperty() 定义新属性或修改原有的属性
var obj = {
  id: 1,
  pname: "小米",
  price: 1999,
}
// 1. 以前的对象添加和修改属性的方式
// obj.num = 1000;
// obj.price = 99;
// console.log(obj);
// 2. Object.defineProperty() 定义新属性或修改原有的属性
Object.defineProperty(obj, "num", {
  value: 1000,
  enumerable: true,
})
console.log(obj)
Object.defineProperty(obj, "price", {
  value: 9.9,
})
console.log(obj)
Object.defineProperty(obj, "id", {
  // 如果值为false 不允许修改这个属性值 默认值也是false
  writable: false,
})
obj.id = 2
console.log(obj)
Object.defineProperty(obj, "address", {
  value: "中国山东蓝翔技校xx单元",
  // 如果只为false 不允许修改这个属性值 默认值也是false
  writable: false,
  // enumerable 如果值为false 则不允许遍历, 默认的值是 false
  enumerable: false,
  // configurable 如果为false 则不允许删除这个属性 不允许在修改第三个参数里面的特性 默认为false
  configurable: false,
})
console.log(obj)
console.log(Object.keys(obj))
delete obj.address
console.log(obj)
delete obj.pname
console.log(obj)
Object.defineProperty(obj, "address", {
  value: "中国山东蓝翔技校xx单元",
  // 如果值为false 不允许修改这个属性值 默认值也是false
  writable: true,
  // enumerable 如果值为false 则不允许遍历, 默认的值是 false
  enumerable: true,
  // configurable 如果为false 则不允许删除这个属性 默认为false
  configurable: true,
})
console.log(obj.address)

Object.freeze() 防止对象数据修改 ​

Object.freeze(对象)方法作用是防止对象中的数据突变。即不能将Object.freeze()作为参数传递给对象,更不能添加,更新或删除对象的属性。

js
const employee = {
  name: "James",
  age: 25,
  available: true
}

// Freezing the object.
Object.freeze(employee);
// updating and adding properties.
employee.name = "Brad";
employee.newProp = "Hard Worker";
console.log(employee);
// Output: {name: "James", age: 25, available: true}

Object.seal() 防止对象增加属性 ​

Object.seal()方法有点类似于Object.freeze(),它可以防止向对象添加新属性,但是可以更改和更新现有属性。

js
const user = {
  name: "Alex",
  age: 23,
  isOnline: false
}

// 使用Object.seal()
Object.seal(user);
// 更新属性。
user.isOnline = true;
// 添加一个属性。
user.active = false;
console.log(user);
// 输出:{名称:“ Alex”,年龄:23,isOnline:true}

示例中:该对象的isOnline属性值已更新,但是无法将新的active属性添加到对象中

Object.create() 创建一个新对象 ​

Object.create()作用是,从另一个现有对象的原型创建一个新对象。

js
const user = {
  firstName: "John",
  lastName: "Doe",
  age: 25,
  fullName(){
    return `${this.firstName} ${this.lastName}`;
  }
}

// 新对象
let newObject = Object.create(user);
// 更新属性
newObject.firstName = "Mehdi";
newObject.lastName = "Aoussiad";
// 也可以在此新对象中使用user的fullName方法。
newObject.fullName();  // 输出:Mehdi Aoussiad
console.log(newObject);
// 输出:{firstName:“ Mehdi”,lastName:“ Aoussiad”}

字符串转对象的三种方式 ​

方式1:JSON.parse(str) ​

js
// 定义一个对象字符串
var str = '{"error":1,"data":"用户不存在"}';
// 使用JSON.parse()
var obj = JSON.parse(str);
// 输出该对象
console.log(obj);

方式2:eval("("+str+")") ​

js
// 定义一个对象字符串
var str = '{"error":1,"data":"用户不存在"}';
// 使用eval();
var obj = eval("(" + str + ")")
// 输出该对象
console.log(obj);

方式3:(new Function("return " + str))() ​

js
// 定义一个对象字符串
var str = '{"error":1,"data":"用户不存在"}';
// 使用Function
var obj = (new Function("return " + str))();
// 输出该对象
console.log(obj);

BOM(浏览器模型) ​

BOM 模型 ​

Browser Object Model 浏览器对象模型,用来操作浏览器中的对象。

BOM 常用对象 ​

BOM 常用对象作用
window浏览器窗体对象
location浏览器上的地址栏对象
history浏览器访问的历史记录对象

window 对象 ​

对话框的相关方法 ​

方法名作用
alert("提示信息")弹出一个信息框
string prompt("提示信息","默认值")弹出一个输入信息框,让用户输入数据
boolean confirm("提示信息")弹出一个确认对话框,有确认或取消两个按钮,点确认返回true,否则返回false

Notes: 所有 window 对象的属性和方法,都可以省略 window

示例:

xml
<script type="text/javascript">
	if(confirm("你确定是否要输入?")){
		var s = prompt("请输入你的信息:","看看什么是默认值!")
		// 输出弹框
		alert(s);
	}else{
		alert("放弃设置");
	}
</script>

计时的相关方法 ​

js
setTimeout("函数名", 间隔毫秒数)
  • 在指定的时间以后,运行一次指定的函数。返回值是当前的计时器。例如,1秒以后调用sum()函数,有以下两种写法:
    • setTimeout("sum()", 1000);
    • setTimeout(sum, 1000);
js
clearTimeout(计时器对象)
  • 清除 setTimeout() 生成的计时器
js
setInterval("函数名", 间隔毫秒数)
  • 不停止地每过指定的时间间隔以后调用一次函数。返回一个当前计时对象,可以利用这个变量清除计时器
js
clearInterval(计时器对象)
  • 清除 setInterval() 生成的计时器。clearInterval()清空对象,只是把 timer 所指定的计时器清除,timer 的值不会发生变化。timer 的值在这里是一个整数,是 number 类型。

Notes: 所有 window 对象的属性和方法,都可以省略 window

日期对象 ​

日期对象的创建 ​

通过 new 关键字可以得到当前的日期对象

js
var date = new Date();

Date 对象的常用方法 ​

js
getFullYear()
  • 从 Date 对象以四位数字返回年份
js
getMonth()
  • 从 Date 对象返回月份(0-11),从0开始,真实的月份要加1
js
getDate()
  • 从 Date 对象返回一个月中的某一天 (1 ~ 31)
js
getDay()
  • 从 Date 对象返回一周中的某一天 (0 ~ 6)。0是周日,1是周1,2是周2,依次类推
js
getHours()
  • 返回 Date 对象的小时 (0 ~ 23
js
getMinutes()
  • 返回 Date 对象的分钟 (0 ~ 59)
js
getSeconds()
  • 返回 Date 对象的秒数 (0 ~ 59)
js
getMilliseconds()
  • 返回 Date 对象的毫秒(0 ~ 999)
js
getTime()
  • 返回 1970 年 1 月 1 日至今的毫秒数
js
toLocaleString()
  • 根据本地时间格式,把 Date 对象转换为字符串

location 地址栏对象 ​

location 是什么 ​

浏览器的地址栏对象,location 对象提供了关于当前打开窗口或者特定框架的 url 信息。一个多框架的窗口对象在 location 属性显示的是父窗口的 URL,每个框架也有一个与之相伴的 location 对象

相关属性 ​

  • hash:location.hash 返回当前框架的 URL 中 # 号之后(包括#号)的部分,即 URL 的锚点
  • host:描述 URL 的主机名和端口,只有端口号是 URL 的一个明确部分时,值中才包括端口号。
  • hostname:同 host 属性,但不包括端口号
  • href:该属性提供一个指定窗口对象的整个 URL 的字符串。有如下两种操作:
    • 得到值:当前地址栏中的访问地址
    • 设置值:可以直接设置为一个值(URL),会对浏览器进行页面的跳转,从当前页面跳转新的页面
js
location.href = 'http://www.moon.com';
  • pathname:URL 的路径名部分,主机名之后的部分,不包括锚点和query(即?之后的部分)
  • port:端口号。如果 URL 中没有端口号则为空,如果输入 URL 有端口号而被服务器自动忽略(redirect到80),在 Firefox 中能得到80,而在 IE 中为空
  • protocol:协议名,且后面紧跟着冒号

常用方法 ​

ts
/** Navigates to the given URL. */
assign(url: string | URL): void;
  • 导航到指定的 URL
ts
/** Reloads the current page. */
reload(): void;
  • 重新加载当前页面,相当于点击了浏览器上的刷新按钮。
ts
/** Removes the current page from the session history and navigates to the given URL. */
replace(url: string | URL): void;
  • 导航跳转到新的页面,并将当前页面从历史记录移除。即无法使用后退方法返回到当前页面。

history 对象 ​

作用 ​

读取浏览器的缓存页面,不访问服务器,访问之前已经访问过的页面。

源码定义如下:

ts
interface History {
    readonly length: number;
    scrollRestoration: ScrollRestoration;
    readonly state: any;
    back(): void;
    forward(): void;
    go(delta?: number): void;
    pushState(data: any, unused: string, url?: string | URL | null): void;
    replaceState(data: any, unused: string, url?: string | URL | null): void;
}

常用方法 ​

ts
back(): void;
  • 返回前一个页面,相当于浏览器上的后退按钮。
ts
forward(): void;
  • 前进到曾经访问过的一个页面,相当于浏览器上的前进按钮。
ts
go(delta?: number): void;
  • 跳转到历史浏览记录中指定的第几个页面。函数的参数是整数类型,可以是负数或正数。如果是负数类似于back();如果是正数,类似于forward()。例如是-2,则表示后退2个页面

DOM(文档对象模型) ​

DOM 概念 ​

Document Object Model 文档对象模型,用于操作网页中的各种元素。对应 JS 中的一个名为 document 对象。

注:使用 document.write() 方法时需要注意,如果直接使用,相当于重新写了一个新的文档,会覆盖原来的网页的元素。

获取 DOM 结点(元素) ​

js
document.getElementById("id")
  • 文档对象通过 id 得到唯一的一个网页标签元素,如果有同名的 id,则得到第1个元素。
js
document.getElementsByName("name")
  • 通过 name 属性得到一组同名的元素,返回一个数组
js
document.getElementsByTagName("标签名")
  • 通过标签的名字得到一组标签,返回一个数组

DOM 元素的 innerHTML 与 innerText 属性 ​

innerHTML ​

通过元素对象的 innerHTML, 可以得到或设置元素的内容,主体是 HTML 内容。如页面有元素如下:

html
<h1 id="moon">xxx</h1>

若要修改其标签元素的内容:

js
document.getElementById("moon").innerHTML = "abc";

innerText ​

元素对象的 innerText,可以得到或设置元素的内容,主体是纯文本内容,标签不起作用。

innerHTML 与 value 选择 ​

一般是表单元素才有 value 属性,会放在 form 中,如:<input>、<select> 等,其中 <textarea> 是没有 value 属性。如果没有 value 属性的,则不能使用 value。

假如要修改元素的主体内容。如:<h3>xxx</h3>,则需要通过 innerHTML 属性进行修改。

示例 ​

编写代码实现:实现当点击『计算折扣价』按钮时展示图2的效果。

操作步骤描述:

  1. 为『计算折扣价』按钮添加点击事件
  2. 为各标签添加属性 id
  3. 书写方法,实现方法体
    1. 根据 id 获取标签元素的值
    2. 根据页面效果拼接结果

页面定义输入框:

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>图书价格计算器</title>
		<!--导入外部JS文件-->
		<script src="js/Day30Test02.js" type="text/javascript"></script>
	</head>
	<body>
		<!--定义表单与表格-->
		<form name="myForm">
			<table>
				<tr>
					<th colspan="2">图书价格计算器</th>
				</tr>
				<tr>
					<td>书名:</td>
					<td><input type="text" name="bookname" id="bookname"/></td>
				</tr>
				<tr>
					<td>作者:</td>
					<td><input type="text" name="author" id="author"/></td>
				</tr>
				<tr>
					<td>价格:</td>
					<td><input type="text" name="price" id="price"/></td>
				</tr>
				<tr>
					<td>折扣:</td>
					<td><input type="text" name="discount" id="discount"/></td>
				</tr>
				<tr>
					<td colspan="2"><input type="button" value="计算折扣价" onclick="count()"/></td>
				</tr>
			</table>
		</form>
	</body>
</html>

通过 js 获取输入框里的值,输出相关信息

js
function count(){
	//使用文档对象的函数,根据ID获取输入元素的值
	var bookname = document.getElementById("bookname").value;
	var author = document.getElementById("author").value;
	var price = document.getElementById("price").value;
	var discount = document.getElementById("discount").value;
	
	var count = price * discount;
	alert("这本书名为《" + bookname + "》," + author + "编著的书,原价是:" + price + ",折扣为:" + discount + "折,折扣后价格为:" + count);
}

DOM 的创建和修改 ​

元素操作常用方法 ​

js
document.createElement("标签名")
  • 创建一个新的标签元素,如:<input>
js
// element 是元素对象
element.setAttribute("属性名", "属性值")
  • 给标签增加或修改属性和属性值。如果原来没有此属性名,则新增;如果原来有此属性名,则修改属性值。
js
document.createTextNode("文本节点内容")
  • 创建一个文本节点,参数是文本内容。如:<h1>文本</h1>
js
// parent 是父元素对象 child 是子元素对象
parent.appendChild(child)
  • 将一个元素追加成当前元素的最后一个子元素
js
// parent 是父元素对象 oldElement、newElement 都是元素对象
parent.insertBefore(newElement,oldElement)
  • 在父元素下面,将一个元素(newElement)插入到指定元素(oldElement)的之前
js
// parent 是父元素对象 child 是子元素对象
parent.removeChild(child)
  • 删除当前元素的一个子元素
js
// element 是元素对象
element.remove()
  • 删除元素本身

通过关系找节点 ​

通过元素对象的相关属性,可以获取指定的节点元素。若返回的是数组,可以通过遍历的方式来访问相关的节点。以下变量 element 为某个元素对象:

js
element.childNodes
  • 得到当前元素所有的子元素,返回数组
js
element.firstChild
  • 得到当前元素的第1个子元素
js
element.lastChild
  • 得到当前元素最后1个子元素
js
element.parentNode
  • 得到当前元素的父元素
js
element.nextSibling
  • 得到当前元素的下一个兄弟元素
js
element.previousSibling
  • 得到当前元素的上一个兄弟元素

DOM 编程综合案例 ​

案例1:计数器对象使用案例 ​

案例需求:实现5秒后跳转到主页的操作。

编写页面元素:

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>自动跳转</title>
		<!--导入外部js文件-->
		<script src="js/Day30Test03.js" type="text/javascript"></script>
	</head>
	<body>
		<span style="font-size: 28px;">操作成功!!</span><br />
		<span style="font-size: 28px;"><span id="num">5</span>秒后回到主页</span>&nbsp;&nbsp;
		<a href="http://ntlias-stu.boxuegu.com">返回</a>
	</body>
</html>

编写 js 实现功能(可以写在页面上的<script>标签,或者定义在外部js文件中)

js
//变量
var n = 5;

// 2. 编写定时器定时执行的函数
function refresh(){
	// 通过id获取span标签的对象
	document.getElementById("num").innerHTML = --n;
	
	// 判断将n=1时跳转
	if(n == 1){
		location.href = "http://www.moon.com"
	}
}

// 1. 设置定时器
setInterval("refresh()", 1000);

案例2需求:显示时钟,暂停、开始和根据时间提示相关信息

编写页面元素:

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>显示当前时间</title>
		<!--使用外部js文件-->
		<script src="../js/Day30Exercise01_02.js" type="text/javascript"></script>
	</head>
	<body>
		<table>
			<caption><h1>当前时间</h1></caption>
			<tr>
				<td><h2 id="h2"></h2></td>
			</tr>
			<tr>
				<td><hr/></td>
			</tr>
			<tr>
				<td height="30px"><span id="h3" style="font-weight: bold; color: red;"></span></td>
			</tr>
			<tr>
				<td><input type="button" id="b1" value="点击我say hi" onclick="clickMe()"/>&nbsp;&nbsp;
					<input type="button" id="b2" value="开始" onclick="begin()"/>&nbsp;&nbsp;
					<input type="button" id="b3" value="暂停" onclick="pause()"/>
				</td>
			</tr>
		</table>
	</body>
</html>

编写 js 实现功能

js
var timer = setInterval("time()", 50);

//定义函数
function time(){
	//获取当前时间对象
	var date = new Date();
	document.getElementById("h2").innerHTML = date.toLocaleString();
}

function clickMe(){
	setInterval("checkTime()", 10);
}

function checkTime(){
	var d = new Date();
	var time = d.getHours();
	// 对当前时间进行判断
	switch (true){
		case time>6 && time<12:
			document.getElementById("h3").innerHTML = "Good morning";
			break;
		case time>=12 && time<=18:
			document.getElementById("h3").innerHTML = "Good afternoon";
			break;
		default:
			document.getElementById("h3").innerHTML = "Good night";
			break;
	}
}

// 开始
function begin(){
	// 每次点击开始就将之前的计时器清空
	window.clearInterval(timer);
	// 开启新的计时器
	timer = setInterval("time()", 50);
}

// 暂停
function pause(){
	window.clearInterval(timer);
}

案例2:选择商品并计算总金额 ​

案例需求:页面上有5件商品,前面都有复选框,名字叫item,value是商品的价格。下面有一个"全选/全不选"的复选框,id是"all",点它实现全选或全不选的功能,还有个反选的按钮,点它实现反选的功能。下面有一个按钮,点它则计算选中商品的总金额。

注:在 DOM 编程中,如果要给一个复选框选中,是将checked属性设置 true,不选中设置为 false,而不是checked="checked"

页面代码:

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>商品清单</title>
		<!-- 引用外部js文件 -->
		<script src="js/test.js" type="text/javascript"></script>
	</head>
	<body>
		<h3>商品价格列表</h3>
		<input type="checkbox" name="item" value="1500" /> 山地自行车1500<br />
		<input type="checkbox" name="item" value="200" /> 时尚女装200<br />
		<input type="checkbox" name="item" value="3000" /> 笔记本电脑3000元<br />
		<input type="checkbox" name="item" value="800" /> 情侣手表800<br />
		<input type="checkbox" name="item" value="2000" /> 桑塔纳2000<br />
		<hr/>
		<input type="checkbox" id="all" onclick="selectAll()" />全选/全不选 &nbsp;
		<input type="button" id="reverse" onclick="reverseSelect()"  value=" 反 选 "/>&nbsp;
		<input type="button" value=" 结 账 " onclick="total()" />&nbsp;<span id="result"></span>
	</body>
</html>

js 代码:

js
// 编写选择全部/不选择全部的函数
function selectAll(){
	// 当点击全选/全不选按键后,根据该按键自身是否勾选还判断是否选择
	// 先获取全部的复选框项的元素对象
	var items = document.getElementsByName("item");
	// 遍历数组,如果按键自己勾选则选全部,不勾则不选择
	var all = document.getElementById("all");
	for (var i = 0; i < items.length; i++) {
		items[i].checked = all.checked;
	}
}

// 编写反选函数
function reverseSelect(){
	// 获取所有复选框对象
	var items = document.getElementsByName("item");
	// 遍历数组,将所有复选框对象的checked属性取反
	for (var i = 0; i < items.length; i++) {
		items[i].checked = !items[i].checked;
	}
}

// 结账按钮函数
function total(){
	// 获取所有复选框对象
	var items = document.getElementsByName("item");
	// 遍历数组,将所有复选框,将所有选中的元素的值相加
	var sum = 0;
	for (var i = 0; i < items.length; i++) {
		if(items[i].checked){
			sum += parseFloat(items[i].value);
		}
	}
	// 将统计后的结果赋值给result元素
	document.getElementById("result").innerHTML = sum;
}

案例3:学生信息管理 ​

案例要求实现以下功能:

  1. 使用 CSS 样式:当鼠标移入时,该行的背景颜色为黄色,当鼠标移出时,该行的背景颜色还原;
  2. 当添加按钮“添加一行数据”时,文本框中的数据添加到表格中且文本框置空;
  3. 当点击表格中的“删除”时,该行数据被删除。

注意事项:

  1. 如果 HTML 里有表格的话,要注意 <table> 标签里默认会有一个<tbody>的标签,在添加元素的时候需要注意。
  2. <a> 标签同时定义了 href 与 onclick 属性,则点击时会优先执行连接的操作,需要给 href 属性设置成javascript:void(0),表示不让 href 属性起作用,而是执行 onclick 属性中的代码

js 代码:

js
JS文件
// 增加行的函数
function addRow(){
	// 获取两个文本框输入的值
	var num = document.getElementById("no").value;
	var name = document.getElementById("name").value;
	// 取出文件框的值后清空文本框
	document.getElementById("no").value = "";
	document.getElementById("name").value = "";
	// 将最后一行的代码包装成字符串,语句的双引号
	var delString = "<a href='javascript:void(0)' onclick='deleteRow(this)'>删除</a>";
	
	// 创建3个td标签对象
	var td1 = document.createElement("td");
	var td2 = document.createElement("td");
	var td3 = document.createElement("td");
	
	// 将文本框的内容放到td标签对象中,分别使用两种不同方法
	// 方式1,先将文本框的内容转成文本节点,再放到td标签中
	var txt = document.createTextNode(num);
	td1.appendChild(txt);
	// 方式2:将文本框的内容直接设置td标签对象的innerHTML属性
	td2.innerHTML = name;
	td3.innerHTML = delString;
	
	// 创建新的tr标签对象,将3个td标签增加到tr中
	var tr = document.createElement("tr");
	tr.appendChild(td1);
	tr.appendChild(td2);
	tr.appendChild(td3);
	
	// 获取tbody标签对象,将新的tr标签增加到tbody对象中
	var tbody = document.getElementById("data");
	tbody.appendChild(tr);
}

// 删除标签函数,函数有参数,是该链接对象
function deleteRow(obj){
	// 提示是否要删除
	if(confirm("确定是否要删除?")){
		// 参数是该链接对象,先得到上级td标签对象,再获取上级tr标签对象
		obj.parentNode.parentNode.remove();
	}
}

CSS样式文件代码:

css
/*使用CSS样式:当鼠标移入时,该行的背景颜色为黄色,当鼠标移出时,该行的背景颜色还原*/
table{
	width: 500px;
	border-collapse: collapse;
}
tr{
	text-align: center;
}
/*使用伪类样式*/
tr:hover{
	background-color: lightcyan;
}

HTML 文件代码:

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>商品清单</title>
		<!-- 引用外部js文件 -->
		<script src="js/test.js" type="text/javascript"></script>
		<!--使用外部样式-->
		<link rel="stylesheet" href="css/test.css" type="text/css" />
	</head>
	<body>
		<div>
			<table border="1" cellspacing="0" cellpadding="3">
				<tbody id="data">
					<tr>
						<th>学号</th>
						<th>姓名</th>
						<th>操作</th>
					</tr>
					<tr>
						<td>00001</td>
						<td>敌法师</td>
						<td>
              <!-- javascript:void(0)表示不让href起作用,而是执行onclick中的代码-->
							<a href="javascript:void(0)" onclick="deleteRow(this)">删除</a>
						</td>
					</tr>
					<tr>
						<td>00002</td>
						<td>剑圣主宰</td>
						<td>
							<a href="javascript:void(0)" onclick="deleteRow(this)">删除</a>
						</td>
					</tr>
				</tbody>
			</table>
			<br />
			学号:<input type="text" id="no" value="" />&nbsp;&nbsp;
			姓名:<input type="text" id="name" value="" />&nbsp;&nbsp;
			<input type="button" id="add" value="新增" onclick="addRow()" />
		</div>
	</body>
</html>

样式操作 ​

JS 修改 CSS 样式 ​

通过获取元素对象,再根据对应样式属性名称修改即可。

在 JavaScript 中,样式的命名规则是:CSS的名字,多个单词,首字母小写,第二个单词去掉减号,首字母大写。

方式一:单个元素修改 ​

每条 JS 代码修改一个样式。一条语句修改一个样式。

js
元素名.style.样式名 = 样式值;

方式二:批量元素修改 ​

首先定义一个类样式,给类起名。一次修改一批样式。

js
元素名.className = 类样式名;

注:在使用 CSS 类选择器时,是在需要修改的元素标签里添加属性 class="xxx"。但在 JS 中修改 CSS 时,如果用类批量修改样式,是获取到修改的元素对象后,给 className 赋值新定义的类选择器名字,而不是 class(可能是因为 class 属性的名称与关键字冲突)。

在 JS 中操作 CSS 属性命名上的区别 ​

使用 JavaScript 修改元素的 css 样式,与直接定义样式的属性名称有些不一样。例如:

CSS中写法JS中的写法说明
color元素名.style.color颜色
font-size元素名.style.fontSize第二个单词首字母大小,去掉中间减号。

事件的处理 ​

事件的概述 ​

事件机制组成描述具体对象
事件源发出事件的对象如:按钮,链接,图片
事件发出什么类型的事情如:点击,双击,鼠标移动事件
监听者对事件进行处理的函数事件处理函数 funnction changeCode()

事件命名的规则 ​

  1. 所有事件以on开头
  2. 所有的事件都是以标签属性的方式存在的,名字的字母都是小写

设置事件 ​

JavaScript 有两种方式设置事件

  • 方式1:命名函数。直接在标签中使用,可以多次使用。
html
<标签 onclick="函数名()" />
  • 方式2:匿名函数。使用匿名函数的方式,只能在一个地方使用。
js
元素.onclick = function(){
    // do something...
}

设置事件的两种方式示例:

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>JS事件</title>
	</head>
	<body>
		<!--设置事件的两种方式-->
		<!--方式一-->
		<h1>设置事件的两种方式</h1>
		<!--设置事件的方式一:直接写在标签中-->
		<input type="button" id="b1" value="我是按键1" onclick="clickMe()" />
		<br /><br />
		<!--设置事件的方式二:以匿名函数的方式存在js代码中,标签中没有on开头的属性-->
		<input type="button" id="b2" value="我是按键2" />&nbsp;&nbsp;
		<input type="button" id="b3" value="我是按键3" />
		
		<script type="text/javascript">
			function clickMe(){
				alert("我是一个按键点击出来的");
			}
			
			document.getElementById("b2").onclick = function (){
				alert("我是匿名函数方式点出来的");
			}
			
			// 使用命名函数,函数后面没有括号
			document.getElementById("b3").onclick = clickMe;
		</script>
	</body>
</html>

常用的事件 ​

事件名作用
onclick单击事件
ondbclick双击事件
onsubmit表单提交事件
onload加载完毕
onfocus得到焦点
onblur失去焦点
onchange文本框和下拉列表内容改变事件
onmousemove鼠标移动
onmouseout鼠标移出
onmouseup鼠标松开
onmousedown鼠标按下
onkeyup键盘按键松开
onkeydown键盘按键按下

加载完成事件 ​

onload 事件:用于一个元素加载完毕以后执行的代码。一般用在页面或图片加载完毕以后激活的事件

如果使用匿名函数的方式设置事件,要注意要在设置事件之前,要设置在页面加载后再触发匿名函数事件,即 window.onload = function(){},因为匿名函数一般需要先得到元素,即document.getElementById("xxx"),如果不设置 onload 事件,那么不一定能得到元素,如果加载 script 放在最后,那就可以得到元素

面试题:在页面加载后就能触发函数执行的三种写法:

  1. window.onload = function()
  2. <body onload = "函数名()"></body>
  3. onload = function() 与第一种相同,只是省略了 window 对象

onload 事件基础使用示例:

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>事件和对指定的元素样式进行操作</title>
		<style type="text/css">
			/*标签选择器*/
			a{
				text-decoration: none;
				font-size: 50px;
			}
		</style>
		<!--使用外部js文件-->
		<script src="js/Day31Test01_01.js" type="text/javascript"></script>
	</head>
	<body>
		<a href="http://www.baidu.com" title="百度" target="_blank" id="a1">百度</a>
		<div id="d1" style="color: blue; display: none;" ><a href="#">百度一下你就知道</a></div>
	</body>
</html>

js 代码:

js
// 在网页加载后再加载事件
window.onload = function(){
	// 当鼠标移动到链接时显示div内容
	document.getElementById("a1").onmouseover = function(){
		document.getElementById("d1").style.display = "";
	}

	// 当鼠标移出链接后隐藏div内容
	document.getElementById("a1").onmouseout = function(){
		document.getElementById("d1").style.display = "none";
	}
}

上面的例子,如果将<script src="js/Day31Test01_01.js" type="text/javascript"></script>放到<body>标签的最后,就算不设置 onload 事件,也可以得到效果。

直接在标签上定义事件:

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>事件和对指定的元素样式进行操作</title>
		<style type="text/css">
			/*标签选择器*/
			a{
				text-decoration: none;
				font-size: 50px;
			}
		</style>
		<!--使用外部js文件-->
		<script src="js/Day31Test01_01_方式2.js" type="text/javascript"></script>
	</head>
	<body>
		<a href="http://www.baidu.com" title="百度" target="_blank" onmouseover="mouseover()" onmouseout="mouseout()">百度</a>
		<div id="d1" style="color: blue; display: none;" ><a href="#">百度一下你就知道</a></div>
	</body>
</html>

鼠标点击事件 ​

  • onclick 点击事件
  • ondblclick 双击事件

单击与双击代码示例:

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>JS事件</title>
	</head>
	<body>
		<table>
			<tr>
				<td>Field1:</td>
				<td><input type="text" id="f1" /></td>
			</tr>
			<tr>
				<td>Field2:</td>
				<td><input type="text" id="f2" /></td>
			</tr>
			<tr>
				<td colspan="2"><input type="button" id="b1" value="单击复制/双击改变颜色"/></td>
			</tr>
		</table>
		
		<script type="text/javascript">
			// 单击事件
			document.getElementById("b1").onclick = function (){
				document.getElementById("f2").value = document.getElementById("f1").value;
			}
			
			// 双击事件
			document.getElementById("b1").ondblclick = function (){
				document.getElementById("f1").style.color = "red";
			}
		</script>
	</body>
</html>

鼠标按下与松开 ​

  • onmousedown 鼠标按下事件
  • onmouseup 鼠标松开事件,必须在按下的元素地方松开才有效果。

鼠标移动事件 ​

  • onmousemove 鼠标移动到某个元素上面,激活事件
  • onmouseout 鼠标从元素上移走,激活事件。

Notes: 如果是图片的鼠标移动事件,要设置图片的大小

鼠标按下与松开、移动事件示例:

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>JS事件</title>
	</head>
	<body>
		<script type="text/javascript">
			window.onload = function(){
				// 鼠标按下事件
				document.getElementById("t3").onmousedown = function (){
					document.getElementById("t3").innerHTML = "鼠标按下了";
				}
			
				// 鼠标松开事件
				document.getElementById("t3").onmouseup = function (){
					this.innerHTML = "鼠标松开了";
				}
			
				// 鼠标移动事件
				document.getElementById("pic").onmousemove = function (){
					this.src = "img/用户登录/bg1.jpg";
				}
				
				// 鼠标移出事件
				document.getElementById("pic").onmouseout = function (){
					document.getElementById("pic").src = "";
				}
			}
		</script>
		
		<h3 id="t3">试试又不会怀孕</h3>
		<img src="" id="pic" width="380" height="300"/>
	</body>
</html>

焦点相关事件 ​

如果一个文本框(控件)得到光标,处于一种可以操作或输入的状态,称为获得焦点。

  • onblur 失去焦点
  • onfocus 得到焦点

改变事件 ​

onchange 一般用于文本框内容改变,或下拉列表选项发生变化

Notes: 文本框必须是失去焦点以后才激活改变事件

键盘相关事件 ​

  • onkeyup 松开键盘某个按键激活的事件
  • onkeydown 按下键盘某个按键激活的事件

键盘码:键盘上每个按键都有一个对应的键盘码,大小字母的键盘码是一样的。得到键盘码需要传入一个参数:事件对象。keyCode 就是键盘码。

表单提交事件 ​

onsubmit 每个表单在提交之前都会有一个表单的提交的事件。一般可以用于表单验证。

如果 onsubmit 中返回 false,可以阻止表单的提交。

html
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>JS事件</title>
		<script type="text/javascript">
			function checkUser(){
				if(document.getElementById("user").value.trim() == ""){
					alert("用户名不能为空");
					return false;
				}
				return true;
			}
		</script>
	</head>
	<body>
		<form action="http://ntlias-stu.boxuegu.com" method="post" onsubmit="return checkUser()">
			<span id="t1">用户名:</span><input type="text" id="user"/><br/>
			<input type="submit" value="登录"/>
		</form>
	</body>
</html>

继承(ES6以前的实现方式) ​

ES6之前并没有提供 extends 继承。但可以通过构造函数+原型对象模拟实现继承,被称为组合继承

call() 函数 ​

通过call()去调用某个函数, 并且修改函数运行时的 this 指向

js
fun.call(thisArg, arg1, arg2, ...)
  • thisArg:当前调用函数 this 的指向对象
  • arg1,arg2:传递的其他参数
js
// call 方法
function fn(x, y) {
  console.log("我想喝手磨咖啡")
  console.log(this)
  console.log(x + y)
}
var o = {
  name: "斩月",
}
// 直接调用函数
// fn();
// 1. call() 可以调用函数
// fn.call();
// 2. call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
fn.call(o, 1, 2)
```10.2. 
### 9.6. 借用构造函数继承父类型属性

核心原理:通过 `call()` 把父类型的this指向子类型的this,这样就可以实现子类型继承父类型的属性。

```js
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
  // this 指向父构造函数的对象实例
  this.uname = uname
  this.age = age
}
// 2 .子构造函数
function Son(uname, age, score) {
  // this 指向子构造函数的对象实例
  Father.call(this, uname, age)
  this.score = score
}
var son = new Son("敌法师", 18, 100)
console.log(son)

借用原型对象继承父类型方法 ​

一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。实现的核心原理:

  1. 将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类()
  2. 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
  3. 将子类的 constructor 从新指向子类的构造函数
js
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
  // this 指向父构造函数的对象实例
  this.uname = uname
  this.age = age
}
Father.prototype.money = function () {
  console.log(100000)
}
// 2 .子构造函数
function Son(uname, age, score) {
  // this 指向子构造函数的对象实例
  Father.call(this, uname, age)
  this.score = score
}
// 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
// Son.prototype = Father.prototype
Son.prototype = new Father()
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son
// 这个是子构造函数专门的方法
Son.prototype.exam = function () {
  console.log("孩子要考试")
}
var son = new Son("剑圣主宰", 18, 100)
console.log(son)
console.log(Father.prototype)
console.log(Son.prototype.constructor)

字符串(String) ​

trim() 方法 ​

trim()方法会从一个字符串的两端删除空白字符。

js
str.trim();

注:trim()方法并不影响原字符串本身,它返回的是一个新的字符串。

replace 方法 ​

通过正则表达式,现实去掉中间所有空格

js
const str = 字符串.replace(/\s/g, "");

数值函数 ​

案例应用 ​

生成一个4位的随机码,包含字母 ​

使用到的 js 中 Math 函数:数学对象,提供对数据的数学计算。

js
Math.random(); // 返回0和1间(包括0,不包括1)的一个随机数。
Math.ceil(n); // 返回大于等于n的最小整数。
Math.round(n); // 返回n四舍五入后整数的值。
Math.floor(n); // 返回小于等于n的最大整数。

示例代码:

js
getVerifyCode: function () {
    var chars = "1234567890abcdefghijklmnopqrstuvwsyzABCDEFGHIJKLMNOPQRSTUVWSYZ".split("");
    var randomNum = "";
    alert(chars.length);
    for (var i = 0; i < 4; i++) {
        randomNum += chars[Math.floor(Math.random() * chars.length)];
    }
    alert("模拟获取验证码:" + randomNum);
}

生成[min,max]的随机数 ​

js
// max - 期望的最大值
// min - 期望的最小值
parseInt(Math.random()*(max-min+1)+min,10);
Math.floor(Math.random()*(max-min+1)+min);

AJAX ​

AJAX 概述 ​

AJAX 全称 Asynchronous Javascript And XML (异步 JavaScript 和 XML),可以使网页实现异步更新,就是不重新加载整个网页的情况下,对网页的某部分进行更新(局部刷新)。传统的网页(不使用AJAX)如果需要更新内容,必须重载整个网页页面。

AJAX = 异步 JavaScript 和 XML,是一种新的思想,整合之前的多种技术,用于创建快速交互式网页应用的网页开发技术。

同步与异步的区别 ​

  • 同步:客户端发送请求到服务端,当服务端返回响应之前,客户端都处于等待卡死状态。
  • 异步:客户端发送请求到服务端,无论服务端是否返回响应,客户端都可以在该页面中随意做其他事情,不会被卡死,提高了用户的体验。

优缺点 ​

优点

  1. 页面无刷新,用户的体验非常好。
  2. 使用异步方式与服务器通信,具有更加迅速的响应能力。
  3. 将以前一些服务器负担的工作转嫁到客户端,利用客户端闲置的能力来处理,减轻服务器和带宽的负担。ajax 的原则是“按需取数据”,可以最大程度的减少冗余请求和响应对服务器造成的负担。
  4. 基于标准化的并被广泛支持的技术,不需要下载插件或者小程序。

缺点

  1. ajax 不支持浏览器 back 按钮。
  2. 存在安全问题,AJAX 暴露了与服务器交互的细节。
  3. 对搜索引擎的支持比较弱
  4. 破坏了程序的异常机制
  5. 调试困难

AJAX 原理分析 ​

  1. 使用 JavaScript 获得浏览器内置的 AJAX 引擎(XMLHttpRequest 异步调用对象)
    1. 通过 AJAX 引擎确定请求路径和请求参数
    2. 通知 AJAX 引擎发送请求
js
var xmlhttp = new XMLHttpRequest();
  1. AJAX 引擎会在不刷新浏览器地址栏的情况下,发送 HTTP 请求
    1. 服务器获得请求参数
    2. 服务器处理请求参数(添加、查询等操作)
    3. 服务器响应数据给浏览器
  2. AJAX 引擎获得服务器响应的数据,通过执行 JavaScript 的回调函数将数据传递给浏览器页面。
    1. 通过设置给 AJAX 引擎的回调函数获得服务器响应的数据
    2. 使用 JavaScript 在指定的位置,显示响应数据,从而局部修改页面的数据,达到局部刷新目的。

JavaScript 中 AJAX 的使用 ​

原生态 JS 操作 ajax 步骤 ​

  1. 获得 ajax 引擎。
  2. 设置回调函数。作用当前 AJAX 请求服务器过程中不同状态变化的时候都会调用此函数
  3. 确定请求路径
  4. 发送请求。JavaScript ajax 处理 GET 和 POST 请求会有细微差异

ajax 引擎连接状态变化过程 ​

ajax 引擎连接状态 readyState 值有 0~4 变化过程。存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。

  • 0: 请求未初始化
  • 1: 服务器连接已建立
  • 2: 请求已接收
  • 3: 请求处理中
  • 4: 请求已完成,且响应已就绪

这里状态值 4 只能说明接收到了服务器的响应服务器处理 ajax 请求结束,但是不能代表正确的获取了服务器的响应,需要配合 http 状态码 200 两个条件就可以说明正确的获取了服务器响应。只有这两个条件满足,xmlhttp.responseText 才可以获取到正确的响应数据。

js
xmlHttp.onreadystatechange = function(){
	if(xmlHttp.readyState == 4 && xmlHttp.status == 200){
		// 获取服务器的响应内容
		var text = xmlHttp.responseText;
	}
}

XMLHttpRequest 对象浏览器兼容 ​

大多数浏览器都支持以下方式得到 XMLHttpRequest

js
var xmlHttp = new XMLHttpRequest();

IE6.0:

js
var xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");

IE5.5 以及更早版本的IE:

js
var xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");

示例:

js
function getXMLhttp(){
	var xmlhttp=null;
	// 谷歌、火狐、IE7+最新浏览器
	if (window.XMLHttpRequest){
		xmlhttp=new XMLHttpRequest();
	}else if (window.ActiveXObject){
		//IE 老版本浏览器(IE5、6 等)
		xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
	}
}

JS 原生 AJAX 现实示例 ​

html
<script type="text/javascript">
	//异步提交请求
	function sendGet(){
		//1.获取ajax引擎对象
		var xmlHttp = new XMLHttpRequest();
		//2.设置回调函数,作用ajax请求服务器过程中不同状态变化的时候都会调用这个函数
		xmlHttp.onreadystatechange = function(){
		//获取服务器响应数据并处理显示
		//xmlHttp.readyState,ajax引擎对象请求服务器过程中不同的状态值
		//xmlHttp.readyState,0~4
		//	0: 请求未初始化 
		//	1: 服务器连接已建立 
		//	2: 请求已接收 
		//	3: 请求处理中 
		//	4: 请求已完成,且响应已就绪 
		//获取服务器响应的数据进行处理并显示
			if(xmlHttp.readyState==4){
				//200,http状态码,浏览器与服务器通信正常
				if(xmlHttp.status==200){
					//通信正常,并且接收到服务器的正常响应数据
					//获取服务器响应数据
					var content = xmlHttp.responseText;//服务器响应的文本字符串
					alert(content);
				}
			}
		};
		//3.设置请求方法与提交服务器地址
		//xmlHttp.open(method, url);
		//method,设置请求方法(get或post)
		//url,设置服务器地址
		xmlHttp.open("get", "${pageContext.request.contextPath}/hello?name=admin&pwd=123456");
		//4.ajax引擎对象发送异步请求
		xmlHttp.send();
	}
	
	//发送异步的post请求
	function sendPost(){
		//1.获取ajax引擎
		var xmlHttp = new XMLHttpRequest();
		//2.设置回调函数
		xmlHttp.onreadystatechange = function(){
			//获取服务器响应的数据进行处理并显示
			if(xmlHttp.readyState==4){
				//200,http状态码,浏览器与服务器通信正常
				if(xmlHttp.status==200){
					//通信正常,并且接收到服务器的正常响应数据
					//获取服务器响应数据
					var content = xmlHttp.responseText;//服务器响应的文本字符串
					alert(content);
				}
			}
		};
		//3.设置请求方法与路径
		xmlHttp.open("post", "${pageContext.request.contextPath}/hello");
		//4.设置请求头信息(如果像表单那样post提交数据必须要设置请求头信息,如果不提交参数数据就不用设置)
		xmlHttp.setRequestHeader("content-type","application/x-www-form-urlencoded");
		//5.发送
		xmlHttp.send("name=admin&pwd=123456");
	}
</script>
<body>
	<input type="button" value="js异步提交get请求" onclick="sendGet();"><br/><br/>
	<input type="button" value="js异步提交post请求" onclick="sendPost();"><br/><br/>
</body>

前端日志输出 ​

在前端开发中,随着项目迭代升级,日志打印逐渐风格不一,合理的日志输出是监控应用状态、调试代码和跟踪用户行为的重要手段。一个好的日志系统能够帮助开发者快速定位问题,提高开发效率。

日志等级 ​

通常,会定义不同的日志等级,以便根据消息的重要性进行分类。日志等级从低到高可以分为以下几类:

  • DEBUG: 详细的开发时信息,用于调试应用。
  • INFO: 重要事件的简要信息,如系统启动、配置等。
  • WARN: 系统能正常运行,但有潜在错误的情况。
  • ERROR: 由于严重的问题,某些功能无法正常运行。
  • FATAL: 非常严重的问题,可能导致系统崩溃。

console 对象 ​

console 是 js 中用于输出日志的对象

常用方法 ​

  1. console.log()
js
// 用于输出普通信息,最常用
console.log("%c%s", "color:red;font-size:20px", "结果是:这样的哟");
  1. console.info()
js
// 用于输出提示性信息
console.info("%s", "color:red;这是结果哟");
  1. console.error()
js
// 用于输出错误信息
console.error("错误");
  1. console.warn():用于输出警示信息
  2. console.count():统计代码被执行的次数
  3. console.assert():对输入的表达式进行断言,只有表达式为 false 时,才输出相应的信息到控制台
  4. console.group():输出一组信息的开头
  5. console.groupEnd():结束一组输出信息
  6. console.dir():直接将该 DOM 结点以 DOM 树的结构进行输出,可以详细查对象的方法发展等等
  7. console.time():计时开始
  8. console.timeEnd():计时结束
  9. console.profile() 和 console.profileEnd():一起使用来查看 CPU 使用相关信息
  10. console.timeLine() 和 console.timeLineEnd():一起使用记录一段时间轴
  11. console.trace():堆栈跟踪相关的调试

格式化符号 ​

格式化符号实现的功能
%s格式化成字符串输出
%d or %i格式化成数值输出
%f格式化成浮点数输出
%o转化成展开的DOM元素输出
%O转化成JavaScript对象输出
%c把字符串按照你提供的样式格式化后输入

日志格式 ​

日志内容应该包含足够的信息,以便于开发者理解发生了什么。一个完整的日志消息通常包括:

  • 时间戳:精确到毫秒的事件发生时间。
  • 日志等级:当前日志消息的等级。
  • 消息内容:描述事件的详细信息。
  • 错误堆栈:如果是错误,提供错误堆栈信息。

日志的格式应该统一,以便于阅读和解析。常见的日志格式如下:

js
[时间戳] [日志等级] [消息内容] [错误堆栈]

示例:

js
[2024-04-01T12:00:00.000Z] [ERROR] Failed to load user data. {stack}

日志类 ​

基础实现 ​

为了更好地控制日志输出,可以封装一个日志工具,来统一管理日志输出。以下是简单的日志工具实现:

js
class Logger {
    static log(level, message, error) {
        const timestamp = new Date().toISOString()
        const stack = error ? error.stack : ''
        const formattedMessage = `[${timestamp}] [${level}] ${message} ${stack}`

        switch (level) {
            case 'DEBUG':
                console.debug(formattedMessage)
                break
            case 'INFO':
                console.info(formattedMessage)
                break
            case 'WARN':
                console.warn(formattedMessage)
                break
            case 'ERROR':
            case 'FATAL':
                console.error(formattedMessage)
                break
            default:
                console.log(formattedMessage)
        }
    }

    static debug(message) {
        this.log('DEBUG', message)
    }

    static info(message) {
        this.log('INFO', message)
    }

    static warn(message) {
        this.log('WARN', message)
    }

    static error(message, error) {
        this.log('ERROR', message, error)
    }

    static fatal(message, error) {
        this.log('FATAL', message, error)
    }
}

// 使用示例
Logger.info('Application is starting...')
Logger.error('Failed to load user data', new Error('Network Error'))

日志等级控制 ​

在开发环境中,可能希望看到尽可能多的日志输出,以便更好地调试应用。但在生产环境中,为了避免性能损耗和过多的日志信息,可能只希望输出 WARN 和以上等级的日志。可以在 Logger 工具类中添加一个等级控制:

js
class Logger {
    static level = 'DEBUG' // 默认为DEBUG级别

    static setLevel(newLevel) {
        this.level = newLevel
    }

    static shouldLog(level) {
        const levels = ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL']
        return levels.indexOf(level) >= levels.indexOf(this.level)
    }

    static log(level, message, error) {
        if (!this.shouldLog(level)) {
            return
        }
        // ...日志输出逻辑
    }

    // ...其他方法
}

// 生产环境中设置日志等级
if (process.env.NODE_ENV === 'production') {
    Logger.setLevel('WARN')
}

// 使用示例
Logger.debug('This will not be logged in production')
Logger.warn('This will be logged in production')

日志格式化 ​

为了进一步提高日志的可读性,可以添加格式化功能,比如为不同等级的日志添加颜色,或者为错误堆栈提供更好的格式化。

js
class Logger {
    // ...其他方法

    static formatStack(stack) {
        if (!stack) return ''
        // 格式化错误堆栈的逻辑
        return stack
            .split('\n')
            .map(line => `    at ${line}`)
            .join('\n')
    }

    static log(level, message, error) {
        // ...日志输出逻辑

        // 格式化错误堆栈
        if (error) {
            formattedMessage += `\n${this.formatStack(error.stack)}`
        }

        // ...输出逻辑
    }
}

日志收集 ​

在生产环境中,可能需要将日志发送到后端服务器进行收集和分析。这可以通过 AJAX 请求或专门的日志服务来实现。例如,可以修改 Logger 工具,添加一个方法来发送日志:

js
class Logger {
    // ...其他方法

    static sendLog(message) {
        // 假设我们有一个日志收集的API
        const logEndpoint = '/api/logs'
        fetch(logEndpoint, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ message }),
        }).catch(error => {
            console.error('Failed to send log', error)
        })
    }
}

// 根据环境变量判断是否发送日志到后端
if (process.env.NODE_ENV === 'production') {
    this.sendLog(formattedMessage)
}

其他 ​

JS的调试 ​

因为 JS 是运行在浏览器端,几乎所有的主流浏览器都提供了 debug 调试的功能,在开发模式中,设置断点进行调试。不同的浏览器对应的操作与快捷键不一样,下面的调试的快捷键以 Chrome 为例:

  • F12 进入开发模式
  • 单步跳过 F10
  • 单步退出 Shift+F11
  • 运行到最后 F8

如果有语法错误,进行浏览器开发模式中会出现提示

EventLoop ​

JavaScript 是单线程的语言 ​

JavaScript 是一门单线程执行的编程语言。也就是说,同一时间只能做一件事情。

单线程执行任务队列的问题:如果前一个任务非常耗时,则后续的任务就不得不一直等待,从而导致程序假死的问题。

同步任务和异步任务 ​

JavaScript 把待执行的任务分为了两类:

  1. 同步任务(synchronous)
    • 又称为非耗时任务,指的是在主线程上排队执行的那些任务
    • 只有前一个任务执行完毕,才能执行后一个任务
  2. 异步任务(asynchronous)
    • 又称为耗时任务,异步任务由 JavaScript 委托给宿主环境进行执行
    • 当异步任务执行完成后,会通知 JavaScript 主线程执行异步任务的回调函数

EventLoop 的基本概念 ​

同步任务和异步任务的执行过程

  1. 同步任务由 JavaScript 主线程次序执行
  2. 异步任务委托给宿主环境执行
  3. 已完成的异步任务对应的回调函数,会被加入到任务队列中等待执行
  4. JavaScript 主线程的执行栈被清空后,会读取任务队列中的回调函数,次序执行
  5. JavaScript 主线程不断重复上面的第 4 步

JavaScript 主线程从“任务队列”中读取异步任务的回调函数,放到执行栈中依次执行。这个过程是循环不断的,所以整个的这种运行机制又称为 EventLoop(事件循环)。

结合 EventLoop 分析输出的顺序案例(面试题) ​

js
import { getResult } from '../utils/util.js'

console.log('A')

// 模拟请求获取数据
getResult(3).then(res => console.log('B'))

setTimeout(() => console.log('C'), 0)

console.log('D')

上面示例最终的输出结果是:ADCB

  • A 和 D 属于同步任务。会根据代码的先后顺序依次被执行
  • C 和 B 属于异步任务。它们的回调函数会被加入到任务队列中,等待主线程空闲时再执行

宏任务和微任务 ​

什么是宏任务和微任务 ​

JavaScript 把异步任务又做了进一步的划分,异步任务又分为两类,分别是:

  1. 宏任务(macrotask):异步 Ajax 请求、setTimeout、setInterval、文件操作等
  2. 微任务(microtask):Promise.then、Promise.catch、Promise.finally、process.nextTick 等

宏任务和微任务的执行顺序 ​

每一个宏任务执行完之后,都会检查是否存在待执行的微任务。如果有,则执行完所有微任务之后,再继续执行下一个宏任务。

宏任务和微任务执行顺序分析案例(面试题) ​

案例1 ​
js
setTimeout(() => console.log('1'))

new Promise(resolve => {
  console.log('2')
  resolve()
}).then(res => console.log('3'))

console.log('4')

最终结果输出是:2431

  1. 先执行所有的同步任务输出:2、4
  2. 再执行微任务输出:3
  3. 再执行下一个宏任务输出:1
案例2 ​
js
console.log('1')

setTimeout(() => {
  console.log('2')
  new Promise(resolve => {
    console.log('3')
    resolve()
  }).then(() => console.log('4'))
})

new Promise(resolve => {
  console.log('5')
  resolve()
}).then(() => console.log('6'))

setTimeout(() => {
  console.log('7')
  new Promise(resolve => {
    console.log('8')
    resolve()
  }).then(() => console.log('9'))
})

最终结果输出是:156234789

  1. 先执行所有的同步任务输出:1、5
  2. 再执行第1个宏任务中的同步任务输出:2、3
  3. 再执行第1个宏任务的微任务输出:4
  4. 再执行第2个宏任务中的同步任务输出:7、8
  5. 再执行第2个宏任务的微任务输出:9

WebSocket ​

概念 ​

WebSocket 是一种在独立的、创建在 TCP 连接上进行全双工通信的协议。Websocket 通过 HTTP/1.1 协议的 101 状态码进行握手。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

WebSocket 通信协议于 2011 年被 IETF 定为标准 RFC 6455,并由 RFC7936 补充规范。WebSocket API 也被 W3C 定为标准。

API 方法(待整理) ​

TODO: 待参考 MDN 网站整理

基础示例 ​

WebSockets 它可以在用户的浏览器和服务器之间打开交互式通信会话。使用此API,可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应。 WebSocket 对象提供了用于创建和管理 WebSocket 连接,以及可以通过该连接发送和接收数据的API。

js
// 创建WebSocket连接.
const socket = new WebSocket('ws://localhost:8080');
 
// 连接成功触发
socket.addEventListener('open', function (event) {
    socket.send('Hello Server!');
});
 
// 监听消息
socket.addEventListener('message', function (event) {
    console.log('Message from server ', event.data);
});

API 接口案例 ​

案例需求 ​

基于 MySQL 数据库 + Express 对外提供用户列表的 API 接口服务。用到的技术点如下:

  • 第三方包 express 和 mysql2
  • ES6 模块化
  • Promise
  • async/await

搭建项目的基本结构 ​

  1. 启用 ES6 模块化支持,在 package.json 中声明 "type": "module"
  2. 安装第三方依赖包。npm install express@4.17.1 mysql2@2.2.5 -S

创建基本的服务器 ​

在项目的根目录下创建 app.js 入口文件,创建基础的服务器

js
import express from 'express'

const app = express()

app.listen(80, () => {
  console.log('server running at http://127.0.0.1')
})

运行以下命令测试是否能开启服务

bash
nodemon app.js

如 nodemon 没有安装,先运行全局安装命令

bash
npm install -g nodemon

卸载则运行

bash
npm uninstall -g nodemon

创建 db 数据库操作模块 ​

创建db/index.js,配置数据库的连接信息

js
import mysql from 'mysql2'

const pool = mysql.createPool({
  host: '127.0.0.1',
  port: 3306,
  database: 'tempdb',
  user: 'root',
  password: '123456',
})

export default pool.promise()

创建请求的处理方法 ​

js
import db from '../db/index.js'

// 使用 ES6 的按需导出语法,将 getAllUser 方法导出
export async function getAllUser(req, res) {
  try {
    // db.query() 函数的返回值是 Promise 实例对象。所以可以使用 async/await 进行异步处理简化
    const [rows] = await db.query('select id, name, gender from user')
    res.send({
      status: 0,
      message: '获取用户列表数据成功!',
      data: rows,
    })
  } catch (err) {
    res.send({
      status: 1,
      message: '获取用户列表数据失败!',
      desc: err.message,
    })
  }
}

配置路由 ​

js
import express from 'express'
import { getAllUser } from '../controller/UserController.js'

// 创建路由对象
const router = new express.Router()

// 挂载路由规则
router.get('/user', getAllUser)

// 使用 ES6 默认导出语法,将路由对象导出
export default router

导入并挂载路由模块 ​

在 app.js 文件中导入并挂载路由模块

js
import express from 'express'
// 默认导入路由对象
import userRouter from './router/user_router.js'

const app = express()

// 挂载用户路由模块
app.use('/api', userRouter)

app.listen(80, () => {
  console.log('server running at http://127.0.0.1')
})

测试 ​

使用 postman 等请求工具,请求http://127.0.0.1/api/user,获取返回数据

ES2020 新特性 ​

可选链操作符 ​

可选链操作符(?.)允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?.操作符的功能类似于.链式操作符,不同之处在于,在引用为空(nullish) (null或者undefined) 的情况下不会引起错误,该表达式短路返回值是undefined。与函数调用一起使用时,如果给定的函数不存在,则返回undefined。

当尝试访问可能不存在的对象属性时,可选链操作符将会使表达式更短、更简明。

js
const adventurer = {
  name: "Alice",
  cat: {
    name: "Dinah",
  },
};

const dogName = adventurer.dog?.name;
console.log(dogName);
// expected output: undefined

const catName = adventurer.cat?.name;
console.log(catName);
// expected output: Dinah

console.log(adventurer.someNonExistentMethod?.());
// expected output: undefined

语法:

js
obj?.prop
obj?.[expr]
arr?.[index]
func?.(args)

参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Optional_chaining

空值合并运算符(??) -- 整理中 ​

参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing

空值合并运算符(??)是一个逻辑运算符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。语法格式:

js
leftExpr ?? rightExpr

与逻辑或运算符(||)不同,逻辑或运算符会在左侧操作数为假值时返回右侧操作数。即如果使用 || 来为某些变量设置默认值,若出现假值(如 '' 或 0)时,可能会遇到意料之外的行为。例如:

js
const foo = null ?? 'default string';
console.log(foo);
// Expected output: "default string"

const baz = 0 ?? 42;
console.log(baz);
// Expected output: 0

const bar = 0 || 42;
console.log(bar);
// Expected output: 42

?? 与 || 的选择建议 ​

空值合并运算符 (??) 的使用场景:

  • 为变量提供默认值:当一个变量可能是 null 或 undefined 时,可以使用空值合并运算符为其提供默认值。 例如:const name = inputName ?? "Unknown";
  • 处理可能缺失的属性:当访问对象属性时,如果该属性可能存在但值为 null 或 undefined,可以使用空值合并运算符提供默认值。 例如:const address = user.address ?? "Unknown";
  • 避免出现假值的情况:当我们只想处理显式定义的值,并避免处理假值(如 false、0、空字符串等)时,可以使用空值合并运算符。 例如:const value = userInputValue ?? 0;

逻辑或运算符 (||) 的使用场景:

  • 提供备选值:当我们需要从多个选项中选择一个有效的值时,可以使用逻辑或运算符。 例如:const result = value1 || value2 || value3;
  • 判断条件:当我们需要检查多个条件中的任一条件是否为真时,可以使用逻辑或运算符。 例如:if (condition1 || condition2) { // 执行操作 }

I know where dry desert ends, green grass grows · MooN