前面我们已经学习了前端代码的骨架——HTML和前端美化工具——CSS。但是作为界面与客户进行交互我们还需要一个语言工具——JavaScript。
因此实际上HTML、CSS、JavaScript三者是这样的关系:
HTML: 网页的结构(骨)
CSS: 网页的表现(皮)
JavaScript: 网页的行为(魂)
接下来我们就来深入学习JavaScript。
相关代码已经上传至博客中前端学习代码: 前端学习,喜欢请点个赞谢谢
目录
2. JavaScript 对象不区分 "属性" 和 "方法"
3. JavaScript 对象没有 private / public 等访问控制机制.
JavaScript的介绍
JavaScript (简称 JS) 是一种高级的、解释型的编程语言。它最初被设计用于让网页“动起来”,如今已发展成为一门功能全面、无处不在的编程语言,是 Web 技术的三大核心之一(另外两个是 HTML 和 CSS)。
发展历史
时间 | 事件 | 意义 |
---|---|---|
1995 | 由 Brendan Eich 在网景公司(Netscape)用时10天创建,最初名为 Mocha,后改为 LiveScript,最终定名为 JavaScript。 | 诞生之初,旨在为浏览器增加动态交互能力。 |
1997 | ECMAScript 1 标准发布(由 ECMA 国际组织制定)。 | JavaScript 拥有了标准规范,确保了不同浏览器能够遵循同一标准实现。 |
2009 | ES5 发布,带来了严格的严格模式 、JSON 支持等。同时,Node.js 诞生。 |
JS 突破了浏览器的界限,可以用于开发服务器端程序,成为一门全栈语言。 |
2015 | ES6 (ECMAScript 2015) 发布,带来了let/const 、箭头函数 、Promise 、类 等革命性新特性。 |
现代 JavaScript 的开端,语言能力得到巨大提升,满足了开发复杂应用的需求。 |
至今 | 每年发布一个新版本(ES2016, ES2017...),以渐进的方式持续加入新特性。 | 语言保持活力,不断进化,适应 |
特点
解释型语言:代码在运行时逐行被解释和执行,无需预先编译。
动态类型:变量在声明时不需要指定数据类型,类型在运行时才确定。
单线程与异步编程:核心执行模型是单线程的,但通过 事件循环(Event Loop) 和 回调函数(Callback) 机制(以及后来的
Promise
和async/await
)来处理异步操作,避免了阻塞。基于原型(Prototype-based):不同于 Java、C++ 的基于类(Class-based)的继承,JS 使用原型链来实现对象继承(ES6 的
class
只是语法糖,其底层仍是基于原型)。跨平台:只要有 JavaScript 引擎(如浏览器中的 V8、SpiderMonkey),JS 代码就可以在任何平台上运行(Windows, macOS, Linux, 移动设备等)。
优势
无处不在:所有现代浏览器都内置了 JS 引擎,无需额外安装环境即可运行。这使得它成为了前端开发的唯一选择。
强大的生态系统:拥有全球最大的开源包管理系统 npm (Node Package Manager),海量的第三方库和框架可供使用,极大地提高了开发效率。
全栈开发:通过 Node.js,开发者可以使用同一种语言同时编写前端和后端代码,降低了上下文切换成本。
易学性:入门门槛相对较低,简单的语法和灵活的规则使其对初学者友好。
社区活跃:拥有极其庞大和活跃的开发者社区,遇到问题时很容易找到解决方案和学习资源。
** versatility (多用途性)**:除了Web开发,还可用于移动应用(React Native, Ionic)、桌面应用(Electron)、游戏开发、甚至物联网(IoT)等领域。
应用领域
领域 | 说明 | 常用技术 |
---|---|---|
Web 前端开发 | 实现网页的动态交互、动画、数据验证、与后端API通信等。 | React, Angular, Vue.js, jQuery |
服务器端开发 | 构建高性能的服务器、Web API、后端服务。 | Node.js, Express.js, Koa, NestJS |
移动应用开发 | 使用JS编写跨平台的移动应用程序(iOS & Android)。 | React Native, Ionic, NativeScript |
桌面应用开发 | 使用Web技术(HTML, CSS, JS)构建跨平台的桌面应用程序。 | Electron (如VSCode, Slack, Discord), NW.js |
游戏开发 | 开发浏览器游戏或通过引擎开发更复杂的游戏。 | Phaser, Three.js, Babylon.js |
数据库操作 | 某些NoSQL数据库直接使用JS进行查询和操作。 | MongoDB |
JavaScript的运行过程
编写的代码是保存在文件中的, 也就是存储在硬盘(外存上).
双击 .html 文件浏览器(应用程序)就会读取文件, 把文件内容加载到内存中(数据流向: 硬盘 => 内存)浏览器会解析用户编写的代码, 把代码翻译成二进制的, 能让计算机识别的指令(解释器的工作)
得到的二进制指令会被 CPU 加载并执行(数据流向: 内存 => CPU)
浏览器分成渲染引擎 + JS 引擎.
渲染引擎: 解析 html + CSS, 俗称 "内核"
JS 引擎: 也就是 JS 解释器. 典型的就是 Chrome 中内置的 V8 JS 引擎逐行读取 JS 代码内容, 然后解析成二进制指令, 再执行
JavaScript的组成
组成部分 | 全称 | 描述 | 类比 |
---|---|---|---|
ECMAScript | - | 语言核心,定义了语言的语法和基本对象。 | C++标准库。提供了std::vector , std::string 等基础组件。 |
DOM | 文档对象模型 | 将HTML文档抽象成一个树形结构,并提供了一套允许JS与页面内容进行交互的API(如增删改查HTML元素、修改样式、处理事件)。 | 一个UI框架(如Qt、MFC)。C++代码通过调用Qt的API来创建按钮、处理点击事件。JS通过调用DOM API来操作网页。 |
BOM | 浏览器对象模型 | 提供与浏览器窗口本身交互的API。由于缺乏标准,不同浏览器实现略有不同。包括: • navigator :浏览器信息• location :当前URL信息• history :浏览器历史记录• screen :屏幕信息• window :全局对象,代表浏览器窗口 |
操作系统API。就像C++程序调用Windows API来操作窗口大小、获取系统信息一样 |
简单来说:
ECMAScript 是 语言本身。
DOM 是 操作网页 的接口。
BOM 是 操作浏览器 的接口。
重要概念:ECMAScript
ECMAScript (ES): 是由 ECMA国际 组织标准化的脚本语言规范。它定义了语言的语法、类型、语句、关键字、保留字、操作符、对象等核心内容。它是一本“规则书”,告诉你这种语言应该怎么写,有哪些功能。
JavaScript (JS): 是 对 ECMAScript 规范的实现和扩展。除了实现ES规范,JavaScript 在浏览器中还包含了其他功能(如DOM、BOM,下面会讲)。
前置知识
JavaScript的书写形式
行内式
直接嵌入到 html 元素内部
<input type="button" value="点我一下" onclick="alert('haha')">
注意, JS 中字符串常量可以使用单引号表示, 也可以 使用双引号表示.
HTML 中推荐使用双引号, JS 中推荐使用单引号.
内嵌式
写到 script 标签中
<script>
alert("haha");
</script>
外部式
写到单独的 .js 文件中
<script src="hello.js"></script>
alert("hehe");
注意, 这种情况下 script 标签中间不能写代码. 必须空着(写了代码也不会执行).
适合代码多的情况.
注释
单行注释 //
多行注释 /* */
使用 ctrl + / 切换注释.
多行注释不能嵌套.
输入输出
输入:prompt
弹出一个输入框
// 弹出一个输入框
prompt("请输入您的姓名:");
输出:alert
弹出一个警示对话框, 输出结果
// 弹出一个输出框
alert("hello");
输出:console.log
在控制台打印一个日志(供程序员看)
// 向控制台输出日志
console.log("这是一条日志");
注意: 在 VSCode 中直接输入 "log" 再按 tab 键, 就可以快速输入 console.log
需要打开浏览器的开发者工具(F12) => Console 标签页 才能看到结果.
这是给程序员看的,不是给用户看的
重要概念: 日志
日志是程序员调试程序的重要手段
去医院看病, 医生会让患者做各种检查, 血常规, 尿常规, B超, CT等... 此时得到一堆检测结果. 这些结果普通人看不懂, 但是医生能看懂, 并且医生要通过这些信息来诊断病情.
这些检测结果就是医生的 "日志"
PS: 相比于医生来说, 程序猿多一个终极大招, "重启下试试"
重要概念: .
console 是一个 js 中的 "对象"
. 表示取对象中的某个属性或者方法. 可以直观理解成 "的" console.log 就可以理解成: 使用 "控制台" 对象 "的" log 方法
JavaScript语法学习
变量
创建变量
var name = 'zhangsan';
var age = 20;
JavaScript中定义变量不需要进行类型说明
var 是 JS 中的关键字, 表示这是一个变量.
= 在 JS 中表示 "赋值", 相当于把数据放到内存的盒子中. = 两侧建议有一个空格每个语句最后带有一个 ; 结尾. JS 中可以省略 ; 但是建议还是加上.
注意, 此处的 ; 为英文分号. JS 中所有的标点都是英文标点.
初始化的值如果是字符串, 那么就要使用单引号或者双引号引起来.
初始化的值如果是数字, 那么直接赋值即可.
除此之外还可以用let来定义变量
二者之间区别如下
特性 | var |
let (以及 const ) |
解释与说明 |
---|---|---|---|
作用域 | 函数作用域 | 块级作用域 | 最核心的区别。var 的作用域是整个函数,而 let 的作用域仅限于其所在的 {} 块内(如 if 、for 、while 或单独的 {} )。 |
变量提升 | 提升且初始化值为 undefined |
提升但未被初始化 | 两者都会被提升到其作用域的顶部。但 var 变量会被初始化为 undefined ,而 let 变量在声明之前处于 “暂时性死区” ,访问会抛出 ReferenceError 。 |
重复声明 | 允许 | 不允许 | 在相同作用域内,用 var 重复声明同一个变量不会报错(这通常是 bug 的来源)。而用 let 重复声明会立即导致 SyntaxError 。 |
全局对象属性 | 是 | 否 | 在全局作用域下,用 var 声明的变量会成为全局对象(浏览器中是 window )的属性。let 声明的变量则不会。 |
循环中的表现 | 有问题 | 符合预期 | 在 for 循环中,var 由于是函数作用域,会导致所有迭代共享同一个变量引用。let 则为每次迭代创建一个新的块级作用域绑定,表现正常。 |
为什么强烈推荐使用 let
?
基于以上区别,推荐使用 let
的主要原因如下
1、块级作用域避免了意外的变量泄露和污染
var
的函数作用域反直觉,尤其是在 if
和 for
循环中,变量会泄露到外部作用域,极易引发难以追踪的 bug。let
的块级作用域与 C++、Java 等语言的行为一致,更符合直觉,能更好地控制变量的生命周期。
示例:循环中的变量泄露
// 使用 var - i 会泄露到循环外部
for (var i = 0; i < 10; i++) { /* ... */ }
console.log(i); // 10 (依然可以访问到 i)
// 使用 let - i 被完美封装在循环内部
for (let j = 0; j < 10; j++) { /* ... */ }
console.log(j); // ReferenceError: j is not defined (符合预期)
2、暂时性死区避免了在声明前使用变量
虽然 var
和 let
都存在提升,但 let
的暂时性死区机制强制要求你先声明再使用。这迫使开发者写出更规范、更可预测的代码,避免了因提升而导致的逻辑错误。
示例:声明前使用
console.log(a); // 输出 undefined (诡异的行为)
var a = 10;
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20; // 直接报错,更容易发现错误
禁止重复声明消除了潜在的命名冲突
在大型项目或协作开发中,禁止重复声明可以有效地避免因不小心重命名变量而导致的意外覆盖,使代码更加安全。更现代的语法标准
let
和const
是 ES6 (2015) 引入的现代标准。使用它们意味着你在编写更现代、更规范的 JavaScript。几乎所有新的代码库和教程都默认使用let/const
。
因此在现代JavaScript开发中尽量避免使用var
使用变量
console.log(age); // 读取变量内容
age = 30; // 修改变量内容
代码示例: 弹框提示用户输入信息, 再弹框显示
var name = prompt("请输入姓名:");
var age = prompt("请输入年龄:");
var score = prompt("请输入分数");
alert("您的姓名是: " + name);
alert("您的年龄是: " + age);
alert("您的分数是: " + score);
也可以把三个输出内容合并成一次弹框
var name = prompt("请输入姓名:");
var age = prompt("请输入年龄:");
var score = prompt("请输入分数");
alert("您的姓名是: " + name + "\n" + "您的年龄是: " + age + "\n" + "您的分数是: " +
score + "\n");
+ 表示字符串拼接, 也就是把两个字符串首尾相接变成一个字符串.
\n 表示换行
动态类型
1) JS 的变量类型是程序运行过程中才确定的(运行到 = 语句才会确定类型)
var a = 10; // 数字
var b = "hehe"; // 字符串
2) 随着程序运行, 变量的类型可能会发生改变.
var a = 10; // 数字
a = "hehe"; // 字符串
这一点和 C Java 这种静态类型语言差异较大.
C, C++, Java, Go 等语言是静态类型语言. 一个变量在创建的时候类型就确定了, 不能在运行时发生改变。如果尝试改变, 就会直接编译报错.
基本数据类型
JS 中内置的几种类型
number: 数字. 不区分整数和小数.
boolean: true 真, false 假.
string: 字符串类型.
undefined: 只有唯一的值
undefined. 表示未定义的值.
null: 只有唯一的值
null. 表示空值
number类型
JS 中不区分整数和浮点数, 统一都使用 "数字类型" 来表示
数字进制表示
计算机中都是使用二进制来表示数据, 而人平时都是使用十进制.因为二进制在使用过程中不太方便(01太多会看花眼).
所以在日常使用二进制数字时往往使用 八进制 和 十六进制 来表示二进制数字
var a = 07; // 八进制整数, 以 0 开头
var b = 0xa; // 十六进制整数, 以 0x 开头
var c = 0b10; // 二进制整数, 以 0b 开头
注意:
一个八进制数字对应三个二进制数字
一个十六进制数字对应四个二进制数字. (两个十六进制数字就是一个字节)各种进制之间的转换, 不需要手工计算, 直接使用计算器即可.
特殊的数字值
Infinity: 无穷大, 大于任何数字. 表示数字已经超过了 JS 能表示的范围.
-Infinity: 负无穷大, 小于任何数字. 表示数字已经超过了 JS 能表示的范围.
NaN: 表示当前的结果不是一个数字.
注意:
1. 负无穷大 和 无穷小 不是一回事. 无穷小指无限趋近与 0, 值为 1 / Infinit
2. 'hehe' + 10 得到的不是 NaN, 而是 'hehe10', 会把数字隐式转成字符串, 再进行字符串拼接.
3. 可以使用 isNaN 函数判定是不是一个非数字.
console.log(isNaN(10)); // false
console.log(isNaN('hehe' - 10)); // true
string 字符串类型
字符串字面值需要使用引号引起来, 单引号双引号均可
var a = "haha";
var b = 'hehe';
var c = hehe; // 运行出错
如果文本中有引号,可以这么处理
var msg = "My name is "zhangsan""; // 出错
var msg = "My name is \"zhangsan\""; // 正确, 使用转义字符. \" 来表示字符串内部的引
号.
var msg = "My name is 'zhangsan'"; // 正确, 搭配使用单双引号
var msg = 'My name is "zhangsan"'; // 正确, 搭配使用单双引号
转义字符
有些字符不方便直接输入, 于是要通过一些特殊方式来表示.
转义序列 | 名称 | 描述与示例 |
---|---|---|
\0 |
空字符 | 表示 Unicode 值为 0 的字符(空字符) 示例: "Hello\0World" - 在字符串中间插入空字符 |
\' |
单引号 | 在由单引号定义的字符串中表示单引号字符 示例: 'It\'s a beautiful day' - 输出: It's a beautiful day |
\" |
双引号 | 在由双引号定义的字符串中表示双引号字符 示例: "He said, \"Hello!\"" - 输出: He said, "Hello!" |
\\ |
反斜杠 | 表示字面意义上的反斜杠字符 示例: "C:\\Windows\\System32" - 输出: C:\Windows\System32 |
\n |
换行符 | 插入一个新行(在Unix/Linux和macOS中常用) 示例: "Line 1\nLine 2" - 输出两行文本 |
\r |
回车符 | 将光标移动到行首(在旧版Mac OS中用作行结束符) 示例: "Loading...\rDone" - 输出: Done...(覆盖效果) |
\v |
垂直制表符 | 插入一个垂直制表符(很少使用) 示例: "Column1\vColumn2" - 垂直对齐文本 |
\t |
水平制表符 | 插入一个制表符(通常相当于4或8个空格) 示例: "Name:\tJohn" - 输出: Name: John |
\b |
退格符 | 将光标向后移动一个位置(在某些环境下会删除前一个字符) 示例: "Hello\b World" - 可能输出: Hell World |
\f |
换页符 | 表示换页(主要用于打印机控制) 示例: "Page 1\fPage 2" - 在打印时换页 |
\uXXXX |
Unicode转义 | 使用4位十六进制数表示Unicode字符 示例: "\u00A9" - 输出: © (版权符号) |
\u{X...} \u{XXXXXX} |
Unicode码点转义 | 使用1-6位十六进制数表示Unicode字符(ES6新增) 示例: "\u{1F604}" - 输出: 😄 (笑脸表情) |
\xXX |
拉丁字符转义 | 使用2位十六进制数表示Latin-1字符 示例: "\xA9" - 输出: © (版权符号) |
\<行末> |
行继续符 | 在字符串中换行而不插入实际换行符(ES5+) 示例: "This is a very long \<br>string that spans two lines." - 单行字符串 |
求长度
使用 String 的 length 属性即可
var a = 'hehe';
console.log(a.length);
var b = '哈哈';
console.log(b.length);
结果:
4 2
单位为字符的数量
字符串拼接
使用 + 进行拼接
var a = "my name is ";
var b = "zhangsan";
console.log(a + b);
注意, 数字和字符串也可以进行拼接
var c = "my score is ";
var d = 100;
console.log(c + d);
注意, 要认准相加的变量到底是字符串还是数字
console.log(100 + 100); // 200
console.log('100' + 100); // 100100
boolean 布尔类型
表示 "真" 和 "假"
boolean 原本是数学中的概念 (布尔代数).
在计算机中 boolean 意义重大, 往往要搭配条件/循环完成逻辑判断. Boolean 参与运算时当做 1 和 0 来看待
console.log(true + 1);
console.log(false + 1);
//这样的操作其实是不科学的. 实际开发中不应该这么写
undefined 未定义数据类型
如果一个变量没有被初始化过, 结果就是 undefined, 是 undefined 类型
var a;
console.log(a)
undefined 和字符串进行相加, 结果进行字符串拼接
console.log(a + "10"); // undefined10
undefined 和数字进行相加, 结果为 NaN
console.log(a + 10);
null 空值类型
null 表示当前的变量是一个 "空值".
var b = null;
console.log(b + 10); // 10
console.log(b + "10"); // null10
注意:
null 和 undefined 都表示取值非法的情况, 但是侧重点不同
null 表示当前的值为空. (相当于有一个空的盒子)
undefined 表示当前的变量未定义. (相当于连盒子都没有)
运算符
分类 | 运算符 | 名称 | 功能描述 | 示例 |
---|---|---|---|---|
算术运算符 | + |
加法 | 对两个操作数进行加法运算,如果有一个操作数是字符串,则进行字符串拼接 | 5 + 3 // 8 'Hello' + 'World' // 'HelloWorld' |
- |
减法 | 对两个操作数进行减法运算 | 5 - 3 // 2 |
|
* |
乘法 | 对两个操作数进行乘法运算 | 5 * 3 // 15 |
|
/ |
除法 | 对两个操作数进行除法运算 | 10 / 2 // 5 |
|
% |
取模 | 返回两个操作数相除的余数 | 10 % 3 // 1 |
|
** |
指数 | 计算第一个操作数的第二个操作数次幂 | 2 ** 3 // 8 |
|
++ |
自增 | 将操作数增加1(可以前置或后置) | let x=5; x++ // x=6 |
|
-- |
自减 | 将操作数减少1(可以前置或后置) | let x=5; x-- // x=4 |
|
赋值运算符 | = |
赋值 | 将右侧的值赋给左侧的变量 | let x = 5 |
+= |
加法赋值 | 将左侧变量与右侧值相加,结果赋给左侧变量 | x += 3 // x = x + 3 |
|
-= |
减法赋值 | 将左侧变量减去右侧值,结果赋给左侧变量 | x -= 3 // x = x - 3 |
|
*= |
乘法赋值 | 将左侧变量乘以右侧值,结果赋给左侧变量 | x *= 3 // x = x * 3 |
|
/= |
除法赋值 | 将左侧变量除以右侧值,结果赋给左侧变量 | x /= 3 // x = x / 3 |
|
%= |
取模赋值 | 将左侧变量与右侧值取模,结果赋给左侧变量 | x %= 3 // x = x % 3 |
|
**= |
指数赋值 | 将左侧变量与右侧值进行指数运算,结果赋给左侧变量 | x **= 3 // x = x ** 3 |
|
比较运算符 | == |
相等 | 比较两个操作数是否相等(会进行类型转换) | 5 == '5' // true |
!= |
不等 | 比较两个操作数是否不相等(会进行类型转换) | 5 != '6' // true |
|
=== |
严格相等 | 比较两个操作数是否相等(不会进行类型转换) | 5 === '5' // false |
|
!== |
严格不等 | 比较两个操作数是否不相等(不会进行类型转换) | 5 !== '5' // true |
|
> |
大于 | 检查左操作数是否大于右操作数 | 5 > 3 // true |
|
< |
小于 | 检查左操作数是否小于右操作数 | 5 < 3 // false |
|
>= |
大于等于 | 检查左操作数是否大于或等于右操作数 | 5 >= 5 // true |
|
<= |
小于等于 | 检查左操作数是否小于或等于右操作数 | 5 <= 3 // false |
|
逻辑运算符 | && |
逻辑与 | 如果两个操作数都为真,则返回真,否则返回假 | true && false // false |
|| |
逻辑或 | 如果两个操作数中有一个为真,则返回真,否则返回假 | true || false // true |
|
! |
逻辑非 | 如果操作数为真,则返回假,反之返回真 | !true // false |
|
位运算符 | & |
按位与 | 对两个操作数的每一个二进制位进行与操作 | 5 & 1 // 1 |
| |
按位或 | 对两个操作数的每一个二进制位进行或操作 | 5 | 1 // 5 |
|
^ |
按位异或 | 对两个操作数的每一个二进制位进行异或操作 | 5 ^ 1 // 4 |
|
~ |
按位非 | 对操作数的每一个二进制位取反 | ~5 // -6 |
|
<< |
左移 | 将第一个操作数的二进制形式向左移动指定位数 | 5 << 1 // 10 |
|
>> |
右移 | 将第一个操作数的二进制形式向右移动指定位数(保留符号位) | 5 >> 1 // 2 |
|
>>> |
无符号右移 | 将第一个操作数的二进制形式向右移动指定位数(不保留符号位) | 5 >>> 1 // 2 |
|
条件运算符 | ? : |
三元条件 | 根据条件选择两个值中的一个:condition ? expr1 : expr2 | age >= 18 ? '成人' : '未成年' |
逗号运算符 | , |
逗号 | 从左到右计算多个表达式,并返回最后一个表达式的值 | let x = (5, 10) // x=10 |
一元运算符 | delete |
删除 | 删除对象的属性或数组的元素 | delete obj.property |
typeof |
类型 | 返回一个字符串,表示未经计算的操作数的类型 | typeof 5 // "number" |
|
void |
无效 | 执行表达式并返回undefined | void(0) |
|
关系运算符 | in |
属性存在 | 如果指定的属性在指定的对象中,则返回true | 'name' in obj |
instanceof |
实例检查 | 如果对象是指定构造函数的实例,则返回true | obj instanceof Array |
|
其他运算符 | new |
创建实例 | 创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例 | new Date() |
yield |
暂停函数 | 暂停和恢复生成器函数 | function* gen() { yield 1; } |
|
... |
展开/剩余 | 展开数组或对象,或用于收集剩余参数 | [...arr], {...obj}, function(...args) |
|
?? |
空值合并 | 如果左侧操作数为null或undefined,则返回右侧操作数,否则返回左侧操作数 | null ?? 'default' // 'default' |
|
?. |
可选链 | 允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效 |
运算符优先级
以下是 JavaScript 中所有运算符的优先级列表,从最高优先级(20)到最低优先级(1)。同一行中的运算符具有相同的优先级。
20 | 分组 | ( ... ) |
不适用 | 表达式分组 |
19 | 成员访问 | . |
从左到右 | 对象属性访问 |
需计算的成员访问 | [ ... ] |
从左到右 | 对象属性访问(使用变量或表达式) | |
new(带参数列表) | new ... ( ... ) |
不适用 | 创建实例(带参数) | |
函数调用 | ( ... ) |
从左到右 | 函数调用 | |
可选链 | ?. |
从左到右 | 可选属性访问 | |
18 | new(无参数列表) | new ... |
从右到左 | 创建实例(无参数) |
17 | 后置递增 | ... ++ |
不适用 | 后置递增 |
后置递减 | ... -- |
不适用 | 后置递减 | |
16 | 逻辑非 | ! ... |
从右到左 | 逻辑非 |
按位非 | ~ ... |
从右到左 | 按位非 | |
一元加法 | + ... |
从右到左 | 一元正号 | |
一元减法 | - ... |
从右到左 | 一元负号 | |
前置递增 | ++ ... |
从右到左 | 前置递增 | |
前置递减 | -- ... |
从右到左 | 前置递减 | |
typeof | typeof ... |
从右到左 | 类型检查 | |
void | void ... |
从右到左 | 返回 undefined | |
delete | delete ... |
从右到左 | 删除属性 | |
await | await ... |
从右到左 | 等待 Promise | |
15 | 幂 | ... ** ... |
从右到左 | 指数运算 |
14 | 乘法 | ... * ... |
从左到右 | 乘法 |
除法 | ... / ... |
从左到右 | 除法 | |
取模 | ... % ... |
从左到右 | 取模(求余) | |
13 | 加法 | ... + ... |
从左到右 | 加法或字符串连接 |
减法 | ... - ... |
从左到右 | 减法 | |
12 | 按位左移 | ... << ... |
从左到右 | 按位左移 |
按位右移 | ... >> ... |
从左到右 | 按位右移(保留符号) | |
无符号右移 | ... >>> ... |
从左到右 | 无符号右移 | |
11 | 小于 | ... < ... |
从左到右 | 小于比较 |
小于等于 | ... <= ... |
从左到右 | 小于等于比较 | |
大于 | ... > ... |
从左到右 | 大于比较 | |
大于等于 | ... >= ... |
从左到右 | 大于等于比较 | |
in | ... in ... |
从左到右 | 属性存在检查 | |
instanceof | ... instanceof ... |
从左到右 | 实例检查 | |
10 | 相等 | ... == ... |
从左到右 | 相等比较(允许类型转换) |
不相等 | ... != ... |
从左到右 | 不相等比较(允许类型转换) | |
严格相等 | ... === ... |
从左到右 | 严格相等比较(不允许类型转换) | |
严格不相等 | ... !== ... |
从左到右 | 严格不相等比较(不允许类型转换) | |
9 | 按位与 | ... & ... |
从左到右 | 按位与操作 |
8 | 按位异或 | ... ^ ... |
从左到右 | 按位异或操作 |
7 | 按位或 | ... | ... |
从左到右 | 按位或操作 |
6 | 逻辑与 | ... && ... |
从左到右 | 逻辑与操作 |
5 | 逻辑或 | ... || ... |
从左到右 | 逻辑或操作 |
空值合并 | ... ?? ... |
从左到右 | 空值合并操作 | |
4 | 条件运算符 | ... ? ... : ... |
从右到左 | 三元条件操作 |
3 | 赋值 | = |
从右到左 | 赋值 |
+= -= *= /= %= |
从右到左 | 运算赋值 | ||
**= <<= >>= >>>= |
从右到左 | 运算赋值 | ||
&= ^= |= &&= ||= ??= |
从右到左 | 运算赋值 | ||
2 | yield | yield ... |
从右到左 | 生成器产出值 |
yield* | yield* ... |
从右到左 | 委托给另一个生成器 | |
1 | 逗号 | ... , ... |
从左到右 | 逗号操作符 |
条件语句
if
// 形式1
if (条件) {
语句
}
// 形式2
if (条件) {
语句1
} else {
语句2
}
// 形式3
if (条件1) {
语句1
} else if (条件2) {
语句2
} else if .... {
语句...
} else {
语句N
}
三元表达式
条件 ? 表达式1 : 表达式2
为真执行表达式1,为假执行表达式2.
注意, 三元表达式的优先级是比较低的
switch
switch (表达式) {
case 值1:
语句1;
break;
case 值2:
语句2:
break;
default:
语句N;
}
循环语句
while循环
while (条件)
{
循环体;
}
执行过程:
先执行条件语句
条件为 true, 执行循环体代码.条件为 false, 直接结束循环
for循环
for (表达式1; 表达式2; 表达式3)
{
循环体
}
表达式1: 用于初始化循环变量.
表达式2: 循环条件
表达式3: 更新循环变量.执行过程:
执行流程:
先执行表达式1, 初始化循环变量再执行表达式2, 判定循环条件如果条件为 false, 结束循环
如果条件为 true, 则执行循环体代码.执行表达式3 更新循环变量
数组
创建数组
使用 new 关键字创建
// Array 的 A 要大写
var arr = new Array();
使用字面量方式创建 [常用]
var arr = [];
var arr2 = [1, 2, 'haha', false]; // 数组中保存的内容称为 "元素"
注意: JS 的数组不要求元素是相同类型.
这一点和 C, C++, Java 等静态类型的语言差别很大. 但是 Python, PHP 等动态类型语言也是如此
访问数组
使用下标的方式访问数组元素(从 0 开始)
var arr = ['小猪佩奇', '小猪乔治', '小羊苏西'];
console.log(arr);
console.log(arr[0]);
console.log(arr[1]);
console.log(arr[2]);
arr[2] = '小猫凯迪';
console.log(arr);
如果下标超出范围读取元素, 则结果为 undefined
console.log(arr[3]); // undefined
console.log(arr[-1]); // undefined
注意: 不要给数组名直接赋值, 此时数组中的所有元素都没
相当于本来 arr 是一个数组, 重新赋值后变成字符串了.
var arr = ['小猪佩奇', '小猪乔治', '小羊苏西'];
arr = '小猫凯迪';
新增数组元素
1. 通过修改 length 新增
相当于在末尾新增元素. 新增的元素默认值为 undefined
var arr = [9, 5, 2, 7];
arr.length = 6;
console.log(arr);
console.log(arr[4], arr[5]);
结果为:
2. 通过下标新增
如果下标超出范围赋值元素, 则会给指定位置插入新元素
var arr = [];
arr[2] = 10;
console.log(arr);
结果为:
此时这个数组的 [0] 和 [1] 都是 undefined
3. 使用 push 进行追加元素
代码示例: 给定一个数组, 把数组中的奇数放到一个 newArr 中.
var arr = [9, 5, 2, 7, 3, 6, 8];
var newArr = [];
for (var i = 0; i < arr.length; i++)
{
if (arr[i] % 2 != 0)
{
newArr.push(arr[i]);
}
}
console.log(newArr);
删除数组中的元素
使用 splice 方法删除元素
var arr = [9, 5, 2, 7];
// 第一个参数表示从下表为 2 的位置开始删除. 第二个参数表示要删除的元素个数是 1 个
arr.splice(2, 1);
console.log(arr);
// 结果
[9, 5, 7]
目前咱们已经用到了数组中的一些属性和方法.
arr.length, length 使用的时候不带括号, 此时 length 就是一个普通的变量(称为成员变量, 也叫属性)
arr.push(), arr.splice() 使用的时候带括号, 并且可以传参数, 此时是一个函数 (也叫做方法)
函数
// 创建函数/函数声明/函数定义
function 函数名(形参列表) {
函数体
return 返回值;
}
// 函数调用
函数名(实参列表) // 不考虑返回值
返回值 = 函数名(实参列表) // 考虑返回值
函数定义并不会执行函数体内容, 必须要调用才会执行. 调用几次就会执行几次.
function hello() {
console.log("hello");
}
// 如果不调用函数, 则没有执行打印语句
hello();
调用函数的时候进入函数内部执行, 函数结束时回到调用位置继续执行. 可以借助调试器来观察.函数的定义和调用的先后顺序没有要求. (这一点和变量不同, 变量必须先定义再使用)
// 调用函数
hello();
// 定义函数
function hello()
{
console.log("hello");
}
关于参数个数
实参和形参之间的个数可以不匹配. 但是实际开发一般要求形参和实参个数要匹配
(1)如果实参个数比形参个数多, 则多出的参数不参与函数运算
sum(10, 20, 30); // 30
(2) 如果实参个数比形参个数少, 则此时多出来的形参值为 undefine
sum(10); // NaN, 相当于 num2 为 undefined.
JavaScript传递参数相较于C/C++/Java这样的语言来说很灵活,不过实际上这种灵活并不是好事
函数表达式
var add = function()
{
var sum = 0;
for (var i = 0; i < arguments.length; i++)
{
sum += arguments[i];
}
return sum;
}
console.log(add(10, 20)); // 30
console.log(add(1, 2, 3, 4)); // 10
console.log(typeof add); // function
此时形如 function() { } 这样的写法定义了一个匿名函数, 然后将这个匿名函数用一个变量来表示.后面就可以通过这个 add 变量来调用函数了.
JS 中函数是一等公民, 可以用变量保存, 也可以作为其他函数的参数或者返回值.
作用域
某个标识符名字在代码中的有效范围.在 ES6 标准之前, 作用域主要分成两个
全局作用域: 在整个 script 标签中, 或者单独的 js 文件中生效.局部作用域/函数作用域: 在函数内部生效.
// 全局变量
var num = 10;
console.log(num);
function test()
{
// 局部变量
var num = 20;
console.log(num);
}
function test2()
{
// 局部变量
var num = 30;
console.log(num);
}
test();
test2();
console.log(num);
// 执行结果
10
20
30
10
创建变量时如果不写 var, 则得到一个全局变量
function test()
{
num = 100;
}
test();
console.log(num);
// 执行结果
100
另外, 很多语言的局部变量作用域是按照代码块(大括号)来划分的, JS 在 ES6 之前不是这样的.
if (1 < 2)
{
var a = 10;
}
console.log(a);
作用域链
背景:
函数可以定义在函数内部
内层函数可以访问外层函数的局部变量.
内部函数可以访问外部函数的变量. 采取的是链式查找的方式. 从内到外依次进行查找
var num = 1;
function test1()
{
var num = 10;
function test2()
{
var num = 20;
console.log(num);
}
test2();
}
test1();
// 执行结果
2
执行 console.log(num) 的时候, 会现在 test2 的局部作用域中查找 num. 如果没找到, 则继续去 test1 中查找. 如果还没找到, 就去全局作用域查找.
就像下面这张图:
对象
基本概念
对象是指一个具体的事物.
"电脑" 不是对象, 而是一个泛指的类别. 而 "我的联想笔记本" 就是一个对象.
在 JS 中, 字符串, 数值, 数组, 函数都是对象.每个对象中包含若干的属性和方法.
属性: 事物的特征.
方法: 事物的行为.
对象需要保存的属性有多个, 虽然数组也能用于保存多个数据, 但是不够好.
例如表示一个学生信息. (姓名蔡徐坤, 身高 175cm, 体重 170斤) var student = ['蔡徐坤', 175, 170];但是这种情况下到底 175 和 170 谁表示身高, 谁表示体重, 就容易分不清
JavaScript 的对象 和 Java 的对象概念上基本一致. 只是具体的语法表项形式差别较大.
使用 字面量 创建对象 [常用]
使用 { } 创建对象
var a = {}; // 创建了一个空的对象
var student =
{
name: '蔡徐坤',
height: 175,
weight: 170,
sayHello: function()
{
console.log("hello");
}
}
使用 { } 创建对象
属性和方法使用键值对的形式来组织.
键值对之间使用 , 分割. 最后一个属性后面的 , 可有可无键和值之间使用 : 分割.
方法的值是一个匿名函数.
使用对象的属性和方法
// 1. 使用 . 成员访问运算符来访问属性 `.` 可以理解成 "的"
console.log(student.name);
// 2. 使用 [ ] 访问属性, 此时属性需要加上引号
console.log(student['height']);
// 3. 调用方法, 别忘记加上 ()
student.sayHello();
使用 new Object 创建对象
var student = new Object(); // 和创建数组类似
student.name = "蔡徐坤";
student.height = 175;
student['weight'] = 170;
student.sayHello = function ()
{
console.log("hello");
}
console.log(student.name);
console.log(student['weight']);
student.sayHello();
注意, 使用 { } 创建的对象也可以随时使用 student.name = "蔡徐坤"; 这样的方式来新增属性.
使用 构造函数 创建对象
前面的创建对象方式只能创建一个对象. 而使用构造函数可以很方便 的创建 多个对象.例如: 创建几个猫咪对象
var mimi =
{
name: "咪咪",
type: "中华田园喵",
miao: function ()
{
console.log("喵");
}
};
var xiaohei =
{
name: "小黑",
type: "波斯喵",
miao: function ()
{
console.log("猫呜");
}
}
var ciqiu =
{
name: "刺球",
type: "金渐层",
miao: function ()
{
console.log("咕噜噜");
}
}
此时写起来就比较麻烦. 使用构造函数可以把相同的属性和方法的创建提取出来, 简化开发过程.
基本语法
function 构造函数名(形参)
{
this.属性 = 值;
this.方法 = function...
}
var obj = new 构造函数名(实参);
注意:
在构造函数内部使用 this 关键字来表示当前正在构建的对象.构造函数的函数名首字母一般是大写的.
构造函数的函数名可以是名词.
构造函数不需要 return
创建对象的时候必须使用 new 关键字. this 相当于 "我"
示例:
function Cat(name, type, sound)
{
this.name = name;
this.type = type;
this.miao = function ()
{
console.log(sound); // 别忘了作用域的链式访问规则
}
}
var mimi = new Cat('咪咪', '中华田园喵', '喵');
var xiaohei = new Cat('小黑', '波斯喵', '猫呜');
var ciqiu = new Cat('刺球', '金渐层', '咕噜噜');
console.log(mimi);
mimi.miao();
理解new关键字
new 的执行过程:
1. 先在内存中创建一个空的对象 { }
2. this 指向刚才的空对象(将上一步的对象作为 this 的上下文)
3. 执行构造函数的代码, 给对象创建属性和方法
4. 返回这个对象 (构造函数本身不需要 return, 由 new 代劳了)
参考以下资料:newhttps://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/new
JavaScript 的对象和 Java 的对象的区别
1. JavaScript 没有 "类" 的概念
对象其实就是 "属性" + "方法" .
类相当于把一些具有共性的对象的属性和方法单独提取了出来, 相当于一个 "月饼模子"在 JavaScript 中的 "构造函数" 也能起到类似的效果.
而且即使不是用构造函数, 也可以随时的通过 { } 的方式指定出一些对象
在 ES6 中也引入了 class 关键字, 就能按照类似于 Java 的方式创建类和对象了
2. JavaScript 对象不区分 "属性" 和 "方法"
JavaScript 中的函数是 "一等公民", 和普通的变量一样. 存储了函数的变量能够通过 ( ) 来进行调用执行.
3. JavaScript 对象没有 private / public 等访问控制机制.
对象中的属性都可以被外界随意访问.
4. JavaScript 对象没有 "继承"
继承本质就是 "让两个对象建立关联". 或者说是让一个对象能够重用另一个对象的属性/方法. JavaScript 中使用 "原型" 机制实现类似的效果.
5. JavaScript 没有 "多态"
多态的本质在于 "程序猿不必关注具体的类型, 就能使用其中的某个方法".
C++ / Java 等静态类型的语言对于类型的约束和校验比较严格. 因此通过 子类继承父类, 并重写父类的方法的方式 来实现多态的效果.
但是在 JavaScript 中本身就支持动态类型, 程序猿在使用对象的某个方法的时候本身也不需要对对象的类型做出明确区分. 因此并不需要在语法层面上支持多态
本期关于JavaScript的语法部分学习介绍到这里就结束了,喜欢请点个赞谢谢
封面图自取: