编程语言的概念
编程
编程:让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程。
计算机程序:就是计算机所执行的一系列的指令集合,而程序全部都是用我们所掌握的语言来编写的,所以人们如果要控制计算机,则需要通过计算机语言向计算机发出命令。
计算机语言
计算机语言:人与计算机之间通讯的语言。它是人与计算机之间传递信息的媒介,用来控制计算机的一系列指令。
计算机语言的种类非常的多,总的来说可以分成三大类:机器语言、汇编语言和高级语言。
计算机最终所执行的都是机器语言,它是由“0”和“1”组成的二进制数,二进制是计算机语言的基础。
编程语言
通过类似于人类语言的 ”语言”来控制计算机,让计算机为我们做事情,这样的语言就叫做编程语言。不同的编程语言,有不同的语法,必须遵守。
比如:C 语言、C++、Java、C#、PHP、JavaScript、Python、Objective-C、Swift、Go 语言等。
编程语言的分类
翻译器
计算机不能直接理解任何除机器语言以外的语言,所以必须要把程序员所编写的高级语言翻译成机器语言,计算机才能执行程序。为此,我们需要一个翻译器。程序语言翻译成机器语言的工具,被称为翻译器。
由此可见,所谓的“翻译”,指的是将人类所编写的源代码转换(翻译)为机器能够执行的指令,这也被称为二进制化。
翻译器翻译的方式有两种:一种是编译,另一种是解释。两种方式之间的区别在于翻译的时机不同。
- 编译器:在代码执行之前,事前把所有的代码一次性翻译好,生成中间代码文件,然后整体执行。
- 解释器:边翻译,边执行(在代码执行时进行及时翻译,并立即执行)。当编译器以解释的方式运行时,也称之为解释器。
1、编译型语言
- 定义:需要事先通过编译器把所有的代码一次性翻译(编译/转换)好,然后整体执行。比如 exe 文件。
- 优点:执行效率高,运行更快。
- 不足:移植性不好,不跨平台;编译之后如果需要修改就需要整个模块重新编译。
- 编译型语言举例:C、C++
(上方图片的来源:编译型语言 )
2、解释型语言
- 定义:在运行过程中(runtime)通过解释器边翻译边执行(也就是逐行翻译:翻译一行,执行一行),不需要事先一次性翻译。
- 优点:移植性好,跨平台。
- 缺点:运行速度不如编译型语言。
- 解释型语言举例:JavaScript、PHP、Python。
Java 语言
Java 语言是属于半编译半解释型语言。翻译过程:
(1)编译:.java
代码文件先通过 javac 命令编译成.class
文件。
(2)执行:.class
文件再通过 jvm 虚拟机,解释执行。有了 jvm 的存在,让 java 跨平台了。
JavaScript 历史和发展
Web 前端有三层:
- HTML:从语义的角度,描述页面结构
- CSS:从审美的角度,描述样式(美化页面)
- JavaScript(简称 JS):从交互的角度,描述行为(实现业务逻辑和页面控制)
JavaScript 的历史
- JavaScript 诞生于1995 年,是由网景公司(Netscape)的员工 Brendan Eich(兰登 • 艾奇,1961 年~)发明,最初命名为 LiveScript。1995 年 12 月与 SUN 公司合作,因市场宣传需要,为了蹭 Java 的热度,改名为 JavaScript。
- JavaScript 是 Sun 注册并授权给 Netscape 使用的商标。后来 Sun 公司 被 Oracle 收购,JavaScript 版权归 Oracle 所有。
- 1996 年,微软为了抢占市场,推出了
JScript
在 IE3.0 中使用。 - 1996 年 11 月网景公司将 JS 提交给 ECMA(国际标准化组织)成为国际标准,用于对抗微软。
JavaScript 是世界上用的最多的脚本语言。
Java 和 JavaScript 的关系
好比:雷锋和雷峰塔的关系、老婆和老婆饼的关系、北大和北大青鸟的关系。
推荐阅读链接:
JavaScript 的发展:蒸蒸日上
- 2003 年之前,JavaScript 被认为“牛皮鲜”,用来制作页面上的广告,弹窗、漂浮的广告。什么东西让人烦,什么东西就是 JavaScript 开发的。所以很多浏览器就推出了屏蔽广告功能。
- 2004 年,JavaScript 命运开始改变。那一年,谷歌公司开始带头使用 Ajax 技术,Ajax 技术就是 JavaScript 的一个应用。并且,那时候人们逐渐开始提升用户体验了。Ajax 有一些应用场景。比如,当我们在搜索引擎框搜文字时,输入框下方的智能提示,可以通过 Ajax 实现。比如,当我们注册网易邮箱时,能够及时发现用户名是否被占用,而不用跳到另外一个页面。从 2005 年开始,几乎整个 B/S 开发界都在热情地追捧 Ajax。
- 2007 年乔布斯发布了第一款 iPhone,这一年开始,更多的用户使用移动设备上网。这一年,互联网开始标准化,按照 W3C 规则三层分离,JavaScript 越来越被重视。JavaScript 在移动页面中,也是不可或缺的。
- 2010 年,人们更加了解HTML5 技术,HTML5 推出了一个东西叫做 Canvas(画布),工程师可以在 Canvas 上进行游戏制作,利用的就是 JavaScript。
- 2011 年,Node.js 诞生,使 JavaScript 能够开发服务器端的程序。
如今,WebApp已经非常流行,也就是用网页技术开发手机应用。手机系统有 iOS、安卓。如果一个APP都用 web 技术,用 html+css+javascript 这一套技术就可以开发多种终端的页面。也易于迭代(网页如果改变,则所有的终端都会生效)。
虽然目前 WebApp(Web 应用)在功能和性能上的体验远不如 Native App(原生应用),但是“在原生 App 中内嵌一些 H5 页面”已经是一种趋势。
JavaScript 介绍
JavaScript 入门易学性
- JavaScript 对初学者比较友好。可以使用任何文本编辑工具编写,只需要浏览器就可以执行程序。
- JavaScript 是有界面效果的(相比之下,C 语言只有白底黑字)。
- JavaScript 的入门较简单(进阶不易)。比如,JS 是弱变量类型的语言,变量只需要用 var/let/const 来声明。而 Java 中变量的声明,要根据变量的类型来定义。
Java 中需要这样定义变量:
int a;
float a;
double a;
String a;
boolean a;
而 JS 中,只需要用一种方式来定义:
// ES5 写法
var a;
// ES6 写法
const a;
let a;
JavaScript 既是前端语言,又是后端语言
当 JavaScript 运行在用户的终端网页,而不是运行在服务器上的时候,我们称之为“前端语言”。前端语言是服务于页面的显示和交互,不能直接操作数据库。
后端语言是运行在服务器上的,比如 Java、C++、PHP 等等,这些语言都能够操作数据库(对数据库进行“增删改查”),并在后台执行各种任务。
另外,Node.js 是用 JavaScript 开发的,我们也可以用 Node.js 技术进行服务器端编程。
JavaScript 的组成
JavaScript 基础分为三个部分:
- ECMAScript:JavaScript 的语法标准。包括变量、表达式、运算符、函数、if 语句、for 语句等。
- DOM:Document Object Model(文档对象模型),JS 操作页面上的元素(标签)的 API。比如让盒子移动、变色、改变大小、轮播图等等。
- BOM:Browser Object Model(浏览器对象模型),JS 操作浏览器部分功能的 API。通过 BOM 可以操作浏览器窗口,比如弹框、控制浏览器跳转、获取浏览器分辨率等等。
通俗理解就是:ECMAScript 是 JS 的语法;DOM 和 BOM 是浏览器运行环境为 JS 提供的 API。
JavaScript 的特点
特点 1:解释型语言
JavaScript 简称 JS,是前端开发的一门脚本语言(解释型语言)。
解释型语言的意思是:程序执行之前,不需要事先被翻译为机器码;而是在运行时,边翻译边执行(翻译一行,执行一行)。关于解释型语言的详细介绍,上一篇文章有介绍。
为什么 JS 是解释型语言呢?这和浏览器的工作原理有关。浏览器中有一个专门的“JS 解析器”可以让 JS 边解析边执行。
由于少了事先编译这一步骤,所以解释型语言开发起来尤为方便,但是解释型语言运行较慢也是它的劣势。不过解释型语言中使用了 JIT 技术,使得运行速度得以改善。
特点 2:单线程
JavaScript 是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作Dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。
设想一下:如果JavaScript 被设计了多线程,如果有一个线程要修改一个dom元素,另一个线程要删除这个dom元素,此时浏览器就会一脸茫然,不知所措。所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
浏览器是多进程的,不同类型的标签页会开启一个新的进程,相同类型的标签页会合并到一个进程
特点 3:ECMAScript 标准
ECMAScript 是一种由 ECMA 国际(前身为欧洲计算机制造商协会,英文名称是 European Computer Manufacturers Association)制定和发布的脚本语言规范。
JavaScript 是由公司开发而成的,问题是不便于其他的公司拓展和使用。所以欧洲的这个 ECMA 的组织,牵头制定 JavaScript 的标准,取名为 ECMAScript。
简单来说,ECMAScript 不是一门语言,而是一个标准。ECMAScript 规定了 JS 的编程语法和基础核心知识,是所有浏览器厂商共同遵守的一套 JS 语法工业标准。
ECMAScript 在 2015 年 6 月,发布了 ECMAScript 6 版本(ES6),语言的能力更强,包含了很多新特性),但也要考虑它的浏览器兼容性问题。
ECMA 赋予了 JavaScript 新的能力和活力。
JavaScript:hello world
引入 JS 代码,有哪几种方式?有三种方式:(和 CSS 的引入方式类似)
- 行内式:写在标签内部。
- 内嵌式(内联式):写在 head 标签中。
- 外链式:引入外部 JS 文件。
方式 1:行内式
代码举例:
<input type="button" value="点我点我" onclick="alert('前端修炼 Hello 方式1')" />
完整的可执行代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<input type="button" value="点我点我" onclick="alert('前端修炼 Hello 方式1')" />
</body>
</html>
分析:
- 可以将单行或少量 JS 代码写在 HTML 标签的事件属性中(以 on 开头的属性),比如放在上面的
onclick
点击事件中。 - 这种书写方式,不推荐使用,原因是:可读性差,尤其是需要编写大量 JS 代码时,很难维护;引号多层嵌套时,也容易出错。
- 关于代码中的「引号」,在 HTML 标签中,我们推荐使用双引号,JS 中我们推荐使用单引号。
方式 2、内嵌式(内联式)
我们可以在 HTML 页面的 <body>
标签里放入<script type=”text/javascript”></script>
标签对,并在<script>
里书写 JavaScript 代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<script type="text/javascript">
// 在这里写 js 代码
alert('前端修炼 hello 方式2');
console.log('qianduanxiulian hello 方式2');
</script>
</body>
</html>
分析:
- text 表示纯文本,因为 JavaScript 代码本身就是纯文本。
- 可以将多行 JS 代码写到
<script>
标签中。 - 内嵌式 JS 是学习时常用的方式。
方式 3:外链式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<!-- 外链式:引入外部的 js 文件:这个 utils.js 文件与当前的 html 文件,处于同一级目录 -->
<script src="utils.js"></script>
</body>
</html>
分析:
- 上面这段代码,依然是放到 body 标签里,可以和内嵌的 JS 代码并列。
- 上方代码的 script 标签已经引入了外部 JS 文件,所以这个标签里面,不可以再写 JS 代码。
总结:
我们在实战开发中,基本都是采用方式 3,因为将 html 文件和 js 文件分开的方式,有利于代码的结构化和复用,符合高内聚、低耦合的思想。很少会有人把一大堆 JS 代码塞到 html 文件里。
window.onload:先加载,最后执行
上面的三种方式,有个共同的地方是:JS 代码都是写在</body>
中的,准确来说,是在页面标签元素的后面,在 body结束标签的前面。
为什么一般是按这样的顺序来写呢?这是因为:浏览器默认会从上至下解析网页(这句话很重要)。当你需要通过 JS 来操作界面上的标签元素的时候,假如将 JS 代码、<script>
标签写到<head>
标签中,或者写在页面标签元素的前面,那么这样的 JS 是无效的,因为标签元素在此时都还没来得及加载,自然无法操作这个元素。
当然,我们可以根据具体需求,将 window.onload 写在 <head>
标签中,或者写在<script>
标签中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
<script type="text/javascript">
window.onload = function(){
document.getElementById("s").style.color="green";
};
</script>
</head>
<body>
<span id="s">要坚强,当然是选择原谅他啊!!!</span>
</body>
</html>
JavaScript 输出语句
弹窗:alert()语句
我们要学习的第一个语句,就是 alert 语句。
代码举例如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
alert('前端修炼手册');
</script>
</body>
</html>
alert(英文翻译为“警报”)的用途:弹出“警告框”。它会在弹窗中显示一条信息,并等待用户按下 “OK”。alert("")
弹窗的效果如下:
弹窗:confirm()语句(含确认/取消)
代码举例如下:
var result = confirm('你在学习前端修炼手册吗?');
console.log(result);
代码运行后,页面上会显示一个弹窗。弹窗上有“确认”和“取消”两个按钮,点击“确定”返回 true
,点击“取消”返回 false
。
效果如下:
弹出输入框:prompt()语句
prompt()
就是专门弹出能够让用户输入的对话框。用得少,测试的时候偶尔会用。
JS 代码如下:
var a = prompt('请随便输入点什么东西吧');
console.log(a);
上方代码中,用户输入的内容,将被传递到变量 a 里面,并在控制台打印出来。
效果如下:
alert()和 prompt()的区别:
- alert() 语句中可以输出数字和字符串,如果要输出字符串,则必须用引号括起来;prompt()语句中,用户不管输入什么内容,都是字符串。
- prompt() 会返回用户输入的内容。我们可以用一个变量,来接收用户输入的内容。
网页内容区域输出:document.write()语句
代码举例:
document.write('前端修炼手册');
页面效果:
控制台输出:console.log() 打印日志
console.log()
表示在控制台中输出。console 表示“控制台”,log 表示“输出”。括号里可以写字符串、数字、变量。
控制台是程序员调试程序的地方,比如使用 console 语句打印日志,测试程序是否运行正常。
在 Chrome 浏览器中,按 F12 即可打开控制台,选择「console」栏,即可看到打印的内容。console.log("")
效果如下:
console 语句可以设置不同的打印等级:
console.log('前端修炼手册1'); //普通打印
console.warn('前端修炼手册2'); //警告打印
console.error('前端修炼手册3'); //错误打印
效果如下:
上图中,不同的打印等级,区别不大,只是颜色背景上的区别,方便肉眼区分、过滤信息。
做前端开发时需要经常使用控制台做调试,我们甚至可以直接在控制台输入 JS 语句,然后打印执行结果。
最后总结
- alert() 主要用来显示消息给用户
- console.log() 用来给程序员做调试用
常量(字面量):数字和字符串
常量也称之为“字面量”,是固定值,不可改变。看见什么,它就是什么。
常量有下面这几种:
- 数字常量(数值常量)
- 字符串常量
- 布尔常量
- 自定义常量
数字常量
数字常量非常简单,直接写数字就行,不需要任何其他的符号。既可以是整数,也可以是浮点数。
例如:
// 不需要加引号
alert(996); // 996是整数
alert(3.14); // 3.14是浮点数(即带了小数)
字符串常量
字符串常量就是用单引号或双引号括起来的内容。可以是单词、句子等,一定要加引号。在JS中,只要是单引号或双引号括起来的内容,都是字符串常量。
例如:
console.log('996');
console.log("前端修炼手册");
布尔常量
布尔常量就是表达真或者假,在JS中用 true 和 false 来表达。
布尔字面量举例:
if (true) {
console.log('如果为真,就走这里面的代码);
}
自定义常量:const
自定义常量是ES6中新增的语法。它的语法格式是这样的:
const 常量名称 = 常量取值;
举例:
const name = '前端修炼'; // 定义常量name,它的取值是 前端修炼
// name = '许嵩'; // 既然 name 是常量,所以这一行是错误的写法,因为 name 无法被修改为其他的值
console.log(name); // 打印结果:千古壹号
开发技巧:用变量定义常量
我们一般不会直接使用常量,否则会导致代码冗余、不易维护。如果多个地方要用到同一个常量,那就建议事先定义一个变量,用来保存这个常量;然后在需要的地方去引用这个变量就行了。另外,当我们学习了ES6中的 const 之后,还可以使用自定义常量达到目的。
变量的概念
变量表示可以被修改的数据。我们通过「变量名」获取数据,甚至修改数据。
变量还可以用来保存常量。
本质:变量是程序在内存中申请的一块用来存放数据的空间。打个比方,超市里的储物格就是变量, 在不同的时间段里, 储物格中存储的数据可以不一样。
变量的定义和赋值
定义变量是在告诉浏览器,我们需要一块内存空间,相当于生成超市里的储物格。
给变量赋值就是往相当于往储物格里塞东西,可能今天上午塞的是面包🍞,下午就换成了蛋糕🍰。
变量的定义/声明(ES5)
在 ES6 语法之前,统一使用var
关键字来声明一个变量。比如:
var name; // 定义一个名为 name 的变量。name是变量名。
var 是英语“variable”变量的缩写。后面要加一个空格,空格后面的东西就是“变量名”。
PS:在 JavaScript 中,永远都是用 var 来定义变量(在 ES6 之前),这和 C、Java 等语言不同。
变量的定义/声明(ES6)
在 ES6 语法及之后的版本里,可以使用 const
、let
关键字来定义一个变量
const name; // 定义一个常量
let age; // 定义一个变量
如果你想定义一个常量,就用 const;如果你想定义一个变量,就用 let。
变量的赋值
变量赋值举例:
name = '前端修炼';
综合起来,变量的定义、赋值、取值,举例如下:
// 定义:声明一个变量
var num;
// 赋值:往变量中存储数据
num = 996;
// 取值:从变量中获取存储的数据
console.log(num);
合并写法
变量的定义和赋值,还可以合并写在一起,是实战中常用的写法。举例如下:
var a = 100; // ES5语法
console.log(a);
const b = hello; // ES6 语法
let c = world; // ES6 语法
c = qianguyihao; // 修改 变量 C 的值
定义一个变量并赋值, 我们称之为变量的初始化。
- 定义变量:var 就是一个关键字,用来定义变量。所谓关键字,就是有特殊功能的单词。
- 变量赋值:等号表示赋值,将等号右边的值,赋给左边的变量。
- 变量名:我们可以给变量起个名字。
变量的初始化【重要】
第一次给变量赋值,称之为“变量的初始化”,这个概念非常重要。第二次给这个变量赋值(也就是修改这个变量的值)就不叫初始化了。
一个变量如果没有进行初始化(只声明,不赋值),那么这个变量中存储的值是undefined
,这个知识点必须知道。
变量的初始化,可以有两种形式,如下。
形式1:先定义,在赋值。举例:
var name;
name = 'qianduanxiulian';
形式2:在定义的同时进行初始化。也就是上一小段讲的“合并写法”。举例:
var name = 'qianduanxiulian';
变量定义和赋值的补充
修改变量的值
一个变量被重新复赋值后,它原有的值就会被覆盖,变量值将以最后一次赋的值为准。
举例:
var a = 100;
a = 110;
console.log(a); // 打印结果:110。因为 110 覆盖了 100
同时定义多个变量
1、同时定义多个变量时,只需要写一个 var, 多个变量名之间用英文逗号隔开。举例:
// 同时定义多个变量
var num1, num2;
2、定义多个变量的同时,分别进行初始化。举例:
// 定义多个变量的同时,进行初始化
var num1 = 10, num2 = 20;
如果多个变量初始化的初始化值都是一样的,则可以这样简写:
var num1, num2;
num1 = num2 = 10; // 重点在这一行
console.log(num1); // 10
console.log(num2); // 10
上面的写法和下面的写法是有区别的:(注意看打印结果)
var num1, num2 = 10;
console.log(num1); // undefined
console.log(num2); // 10
变量之间可以相互赋值
var num1, num2;
num1 = 10;
num2 = num1; // 把 num1 的值拷贝一份,赋值给 num2
console.log(num2); // 10
变量如果重复定义
在ES5中,如果用 var 定义了同名变量,那么,后定义的变量,会覆盖先定义的变量。举例:
var name = '前端';
var name = '修炼手册'; // 这里会重新定义一个新的变量 name
console.log(name); // 修炼手册
变量声明和赋值的特殊情况
变量建议先声明,再使用;否则可能会产生意想不到的结果。具体如下。
写法 1、先声明,再赋值:(正常)
var a;
a = 100;
console.log(a); // 打印结果:100
写法 2、不声明,直接赋值:(正常)
a = 100;
console.log(a); // 打印结果:100
写法 3、只声明,不赋值:(注意,打印 undefined)
var a;
console.log(a); // 打印结果:undefined
写法 4、不声明,不赋值,直接使用:(会报错)
console.log(a); // 会报错
控制台会报错:
补充:写法 1 和写法 2 虽然都正常,但这两种写法是有区别的,等以后学习了「变量提升」的概念就明白了。大多数情况下,我们都是采用的写法 1。
举例
举例:交换两个变量的值
代码实现:
var a1 = 100;
var a2 = 200;
var temp;
temp = a1;
a1 = a2;
a2 = temp;
变量的命名规则(重要)
JS是大小敏感的语言。也就是说 A 和 a 是两个变量。大写字母是可以使用的,比如:
var A = 250; //变量1
var a = 888; //变量2
我们来整理一下变量的命名规则,非常重要。
必须遵守:
- 只能由字母(A-Z、a-z)、数字(0-9)、下划线(_)、美元符( $ )组成。
- 不能以数字开头。必须以字母(A-Z、a-z)、下划线(_)或者美元符( $ )开头。变量名中不允许出现空格。尤其注意,变量名中不能出现中划线
-
,很多人写了多年代码都不知道这一点,让人大跌眼镜。 - 严格区分大小写(JS 是区分大小写的语言)。
- 不能使用 JS 语言中保留的「关键字」和「保留字」作为变量名。
- 变量名长度不能超过 255 个字符。
- 汉语可以作为变量名。但是不建议使用,因为 low。
建议遵守:
- 命名要有可读性,方便顾名思义。
- 建议用驼峰命名法。比如 getElementById、getUserInfo、aaaOrBbbAndCcc
补充:
1、不能以数字开头,是为了把数字和字母区分开。
2、JS底层保存标识符的时候,是采用的 Unicode 编码。所以理论上讲,在遵守命名规则的前提下,utf-8中包含的所有内容都可以作为标识符。
具体可以参考《技术规范手册》
标识符
标识符:在 JS 中所有的可以由我们自主命名的都可以称之为标识符。包括:变量名、函数名、属性名、参数名都是属于标识符。
通俗来讲,标识符就是我们写代码时为某些东西起的名字。类似于人出生的时候,起个人名。
标识符的命名规则和变量的命令规则是一样的。关于变量的命名规则,详见上一段。
标识符不能使用语言中保留的关键字及保留字。
//千万不要这么用
var 锄禾日当午 = 789;
console.log('锄禾日当午');//789,能用,但不规范
function 汗滴禾下土(){
console.log('锄禾日当午');// 能用,但不规范
}
关键字
关键字:被JS赋予了特殊含义的单词。也就是说,关键字是 JS 本身已经使用了的单词,我们不能再用它们充当变量名、函数名等标识符。关键字在开发工具中会显示特殊的颜色。
JS 中的关键字如下:
if、else、switch、break、case、default、for、in、do、while、
var、let、const、void、function、continue、return、
try、catch、finally、throw、debugger、
this、typeof、instanceof、delete、with、
export、new、class、extends、super、with、yield、import、static、
true、false、null、undefined、NaN
保留字
保留字:实际上就是预留的“关键字”。它们虽然现在还不是关键字,但是未来可能会成为关键字。同样不能用它们当充当变量名、函数名等标识符。
JS 中的保留字如下:
enum、await
abstract、boolean、byte、char、double、final、float、goto、int、long、native、short、synchronized、transient、volatile、
arguments eval Infinity
# 以下关键字只在严格模式中被当成保留字,在ES6中是属于关键字
implements、interface、package、private、protected、public
当你在网上搜“JS保留字”的时候,你会找到很多版本,每个版本都不一样,各有各的说法。如果有不一样的地方,请以我写的为准。
其实,以谁的版本作为标准并不重要,因为有些单词到底是关键字还是保留字,并没有严格的界限。JS 关于保留字的规则非常复杂,上放列表中的一些单词在特殊情况下其实是可以使用的。
我们只需要记住一点:上面提到的所有关键字和保留字,我们都不要用它们作为变量名或者参数名。不要尝试这些奇怪的做法。
如果你还想了解更多,可以看这几个参考链接:
- 知乎问答:undefined是保留字吗?
- 书籍《JavaScript 悟道》的第一章:保留字部分。
数据类型
数据分类
数据分为:静态数据、动态数据。
静态数据
静态数据是指一些永久性的数据。一般是以文件的形式存储在硬盘上,比如文档、照片、视频等文件。
电脑关闭后,静态数据仍然还在。只要不主动删掉数据或者硬盘没损坏,这些数据就一直都在。
动态数据
动态数据是在程序运行过程中,动态产生的临时数据,这些数据可能随时发生变化。一般存储在内存中。
电脑关闭后,这些数据会被清除。
为何不把应用程序的动态数据加载到硬盘中执行呢?这主要是因为,内存的访问速度比硬盘快无数倍。
变量的数据类型
为什么需要数据类型
在计算机中,不同的数据所需占用的存储空间不同,为了充分利用存储空间,于是定义了不同的数据类型。而且,不同的数据类型,寓意也不同。
我们都知道,无论这个变量是字符串类型,还是数字类型,我们都可以直接用 var
去定义它。比如:
var a = 'hello word';
var b = 123;
为什么可以这样做呢?这是因为:JavaScript 是一种「弱类型语言」,或者说是一种「动态语言」,这意味着不需要提前声明变量的类型,在程序运行过程中,类型会自动被确定。
JS 的变量数据类型,是在程序运行的过程中,根据等号右边的值来确定的。而且,变量的数据类型是可以变化的。比如说:
var name = 'qianduanxiulian';
name = 123; // 强制将变量 name 修改为 数字类型
八种数据类型
- 基本数据类型(值类型):String 字符串、Number 数值、Boolean 布尔值、Null 空值、Undefined 未定义、Symbol、BigInt 大型数值。
- 引用数据类型(引用类型):Object 对象。
注意:内置对象 Function、Array、Date、RegExp、Error 等都是属于 Object 类型。也就是说,除了那七种基本数据类型之外,其他的,都称之为 Object 类型。
BigInt 和 Symbol 是ES6中新增的类型:
- Symbol 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
- BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。
数据类型之间最大的区别:
- 基本数据类型:参数赋值的时候,传数值。
- 引用数据类型:参数赋值的时候,传地址。
一个经典的例子
基本数据类型举例:
var a = 23;
var b = a;
a++;
console.log(a); // 打印结果:24
console.log(b); // 打印结果:23
上面的代码中:a 和 b 都是基本数据类型,让 b 等于 a,然后改变 a 的值之后,发现 b 的值并没有被改变。
但是在引用数据类型中,就不同了,我们来看一看。
引用数据类型举例:
var obj1 = new Object();
obj1.name = 'smyh';
// 让 obj2 等于 obj1
var obj2 = obj1;
// 修改 obj1 的 name 属性
obj1.name = 'vae';
console.log(obj1.name); // 打印结果:vae
console.log(obj2.name); // 打印结果:vae
上面的代码中:obj1 和 obj2 都是引用数据类型,让 obj2 等于 obj1,然后修改 obj1.name 的值之后,发现 obj2.name 的值也发生了改变。
从上面的例子中,可以反映出,基本数据类型和引用数据类型是有区别的。
栈内存和堆内存
我们首先记住一句话:JS 中,所有的变量都是保存在栈内存中的。
然后来看看下面的区别。
- 基本数据类型:
- 基本数据类型的值,直接保存在栈内存中。值与值之间是独立存在,修改一个变量不会影响其他的变量。
- 引用数据类型:
- 对象是保存到堆内存中的。每创建一个新的对象,就会在堆内存中开辟出一个新的空间;而变量保存了对象的内存地址(对象的引用),保存在栈内存当中。如果两个变量保存了同一个对象的引用,当一个通过一个变量修改属性时,另一个也会受到影响。
基本数据类型:字符串 String
String 字符串
语法
字符串型可以是引号中的任意文本,其语法为:双引号 ""
或者单引号 ''
。
来看个示例。下面的这些,都是字符串:
var a = 'abcde';
var b = '前端修炼';
var c = '123123';
var d = '哈哈哈哈哈';
var e = ''; //空字符串
var f = haha; // 没使用引号,到这里会直接报错。因为会被认为是js代码,但是之前并没有定义 haha。
console.log(typeof a);
console.log(typeof b);
console.log(typeof c);
console.log(typeof d);
console.log(typeof e);
控制台输出如下:
string
string
string
string
string
引号的注意事项
1、单引号和双引号不能混用。比如下面这样写是不可以的:
var str = 'hello"; // 报错:Uncaught SyntaxError: Invalid or unexpected token
2、同类引号不能嵌套:双引号里不能再放双引号,单引号里不能再放单引号。
3、单引号里可以嵌套双引号;双引号里可以嵌套单引号。
转义字符
在字符串中我们可以使用\
作为转义字符,当表示一些特殊符号时可以使用\
进行转义。
\"
表示"
双引号\'
表示'
单引号\\
表示\
\r
表示回车\n
表示换行。n 的意思是 newline。\t
表示缩进。t 的意思是 tab。\b
表示空格。b 的意思是 blank。
举例:
var str1 = '我说:"今天\t天气真不错!"';
var str2 = '\\\\\\';
console.log(str1);
console.log(str2);
上方代码的打印结果:
我说:"今天 天气真不错!"
\\\
获取字符串的长度
字符串是由若干个字符组成的,这些字符的数量就是字符串的长度。我们可以通过字符串的 length 属性可以获取整个字符串的长度。
代码举例:
var str1 = '前端修炼';
var str2 = '前端修炼,永不止步!';
var str3 = 'qianduanxiulian';
var str4 = 'qianduanxiulian, keep moving!';
console.log(str1.length); // 4
console.log(str2.length); // 10
console.log(str3.length); // 15
console.log(str4.length); // 29
由此可见,字符串的 length 属性,在判断字符串的长度时,会认为:
- 一个中文算一个字符,一个英文算一个字符
- 一个标点符号(包括中文标点、英文标点)算一个字符
- 一个空格算一个字符
字符串拼接
多个字符串之间可以使用加号 +
进行拼接。
拼接语法:
字符串 + 任意数据类型 = 拼接之后的新字符串;
拼接规则:拼接前,会把与字符串相加的这个数据类型转成字符串,然后再拼接成一个新的字符串。
代码举例:(字符串与六大数据类型相加)
var str1 = '前端修炼' + '永不止步';
var str2 = '前端修炼' + 666;
var str3 = '前端修炼' + true;
var str4 = '前端修炼' + null;
var str5 = '前端修炼' + undefined;
var obj = { name: '前端修炼', age: 28 };
var str6 = '前端修炼' + obj;
console.log(str1);
console.log(str2);
console.log(str3);
console.log(str4);
console.log(str5);
console.log(str6);
打印结果:
前端修炼永不止步
前端修炼666
前端修炼true
前端修炼null
前端修炼undefined
前端修炼[object Object]
字符串的不可变性
字符串里面的值不可被改变。虽然看上去可以改变内容,但其实是地址变了,内存中新开辟了一个内存空间。
代码举例:
var str = 'hello';
str = 'qianduanxiulian';
比如上面的代码,当重新给变量 str 赋值时,常量hello
不会被修改,依然保存在内存中;str 会改为指向qianduanxiulian
。
模板字符串(模板字面量)
ES6 中引入了模板字符串,让我们省去了字符串拼接的烦恼。下面一起来看看它的特性。
在模板字符串中插入变量
以前,让字符串进行拼接的时候,是这样做的:(传统写法的字符串拼接)
var name = 'coder';
var age = '26';
console.log('name:' + name + ',age:' + age); //传统写法
这种写法,比较繁琐,而且容易出错。
现在,有了 ES6 语法,字符串拼接可以这样写:
var name = '前端开发';
var age = '26';
console.log('我是' + name + ',age:' + age); //传统写法
console.log(`我是${name},age:${age}`); //ES6 写法。注意语法格式
注意,上方代码中,倒数第二行用的符号是单引号,最后一行用的符号是反引号(在 tab 键的上方)。
在模板字符串中插入表达式
以前,在字符串中插入表达式的写法必须是这样的:
const a = 5;
const b = 10;
console.log('this is ' + (a + b) + ' and\nnot ' + (2 * a + b) + '.');
现在,通过模板字符串,我们可以使用一种更优雅的方式来表示:
const a = 5;
const b = 10;
// 下面这行代码,故意做了换行。
console.log(`this is ${a + b} and
not ${2 * a + b}.`);
打印结果:
this is 15 and
not 20.
模板字符串中可以换行
因为模板字符串支持换行,所以可以让代码写得非常美观。
代码举例:
const result = {
name: '前端修炼',
age: 28,
sex: '男',
};
// 模板字符串支持换行
const html = `<div>
<span>${result.name}</span>
<span>${result.age}</span>
<span>${result.sex}</span>
</div>`;
console.log(html); // 打印结果也会换行
模板字符串中可以调用函数
模板字符串中可以调用函数。字符串中调用函数的位置,将会显示函数执行后的返回值。
举例:
function getName() {
return 'qianduanxiulian';
}
console.log(`www.${getName()}.com`); // 打印结果:www.qianduanxiulian.com
模板字符串支持嵌套使用
const nameList = ['HTML', 'CSS', 'JavaScript'];
function myTemplate() {
// join('') 的意思是,把数组里的内容合并成一个字符串
return `<ul>
${nameList.map((item) => `<li>${item}</li>`).join('')}
</ul>`;
}
document.body.innerHTML = myTemplate();
效果如下:
基本数据类型:布尔值 Boolean
布尔型有两个值:true 和 false。主要用来做逻辑判断: true 表示真,false 表示假。
布尔值直接使用就可以了,千万不要加引号。
代码:
var a = true;
console.log(typeof a);
控制台输出结果:
boolean
布尔型和数字型相加时, true 按 1 来算 ,false 按 0 来算。
基本数据类型:数值型Number
在 JS 中所有的数值都是 Number 类型,包括整数和浮点数(小数)。
var a = 100; // 定义一个变量 a,并且赋值整数100
console.log(typeof a); // 输出变量 a 的类型
var b = 12.3; // 定义一个变量 b,并且赋值浮点数 12.3
console.log(typeof a);
上方代码的输出结果为:
number
number
再次补充:在 JS 中,只要是数,就是 Number 数值型的。无论整浮、浮点数(即小数)、无论大小、无论正负,都是 Number 类型的。
数值范围
由于内存的限制,ECMAScript 并不能保存世界上所有的数值。
- 最大值:
Number.MAX_VALUE
,这个值为: 1.7976931348623157e+308- 正负1.7976931348623157乘以10的308次方
- 最小值:
Number.MIN_VALUE
,这个值为: 5e-324- 正负5乘以10的负324次方
如果使用 Number 表示的变量超过了最大值,则会返回 Infinity。
- 无穷大(正无穷):Infinity
- 无穷小(负无穷):-Infinity
注意:typeof Infinity
的返回结果是 number。
NaN
NaN:是一个特殊的数字,表示 Not a Number,非数值。在进行数值运算时,如果得不到正常结果,就会返回 NaN。
比如:
console.log('abc' / 18); //结果是NaN
console.log('abc' * 'abcd'); //按理说,字符串相乘是没有结果的,但如果你非要让JS去算,它就一定会给你一个结果,结果是NaN。
注意事项:
typeof NaN
的返回结果是 number。- Undefined 和任何数值计算的结果为 NaN。NaN 与任何值都不相等,包括 NaN 本身。
- 关于 isNaN() 函数,可以看后续的文章《JavaScript基础/typeof和数据类型转换》。
连字符和加号的区别
键盘上的+
可能是连字符,也可能是数字的加号。如下:
console.log("我" + "爱" + "你"); //连字符,把三个独立的汉字,连接在一起了
console.log("我+爱+你"); //原样输出
console.log(1+2+3); //输出6
输出:
我爱你
我+爱+你
6
总结:如果加号两边都是 Number 类型,此时是数字相加。否则,就是连字符(用来连接字符串)。
举例 1:
var a = '1';
var b = 2;
console.log(a + b);
控制台输出:
12
举例 2:
var a = 1;
var b = 2;
console.log("a" + b); //"a"就不是变量了!所以就是"a"+2 输出a2
控制台输出:
a2
于是我们明白了,在变量中加入字符串进行拼接,可以被同化为字符串。【重要】
隐式转换
我们知道,"2"+1
得到的结果其实是字符串,但是"2"-1
得到的结果却是数值 1,这是因为计算机自动帮我们进行了“隐式转换”。
也就是说,-
、*
、/
、%
这几个符号会自动进行隐式转换。例如:
var a = '4' + 3 - 6;
console.log(a);
输出结果:
37;
虽然程序可以对-
、*
、/
、`%``这几个符号自动进行“隐式转换”;但作为程序员,我们最好自己完成转换,方便程序的可读性。
关于隐式转换的详细内容,后面在《typeof和数据类型转换》还会讲解到。
浮点数的运算
运算精度问题
在 JS 中,整数的运算基本可以保证精确;但是小数的运算,可能会得到一个不精确的结果。所以,千万不要使用 JS 进行对精确度要求比较高的运算。
如下:
var a = 0.1 + 0.2;
console.log(a); //打印结果十分意外:0.30000000000000004
上方代码中,打印结果并不是 0.3,而是 0.30000000000000004。
这是因为,计算机在做运算时,所有的运算都要转换成二进制去计算。然而,有些数字转换成二进制之后,无法精确表示。比如说,0.1 和 0.2 转换成二进制之后,是无穷的,因此存在浮点数的计算不精确的问题。
处理数学运算的精度问题
如果只是一些简单的精度问题,可以使用 toFix()
方法进行小数的截取。备注:关于 toFixed()
方法, 详见后面的文章《内置对象:Number 和 Math》。
在实战开发中,关于浮点数计算的精度问题,往往比较复杂。市面上有很多针对数学运算的开源库,比如decimal.js、 Math.js。这些开源库都比较成熟,我们可以直接拿来用。
- Math.js:属于很全面的运算库,文件很大,压缩后的文件就有 500kb。如果你的项目涉及到大型的复杂运算,可以使用 Math.js。
- decimal.js:属于轻量的运算库,压缩后的文件只有 32kb。大多数项目的数学运算,使用 decimal.js 足够了。
使用对比:
console.log('加法:');
var a = 0.1;
var b = 0.2;
console.log(a + b);
console.log(new Decimal(a).add(new Decimal(b)).toNumber());
console.log('减法:');
var a = 1.0;
var b = 0.7;
console.log(a - b);
console.log(new Decimal(a).sub(new Decimal(b)).toNumber());
console.log('乘法:');
var a = 1.01;
var b = 1.003;
console.log(a * b);
console.log(new Decimal(a).mul(new Decimal(b)).toNumber());
console.log('除法:');
var a = 0.029;
var b = 10;
console.log(a / b);
console.log(new Decimal(a).div(new Decimal(b)).toNumber());
打印结果:
加法:
0.30000000000000004
0.3
减法:
0.30000000000000004
0.3
乘法:
1.0130299999999999
1.01303
除法:
0.0029000000000000002
0.0029
变量值的传递(赋值)
语句:
a = b;
把 b 的值赋给 a,b 不变。
将等号右边的值,赋给左边的变量;等号右边的变量,值不变。
来做几个题目。
举例 1:
// a b c
var a = 1; // 1
var b = 2; // 1 2
var c = 3; // 1 2 3
a = b + c; // 5 2 3
b = c - a; // 5 -2 3
c = a * b; // 5 -2 -10
console.log(a);
console.log(b);
console.log(c);
输出:
5
-2
-10
举例 2:
//a b c
var a = 1;
var b = 2;
var c = 3; // 1 2 3
a = a + b; // 3 2 3
b = b + a; // 3 5 3
c = c + b; // 3 5 8
console.log(a); // 3
console.log(b); // 5
console.log(c); // 8
输出:
3
5
8
举例 3:
//a b
var a = '1';
var b = 2; // "1" 2
a = a + b; // "12" 2
b = b + a; // "12" "212"
console.log(a); // 输出12
console.log(b); // 输出212
输出:
12
212
举例 4:
//a b
var a = '1';
var b = 2;
a = b + a; //"21" 2
b = b + a; //"21" "221"
console.log(a); //21
console.log(b); //221
效果:
21
221
举例 5:(这个例子比较特殊,字符串减去数字)
var a = '3';
var b = 2;
console.log(a - b);
效果:(注意,字符串 - 数值 = 数值)
1
基本数据类型:空对象 Null
null 专门用来定义一个空对象。例如:let a = null
,又例如 Object.create(null)
.
如果你想定义一个变量用来保存引用类型,但是还没想好放什么内容,这个时候,可以在初始化时将其设置为 null。你可以把 null 理解为:null 虽然是一个单独的数据类型,但null 相当于是一个 object,只不过地址为空(空指针)而已。
比如:
let myObj = null;
cosole.log(typeof myObj); // 打印结果:object
补充:
- Null 类型的值只有一个,就是 null。比如
let a = null
。 - 从语义上讲,null表示一个空对象,所以使用 typeof 检查一个 null 值时,会返回 object。
基本数据类型:undefined:未定义类
case1:变量已声明,未赋值时
声明了一个变量,但没有赋值,此时它的值就是 undefined
。举例:
let name;
console.log(name); // 打印结果:undefined
console.log(typeof name); // 打印结果:undefined
补充:
- Undefined 类型的值只有一个,就是 undefind。比如
let a = undefined
。 - 使用 typeof 检查一个 undefined 值时,会返回 undefined。
case2:变量未声明(未定义)时
如果你从未声明一个变量,就去使用它,则会报错(这个大家都知道);此时,如果用 typeof
检查这个变量时,会返回 undefined
。举例:
console.log(typeof a); // undefined
console.log(a); // 打印结果:Uncaught ReferenceError: a is not defined
case3:函数无返回值时
如果一个函数没有返回值,那么,这个函数的返回值就是 undefined。
或者,也可以这样理解:在定义一个函数时,如果末尾没有 return 语句,那么,其实就是 return undefined
。
举例:
function foo() {}
console.log(foo()); // 打印结果:undefined
case4:调用函数时,未传参
调用函数时,如果没有传参,那么,这个参数的值就是 undefined。
举例:
function foo(name) {
console.log(name);
}
foo(); // 调用函数时,未传参。执行函数后的打印结果:undefined
实际开发中,如果调用函数时没有传参,我们可以根据需要给形参设置一个默认值:
function foo(name) {
name = name || 'qianduanxiulian';
}
foo();
等学习了 ES6 之后,上方代码也可以这样写:
function foo(name = 'qianduanxiulian') {}
foo();
其他区别
undefined 实际上是由 null 衍生出来的,所以null == undefined
的结果为 true。
但是 null === undefined
的结果是 false。它们虽然相似,但还是有区别的,其中一个区别是,和数字运算时:
- 10 + null 结果为 10。
- 10 + undefined 结果为 NaN。
规律总结:
- 任何值和 null 运算,null 可看做 0 运算。
- 任何数据类型和 undefined 运算都是 NaN。
数据类型转换
变量的数据类型转换:将一种数据类型转换为另外一种数据类型。
通常有三种形式的类型转换:
- 转换为字符串类型
- 转换为数字型
- 转换为布尔型
你会专门把某个数据类型转换成 null 或者 undefined 吗?不会,因为这样做,没有意义。
变量的类型转换的分类
显式类型转换
- toString()
- String()
- Number()
- parseInt(string)
- parseFloat(string)
-
隐式类型转换
isNaN ()
- 自增/自减运算符:
++
、—-
- 正号/负号:
+a
、-a
- 加号:
+
- 运算符:
-
、*
、/
隐式类型转换(特殊)
- 逻辑运算符:
&&
、||
、!
。非布尔值进行与或运算时,会先将其转换为布尔值,然后再运算。&&
、||
的运算结果是原值,!
的运算结果为布尔值。 - 关系运算符:
<
、>
<=
>=
等。关系运算符,得到的运算结果都是布尔值:要么是 true,要么是 false。
运算符在接下来的文章中会详细讲解。
针对上面这两种类型转换,这篇文章来详细介绍。
一、转换为 String
1. 调用 toString()方法
语法:
变量.toString();
常量.toString(); // 这里的常量,不允许是数字,但可以是其它常量
// 或者用一个新的变量接收转换结果
var result = 变量.toString();
【重要】该方法不会影响到原变量,它会将转换的结果返回。当然我们还可以直接写成a = a.toString()
,这样的话,就是直接修改原变量。
举例:
// 基本数据类型
var a1 = 'qianduanxiulian';
var a2 = 29;
var a3 = true;
// 引用数据类型
var a4 = [1, 2, 3];
var a5 = { name: 'qianduanxiulian', age: 29 };
// null 和 undefined
var a6 = null;
var a7 = undefined;
// 打印结果都是字符串
console.log(a1.toString()); // "qianduanxiulian"
console.log(a2.toString()); // "29"
console.log(a3.toString()); // "true"
console.log(a4.toString()); // "1,2,3"
console.log(a5.toString()); // "object"
// 下面这两个,打印报错
console.log(a6.toString()); // 报错:Uncaught TypeError: Cannot read properties of null
console.log(a7.toString()); // 报错:Uncaught TypeError: Cannot read properties of undefined
效果:
一起来看看 toString() 的注意事项。
(1)null 和 undefined 这两个值没有 toString() 方法,所以它们不能用 toString() 。如果调用,会报错。
console.log(null.toString());
console.log(undefined.toString());
如果你不确定一个值是不是null
或undefined
,可以使用String()
函数,下一小段会讲。
(2)多数情况下,toString()
不接收任何参数;当然也有例外:Number 类型的变量,在调用 toString()时,可以在方法中传递一个整数作为参数。此时它会把数字转换为指定的进制,如果不指定则默认转换为 10 进制。例如:
var a = 255;
//Number数值在调用toString()时,可以在方法中传递一个整数作为参数
//此时它将会把数字转换为指定的进制,如果不指定则默认转换为10进制
a = a.toString(2); // 转换为二进制
console.log(a); // "11111111"
console.log(typeof a); // string
(3)纯小数的小数点后面,如果紧跟连续6个或6个以上的“0”时,那么,将用e来表示这个小数。代码举例:
const num1 = 0.000001; // 小数点后面紧跟五个零
console.log(num1.toString()); // 打印结果:"0.000001"
const num2 = 0.0000001; // 小数点后面紧跟六个零
console.log(num2.toString()); // 【重点关注】打印结果:"1e-7"
const num3 = 1.0000001;
console.log(num3.toString()); // 打印结果:"1.0000001"
const num4 = 0.10000001;
console.log(num4.toString()); // 打印结果:"0.10000001"
(4)常量可以直接调用 toString() 方法,但这里的常量,不允许是数字。举例如下:
1.toString(); // 注意,会报错
1..toString(); // 合法。得到的结果是字符串"1"
1.2.toString(); // 合法。得到的结果是字符串"1.2"
(1).toString(); // 合法。得到的结果是字符串"1"
上方代码中,为何出现这样的打印结果?这是因为:
- 第一行代码:JS引擎认为
1.toString()
中的.
是小数点,小数点后面的字符是非法的。 - 第二行、第三行代码:JS引擎认为第一个
.
是小数点,第二个.
是属性访问的语法,所以能正常解释实行。 - 第四行代码:用
()
排除了.
被视为小数点的语法解释,所以这种写法也能正常解释执行。
参考链接:你不知道的toString方法
(5)既然常量没有方法,那它为什么可以调用 toString()呢?这是因为,除了 null、undefined之外,其他的常量都有对应的特殊的引用类型——基本包装类型,所以代码在解释执行的时候,会将常量转为基本包装类型,这样就可以调用相应的引用类型的方法。
我们在后续的内容《基本包装类型》中会专门讲到基本包装类型。
2. 使用 String()函数
语法:
String(变量/常量);
使用 String()函数做强制类型转换时:
- 对于 Number、Boolean、String、Object 而言,本质上就是调用 toString()方法,返回结果同 toString()方法。
- 但是对于 null 和 undefined,则不会调用 toString()方法。它会将 null 直接转换为 “null”。将 undefined 直接转换为 “undefined”。
该方法不会影响到原数值,它会将转换的结果返回。
3. 隐式类型转换:字符串拼接
格式:变量+”” 或者 变量+”abc”
举例:
var a = 123; // Number 类型
console.log(a + ''); // 打印结果:"123"
console.log(a + 'haha'); // 打印结果:"123haha"
上面的例子中,打印的结果,都是字符串类型的数据。实际上底层是调用的 String() 函数。
二、转换为 Number
1. 使用 Number() 函数
语法:
const result = Number(变量/常量);
情况一:字符串 —> 数字
(1)如果字符串中是纯数字,则直接将其转换为数字。
(2)如果字符串是一个空串或者是一个全是空格的字符串,则转换为 0。
(3)只要字符串中包含了其他非数字的内容(小数点
按数字来算),则转换为 NaN。怎么理解这里的 NaN 呢?可以这样理解,使用 Number() 函数之后,如果无法转换为数字,就会转换为 NaN。
情况二:布尔 —> 数字
(1)true 转成 1
(2)false 转成 0
情况三:null —> 数字,结果为:0
情况四:undefined —> 数字,结果为:NaN
2. 隐式类型转换:正负号 +a、-a
注意,这里说的是正号/负号,不是加号/减号。
任何值做+a
、-a
运算时, 底层调用的是 Number() 函数。不会改变原数值;得到的结果,会改变正负性。
代码举例:
const a1 = '123';
console.log(+a1); // 123
console.log(-a1); // -123
const a2 = '123abc';
console.log(+a2); // NaN
console.log(-a2); // NaN
const a3 = true;
console.log(+a3); // 1
console.log(-a3); // -1
const a4 = false;
console.log(+a4); // 0
console.log(-a4); // -0
const a5 = null;
console.log(+a5); // 0
console.log(-a5); // -0
const a6 = undefined;
console.log(+a6); // NaN
console.log(-a6); // NaN
3. 使用 parseInt()函数:字符串 -> 整数
语法:
const result = parseInt(需要转换的字符串)
parseInt():将传入的数据当作字符串来处理,从左至右提取数值,一旦遇到非数值就立即停止;停止时如果还没有提取到数值,就返回NaN。
parse 表示“转换”,Int 表示“整数”。例如:
parseInt('5'); // 得到的结果是数字 5
按照上面的规律,parseInt()的转换结果,列举如下。
情况一:字符串 —> 数字
- 只保留字符串最开头的数字,后面的中文自动消失。
- 如果字符串不是以数字开头,则转换为 NaN。
- 如果字符串是一个空串或者是一个全是空格的字符串,转换时会报错。
情况二:Boolean —> 数字,结果为:NaN
情况三:Null —> 数字,结果为:NaN
情况四:Undefined —> 数字,结果为:NaN
Number() 函数和 parseInt() 函数的区别:
就拿Number(true)
和 parseInt(true)/parseFloat(true)
来举例,二者在使用时,是有区别的:
- Number(true) :千方百计地想转换为数字;如果转换不了则返回 NaN。
- parseInt(true)/parseFloat(true) :提取出最前面的数字部分;没提取出来,那就返回 NaN。
parseInt()具有以下特性:
(1)parseInt()、parseFloat()会将传入的数据当作字符串来处理。也就是说,如果对非 String使用 parseInt()、parseFloat(),它会先将其转换为 String 然后再操作。【重要】
比如:
var a = 168.23;
console.log(parseInt(a)); //打印结果:168 (因为是先将 a 转为字符串"168.23",然后然后再操作)
var b = true;
console.log(parseInt(b)); //打印结果:NaN (因为是先将 b 转为字符串"true",然后然后再操作)
var c = null;
console.log(parseInt(c)); //打印结果:NaN (因为是先将 c 转为字符串"null",然后然后再操作)
var d = undefined;
console.log(parseInt(d)); //打印结果:NaN (因为是先将 d 转为字符串"undefined",然后然后再操作)
(2)只保留字符串最开头的数字,后面的中文自动消失。例如:
console.log(parseInt('2017在公众号上写了6篇文章')); //打印结果:2017
console.log(parseInt('2017.01在公众号上写了6篇文章')); //打印结果仍是:2017 (说明只会取整数)
console.log(parseInt('aaa2017.01在公众号上写了6篇文章')); //打印结果:NaN (因为不是以数字开头)
(3)自动截断小数:取整,不四舍五入。
例 1:
var a = parseInt(5.8) + parseInt(4.7);
console.log(a);
打印结果:
9
例 2:
var a = parseInt(5.8 + 4.7);
console.log(a);
打印结果:
10;
(4)带两个参数时,表示在转换时,包含了进制转换。
代码举例:
var a = '110';
var num = parseInt(a, 16); // 【重要】将 a 当成 十六进制 来看待,转换成 十进制 的 num
console.log(num);
打印结果:
272
如果你对打印结果感到震惊,请仔细看上面的代码注释。就是说,无论 parseInt() 里面的进制参数是多少,最终的转换结果是十进制。
我们来看下面的代码,打印结果继续震惊。
var a = '5';
var num = parseInt(a, 2); // 将 a 当成 二进制 来看待,转换成 十进制 的 num
console.log(num); // 打印结果:NaN。因为 二进制中没有 5 这个数,转换失败。
4. parseFloat()函数:字符串 —> 浮点数(小数)
parseFloat()的作用是:将字符串转换为浮点数。
parseFloat()和 parseInt()的作用类似,不同的是,parseFloat()可以获得小数部分。
代码举例:
var a = '123.456.789px';
console.log(parseFloat(a)); // 打印结果:123.456
parseFloat() 的几个特性,可以参照 parseInt()。
三、转换为 Boolean
转换结果列举【重要】
其他的数据类型都可以转换为 Boolean 类型。无论是隐式转换,还是显示转换,转换结果都是一样的。有下面几种情况:
(1)情况一:数字 —> 布尔。 0 和 NaN的转换结果 false,其余的都是 true。比如 Boolean(NaN)
的结果是 false。
(2)情况二:字符串 —-> 布尔。空串的转换结果是false,其余的都是 true。全是空格的字符串,转换结果也是 true。字符串'0'
的转换结果也是 true。
(3)情况三:null 和 undefined 都会转换为 false。
(4)情况四:引用数据类型会转换为 true。注意,空数组[]
和空对象{}
,转换结果也是 true,这一点,很多人都不知道。
重中之重来了:
转换为 Boolean 的上面这几种情况,极其重要,开发中会频繁用到。比如说,我们在项目开发中,经常需要对一些非布尔值做逻辑判断,符合条件后,才做下一步的事情。这个逻辑判断就是依据上面的四种情况。
举例:(接口返回的内容不为空,前端才做进一步的事情)
const result1 = '';
const result2 = { a: 'data1', b: 'data2' };
if (result1) {
console.log('因为 result1的内容为空,所以代码进不了这里');
}
if (result2 && result2.a) {
// 接口返回了 result2,且 result2.a 里面有值,前端才做进一步的事情
console.log('代码能进来,前端继续在这里干活儿');
}
这里再次强调一下,空数组[]
和空对象{}
转换为 Boolean 值时,转换结果为 true。
1. 隐式类型转换:逻辑运算
当非 Boolean 类型的数值和 Boolean 类型的数值做比较时,会先把前者临时进行隐式转换为 Boolean 类型,然后再做比较;且不会改变前者的数据类型。举例如下:
const a = 1;
console.log(a == true); // 打印结果:true
console.log(typeof a); // 打印结果:number。可见,上面一行代码里,a 做了隐式类型转换,但是 a 的数据类型并没有发生变化,仍然是 Number 类型
console.log(0 == true); // 打印结果:false
2. 使用 !!
使用 !!
可以显式转换为 Boolean 类型。比如 !!3
的结果是 true。
3. 使用 Boolean()函数
使用 Boolean()函数可以显式转换为 Boolean 类型。
隐式类型转换
重点:隐式类型转换,内部调用的都是显式类型的方法。
常见的隐式类型转换,包括下面这几种:
- isNaN() 函数
- 运算符:加号
+
- 运算符:
-
、*
、/
、%
- 运算符:正号/负号 +a
、
-a` - 自增/自减运算符:
++
、—-
本段先来讲一下 isNaN() ,其他内容,将在《运算符》中讲到
isNaN() 函数
语法:
isNaN(参数);
解释:判断指定的参数是否为 NaN(非数字类型),返回结果为 Boolean 类型。也就是说:任何不能被转换为数值的参数,都会让这个函数返回 true。
执行过程:
(1)先调用Number(参数)
函数;
(2)然后将Number(参数)
的返回结果是否为数值。如果不为数值,则最终结果为 true;如果为数值,则最终结果为 false。
代码举例:
console.log(isNaN('123')); // 返回结果:false。
console.log(isNaN(null)); // 返回结果:false
console.log(isNaN('abc')); // 返回结果:true。因为 Number('abc') 的返回结果是 NaN
console.log(isNaN(undefined)); // 返回结果:true
console.log(isNaN(NaN)); // 返回结果:true
运算符的定义和分类
运算符的定义
运算符:也叫操作符,是一种符号。通过运算符可以对一个或多个值进行运算,并获取运算结果。
表达式:数字、运算符、变量的组合(组成的式子)。
表达式最终都会有一个运算结果,我们将这个结果称为表达式的返回值。
比如:+
、*
、/
、()
都是运算符,而(3+5)/2
则是表达式。
比如:typeof 就是运算符,可以获得一个值的类型。它会将该值的类型以字符串的形式返回,返回值可以是 number、string、boolean、undefined、object。
运算符的分类
JS 中的运算符,分类如下:
- 算数运算符
- 自增/自减运算符
- 一元运算符
- 三元运算符(条件运算符)
- 逻辑运算符
- 赋值运算符
- 比较运算符
算数运算符
算术运算符:用于执行两个变量或值的算术运算。
常见的算数运算符有以下几种:
运算符 | 描述 |
---|---|
+ | 加、字符串连接 |
- | 减 |
* | 乘 |
/ | 除 |
% | 获取余数(取余、取模) |
求余的举例:
假设用户输入 345,怎么分别得到 3、4、5 这三个数呢?
答案:
得到3的方法:345 除以100,得到3.45然后取整,得到3。即:parseInt(345/100)
得到4的方法:345 除以100,余数是45,除以10,得到4.5,取整。即:parseInt(345 % 100 / 10)
得到5的方法:345 除以10,余数就是5。即:345 % 10
算数运算符的运算规则
(1)* / %
的优先级高于 + -
(2)无论是+ - * / %
都是左结合性(从左至右计算)
(2)小括号( )
:能够影响计算顺序,且可以嵌套。没有中括号、没有大括号,只有小括号。
举例 1:(取余)
console.log(3 % 5);
输出结果为 3。
举例 2:(注意运算符的优先级)
var a = 1 + ((2 * 3) % 4) / 3;
结果分析:
原式 = 1 + 6 % 4 / 3 = 1 + 2 / 3 = 1.66666666666666
取模(取余)运算
格式:
余数 = m % n;
计算结果注意:
- 取余运算结果的正负性,取决于 m,而不是 n。比如:
10 % -3
的运算结果是 1。-10 % 3
的运算结果是-1。 - 如果 n < 0,那就先把 n 取绝对值后,再计算。等价于 m % (-n)。
- 如果 n 是 0,那么结果是 NaN。
- 在 n > 0 的情况下:
- 如果 m>=n,那就正常取余。
- 如果 m<n,那结果就是 m。
浮点数运算的精度问题
浮点数值的最高精度是 17 位小数,但在进行算术计算时,会丢失精度,导致计算不够准确。比如:
console.log(0.1 + 0.2); // 运算结果不是 0.3,而是 0.30000000000000004
console.log(0.07 * 100); // 运算结果不是 7,而是 7.000000000000001
因此,不要直接判断两个浮点数是否相等。前面的《基本数据类型:Number》有详细介绍。
隐式类型转换——运算符:加号 +
- 字符串 + XX = 字符串
任何值和字符串做加法运算,都会先调用 String() 函数转换为字符串,然后再做拼串操作。最终的运算结果是字符串。
比如:
result1 = 1 + 2 + '3'; // 字符串:33
result2 = '1' + 2 + 3; // 字符串:123
- Boolean + 数字 = 数字
Boolean 型和数字型相加时, true 按 1 来算 ,false 按 0 来算。这里其实是先调 Number() 函数,将 Boolean 类型转为 Number 类型,然后再和 数字相加。
- null + 数字 = 数字
等价于:0 + 数字
- undefined + 数字 = NaN
计算结果:NaN
- 任何值和 NaN 运算的结果都是 NaN。
隐式类型转换——运算符:-
、*
、/
、%
任何非 Number 类型的值做-
、*
、/
、%
运算时,会将这些值转换为 Number 然后再运算(内部调用的是 Number() 函数),运算结果是 Number 类型。
任何数据和 NaN进行运算,结果都是NaN。
比如:
var result1 = 100 - '1'; // 99
var result2 = true + NaN; // NaN
自增和自减运算符
自增运算符 ++
作用:可以快速对一个变量进行加1操作。只能操作变量,不能操作常量或者表达式。
例如:
let a1 = 1;
let a2 = 2;
a1++;
const result = a1++ + a2; // result的结果为4
// (a1+a2)++; // 报错,没有这种写法
const a3 = 3;
a3++; // 报错,因为常量无法再自加
自增分成两种:a++
和++a
。共同点:
(1)无论是 a++
还是++a
,自增都会使原变量的值加 1。
(2)我们要注意的是:a
是变量,而a++
和++a
是表达式。
那这两种自增,有啥区别呢?区别是:a++
和 ++a
的值不同:(也就是说,表达式的值不同)
a++
这个表达式的值等于原变量的值(a 自增前的值)。可以这样理解:先把 a 的值赋值给表达式,然后 a 再自增。++a
这个表达式的值等于新值 (a 自增后的值)。 可以这样理解:a 先自增,然后把自增后的值赋值给表达式。
自减运算符 --
作用:可以快速对一个变量进行减1操作。原理同自增运算符。
开发时,大多使用后置的自增/自减,并且代码独占一行,例如:num++
,或者 num--
。
代码举例
var n1 = 10;
var n2 = 20;
var result1 = n1++;
console.log(n1); // 11
console.log(result1); // 10
result = ++n1;
console.log(n1); //12
console.log(result); //12
var result2 = n2--;
console.log(n2); // 19
console.log(result2); // 20
result2 = --n2;
console.log(n2); // 18
console.log(result2); // 18
隐式类型转换
自增和自减时,a的执行过程:
(1)先调用Number(a)
函数;
(2)然后将Number(a)
的返回结果进行 加 1 操作,得到的结果赋值给 a。
举例 1:
let a = '666'; // 这里不能用 const 定义,否则报错。
a++;
console.log(a); // 打印结果:667
console.log(typeof a); // 打印结果: number
举例2:
let a = 'abc';
a++;
console.log(a); // 打印结果:NaN。因为 Number('abc')的结果为 NaN,再自增后,结果依然是 NaN
console.log(typeof a); // 打印结果:number
一元运算符
一元运算符,只需要一个操作数。常见的一元运算符如下。
typeof
typeof 就是典型的一元运算符,因为后面只跟一个操作数。
typeof()
表示“获取变量的数据类型”,它是 JS 提供的一个操作符。返回的是小写,语法为:(两种写法都可以)
// 写法1
typeof 变量;
// 写法2
typeof(变量);
typeof 这个运算符的返回结果就是变量的类型。那返回结果的类型是什么呢?是字符串。
返回结果:
typeof 的语法 | 返回结果 |
---|---|
typeof 数字(含 typeof NaN) | number |
typeof 字符串 | string |
typeof 布尔型 | boolean |
typeof 对象 | object |
typeof 方法 | function |
typeof null | object |
typeof undefined | undefined |
备注 1:为啥 typeof null
的返回值也是 object 呢?因为 null 代表的是空对象。
备注 2:typeof NaN
的返回值是 number,上一篇文章中讲过,NaN 是一个特殊的数字。
返回结果举例:
var a = '123';
console.log(typeof a); // 打印结果:string
console.log(typeof []); // 空数组的打印结果:object
console.log(typeof {}); // 空对象的打印结果:object
代码解释:这里的空数组[]
、空对象{}
,为啥他们在使用 typeof 时,返回值也是 object
呢?因为空数组、空对象都是引用数据类型 Object。
typeof 无法区分数组,但 instanceof 可以。比如:
console.log([] instanceof Array); // 打印结果:true
console.log({} instanceof Array); // 打印结果:false
关于 instanceof 的详细内容,以后讲对象的时候,会详细介绍。
正号/负号:+a
、-a
注意,这里说的是正号/负号,不是加号/减号。
(1)不会改变原数值。
(1)正号不会对数字产生任何影响。比如说,2
和+2
是一样的。
(2)我们可以对其他的数据类型使用+
,来将其转换为 number【重要的小技巧】。比如:
var a = true;
a = +a; // 注意这行代码的一元运算符操作
console.log('a:' + a);
console.log(typeof a);
console.log('-----------------');
var b = '18';
b = +b; // 注意这行代码的一元运算符操作
console.log('b:' + b);
console.log(typeof b);
打印结果:
a:1
number
-----------------
b:18
number
(3)负号可以对数字进行取反。
隐式类型转换——正号/负号
任何值做+a
、-a
运算时, 内部调用的是 Number() 函数。
举例:
const a = '666';
const b = +a; // 对 a 进行一元运算,b是运算结果
console.log(typeof a); // 打印结果:string。说明 a 的数据类型保持不变。
console.log(a); // 打印结果:"666"。不会改变原数值。
console.log(typeof b); // 打印结果:number。说明 b 的数据类型发生了变化。
console.log(b); // 打印结果:666
三目运算符
三目运算符也叫三元运算符、条件运算符。
语法:
条件表达式 ? 语句1 : 语句2;
执行流程——条件运算符在执行时,首先对条件表达式进行求值:
- 如果该值为 true,则执行语句 1,并返回执行结果
- 如果该值为 false,则执行语句 2,并返回执行结果
如果条件表达式的求值结果是一个非布尔值,会将其转换为布尔值然后再运算。
逻辑运算符
逻辑运算符有三个:
&&
: 与(且)。两个都为真,结果才为真。特点:一假则假。||
:或。只要有一个是真,结果就是真。特点:特点: 一真则真。!
:非。对一个布尔值进行取反。特点: 真变假, 假变真。
注意:能参与逻辑运算的,都是布尔值。
连比的写法:
来看看逻辑运算符连比的写法。
举例 1:
console.log(3 < 2 && 2 < 4);
输出结果为 false。
举例 2:(判断一个人的年龄是否在 18~65 岁之间)
const a = prompt('请输入您的年龄');
if (a >= 18 && a < 65) {
alert('可以上班');
} else {
alert('准备退休');
}
PS:上面的a>=18 && a<= 65
千万别想当然地写成18<= a <= 65
,没有这种语法。
非布尔值的与或运算【重要】
之所以重要,是因为在实际开发中,我们经常用这种代码做容错处理或者兜底处理。
非布尔值进行与或运算时,会先将其转换为布尔值,然后再运算,但返回结果是原值。比如说:
var result = 5 && 6; // 运算过程:true && true;
console.log('result:' + result); // 打印结果:6(也就是最后面的那个值)
上方代码可以看到,虽然运算过程为布尔值的运算,但返回结果是原值。
那么,返回结果是哪个原值呢?我们来看一下。
1、两个非布尔值,做逻辑运算:
且运算的返回结果:
- 如果第一个值为 false,则只执行第一条语句,并直接返回第一个值;不会再往后执行。
- 如果第一个值为 true,则继续执行第二条语句,并返回第二个值(无论第二个值的结果如何)。
或运算的返回结果:
- 如果第一个值为 true,则只执行第一条语句,并直接返回第一个值;不会再往后执行。
- 如果第一个值为 false,则继续执行第二条语句,并返回第二个值(无论第二个值的结果如何)。
2、三个及以上的非布尔值,做逻辑运算:
且运算的返回结果:(value1 && value2 && value3)
- 从左到右依次计算操作数,找到第一个为 false 的值为止。
- 如果所有的值都为 true,则返回最后一个值。
或运算的返回结果:(value1 || value2 || value3)
- 从左到右依次计算操作数,找到第一个为 true 的值为止。
- 如果所有的值都为 false,则返回最后一个值。
非布尔值的 !
运算
非布尔值进行非运算时,会先将其转换为布尔值,然后再运算,返回结果是布尔值。
举例:
let a = 10;
a = !a;
console.log(a); // false
console.log(typeof a); // boolean
短路运算的妙用【重要】
下方举例中的写法技巧,在实际开发中,经常用到。这种写法,是一种很好的「容错、容灾、降级」方案,需要多看几遍。
1、JS 中的&&
属于短路的与:
- 如果第一个值为 false,则不会执行后面的内容。
- 如果第一个值为 true,则继续执行第二条语句,并返回第二个值。
举例:
const a1 = 'qianduanxiulian';
// 第一个值为true,会继续执行后面的内容
a1 && alert('看 a1 出不出来'); // 可以弹出 alert 框
const a2 = undefined;
// 第一个值为false,不会继续执行后面的内容
a2 && alert('看 a2 出不出来'); // 不会弹出 alert 框
2、JS 中的||
属于短路的或:
- 如果第一个值为 true,则不会执行后面的内容。
- 如果第一个值为 false,则继续执行第二条语句,并返回第二个值。
实际开发中,我们经常是这样来做「容错处理」的,如下。
举例1:
const result; // 请求接口时,后台返回的内容
let errorMsg = ''; // 前端的文案提示
if (result & result.retCode == 0) {
errorMsg = '恭喜你中奖啦~'
}
if (result && result.retCode != 0) {
// 接口返回异常码时
errorMsg = result.msg || '活动太火爆,请稍后再试'; // 文案提示信息,优先用 接口返回的msg字段,其次用 '活动太火爆,请稍后再试' 这个文案兜底。
}
if (!result) {
// 接口挂掉时
errorMsg = '网络异常,请稍后再试';
}
举例2,当前端成功调用一个接口后,返回的数据为 result 对象。这个时候,我们用变量 a 来接收 result 里的图片资源:
if (result.retCode == 0) {
var a = result && result.data && result.data.imgUrl || 'http://img.smyhvae.com/20160401_01.jpg';
}
上方代码的意思是,获取返回结果中的result.data.imgUrl
这个图片资源;如果返回结果中没有 result.data.imgUrl
这个字段,就用 http://img.smyhvae.com/20160401_01.jpg
作为兜底图片。这种写法,在实际开发中经常用到。
赋值运算符
赋值:将等号右侧的值赋给符号左侧的变量。
赋值运算符包括哪些
=
直接赋值。比如var a = 5
。意思是,把 5 这个值,往 a 里面存一份。简称:把 5 赋值给 a。+=
:比如 a += 5 等价于 a = a + 5。-=
:比如 a -= 5 等价于 a = a - 5。*=
:比如 a = 5 等价于 a = a 5。/=
:比如 a /= 5 等价于 a = a / 5。%=
:比如 a %= 5 等价于 a = a % 5。
注意事项
(1)算数运算符的优先级高于赋值运算符。举例:
const result = 1 + 2; // 先计算 1 + 2,再把计算结果赋值给 result。因为算数运算符的优先级高于赋值运算符。
(2)赋值运算符的结合性是右结合性(从右至左的顺序计算)。举例:
const a1, a2;
a1 = a2 = 3; // 先将 3 复制给 a2,再将 a2 的值赋值给 a1
比较运算符
比较运算符可以比较两个值之间的大小关系,如果关系成立它会返回 true,如果关系不成立则返回 false。
比较运算符有很多种,比如:
> 大于号
< 小于号
>= 大于或等于
<= 小于或等于
== 等于
=== 全等于
!= 不等于
!== 不全等于
比较运算符,得到的结果都是布尔值:要么是 true,要么是 false。如果关系成立,就返回true;如果关系不成立,就返回false。
举例如下:
const result = 5 > 10; // false
非数值的比较
(1)对于非数值进行比较时,会将其转换为数值类型(内部是调用Number()方法
),再进行比较。
举例如下:
console.log(1 > true); //false
console.log(1 >= true); //true
console.log(1 > '0'); //true
//console.log(10 > null); //true
//任何值和NaN做任何比较都是false
console.log(10 <= 'hello'); //false
console.log(true > false); //true
(2)特殊情况:如果参与比较的都是字符串,则不会将其转换为数字进行比较,比较的是字符串的Unicode 编码。【非常重要,这里是个大坑,很容易踩到】
比较字符编码时,是一位一位进行比较,顺序从左到右。如果大一样,则继续比较下一位。
比如说,当你尝试去比较"123"
和"56"
这两个字符串时,你会发现,字符串”56”竟然比字符串”123”要大(因为 5 比 1 大)。也就是说,下面这样代码的打印结果,其实是 true:(这个我们一定要注意,在日常开发中,很容易忽视)
// 比较两个字符串时,比较的是字符串的字符编码,所以可能会得到不可预期的结果
console.log('56' > '123'); // true
因此:当我们想比较两个字符串型的数字时,一定一定要先转型再比较大小,比如 parseInt()
。
(3)任何值和 NaN 做任何比较都是 false。
==
符号的强调
==
这个符号,它是判断是否等于,而不是赋值。注意事项如下:
(1)==
这个符号,还可以验证字符串是否相同。例如:
console.log('我爱你中国' == '我爱你中国'); // 输出结果为true
(2)==
这个符号并不严谨,会做隐式转换,将不同的数据类型,转为相同类型进行比较。例如:
console.log('6' == 6); // 打印结果:true。这里的字符串"6"会先转换为数字6,然后再进行比较
console.log(true == '1'); // 打印结果:true
console.log(0 == -0); // 打印结果:true
console.log(null == 0); // 打印结果:false
(3)undefined 衍生自 null,所以这两个值做相等判断时,会返回 true。
console.log(undefined == null); //打印结果:true。
(4)NaN 不和任何值相等,包括它本身。
console.log(NaN == NaN); //false
console.log(NaN === NaN); //false
问题:那如果我想判断 b 的值是否为 NaN,该怎么办呢?
答案:可以通过 isNaN()函数来判断一个值是否是 NaN。举例:
console.log(isNaN(b));
如上方代码所示,如果 b 为 NaN,则返回 true;否则返回 false。
===
全等符号的强调
全等在比较时,不会做类型转换。如果要保证完全等于(即:不仅要判断取值相等,还要判断数据类型相同),我们就要用三个等号===
。例如:
console.log('6' === 6); //false
console.log(6 === 6); //true
上述内容分析出:
==
两个等号,不严谨,”6”和 6 是 true。===
三个等号,严谨,”6”和 6 是 false。
另外还有:**==**
的反面是**!=**
,**===**
的反面是**!==**
。例如:
console.log(3 != 8); // true
console.log(3 != '3'); // false,因为3=="3"是true,所以反过来就是false。
console.log(3 !== '3'); // true,应为3==="3"是false,所以反过来是true。
不同数据类型之间的大小比较
这一段是比较运算符的延伸,内容繁琐,新手可以不用记,等以后用到的时候再查阅。
数值类型和其他类型比较
先将其他类型隐式转换为数值类型(内部是调用Number()
方法),然后比较大小。代码举例:
//字符串与数字比较
console.log('200' > 100); // true
console.log('a' > 100); // false。 'a' 被转换成 NaN 进行比较
console.log('110a' > 100); // false。 '110a' 被转换成 NaN 进行比较。说明`110a`在做隐式转换的时候,是调用了 Number('110a')方法,而不是调用 parseInt('110a')方法
// 布尔值与数字比较
console.log(true == 1); // true
console.log(false == 0); // true
// null 与数字进行比较
console.log(null < 0); // false
console.log(null == 0); // false
console.log(null > 0); // false
console.log(null <= 0); // true。这是一个很严重的bug
console.log(null >= 0); // true。同上
// undefined 与数字进行比较:结果都是 false
console.log(undefined > 0);
console.log(undefined == 0);
console.log(undefined < 0);
console.log(undefined >= 0);
日期大小比较
如果日期的格式为字符串,则比较字符串的Unicode 编码。代码举例:
const myDate1 = new Date(2022, 8, 8);
const myDate2 = new Date(2022, 8, 9);
const myDate3 = new Date(2022, 9, 8);
const myDate4 = new Date(2023, 8, 8);
console.log(myDate1 < myDate2); // true
console.log(myDate1 < myDate3); // true
console.log(myDate3 < myDate4); // true
const date1 = '2022-08-08'; // "2022/08/08"同理
const date2 = '2022-08-09'; // "2022/08/09"同理
const date3 = '2022-09-08'; // "2022/09/08"同理
const date4 = '2023-08-08'; // "2023/08/08"同理
console.log(date1 < date2); // true
console.log(date1 < date3); // true
console.log(date3 < date4); // true
const time1 = '2022-08-08 08:00:00';
const time2 = '2022-08-08 08:00:01';
const time3 = '2022-08-08 08:01:00';
const time4 = '2022-08-08 09:00:00';
console.log(time1 < time2); // true
console.log(time1 < time3); // true
console.log(time1 < time4); // true
// 数据类型不同,此处是先将 myDate1 转为字符串类型,然后比较大小。可想而知,结果都是 false
console.log(myDate1 >= date1); // false
console.log(myDate1 <= date1); // false
// 虽然时间格式不同,但都是字符串,所以可以比较大小
console.log(date1 < time1); // true
参考链接:
逗号运算符
逗号运算符一般用于简化代码。逗号运算符的优先级是所有运算符中最低的。
逗号运算符也是一个运算符, 所以也有运算符结果。它的运算符结果是最后一个表达式的结果。
代码举例:
// 利用逗号运算符同时定义多个变量
let a, b;
// 利用逗号运算符同时给多个变量赋值
a = 10, b = 5;
const res1 = (1 + 2, 3 + 4, 5 + 6); // 打印结果:11
运算符的优先级
运算符的优先级如下:(优先级从高到低)
.
、[]
、new
()
++
、--
!
、~
、+
(单目)、-
(单目)、typeof
、void
、delete
*
、/
、%
+
(双目)、-
(双目)<<
、>>
、>>>
- 比较运算符:
<
、<=
、>
、>=
- 比较运算符:
==
、!==
、===
、!==
&
^
|
- 逻辑运算符:
&&
(注意:逻辑与&&
比逻辑或||
的优先级更高) - 逻辑运算符:
||
?:
=
、+=
、-=
、*=
、/=
、%=
、<<=
、>>=
、>>>=
、&=
、^=
、|=
,
备注:在实际写代码的时候,如果你不清楚哪个优先级更高,可以先尝试把括号用上。
流程控制语句:选择结构(if和switch)
代码块
用{}
包围起来的代码,就是代码块。
在 ES5 语法中,代码块,只具有分组的作用,没有其他的用途。代码块中的内容,在外部是完全可见的。举例:
{
var a = 2;
alert('qianduanxiulian');
console.log('前端修炼');
}
console.log('a = ' + a);
打印结果:(可以看出,虽然变量 a 是定义在代码块中的,但是在外部依然可以访问)
前端修炼
a = 2
流程控制语句
在一个程序执行的过程中,各条语句的执行顺序对程序的结果是有直接影响的。所以,我们必须清楚每条语句的执行流程。而且,很多时候我们要通过控制语句的执行顺序来实现我们想要的业务逻辑和功能。
流程控制语句分类
- 顺序结构
- 选择结构:if 语句、switch 语句
- 循环结构:while 语句、for 语句
顺序结构
按照代码的先后顺序,依次执行。结构图如下:
if 语句
if 语句有以下三种形式。
if 语句的三种形式
形式1:(条件成立才执行。如果条件不成立,那就什么都不做)
if (条件表达式) {
// 条件为真时,做的事情
}
对于非布尔类型的数据,会先转换成布尔类型再判断。下同。
形式 2:
if (条件表达式) {
// 条件为真时,做的事情
} else {
// 条件为假时,做的事情
}
形式3:(多分支的 if 语句)
if (条件表达式1) {
// 条件1为真时,做的事情
} else if (条件表达式2) {
// 条件1不满足,条件2满足时,做的事情
} else if (条件表达式3) {
// 条件1、2不满足,条件3满足时,做的事情
} else {
// 条件1、2、3都不满足时,做的事情
}
以上所有的语句体中,只执行其中一个。
做个题目
根据BMI(身体质量指数)显示一个人的体型。
BMI指数,就是体重、身高的一个计算公式。公式是:
BMI =体重÷身高的平方
比如,老师的体重是81.6公斤,身高是1.71米。
那么老师的BMI就是 81.6 ÷ 1.712 等于 27.906022365856163
过轻:低于18.5
正常:18.5-24.99999999
过重:25-27.9999999
肥胖:28-32
非常肥胖, 高于32
用JavaScript开发一个程序,让用户先输入自己的体重,然后输入自己的身高(弹出两次prompt框)。
计算它的BMI,根据上表,弹出用户的身体情况。比如“过轻” 、 “正常” 、“过重” 、 “肥胖” 、“非常肥胖”。
答案:
写法 1:
//第一步,输入身高和体重
var height = parseFloat(prompt('请输入身高,单位是米'));
var weight = parseFloat(prompt('请输入体重,单位是公斤'));
//第二步,计算BMI指数
var BMI = weight / Math.pow(height, 2);
//第三步,if语句来判断
if (BMI < 18.5) {
alert('偏瘦');
} else if (BMI < 25) {
alert('正常');
} else if (BMI < 28) {
alert('过重');
} else if (BMI <= 32) {
alert('肥胖');
} else {
alert('非常肥胖');
}
if 语句的嵌套
我们通过下面这个例子来引出 if 语句的嵌套。
一个加油站为了鼓励车主多加油,所以加的多有优惠。
92号汽油,每升6元;如果大于等于20升,那么每升5.9;
97号汽油,每升7元;如果大于等于30升,那么每升6.95
编写JS程序,用户输入自己的汽油编号,然后输入自己加多少升,弹出价格。
代码实现如下:
//第一步,输入
var bianhao = parseInt(prompt('您想加什么油?填写92或者97'));
var sheng = parseFloat(prompt('您想加多少升?'));
//第二步,判断
if (bianhao == 92) {
//编号是92的时候做的事情
if (sheng >= 20) {
var price = sheng * 5.9;
} else {
var price = sheng * 6;
}
} else if (bianhao == 97) {
//编号是97的时候做的事情
if (sheng >= 30) {
var price = sheng * 6.95;
} else {
var price = sheng * 7;
}
} else {
alert('不好意思,没有这个编号的汽油!');
}
alert('价格是' + price);
switch 语句(条件分支语句)
语法格式
switch(表达式) {
case 值1:
语句体1;
break;
case 值2:
语句体2;
break;
...
...
default:
语句体 n+1;
break;
}
解释:switch 可以理解为“开关、转换” 。case 可以理解为“案例、选项”。
switch 语句的执行流程
流程图如下:
执行流程如下:
(1)首先,计算出表达式的值,和各个 case 依次比较,一旦有对应的值,就会执行相应的语句,在执行的过程中,遇到 break 就会结束。
(2)然后,如果所有的 case 都和表达式的值不匹配,就会执行 default 语句体部分。
default不一定要写在最后面。 switch 中的 default 无论放到什么位置,都会等到所有case 都不匹配再执行。default 也可以省略。
switch 语句的结束条件【非常重要】
- 情况 a:遇到 break 就结束(而不是遇到 default 就结束)。因为 break 在此处的作用是,立即结束并退出整个 switch 语句。
- 情况 b:执行到程序的末尾就结束。
我们稍后讲 case穿透的时候,你就会明白其中的奥妙了。
switch 和 case 后面的值
switch 后面的括号里可以是变量、常量、表达式, 通常是一个变量(一般的做法是:先把表达式存放到变量中)。
case 后面的值可以是变量、常量、表达式。
注意,case的判断逻辑是**===**
,不是**==**
。因此,字符串'6'
和 数字 6
是不一样的。
举例 1:
let msg = 'notice';
switch (msg) {
case 'notice':
console.log('提示');
break;
case 'warning':
console.log('警告');
break;
case 'error':
console.log('错误');
break;
default:
console.log('默认文案');
break;
}
举例 2:(case 后面的是表达式)
let age = 28;
switch (true) {
case age < 18:
console.log('未成年人');
break;
case age >= 18 && age <= 65:
console.log('还能干活儿');
break;
case age > 65:
console.log('该退休了');
break;
default:
console.log('默认文案');
break;
}
代码解释:由于 switch 里的值是 true,所以,在众多的 case 语句中,会去匹配第一个符合 case true
的语句,然后命中这条语句。
case 穿透
switch 语句中的break
可以省略,但一般不建议(对于新手而言)。否则结果可能不是你想要的,会出现一个现象:case 穿透。
当然,如果你能利用好 case 穿透,会让代码写得十分优雅。
举例 1:(case 穿透的情况)
const num = 4;
//switch判断语句
switch (num) {
case 1:
console.log('星期一');
break;
case 2:
console.log('星期二');
break;
case 3:
console.log('星期三');
break;
case 4:
console.log('星期四');
//break;
case 5:
console.log('星期五');
//break;
case 6:
console.log('星期六');
break;
case 7:
console.log('星期日');
break;
default:
console.log('你输入的数据有误');
break;
}
上方代码的运行结果,可能会令你感到意外:
星期四
星期五
星期六
上方代码的解释:因为在 case 4 和 case 5 中都没有 break,那语句走到 case 6 的 break 才会停止。
举例 2:
//switch判断语句
var number = 5;
switch (number) {
default:
console.log('我是defaul语句');
// break;
case 2:
console.log('第二个呵呵:' + number);
//break;
case 3:
console.log('第三个呵呵:' + number);
break;
case 4:
console.log('第四个呵呵:' + number);
break;
}
上方代码的运行结果,你也许会意外:
我是defaul语句
第二个呵呵:5
第三个呵呵:5
上方代码的解释:代码走到 default 时,因为没有遇到 break,所以会继续往下走,直到遇见 break 或者走到程序的末尾。 从这个例子可以看出:switch 语句的结束与 default 的顺序无关。
switch 语句的实战举例:替换 if 语句
我们实战开发中,经常需要根据接口的返回码 retCode ,来让前端做不同的展示。
这种场景是业务开发中经常出现的,请一定要掌握。然而,很多人估计会这么写:
写法 1(不推荐)
let retCode = 1003; // 返回码 retCode 的值可能有很多种情况
if (retCode == 0) {
alert('接口联调成功');
} else if (retCode == 101) {
alert('活动不存在');
} else if (retCode == 103) {
alert('活动未开始');
} else if (retCode == 104) {
alert('活动已结束');
} else if (retCode == 1001) {
alert('参数错误');
} else if (retCode == 1002) {
alert('接口频率限制');
} else if (retCode == 1003) {
alert('未登录');
} else if (retCode == 1004) {
alert('(风控用户)提示 活动太火爆啦~军万马都在挤,请稍后再试');
} else {
// 其他异常返回码
alert('系统君失联了,请稍候再试');
}
如果你是按照上面的 if else
的方式来写各种条件判断,说明你的代码水平太初级了,会被人喷的,千万不要这么写。这种写法,容易导致嵌套太深,可读性很差。
那要怎么改进呢?继续往下看。
写法 2(推荐。通过 return 的方式,将上面的写法进行改进)
let retCode = 1003; // 返回码 retCode 的值可能有很多种情况
handleRetCode(retCode);
// 方法:根据接口不同的返回码,处理前端不同的显示状态
function handleRetCode(retCode) {
if (retCode == 0) {
alert('接口联调成功');
return;
}
if (retCode == 101) {
alert('活动不存在');
return;
}
if (retCode == 103) {
alert('活动未开始');
return;
}
if (retCode == 104) {
alert('活动已结束');
return;
}
if (retCode == 1001) {
alert('参数错误');
return;
}
if (retCode == 1002) {
alert('接口频率限制');
return;
}
if (retCode == 1003) {
alert('未登录');
return;
}
if (retCode == 1004) {
alert('(风控用户)提示 活动太火爆啦~军万马都在挤,请稍后再试');
return;
}
// 其他异常返回码
alert('系统君失联了,请稍候再试');
return;
}
上面的写法 2,是比较推荐的写法:直接通过 return 的方式,让 function 里的代码不再继续往下走,这就达到目的了。对了,因为要用到 return ,所以整段代码是封装到一个 function 里的。
如果你以后看到有前端小白采用的是写法 1,请一定要把写法 2传授给他:不需要那么多的 if else,直接用 return 返回就行了。
写法 3(推荐。将 if else 改为 switch)
let retCode = 1003; // 返回码 retCode 的值可能有很多种情况
switch (retCode) {
case 0:
alert('接口联调成功');
break;
case 101:
alert('活动不存在');
break;
case 103:
alert('活动未开始');
break;
case 104:
alert('活动已结束');
break;
case 1001:
alert('参数错误');
break;
case 1002:
alert('接口频率限制');
break;
case 1003:
alert('未登录');
break;
case 1004:
alert('(风控用户)提示 活动太火爆啦~军万马都在挤,请稍后再试');
break;
// 其他异常返回码
default:
alert('系统君失联了,请稍候再试');
break;
}
在实战开发中,方式 3 是非常推荐的写法,甚至比方式 2 还要好。我们尽量不要写太多的 if 语句,避免代码嵌套过深。
switch 语句的优雅写法:适时地去掉 break
我们先来看看下面这段代码:(不推荐)
let day = 2;
switch (day) {
case 1:
console.log('work');
break;
case 2:
console.log('work');
break;
case 3:
console.log('work');
break;
case 4:
console.log('work');
break;
case 5:
console.log('work');
break;
case 6:
console.log('relax');
break;
case 7:
console.log('relax');
break;
default:
break;
}
上面的代码,咋一看,好像没啥毛病。但你有没有发现,重复代码太多了?
实战开发中,凡是有重复的地方,我们都必须要想办法简化。写代码就是在不断重构的过程。
上面的代码,可以改进如下:(推荐,非常优雅)
let day = 2;
switch (day) {
case 1:
case 2:
case 3:
case 4:
case 5:
console.log('work');
break; // 在这里放一个 break
case 6:
case 7:
console.log('relax');
break; // 在这里放一个 break
default:
break;
}
}
你没看错,就是上面的这种写法,能达到同样的效果,非常优雅。
小白可能认为这样的写法可读性不强,所以说他是小白。我可以明确告诉你,改进后的这种写法,才是最优雅的、最简洁、可读性最好的。
补充
if 和 switch如何选择
如果是对区间进行判断,则建议用 if。如果是对几个固定的值进行判断,那么,数量少的话用 if,数量多的话用switch。
用 return 代替 if else
业务场景举例:
我们在实战业务中涉及到调接口时,一般会这样做:
- 接口返回码为 0 时,前端 resolve。
- 接口返回未登录时,前端跳转到登录页面。
- 接口返回其他情况,或者无返回时,前端 reject。
写法 1、if else 的写法:(不推荐)
if (res) {
if (+res.retCode == 0) {
resolve(res);
} else if (+res.retCode == 8888) {
goLogin();
} else {
reject(res);
}
} else {
reject();
}
写法 2、 return 的写法:(推荐)
if (!res || +res.retCode !== 0) {
if (+res.retCode === 8888) {
// 未登录
goLogin();
return;
}
reject(res);
return;
}
resolve(res);
备注:如果你没学过 Promise,这个例子可以先不看。等以后学了 Promise 再回来看就很容易明白了。
流程控制语句:循环结构(for和while)
for 循环
for 循环的语法
语法:
for(①初始化表达式; ②条件表达式; ④更新表达式){
③语句...
}
执行流程:
①执行初始化表达式,初始化变量(初始化表达式只会执行一次)
②执行条件表达式,判断是否执行循环:
如果为true,则执行循环③
如果为false,终止循环
④执行更新表达式,更新表达式执行完毕继续重复②
for 循环举例:
for (var i = 1; i <= 100; i++) {
console.log(i);
}
上方代码的解释:
上方代码的遍历步骤:
程序一运行,将执行var i = 1;这条语句, 所以i的值是1。
然后程序会验证一下i < 13是否满足,1<13是真,所以执行一次循环体(就是大括号里面的语句)。
执行完循环体之后,会执行i=i+4这条语句,所以i的值,是5。
程序会会验证一下i < 13是否满足,5<13是真,所以执行一次循环体(就是大括号里面的语句)。
执行完循环体之后,会执行i=i+4这条语句,所以i的值,是9。
程序会会验证一下i < 13是否满足,9<13是真,所以执行一次循环体(就是大括号里面的语句)。
执行完循环体之后,会执行i=i+4这条语句,所以i的值,是13。
程序会会验证一下i < 13是否满足,13<13是假,所以不执行循环体了,将退出循环。
最终输出输出结果为:1、5、9
接下来做几个题目。
题目 1:
for (var i = 1; i < 10; i = i + 3) {
i = i + 1;
console.log(i);
}
输出结果:2、6、10
题目 2:
for (var i = 1; i <= 10; i++) {}
console.log(i);
输出结果:11
题目 3:
for (var i = 1; i < 7; i = i + 3) {}
console.log(i);
输出结果:7
题目 4:
for (var i = 1; i > 0; i++) {
console.log(i);
}
死循环。
while 循环语句
while 循环
语法:
while(条件表达式){
语句...
}
执行流程:
while语句在执行时,先对条件表达式进行求值判断:
如果值为true,则执行循环体:
循环体执行完毕以后,继续对表达式进行判断
如果为true,则继续执行循环体,以此类推
如果值为false,则终止循环
如果有必要的话,我们可以使用 break 来终止循环。
do…while 循环
语法:
do{
语句...
}while(条件表达式)
执行流程:
do...while语句在执行时,会先执行循环体:
循环体执行完毕以后,在对while后的条件表达式进行判断:
如果结果为true,则继续执行循环体,执行完毕继续判断以此类推
如果结果为false,则终止循环
while 循环和 do…while 循环的区别
这两个语句的功能类似,不同的是:
- while 是先判断后执行,而 do…while 是先执行后判断。
也就是说,do…while 可以保证循环体至少执行一次,而 while 不能。
while 循环举例
题目:假如投资的年利率为 5%,试求从 1000 块增长到 5000 块,需要花费多少年?
代码实现:
<!DOCTYPE html>
<html lang="">
<head>
<meta />
<meta />
<meta />
<title>Document</title>
</head>
<body>
<script>
/*
* 假如投资的年利率为5%,试求从1000块增长到5000块,需要花费多少年
*
* 1000 1000*1.05
* 1050 1050*1.05
*/
//定义一个变量,表示当前的钱数
var money = 1000;
//定义一个计数器
var count = 0;
//定义一个while循环来计算每年的钱数
while (money < 5000) {
money *= 1.05;
//使count自增
count++;
}
console.log(money);
console.log('一共需要' + count + '年');
</script>
</body>
</html>
打印结果:
5003.18854203379
一共需要33年
另外,你也可以自己算一下,假如投资的年利率为 5%,从 1000 块增长到 1 万块,需要花费 48 年:
10401.269646942128
一共需要48年
break 和 continue
这个知识点非常重要。
break
- break 可以用来退出 switch 语句或退出整个循环语句(循环语句包括 for 循环、while 循环。不包括 if。单独的 if 语句里不能用 break 和 continue,否则会报错)。
- break 会立即终止离它最近的那个循环语句。
- 可以为循环语句创建一个 label,来标识当前的循环(格式:label:循环语句)。使用 break 语句时,可以在 break 后跟着一个 label,这样 break 将会结束指定的循环,而不是最近的。
举例 1:通过 break 终止循环语句
for (var i = 0; i < 5; i++) {
console.log('i的值:' + i);
if (i == 2) {
break; // 注意,虽然在 if 里 使用了 break,但这里的 break 是服务于外面的 for 循环。
}
}
打印结果:
i的值:0
i的值:1
i的值:2
举例 2:label 的使用
outer: for (var i = 0; i < 5; i++) {
console.log('外层循环 i 的值:' + i);
for (var j = 0; j < 5; j++) {
break outer; // 直接跳出outer所在的外层循环(这个outer是我自定义的label)
console.log('内层循环 j 的值:' + j);
}
}
打印结果:
外层循环 i 的值:0
continue
- continue 可以用来跳过当次循环,继续下一次循环。
- 同样,continue 默认只会离他最近的循环起作用。
- 同样,如果需要跳过指定的当次循环,可以使用 label 标签。
举例:
for (var i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue;
}
console.log('i的值:' + i);
}
打印结果:
i的值:1
i的值:3
i的值:5
i的值:7
i的值:9
练习:99 乘法表
代码实现:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title></title>
<style type="text/css">
body {
width: 2000px;
}
span {
display: inline-block;
width: 80px;
}
</style>
<script type="text/javascript">
/*
* 1.打印99乘法表
* 1*1=1
* 1*2=2 2*2=4
* 1*3=3 2*3=6 3*3=9
* 1*4=4 2*4=8 3*4=12 4*4=16
* ....9*9=81
*
* 2.打印出1-100之间所有的质数
*/
//创建外层循环,用来控制乘法表的高度
for (var i = 1; i <= 9; i++) {
//创建一个内层循环来控制图形的宽度
for (var j = 1; j <= i; j++) {
document.write('<span>' + j + '*' + i + '=' + i * j + '</span>');
}
//输出一个换行
document.write('<br />');
}
</script>
</head>
<body></body>
</html>
页面效果: