JavaScript 基础

说明: 本章节所介绍的 JavaScript 并不深入,因为在 Web 安全领域不需要对 JavaScript 掌握非常深入,我们只需要能够利用本章节提及到的一些语句对网站进行测试就足够了。

1)JavaScript 简介

1.1)JavaScript 背景

Web 前端有三层:

  • HTML:从语义的角度,描述页面结构。
  • CSS:从审美的角度,描述样式( 美化页面 )
  • JavaScript:从交互的角度,描述行为( 实现业务逻辑和页面控制 )

JavaScript 在红队攻防中具有重要的作用,了解 JS 在攻击和防御中的运用对于提高安全防护能力具有重要意义。

JavaScript 发展历史

JavaScript 诞生于 1995 年。网景公司的员工布兰登 • 艾奇(Brendan Eich,1961年~)在1995年开发出了 JavaScript 语言。
JavaScript 是由网景公司(Netscape)发明,最初命名为 LiveScript;1995 年 12 月与 SUN 公司合作,因市场宣传需要,改名为 JavaScript。
JavaScript 是 Sun 注册并授权给 Netscape 使用的商标。后来 Sun 公司被 Oracle 收购,JavaScript 版权归 Oracle 所有。

备注:由于 Sun 公司当时的 Java 语言特别火,所以为了傍大牌,就借势改名为 JavaScript

同时期还有其他的网页语言,比如 VBScript、JScript 等等,但是后来都被 JavaScript 打败了,所以现在的浏览器中,只运行一种脚本语言就是 JavaScript。

1996 年,微软为了抢占市场,推出了 JScript 在 IE3.0 中使用。
1996 年 11 月网景公司将 JS 提交给 ECMA (国际标准化组织) 成为国际标准,用于对抗微软。
JavaScript 是世界上用的最多的 脚本语言

JavaScript 的发展

2003 年之前,JavaScript 被认为** “牛皮癣”**,用来制作页面上的广告,弹窗、漂浮的广告。什么东西让人烦,什么东西就是 JavaScript 开发的。所以很多浏览器就推出了屏蔽广告功能。
2004 年,JavaScript 命运开始改变。那一年,谷歌公司开始带头使用 Ajax 技术,Ajax 技术 就是 JavaScript 的一个应用。并且,那时候人们逐渐开始提升用户体验了。Ajax 有一些应用场景。比如,当我们在百度搜索框搜文字时,输入框下方的智能提示,可以通过 Ajax 实现。比如,当我们注册网易邮箱时,能够及时发现用户名是否被占用,而不用调到另外一个页面。从 2005 年开始,几乎整个 B/S 开发界都在热情地追捧 Ajax。
2007 年乔布斯发布了第一款 iPhone,这一年开始,用户就多了上网的途径,就是用移动设备上网。JavaScript 在移动页面中,也是不可或缺的。并且这一年,互联网开始标准化,按照 W3C 规则三层分离,JavaScript 越来越被重视。
2010 年,人们更加了解 HTML5 技术,HTML5 推出了一个东西叫做 Canvas(画布),工程师可以在 Canvas 上进行游戏制作,利用的就是 JavaScript。
2011 年,Node.js 诞生,使 JavaScript 能够开发服务器程序了。
如今,WebApp 已经非常流行,就是用 网页技术开发手机应用
手机系统有 iOS、安卓。比如公司要开发一个“携程网”App,就需要招聘三队人马,比如 iOS 工程师 10 人,安卓工程师 12 人,前端工程师 8 人。共30人,开发成本大;而且如果要做需求迭代,就要改 3 个版本。现在,假设公司都用 Web 技术,用 HTML+CSS+JavaScript 这一套技术就可以开发多种终端的页面。也易于迭代(网页一改变,所有的终端都生效了)。
虽然目前 WebApp(Web应用)在功能和性能上的体验远不如 Native App(原生应用),但是“在原生 App 中内嵌一部分 H5 页面”已经是一种趋势。

1.2)JavaScript 介绍

JavaScript 入门易学性
  • JavaScript 对初学者比较友好、简单易用。可以使用任何文本编辑工具编写,只需要浏览器就可以执行程序。
  • JavaScript 是有界面效果的(相比之下,C 语言只有白底黑字)。
  • JavaScript 是 **弱变量类型 **的语言,变量只需要用 var/let/const 来声明。而 Java 中变量的声明,要根据变量的类型来定义。

**举例:**比如 Java 中需要定义如下变量:

int a;			// 整型
float a;		// 单精度浮点类型
double a;		// 双精度浮点类型
String a;		// 字符串类型
boolean a;		// 布尔型

而 JavaScript 中,只需要用一种方式来定义:

// ES5 写法
var a;

// ES6 写法
const a;	// 常量
let a;		// 变量
JavaScript 是脚本语言

JavaScript 运行在用户的终端网页上,而不是服务器上,此时我们称之为** “前端语言”**。就是服务于页面的交互和视觉,不能直接操作数据库。

**“后端语言” **是运行在服务器上的,比如 PHP、ASP、JSP 等等,这些语言都能够操作数据库,能够对数据库进行 “增删改查” 操作。

**备注:**Node.js 是用 JavaScript 开发的,我们也可以用 Node.js 技术进行服务器端编程。

虽然,前端语言 JavaScript 无法直接操作后端数据库。不过我们 Web 安全工程师可以利用前端的漏洞操作以执行未授权的后端操作,包括访问、篡改或删除敏感数据。因此,对前端代码的审查和安全性加固同样重要,以确保整个 Web 应用程序的安全性。

JavaScript 的组成

JavaScript 基础分为三个部分:

  • ECMAScript:JavaScript 的 语法标准。包括 变量、表达式、运算符、函数、if 语句、for 语句 等。
  • DOM:Document Object Model(文档对象模型),操作页面上的元素的 API。比如 让盒子移动、变色、改变大小、轮播图 等等。
  • BOM:Browser Object Model(浏览器对象模型),操作浏览器部分功能的 API。通过 BOM 可以 操作浏览器窗口,比如弹框、控制浏览器跳转、获取浏览器分辨率 等等。

**通俗理解就是:**ECMAScript 是 JS 的语法;DOM 和 BOM 浏览器运行环境为 JS 提供的 API。

1.3)JavaScript 的特点

特点 1:解释型语言

JavaScript 是解释型语言,不需要事先被翻译为机器码;而是边翻译边执行(翻译一行,执行一行)。

特点 2:单线程

参考:https://www.yuque.com/blogking/cookie/el4ro2krublv9t3r

JavaScript 是单线程的,同一时刻只能执行特定的任务。

特点 3:ECMAScript 标准
  • ECMAScript 是一种由 ECMA 国际( 前身为欧洲计算机制造商协会,英文名称是 European ComputerManufacturers Association)制定和发布的脚本语言规范。
  • JavaScript 是由公司开发而成的,问题是不便于其他的公司拓展和使用。所以欧洲的这个 ECMA 的组织,牵头制定 JavaScript 的标准,取名为 ECMAScript。
  • 简单来说,ECMAScript 不是一门语言,而是一个标准。ECMAScript 规定了 JS 的编程语法和基础核心知识,是所有浏览器厂商共同遵守的一套 JS 语法工业标准。
  • ECMAScript 在 2015 年 6 月,发布了 ECMAScript 6 版本(ES6),语言的能力更强(也包含了很多新特性)。
  • ECMA 赋予了 JavaScript 新的能力和活力。

1.4)编程语言的分类

翻译器

计算机不能直接理解任何除机器语言以外的语言,所以必须要把程序员所编写的语言翻译成机器语言,计算机才能执行程序。程序语言翻译成机器语言的工具,被称为翻译器。
由此可见,所谓的 “翻译”,指的是翻译成计算机能够执行的指令。

翻译器翻译的方式有两种:一种是编译,另一种是解释。两种方式之间的区别在于翻译的时机不同。

  • **编译器:**在代码执行之前,事前把所有的代码一次性翻译好,生成中间代码文件,然后整体执行。
  • **解释器:**边翻译,边执行(在代码执行时进行及时翻译,并立即执行)。当编译器以解释的方式运行时,也称之为解释器。

对应的语言,称之为 “编译型语言”“解释型语言”

编译型语言
  • **定义:**事先把所有的代码一次性翻译好,然后整体执行。
  • **优点:**运行更快。
  • **不足:**移植性不好,无法跨平台。
  • 编译型语言举例:C、C++、Go

比如说,C 语言的代码文件是 .c 后缀,翻译之后文件是 .obj 后缀,系统执行的是 obj 文件;再比如,Java 语言的代码文件是 .java 后缀,翻译之后的文件是 .class 后缀。( 注意,Java 语言不是严格的编译型语言,这个一会儿会讲)

解释型语言
  • 定义:边翻译边执行(翻译一行,执行一行),不需要事先一次性翻译。
  • **优点:**移植性好,跨平台。
  • **缺点:**运行更慢。( 慢是相对来说,安全的需求还没达到机器极限性能 )
  • 解释型语言举例:JavaScript、PHP、Python。
Java 语言

**Java 语言:**既不是编译型语言,也不是解释型语言。翻译过程:
(1)编译: .java 代码文件先通过 javac 命令编译成 .class 文件。
(2)执行: .class 文件再通过 jvm 虚拟机,解释执行。有了 jvm 的存在,让 java 跨平台了。
既有编译过程,也有解释执行的过程。

1.5)JavaScript 入门

安装 JavaScript

首先需要安装 nodejs

下载地址:https://nodejs.org/en/download/prebuilt-installer
然后 VSCode 安装 Code Runner 插件
image.png

开始写第一行 JavaScript 代码

JavaScript 代码的书写位置在哪里呢?
这个问题,也可以理解成:引入 JS 代码,有哪几种方式。

**方式一:**行内式
代码举例:

<input type=="button" value="点我点我" οnclick="alert('网络安全')" />

image.png

完整的可执行代码如下:

<!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="点我点我" οnclick="alert('网络安全')" />
  </body>
</html>

分析:

  • 可以 将单行或少量 JS 代码写在 HTML 标签的事件属性 中(以 on 开头的属性),比如放在上面的 onclick 点击事件中。
  • 这种书写方式,不推荐使用,原因是:可读性差,尤其是需要编写大量 JS 代码时,容易出错;引号多层嵌套时,也容易出错。
  • 关于代码中的「引号」,在 HTML 标签中推荐使用双引号,JS 中推荐使用单引号。

**方式二:**内嵌式

我们可以在 html 页面的 标签里放入 标签对,并在

    <script type="text/javascript">
        //在这里书写 JS 代码
        alert('网络安全');
        console.log('树立网安意识,莫念无名之利.');
    </script>

image.png
image.png
分析:

  • text 表示纯文本,因为 JavaScript 也是一个纯文本的语言。
  • 可以将多行 JS 代码 写到

扩展知识:https://cloud.tencent.com/developer/article/1612474
XSS 漏洞:利用 XSS 漏洞,黑客可以在网站中输入恶意代码,最终被渲染到 HTML 网页,攻击浏览网页的用户。
跨网站脚本(Cross-site scripting,XSS) 又称为 跨站脚本攻击,是一种经常出现在 Web 应用程序的安全漏洞攻击,也是代码注入的一种。XSS 是由于 Web 应用程序对用户的输入过滤不足而产生的,攻击者利用网站漏洞把恶意的脚本代码注入到网页之中,当其他用户浏览这些网页时,就会执行其中的恶意代码,对受害者用户可能采取 Cookie 窃取、会话劫持、钓鱼欺骗等各种攻击。这类攻击通常包含了 HTML 以及用户端脚本语言。
XSS 攻击 通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是 JavaScript,但实际上也可以包括 Java、VBScript、ActiveX、 Flash或者甚至是普通的 HTML。攻击成功后,攻击者可能得到更高的权限(如执行一些操作)、私密网页内容、会话和 cookie 等各种内容。
**总结:**当一个网页的信息输入框没有正确地过滤和转义用户输入时,就可能会被攻击者利用来注入跨站脚本(XSS)。
image.png

参考:https://blog.youkuaiyun.com/weixin_46709219/article/details/109698457

:::info
验证一个站点是否可以被 XSS 注入

  1. 站点输入框是否可以填写 JS 代码
  2. 填写的 JS 代码是否可以实现对应效果

满足上述条件其一,则该站点存在 XSS 注入漏洞风险
:::

// 这段代码是一个简单的 JavaScript 脚本
// 它会在网页加载时弹出一个警告框, 显示当前页面的 cookie 信息.
// 这种操作可能会被用于恶意目的, 例如盗取用户的登录凭据或其他敏感信息.
// 因此, 在编写和执行类似脚本时需要格外小心, 确保安全性和合法性.
<script> alert(document.cookie); </script>
<script> alert('Hack'); </script>
<script> print('Hack'); </script>


什么是 XSS 攻击?
XSS(跨站脚本)攻击是一种常见的安全漏洞。它发生在恶意用户将代码(通常是 JavaScript 脚本)注入到网页中。当其他用户浏览该网页时,这段恶意代码会在他们的浏览器中运行,可能会窃取数据、劫持用户会话或执行其他恶意操作。

HTML 实体编码 是怎么防护 XSS 攻击的?
当你在网页中显示用户输入的内容时,直接显示这些内容可能会带来安全风险。
例如,如果一个用户在评论中输入了:

<script>alert('XSS');</script>

如果没有保护措施,这段代码会直接在网页上执行,显示一个弹窗。这就是一个 XSS 攻击。
HTML 实体编码通过 将特殊字符转换为不会被浏览器解释为代码的形式,来防止这种情况发生。

如何工作?

  1. 特殊字符转义:HTML实体编码会将像 < 、> 这些特殊字符转换成 &lt;、&gt; 这样一来,即使用户输入了恶意代码,浏览器也不会执行它,而是显示这些字符。
  2. 示例
    • 用户输入:<script>alert('XSS');</script>
    • 经过 HTML 实体编码后:&lt;script&gt;alert('XSS');&lt;/script&gt;

浏览器会将编码后的内容显示为普通文本:

<script>alert('XSS');</script>

而不会执行它。

通俗类比
想象一下,你在写一个公告板,每个人都可以贴便条。如果有人贴了一张上面写着“爆炸”的便条,别人看到可能会惊慌失措。但如果你在展示这些便条前,先把“爆炸”这个词改成“[危险词汇]”,那么别人看到就知道这是一个不能直接展示的词,而不会被吓到。
同样的道理,HTML 实体编码就是在把“危险”的字符换成“安全”的字符,让浏览器知道这些只是普通文字,而不是需要执行的代码。

总结
HTML 实体编码通过将用户输入的特殊字符转化为不会被浏览器执行的形式,有效地防止了 XSS 攻击。这样,用户输入的任何恶意代码都会被当作普通文本显示,而不会对其他用户造成威胁。


**方式三:**引入外部的 JS 文件

这种引入外部 JS 文件的方式,更难以让运维人员发现其漏洞。

<!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>
    <!-- 这里引入外部的 JS 文件 -->
    <script src="tool.js"></script>
    <!-- <script src="https://www.hacker.com/tool.js"></script> -->
</body>
</html>

image.png

tool.js 文件内容( 所有 JS 内容都写入在该 JS 文件中 )

alert('网络安全');

image.png

上面这段代码,依然是放到 body 标签里,可以和内嵌的 JS 代码并列。
另外,引用外部 JS 文件的 script 标签中间不可以再写代码。

总结:
我们在实战开发中,基本都是采用方式三,因为这种方式,可以确保 HTML 文件和 JS 文件是分开的,有利于代码的结构化和复用。很少会有人把一大堆 JS 代码塞到 HTML 文件里。但是在学习 Web 安全时,我们都是通过方式一或方式二去进行漏洞发现或利用的。

JS 的一些简单语法规则
  1. JavaScript 对换行、缩进、空格不敏感,每一条语句以分号结尾。
<script type="text/javascript">
    alert('床前明月光');
    alert('疑是地上霜');
</script>

等价于:

<script type="text/javascript">
    alert('床前明月光');alert('疑是地上霜');
</script>

**备注:**每一条语句末尾要加上分号,虽然分号不是必须加的,如果不写分号,浏览器会自动添加,但是会消耗一些系统资源。

  1. 所有的符号,都是英语的,比如括号、引号、分号。

  2. 严格区分大小写

注释

我们不要把 HTML、CSS、JavaScript 三者的注释格式搞混淆了。

  1. HTML 的注释
<!-- 我是注释 -->
  1. CSS 的注释
<style type="text/css">

/*
我是注释
*/

p{
  font-weight: bold;
  font-style: italic;
  color: red;
}

</style>

注意:CSS 只有 /* */ 这种注释,没有 // 这种注释。
而且注释要写在

  1. JavaScript 的注释

单行注释:

// 我是注释

多行注释:

/*
    多行注释
    多行注释
*/

**补充:**VS Code 中,单行注释的快捷键是「Ctrl + /」,多行注释的默认快捷键是 Alt + Shift + A。
当然,如果你觉得多行注释的默认快捷键不方便,我们还可以修改默认快捷键。操作如下:首选项 --> 键盘快捷方式 --> 查找“注释”这两个字 --> 将原来的快捷键修改为「Ctrl + Shift + /」

JavaScript 输入输出语句

参考:https://juejin.cn/post/7000740929636139038

  1. 弹出警告框:alert 语句

我们要学习的第一个语句,就是 alert 语句。

alert(英文翻译为 “警报”)的用途:弹出 “警告框”

    <script>
        alert('马哥教育');
    </script>

image.png

总结:
alert() 方法是 显示一条弹出提示消息和确认按钮的警告框。
需要注意的是 :alert() 是一个阻塞的函数,如果我们不点确认按钮,后面的内容就不会加载出来。( 并且我们也可以在 alert 中填写命令,比如 document.cookie 等。则会执行该命令返回其执行结果 )

  1. **控制台输出:
  • console.log(“”) 表示在控制台中输出。console 表示“控制台”,log 表示“输出”。
  • 在浏览器中,按 F12 即可打开控制台,选择「console」栏,即可看到打印的内容。
<script>
    console.log("恭喜你成功引起了我的注意");
</script>

image.png

**控制台:**是工程师、程序员调试程序的地方。程序员经常使用这条语句输出一些东西,来测试程序是否正确。

普通人是不会在意控制台的,但是有些网站另藏玄机。有个很有意思的地方是,百度首页的控制台,悄悄地放了一段招聘信息:
image.png
毕竟做前端的人是经常使用控制台的。
**总结:**alert() 主要用来显示消息给用户,console.log() 用来给程序员自己调试用的。

  1. 弹出输入框:prompt 语句

prompt() 就是专门用来弹出能够让用户输入的对话框。用得少,测试的时候偶尔会用。

<script>
    var a = prompt("请输入你的看法");
    console.log(a);
</script>

image.png
image.png
上方代码中,用户输入的内容,将被传递到变量 a 里面,并在控制台打印出来。

prompt() 语句中,用户不管输入什么内容,都是字符串。
alert() 和 prompt() 的区别:

  • alert() 可以直接使用。
  • prompt() 会返回用户输入的内容。我们可以用一个变量,来接收用户输入的内容。

示例:

<!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>
        //方式一
        prompt("开心吗?"); // 这个显示内容也可以不写, 但就没有交互的意义了.
        
        //方式二
        var x; 
        var name=prompt("请输入你的名字","Hacker"); //显示默认文本 "Hacker"
        if (name!=null && name!=""){ 
         x="你好! " + name + "。";
         document.write(x)
        } 
    </script>
</body>
</html>

2)JavaScript 变量

02-变量.pdf

2.1)字面量:数字与字符串

“字面量” 即 常量,是固定值,不可改变。看见什么,它就是什么。
字面量有 3 种:

  • 数字
  • 字符串
  • 布尔字面量 True False
  1. 数值的字面量 非常简单,写上去就行,不需要任何的符号。例如:
alert(886); // 886 是数字, 所以不需要加引号.

image.png

  1. 字符串的字面量 也很简单,但一定要加上引号,可以是单词、句子等。例如:
console.log('886');
console.log('网络安全');

**因此:**886 是数字,‘886’ 是字符串。
image.png

  1. 布尔字面量 举例:
if (true) {
  console.log('如果为真,就走这里面的代码');
}

image.png

总结:
常量都可以直接使用,但是我们一般不会直接使用常量。
**如果直接使用常量的话,比较麻烦。**比如说,多个地方要用到同一个常量,还不如事先定义一个变量,用来保存常量。
变量更方便我们使用,所以在开发中都是通过变量去保存一个字面量,而不会直接使用字面量。


案例展示:
常数(不变)变量,即永远不会改变的变量,使用关键字const声明
如果使用了 const 声明来了 常量,它们将不能修改,否则会报错;

// 圆周率设置为常量
const PI = 3.14; 

// 如果尝试重新赋值PI = 4.66; //报错invalid assignment to const `PI'

作为一个**“常数”**,首先是意味着值永远不变。其次,大致可以分为两种情况:
1、在执行之前就已知了( 比如十六进制值的颜色,#000000,就代表的黑色 )
2、在执行期间被“计算”出来,但初始赋值之后就不会改变。(比如页面的加载时间)
根据情况的不同,命名方式也有点区别:

  • **情况一:**建议使用【大写字母+下划线】
比如声明颜色常量:
const COLOR_RED = "#F00";
const COLOR_GREEN = "#0F0";
const COLOR_BLUE = "#00F";
const COLOR_ORANGE = "#FF7F00";

// 当我们需要选择一个颜色
let color = COLOR_ORANGE;
alert(color); // #FF7F00

**优点:**COLOR_RED 一看就知道是红色,比 #F00 更好记,可以提高代码可读性。

  • **情况二:**使用常规的变量命名
const pageLoadTime = /* 网页加载所需的时间 */;

因为pageLoadTime 虽然是个常量,即值在赋值之后不会改变。但是他的值在页面加载之前是未知的,所以采用常规命名。


2.2)变量的概念

参考:https://www.yuque.com/blogking/cookie/ixfgcuhiwgb2ttg9

**变量:**是用于存放数据的容器。我们通过「变量名」获取数据,甚至可以修改数据。
变量还可以用来保存字面量( 常量 )。
**本质:**变量是程序在内存中申请的一块用来存放数据的空间。

  1. 变量的声明

在 ES6 语法之前,统一使用 var 关键字来声明一个变量。比如:

var name; // 声明一个名为 name 的变量

PS:在 JavaScript 中,永远都是用 var 来定义变量(在 ES6 之前),这和 C、Java 等语言不同。

在 ES6 语法之后,可以使用 constlet 关键字来声明一个变量

如果你想定义一个常量,就用 const;如果你想定义一个变量,就用 let。

const name; // 声明一个名为 name 的常量
 
let age; // 声明一个名为 age 的变量
  1. 变量的赋值

直接赋值

title = '网络安全'

变量的声明和赋值,写在一起,举例如下:

var a = 100; // ES5 语法
 
const b = 'hello'; // ES6 语法
let b = 'world'; 	 // ES6 语法

如下图所示:

image.png
var 是英语 “variant” 变量的缩写。后面要加一个空格,空格后面的东西就是 “变量名”:

  • 定义变量:var 就是一个关键字,用来定义变量。所谓关键字,就是有特殊功能的单词。
  • 变量的赋值:等号表示赋值,将等号右边的值,赋给左边的变量。
  • 变量名:我们可以给变量任意的取名字。
  1. 变量的初始化

**有经验的程序员,**会把声明和赋值写在一起:

var a = 100; //声明, 并且赋值100

console.log(a); //输出 100

声明一个变量并赋值, 我们称之为 变量的初始化
image.png

2.3)变量声明和赋值的补充

  1. 修改变量的值

一个变量被重新复赋值后,它原有的值就会被覆盖,变量值将以最后一次赋的值为准。

var a = 100;
a = 110;

console.log(a); // 打印结果: 110. 因为 110 覆盖了 100

image.png

  1. 同时声明多个变量

同时声明多个变量时,只需要写一个 var, 多个变量名之间用英文逗号隔开。

var name = '马哥教育', age = 27, number = 100;

console.log(name);
console.log(age);
console.log(number);

image.png

  1. 变量声明的几种特殊情况

变量建议先声明,再使用。否则可能会报错。具体如下。

**写法 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); // 会报错

控制台会报错:( a is not defined )

image.png
**补充:**写法 1 和写法 2 虽然都正常,但这两种写法是有区别的,等以后学习了「变量提升」的概念就明白了。大多数情况下,我们都是采用的写法 1( 最规范 )。

2.4)变量的命名规范

大写字母是可以使用的,并且大小写敏感。也就是说 A 和 a 是两个变量。

var A = 250; // 变量 1

var a = 888; // 变量 2

我们来整理一下 变量的命名规范

  • 只能由字母(A-Z、a-z)、数字(0-9)、下划线(_)、美元符( $ )组成
  • 不能以数字开头。也就是说,必须以字母(A-Z、a-z)、下划线(_)或者美元符( $ )开头。变量名中不允许出现空格。
  • 不用使用 JS 语言中保留的「关键字」和「保留字」作为变量名。
  • 建议用驼峰命名规则。比如 getElementById、matherAndFather、aaaOrBbbAndCcc
  • 变量名会区分大小写 ( JavaScript 是区分大小写的语言) 。
  • 变量名长度不能超过 255 个字符。
  • 汉语可以作为变量名。但是不建议使用,因为 Low。

2.5)标识符、关键字、保留字

  1. 标识符
  • 标识符:在 JS 中所有的可以由我们自主命名的都可以称之为标识符。
  • 例如:变量名、函数名、属性名、参数名 都是属于标识符。通俗来讲,标识符就是我们写代码时为它们起的名字。
  • 标识符的命名规则和变量的命令规则是一样的。看上面一段就可以了。
  • 同样,标识符不能使用语言中保留的关键字保留字。如下。
  1. 关键字

关键字:是指 JS 本身已经使用了的单词,我们不能再用它们充当变量、函数名等标识符。
:::info
JS 中的
关键字
如下:
:::

breakcontinuecasedefaultifelseswitchforindowhiletry、catch、finally、throwvarvoidfunctionreturnnewthistypeofinstanceofdeletewithtruefalsenullundefined
  1. 保留字

保留字:实际上就是 预留的 “关键字”。意思是现在虽然还不是关键字,但是未来可能会成为关键字,同样不能使用它们当充当变量名、函数名等标识符。
:::info
JS 中的
保留字
如下:
:::

abstract、boolean、byte、char、classconstdebugger、double、enumexportextends、final、float、goto
implementsimport、int、interface、long、native、packageprivateprotectedpublic、short、staticsuper、synchronized、throws、
transient、volatile

:::info
**举例:**交换两个变量的值
:::

var a1 = 100;
var a2 = 200;

var temp;			// 利用第三个变量 temp 来帮助 a1 a2 完成变量的交换

temp = a1;
a1 = a2;
a2 = temp;
console.log(a1);
console.log(a2);

3)JavaScript 数据类型

03-变量的数据类型:基本数据类型和引用数据类型.pdf

3.1)变量的数据类型

为什么需要数据类型

在计算机中,不同的数据所需占用的存储空间不同,为了充分利用存储空间,于是定义了不同的数据类型。而且,不同的数据类型,寓意也不同。

我们知道,JavaScript 无论这个变量是字符串类型,还是数字类型,我们都可以直接用 var 去定义它。

var a = 'hello word';

var b = 123;

为什么可以这样做呢?这是因为:JavaScript 是一种「弱类型语言」,或者说是一种「动态语言」,这意味着不需要提前声明变量的类型,在程序运行过程中,类型会自动被确定。

**JS 的变量数据类型,是在程序运行的过程中,根据等号右边的值来确定的。**而且,变量的数据类型是可以变化的。比如说:

var name = 'magedu';

name = 123; // 强制将变量 name 修改为 数字类型
JS 中一共有 六种数据类型
  • **基本数据类型(值类型):**String 字符串、Number 数值、Boolean 布尔值、Null 空值、Undefined 未定义。
  • **引用数据类型(引用类型):**Object 对象。

**注意:**内置对象 Function、Array、Date、RegExp、Error 等都是属于 Object 类型。也就是说,除了那五种基本数据类型之外,其他的,都称之为 Object 类型。

面试问:引用数据类型有几种?

面试答:只有一种,即 Object 类型。

数据类型之间最大的区别:

  • 基本数据类型:参数赋值的时候,传数值。
  • 引用数据类型:参数赋值的时候,传地址(修改的同一片内存空间)。

3.2)一个经典的例子

基本数据类型 举例:
var a = 23;
var b = a;

a++; //数值加1, 地址不变

console.log(a); // 打印结果:24
console.log(b); // 打印结果:23

上面的代码中:a 和 b 都是基本数据类型,让 b 等于 a,然后 改变 a 的值之后,发现 b 的值并没有被改变。
但是在引用数据类型中,就不同了,我们来看一看。

引用数据类型 举例:
var obj1 = new Object();
obj1.name = 'magedu';

// 让 obj2 等于 obj1
var obj2 = obj1;

// 修改 obj1 的 name 属性
obj1.name = '网络安全';

console.log(obj1.name); // 打印结果: 网络安全
console.log(obj2.name); // 打印结果: 网络安全

上面的代码中:obj1 和 obj2 都是引用数据类型,让 obj2 等于 obj1,然后 修改 obj1.name 的值之后,发现 obj2.name 的值也发生了改变。
从上面的例子中,可以反映出,基本数据类型和引用数据类型是有区别的。
那到底有什么区别呢?我们进一步往下看。

:::info
HTML 源代码断点调试:https://blog.youkuaiyun.com/qq_45890970/article/details/122549384

  1. 断点 5:obj1.name = magedu obj2 = undefined
  2. 断点 8:obj1.name = magedu obj2.name = magedu
  3. 断点 10:obj1.name = 网络安全 obj2.name = 网络安全
    :::
    image.png

3.3)栈内存 和 堆内存

我们首先记住一句话:JS 中,所有的变量都是保存在栈内存中的。
然后来看看下面的区别。

  1. 基本数据类型:

基本数据类型的值,直接保存在栈内存中。值与值之间是独立存在,修改一个变量不会影响其他的变量。

  1. 引用数据类型:

对象是保存到堆内存中的。
每创建一个新的对象,就会在堆内存中开辟出一个新的空间;而变量保存了对象的内存地址(对象的引用),保存在栈内存当中。如果两个变量保存了同一个对象的引用,当一个变量修改属性时,另一个变量也会受到影响。

引用数据类型 可以理解为 硬链接,基本数据类型可以理解为是 源文件复制

引用数据类型 就像是硬链接,因为它们存储的是指向数据的地址;
基本数据类型 则像是源文件的复制,因为它们直接存储数据的值。

4)基本数据类型:String 与 Boolean

04-基本数据类型:String 和 Boolean.pdf

4.1)String 字符串

语法

字符串型可以是引号中的任意文本,其语法为:双引号 "" 或者单引号 ''

来看个示例。下面的这些,都是字符串:

var a = "www.magedu.com";
var b = "马哥教育";
var c = "网络安全";
var d = 'JavaScript';
var e = ""; // 空字符串

console.log(typeof a);	// typeof: 查看变量的数据类型
console.log(typeof b);
console.log(typeof c);
console.log(typeof d);
console.log(typeof e);

控制台输出如下:

string
string
string
string
string

image.png

错误案例:

var f = haha; // 由于没使用引号, 这里会直接报错

image.png
:::info
typeof 是一个 JavaScript 关键字,当你使用它时,将返回变量的类型。
:::

引号的注意事项
  1. 单引号和双引号不能混用。比如下面这样写是不可以的:
var str = 'hello"; // 报错: Uncaught SyntaxError: Invalid or unexpected token

image.png

  1. 同类引号不能嵌套:双引号里不能再放双引号,单引号里不能再放单引号,但是可以用 \ 实现转义。

image.png
image.png

  1. 单引号里可以嵌套双引号;双引号里可以嵌套单引号。

image.png
image.png

转义字符

在字符串中我们可以使用 \ 作为转义字符,当表示一些特殊符号时可以使用 \ 进行转义。

  • \" 表示 " 双引号
  • \' 表示 ’ 单引号
  • \\ 表示 \
  • \r 表示回车
  • \n 表示换行。n 的意思是 newline。
  • \t 表示缩进。t 的意思是 tab。
  • \b 表示空格。b 的意思是 blank。

举例:

var str1 = "我说:\"今天\t天气真不错!\"";
var str2 = "\\\\\\";

console.log(str1);
console.log(str2);

上方代码的打印结果:

我说:"今天	天气真不错!"
\\\

image.png

获取字符串的长度

字符串是由若干个字符组成的,这些字符的数量就是字符串的长度。我们可以通过字符串的 length 属性可以获取整个字符串的长度。
代码举例:

var str1 = '马哥教育';
var str2 = 'www.magede.com';

var str3 = 'magedu';
var str4 = 'magedu, keep moving!';

console.log(str1.length); // 4
console.log(str2.length); // 14
console.log(str3.length); // 6
console.log(str4.length); // 20

image.png
:::info
由此可见,字符串的 length 属性,在判断字符串的长度时,会认为:

  • 一个中文算一个字符,一个英文算一个字符
  • 一个标点符号(包括中文标点、英文标点)算一个字符
  • 一个空格算一个字符
    :::
字符串拼接

多个字符串之间可以使用加号 **+** 进行拼接。

**拼接语法:**字符串 + 任意数据类型 = 拼接之后的新字符串;
**拼接原理:**拼接前,会把与字符串相加的这个数据类型转成字符串,然后再拼接成一个新的字符串。

:::info
代码举例:( 字符串与六大数据类型相加 )
:::

var str1 = '马哥教育';
var str2 = '马哥教育' + 666;
var str3 = '马哥教育' + true;
var str4 = '马哥教育' + null;
var str5 = '马哥教育' + undefined;

console.log(str1);
console.log(str2);
console.log(str3);
console.log(str4);
console.log(str5);

:::info
打印结果:
:::

马哥教育
马哥教育666
马哥教育true
马哥教育null
马哥教育undefined

image.png

代码举例

var obj = { name: '马哥教育', age: 28 };
var str6 = '马哥教育' + obj;

console.log(str6);

image.png

字符串的不可变性

字符串里面的值不可被改变。虽然看上去可以改变内容,但其实是地址变了,内存中新开辟了一个内存空间。
代码举例:

var str = 'hello';

str = 'magedu';

比如上面的代码,当重新给变量 str 赋值时,常量 hello 不会被修改,依然保存在内存中;str 会改为指向 magedu
image.png

模板字符串(模板字面量)

ES6 中引入了 模板字符串,让我们省去了字符串拼接的烦恼。下面一起来看看它的特性。

在模板字符串中插入变量

以前,让字符串进行拼接的时候,是这样做的:( 传统写法的字符串拼接 )

var name = 'magedu';
var age = '26';
console.log('name:' + name + ',age:' + age); // 传统写法

这种写法,比较繁琐,而且容易出错。
image.png

现在,有了 ES6 语法,字符串拼接可以这样写:

var name = 'magedu';
var age = '26';

console.log('我是' + name + ',age:' + age); // 传统写法

console.log(`我是${name},年龄:${age}`); // ES6 写法, 注意语法格式

**注意:**上方代码中,倒数第二行用的符号是单引号,最后一行用的符号是反引号( 在 Tab 键的上方)。
image.png

在模板字符串中插入表达式

以前,在字符串中插入表达式的写法必须是这样的:

const a = 5;
const b = 10;
console.log('this is ' + (a + b) + ' and\nnot ' + (2 * a + b) + '.');

image.png

现在,通过模板字符串,我们可以使用一种更优雅的方式来表示:

const a = 5;
const b = 10;

// 下面这行代码, 故意做了换行.
console.log(`this is ${a + b} and
not ${2 * a + b}.`);

image.png

模板字符串中可以换行

因为模板字符串支持换行,所以可以让代码写得非常美观。
代码举例:

const result = {
  name: 'magedu',
  age: 28,
  sex: '男',
};

// 模板字符串支持换行
const html = `<div>
    <span>${result.name}</span>
    <span>${result.age}</span>
    <span>${result.sex}</span>
</div>`;

console.log(html); // 打印结果也会换行

image.png
image.png

模板字符串中可以调用函数

模板字符串中可以调用函数。字符串中调用函数的位置,将会显示函数执行后的返回值。

function getName() {
    return 'magedu';
}

console.log(`www.${getName()}.com`); // 打印结果: www.magedu.com

image.png

模板字符串支持嵌套使用
const nameList = ['马哥教育', '渗透测试', '网络安全'];

function myTemplate() {
  // join('') 的意思是, 把数组里的内容合并成一个字符串
  return `<ul>
    ${nameList
      .map((item) => `<li>${item}</li>`)
      .join('')}
    </ul>`;
}
document.body.innerHTML = myTemplate();

image.png
image.png

布尔值:Boolean

布尔型有两个值:true 和 false。主要用来做逻辑判断: true 表示真,false 表示假。
布尔值直接使用就可以了,千万不要加上引号。( 加上引号不就变成字符串类型了 )
代码:

var a = true;
console.log(typeof a);

控制台输出结果:

boolean	// 布尔类型

布尔型和数字型相加时, true 按 1 来算 ,false 按 0 来算。

var str1 = 1;
var str2 = str1 + true;

console.log(str1);
console.log(str2);

image.png

5)基本数据类型:Number

05-基本数据类型:Number.pdf

5.1)数值型: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 类型的。

5.2)数值范围

由于内存的限制,ECMAScript 并不能保存世界上所有的数值。

  • 最大值: Number.MAX_VALUE,这个值为:1.7976931348623157e+308
  • 最小值: Number.MIN_VALUE,这个值为: 5e-324

如果使用 Number 表示的变量超过了最大值,则会返回 Infinity

  • 无穷大(正无穷):Infinity
  • 无穷小(负无穷):-Infinity

注意:typeof Infinity的返回结果是 number。

var a = Infinity;
console.log(typeof a);

5.3)NaN

**NaN:**是一个特殊的数字,表示 Not a Number,非数值。比如:

console.log("abc" / 18);		 // 结果是 NaN

console.log("abc" * "abcd"); // 按理说, 字符串相乘是没有结果的, 但如果你非要让 JS 去算, 它就一定会给你一个结果. 结果是 NaN

注意:typeof NaN的返回结果是 number
Undefined 和任何数值计算的结果为 NaN。NaN 与任何值都不相等,包括 NaN 本身。

5.4)连字符和加号的区别

键盘上的 + 可能是连字符,也可能是数字的加号。如下:

console.log("我" + "爱" + "你"); // 连字符, 把三个独立的汉字, 连接在一起了

console.log("我+爱+你"); // 原样输出

console.log(1+2+3);	// 输出 6

输出:

我爱你
我+爱+你
6

总结:如果加号两边都是 Number 类型,此时是数字相加。
否则,就是连字符( 用来连接字符串 )。
提问:“我+爱+你” 中的 + 是连字符还是加号?是字符串。

举例 1:

var a = "1";	// 字符串 1
var b = 2;		// 数值 2

console.log(a + b);

控制台输出:

12

举例 2:

没加双引号就是变量,加双引号就转换成字符本身。

var a = 1;
var b = 2;
console.log("a" + b); // "a" 就不是变量了!所以就是 "a"+2 输出 a2

控制台输出:

a2

于是我们明白了,在变量中加入字符串进行拼接,可以被同化为字符串。【重要】

5.5)隐式转换

我们知道,"2"+1 得到的结果其实是字符串21,但是"2"-1 得到的结果却是数值 1,这是因为计算机自动帮我们进行了**“隐式转换”**。
也就是说,-*/% 这几个符号会自动进行隐式转换。例如:

var a = "4" + 3 - 6;

console.log(a);			// 输出 37

输出结果:

37

虽然程序可以对 -*/% 这几个符号自动进行**“隐式转换”**;但作为程序员,我们最好自己完成转换,方便程序的可读性。

5.6)浮点数的运算

运算精度问题

在 JS 中,整数的运算基本可以保证精确;但是**小数的运算,可能会得到一个不精确的结果。**所以,千万不要使用 JS 进行对精确度要求比较高的运算。

var a = 0.1 + 0.2;

console.log(a); // 打印结果: 0.30000000000000004

上方代码中,打印结果并不是 0.3,而是 0.30000000000000004
:::info
这是因为,计算机在做运算时,所有的运算都要转换成二进制去计算。然而,有些数字转换成二进制之后,无法精确表示。比如说,0.1 和 0.2 转换成二进制之后,是无穷的,因此存在浮点数的计算不精确的问题。
:::

处理数学运算的精度问题

如果只是一些简单的精度问题,可以使用 toFix() 方法进行小数的截取。
在实战开发中,关于浮点数计算的精度问题,往往比较复杂。市面上有很多针对数学运算的开源库,比如 decimal.js、 Math.js。这些开源库都比较成熟,我们可以直接拿来用。

  • **Math.js:**属于很全面的运算库,文件很大,压缩后的文件就有 500 kb。如果你的项目涉及到大型的复杂运算,可以使用 Math.js。
  • decimal.js:属于轻量的运算库,压缩后的文件只有 32 kb。大多数项目的数学运算,使用decimal.js 足够了。

在使用这几个开源库时,既可以用 cdn 的方式引入,也可以用 npm 包的方式引入。
比如说,通过 cdn 的方式引入 decimal.js 时,可以这样用:

<!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
      src="https://cdn.bootcdn.net/ajax/libs/decimal.js/10.2.0/decimal.min.js">		// 引用开源库
    </script>
    <script>
      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());		// 利用开源库计算
    </script>
  </body>
</html>

image.png
通常,我们不会在前端使用 JS 做运算, 因为客户端代码可以被用户轻松访问和修改,存在安全风险。

5.7)变量值的传递(赋值)

语句:

a = b;

把 b 的值赋给 a,b 不变。
来做几个题目。

举例 1:

var a = 1;
var b = 2;
var c = 3;
a = b + c; 					// 5 2 3
b = c - a; 					// -2 3 5
c = a * b;					// -10 5 -2

console.log(a);
console.log(b);
console.log(c);

输出:

5
-2
-10

举例 2:

var a = 1;
var b = 2;
var c = 3;
a = a + b; 					// 3 1 2
b = b + a; 					// 5 2 3
c = c + b; 					// 8 5 3

console.log(a); 		// 3
console.log(b); 		// 5
console.log(c); 		// 8

输出:

3
5
8

举例 3:

var a = "1";
var b = 2;
a = a + b;					//	12  "1"  2
b = b + a; 					//  212  2  "12"

console.log(a); 		// 输出 12
console.log(b);			// 输出 212

输出:

12
212

举例 4:

var a = "1";
var b = 2;
a = b + a; 			// "21"  2  "1"
b = b + a; 			// "221" 2  "21"

console.log(a); // 21
console.log(b) 	// 221

效果:

21
221

举例 5:( 这个例子比较特殊,字符串减去数字 )

var a = "3";
var b = 2;
console.log(a-b);		// "3" -2 ( 隐式转换 )

效果:( 注意,字符串 - 数值 = 数值 )

1

6)基本数据类型:Null 与 Undefined

06-基本数据类型:Null 和 Undefined.pdf
很多其他的语言中( 比如 Python ),只有 null;但 JS 语言中,既有 null,又有 undefined。
很多人会弄混,由此觉得 JS 语言很麻烦。
其实不然,学习完这篇文章后,你会发现 null 和 undefined 的区别很容易理解。

6.1)Null:空对象

null 专门用来定义一个空对象( 例如: let a = null )。
如果你想定义一个变量用来保存引用类型,但是还没想好放什么内容,这个时候,可以在初始化时将其设置为 null。

let myObj = null;

console.log(typeof myObj); // 打印结果:object 引用类型

补充:

  • Null 类型的值只有一个,就是 null。比如 let a = null 。
  • 使用 typeof 检查一个 null 值时,会返回 object。

6.2)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 || 'magedu';
}

foo();

等学习了 ES6 之后,上方代码也可以这样写:

function foo(name = 'magedu') {}

foo();
其他区别

null 和 undefined 有很大的相似性。看看null == undefined 的结果为 true 也更加能说明这点。
但是 null === undefined 的结果是 false。

var a = null == undefined;

console.log(a);

它们虽然相似,但还是有区别的,其中一个区别是,和数字运算时:

  • 10 + null 结果为 10。
  • 10 + undefined 结果为 NaN。

规律总结:

  • 任何数据类型和 undefined 运算都是 NaN;
  • 任何值和 null 运算,null 可看做 0 运算。

:::warning
JavaScript 中 = == === 的区别
:::
在 JavaScript 中除了用 == 操作符来判断是否相等外,还有一个 === 操作符,
它们的区别是:== 操作符会先将两边的值进行强制类型转换再比较是否相等,而 === 操作符不会进行类型转换。== 操作符只要求比较两个值是否相等,而 === 操作符不仅要求值相等,而且要求类型相同。!=!== 的区别也是类似的,!= 号会做强制类型转换,而 !==不会。

=: 赋值
==: 判断等于
===: 也是判断等于,但比 == 严谨

在 Chrome 浏览器的环境下,测试一下就知道了:

第一个操作数是数值类型的 55,第二个操作数是字符串类型的 “55” 。当一个操作数是字符串,另一个操作数是数值的时候,操作符 == 会把字符串转换成数值,所以结果就返回了 True。
=== 不会做强制转换,所以数值类型的 55 和字符串类型的 “55” 当然是不相等 False 的了。

// True
55 == '55'

// False
55 === '55'

image.png
image.png

7)运算符

07-运算符&事件.pdf

7.1)运算符的定义和分类

运算符的定义

运算符:也叫操作符,是一种符号。通过运算符可以对一个或多个值进行运算,并获取运算结果。
表达式:由数字、运算符、变量的组合( 组成的式子 )。
表达式最终都会有一个运算结果,我们将这个结果称为表达式的
返回值

比如: +*/ 、 ( 都是
运算符
,而 (3+5)/2 则是表达式
比如:typeof 就是运算符,可以来获得一个值的类型。它会将该值的类型以字符串的形式返回,返回值可以是 number、string、boolean、undefined、object。

运算符的分类

JS 中的运算符,分类如下:

  • 算术运算符
  • 自增/自减运算符
  • 一元运算符
  • 逻辑运算符
  • 赋值运算符
  • 比较运算符
  • 三元运算符(条件运算符)

下面来逐一讲解。

7.2)算术运算符

**算术运算符:**用于执行两个变量或值的算术运算。
常见的算术运算符有以下几种:
image.png

求余的举例:
:::info
假设用户输入 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)小括号 ( ) :能够影响计算顺序,且可以嵌套。没有中括号、没有大括号,只有小括号。
(3)百分号:取余。只关心余数。
举例 1:( 取余 )

console.log(3 % 5);

输出结果为 3

举例 2:( 注意运算符的优先级 )

var a = 1 + 2 * 3 % 4 / 3;

// 解题思路
1 + 6 % 4 /3
1 + 2 / 3
1.666666666666

结果分析:1 + 6 % 4 / 3 = 1 + 2 / 3 = 1.66666666666666
**补充:**关于算术运算符的注意事项,详见上一篇文章里的 “数据类型转换” 的知识点。

浮点数运算的精度问题

浮点数值 的 最高精度是 17 位小数,因此在进行算术计算时,会丢失精度,导致计算不够准确。比如:

console.log(0.1 + 0.2); // 运算结果不是 0.3, 而是 0.30000000000000004

console.log(0.07 * 100); // 运算结果不是 7, 而是 7.000000000000001

所以,我们不要直接判断两个浮点数是否相等。

7.3)自增和自减

自增 ++

自增分成两种: a++++a
(1)一个变量自增以后,原变量的值会立即自增 1。也就是说,无论是 a++ 还是 ++a ,都会立即使原变量的值自增 1。
(2)我们要注意的是: a 是变量,而 a++ 和 ++a 是表达式

那这两种自增,有啥区别呢?区别是: a++++a 的值不同:(也就是说,表达式的值不同)

  • a++ 这个表达式的值等于原变量的值( a 自增前的值 )。你可以这样理解:先把 a 的值赋值给表达式,然后 a 再自增。( a++:先用 a 再 ++ )
  • ++a 这个表达式的值等于新值 ( a 自增后的值 )。 你可以这样理解:a 先自增,然后再把自增后的值赋值给表达式。( ++a:先 ++ 再用 a )
var a = 1;
console.log(a++);		// 1
console.log(++a);		// 3

var a = 1;
console.log(a++);		// 1
var b = 1;
console.log(++b);		// 2
console.log(a,b);		// 2,2
自减 --

原理同上。
开发时,大多使用后置的自增/自减,并且代码独占一行,
例如: 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

7.4)一元运算符

一元运算符:只需要一个操作数。

常见的一元运算符如下。

typeof

typeof 就是典型的一元运算符,因为后面只跟一个操作数。

var a = '123';
console.log(typeof a); // 打印结果: string
正号 +

(1)正号不会对数字产生任何影响。比如说, 2+2 是一样的。
(2)我们可以对一个其他的数据类型使用 + ,来将其转换为 number【重要的小技巧】
比如:

var a = true;
a = +a;			// 注意这行代码的一元运算符操作 ( 将 true 字符串类型 转换成了数值类型 )
console.log('a:' + a); // 这里的 a: 是字符串, + 号表示字符串拼接 ( 最后的 a 这是数值 1 )
console.log(typeof a); // 这里的 a 是数值类型
var b = '18';		// 字符串类型的 18
b = +b; 				// 注意这行代码的一元运算符操作 ( 将字符串类型的 18 转换成了数值类型 )
console.log('b:' + b);
console.log(typeof b);

image.png

打印结果:

a: 1
number

-----------------
  
b: 18
number
负号 -

负号可以对数字进行取反。( 取反值:+5 反之 -5 )

7.5)逻辑运算符

逻辑运算符有三个:

  • && 与(且):两个都为真,结果才为真。and
  • || 或:只要有一个是真,结果就是真。or
  • ! 非:对一个布尔值进行取反。

**注意:**能参与逻辑运算的,都是布尔值。

连比的写法:

来看看逻辑运算符连比的写法。

举例 1:

// 如下案例: 基于 && 逻辑与运算
// 只有两个都为真, 结果才为真 ( 如下案例: 只有一个为真. 那就为假 )
console.log(3 < 2 && 2 < 4);

输出结果为 false。

举例 2:( 判断一个人的年龄是否在 18~65 岁之间 )

const a = prompt('请输入您的年龄');
if ( a >= 18 && a <= 65) {
  alert('该数值在 18 - 65 之间');
} else {
  alert('该数值不在 18 - 65 之间');
}

**PS:**上面的 a>=18 && a<= 65 千万别想当然地写成 18<= a <= 65,没有这种语法。
image.png
image.png
image.png

非布尔值的与或运算【重要】

目前进度:01:11:40

之所以重要,是因为在实际开发中,我们经常用这种代码做容错处理或者兜底处理。

非布尔值进行与或运算时,会先将其转换为布尔值,然后再运算,但返回结果是原值。比如说:

var result = 5 && 6; // 运算过程: true && true;

console.log('result:' + result); // 打印结果: 6 (也就是说最后面的那个值.)

上方代码可以看到,虽然运算过程为布尔值的运算,但返回结果是原值。
image.png

:::info
那么,返回结果是哪个原值呢?我们来看一下。
:::
与运算的返回结果:( 以多个非布尔值的运算为例 )

  • 如果第一个值为 false,则执行第一条语句,并直接返回第一个值;不会再往后执行。
  • 如果第一个值为 true,则继续执行第二条语句,并返回第二个值( 如果所有的值都为 true,则返回的是最后一个值 )。

或运算的返回结果:( 以多个非布尔值的运算为例 )

  • 如果第一个值为 true,则执行第一条语句,并直接返回第一个值;不会再往后执行。
  • 如果第一个值为 false,则继续执行第二条语句,并返回第二个值( 如果所有的值都为 false,则返回的是最后一个值 )。
var result = 5 || 6; // 运算过程: true || true;

console.log('result:' + result); // 打印结果: 5 (也就是说最后前面的那个值.)

image.png

实际开发中,我们经常是这样来做**「容错处理」**的:

当前端成功调用一个接口后,返回的数据为 result 对象。这个时候,我们用变量 a 来接收 result 里的图片资源。通常的写法是这样的:

if (result.resultCode == 0) {
  var a = result && result.data && result.data.imgUrl || 'https://www.magedu.com/20211113_01.jpg';
}

上方代码的意思是,获取返回结果中的 result.data.imgUrl 这个图片资源;如果返回结果中没有 result.data.imgUrl 这个字段,就用 https://www.magedu.com/20211113_01.jpg 作为兜底图片。这种写法,在实际开发中经常用到。

非布尔值的 ! 运算

非布尔值进行非运算时,会先将其转换为布尔值,然后再运算,返回结果是布尔值。

let a = 10;		// 数值类型 10 ( True )
a = !a				// 使用 ! 运算将 10 转换为非 True ( False )

console.log(a); // false
console.log(typeof a); // boolean ( 布尔值类型 )

7.6)赋值运算符

可以将符号右侧的值赋值给符号左侧的变量。

  • = 直接赋值。比如 var a = 5
  • += 。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

7.7)运算符的优先级

运算符的优先级如下:( 优先级从高到低 )

  • .[]new
  • ()
  • ++--
  • !~+ (单目)、 - (单目)、 typeofvoiddelete
  • %*/
  • + (双目)、 - (双目)
  • <<>>>>>
  • 关系运算符: <<=>>=
  • ==!=====!==
  • &
  • ^
  • |
  • &&
  • ||
  • ?:
  • =+=-=*=/=%=<<=>>=>>>=&=^=|=
  • ,

**注意:**逻辑与 && 比逻辑或 || 的优先级更高。
备注:你在实际写代码的时候,如果不清楚哪个优先级更高,可以把括号运用上。

7.8)Unicode 编码

:::info
Unicode 编码 则是采用双字节 16 位来进行编号,可编 65536 字符,基本上包含了世界上所有的语言字符,它也就成为了全世界一种通用的编码,而且用十六进制 4 位表示一个编码,非常简结直观,为大多数开发者所接受,特别是十六进制编码后,可以解决汉字在 js 再编码过程中出现乱码问题,提高解释速度,我们建议在 js 脚本中使用十六进制 unicode 编码。
:::
:::info
各位同学可以先在网上查一下“Unicode 编码表”。
https://javawind.com/tools/native2ascii.jsp
image.png
:::
在字符串中可以使用转义字符输入 Unicode 编码。格式如下:

\u四位编码
\u四位编码\u四位编码

:::info
举例如下:
:::

console.log("\u5f00\u6e90\u6781\u5ba2\u884c");

image.png

console.log("\u2600"); // 这里的 2600 采用的是 16 进制
console.log("\u2602"); // 这里的 2602 采用的是 16 进制

image.png

7.9)事件句柄

HTML 4.0 的新特性之一是有能力使 HTML 事件触发浏览器中的动作(action),比如当用户点击某个HTML 元素时启动一段 JavaScript。
下面是一个属性列表,这些属性可插入 HTML 标签来定义事件动作,相当于是在 HTML 标签中插入了事件句柄,可以接收 JS 代码并执行。

属性当以下情况发生时,出现此事件FFNIE
onabort图像加载被中断134
onblur元素失去焦点123
onchange用户改变域的内容123
onclick鼠标点击某个对象123
ondblclick鼠标双击某个对象144
onerror当加载文档或图像时发生某个错误134
onfocus元素获得焦点123
onkeydown某个键盘的键被按下143
onkeypress某个键盘的键被按下或按住143
onkeyup某个键盘的键被松开143
onload某个页面或图像被完成加载123
onmousedown某个鼠标按键被按下144
onmousemove鼠标被移动163
onmouseout鼠标从某元素移开144
onmouseover鼠标被移到某元素之上123
onmouseup某个鼠标按键被松开144
onreset重置按钮被点击134
onresize窗口或框架被调整尺寸144
onselect文本被选定123
onsubmit提交按钮被点击123
onunload用户退出页面123

:::info
举例:
:::

// 英文 ( 可直接使用 )
<a onmouseover=alert(123)>请使用鼠标触碰我</a>

// 中文 ( 需要转换为 Unicode 编码 )
<a onmouseover=alert(\u771f\u542c\u8bdd)>请使用鼠标触碰我</a>

image.png

// 英文 ( 可直接使用 )
<a onclick=alert(123)>请使用鼠标点击我</a>

// 中文 ( 需要转换为 Unicode 编码 )
<a οnclick=alert("\u771f\u542c\u8bdd")>请使用鼠标点击我</a>

image.png

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jaxx.Wang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值