avatar


1.基础语法

JavaScript,一种前端语言。与HTML、CSS合称为前端三件套。

这三种语言的关系,如图所示:
HTML、CSS以及JavaScript

在这里,我们主要讨论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,分为三部分:

  1. ECMAScript(JavaScript语法)
  2. DOM(页面文档对象类型)
  3. BOM(浏览器对象模型)

ECMAScript,JavaScript的语法和基础核心,是所有浏览器厂商共同遵守的一套JavaScript语法工业标准。
DOM,Document Object Model,文档对象模型,通过DOM可以对页面上的各种元素进行操作(大小、位置、颜色等)。
BOM,Browser Object Model,浏览器对象模型,通过BOM可以操作浏览器窗口,比如弹出框、控制浏览器跳转、获取分辨率等。

这一章,我们主要讨论"ECMAScript",下一章我们会讨论"DOM"和"BOM"。

JavaScript写在哪里

JavaScript写在哪里呢?
一共有三个地方可以写JavaScript

  1. 内嵌
  2. 外部
  3. 行内

内嵌

就拿《关于弹幕视频网站的例子:二十四小时直播》这个页面来说,会在两处看到JavaScript代码。
内嵌和外部。

监听窗口动作,在窗口即将关闭的时候,主动断开实时弹幕聊天。和这部分功能相关的代码,就是写在"内嵌",即写在<script>标签中。

1
2
3
4
5
6
7
<script type="text/javascript">
window.onbeforeunload = function (e) {
// 窗口关闭,主动下线
chatRoom.quit();
im.disconnect();
};
</script>

外部

和播放器相关的代码。就是写在"外部",通过src属性引用了外部的JavaScript文件。

1
<script src="/js/50/player.js"></script>

这种方式有利于HTML页面代码结构化,把大段JavaScript代码独立到HTML页面之外,既美观,也方便文件级别的复用。
(比如,这个播放器,除了直播页面用,点播页面也在用。)

需要注意的是,引用外部JavaScript文件的<script>标签中间不可以写JavaScript代码。

行内

第三种是行内,将JavaScript代码写在HTML标签的事件属性中(以on开头的属性),如:onclick。
比如,我们这个网站的"邮箱"、"微信"以及"Github"图标,采取的就是行内JavaScript。

1
2
3
<a class="social-icon"
onclick="layer.confirm('<a style=&quot;color:black&quot;>邮箱:i@m.kakawanyifan.com</a>',{title:false,closeBtn:0,skin:'layui-layer-molv',btn:['发送邮件','取消'],btn2:function(index,layero){}},function(index){layer.close(index);window.location.href='mailto:i@m.kakawanyifan.com';})"
title="E-Mail"><i class="fas fa-envelope" aria-hidden="true"></i></a>

运行JS脚本的几种方式

  1. 使用浏览器编写简单JS语句
  2. 通过node运行
    下载nodejs, 使用node 文件名.js运行
    不支持DOM和BOM操作。
  3. 使用IDE,例如,VSCode。
    不支持DOM和BOM操作。
    我们会在《3.Node.js》做详细的讨论。

其中,方法二和方法三,不支持DOM和BOM操作。

注释

我们知道,在Java中,注释有三种:单行注释、多行注释以及文档注释。
而在JavaScript中,只有单行注释和多行注释。

单行注释

在上文的内嵌式的例子中,我们就见识到了单行注释,//符号。

1
2
3
4
5
6
7
<script type="text/javascript">
window.onbeforeunload = function (e) {
// 窗口关闭,主动下线
chatRoom.quit();
im.disconnect();
};
</script>

多行注释

多行注释用的是/* */

示例代码:

1
2
3
4
/*
获取用户年龄和姓名
并通过提示框显示出来
*/

输入输出语句

JavaScript提供了一些输入输出语句,其常用的语句如下:

语句 作用
console.log(msg) 浏览器控制台打印输出信息
alert(msg) 浏览器弹出警示框
prompt(info) 浏览器弹出输入框

提个问题,这些是属于ECMAScript、DOM还是BOM?
这些都是直接控制浏览器的,属于BOM,Browser Object Model,浏览器对象模型。

其中,关于console.log或许不好理解,因为console.log,这个不仅在浏览器中有,在Node.js中也有,用于在控制台打印输出信息。
但是,这只是Node.js中也提供了console.log,并不能说明console.log属于ECMAScript。
在浏览器中,console.log的完整表达是:

1
window.console.log("XXX");

变量

变量概述

变量:在程序运行过程中,其值可以发生改变的量,从本质上讲,变量是内存中的一小块区域。

变量的使用

声明变量

示例代码:

1
2
3
//  声明变量
// 声明一个 名称为age 的变量
var age;

解释说明:var是一个 JavaScript关键字,用来声明变量(variable)。使用该关键字声明变量后,计算机会自动为变量分配内存空间。age是变量名,我们通过变量名来访问内存中分配的空间。

赋值

示例代码:

1
2
// 给 age  这个变量赋值为10
age = 10;

解释说明:=用来把右边的值赋给左边的变量空间中。

变量的初始化

声明变量的同时赋值,就是变量的初始化。

另外,我们可以看看"只声明不赋值"、"不声明不赋值"和"声明并赋值"这三种情况。

只声明不赋值
示例代码:

1
2
3
// 只声明不赋值
var age;
console.log(age);

运行结果:

1
undefined

不声明不赋值
示例代码:

1
2
// 不声明不赋值
console.log(age)

运行结果:

1
2
VM209:1 Uncaught ReferenceError: ae is not defined
at <anonymous>:1:13

声明并赋值
示例代码:

1
2
3
// 声明并赋值
var age = 10;
console.log (age);

运行结果:

1
10

和Java类似,JavaScript也可以在同一行定义多个变量,中间使用逗号隔开。
(Java是可以在同一行定义多个同一种数据类型的变量,但JavaScript不要求数据类型相同。)
示例代码:

1
var age = 10,  name = 'zs', sex = 2;

同样,不建议这么写!降低了程序的可读性!

变量命名规范

  1. 由字母A-Za-z、数字0-9、下划线_、美元符号$组成
    如:usrAgenum01_name
  2. 严格区分大小写。
    var app;var App;是两个不一样的变量
  3. 不能以数字开头。
  4. 不能是关键字、保留字。
    例如:varforwhile
  5. 变量名必须有意义。
  6. 遵守驼峰命名法。首字母小写,后面单词的首字母大写。

基本数据类型及其包装类

数据类型特点

《基于Java的后端开发入门:1.基础语法》这一章,我们说Java是一种强类型语言,一旦一个变量被指定了某个数据类型,如果不经过类型转换,那么它就永远是这个数据类型了。

而JavaScript,是一种弱类型语言,而且JavaScript拥有动态类型。即特点有:

  1. 不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。
  2. 相同的变量可用作不同的类型。

我们来看具体的例子。
示例代码:

1
2
3
4
// 这是一个数字型
var age = 10;
// 这是一个字符串
var areYouOk = '是的';

解释说明:在代码运行时,变量的数据类型是由JavaScript引擎根据=右边变量值的数据类型来判断的。

示例代码:

1
2
3
4
5
6
// x 为数字
var x = 6;
console.log(x)
// x 为字符串
x = "Bill";
console.log(x)

运行结果:

1
2
6
Bill

解释说明:在上述代码中,相同的变量被用作了不同的类型。

基本数据类型

在Java中,数据类型被分为基本数据类型和引用数据类型。
基本数据类型有四大类、共计八种。分别是:

  1. 整数类型:byteshortintlong
  2. 浮点类型:floatdouble
  3. 字符类型:char
  4. 布尔类型:boolean

在JavaScript中,数据类型也被分为"基本数据类型"(“简单数据类型”、“值类型”)和"引用数据类型"(“引用类型”、“复杂数据类型”)。

基本数据类型有七种,分别是:

  1. string
  2. number
  3. bigint
  4. boolean
  5. null
  6. undefined
  7. symbol

其特点有:

  1. 非对象
  2. 无方法

包装类

除了nullundefined,剩下的五种基本数据类型还有其对应的包装类。

  1. String:为字符串基本类型的包装类。
  2. Number:为数值基本类型的包装类。
  3. BigInt:为大整数基本类型的包装类。
  4. Boolean:为布尔基本类型的包装类。
  5. Symbol:为字面量基本类型的包装类。

number及其包装类

number简介

number,数值,既可以用来保存整数值,也可以保存浮点数。

示例代码:

1
2
3
4
// 整数
var age = 21;
// 小数
var Age = 21.3747;

进制

在生活中,我们一般用的都是十进制。但在计算机中,除了十进制,还有二进制、十六进制以及八进制。

示例代码:

1
2
3
4
5
6
7
8
// 十进制
var a = 7
// 二进制
var b = 0b111
// 八进制
var c = 07
// 十六进制
var d = 0xA

解释说明:

  • 二进制前面补0b
  • 八进制前面补0
  • 十六进制前面补0x

范围

在Java中,不同的数字类型都有其表示范围。在JavaScript中,数值同样有其范围,即最大值和最小值。

  1. Number.MAX_VALUE:最大值
  2. Number.MIN_VALUE:最小值

示例代码:

1
2
console.log(Number.MAX_VALUE)
console.log(Number.MIN_VALUE)

运行结果:

1
2
1.7976931348623157e+308
5e-324

注意!
这里用的就是包装类,基本数据类型,非对象,无方法。怎么这里还MAX_VALUEMIN_VALUE了?
因为这是包装类。

三个特殊值

在JavaScript中,还有三个特殊值。

  1. Infinity:正无穷
  2. -Infinity:负无穷
  3. NaN:Not a number,代表一个非数值

题外话,有些资料会说-Infinity是无穷小,这个在数学上是不对的。

isNaN()

isNaN(),is Not a Number,用来判断一个变量是否为非数字的类型,返回"true"或者"false"。
示例代码:

1
2
3
4
5
var userAge = 21;
var isNum = isNaN(userAge);
console.log(isNum);
var userName = "andy";
console.log(isNaN(userName));

运行结果:

1
2
false
true

特别的,如果是字符串类型的数字呢?比如var zero = "0";

示例代码:

1
2
var zero = "0";
console.log(isNaN(zero));

运行结果:

1
false

string及其包装类

string简介

在Java中,string并不是基本数据类型。但是在JavaScript中,string是基本数据类型。

JavaScript中的字符串,用引号包裹,双引号""或单引号'',都可以。

示例代码:

1
2
3
4
5
6
7
8
9
10
// 使用双引号表示字符串
var strMsg = "我爱北京天安门";
// 使用单引号表示字符串
var strMsg2 = '我爱吃猪蹄';

// 常见错误
// 报错,没使用引号,会被认为是JavaScript代码
var strMsg3 = 我爱大肘子;
// 报错,不能 单双引号搭配
var badQuotes = 'What on earth?";

字符串引号嵌套

虽然JavaScript中,单引号和双引号不能混用,但是可以嵌套使用。
可以用单引号嵌套双引号,也可以用双引号嵌套单引号。

示例代码:

1
2
3
4
// 可以用''包含""
var strMsg = '我是"高帅富"程序猿';
// 也可以用"" 包含''
var strMsg2 = "我是'高帅富'程序猿";

字符串转义符

与Java一样,在Javascript中,也有一些转义字符。

  1. \n,换行符,n是"newline"的意思。
  2. \\,斜杠,\
  3. \',单引号,'
  4. \",双引号,"
  5. \t,缩进
  6. \b,空格

字符串长度

字符串是由若干字符组成的,这些字符的数量就是字符串的长度。通过字符串的"length属性"可以获取整个字符串的长度。

示例代码:

1
2
var strMsg = '好的';
console.log(strMsg.length);

运行结果:

1
2

解释说明:所利用的是包装类。

鉴于string的包装类应用非常广泛,我们会在下文进行更多的讨论

字符串拼接

JavaScript中的字符串拼接和Java中的字符串拼接规则一样,从左到右逐个执行,没有出现字符串,+就依旧是算术运算符,如果出现了,就是字符串连接运算符。

示例代码:

1
2
3
console.log(1 + 2 + "String")
console.log(1 + 2 + "String" + 3 + 4)
console.log(1 + 2 + "String" + (3 + 4))

运行结果:

1
2
3
3String
3String34
3String7

题外话,关于这点,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,布尔类型,有两个值:truefalse,其中true表示真(对),而false表示假(错)。

布尔型和数字型相加

布尔型和数字型相加的时候,true的值为1false的值为0

示例代码:

1
2
console.log(true + 1);
console.log(false + 1);

运行结果:

1
2
2
1

题外话,在Python中,也是这样的。

示例代码:

1
2
print(True + 1)
print(False + 1)

运行结果:

1
2
2
1

那么,在Java中,编译都过不去。

布尔型的比较规则

我们来看一个很有趣的现象。

示例代码:

1
2
3
4
5
6
7
if ('0'){
console.log("'0' is true");
}

if ('0' == false){
console.log("'0' is false");
}

运行结果:

1
2
'0' is true
'0' is false

'0'到底是true还是false

JavaScript在做比较的时候,有这么三条规则:

  1. 如果比较的两者中有bool,会把bool先转换为对应的number,即01
  2. 如果比较的双方中有一方为number一方为string,会把string转换为数字
  3. 把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
2
3
4
5
var variable;
console.log(variable);
console.log('你好' + variable);
console.log(11 + variable);
console.log(true + variable);

运行结果:

1
2
3
4
undefined
你好undefined
NaN
NaN

null

一个声明后的变量,可以被赋null值,里面存的值为空。

特别关注一下null和其他基本数据类型的运算。
与字符串是进行拼接,与其他类型的运算结果是NaN

示例代码:

1
2
3
4
5
var vari = null;
console.log(vari)
console.log('你好' + vari);
console.log(11 + vari);
console.log(true + vari);

运行结果:

1
2
3
4
null
你好null
11
1

注意,第一个null和字符串'null'不一样。

获取变量的数据类型

如何获取某个变量的数据类型呢?typeof()

示例代码:

1
2
3
4
5
console.log(typeof('haode'))
console.log(typeof(1))
console.log(typeof(true))
console.log(typeof(a))
console.log(typeof(null))

运行结果:

1
2
3
4
5
string
number
boolean
undefined
object

注意,null的"typeof"是object

数据类型转换

为什么要进行数据类型的转换

我们举例子来说明。
现在有这么一个场景,用户输入一个数字,我们在基础上加一,然后返回给用户。

示例代码:

1
2
3
var num = prompt('输入一个数字:');
var rnt = num + 1;
console.log(rnt)

运行结果:

1
11

我们输入了在弹框输入的是1,但是得到结果却是11
这是因为,通过表单、prompt等获取过来的数据默认是字符串类型的。
如果要实现上述的需求,就需要转换变量的数据类型。

转换为数字型

转换为数字型有四种方法。

方法 作用
parseInt() 将字符串类型转成整数数值型
parseFloat() 将字符串类型转成浮点数数值型
Number() 将字符串类型转成数值型
-*/ 隐式转换,即我们在进行算数运算的时候,JavaScript自动转换了数据类型。
不包括+

示例代码:

1
2
3
4
5
6
var year = prompt('请输入您的出生年份:');
console.log(typeof(year))
// 隐式转换
var result = 2021 - year;
console.log(typeof(result))
console.log('您的年龄是:' + result + '岁');

运行结果:

1
2
3
string
number
您的年龄是:28岁

示例代码:

1
2
3
4
5
6
7
var num1 = prompt('请输入第一个值:');
console.log(typeof(num1))
var num2 = prompt('请输入第二个值:');
console.log(typeof(num2))
console.log(num1 + num2)
var result = parseFloat(num1) + parseFloat(num2);
console.log('结果是:' + result);

运行结果:

1
2
3
4
string
string
1.22.3
结果是:3.5

转换为字符串

转换为字符串型有三种方式。

方法 说明
toString() 被转换的内容.toString()
String() String(被转换的内容)
+ 被转换的内容加上一个空的字符串

示例代码:

1
2
3
4
5
6
7
var num = 1;
console.log(num.toString())
console.log(typeof(num.toString()))
console.log(String(num))
console.log(typeof(String(num)))
console.log(num + '')
console.log(typeof(num + ''))

运行结果:

1
2
3
4
5
6
1
string
1
string
1
string

注意,toString()String(),使用方式不一样。

转换为布尔型

转换为布尔型的方法为:Boolean(被转换的内容)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// false
console.log(Boolean(''));
// false
console.log(Boolean(0));
// false
console.log(Boolean(NaN));
// false
console.log(Boolean(null));
// false
console.log(Boolean(undefined));
// true
console.log(Boolean('小白'));
// true
console.log(Boolean(12));

运行结果:

1
2
3
4
5
6
7
false
false
false
false
false
true
true

''0NaNnullundefined,都会被转换为false。 其余值都会被转换为true

if中,会自动转换。
示例代码:

1
2
3
4
5
if (undefined){
console.log(1)
}else{
console.log(2)
}

运行结果:

1
2

运算符

概述

运算符(operator)也被称为操作符,是用于实现赋值、比较和执行算数运算等功能的符号。

JavaScript中常用的运算符有:

  • 算数运算符
  • 递增和递减运算符
  • 比较运算符
  • 逻辑运算符
  • 赋值运算符

算数运算符

算术运算符概述

算术运算符,用于执行两个变量或值的算术运算。
算数运算符有五种:

运算符 作用
+
-
*
/
% 求余

浮点数的精度问题

我们来看一个现象。
示例代码:

1
2
console.log(0.1+0.2)
console.log((0.1 + 0.2).toString().length)

运行结果:

1
2
0.30000000000000004
19

小数点后有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

递增和递减运算符

如果需要反复给数字变量添加或减去11,可以使用递增++或递减--运算符来完成。

++--,既可以放在变量的后边,也可以放在变量的前边。

单独使用的时候:

  • ++--无论是放在变量的前边还是后边,结果是一样的。

参与操作的时候:

  • 如果放在变量的后边,先拿变量参与操作,后拿变量做++或者--
  • 如果放在变量的前边,先拿变量做++或者--,后拿变量参与操作。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var i = 10;
// 单独使用
i++;
// i:11
console.log("i:" + i);

var j = 10;
// 单独使用
++j;
// j:11
console.log("j:" + j);

var x = 10;
// 赋值运算,++在后边,所以是使用x原来的值赋值给y,x本身自增1
var y = x++;
// x:11,y:10
console.log("x:" + x + ", y:" + y);

var m = 10;
// 赋值运算,++在前边,所以是使用m自增后的值赋值给n,m本身自增1
var n = ++m;
// m:11,m:11
console.log("m:" + m + ", m:" + m);

运行结果:

1
2
3
4
i:11
j:11
x:11, y:10
m:11, m:11

题外话
在Java中也是同样的规则,但在Python中,不存在++运算符。

比较运算符

比较运算符,也称"关系运算符",是两个数据进行比较时所使用的运算符,比较运算后,会返回一个布尔值(true/false)作为比较运算的结果。
比较运算符有:

运算符 备注
<
>
<=
>=
!=
==
=== 要求数据类型也一致

示例代码:

1
2
3
4
5
console.log(1 == 1)
console.log(1 === 1)

console.log(1 == '1')
console.log(1 === '1')

运行结果:

1
2
3
4
true
true
true
false

解释说明:
为什么1 == '1'true
因为在JavaScript在做比较的时候,有这么三条规则:

  1. 如果比较的两者中有bool,会把bool先转换为对应的number,即01
  2. 如果比较的双方中有一方为number一方为string,会把string转换为数字
  3. 把string直接转换为bool的时候,空字符串''转换为false,除此外的一切字符串转换为true

在这里,一方为number,一方为string,会把string转换为数字。

逻辑运算符

逻辑运算符是用来进行布尔值运算的运算符,其返回值也是布尔值。

运算符 作用
&&
||
!

在Java中,有逻辑运算符和短路逻辑运算符。
但在JavaScript中,只有逻辑运算符。
JavaScript中的逻辑运算符对应的就是Java中的短路逻辑运算符。

短路是指,在逻辑与运算中,只要有一个表达式的值为false,那么结果就可以判定为false了,不会将所有表达式的值都计算出来。同理在逻辑或运算中,一旦发现值为true,右边的表达式将不再参与运算。

&&:如果左边为真,右边执行;如果左边为假,右边不执行。
||:如果左边为假,右边执行;如果左边为真,右边不执行。

示例代码:

1
2
3
4
5
6
7
var x = 3;
var y = 4;

// 左边已经可以确定结果为false,右边不参与运算
console.log((x++ > 4) && (y++ > 5));
console.log(x);
console.log(y);

运行结果:

1
2
3
false
4
4

赋值运算符

运算符 作用
= 直接赋值
+= 加后再赋值
-= 减后再赋值
*= 乘后再赋值
/= 除后再赋值
%= 求余后再赋值

示例代码:

1
2
3
4
5
6
7
8
9
10
11
var a = 10;
a += 5;
console.log(a)
a -= 5;
console.log(a)
a *= 10;
console.log(a)
a /= 10;
console.log(a)
a %= 3
console.log(a)

运行结果:

1
2
3
4
5
15
10
100
10
1

三元运算符

三元运算符,其实也属于一种流程控制,有部分资料会把三元运算符放在流程控制中进行讨论。

三元运算符语法格式:

1
关系表达式 ? 表达式一 : 表达式二;

问号前面的是判断条件,判断结果为boolean型,为true时执行表达式一,为false时执行表达式二。

我们来看具体的例子。
示例代码:

1
2
3
4
5
var a = 10;
var b = 20;
// 判断 a>b 是否为真,如果为真取a的值,如果为假,取b的值
var c = a > b ? a : b;
console.log(c);

运行结果:

1
20

运算符优先级

运算符优先级,从高到低,如下:

  1. 小括号
  2. 一元
  3. 算数
  4. 关系
  5. 相等
  6. 逻辑
  7. 赋值
  8. 逗号

那么,来吧!
下面的式子,输出什么?

1
console.log( 4 >= 6 || '人' != '阿凡达' && !(12 * 2 == 144) && true)

拒绝回答,代码要可读,虽然有运算符的优先级,但不要玩这个,不要秀这种操作。
关于运算符的优先级,我们了解且能做简单的应用即可。
不要去做深度的应用!不要去做深度的应用!

流程控制

在一个程序执行的过程中,各条语句的执行顺序对程序的结果是有直接影响的。所以,我们需要清楚每条语句的执行流程。而且,很多时候要通过控制语句的执行顺序来实现我们想要的功能。

流程控制语句分类:

  1. 顺序结构
  2. 分支结构(if, switch)
  3. 循环结构(for, while, do…while)

顺序结构

顺序结构是程序中最简单最基本的流程控制,没有特定的语法结构,按照代码的先后顺序,依次执行,程序中大多数的代码都是这样执行的。

分支结构

if结构

if

1
2
3
if (关系表达式) {
语句体;
}

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
//定义两个变量
var a = 10;
var b = 20;
//需求:判断a和b的值是否相等,如果相等,就在控制台输出:a等于b
if(a == b) {
console.log("a等于b");
}
//需求:判断a和c的值是否相等,如果相等,就在控制台输出:a等于c
var c = 10;
if(a == c) {
console.log("a等于c");
}

运行结果:

1
a等于c

if-else

1
2
3
4
5
if (关系表达式) {
语句体1;
} else {
语句体2;
}

示例代码:

1
2
3
4
5
6
7
8
9
//定义两个变量
var a = 10;
var b = 50;
//需求:判断a是否大于b,如果是,在控制台输出:a的值大于b,否则,在控制台输出:a的值不大于b
if(a > b) {
console.log("a的值大于b");
} else {
console.log("a的值不大于b");
}

运行结果:

1
a的值不大于b

if-else if

1
2
3
4
5
6
7
8
9
if (关系表达式1) {
语句体1;
} else if (关系表达式2) {
语句体2;
}

else {
语句体n+1;
}

示例代码:

1
2
3
4
5
6
7
8
9
10
11
//定义两个变量
var a = 10;
var b = 10;
//需求:判断a是否大于b,如果是,在控制台输出:a的值大于b,否则,在控制台输出:a的值不大于b
if(a > b) {
console.log("a的值大于b");
} else if (a < b){
console.log("a的值小于b");
} else {
console.log("a的值等于b");
}

运行结果:

1
a的值等于b

switch结构

switch

格式:

1
2
3
4
5
6
7
8
9
10
11
12
switch (表达式) {
case 1:
语句体1;
break;
case 2:
语句体2;
break;
...
default:
语句体n+1;
break;
}

执行流程:

  • 首先计算出表达式的值。
  • 其次,和case依次比较,一旦有对应的值,就会执行相应的语句,在执行的过程中,遇到break就会结束。
  • 最后,如果所有的case都和表达式的值不匹配,就会执行default语句体部分,然后结束。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//键盘录入月份数据,使用变量接收
var sc = prompt("请输入一个月份:");
var month = parseInt(sc)
switch(month) {
case 1:
case 2:
case 12:
console.log("冬季");
break;
case 3:
case 4:
case 5:
console.log("春季");
break;
case 6:
case 7:
case 8:
console.log("夏季");
break;
case 9:
case 10:
case 11:
console.log("秋季");
break;
default:
console.log("你输入的月份有误");
}

运行结果:

1
2
3
请输入一个月份:
1
冬季

case穿透

注意:如果switch中的case,没有对应break的话,则会出现case穿透的现象。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//键盘录入月份数据,使用变量接收
var sc = prompt("请输入一个月份:");
var month = parseInt(sc)
switch(month) {
case 1:
case 2:
case 12:
console.log("冬季");
// break;
case 3:
case 4:
case 5:
console.log("春季");
// break;
case 6:
case 7:
case 8:
console.log("夏季");
// break;
case 9:
case 10:
case 11:
console.log("秋季");
// break;
default:
console.log("你输入的月份有误");
}

运行结果:

1
2
3
4
5
冬季
春季
夏季
秋季
你输入的月份有误

循环结构

for循环

注意!在for循环中,建议let来声明index,具体原因,我们会在下文讨论ES6新特性的时候,专门讨论。

循环:循环语句可以在满足循环条件的情况下,反复执行某一段代码,这段被重复执行的代码被称为循环体语句,当反复执行这个循环体时,需要在合适的时候把循环判断条件修改为false,从而结束循环,否则循环将一直执行下去,形成死循环。

for循环格式:

1
2
3
for (初始化语句;条件判断语句;条件控制语句) {
循环体语句;
}
  • 初始化语句:用于表示循环开启时的起始状态,简单说就是循环开始的时候什么样。
  • 条件判断语句:用于表示循环反复执行的条件,简单说就是判断循环是否能一直执行下去。
  • 循环体语句:用于表示循环反复执行的内容,简单说就是循环反复执行的事情。
  • 条件控制语句:用于表示循环执行中每次变化的内容,简单说就是控制循环是否能执行下去。

执行流程:

  1. 执行初始化语句
  2. 执行条件判断语句,看其结果是true还是false。如果是false,循环结束;如果是true,继续执行。
  3. 执行循环体语句
  4. 执行条件控制语句
  5. 回到第二步继续

示例代码:

1
2
3
4
5
6
7
8
9
//需求:输出数据1-5
for(let i=1; i<=5; i++) {
console.log(i);
}
console.log("--------");
//需求:输出数据5-1
for(let i=5; i>=1; i--) {
console.log(i);
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
--------
5
4
3
2
1

解释:
注意,在上文我们讨论了。

  • ++如果放在变量的后边,先拿变量参与操作,后拿变量做++
  • ++如果放在变量的前边,先拿变量做++,后拿变量参与操作。

但是!在这里的++,不属于上述的两种,属于单独使用!(毕竟,我们是用;隔开的。)

while循环

1
2
3
4
5
初始化语句;
while (条件判断语句) {
循环体语句;
条件控制语句;
}

while循环执行流程:

  1. 执行初始化语句
  2. 执行条件判断语句,看其结果是true还是false。如果是false,循环结束;如果是true,继续执行。
  3. 执行循环体语句
  4. 执行条件控制语句
  5. 回到第二步继续

举个例子。
需求:世界最高山峰是珠穆朗玛峰(8844.43米=8844430毫米),假如我有一张足够大的纸,它的厚度是0.1毫米。请问,我折叠多少次,可以折成珠穆朗玛峰的高度?

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//定义一个计数器,初始值为0
var count = 0;
//定义纸张厚度
var paper = 0.1;
//定义珠穆朗玛峰的高度
var zf = 8844430;
//因为要反复折叠,所以要使用循环,但是不知道折叠多少次,这种情况下更适合使用while循环
//折叠的过程中当纸张厚度大于珠峰就停止了,因此继续执行的要求是纸张厚度小于珠峰高度
while(paper <= zf) {
//循环的执行过程中每次纸张折叠,纸张的厚度要加倍
paper *= 2;
//在循环中执行累加,对应折叠了多少次
count++;
}
//打印计数器的值
console.log("需要折叠:" + count + "次");

运行结果:

1
需要折叠:27次

do…while循环

1
2
3
4
5
初始化语句;
do {
循环体语句;
条件控制语句;
}while(条件判断语句);

执行流程:

  1. 执行初始化语句
  2. 执行循环体语句
  3. 执行条件控制语句
  4. 执行条件判断语句,看其结果是true还是false。如果是false,循环结束;如果是true,继续执行。
  5. 回到第二步继续

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
//需求:在控制台输出5次"HelloWorld"
//for循环实现
for(let i=1; i<=5; i++) {
console.log("HelloWorld");
}
console.log("--------");
//do...while循环实现
var j = 1;
do {
console.log("HelloWorld");
j++;
}while(j<=5);

运行结果:

1
2
3
4
5
6
7
8
9
10
11
HelloWorld
HelloWorld
HelloWorld
HelloWorld
HelloWorld
--------
HelloWorld
HelloWorld
HelloWorld
HelloWorld
HelloWorld

跳转控制语句

跳转控制语句(break),结束循环。
跳转控制语句(continue),跳过本次循环,继续下次循环。

循环嵌套

循环嵌套概述:在循环中,继续定义循环

示例代码:

1
2
3
4
5
6
7
//外循环控制小时的范围,内循环控制分钟的范围
for (let hour = 0; hour < 24; hour++) {
for (let minute = 0; minute < 60; minute++) {
console.log(hour + "时" + minute + "分");
}
console.log("--------");
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0时0分
0时1分
0时2分
0时3分
0时4分
0时5分

【部分运行结果略】

23时55分
23时56分
23时57分
23时58分
23时59分

三种循环的区别

  • for循环和while循环先判断条件是否成立,然后决定是否执行循环体(先判断后执行)
  • do…while循环先执行一次循环体,然后判断条件是否成立,是否继续执行循环体(先执行后判断)
  • for循环和while的区别:
    • 条件控制语句所控制的自增变量,因为归属for循环的语法结构中,在for循环结束后,就不能再次被访问到了
    • 条件控制语句所控制的自增变量,对于while循环来说不归属其语法结构中,在while循环结束后,该变量还可以继续使用

数组

什么是数组

数组是指一组数据的集合,其中的每个数据被称作元素,在数组中可以存放任意类型(可以类型不相同)的元素。
数组是一种将一组数据存储在单个变量名下的优雅方式。

数组的创建方式

利用new创建数组

1
var 数组名 = new Array() ;

利用数组字面量创建数组

1
2
3
4
//1. 使用数组字面量方式创建空的数组
var 数组名 = [];
//2. 使用数组字面量方式创建带初始值的数组
var 数组名 = ['小白','小黑','大黄','瑞奇'];

题外话
在Java和Python中也有数组,Java中的数组要求数组中的元素都是同一类型的,但是Python和JavaScript,没有这个要求,可以类型不同。

示例代码:

1
var arrStus = ['小白',12,true,28.9];

获取数组中的元素

通过索引来访问数组中的元素,以数组名[索引]的形式。

示例代码:

1
2
3
4
// 定义数组
var arrStus = [1,2,3];
// 获取数组中的第2个元素
console.log(arrStus[1]);

运行结果:

1
2

数组的长度

通过"数组名.length"可以获取数组元素的数量(数组长度)。

示例代码:

1
2
var arrStus = [1,2,3];
console.log(arrStus.length);

运行结果:

1
3

遍历数组

那么,怎么遍历数组呢?
四种方法:

  1. for循环遍历
  2. forEach遍历
  3. for-of遍历
  4. for-in遍历

for循环遍历

for循环和数组长度,配合使用。

示例代码:

1
2
3
4
var arr = ['red','green', 'blue'];
for(let i = 0; i < arr.length; i++){
console.log(arrStus[i]);
}

运行结果:

1
2
3
1
2
3

forEach遍历

示例代码:

1
2
3
4
var arr = ['red','green', 'blue'];
arr.forEach(function(value,i){
console.log('forEach遍历:'+i+'--'+value);
})

运行结果:

1
2
3
forEach遍历:0--red
forEach遍历:1--green
forEach遍历:2--blue

数组自带的循环,主要功能是遍历数组,实际性能不佳。
而且,不能使用break语句中断循环,也不能使用return语句返回到外层函数。
不建议使用。

for-of遍历

示例代码:

1
2
3
4
var arr = ['red','green', 'blue'];
for(let i of arr){
console.log(i);
}

运行结果:

1
2
3
red
green
blue

for-of,性能比forEach()更强,而且可以正确响应break、continue和return语句。

此外:

  • for-of循环不仅支持数组,还支持大多数类数组对象,例如DOM NodeList对象。
  • for-of循环还支持字符串遍历

示例代码:

1
2
3
for(let i of 'abc'){
console.log(i);
}

运行结果:

1
2
3
a
b
c

for-in遍历

其实这种方法,更多时候用来对象的属性,当然,万物皆对象,那么也就可以用来遍历数组。

示例代码:

1
2
3
4
5
6
var arr = ['red','green', 'blue'];
for (let index in arr){
console.log(index);
console.log(typeof(index));
console.log(arr[index]);
}

运行结果:

1
2
3
4
5
6
7
8
9
0
string
red
1
string
green
2
string
blue

注意,for-in遍历的index值是'0''1''2'等,而且是字符串类型的。

数组中新增元素

两种方法:

  1. 通过修改"length"长度新增数组元素
  2. 通过修改数组索引新增数组元素

通过修改"length"长度新增数组元素

可以通过修改length长度来实现数组扩容,然后新增元素。
在数组中,length属性是可读写的

示例代码:

1
2
3
4
5
6
var arr = ['red', 'green', 'blue', 'pink'];
arr.length = 7;
console.log(arr);
console.log(arr[4]);
console.log(arr[5]);
console.log(arr[6]);

运行结果:

1
2
3
4
5
(7) ['red', 'green', 'blue', 'pink', 空属性 × 3]
undefined
undefined
undefined
undefined

其中索引号是4、5、6的空间没有给值,就是声明变量未给值,默认值就是 undefined。

通过修改数组索引新增数组元素

可以通过修改数组索引的方式追加数组元素

示例代码:

1
2
3
var arr = ['red', 'green', 'blue', 'pink'];
arr[4] = 'hotpink';
console.log(arr);

运行结果:

1
(5) ['red', 'green', 'blue', 'pink', 'hotpink']

虽然数组一开始的长度是4,也就是说index只有0、1、2、3,但我们可以直接在index为4的地方添加元素。这种方式也是我们最常用的一种方式。

函数

什么是函数

函数:封装了一段可被重复调用执行的代码块,通过此代码块可以实现大量代码的重复使用。

函数的使用

函数在使用时分为两步:声明函数和调用函数。

声明函数

例如:

1
2
3
4
// 声明函数
function 函数名() {
//函数体代码
}

function是声明函数的关键字,必须小写。
函数的声明方式有两种,这是其中一种。

调用函数

1
2
3
// 调用函数
// 通过调用函数名来执行函数体代码
函数名();

函数的两种声明方式

自定义函数方式(命名函数)

利用函数关键字 function 自定义函数方式。

1
2
3
4
// 声明定义方式
function fn() {...}
// 调用
fn();
  • 因为有名字,所以也被称为命名函数
  • 调用函数的代码既可以放到声明函数的前面,也可以放在声明函数的后面

函数表达式方式(匿名函数)

1
2
3
4
// 这是函数表达式写法,匿名函数后面跟分号结束
var fn = function(){...};
// 调用的方式,函数调用必须写到函数体下面
fn();
  • 因为函数没有名字,所以也被称为匿名函数
  • 变量fn存储的是一个函数
  • 函数表达式方式原理跟声明变量方式是一致的
  • 函数调用的代码必须写到函数体后面

函数的封装

还有一个概念,函数的封装。
函数的封装是把一个或者多个功能通过函数的方式封装起来,对外只提供一个简单的函数接口。

函数的参数

形参和实参

在声明函数时,可以在函数名称后面的小括号中添加一些参数,这些参数被称为形参,而在调用该函数时,同样也需要传递相应的参数,这些参数被称为实参。

  • 形参:方法定义中的参数
  • 实参:方法调用中的参数

形参和实参个数匹配问题

形参和实参个数匹配问题有三种情况:

  1. 形参和实参个数相等,输出正确结果
  2. 实参个数多于形参个数,只取到形参的个数
  3. 实参个数少于形参个数,多的形参为undefined

示例代码:

1
2
3
4
5
6
7
8
9
function sum(num1, num2) {
return (num1 + num2);
}
// 形参和实参个数相等,输出正确结果
console.log(sum(100, 200));
// 实参个数多于形参,只取到形参的个数
console.log(sum(100, 400, 500, 700));
// 实参个数少于形参,多的形参定义为undefined
console.log(sum(200));

运行结果:

1
2
3
300
500
NaN

第三种情况,计算结果为NaN,这个我们在讨论"undefined"的时候,已经见到过了。

参数的默认值

如果我们想给参数确定默认值怎么办?
有这么一个技巧let p = num || 999

示例代码:

1
2
3
4
5
6
function func(num) {
let p = num || 999
console.log(p)
}
func(1)
func()

运行结果:

1
2
1
999

在ES6中,有了更优雅的方式,我们在下文讨论ES6新特性的时候讨论。

函数的返回值

return

有的时候,我们会希望函数将值返回给调用者,此时通过使用return就可以实现。

return的语法格式如下:

示例代码:

1
2
3
4
5
6
7
8
// 声明函数
function 函数名(){
...
return 需要返回的值;
}
// 调用函数
// 此时调用函数就可以得到函数体内return 后面的值
函数名();

需要注意的是:

  1. return之后的代码不会被执行。
  2. 如果函数没有return,返回的值是undefined。
  3. return只能返回一个值,如果用逗号隔开多个值,以最后一个为准。

举例:

  1. 如果函数没有return,返回的值是undefined。示例代码:
    1
    2
    3
    4
    5
    6
    function sum(num1, num2) {
    num1 + num2;
    }
    console.log(sum(100, 200));
    console.log(sum(100, 400, 500, 700));
    console.log(sum(200));
    运行结果:
    1
    2
    3
    undefined
    undefined
    undefined
  2. return只能返回一个值,如果用逗号隔开多个值,以最后一个为准。示例代码:
    1
    2
    3
    4
    5
    6
    7
    function 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
2
3
4
5
6
7
8
9
10
11
function maxValue() {
var max = arguments[0];
for (let i = 0; i < arguments.length; i++) {
if (max < arguments[i]) {
max = arguments[i];
}
}
return max;
}
console.log(maxValue(2, 4, 5, 9));
console.log(maxValue(12, 4, 9));

运行结果:

1
2
9
12

作用域

通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突。

作用域分类

作用域有两种:

  1. 全局作用域,作用于所有代码执行的环境。
  2. 局部作用域,作用于函数内的代码环境,就是局部作用域。因为跟函数有关系,所以也称为函数作用域。

JavaScript没有块级作用域(ES6之前)

在ES6中,有块级作用域了,我们会在下文讨论ES6新特性的时候,专门讨论。

接下来,我们来看一个很有趣的现象。

  1. 在Java中,示例代码:
    1
    2
    3
    4
    5
    if (true){
    int num = 123;
    // System.out.println(num);
    }
    System.out.println(num);
    运行结果:
    1
    2
    java: 找不到符号
    符号: 变量 num
    报错了,这个没有任何问题。
  2. 在Python中,示例代码:
    1
    2
    3
    4
    5
    if True:
    num = 123
    # print(num)

    print(num)
    运行结果:
    1
    123
    居然没有报错?
  3. 在JavaScript中,示例代码:
    1
    2
    3
    4
    5
    if(true){
    var num = 123;
    // console.log(123);
    }
    console.log(num);
    运行结果:
    1
    123
    也没有报错。

在Java语言中,在if语句、循环语句中创建的变量,仅仅只能在本if语句、本循环语句中使用。这被称为块级作用域。
但在JavaScript中,没有块级作用域的概念。

变量的作用域

在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)。

  1. 全局变量在代码的任何位置都可以使用
  2. 在全局作用域下var声明的变量是全局变量
  3. 特殊情况下,在函数内不使用var声明的变量也是全局变量(不建议使用)

在局部作用域下声明的变量叫做局部变量(在函数内部定义的变量)。

  1. 局部变量只能在该函数内部使用
  2. 在函数内部var声明的变量是局部变量
  3. 函数的形参实际上就是局部变量

作用域链

那么,如果一个变量名在局部作用域和函数作用域中都存在呢?
到底以哪个为准?
这就是作用域链。

示例代码:

1
2
3
4
5
6
7
8
9
function f1() {
var num = 123;
function f2() {
console.log( num );
}
f2();
}
var num = 456;
f1();

运行结果:

1
123

解释说明:
就近原则
作用域链

作用域链:采取就近原则的方式来查找变量最终的值。

预解析

注意!在ES6中,预解析还有些不一样的地方。我们会在下文讨论ES6新特性的时候,专门讨论。

在讨论预解析之前,我们先来看几个有趣的现象。

  1. 没有初始化,示例代码:
    1
    2
    // 没有初始化
    console.log(num);
    运行结果:
    1
    Uncaught ReferenceError: num is not defined
  2. 初始化在后,示例代码:
    1
    2
    3
    // 初始化在后面
    console.log(num);
    var num = 10;
    运行结果:
    1
    undefined
  3. 调用在声明之前,示例代码:
    1
    2
    3
    4
    5
    // 调用在声明之前
    fn();
    function fn() {
    console.log('打印');
    }
    运行结果:
    1
    打印
  4. 匿名,调用在声明之前,示例代码:
    1
    2
    3
    4
    5
    // 匿名,调用在声明之前
    fn();
    var fn = function() {
    console.log('想不到吧');
    }
    运行结果:
    1
    Uncaught TypeError: fn is not a function

为什么会这样?
因为JavaScript解释器在运行JavaScript代码的时候分为两步:预解析和执行。

  1. 预解析:JavaScript代码执行之前,浏览器会默认把带有varfunction声明的变量在内存中进行提前声明或者定义,但不会进行赋值
  2. 执行:从上到下执行JavaScript语句。

第一个现象,num没有声明,所以报错。
第二个现象,num声明了,但是没有赋值,所以值为undefined。
第三个现象,函数已经声明和定义过了,所以没有报错。
第四个现象,采用了匿名函数,变量fn只进行了声明,没有赋值,所以报错了。

对象

关于对象的定义,我们不做太多讨论。可以参考《基于Java的后端开发入门:2.面向对象》

创建对象的三种方式

在JavaScript中,有三种方法创建对象。

  1. 利用字面量创建对象
  2. 利用new Object创建对象
  3. 利用构造函数创建对象

利用字面量创建对象

对象字面量:就是花括号{ }里面包含了表达这个具体事物(对象)的属性和方法。{ }里面采取键值对的形式表示

  • 键:相当于属性名。
  • 值:相当于属性值,可以是任意类型的值(数字类型、字符串类型、布尔类型,函数类型等)。

示例代码:

1
2
3
4
5
6
7
8
var star = {
name : 'k',
age : 18,
sex : '男',
sayHi : function(){
console.log('大家好啊');
}
};

对象里面的属性调用:

  1. 对象.属性名
  2. 对象['属性名'](方括号里面的属性必须加引号)

对象里面的方法调用:对象.方法名()

示例代码:

1
2
3
4
5
6
// 调用名字属性
console.log(star.name)
// 调用名字属性
console.log(star['name'])
// 调用 sayHi 方法,注意,一定不要忘记带后面的括号
star.sayHi();

运行结果:

1
2
3
k
k
大家好啊

特别的,我们来比较一下变量和属性的区别,以及函数和方法的区别。

变量和属性

  • 变量:单独声明赋值,单独存在。
  • 属性:对象里面的变量称为属性,不需要声明,用来描述该对象的特征。

函数和方法

  • 函数:单独存在的,通过"函数名()"的方式就可以调用。
  • 方法:对象里面的函数称为方法,方法不需要声明,使用"对象.方法名()"的方式就可以调用,方法用来描述该对象的行为和功能。

利用new Object创建对象

示例代码:

1
2
3
4
5
6
7
var andy = new Obect();
andy.name = 'k';
andy.age = 18;
andy.sex = '男';
andy.sayHi = function(){
alert('大家好啊');
}

利用构造函数创建对象

构造函数:是一种特殊的函数,主要用来初始化对象。

我们直接来看具体的例子。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.sayHi = function() {
alert('我的名字叫:' + this.name + ',年龄:' + this.age + ',性别:' + this.sex);
}
}
var bigbai = new Person('大白', 100, '男');
var smallbai = new Person('小白', 21, '男');
console.log(bigbai.name);
console.log(smallbai.name);

运行结果:

1
2
大白
小白

注意

  1. 构造函数约定首字母大写。
  2. 函数内的属性和方法前面需要添加this,表示当前对象的属性和方法。
  3. 构造函数中不需要return返回结果。
  4. 当我们创建对象的时候,必须用new来调用构造函数。

遍历对象属性

遍历对象的属性的方法是for-in,我们在上文就讨论过这种方法,当时用来遍历数组,而且当时我们说for-in更多的用途是用来遍历对象的属性。

for-in,其语法如下:

1
2
3
for (变量 in 对象名字) {
// 在此执行代码
}

示例代码:

1
2
3
4
5
6
for (let k in obj) {
// 这里的 k 是属性名
console.log(k);
// 这里的 obj[k] 是属性值
console.log(obj[k]);
}

内置对象

JavaScript中的对象分为3种:

  1. 自定义对象
  2. 内置对象
  3. 浏览器对象

关于自定义对象,我们在上文进行了简单的讨论。浏览器对象,我们会在下一章讨论。
这里主要讨论内置对象,即JavaScript语言自带的一些对象

常见的内置对象有

  1. Math
  2. Date
  3. Array
  4. String

我们分别讨论。

Math

Math概述

Math对象,跟数学相关的运算,而且不需要进行实例化,直接调用。

常见的有:

方法 作用
Math.PI 圆周率
Math.floor() 向下取整
Math.ceil() 向上取整
Math.round() 就近取整,注意-3.5,结果是-3
Math.abs() 绝对值
Math.max() 最大值
Math.min() 最小值

random()

random(),随机返回一个小数,其取值范围是[0,1)[0,1),而且在[0,1)[0,1)是均衡的。

随机数方法比较

上文我们讨论了,random(),取值范围是[0,1)[0,1)
那么,如果我们想获取[n,m)[n,m)范围呢?
很简单,比如[0,100)[0,100),那么乘以100就OK了。
如果是[0,100)[0,100)之间的整数呢?即[0,99][0,99]
那就用取整函数。
那么,这时候就有讲究了。

  • Math.ceil(Math.random()*100);,向上取整,那么取到0的概率极小。
  • Math.floor(Math.random()*100);,可均衡获取0到99的随机整数。
  • Math.round(Math.random()*100);,基本均衡获取0到100的随机整数,但获取最小值00和最大值100100的几率少一半,因为头尾的分布区间只有其他数字的一半。

Date

Date概述

Date,用来处理日期和时间。
和Math不一样,他是一个构造函数,需要实例化后才能使用。

获取当前时间

直接实例化,那么就是当前时间。

示例代码:

1
2
var now = new Date();
console.log(now);

运行结果:

1
Tue Nov 16 2021 13:54:07 GMT+0800 (中国标准时间)

获取指定时间

在构造函数中,指定时间,那么获取的就是指定时间。

示例代码:

1
2
3
4
console.log(new Date(2021, 9, 18, 05, 58, 00))
console.log(new Date('2021-5-1'))
console.log(new Date('2021/5/1'))
console.log(new Date('2021-10-18 05:58:00'))

运行结果:

1
2
3
4
Mon Oct 18 2021 05:58:00 GMT+0800 (中国标准时间)
Sat May 01 2021 00:00:00 GMT+0800 (中国标准时间)
Sat May 01 2021 00:00:00 GMT+0800 (中国标准时间)
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var now = new Date();
//获取当前年份(1900到现在)
console.log(now.getYear());
//获取完整的年份(4位)
console.log(now.getFullYear());
//获取当前月份(0-11,0代表1月)
console.log(now.getMonth());
//获取当前日(1-31)
console.log(now.getDate());
//获取当前星期X(0-6,0代表星期天)
console.log(now.getDay());
//获取当前时间(从1970.1.1开始的毫秒数)
console.log(now.getTime());
//获取当前小时数(0-23)
console.log(now.getHours());
//获取当前分钟数(0-59)
console.log(now.getMinutes());
//获取当前秒数(0-59)
console.log(now.getSeconds());
//获取当前毫秒数(0-999)
console.log(now.getMilliseconds());
//获取当前日期
console.log(now.toLocaleDateString());
//获取当前时间
var t=now.toLocaleTimeString()
console.log(t);
//获取日期与时间
console.log(t.toLocaleString());

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
121
2021
10
16
2
1637042815012
14
6
55
12
2021/11/16
下午2:06:55
下午2:06:55

有些资料会说getYear()获取的是两位数的年份,这些资料确实需要更新了。

获取时间戳

方法 备注
Date.parse(new Date()) 精确到秒
new Date().valueOf() 精确到毫秒
new Date().getTime() 精确毫秒

示例代码:

1
2
3
console.log(Date.parse(new Date()))
console.log(new Date().valueOf())
console.log(new Date().getTime())

运行结果:

1
2
3
1637043221000
1637043221290
1637043221290

获取时区

new Date().getTimezoneOffset(),返回的是标准时间减去我们的时间的分钟数。

示例代码:

1
console.log(new Date().getTimezoneOffset())

运行结果:

1
-480

解释说明:
我们处在东8区,即+8小时,标准时间减去我们时间的分钟数就是-480

Array

创建数组对象

创建数组对象,有两种方法:

  1. 字面量方式
  2. new Array()

判断是否为数组

方法 作用
instanceof 判断一个对象是否属于某种类型
Array.isArray() 判断一个对象是否为数组,HTML5中提供的方法

示例代码:

1
2
3
4
5
6
7
8
var arr = [1, 23];
var obj = {};
console.log(arr instanceof Array);
console.log(obj instanceof Array);
console.log(Array.isArray(arr));
console.log(Array.isArray(obj));
console.log(arr instanceof Object);
console.log(obj instanceof Object);

运行结果:

1
2
3
4
5
6
true
false
true
false
true
true

解释说明:arr既是数组,也是对象,毕竟万物皆对象。

添加删除数组元素的方法

方法 作用 返回
push() 在末尾添加一个或多个元素 新数组的长度
pop() 弹出数组最后一个元素 被弹出的元素
unshift() 在开头添加一个或多个元素 新数组的长度
shift() 弹出数组的第一个元素 被弹出的元素

示例代码:

1
2
3
4
5
6
7
8
9
10
11
var arr = [1500, 1200, 2000, 2100, 1800];
var newArr = [];

var temp = arr.pop()
while (temp){
if (temp < 2000) {
newArr.push(temp);
}
temp = arr.pop()
}
console.log(newArr);

运行结果:

1
(3) [1800, 1200, 1500]

数组排序

方法 作用 是否修改原数组
reverse() 颠倒数组中元素的顺序
sort() 对数组中的元素进行排序

那么,来吧!
示例代码:

1
[1, 64, 9, 6].sort()

运行结果:

1
(4) [1, 6, 64, 9]

好像不对啊,再来。

示例代码:

1
2
var arr = [1, 64, 9, 6];
arr.sort()

运行结果:

1
(4) [1, 6, 64, 9]

还是不对!
都是字母序啊。

示例代码:

1
2
3
4
5
6
7
8
var arr = [1, 64, 9, 6];
arr.sort(function(a, b) {
// 降序
return b - a;
// 升序
// return a - b;
});
console.log(arr);

运行结果:

1
[64, 9, 6, 1]

注意:默认字母序

数组索引方法

方法 作用 是否修改原数组
indexOf() 数组中查找给定元素的第一个索引 索引号,如果不存在返回-1
lastIndexOf() 在数组中的最后一个索引 索引号,如果不存在返回-1

所以,常用来判断数组中是否包含某个元素。

数组转换为字符串

方法 作用 返回
toString() 把数组转换成字符串,逗号分隔 字符串
join('分隔符') 把数组转换成字符串,指定的分隔符分隔 字符串

示例代码:

1
2
3
arr = [1,2,3]
console.log(arr.toString())
console.log(arr.join('#'))

运行结果:

1
2
1,2,3
1#2#3

更多方法

方法 作用 返回
concat() 拼接两个或多个数组 新数组(不影响原数组)
slice() 数组截取slice(begin,end) 被截取的新数组(不影响原数组)
splice() 数组删除splice(第一个开始,要删除的个数) 被删除的新数组(会影响原数组)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a1 = [1,2,3]
a2 = [4,5,6]
a3 = [7,8,9]

a = a1.concat(a2,a2)

console.log(a1)
console.log(a2)
console.log(a3)
console.log(a)

b = a.slice(2,6)
console.log(a)
console.log(b)

c = b.splice(1,2)
console.log(b)
console.log(c)

运行结果:

1
2
3
4
5
6
7
8
(3) [1, 2, 3]
(3) [4, 5, 6]
(3) [7, 8, 9]
(9) [1, 2, 3, 4, 5, 6, 4, 5, 6]
(9) [1, 2, 3, 4, 5, 6, 4, 5, 6]
(4) [3, 4, 5, 6]
(2) [3, 6]
(2) [4, 5]

关于数组合并,在ES6中有新方法。我们在讨论ES6新特性的时候讨论。

String

基本包装类型

这个类型,我们在上文进行过一些简单的讨论。是基本数据类型字符串的一个包装类。
在这里,我们会进行更详细的讨论。

示例代码:

1
2
var str = 'andy';
console.log(str.length);

运行结果:

1
4

有没有问题?
没什么问题啊,长度是4啊。
不是说这个。
上文我们讨论过,字符串属于基本数据类型,都已经是基本数据类型了,怎么还有"length"属性?

这是因为JavaScript会把基本数据类型包装为引用数据类型,即基本数据类型的包装类型。

具体执行了如下三步:

  1. 生成临时变量,把简单类型包装为复杂数据类型
    1
    var temp = new String('andy');
  2. 赋值给我们声明的字符变量
    1
    str = temp;
  3. 销毁临时变量
    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
2
var banKeyWord = '关键词一,关键词二,关键词三,关键词四'
console.log(banKeyWord.replace(',',','))

运行结果:

1
关键词一,关键词二,关键词三,关键词四

看起来没有完全替换啊,只替换了前面的?
因为确实不会完全替换,如果需要完全替换,可以考虑正则表达式。

示例代码:

1
2
var banKeyWord = '关键词一,关键词二,关键词三,关键词四'
console.log(banKeyWord.replace(/,/g,','))

运行结果:

1
关键词一,关键词二,关键词三,关键词四

split()方法

split(),切分字符串,它可以将字符串切分为数组。在切分完毕之后,返回的是一个新数组。

示例代码:

1
2
3
var str = 'a,b,c,d';
// 返回的是一个数组 [a, b, c, d]
console.log(str.split(','));

运行结果:

1
(4) ['a', 'b', 'c', 'd']

在ES6中,String还有一些新方法,我们在下文讨论ES6新特性的时候专门讨论。

更多方法

方法 作用
toUpperCase() 转换大写
toLowerCase() 转换小写

ES6的新特性

let

在ES6中,声明变量有了新的关键字

  1. let
  2. const

let声明的变量不存在预解析

在上文,我们讨论了预解析,有这么一个现象。

示例代码:

1
2
3
// 初始化在后面
console.log(num);
var num = 10;

运行结果:

1
undefined

原因我在上文讨论过。
因为在执行之前,还有一个预解析阶段,浏览器会默认把带有varfunction声明的变量在内存中进行提前声明或者定义,但不会进行赋值
所以打印的内容是undefined

但是如果我们用let声明变量的话,没这么复杂。直接不可用。

示例代码:

1
2
console.log(num);
let num = 10;

运行结果:

1
Uncaught SyntaxError: Identifier 'num' has already been declared

let声明的变量不存在预解析。

let声明的变量不允许重复声明

var声明的变量,还有这么一个特点,可以重复声明。
示例代码:

1
2
3
4
5
var num = 1
var num = 2
console.log(num)
var num = 'a'
console.log(num)

运行结果:

1
2
2
a

但是如果用let呢?
示例代码:

1
2
3
4
5
let num = 1
let num = 2
console.log(num)
let num = 'a'
console.log(num)

运行结果:

1
Uncaught SyntaxError: Identifier 'num' has already been declared

let声明的变量不允许重复声明。
(当然,是指在同一作用域内不允许重复声明。)

let声明的变量存在块级作用域

在上文,我们还提到了JavaScript中不存在块级作用域。

示例代码:

1
2
3
4
5
if(true){
var num = 123;
// console.log(123);
}
console.log(num);

运行结果:

1
123

但是,如果是用let声明的变量呢?

示例代码:

1
2
3
4
5
if(true){
let num = 123;
// console.log(123);
}
console.log(num);

运行结果:

1
Uncaught ReferenceError: num is not defined

let声明的变量存在块级作用域

那么,有哪些属于块级作用域呢?
除了一个{ }包裹,典型的就是for循环。

示例代码:

1
2
3
4
for (var i=0;i<3;i++){

}
console.log(i)

运行结果:

1
3

示例代码:

1
2
3
4
for (let i=0;i<3;i++){

}
console.log(i)

运行结果:

1
Uncaught ReferenceError: i is not defined

所以,for循环中,更建议用let来声明index。因为index变量只在该块内作用,避免对其他代码造成影响。

忘掉var吧

我的个人观点,let的出现,就是为了填补var的诸多坑,所以请忘掉var吧。

const

const,声明常量。
什么是常量,就不会被改变的量,不允许重新被赋值。

(这么说不完全准确,如果是引用数据类型的话,还是可能被改变)

示例代码:

1
2
3
4
const v = 1
console.log(v)
v = 2
console.log(v)

运行结果:

1
2
1
Uncaught TypeError: Assignment to constant variable.

示例代码:

1
2
3
const arr = [1,2,3,4]
arr[0] = 5
console.log(arr)

运行结果:

1
(4) [5, 2, 3, 4]

解释说明:
虽然是const,但是还是被改变了。原因我们会在本章最后,讨论内存模型的时候进行讨论。

String

在ES6中,String有了几个新方法。
我们依次讨论。

includes

includes,顾名思义,包含。

示例代码:

1
2
3
4
5
6
// 是否包含
console.log('123'.includes('1'))
// 从指定的index开始,是否包含
console.log('123'.includes('1',0))
console.log('123'.includes('1',1))
console.log('123'.includes('1',2))

运行结果:

1
2
3
4
true
true
false
false

startsWith

startsWith,顾名思义,是否以XXX开头

endWith

endWith,顾名思义,是否以XXX开头

函数

参数的默认值

在ES6中,参数的默认值有了非常优雅的方法。
示例代码:

1
2
3
4
5
function func(num = 999) {
console.log(num)
}
func(1)
func()

运行结果:

1
2
1
999

剩余参数

示例代码:

1
2
3
4
5
function func(a,...rest) {
console.log(a)
console.log(rest)
}
func(1,2,3,4,5)

运行结果:

1
2
1
(4) [2, 3, 4, 5]

注意!...前面有,,但是后面没有。

扩展运算符

剩余参数的作用是把多个参数合并成一个数组。
扩展运算符作用相反,把数组拆成多个参数。

示例代码:

1
2
3
4
5
function func(a,b,c,d,e) {
console.log(a + b + c + d + e)
}
var arr = [1,2,3,4,5]
func(...arr)

运行结果:

1
15

合并数组

扩展运算符还有一个用途,合并数组。

示例代码:

1
2
3
4
var a1 = [1,2,3]
var a2 = [4,5,6]
var a3 = [...a1,...a2]
console.log(a3)

运行结果:

1
(6) [1, 2, 3, 4, 5, 6]

ES6还有一些新特性,比如:

  • 变量的解构赋值(也被谬传为"结构赋值"),我们不讨论,因为降低了代码的可读性。
  • 箭头函数,我们不讨论,这个像Java中的"函数式编程",我不喜欢拿JavaScript当MATLAB用。

内存分配

最后一个话题,JavaScript的内存分配。
实际上,有了Java的基础,这部分很容易讨论。

堆和栈

首先!在JavaScript中没有堆栈的概念。但是,通过堆栈的方式,可以更容易理解。

堆和栈

基本数据类型的变量,存放到栈中。
基本数据类型的内存分配

引用数据类型的遍历,栈中存放的是地址,真正的对象实例存放在堆中。
简单数据类型的内存分配

这就是能解释为什么const修饰的引用数据类型的变量,可以被改变了。因为const修饰之后,只是栈内存的地址不能被修改,但是堆内存依旧可以被修改。

基本数据类型传参

函数的形参也可以看做是一个变量,当我们把一个值类型变量作为参数传给函数的形参时,其实是把变量在栈空间里的值复制了一份给形参,那么在方法内部对形参做任何修改,都不会影响到的外部变量。

示例代码:

1
2
3
4
5
6
7
function fn(a) {
a++;
console.log(a);
}
var x = 10;
fn(x);
console.log(x);

运行结果:

1
2
11
10

引用数据类型传参

函数的形参也可以看做是一个变量,当我们把引用类型变量传给形参时,其实是把变量在栈空间里保存的堆地址复制给了形参,形参和实参其实保存的是同一个堆地址,所以操作的是同一个对象。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Person(name) {
this.name = name;
}
function f1(x) {
// x = p
console.log(x.name);
x.name = "张学友";
console.log(x.name);
}
var p = new Person("刘德华");
console.log(p.name);
f1(p);
console.log(p.name);

运行结果:

1
2
3
4
刘德华
刘德华
张学友
张学友
文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/12001
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

评论区