JavaScript,一种前端语言。与HTML、CSS合称为前端三件套。
这三种语言的关系,如图所示:
在这里,我们主要讨论JavaScript。
开始
解释型和编译型
除了JavaScript,还有一门常见的编程语言,Java。
那么,这两门编程语言之间是什么关系呢?
雷锋和雷峰塔的关系,没有关系。
而且,JavaScript是解释型语言,Java是编译型语言,可以说"feng"都不是同一个"feng"。
解释型语言和编译型语言的区别,如图所示:
首先,计算机只能处理机器语言,所有的程序语言,需要翻译成机器语言才能执行。
把程序语言翻译成机器语言的方式有两种:解释和编译。
- 解释:在运行时进行及时解释,并立即执行。
- 编译:在运行之前进行编译,生成中间代码文件,然后再翻译为机器语言进行执行。
浏览器的引擎
JavaScript就是一种解释型语言,除了可以应用在客户端外,还可以基于Node.js,应用在服务端。
在这里,我们只讨论JavaScript在客户端,尤其是在浏览器的应用。
一个浏览器,通常由两个主要部分组成:渲染引擎和JavaScript引擎。
- 渲染引擎:用来解析HTML与CSS,俗称内核。
比如新版本Chrome浏览器的blink,以及旧版本Chrome浏览器的webkit。 - JavaScript引擎:也称为JavaScript解释器,读取网页中的JavaScript代码,逐行解释每一句JavaScript代码,将其翻译为机器语言,然后由计算机去执行。
比如Chrome浏览器的V8,Firefox浏览器的OdinMonkey,Safari浏览器的JSCore。其中,Chrome浏览器的V8引擎性能最好。
JavaScript的组成
JavaScript,分为三部分:
- ECMAScript(JavaScript语法)
- DOM(页面文档对象类型)
- BOM(浏览器对象模型)
ECMAScript,JavaScript的语法和基础核心,是所有浏览器厂商共同遵守的一套JavaScript语法工业标准。
DOM,Document Object Model,文档对象模型,通过DOM可以对页面上的各种元素进行操作(大小、位置、颜色等)。
BOM,Browser Object Model,浏览器对象模型,通过BOM可以操作浏览器窗口,比如弹出框、控制浏览器跳转、获取分辨率等。
这一章,我们主要讨论"ECMAScript",下一章我们会讨论"DOM"和"BOM"。
JavaScript写在哪里
JavaScript写在哪里呢?
一共有三个地方可以写JavaScript
- 内嵌
- 外部
- 行内
内嵌
就拿《关于弹幕视频网站的例子:二十四小时直播》这个页面来说,会在两处看到JavaScript代码。
内嵌和外部。
监听窗口动作,在窗口即将关闭的时候,主动断开实时弹幕聊天。和这部分功能相关的代码,就是写在"内嵌",即写在<script>
标签中。
1 | <script type="text/javascript"> |
外部
和播放器相关的代码。就是写在"外部",通过src属性引用了外部的JavaScript文件。
1 | <script src="/js/50/player.js"></script> |
这种方式有利于HTML页面代码结构化,把大段JavaScript代码独立到HTML页面之外,既美观,也方便文件级别的复用。
(比如,这个播放器,除了直播页面用,点播页面也在用。)
需要注意的是,引用外部JavaScript文件的<script>
标签中间不可以写JavaScript代码。
行内
第三种是行内,将JavaScript代码写在HTML标签的事件属性中(以on开头的属性),如:onclick。
比如,我们这个网站的"邮箱"、"微信"以及"Github"图标,采取的就是行内JavaScript。
1 | <a class="social-icon" |
运行JS脚本的几种方式
- 使用浏览器编写简单JS语句
- 通过node运行
下载nodejs, 使用node 文件名.js
运行
不支持DOM和BOM操作。 - 使用IDE,例如,VSCode。
不支持DOM和BOM操作。
我们会在《4.Node.js》做详细的讨论。
其中,方法二和方法三,不支持DOM和BOM操作。
注释
我们知道,在Java中,注释有三种:单行注释、多行注释以及文档注释。
而在JavaScript中,只有单行注释和多行注释。
单行注释
在上文的内嵌式的例子中,我们就见识到了单行注释,//
符号。
1 | <script type="text/javascript"> |
多行注释
多行注释用的是/* */
。
示例代码:
1 | /* |
输入输出语句
JavaScript提供了一些输入输出语句,其常用的语句如下:
语句 | 作用 |
---|---|
console.log(msg) |
浏览器控制台打印输出信息 |
alert(msg) |
浏览器弹出警示框 |
prompt(info) |
浏览器弹出输入框 |
提个问题,这些是属于ECMAScript、DOM还是BOM?
这些都是直接控制浏览器的,属于BOM,Browser Object Model,浏览器对象模型。
变量
变量概述
变量:在程序运行过程中,其值可以发生改变的量,从本质上讲,变量是内存中的一小块区域。
变量的使用
声明变量
示例代码:
1 | // 声明变量 |
解释说明:var
是一个 JavaScript关键字,用来声明变量(variable)。使用该关键字声明变量后,计算机会自动为变量分配内存空间。age
是变量名,我们通过变量名来访问内存中分配的空间。
赋值
示例代码:
1 | // 给 age 这个变量赋值为10 |
解释说明:=
用来把右边的值赋给左边的变量空间中。
变量的初始化
声明变量的同时赋值,就是变量的初始化。
另外,我们可以看看"只声明不赋值"、"不声明不赋值"和"声明并赋值"这三种情况。
只声明不赋值
示例代码:
1 | // 只声明不赋值 |
运行结果:
1 | undefined |
不声明不赋值
示例代码:
1 | // 不声明不赋值 |
运行结果:
1 | VM209:1 Uncaught ReferenceError: ae is not defined |
声明并赋值
示例代码:
1 | // 声明并赋值 |
运行结果:
1 | 10 |
和Java类似,JavaScript也可以在同一行定义多个变量,中间使用逗号隔开。
(Java是可以在同一行定义多个同一种数据类型的变量,但JavaScript不要求数据类型相同。)
示例代码:
1 | var age = 10, name = 'zs', sex = 2; |
同样,不建议这么写!降低了程序的可读性!
变量命名规范
- 由字母
A-Z
、a-z
、数字0-9
、下划线_
、美元符号$
组成
如:usrAge
、num01
、_name
- 严格区分大小写。
var app;
和var App;
是两个不一样的变量 - 不能以数字开头。
- 不能是关键字、保留字。
例如:var
、for
、while
- 变量名必须有意义。
- 遵守驼峰命名法。首字母小写,后面单词的首字母大写。
基本数据类型及其包装类
数据类型特点
在《基于Java的后端开发入门:1.基础语法》这一章,我们说Java是一种强类型语言,一旦一个变量被指定了某个数据类型,如果不经过类型转换,那么它就永远是这个数据类型了。
而JavaScript,是一种弱类型语言,而且JavaScript拥有动态类型。即特点有:
- 不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。
- 相同的变量可用作不同的类型。
我们来看具体的例子。
示例代码:
1 | // 这是一个数字型 |
解释说明:在代码运行时,变量的数据类型是由JavaScript引擎根据=
右边变量值的数据类型来判断的。
示例代码:
1 | // x 为数字 |
运行结果:
1 | 6 |
解释说明:在上述代码中,相同的变量被用作了不同的类型。
基本数据类型
在Java中,数据类型被分为基本数据类型和引用数据类型。
基本数据类型有四大类、共计八种。分别是:
- 整数类型:
byte
、short
、int
、long
。 - 浮点类型:
float
、double
。 - 字符类型:
char
。 - 布尔类型:
boolean
。
在JavaScript中,数据类型也被分为"基本数据类型"(“简单数据类型”、“值类型”)和"引用数据类型"(“引用类型”、“复杂数据类型”)。
基本数据类型有七种,分别是:
string
number
bigint
boolean
null
undefined
symbol
其特点有:
- 非对象
- 无方法
包装类
除了null
和undefined
,剩下的五种基本数据类型还有其对应的包装类。
String
:为字符串基本类型的包装类。Number
:为数值基本类型的包装类。BigInt
:为大整数基本类型的包装类。Boolean
:为布尔基本类型的包装类。Symbol
:为字面量基本类型的包装类。
number及其包装类
number简介
number,数值,既可以用来保存整数值,也可以保存浮点数。
示例代码:
1 | // 整数 |
进制
在生活中,我们一般用的都是十进制。但在计算机中,除了十进制,还有二进制、十六进制以及八进制。
示例代码:
1 | // 十进制 |
解释说明:
- 二进制前面补
0b
- 八进制前面补
0
- 十六进制前面补
0x
。
范围
在Java中,不同的数字类型都有其表示范围。在JavaScript中,数值同样有其范围,即最大值和最小值。
Number.MAX_VALUE
:最大值Number.MIN_VALUE
:最小值
示例代码:
1 | console.log(Number.MAX_VALUE) |
运行结果:
1 | 1.7976931348623157e+308 |
注意!
这里用的就是包装类,基本数据类型,非对象,无方法。怎么这里还MAX_VALUE
、MIN_VALUE
了?
因为这是包装类。
三个特殊值
在JavaScript中,还有三个特殊值。
Infinity
:正无穷-Infinity
:负无穷NaN
:Not a number,代表一个非数值
题外话,有些资料会说
-Infinity
是无穷小,这个在数学上是不对的。
isNaN()
isNaN()
,is Not a Number,用来判断一个变量是否为非数字的类型,返回"true"或者"false"。
示例代码:
1 | var userAge = 21; |
运行结果:
1 | false |
特别的,如果是字符串类型的数字呢?比如var zero = "0";
示例代码:
1 | var zero = "0"; |
运行结果:
1 | false |
string及其包装类
string简介
在Java中,string并不是基本数据类型。但是在JavaScript中,string是基本数据类型。
JavaScript中的字符串,用引号包裹,双引号""
或单引号''
,都可以。
示例代码:
1 | // 使用双引号表示字符串 |
字符串引号嵌套
虽然JavaScript中,单引号和双引号不能混用,但是可以嵌套使用。
可以用单引号嵌套双引号,也可以用双引号嵌套单引号。
示例代码:
1 | // 可以用''包含"" |
字符串转义符
与Java一样,在Javascript中,也有一些转义字符。
\n
,换行符,n
是"newline"的意思。\\
,斜杠,\
\'
,单引号,'
\"
,双引号,"
\t
,缩进\b
,空格
字符串长度
字符串是由若干字符组成的,这些字符的数量就是字符串的长度。通过字符串的"length属性"可以获取整个字符串的长度。
示例代码:
1 | var strMsg = '好的'; |
运行结果:
1 | 2 |
解释说明:所利用的是包装类。
字符串拼接
JavaScript中的字符串拼接和Java中的字符串拼接规则一样,从左到右逐个执行,没有出现字符串,+
就依旧是算术运算符,如果出现了,就是字符串连接运算符。
示例代码:
1 | console.log(1 + 2 + "String") |
运行结果:
1 | 3String |
题外话,关于这点,JavaScript和Java一样,但是Python不是这样的。
示例代码:
1 print(1 + 2 + "String")运行结果:
1
2
3
4 Traceback (most recent call last):
File "/Users/kaka/Documents/50/q.py", line 1, in <module>
print(1 + 2 + "String")
TypeError: unsupported operand type(s) for +: 'int' and 'str'
boolean
boolean,布尔类型,有两个值:true
和false
,其中true
表示真(对),而false
表示假(错)。
布尔型和数字型相加
布尔型和数字型相加的时候,true
的值为1
,false
的值为0
。
示例代码:
1 | console.log(true + 1); |
运行结果:
1 | 2 |
题外话,在Python中,也是这样的。
示例代码:
1
2 print(True + 1)
print(False + 1)运行结果:
1
2 2
1那么,在Java中,编译都过不去。
布尔型的比较规则
我们来看一个很有趣的现象。
示例代码:
1 | if ('0'){ |
运行结果:
1 | '0' is true |
'0'
到底是true
还是false
?
JavaScript在做比较的时候,有这么三条规则:
- 如果比较的两者中有bool,会把bool先转换为对应的number,即
0
和1
- 如果比较的双方中有一方为number一方为string,会把string转换为数字
- 把string直接转换为bool的时候,空字符串
''
转换为false
,除此外的一切字符串转换为true
(更详细的转换规则,我们在下文"转换为布尔型",会做更多的讨论。)
在第一次比较的时候,直接把'0'
放在if的表达式中,相当于直接把string转换为bool,所以为真。
在第二次比较的时候,会先把false转换为0,然后把'0'
转换为0, 左右两边都是0,所以也是真。
题外话,在Python中,没这么复杂。
示例代码:
1
2
3
4
5
6
7 if '0':
print("'0' is True")
if '0' == False:
print("'0' is False")
else:
print("'0' is True")运行结果:
1
2 '0' is True
'0' is True那么,在Java中呢?编译都过不去。
undefined
一个声明后没有被赋值的变量会有一个默认值undefined
特别关注一下undefined
和其他基本数据类型的运算。
与字符串是进行拼接,与其他类型的运算结果是NaN
。
示例代码:
1 | var variable; |
运行结果:
1 | undefined |
null
一个声明后的变量,可以被赋null
值,里面存的值为空。
特别关注一下null
和其他基本数据类型的运算。
与字符串是进行拼接,与其他类型的运算结果是NaN
。
示例代码:
1 | var vari = null; |
运行结果:
1 | null |
注意,第一个null
和字符串'null'
不一样。
获取变量的数据类型
如何获取某个变量的数据类型呢?typeof()
示例代码:
1 | console.log(typeof('haode')) |
运行结果:
1 | string |
注意,null
的"typeof"是object
。
数据类型转换
为什么要进行数据类型的转换
我们举例子来说明。
现在有这么一个场景,用户输入一个数字,我们在基础上加一,然后返回给用户。
示例代码:
1 | var num = prompt('输入一个数字:'); |
运行结果:
1 | 11 |
我们输入了在弹框输入的是1
,但是得到结果却是11
。
这是因为,通过表单、prompt等获取过来的数据默认是字符串类型的。
如果要实现上述的需求,就需要转换变量的数据类型。
转换为数字型
转换为数字型有四种方法。
方法 | 作用 |
---|---|
parseInt() |
将字符串类型转成整数数值型 |
parseFloat() |
将字符串类型转成浮点数数值型 |
Number() |
将字符串类型转成数值型 |
- 、* 、/ |
隐式转换,即我们在进行算数运算的时候,JavaScript自动转换了数据类型。 不包括 + |
示例代码:
1 | var year = prompt('请输入您的出生年份:'); |
运行结果:
1 | string |
示例代码:
1 | var num1 = prompt('请输入第一个值:'); |
运行结果:
1 | string |
转换为字符串
转换为字符串型有三种方式。
方法 | 说明 |
---|---|
toString() |
被转换的内容.toString() |
String() |
String(被转换的内容) |
+ |
被转换的内容加上一个空的字符串 |
示例代码:
1 | var num = 1; |
运行结果:
1 | 1 |
注意,toString()
和String()
,使用方式不一样。
转换为布尔型
转换为布尔型的方法为:Boolean(被转换的内容)
示例代码:
1 | // false |
运行结果:
1 | false |
''
、0
、NaN
、null
、undefined
,都会被转换为false
。 其余值都会被转换为true
。
在if
中,会自动转换。
示例代码:
1 | if (undefined){ |
运行结果:
1 | 2 |
运算符
概述
运算符(operator)也被称为操作符,是用于实现赋值、比较和执行算数运算等功能的符号。
JavaScript中常用的运算符有:
- 算数运算符
- 递增和递减运算符
- 比较运算符
- 逻辑运算符
- 赋值运算符
算数运算符
算术运算符概述
算术运算符,用于执行两个变量或值的算术运算。
算数运算符有五种:
运算符 | 作用 |
---|---|
+ |
加 |
- |
减 |
* |
乘 |
/ |
除 |
% |
求余 |
浮点数的精度问题
我们来看一个现象。
示例代码:
1 | console.log(0.1+0.2) |
运行结果:
1 | 0.30000000000000004 |
小数点后有17位小数,这就是浮点数的最高精度,17位小数。
怎么最后多了一个4
?
因为,在进行算术计算时其精确度较差。
所以,不要直接判断两个浮点数是否相等。
题外话,不仅仅JavaScript是这样,Java、Python都是这样。
示例代码:
1 System.out.println(0.1 + 0.2);运行结果:
1 0.30000000000000004示例代码:
1 print(0.1 + 0.2)运行结果:
1 0.30000000000000004
递增和递减运算符
如果需要反复给数字变量添加或减去,可以使用递增++
或递减--
运算符来完成。
++
和--
,既可以放在变量的后边,也可以放在变量的前边。
单独使用的时候:
++
和--
无论是放在变量的前边还是后边,结果是一样的。
参与操作的时候:
- 如果放在变量的后边,先拿变量参与操作,后拿变量做
++
或者--
。 - 如果放在变量的前边,先拿变量做
++
或者--
,后拿变量参与操作。
示例代码:
1 | var i = 10; |
运行结果:
1 | i:11 |
题外话
在Java中也是同样的规则,但在Python中,不存在++
运算符。
比较运算符
比较运算符,也称"关系运算符",是两个数据进行比较时所使用的运算符,比较运算后,会返回一个布尔值(true/false)作为比较运算的结果。
比较运算符有:
运算符 | 备注 |
---|---|
< |
|
> |
|
<= |
|
>= |
|
!= |
|
== |
|
=== |
要求数据类型也一致 |
示例代码:
1 | console.log(1 == 1) |
运行结果:
1 | true |
解释说明:
为什么1 == '1'
为true
?
因为在JavaScript在做比较的时候,有这么三条规则:
- 如果比较的两者中有bool,会把bool先转换为对应的number,即
0
和1
- 如果比较的双方中有一方为number一方为string,会把string转换为数字
- 把string直接转换为bool的时候,空字符串
''
转换为false
,除此外的一切字符串转换为true
在这里,一方为number,一方为string,会把string转换为数字。
逻辑运算符
逻辑运算符是用来进行布尔值运算的运算符,其返回值也是布尔值。
运算符 | 作用 |
---|---|
&& |
与 |
|| |
或 |
! |
非 |
在Java中,有逻辑运算符和短路逻辑运算符。
但在JavaScript中,只有逻辑运算符。
JavaScript中的逻辑运算符对应的就是Java中的短路逻辑运算符。
短路是指,在逻辑与运算中,只要有一个表达式的值为false,那么结果就可以判定为false了,不会将所有表达式的值都计算出来。同理在逻辑或运算中,一旦发现值为true,右边的表达式将不再参与运算。
&&
:如果左边为真,右边执行;如果左边为假,右边不执行。
||
:如果左边为假,右边执行;如果左边为真,右边不执行。
示例代码:
1 | var x = 3; |
运行结果:
1 | false |
赋值运算符
运算符 | 作用 |
---|---|
= |
直接赋值 |
+= |
加后再赋值 |
-= |
减后再赋值 |
*= |
乘后再赋值 |
/= |
除后再赋值 |
%= |
求余后再赋值 |
示例代码:
1 | var a = 10; |
运行结果:
1 | 15 |
三元运算符
三元运算符,其实也属于一种流程控制,有部分资料会把三元运算符放在流程控制中进行讨论。
三元运算符语法格式:
1 | 关系表达式 ? 表达式一 : 表达式二; |
问号前面的是判断条件,判断结果为boolean型,为true时执行表达式一,为false时执行表达式二。
我们来看具体的例子。
示例代码:
1 | var a = 10; |
运行结果:
1 | 20 |
运算符优先级
运算符优先级,从高到低,如下:
- 小括号
- 一元
- 算数
- 关系
- 相等
- 逻辑
- 赋值
- 逗号
那么,来吧!
下面的式子,输出什么?
1 | console.log( 4 >= 6 || '人' != '阿凡达' && !(12 * 2 == 144) && true) |
拒绝回答,代码要可读,虽然有运算符的优先级,但不要玩这个,不要秀这种操作。
关于运算符的优先级,我们了解且能做简单的应用即可。
不要去做深度的应用!不要去做深度的应用!
流程控制
在一个程序执行的过程中,各条语句的执行顺序对程序的结果是有直接影响的。所以,我们需要清楚每条语句的执行流程。而且,很多时候要通过控制语句的执行顺序来实现我们想要的功能。
流程控制语句分类:
- 顺序结构
- 分支结构(if, switch)
- 循环结构(for, while, do…while)
顺序结构
顺序结构是程序中最简单最基本的流程控制,没有特定的语法结构,按照代码的先后顺序,依次执行,程序中大多数的代码都是这样执行的。
分支结构
if结构
if
1 | if (关系表达式) { |
示例代码:
1 | //定义两个变量 |
运行结果:
1 | a等于c |
if-else
1 | if (关系表达式) { |
示例代码:
1 | //定义两个变量 |
运行结果:
1 | a的值不大于b |
if-else if
1 | if (关系表达式1) { |
示例代码:
1 | //定义两个变量 |
运行结果:
1 | a的值等于b |
switch结构
switch
格式:
1 | switch (表达式) { |
执行流程:
- 首先计算出表达式的值。
- 其次,和case依次比较,一旦有对应的值,就会执行相应的语句,在执行的过程中,遇到break就会结束。
- 最后,如果所有的case都和表达式的值不匹配,就会执行default语句体部分,然后结束。
示例代码:
1 | //键盘录入月份数据,使用变量接收 |
运行结果:
1 | 请输入一个月份: |
case穿透
注意:如果switch中的case,没有对应break的话,则会出现case穿透的现象。
示例代码:
1 | //键盘录入月份数据,使用变量接收 |
运行结果:
1 | 冬季 |
循环结构
for循环
循环:循环语句可以在满足循环条件的情况下,反复执行某一段代码,这段被重复执行的代码被称为循环体语句,当反复执行这个循环体时,需要在合适的时候把循环判断条件修改为false,从而结束循环,否则循环将一直执行下去,形成死循环。
for循环格式:
1 | for (初始化语句;条件判断语句;条件控制语句) { |
- 初始化语句:用于表示循环开启时的起始状态,简单说就是循环开始的时候什么样。
- 条件判断语句:用于表示循环反复执行的条件,简单说就是判断循环是否能一直执行下去。
- 循环体语句:用于表示循环反复执行的内容,简单说就是循环反复执行的事情。
- 条件控制语句:用于表示循环执行中每次变化的内容,简单说就是控制循环是否能执行下去。
执行流程:
- 执行初始化语句
- 执行条件判断语句,看其结果是true还是false。如果是false,循环结束;如果是true,继续执行。
- 执行循环体语句
- 执行条件控制语句
- 回到第二步继续
示例代码:
1 | //需求:输出数据1-5 |
运行结果:
1 | 1 |
解释:
注意,在上文我们讨论了。
++
如果放在变量的后边,先拿变量参与操作,后拿变量做++
。++
如果放在变量的前边,先拿变量做++
,后拿变量参与操作。
但是!在这里的++
,不属于上述的两种,属于单独使用!(毕竟,我们是用;
隔开的。)
while循环
1 | 初始化语句; |
while循环执行流程:
- 执行初始化语句
- 执行条件判断语句,看其结果是true还是false。如果是false,循环结束;如果是true,继续执行。
- 执行循环体语句
- 执行条件控制语句
- 回到第二步继续
举个例子。
需求:世界最高山峰是珠穆朗玛峰(8844.43米=8844430毫米),假如我有一张足够大的纸,它的厚度是0.1毫米。请问,我折叠多少次,可以折成珠穆朗玛峰的高度?
示例代码:
1 | //定义一个计数器,初始值为0 |
运行结果:
1 | 需要折叠:27次 |
do…while循环
1 | 初始化语句; |
执行流程:
- 执行初始化语句
- 执行循环体语句
- 执行条件控制语句
- 执行条件判断语句,看其结果是true还是false。如果是false,循环结束;如果是true,继续执行。
- 回到第二步继续
示例代码:
1 | //需求:在控制台输出5次"HelloWorld" |
运行结果:
1 | HelloWorld |
跳转控制语句
跳转控制语句(break),结束循环。
跳转控制语句(continue),跳过本次循环,继续下次循环。
循环嵌套
循环嵌套概述:在循环中,继续定义循环
示例代码:
1 | //外循环控制小时的范围,内循环控制分钟的范围 |
运行结果:
1 | 0时0分 |
三种循环的区别
- for循环和while循环先判断条件是否成立,然后决定是否执行循环体(先判断后执行)
- do…while循环先执行一次循环体,然后判断条件是否成立,是否继续执行循环体(先执行后判断)
- for循环和while的区别:
- 条件控制语句所控制的自增变量,因为归属for循环的语法结构中,在for循环结束后,就不能再次被访问到了
- 条件控制语句所控制的自增变量,对于while循环来说不归属其语法结构中,在while循环结束后,该变量还可以继续使用
数组
什么是数组
数组是指一组数据的集合,其中的每个数据被称作元素,在数组中可以存放任意类型(可以类型不相同)的元素。
数组是一种将一组数据存储在单个变量名下的优雅方式。
数组的创建方式
利用new创建数组
1 | var 数组名 = new Array() ; |
利用数组字面量创建数组
1 | //1. 使用数组字面量方式创建空的数组 |
题外话
在Java和Python中也有数组,Java中的数组要求数组中的元素都是同一类型的,但是Python和JavaScript,没有这个要求,可以类型不同。
示例代码:
1 | var arrStus = ['小白',12,true,28.9]; |
获取数组中的元素
通过索引来访问数组中的元素,以数组名[索引]
的形式。
示例代码:
1 | // 定义数组 |
运行结果:
1 | 2 |
数组的长度
通过"数组名.length"可以获取数组元素的数量(数组长度)。
示例代码:
1 | var arrStus = [1,2,3]; |
运行结果:
1 | 3 |
遍历数组
那么,怎么遍历数组呢?
四种方法:
- for循环遍历
- forEach遍历
- for-of遍历
- for-in遍历
for循环遍历
for循环和数组长度,配合使用。
示例代码:
1 | var arr = ['red','green', 'blue']; |
运行结果:
1 | 1 |
forEach遍历
示例代码:
1 | var arr = ['red','green', 'blue']; |
运行结果:
1 | forEach遍历:0--red |
数组自带的循环,主要功能是遍历数组,实际性能不佳。
而且,不能使用break语句中断循环,也不能使用return语句返回到外层函数。
不建议使用。
for-of遍历
示例代码:
1 | var arr = ['red','green', 'blue']; |
运行结果:
1 | red |
for-of,性能比forEach()更强,而且可以正确响应break、continue和return语句。
此外:
- for-of循环不仅支持数组,还支持大多数类数组对象,例如DOM NodeList对象。
- for-of循环还支持字符串遍历
示例代码:
1 | for(let i of 'abc'){ |
运行结果:
1 | a |
for-in遍历
其实这种方法,更多时候用来对象的属性,当然,万物皆对象,那么也就可以用来遍历数组。
示例代码:
1 | var arr = ['red','green', 'blue']; |
运行结果:
1 | 0 |
注意,for-in遍历的index值是'0'
、'1'
、'2'
等,而且是字符串类型的。
数组中新增元素
两种方法:
- 通过修改"length"长度新增数组元素
- 通过修改数组索引新增数组元素
通过修改"length"长度新增数组元素
可以通过修改length
长度来实现数组扩容,然后新增元素。
在数组中,length
属性是可读写的
示例代码:
1 | var arr = ['red', 'green', 'blue', 'pink']; |
运行结果:
1 | (7) ['red', 'green', 'blue', 'pink', 空属性 × 3] |
其中索引号是4、5、6的空间没有给值,就是声明变量未给值,默认值就是 undefined。
通过修改数组索引新增数组元素
可以通过修改数组索引的方式追加数组元素
示例代码:
1 | var arr = ['red', 'green', 'blue', 'pink']; |
运行结果:
1 | (5) ['red', 'green', 'blue', 'pink', 'hotpink'] |
虽然数组一开始的长度是4,也就是说index只有0、1、2、3,但我们可以直接在index为4的地方添加元素。这种方式也是我们最常用的一种方式。
函数
什么是函数
函数:封装了一段可被重复调用执行的代码块,通过此代码块可以实现大量代码的重复使用。
函数的使用
函数在使用时分为两步:声明函数和调用函数。
声明函数
例如:
1 | // 声明函数 |
function
是声明函数的关键字,必须小写。
函数的声明方式有两种,这是其中一种。
调用函数
1 | // 调用函数 |
函数的两种声明方式
自定义函数方式(命名函数)
利用函数关键字 function 自定义函数方式。
1 | // 声明定义方式 |
- 因为有名字,所以也被称为命名函数
- 调用函数的代码既可以放到声明函数的前面,也可以放在声明函数的后面
函数表达式方式(匿名函数)
1 | // 这是函数表达式写法,匿名函数后面跟分号结束 |
- 因为函数没有名字,所以也被称为匿名函数
- 变量fn存储的是一个函数
- 函数表达式方式原理跟声明变量方式是一致的
- 函数调用的代码必须写到函数体后面
函数的封装
还有一个概念,函数的封装。
函数的封装是把一个或者多个功能通过函数的方式封装起来,对外只提供一个简单的函数接口。
函数的参数
形参和实参
在声明函数时,可以在函数名称后面的小括号中添加一些参数,这些参数被称为形参,而在调用该函数时,同样也需要传递相应的参数,这些参数被称为实参。
- 形参:方法定义中的参数
- 实参:方法调用中的参数
形参和实参个数匹配问题
形参和实参个数匹配问题有三种情况:
- 形参和实参个数相等,输出正确结果
- 实参个数多于形参个数,只取到形参的个数
- 实参个数少于形参个数,多的形参为undefined
示例代码:
1 | function sum(num1, num2) { |
运行结果:
1 | 300 |
第三种情况,计算结果为NaN
,这个我们在讨论"undefined"的时候,已经见到过了。
参数的默认值
如果我们想给参数确定默认值怎么办?
有这么一个技巧let p = num || 999
示例代码:
1 | function func(num) { |
运行结果:
1 | 1 |
函数的返回值
return
有的时候,我们会希望函数将值返回给调用者,此时通过使用return就可以实现。
return的语法格式如下:
示例代码:
1 | // 声明函数 |
需要注意的是:
- return之后的代码不会被执行。
- 如果函数没有return,返回的值是undefined。
- return只能返回一个值,如果用逗号隔开多个值,以最后一个为准。
举例:
- 如果函数没有return,返回的值是undefined。示例代码:运行结果:
1
2
3
4
5
6function sum(num1, num2) {
num1 + num2;
}
console.log(sum(100, 200));
console.log(sum(100, 400, 500, 700));
console.log(sum(200));1
2
3undefined
undefined
undefined - return只能返回一个值,如果用逗号隔开多个值,以最后一个为准。示例代码:运行结果:
1
2
3
4
5
6
7function func(num1,num2){
//函数体
return num1,num2;
}
// 调用函数,传入两个实参,并通过 resNum 接收函数返回值
var resNum = func(21,6);
console.log(resNum);1
6
break ,continue ,return 的区别
break
:结束当前的循环体。
continue
:跳出本次循环,继续执行下次循环。
return
:不仅可以退出循环,还能够返回return语句中的值,同时还可以结束当前的函数体内的代码。
arguments的应用
arguments,作用类似于在《基于Java的后端开发入门:4.集合》讨论的可变参数,及参数的个数不固定。
但只是类似,在JavaScript中,arguments实际上它是当前函数的一个内置对象。所有函数都内置了一个arguments对象,arguments对象中存储了传递的所有实参。
此外,arguments在形式上类似于数组,但是没有数组的push, pop等方法。
示例代码:
1 | function maxValue() { |
运行结果:
1 | 9 |
作用域
通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突。
作用域分类
作用域有两种:
- 全局作用域,作用于所有代码执行的环境。
- 局部作用域,作用于函数内的代码环境,就是局部作用域。因为跟函数有关系,所以也称为函数作用域。
JavaScript没有块级作用域(ES6之前)
接下来,我们来看一个很有趣的现象。
- 在Java中,示例代码:运行结果:
1
2
3
4
5if (true){
int num = 123;
// System.out.println(num);
}
System.out.println(num);报错了,这个没有任何问题。1
2java: 找不到符号
符号: 变量 num - 在Python中,示例代码:运行结果:
1
2
3
4
5if True:
num = 123
# print(num)
print(num)居然没有报错?1
123
- 在JavaScript中,示例代码:运行结果:
1
2
3
4
5if(true){
var num = 123;
// console.log(123);
}
console.log(num);也没有报错。1
123
在Java语言中,在if语句、循环语句中创建的变量,仅仅只能在本if语句、本循环语句中使用。这被称为块级作用域。
但在JavaScript中,没有块级作用域的概念。
变量的作用域
在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)。
- 全局变量在代码的任何位置都可以使用
- 在全局作用域下
var
声明的变量是全局变量 - 特殊情况下,在函数内不使用
var
声明的变量也是全局变量(不建议使用)
在局部作用域下声明的变量叫做局部变量(在函数内部定义的变量)。
- 局部变量只能在该函数内部使用
- 在函数内部
var
声明的变量是局部变量 - 函数的形参实际上就是局部变量
作用域链
那么,如果一个变量名在局部作用域和函数作用域中都存在呢?
到底以哪个为准?
这就是作用域链。
示例代码:
1 | function f1() { |
运行结果:
1 | 123 |
解释说明:
就近原则
作用域链:采取就近原则的方式来查找变量最终的值。
预解析
注意!在ES6中,预解析还有些不一样的地方。我们会在下文讨论ES6新特性的时候,专门讨论。
在讨论预解析之前,我们先来看几个有趣的现象。
- 没有初始化,示例代码:运行结果:
1
2// 没有初始化
console.log(num);1
Uncaught ReferenceError: num is not defined
- 初始化在后,示例代码:运行结果:
1
2
3// 初始化在后面
console.log(num);
var num = 10;1
undefined
- 调用在声明之前,示例代码:运行结果:
1
2
3
4
5// 调用在声明之前
fn();
function fn() {
console.log('打印');
}1
打印
- 匿名,调用在声明之前,示例代码:运行结果:
1
2
3
4
5// 匿名,调用在声明之前
fn();
var fn = function() {
console.log('想不到吧');
}1
Uncaught TypeError: fn is not a function
为什么会这样?
因为JavaScript解释器在运行JavaScript代码的时候分为两步:预解析和执行。
- 预解析:JavaScript代码执行之前,浏览器会默认把带有
var
和function
声明的变量在内存中进行提前声明或者定义,但不会进行赋值。 - 执行:从上到下执行JavaScript语句。
第一个现象,num
没有声明,所以报错。
第二个现象,num
声明了,但是没有赋值,所以值为undefined。
第三个现象,函数已经声明和定义过了,所以没有报错。
第四个现象,采用了匿名函数,变量fn
只进行了声明,没有赋值,所以报错了。
对象
关于对象的定义,我们不做太多讨论。可以参考《基于Java的后端开发入门:2.面向对象》。
创建对象的三种方式
在JavaScript中,有三种方法创建对象。
- 利用字面量创建对象
- 利用
new Object
创建对象 - 利用构造函数创建对象
利用字面量创建对象
对象字面量:就是花括号{ }
里面包含了表达这个具体事物(对象)的属性和方法。{ }
里面采取键值对的形式表示
- 键:相当于属性名。
- 值:相当于属性值,可以是任意类型的值(数字类型、字符串类型、布尔类型,函数类型等)。
示例代码:
1 | var star = { |
对象里面的属性调用:
对象.属性名
对象['属性名']
(方括号里面的属性必须加引号)
对象里面的方法调用:对象.方法名()
示例代码:
1 | // 调用名字属性 |
运行结果:
1 | k |
特别的,我们来比较一下变量和属性的区别,以及函数和方法的区别。
变量和属性
- 变量:单独声明赋值,单独存在。
- 属性:对象里面的变量称为属性,不需要声明,用来描述该对象的特征。
函数和方法
- 函数:单独存在的,通过"函数名()"的方式就可以调用。
- 方法:对象里面的函数称为方法,方法不需要声明,使用"对象.方法名()"的方式就可以调用,方法用来描述该对象的行为和功能。
利用new Object创建对象
示例代码:
1 | var andy = new Obect(); |
利用构造函数创建对象
构造函数:是一种特殊的函数,主要用来初始化对象。
我们直接来看具体的例子。
示例代码:
1 | function Person(name, age, sex) { |
运行结果:
1 | 大白 |
注意
- 构造函数约定首字母大写。
- 函数内的属性和方法前面需要添加
this
,表示当前对象的属性和方法。 - 构造函数中不需要
return
返回结果。 - 当我们创建对象的时候,必须用
new
来调用构造函数。
遍历对象属性
遍历对象的属性的方法是for-in,我们在上文就讨论过这种方法,当时用来遍历数组,而且当时我们说for-in更多的用途是用来遍历对象的属性。
for-in,其语法如下:
1 | for (变量 in 对象名字) { |
示例代码:
1 | for (let k in obj) { |
内置对象
JavaScript中的对象分为3种:
- 自定义对象
- 内置对象
- 浏览器对象
关于自定义对象,我们在上文进行了简单的讨论。浏览器对象,我们会在下一章讨论。
这里主要讨论内置对象,即JavaScript语言自带的一些对象
常见的内置对象有
- Math
- Date
- Array
- String
我们分别讨论。
Math
Math概述
Math对象,跟数学相关的运算,而且不需要进行实例化,直接调用。
常见的有:
方法 | 作用 |
---|---|
Math.PI |
圆周率 |
Math.floor() |
向下取整 |
Math.ceil() |
向上取整 |
Math.round() |
就近取整,注意-3.5 ,结果是-3 |
Math.abs() |
绝对值 |
Math.max() |
最大值 |
Math.min() |
最小值 |
random()
random()
,随机返回一个小数,其取值范围是,而且在是均衡的。
随机数方法比较
上文我们讨论了,random()
,取值范围是
那么,如果我们想获取范围呢?
很简单,比如,那么乘以100就OK了。
如果是之间的整数呢?即。
那就用取整函数。
那么,这时候就有讲究了。
Math.ceil(Math.random()*100);
,向上取整,那么取到0的概率极小。Math.floor(Math.random()*100);
,可均衡获取0到99的随机整数。Math.round(Math.random()*100);
,基本均衡获取0到100的随机整数,但获取最小值和最大值的几率少一半,因为头尾的分布区间只有其他数字的一半。
Date
Date概述
Date,用来处理日期和时间。
和Math不一样,他是一个构造函数,需要实例化后才能使用。
获取当前时间
直接实例化,那么就是当前时间。
示例代码:
1 | var now = new Date(); |
运行结果:
1 | Tue Nov 16 2021 13:54:07 GMT+0800 (中国标准时间) |
获取指定时间
在构造函数中,指定时间,那么获取的就是指定时间。
示例代码:
1 | console.log(new Date(2021, 9, 18, 05, 58, 00)) |
运行结果:
1 | Mon Oct 18 2021 05:58:00 GMT+0800 (中国标准时间) |
注意第一个方法,月份从0
开始。
日期格式化
在JavaScript中,日期格式化,需要我们手动进行拼接,依赖如下的方法:
方法 | 作用 |
---|---|
getYear() |
获取当前年份(1900到现在) |
getFullYear() |
获取完整的年份(4位,1970-???) |
getMonth() |
获取当前月份(0-11,0代表1月) |
getDate() |
获取当前日(1-31) |
getDay() |
获取当前星期X(0-6,0代表星期天) |
getTime() |
获取当前时间(从1970.1.1开始的毫秒数) |
getHours() |
获取当前小时数(0-23) |
getMinutes() |
获取当前分钟数(0-59) |
getSeconds() |
获取当前秒数(0-59) |
getMilliseconds() |
获取当前毫秒数(0-999) |
toLocaleDateString() |
获取当前日期 |
示例代码:
1 | var now = new Date(); |
运行结果:
1 | 121 |
有些资料会说
getYear()
获取的是两位数的年份,这些资料确实需要更新了。
获取时间戳
方法 | 备注 |
---|---|
Date.parse(new Date()) |
精确到秒 |
new Date().valueOf() |
精确到毫秒 |
new Date().getTime() |
精确毫秒 |
示例代码:
1 | console.log(Date.parse(new Date())) |
运行结果:
1 | 1637043221000 |
获取时区
new Date().getTimezoneOffset()
,返回的是标准时间减去我们的时间的分钟数。
示例代码:
1 | console.log(new Date().getTimezoneOffset()) |
运行结果:
1 | -480 |
解释说明:
我们处在东8区,即+8小时,标准时间减去我们时间的分钟数就是-480
。
Array
创建数组对象
创建数组对象,有两种方法:
- 字面量方式
- new Array()
判断是否为数组
方法 | 作用 |
---|---|
instanceof |
判断一个对象是否属于某种类型 |
Array.isArray() |
判断一个对象是否为数组,HTML5中提供的方法 |
示例代码:
1 | var arr = [1, 23]; |
运行结果:
1 | true |
解释说明:arr既是数组,也是对象,毕竟万物皆对象。
添加删除数组元素的方法
方法 | 作用 | 返回 |
---|---|---|
push() |
在末尾添加一个或多个元素 | 新数组的长度 |
pop() |
弹出数组最后一个元素 | 被弹出的元素 |
unshift() |
在开头添加一个或多个元素 | 新数组的长度 |
shift() |
弹出数组的第一个元素 | 被弹出的元素 |
示例代码:
1 | var arr = [1500, 1200, 2000, 2100, 1800]; |
运行结果:
1 | (3) [1800, 1200, 1500] |
数组排序
方法 | 作用 | 是否修改原数组 |
---|---|---|
reverse() |
颠倒数组中元素的顺序 | 是 |
sort() |
对数组中的元素进行排序 | 是 |
那么,来吧!
示例代码:
1 | [1, 64, 9, 6].sort() |
运行结果:
1 | (4) [1, 6, 64, 9] |
好像不对啊,再来。
示例代码:
1 | var arr = [1, 64, 9, 6]; |
运行结果:
1 | (4) [1, 6, 64, 9] |
还是不对!
都是字母序啊。
示例代码:
1 | var arr = [1, 64, 9, 6]; |
运行结果:
1 | [64, 9, 6, 1] |
注意:默认字母序。
数组索引方法
方法 | 作用 | 是否修改原数组 |
---|---|---|
indexOf() |
数组中查找给定元素的第一个索引 | 索引号,如果不存在返回-1 |
lastIndexOf() |
在数组中的最后一个索引 | 索引号,如果不存在返回-1 |
所以,常用来判断数组中是否包含某个元素。
数组转换为字符串
方法 | 作用 | 返回 |
---|---|---|
toString() |
把数组转换成字符串,逗号分隔 | 字符串 |
join('分隔符') |
把数组转换成字符串,指定的分隔符分隔 | 字符串 |
示例代码:
1 | arr = [1,2,3] |
运行结果:
1 | 1,2,3 |
更多方法
方法 | 作用 | 返回 |
---|---|---|
concat() |
拼接两个或多个数组 | 新数组(不影响原数组) |
slice() |
数组截取slice(begin,end) | 被截取的新数组(不影响原数组) |
splice() |
数组删除splice(第一个开始,要删除的个数) | 被删除的新数组(会影响原数组) |
示例代码:
1 | a1 = [1,2,3] |
运行结果:
1 | (3) [1, 2, 3] |
String
基本包装类型
这个类型,我们在上文进行过一些简单的讨论。是基本数据类型字符串的一个包装类。
在这里,我们会进行更详细的讨论。
示例代码:
1 | var str = 'andy'; |
运行结果:
1 | 4 |
有没有问题?
没什么问题啊,长度是4啊。
不是说这个。
上文我们讨论过,字符串属于基本数据类型,都已经是基本数据类型了,怎么还有"length"属性?
这是因为JavaScript会把基本数据类型包装为引用数据类型,即基本数据类型的包装类型。
具体执行了如下三步:
- 生成临时变量,把简单类型包装为复杂数据类型
1
var temp = new String('andy');
- 赋值给我们声明的字符变量
1
str = temp;
- 销毁临时变量
1
temp = null;
字符串的不可变
字符串不可变指的是,值不可变,虽然看上去可以改变内容,但其实是地址变了,内存中新开辟了一个内存空间。
这个我们在《基于Java的后端开发入门:3.最常用的Java自带的类》中有过讨论,这里不赘述。
根据字符返回位置
方法 | 作用 |
---|---|
indexOf('要查找的字符',开始的位置) |
返回指定内容在字符串中的位置,如果找不到会返回-1 |
lastIndexOf() |
从后往前找,只找第一个匹配的 |
根据位置返回字符
方法 | 作用 |
---|---|
charAt(index) |
获取指定位置的字符 |
charCodeAt(index) |
返回指定位置的ASCII码 |
str(index) |
获取指定位置的字符(HTML5新特性) |
字符串操作方法
方法 | 作用 |
---|---|
concat(str1,str2,str3) |
连接两个或多个字符串 |
substr(start,length) |
从start位置开始,截取length个字符 |
substring(start,end) |
从start位置开始,截取到end,end取不到 |
slice(start,end) |
从start位置开始,截取到end,end取不到 |
replace()方法
replace(被替换的字符串,要替换为的字符串)
比如,我们要把全角的逗号,
替换为半角的逗号,
。
示例代码:
1 | var banKeyWord = '关键词一,关键词二,关键词三,关键词四' |
运行结果:
1 | 关键词一,关键词二,关键词三,关键词四 |
看起来没有完全替换啊,只替换了前面的?
因为确实不会完全替换,如果需要完全替换,可以考虑正则表达式。
示例代码:
1 | var banKeyWord = '关键词一,关键词二,关键词三,关键词四' |
运行结果:
1 | 关键词一,关键词二,关键词三,关键词四 |
split()方法
split()
,切分字符串,它可以将字符串切分为数组。在切分完毕之后,返回的是一个新数组。
示例代码:
1 | var str = 'a,b,c,d'; |
运行结果:
1 | (4) ['a', 'b', 'c', 'd'] |
更多方法
方法 | 作用 |
---|---|
toUpperCase() |
转换大写 |
toLowerCase() |
转换小写 |
ES6的新特性
let
在ES6中,声明变量有了新的关键字
let
const
let声明的变量不存在预解析
在上文,我们讨论了预解析,有这么一个现象。
示例代码:
1 | // 初始化在后面 |
运行结果:
1 | undefined |
原因我在上文讨论过。
因为在执行之前,还有一个预解析阶段,浏览器会默认把带有var
和function
声明的变量在内存中进行提前声明或者定义,但不会进行赋值。
所以打印的内容是undefined
。
但是如果我们用let
声明变量的话,没这么复杂。直接不可用。
示例代码:
1 | console.log(num); |
运行结果:
1 | Uncaught SyntaxError: Identifier 'num' has already been declared |
let声明的变量不允许重复声明
用var
声明的变量,还有这么一个特点,可以重复声明。
示例代码:
1 | var num = 1 |
运行结果:
1 | 2 |
但是如果用let
呢?
示例代码:
1 | let num = 1 |
运行结果:
1 | Uncaught SyntaxError: Identifier 'num' has already been declared |
let声明的变量存在块级作用域
在上文,我们还提到了JavaScript中不存在块级作用域。
示例代码:
1 | if(true){ |
运行结果:
1 | 123 |
但是,如果是用let
声明的变量呢?
示例代码:
1 | if(true){ |
运行结果:
1 | Uncaught ReferenceError: num is not defined |
那么,有哪些属于块级作用域呢?
除了一个{ }
包裹,典型的就是for
循环。
示例代码:
1 | for (var i=0;i<3;i++){ |
运行结果:
1 | 3 |
示例代码:
1 | for (let i=0;i<3;i++){ |
运行结果:
1 | Uncaught ReferenceError: i is not defined |
所以,for循环中,更建议用let
来声明index。因为index变量只在该块内作用,避免对其他代码造成影响。
忘掉var吧
我的个人观点,let
的出现,就是为了填补var
的诸多坑,所以请忘掉var
吧。
const
const
,声明常量。
什么是常量,就不会被改变的量,不允许重新被赋值。
(这么说不完全准确,如果是引用数据类型的话,还是可能被改变)
示例代码:
1 | const v = 1 |
运行结果:
1 | 1 |
示例代码:
1 | const arr = [1,2,3,4] |
运行结果:
1 | (4) [5, 2, 3, 4] |
解释说明:
虽然是const,但是还是被改变了。原因我们会在本章最后,讨论内存模型的时候进行讨论。
String
在ES6中,String有了几个新方法。
我们依次讨论。
includes
includes
,顾名思义,包含。
示例代码:
1 | // 是否包含 |
运行结果:
1 | true |
startsWith
startsWith
,顾名思义,是否以XXX开头
endWith
endWith
,顾名思义,是否以XXX开头
函数
参数的默认值
在ES6中,参数的默认值有了非常优雅的方法。
示例代码:
1 | function func(num = 999) { |
运行结果:
1 | 1 |
剩余参数
示例代码:
1 | function func(a,...rest) { |
运行结果:
1 | 1 |
注意!...
前面有,
,但是后面没有。
扩展运算符
剩余参数的作用是把多个参数合并成一个数组。
扩展运算符作用相反,把数组拆成多个参数。
示例代码:
1 | function func(a,b,c,d,e) { |
运行结果:
1 | 15 |
合并数组
扩展运算符还有一个用途,合并数组。
示例代码:
1 | var a1 = [1,2,3] |
运行结果:
1 | (6) [1, 2, 3, 4, 5, 6] |
内存分配
最后一个话题,JavaScript的内存分配。
实际上,有了Java的基础,这部分很容易讨论。
堆和栈
首先!在JavaScript中没有堆栈的概念。但是,通过堆栈的方式,可以更容易理解。
基本数据类型的变量,存放到栈中。
引用数据类型的遍历,栈中存放的是地址,真正的对象实例存放在堆中。
这就是能解释为什么const修饰的引用数据类型的变量,可以被改变了。因为const修饰之后,只是栈内存的地址不能被修改,但是堆内存依旧可以被修改。
基本数据类型传参
函数的形参也可以看做是一个变量,当我们把一个值类型变量作为参数传给函数的形参时,其实是把变量在栈空间里的值复制了一份给形参,那么在方法内部对形参做任何修改,都不会影响到的外部变量。
示例代码:
1 | function fn(a) { |
运行结果:
1 | 11 |
引用数据类型传参
函数的形参也可以看做是一个变量,当我们把引用类型变量传给形参时,其实是把变量在栈空间里保存的堆地址复制给了形参,形参和实参其实保存的是同一个堆地址,所以操作的是同一个对象。
示例代码:
1 | function Person(name) { |
运行结果:
1 | 刘德华 |