文章目录
什么是JavaScript
JavaScript 是一种高级的、解释型的编程语言,通常用于客户端脚本,允许开发者在浏览器中创建动态、交互式内容。它是前端开发的核心技术之一,与 HTML 和 CSS 并驾齐驱,共同构成了现代网页开发的基础。
HTML、CSS、JavaScript之间的关系:
JavaScript 的运行过程
- JavaScript编写的代码是保存在文件中的,也就是存储在硬盘(外存上)
- 双击 .html 文件,浏览器(应用程序)就会读取文件,将文件内容加载到内存中
- 浏览器会解析用户编写的代码,将代码翻译为二进制的,能让计算机识别的指令
- 得到的二进制指令会被 CPU 加载并执行
浏览器分为渲染引擎 + JS 引擎:
- 渲染引擎:解析 HTML + CSS,俗称“内核”
- JS 引擎:也就是 JS 解释器,典型的就是 chrome 中内置的 V8
JavaScript 的组成
JavaScript 由下面的内容组成:
- ECMAScript(简称 ES):JavaScript 语法
- DOM:页面文档对象模型,对页面中的元素进行操作
- BOM:浏览器对象模型,对浏览器窗口进行操作
光有 JS 基础语法,只能写一些基础的逻辑流程,要想完成更为复杂的操作,完成浏览器以及页面的交互,就需要 DOM API 和 BOM API。
JavaScript 的书写方式
JavaScript 和 CSS 类似,都有三种书写方式:
1. 行内式
直接将 JS 嵌入到 HTML 元素内部:
<input type="button" value="JavaScript样例" onclick="alert('hello world!')">
该方式只适合写较少且较短的 JS 代码。
2. 内嵌式
将 JS 代码写在 script 标签中:
<script>
alert('hahahahahah')
</script>
3. 外部式
可以将所有的 JS 代码都写入到一个 .js 文件中,然后通过 <script src=""></script>
来引入 js 文件:
<script src="hello.js"></script>
alert('你好!')
注释
JavaScript 注释分为单行注释——\\
和 多行注释——/* */
// 我是单行注释
/*
我是多行注释
我是多行注释
我是多行注释
*/
输入输出
JavaScript 输入使用 prompt('提示词')
:
<script>
prompt('请输入你的名字')
</script>
JavaScript 的输出分为两种,一种是弹出警示框输出alert('输出内容')
,一种是输出在控制台console.log('输出内容')
:
<script>
alert('你好')
</script>
<script>
console.log('你好')
</script>
f12 打开控制台:
JavaScript 变量
JavaScript 中创建变量可以使用 var
关键字和 let
关键字,这两个关键字的区别不大,但是建议使用 let 来创建变量,因为 let 的出现是晚于 var 的,功能更强大。
<script>
var name = '张三';
let age = 18
</script>
每个 JS 语句后面都以分号 ; 作为结束的标志,但是在 .js 文件中每一个语句后面的分号是可以省略的。
<script>
let name = prompt('请输入您的名字:');
alert('你好' + name);
</script>
可以发现,JavaScript 定义变量的时候,只需要使用 var 或者 let 关键字就可以,而不需要指定变量的类型,换句话说就是:JavaScript 是弱类型的语言,也就是动态类型的语言。
JS 的变量类型是程序运行过程中才可以确定的,只有运行到 = 的语句的时候才可以确定变量的类型。
<script>
var a = 10;
var b = 'haha';
</script>
<script>
var a = 10;
console.log('a的数据类型为:' + typeof(a));
a = 'haha'; //在程序运行的过程中,变量的类型可能会发生变化
console.log('a的数据类型为:' + typeof(a));
</script>
var 定义的变量和 let 定义的变量的区别
作用域(Scope):
- var声明的变量具有函数作用域或全局作用域。在函数内部使用var声明的变量,只能在该函数内部访问,但如果在函数外部(全局作用域)使用var声明变量,则可以在整个脚本中访问它。然而,需要注意的是,由于变量提升(Variable Hoisting)的原因,var声明的变量可以在其声明的代码之前就被访问,但此时它的值是undefined。
- let声明的变量具有块级作用域(block scope)。这意味着变量只在其声明的代码块(如{}内部)内有效。在块外部无法访问到该变量。此外,let声明的变量不存在变量提升,也就是说,它不能在声明之前被访问。
生命周期(Lifetime):
- var声明的变量在函数执行完毕或脚本执行完毕后不会立即被销毁,而是会保留在内存中,直到窗口或标签页被关闭。这就是所谓的闭包(Closure)现象,即函数外部无法访问函数内部的var变量,但函数内部可以访问函数外部的var变量。
- let声明的变量在块执行完毕后会被销毁(除非被封闭在闭包中)。这意味着let变量具有更精确的生命周期控制。
重复声明:
- 使用var可以重复声明同一个变量,而不会报错,但这样做会导致之前的值被覆盖。
- 使用let不能在同一作用域内重复声明同一个变量,否则会抛出语法错误(SyntaxError)。
暂时性死区(Temporal Dead Zone, TDZ):
- let变量在声明之前(在块或函数内)处于一个“暂时性死区”(Temporal Dead Zone, TDZ)。在这个区域内,变量是不可用的,访问它会抛出一个引用错误(ReferenceError)。这是let的一个重要特性,有助于捕获常见的编码错误。
<script>
var num = 10;
if (true) {
var num = 20;
console.log(num);
}
console.log(num);
</script>
var 只会在遇到函数的时候,函数的{}内部的区域才会被认为是一个作用域,所以在 if 语句块内的 var num 和 if 语句块之外的 var num 是同一个变量,他们的作用域是相同的。
<script>
let num = 10;
if (true) {
let num = 20;
console.log(num);
}
console.log(num);
</script>
使用 let 定义的变量,当遇到 {} 的时候,就会被认为是在一个新的作用域,所以 if 语句块之外的 let num 和 if 语句块之内的 let num 不是一个变量,两个变量的作用域不同。
JavaScript 基本数据类型
JavaScript 内置的几种数据类型:
- number:数字类型,不区分正数和浮点数
- string:字符串类型
- boolean:true 真,false 假
- undefined:只有唯一的值 undefined,表示未定义的值
- null:只有唯一的值 null,表示空值
number 数字类型
JavaScript 中不像 c++ 和 Java 那样区分浮点型和整型,统一都是“数字类型”。
<script>
let num = 1;
console.log('num的数据类型:' + typeof(num));
num = 3.14;
console.log('num的数据类型:' + typeof(num));
</script>
数字不止有我们日常使用的十进制,也可以通过 0o
前缀和 0x
前缀 和 0b
前缀分别表示八进制、十六进制和二进制:
<script>
let num = 0o1237;
console.log(num);
num = 0x123a;
console.log(num);
num = 0b010101;
console.log(num);
</script>
JavaScript 中特殊的数字
- Infinity:无穷大,大于任何数字。表示数字已经超过了 JS 能表示的范围
- -Infinity:负无穷大,小于任何数字。表示数字已经超过了 JS 能表示的范围
- NaN:表示当前的结果不是一个数字
<script>
let num = Number.MAX_VALUE; //得到Number所能表示的最大范围
console.log(num);
console.log(num * 2); //超过了Number能表示的最大范围,所以得到Infinity
console.log(-num * 2);
console.log('haha' - 10); //得到NaN
</script>
注意:
- 负无穷大和无穷小不是一回事,无穷小指无限趋近于0,值为 1 / Infinity
- ‘hehe’ + 10 得到的不是 NaN,而是 ‘hehe10’,会隐式的将数字转换为字符串类型,再进行字符串拼接
- 可以使用 isNaN 函数判断一个变量是否不是一个数字
string 字符串类型
JavaScript 中的字符串和 python 类似,使用单引号或者双引号包裹起来的都可以称为是字符串。
<script>
let s = '你好';
console.log('s的数据类型' + typeof(s));
s = "hello";
console.log('s的数据类型' + typeof(s));
s = haha; //报错
console.log('s的数据类型' + typeof(s));
</script>
正是因为 JavaScript 单引号和双引号都可以表示为字符串,所以在字符串中表示引号就比较方便:
<script>
console.log("女神对我说:'我爱你'");
console.log('女神对我说:"我爱你"');
</script>
当然,使用转义符表示引号也是可以的:
<script>
console.log("女神对我说:'我爱你'");
console.log('女神对我说:"我爱你"');
console.log("女神对我说:\"我爱你\"");
console.log('女神对我说:\'我爱你\'');
</script>
字符串类型的常用的属性和方法
length属性
每个字符串默认存在一个 length 属性用来记录字符串的长度:
<script>
let num = 'hello';
console.log('num的长度为:' + num.length);
num = '你好';
console.log('num的长度为:' + num.length);
</script>
JavaScript 字符串中的 length 属性记录的是字符串的字符个数,而不是字节个数,所以我们的中文字符串的长度也就是汉字的个数。
字符串拼接
JavaScript 字符串拼接可以直接使用 + 来实现:
<script>
let s1 = 'hello';
let s2 = ' world';
console.log(s1 + s2);
</script>
在要拼接的两个变量中只要有一个是字符串类型,那么最终拼接之后的结果就是字符串:
<script>
let s1 = 'hello ';
let s2 = 20;
console.log(s1 + s2);
</script>
boolean 布尔类型
JavaScript 和其他编程语言一样 boolean 类型的取值只有真(true)和假(false),但是和一些语言不同的是:JavaScript 的布尔类型值可以参与计算:
<script>
console.log(true + 1);
console.log(false + 1);
</script>
在 JavaScript 中 true 可以表示为数字1,fasle 可以表示为数字0。
undefined 未定义数据类型
如果一个变量在定义的时候没有初始化,那么该变量的类型暂时就是 undefined 类型:
<script>
let val;
console.log("val的数据类型是:" + typeof(val));
val = 'abc'
console.log("val的数据类型是:" + typeof(val)); //对变量赋值之后就可以确定变量的数据类型了
</script>
undefined 类型的值是 undefined,当 undefined 数据类型的变量和字符串进行 + 的时候,就会进行字符串的拼接:
<script>
let val;
console.log(val + '你好');
</script>
undefined 数据类型的变量和数字进行相加,得到的结果是 NaN:
<script>
let val;
console.log(val + 10);
</script>
null 空值类型
在对变量进行赋值的时候,如果赋值的是 null,那么就表示该变量的数据类型是空值类型:
<script>
let val = null;
console.log(val + 10);
console.log(val + '你好');
</script>
运算符
算术运算符
赋值运算符
字符串运算符
比较运算符
这里强调一下 ==
和 ===
的区别,==
在进行比较的时候会进行隐式类型的转换,而 ===
则不会进行隐式类型转换,也就是是说 ===
不仅比较变量的值相等,还会比较变量的类型相等:
<script>
let val1 = 10;
let val2 = '10';
console.log(val1 == val2);
console.log(val1 === val2);
</script>
在使用 ==
比较的时候,数字 10 首先会进行类型的转换,10 转换为 ‘10’,然后两者再比较的结果就是相等的了,而 ===
比较的就是 10 和 ‘10’,所以结果就是 false。
条件运算符
跟 Java 的三目运算符是一样的。
逻辑运算符
位运算符
条件语句
if (条件1) {
语句1
}else if (条件2) {
语句2
}
...
else {
语句n
}
<script>
let year = parseInt(prompt('请输入年份:'));
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
alert('闰年');
}else {
alert('不是闰年');
}
</script>
switch 选择语句
跟 Java 一样,通过使用 switch 来实现选择语句:
<script>
let day = parseInt(prompt('请输入数字'));
switch(day) {
case 1:
alert('星期一');
break;
case 2:
alert('星期二');
break;
case 3:
alert('星期三');
break;
case 4:
alert('星期四');
break;
case 5:
alert('星期五');
break;
case 6:
alert('星期六');
break;
case 7:
alert('星期日');
break;
default: //如果前面的情况都不满足的话就执行default中的语句
alert('您输入的数字非法,请检查后重新输入!');
}
</script>
循环语句
JavaScript 中表示循环结构的方式有两种:while 和 for。
while (条件) {
循环体;
}
执行过程:先判断条件语句是否为 true,为true才进入循环,否则不进入。
for (表达式1; 表达式2; 表达式3) {
循环体;
}
- 表达式1:用于初始化循环变量
- 表达式2:循环条件
- 表达式3:更新循环变量
执行过程:
- 先执行表达式1初始化循环变量(表达式1也可以为空语句)
- 再执行表达式2,判断循环变量是否符合条件,如果表达式2的结果为true才进行循环,否则结束循环
- 执行完循环体中的语句之后,执行表达式3
循环结构中常用的关键字:break 和 continue。
break 用于跳出循环,当执行到 break 的时候,该此循环中的 break 之后的代码将不会执行,直接跳出循环,结束循环。
continue 用于结束本次循环,继续下一次循环,当执行到 continue 语句的时候,该次循环 continue 之后的代码将不会执行了,继续下一次循环:
<script>
for (let i = 0; i < 10; i++) {
console.log(i);
if (i == 3) {
continue;
}
console.log(i);
}
</script>
<script>
for (let i = 0; i < 10; i++) {
console.log(i);
if (i == 3) {
break;
}
console.log(i);
}
</script>
注意:当我们在 while 循环中使用 continue 的时候,在 continue 之前需要更改循环变量,否则就会死循环:
<script>
let i = 0;
while (i < 10) {
console.log(i);
if (i == 3) {
continue;
}
console.log(i);
i++;
}
</script>
要先避免死循环,就需要在本次结束循环之前修改循环变量:
<script>
let i = 0;
while (i < 10) {
console.log(i);
if (i == 3) {
i++;
continue;
}
console.log(i);
i++;
}
</script>
数组
JavaScript 中创建数组主要有两种方式:
- 使用 new 关键字创建
- 使用字面常量方式创建
<script>
let arr1 = new Array(); //使用new关键字创建
let arr2 = [] //使用字面常量创建
let arr3 = [1,'你好'] //也可以在创建的时候就向数组中添加元素
</script>
JavaScript 中的数组中的元素不要求数据类型都相同
获取数组元素
获取数组中的元素可以直接通过数组名来获取数组中的全部元素,也可以通过数组名[下标]的方式访问某个特定的元素(数组下标从0开始)。
<script>
let arr = ['张三','李四','王五','赵六',1,2,3]
console.log(arr)
console.log(arr[0])
console.log(arr[1])
console.log(arr[5])
</script>
如果访问的元素的下标超过了数组可以表示的范围,那么获取到的结果就是 undefined:
console.log(arr[10])
既然通过下标可以获取到数组中指定的元素,那么也就可以修改某个特定的元素:
<script>
let arr = ['张三','李四','王五','赵六',1,2,3];
console.log(arr);
arr[1] = 100;
console.log(arr)
</script>
新增数组元素
JavaScript 中新增数组元素的方式有三种:
- 修改 length 属性
- 通过下标新增
- 使用 push 进行追加元素
<script>
let arr = [1,4,3,3]
arr.length = 6
console.log(arr[4])
console.log(arr[5])
</script>
通过修改 length 属性来添加的元素的默认值是 undefined。
<script>
let arr = []
arr[1] = 10
console.log(arr[0])
console.log(arr[1])
</script>
<script>
let arr = [1,2,3,4,5]
arr.push(10)
console.log(arr)
</script>
删除数组中的元素
JavaScript 中删除数组中的元素可以使用 splice 方法:arr.splice(index,n)
,index 表示从数组哪个元素开始删除,n 表示从 index 开始删除多少个元素。
<script>
let arr = [1,2,3,4,5]
arr.splice(1,3)
console.log(arr)
</script>
函数
语法格式:
//创建函数/函数声明/函数定义
function 函数名(参数列表) {
函数体
return 返回值
}
//函数的调用
函数名(实参列表) //不考虑返回值
返回值 = 函数名(实参列表) //考虑返回值
<script>
function sayHello() {
console.log('hello');
}
</script>
当我们定义完成函数之后,函数不会自动执行,只有函数被调用之后函数才会执行:
sayHello();
函数的定义和调用的先后顺序没有要求,这点不跟 python、c 等定义函数必须要调用函数之前。
对于参数个数
如果实参的个数多余形式参数的个数,那么形参会根据实参的顺序来进行初始化,多出来的实参不参与计算;如果实际参数的个数少于形式参数的个数的话,那么多出来的形式参数的值就是 undefined。
<script>
function sum(a,b) {
return a + b;
}
let ret = sum(10,20) //实参和形参的个数相同
console.log(ret)
ret = sum(10) //实参的个数少于形参的个数
console.log(ret)
ret = sum(10,20,30) //实参的个数多余形参的个数
console.log(ret)
</script>
除了上面的定义函数的方式之外,还有一种定义函数的方式:
let 函数名 = function() {
函数体
return 返回值
}
<script>
let sum = function(a,b) {
return a + b;
}
let ret = sum(10,20);
console.log(ret)
</script>
- function(){} 这样的写法定义了一个匿名的函数,然后将这个匿名函数用一个变量来表示,后面就可以通过这个变量来调用函数了
变量在函数中的作用域
在 ES6 标准之前,作用域主要分为两个:
- 全局作用域:在整个 script 标签中,或者单独的 .js 文件中生效
- 局部作用域/函数作用域:在函数内部生效
<script>
let num = 10; //全局变量,在整个script标签中都可以访问到
function test() {
console.log(num);
}
test() //10
</script>
<script>
function test() {
let num = 10; //局部变量,该变量只有在test函数中才可以被访问到,出了这个函数之后就会被销毁
}
test();
console.log(num);
</script>
创建变量的时候如果不写 let 或者 var 的话,那么该变量就是一个全局变量:
<script>
function test() {
num = 10;
}
test();
console.log(num);
</script>
作用域链
如果在函数的外部和内部都存在,那么访问这个变量的时候访问的结果是什么呢?这就需要知道什么是变量的作用域链了:变量的查找是从内到外查找的,换句话说就是在该访问之间的代码中服从就近原则。
<script>
let num = 10;
function test() {
let num = 20;
console.log(num);
}
test();
</script>
对象
这里对象的概念是什么我就不给大家解释了,大家有兴趣可以自己去百度。
在 JavaScript 中如何创建对象呢?有三种方式:
- 使用字面量创建对象
- 使用 new Object 创建对象
- 使用构造函数创建对象
使用字面量创建对象
使用字面量创建对象的时候,对象内的属性和属性的值之间需要使用 : 分隔,并且属性声明的时候不能使用 let 或者 var,属性之间使用 , 分隔。
<script>
let a = {} //创建一个空对象
let student = {
name: '张三',
age: 18,
sex: '男',
sayHello: function() {
console.log(this.name + '说:hello')
}
}
console.log(student.name);
console.log(student['age']);
console.log(student.sex);
student.sayHello();
</script>
在对象之内的函数中要想访问本对象的属性的时候需要使用 this.属性名
来访问。在对象之外访问对象中的属性或者函数的时候,可以使用 对象名.属性名/函数名
或者 对象名[’属性名‘]
来访问。
使用 new Object 创建对象
当使用 new Object 创建对象的时候,此时的对象还只是一个空对象,要想让对象具有属性或者方法,就需要使用 对象名.属性名/函数名 = ...
来为对象添加属性或者函数:
<script>
let student = new Object();
student.name = '张三';
student.age = 18;
student.sex = '男';
student.sayHello = function() {
console.log(student.name + '说:hello');
}
console.log(student.name);
console.log(student['age']);
console.log(student.sex);
student.sayHello();
</script>
使用构造函数创建对象
上面的两种方式只能创建一个对象,而使用构造函数可以很方便的创建多个对象。
function 构造函数名(参数列表) {
this.属性 = 值;
this.方法 = function...
}
let obj = new 构造函数名(实参);
<script>
function Cat(name,type,sound) {
this.name = name;
this.type = type;
this.sound = function() {
console.log(sound);
}
}
let huahua = new Cat('花花','狸花猫','喵');
let mimi = new Cat('咪咪','银渐层','喵喵');
console.log(mimi);
mimi.sound();
</script>