1-JavaScript-ECMAScript核心-基础部分

about

摘自《JavaScript权威指南第六版》

JavaScript是面向 Web 的编程语言。绝大多数现代网站都使用了 JavaScript,并且所有的现代 Web 浏览器 ― 基于桌面系统、游戏机、平板电脑和智能手机的浏览器 ― 均包含了 JavaScript解释器。这使得 JavaScript能够称得上史上使用最广泛的编程语言。 JavaScript也是前端开发工程师必须掌握的三种技能之一:

描述网页内容的 HTML

描述网页样式的 CSS

描述网页行为的 JavaScript。

何时使用JavaScript

  • 客户端数据计算。
  • 客户端表单验证。
  • 动画效果。

JavaScript的发展史

  • 1992年,Nombas公司为自己的CEnvi软件开发了一款脚本语言ScriptEase,可以嵌在网页中。
  • 1995年,Netscape(网景)公司为自己的Navigator2.0(航海家)浏览器(火狐浏览器的前身)开发了另一种客户端脚本语言LiveScript,且据不可靠消息,后来为了赶时髦(由当时的sun公司介入),就蹭了当时很热的Java语言,重命名成了JavaScript。
  • 1996年,Microsoft公司为了进军浏览器市场,在IE3.0浏览器发布了自己的JavaScript的实现,称为JScript。
  • 1997年,JavaScript1.1作为草案提交给了ECMA(European Computer Manufacturers Association,欧洲计算机制造商协会,是一个开发计算机硬件、通信和程序语言标准的非盈利组织),然后各厂家合力推出了ECMA-262标准,定义了全新的ECMAScript标准脚本语言。而各大浏览器厂家开始"努力"将ECMAScript作为实现的标准和目标。

ECMAScript/JavaScript/Jscript

ECMAScript:由ECMA组织指定的JavaScript语言国际标准规范,一般简称es。

JavaScript:Netscape按照ECMAScript标准实现的JavaScript语言版本。一般简称js。

Jscript:微软按照ECMAScript标准实现的JavaScript语言版本。

打个比喻就是两个厨子按照菜谱做出来的两道菜,虽然本质上一样,但细微之处,稍有不同。

JavaScript核心三部分

  • 核心语法:ECMAScript相关标准。
  • DOM:专门用来操作网页内容的。
  • BOM:专门用来操作浏览器的。

不同厂商对ECMAScript标准的不同实现

浏览器 JavaScript实现(引擎)
FireFox SpiderMonkey
IE JScript/Chakra
Safari JavaScriptCore
Chrome V8

注意,JavaScript引擎包含两个核心引擎:

  • 模板引擎,解析渲染HTML+CSS。
  • js引擎,解释执行js代码。

JavaScript与nodejs

Node.js 不是一种独立的语言,也不是一个 JavaScript 框架,而是一个让 JavaScript 运行在服务端的开发平台,它让 JavaScript 成为脚本语言世界的一等公民,在服务端堪与 PHP、Python、Perl、Ruby 平起平坐.Node.js 可以作为服务器向用户提供服务,与 PHP、Python、Ruby相比,它跳过了 Apache、Nginx 等 HTTP服务器,直接面向前端开发。

所以,解释执行JavaScript代码只需要浏览器即可,而nodejs要手动去下载安装后才能使用。

如何学习JavaScript

从hello world开始

我们来通过JavaScript编写第一行代码。那么我们在哪写JavaScript代码呢?

JavaScript代码写在script标签中,所以,这里推荐script标签你应该写在:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Document</title>
    <!-- 1. JavaScript代码写在script标签中 -->
    <script>
        // 在网页中输出内容
        document.write('hello world!');
        // 在网页上弹出警告框
        alert('hello world!');
        // 在浏览器的控制台输出
        console.log('hello world!');
    </script>
    <!-- 2. 从外部文件中引入,但这个标签内就能再写js代码了 -->
    <script src="a.js"></script>
</head>
<body>
<!-- 3. 将js代码写在标签的指定属性中 -->
<button onclick="alert('点我干嘛?')">点我</button>
</body>
</html>

基本语法

基础语法规范

// js中的单行注释
/*
   js中的单行注释
*/

// 每个语句都要以分号结束,在js内部中有自动添加分号的机制;所以不写也不报错.......但推荐写上
console.log('hello world!');

// js中严格区分大小写,下面两个alert不一样
alert();
Alert();

// js会忽略空格和换行,所以我们可以通过空格和换行对代码进行格式化

// js中的真假,注意全小写
true
false

字面量、变量、常量

在js中,字面量指的是一个具体的值,它所表示的就是你看到字面意思,1就是1,2就是2,比如:1, 2, ‘hello’, true 等等。字面量可以在js中直接使用,但通常不会这么做,而通常使用变量:

// var a;     // 通过 var 关键字声明一个变量
// a = 10;     // 为声明的变量赋值
// var b = 11; // 也可以声明和赋值一起做

// var这个关键字有点"老"了,可以使用 let 来声明变量,但注意,不能用let多次声明同一个变量
let b;
b = 10;
let c = 20;
// 注意,变量的类型根据它的值来决定的,变量也可以被修改

// 使用const用来声明常量,注意,js中的常量不能修改
const = 20;

变量和值的内存关系

当一个变量的值被修改时,只会影响它自己,对其他变量不会产生任何影响。

let a = 10;
let b = a;
console.log(a, b); // 10 10
a++;
console.log(a, b); // 11 10

标识符

在程序中所有的可以自主命名的内容都可以认为是标识符,比如:变量、类名、函数名.....

标识符的规范要求:

  • 标识符由数字、字母、下划线组成。
  • 标识符不能由数字开头。
  • 标识符应该避开js中的关键字和保留字、内置函数,比如你不能为一个标识符起名为let、var、alert等。
    • 查看js中的关键字和保留字,点我
  • 标识符的命名推荐使用驼峰体,且推荐小驼峰体,首字母小写,后续每个单词的首字母大写,如userName

基础数据类型

基础的数据类型包括:字符串、整型、布尔型、null和undefined。

基础的数据类型时整个js语言的基石。

这些基础数据类型一经定义无法改变。

字符串string

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String

创建字符串

// 可以通过单、双引号来界定字符串
let s1 = "abc";
let s2 = "I'm zhangkai";  // 这里面的单引号就相当于一个普通引号
let s3 = 'I\'m zhangkai'; // 通过转义字符将引号转为普通引号

模板字符串和字符串的拼接

let name = "张开";
let age = 18;
// 字符串拼接
let s = "他的名字是: " + name + ",他的年龄是: " + age + "。";
console.log(s);  // 他的名字是: 张开,他的年龄是: 18。

// es6的新语法,模板字符串,使用反引号来做字符串的格式化
let s1 = `他的名字是: ${name},他的年龄是: ${age}。`;
console.log(s1); // 他的名字是: 张开,他的年龄是: 18。

补充其他特殊字符

特殊字符 描述
\ 转义字符
\n 换行,注意,换行在页面中相当于空格,如document.write('你好\n张开');
\t 制表符tab
\\ 表示\

字符串的常用方法

参考:

数值类型number

// 在js中,数值类型包括整数和小数
let n1 = 123;
let n2 = 12.3;
console.log(typeof n1, typeof n2);  // number number
// typeof查看对象的类型

// 对于16位以内的整数相加会得到精确的结果,如果数字太大,相加的结果就是个近似值,这点要注意
// 对于小数来说,计算结果也可以能得到一个近似值
console.log(12345678910111213141516 + 1);  // 1.2345678910111212e+22
console.log(0.1 + 0.2);  // 0.30000000000000004

// 当数值超过一定范围后,会有infinity表示无穷
let n3 = 12345678910111213141516 ** 100;
console.log(n3, typeof n3); // Infinity number
// 由上例可以见,infinity的类型也是number类型

let n4 = 10 - 'hello';
console.log(n4, typeof n4); // NaN number
// NaN也是一个特殊数字,表示 not a number 非法数字

// 对于js中大整数的弊端,在es2020最新的标准中,新增了一个新的数字类型 bigint 表示大整数,能表示大多呢?想多大就多大
let n5 = 100n;
let n6 = 12345678910111213141516171819201234567891011121314151617181920n;
console.log(n5, typeof n5); // 100n bigint
console.log(n6, typeof n6); // 12345678910111213141516171819201234567891011121314151617181920n bigint
// 注意,bigint类型只能和bigint类型进行运算,而不能和其它类型如number进行运算
// console.log(n5 + 10); // TypeError: Cannot mix BigInt and other types, use explicit conversions
console.log(n5 + 10n);  // 110n

其他进制

// 二进制以 0b开头
// 八进制以 0o开头
// 十六进制以 0x开头
// 无论哪个进制的数字,输出都会转换为10进制表示
let n1 = 0b10;
let n2 = 0o10;
let n3 = 0x10;
console.log(n1, n2, n3);  // 2 8 16

布尔值boolen

通常用布尔值来进行逻辑判断。

// 布尔值只有true和false这两个值
let t = true;
let f = false;
console.log(t, typeof t); // true boolean
console.log(f, typeof f); // false boolean

null和undefined

null通常用来表示一个空的对象,一个不存在的对象。

  • null类型只有一个值,那就是null。
  • 使用typeof检查null时,会返回一个object,意思是这个对象是存在的,但不存在也是一个对象——我瞎猜的!!

undefined表示未定义,当定义一个变量但未赋值时,那这个对象就是undefined。

  • undefined类型的值只有一个,那就是undefined。
  • 如果typeof检查undefined时,会返回一个字符串类型的undefined。
let n;
console.log(n);  // undefined

当你将一个值设置为空时,请使用null而不要用undefined。

类型转换

所谓的类型转换,指的是将其他数据类型转换为string、number、boolean类型。

其他类型转字符串

来看都有哪些类型能转为字符串。

let a = 10;
let b = a.toString(); // 转换后生成一个新值,被赋值给变量b
console.log(a, typeof a); // 10 number   可见a还是那个a
console.log(b, typeof b); // 10 string
let c = NaN;
let d = true;
let e = null;
let f = undefined;
console.log(c.toString(), typeof c.toString());  // NaN string
console.log(d.toString(), typeof d.toString());  // true string
// console.log(e.toString(), typeof e.toString()); // TypeError: Cannot read property 'toString' of null
// console.log(f.toString(), typeof f.toString()); // TypeError: Cannot read property 'toString' of undefined

console.log(String(a), typeof String(a)); // 10 string
console.log(String(c), typeof String(c)); // NaN string
console.log(String(d), typeof String(d)); // true string
console.log(String(e), typeof String(e)); // null string
console.log(String(f), typeof String(f)); // undefined string

几种常见的类型中,undefined和null无法转为字符串(因为它俩没有toString方法),其他都正常。说白了,只要被转的数据类型具有toString方法,就能转为字符串。

注意,转换类型,并不是将原数值直接转类型,而是转类型后会生成一个新的值,原值不变。

另外的,还可以使用String函数来转换,其原理:

  • 对于具有toString方法的数据类型,直接调用其toString方法进行转为字符串。
  • 对于不具有toString方法的数据类型,null和undefined来说:
    • null转为"null"。
    • undefined转为"undefined"。

但String其实是用来声明一个字符串的。

其他类型转number

使用Number函数来转换,其转换情况:

  • 字符串转number:
    • 如果字符串是合法的数字,如"123",则能转换成对应的数字。
    • 如果字符串不是合法的数字,如123ababc,则转换为NaN。
  • 布尔值:
    • true转为1。
    • fasle转为0。
  • undefined和null:
    • undefined转为NaN。
    • null转为0。
let a;
a = "abc"; // NaN number
a = "314abc"; // NaN number
a = "3.14"; // 3.14 number
a = "0xfff"; // 4095 number
a = "Infinity"; // Infinity number
a = true; // 1 number
a = false; // 0 number
a = null; // 0 number
a = undefined; // NaN number
console.log(Number(a), typeof Number(a));

除了Number之外还有两个函数可以转数字。

parseInt将一个字符串转换为数字:

  • 该函数会从左到右依次读取字符串的每个合法的数字类型的字符,然后将其转换为数字,如123px的转换结果为123
  • 也可以对数字进行取整,但性能稍差。
let a;
a = "123"; // 123 number
a = "123px"; // 123 number
a = "123px123"; // 123 number
a = "3.14"; // 3 number
a = 3.14; // 3 number
console.log(parseInt(a), typeof parseInt(a));

parseInt还有个邻居叫做parseFloat函数,它专门用来转有效的浮点型的。

let a;
a = "123"; // 123 number
a = "123px"; // 123 number
a = "123px123"; // 123 number
a = "3.14"; // 3.14 number
a = "3.14px"; // 3.14 number
a = "3.14px123"; // 3.14 number
console.log(parseFloat(a), typeof parseFloat(a));

其它类型转布尔值

在JavaScript中,所有的对象、基础数据类型和其它数据类型都有自己的对应的布尔值。

对于数值来说,除了0,其它都转为true。

另外,所有表示没有或者错误的都转为false。

let a;
a = 1; // true boolean
a = -1; // true boolean
a = Infinity; // true boolean
a = 0; // false boolean

// 字符串,除了空字符之外都是true
a = 'abc'; // true boolean
a = '  '; // true boolean
a = ''; // false boolean

a = NaN; // false boolean
a = undefined; // false boolean
a = null; // false boolean

a = false; // false boolean
a = true; // true boolean
console.log(Boolean(a), typeof  Boolean(a));

所有,false的情况:0、NaN、null、false、undefined、空字符串。

隐式类型转换

// 原来我们都是通过Number函数来将一个任意类型的值转为数字
// 现在对于能转为数字的类型,可以通过 + 0; * 1 的方式来做
let a;
a = true + 0; // 1
a = true * 1; // 1
a = true - 0; // 1
a = null - 0; // 0
// 上面的true,包括false、null本身就可以通过Number转为一个数字,所以上面的操作经过了一个隐式转换
// 先将true之类的转为数字,然后再跟相加或者相乘得到结果。
// 下面的undefined之所以不行,是因为undefined本身转为数字的结果就是NaN,所以隐式转换失败
a = undefined - 0; // NaN
console.log(a);

// 对于字符串来说,任何值和字符串做加法时,也会将其它类型的值转为字符串类型的结果,然后跟字符串相加。
a = 'hello' + 123; // hello123 string
a = 1 + 'hello' + 2; // 1hello2 string
// 利用其隐式转换的特性,我们可以将其它类型的值转为字符串,也就是加上空字符串
// 这是除了String函数转类型之外的另一种方式,原理是一样的,先String转类型再相加
a = true + ''; // true string
console.log(a, typeof a); // 1

// 再来看其它情况
// +号不会对数值产生影响
a = 10;
// a = +a; // 10 number
// 但 - 号会对数值进行符号位取反,即正的变负的;负的变正的
a = -a; // -10 number
a = -20;
a = -a; // 20 number

// 对于非数值类型的正负运算时,它会先将其转换为数值,然后再运算
// 如下示例,也是发生了隐式转换,即先转为数值再正负运算
a = true;
a = +true; // 1 number
console.log(a, typeof a);

运算符

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators

首先理解一下表达式的概念。那什么是表达式呢?如一个计算面积的算是就算是表达式。

表达式可以分解为运算符(操作符)与操作数,运算符是为了完成某个功能,它们由如+-这样的符号或者其它特定的关键字表示,运算符需要数据来进行计算,这样的数据被称为操作数。在计算面积的示例中,3和4为操作数,而area整体则称为表达式,如:

// area = l * w
let l = 3;
let w = 4;
let area = l * w;
console.log(area); // 12

我们将长和宽通过变量保存起来,然后通过表达式来计算它的面积,计算的结果保存在area中,通过打印area输出结果。
运算符和表达式是程序的主要基本构成。

关系运算符

关系运算符执行的是比较运算。每个关系运算符都返回一个布尔值。

运算符 描述 备注
> 大于
< 小于
>= 大于等于
<= 小于等于

关于强弱的事儿,你一定有疑问,来看示例:

let a = 2;
let b = "2";
console.log(a == b);  // true
console.log(a === b); // false
console.log(a != b);  // false
console.log(a !== b); // true

对于非数值类型进行大小比较时,会先将其转换为数字,然后再比较:

let a;
a = 10 < "55"; // true
a = 10 < "5"; // false

// undefined转为数值为NaN,null转成0, 而NaN表示 not a number,不是个数字
// 不是数字的和0比,没法比,所以怎么比都返回false
a = null < undefined; // false
a = null > undefined; // false

// 如果是两个字符串比较大小,不再是转为数字进行比较,而是按照字符在ASCII码表上的位置(数字编号)进行逐位比较的
a = '1' < '5'; // true
a = '10' < '5'; // true
console.log(a);

等性运算符

https://www.w3school.com.cn/js/pro_js_operators_equality.asp

等性运算就很复杂了啊!!!

首先,等性运算符,也有称相等运算符、比较运算符,主要用来判断两个值是否相等的。

ECMAScript 提供了两套等性运算符:等号(==)和非等号(!==)用于处理原始值,全等号(===)和非全等号(!===)用于处理对象。

等号和非等号的等性运算

为了确保等号两边的值相等或者非等号两边的值不相等,两边的值都会进行类型转换,其转换规则参考:

  • 如果是布尔值、字符串、number类型之间的比较,那么检查相等性之前,会把它转换为数值(false 转换成 0,true 为 1)。
  • 值 null 和 undefined 相等,且在检查相等性时,不能把 null 和 undefined 转换成其他值。
  • 如果两个值有一边或着两边全是 NaN,等号将返回 false,非等号将返回 true,也可以直接说NaN不和任何值相等,包括它本身。
  • 如果两边都是对象,那么比较的是它们的引用值。如果两个指向同一对象,那么等号返回 true,否则两个不等。
let a;
// 同类型无需多说
a = 10 == 10; // true
a = 'abc' == 'ABC'; // false
a = true == true; // true

// number和string比的话,会先将string类型转换成number再比
a = 10 == '10'; // true   '10' --> 10
a = 10 == '11'; // false

// 布尔值和number或者布尔值和string比,也是都先转为number后再比
a = true == 1; // true
a = true == '1'; // true
a = true == 'true'; // false

// 值 null 和 undefined 相等,且在检查相等性时,不能把 null 和 undefined 转换成其他值。
a = null == undefined; // true
a = null == 0; // false  虽然Number(null)的结果是0,但它仍然不等于0,所以,证明null在等性运算时,不做类型转换
// 但使用null做其它运算时,如大于等于、小于等于时,会转换类型,这点需要注意
a = null >= 0; // true

// 如果两个值有一边或着两边全是 NaN,等号将返回 false,非等号将返回 true。
a = NaN == NaN; // false
a = NaN == true; // false
a = true == NaN; // false
console.log(a, Number(null));

全等号和非全等号的等性运算

全等号和非全等号这里就相对简单些,因为在比较时不会发生自动的类型转换,即如果两边的类型不同,直接返回false。

let a;
a = 10 === 10; // true
a = 10 === '100'; // false
a = null === undefined; // false  null和undefined相等但不全等
a = true === 1; // false
a = NaN === NaN; // false
console.log(a, Number(null));

另外,关于NaN,上面说NaN不和任何值相等,包括它本身,那我如何判断一个值是否是NaN呢?这里有个专门的函数来判断:

let a;
a = NaN;
console.log(isNaN(a)); // true

a = 123;
console.log(isNaN(a)); // false

最后,如果在等性运算时,优先使用全等号或者非全等号运算。

算数运算符

运算符 描述 备注
+
-
*
** 平方
/ 在js中,如果0作为被除数,则返回Infinity。
% 取模
++ 自增,每次自增1 自增自减的特点注意看示例
-- 自减,每次自减1

先来看自增:

let a;
a = 2;
// ++在后,返回自增前的值,即原值
// console.log(a++); // 2
// console.log(a);   // 3

// ++在前,返回自增后的值,即加一后的新值
// console.log(++a); // 3
// console.log(a);   // 3

// 下面a的值是多少?
a = a++; // 等价于 a = 2
console.log(a); // 2

// 更多练习
let a;
// console.log(a); // undefined
// a的值是多少?
a = a++ + ++a + a;
console.log(a); // NaN
// 因为a本身没有赋值所以是undefined,最后加来加去也是一个不存在的值,所以返回NaN

// a的值是多少?
a = 2;
//   2     4    4
a = a++ + ++a + a;
// a++的结果是2,但++a时的a是a++的结果3,再++等于4;最后的a是4,最终结果是10
console.log(a); // 10

自减:

无论--在前还是在后,都是用来使变量自减一的,区别:

  • --a返回自减后的值,即新值,说白了,先给钱再干活。
  • a--返回自减前的值,即原值,数白了,先干活再给钱。
let a;
a = --a; // NaN  因为a没赋值

a = 2;
a = --a; // 1

a = 2;
a--; // 1
console.log(a);

更多的:

// 取模
console.log(a % 2);  // 0

// 下面是正常的幂运算
a = 2 ** 10; // 1024
// 如果是0.5,则是求一个数的平方根
a = 2 ** 0.5; // 1.4142135623730951
a = 16 ** 0.5; // 4
console.log(a);

// 字符串的相加,注意,任何值和字符串做加法时,都会其他类型的值先转为字符串,然后再进行字符串的拼接
// 这就是内部做了隐式的类型转换
a = 'hello ' + 'world';
console.log(a); // hello world

// 除了字符串的相加之外,对其他类型的值进行算术运算时,都会转换为数值后再进行运算
a = true + false; // 1
a = true + 1; // 2
a = 123 * null; // 0

// 除了字符串,任何值和NaN做任何运算结果都是NaN
a = 123 - undefined; // NaN
a = 123 + NaN; // NaN
a = '123' + NaN; // 123NaN

赋值运算符

运算符 描述 备注
= let a = 10;,将右边的值赋值给左边的变量
+= 加法赋值运算符,变量加完值再将值赋值给这个变量本身
-= 减法赋值运算符
*= 乘法赋值运算符
/= 除法赋值运算符
%= 除法赋值运算符
**= 幂赋值运算符
let a;
a = 2;
a += 2; // 4
a -= 2; // 2
a *= 2; // 4
a /= 2; // 2
a **= 2; // 4
a %= 2; // 0
console.log(a);

逻辑运算符

运算符 描述 备注
! 逻辑非,not取反的意思
&& 逻辑与,and的意思
|| 逻辑或,两边只要有一边为true,则返回true,否则返回false

布尔值的逻辑运算:

/* 逻辑非 */
let a;
a = true; // true
a = !a; // false
a = false; // false
a = !a; // true
console.log(a);

// 如果对一个非布尔值进行逻辑非运算
// 也是先Boolean转为布尔值,再取反
let a;
a = 10;
a = !a; // false boolean

// 利用上面的套路,可以对一个任意值,去两次反来将其转换为布尔值
a = 10;
a = !!a; // true boolean
console.log(a, typeof a);


/* 逻辑与,如果两侧的结果都是true,则返回true,否则返回false */
let a;
a = 3 > 2 && 2 != 1; // true boolean
a = true && false; // false boolean
a = true && true; // true boolean
a = false && true; // false boolean
a = false && false; // false boolean
console.log(a, typeof a);


/* 逻辑或,两边只要有一边为true,则返回true,否则返回false */
// 另外,或运算是短路运算,即只要左边的是true,就返回真,不管右边是否为真
// 如果左边的是false,就根据右边的值进行判断返回
let a;
a = true || false; // true boolean
a = true || true; // true boolean
a = false || true; // true boolean
a = false || false; // false boolean
console.log(a, typeof a);

非布尔值的逻辑运算:

/*
与运算:如果对非布尔值进行与运算,会首先将其转换为布尔值,然后运算,最终返回其原值
*/
let a;
// 如果两边都为true,返回右边的值
a = 'abc' && 1; // 1
a = 1 && 'abc'; // abc

// 如果两边一边为真一边为假,返回假的值
a = 1 && NaN; // NaN
a = NaN && 1; // NaN

// 如果两边都为假,返回左边的
a = NaN && 0; // NaN
a = 0 && NaN; // 0
console.log(a);


/*
或运算:如果对非布尔值进行或运算,会首先将其转换为布尔值,然后运算,最终返回其原值
*/
let a;
// 如果两边都为true,返回左边的值,即只要发现左边为真,就返回左边的值,右边的就不看了
a = 'abc' || 1; // abc
a = 1 || 'abc'; // 1

// 如果两边一边为真一边为假,返回真的值
a = 1 || NaN; // 1
a = NaN || 1; // 1

// 如果两边都为假,返回右边的,即发现左边为假,就把右边的值返回了,虽然右边的值也是假值
a = NaN || 0; // 0
a = 0 || NaN; // NaN
console.log(a);

条件运算符

所谓的条件运算符,在其他语言中也被称为三元运算符或者三目运算符,其语法:

条件表达式 ? 语句1 : 语句2

执行流程:首先执行条件表达式,然后根据该表达式执行的布尔结果:

  • true则执行语句1。
  • false则执行语句2。
let a = 10;
let b = 20;
a > b ? ++a : ++b;
console.log(a, b); // 10 21

let max = a > b ? a : b;
console.log(max); // 21

// 扩展,三个值进行比较
let a = 10;
let b = 20;
let c = 30;
// 第一种写法
// let max = a > b ? a : b;
// max = max > c ? max : c;
// console.log(max); // 30

// 第二种写法,可读性较差,不推荐
// let max = a > b ? a > c ? a : c : b > c ? b : c;
let max = a > b ? a > (c ? a : c ): (b > c ? b : c);
console.log(max); // 30

运算符的优先级

所谓的运算符的优先级,也就是说多个运算符中,先执行和后执行谁的问题,就像数学上先乘除后加减,同级的从左到右一次运算。

所以,js中,这么多的优先级,也有自己的优先级,参考:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#table

上述链接中的表中,从上到下优先级依次降低,每个优先级的序号表示多个运算符处于同级。

优先级的表中列了很多,一般的,我们也无需都记住,遇到优先级不清楚的,可以查表或者用括号来改变优先级。

流程控制语句

无论在什么语言中,就目前而言,一般的代码执行起来基本上都可以包括三个部分:

  • 顺序执行,代码从上到下依次执行。
  • 分支语句,或称为条件语句、选择语句,在顺序执行过程中,根据条件的不同,执行不同的代码逻辑。
  • 循环语句,在顺序执行过程中,根据循环条件的成立与否,决定是否重复执行循环语句内的代码逻辑。

顺序执行没啥好说的,我们将重点研究分支语句和循环语句。

所谓的不同的代码逻辑,就是指将代码分为若干块,或者组,根据需要执行不同的代码块内的若干代码。

所以,先来了解下代码块。

代码块

js中通过{}来标识不同的代码块,一对{}表示一个代码块。

同一代码块中的代码,要么都执行,要么都不执行。

代码块也标识了作用域的概念,即变量、对象等起作用的范围,看示例:

{
    // 代码块中使用let声明的变量,只能在代码块内部使用,外部无法访问
    // 这说明let声明的变量,只能在当前代码块内使用,即变量起作用的范围
    let a = 10;
    console.log('代码块中的a = ', a); // 代码块中的a =  10
}
// console.log('代码块外部的a = ', a); // ReferenceError: a is not defined


{
    // 但是使用var声明的变量,没有块作用域的限制,在外部也可以访问
    var b = 10;
    console.log('代码块中的b = ', b); // 代码块中的b =  10
}
console.log('代码块外部的b = ', b); // 代码块外部的b =  10

另外,代码块的{}结尾无需使用;作为结束。

条件语句if/else

条件语句这里则主要通过if/else语句来控制。注意,条件语句能够反复使用和嵌套使用。

基本语法:

let a = 10;
// 括号内为条件表达式,if语句就是根据条件表达式的执行结果来决定执行哪部分代码
if (a > 10) {
    // 当条件表达式的执行结果为true时,执行当前代码块的内容
    console.log(1);
}else{
    // 当条件表达式的执行结果为false时,执行当前代码块的内容
    console.log(0);
}

相关用法:

// if单独使用
let a = 10;
if (a > 10) {
    console.log(1);
}

// 可以重复使用
if (a == 10) {
    console.log(1);
}

// if/else结构,只能执行其中一个代码块
let a = 10;
if (a > 10) {
    console.log(1);
}else{
    console.log(0);
}

// if的嵌套形式
let numb = prompt("输入一个整数:");
let num = Number(numb);
if (isNaN(num)){
    alert(`你输入的[${numb}]不是合法数字类型!`);
}else{
    if (num % 2 === 0){
        alert(`你输入的[${num}]是偶数`);
    }else{
        alert(`你输入的[${num}]是奇数`);
    }
}

// 多条件的if语句,注意,只执行其中一个符合条件的分支
let a = 10;
if (a > 10) {
    console.log('a > 10');
}else if(a < 10){
    console.log('a < 10');
}else if(a == 10){
    console.log('a == 10')
}else{
    console.log('上面的条件都不成立,才执行我这个分支')
}

// 另外,注意条件语句的设定,避免出现dead code,永远也不会执行
// 如下示例,只要年龄大于等于10岁,都会被第一个if条件拦截,后面的几个分支就永远不会执行
// 解决方式就是将条件写完整
let age = 38;
if (age >= 10){
    console.log('你已经成年了!');
}else if(age >= 40){
    console.log('40不惑!');
}else if(age >= 70){
    console.log('古来稀!');
}else{
    console.log('小屁孩!')
}

练习:

// 1. 编写一个程序,获取用户输入的数字,然后通过程序显示这个数字是奇数还是偶数
// 2. 编写程序,判断给定的年份是否是闰年,如果一个年份可以被4整除而不能被100整除,或者可以被400整除,那这个年份就是闰年
// 3. 编写程序,获取用户输入的狗的年龄,然后通过程序显示相当于多少岁的人的年龄,如5岁的狗,相当于10.5 + 10.5 + 4 + 4 + 4 = 33岁
//	  即狗的头两年,每年相当于人类10.5岁,后续都是每年相当于人类长4岁,如果用户输入的是负数,请给出相应的提示

参考:

// 1. 编写一个程序,获取用户输入的数字,然后通过程序显示这个数字是奇数还是偶数
let numb = prompt("输入一个整数:");
let num = Number(numb);
if (isNaN(num)){
    alert(`你输入的[${numb}]不是合法数字类型!`);
}else{
    if (num % 2 === 0){
        alert(`你输入的[${num}]是偶数`);
    }else{
        alert(`你输入的[${num}]是奇数`);
    }
}

// 2. 编写程序,判断给定的年份是否是闰年,如果一个年份可以被4整除而不能被100整除,或者可以被400整除,那这个年份就是闰年
let numb = prompt("输入年份:");
num = Number(numb);
if (isNaN(num)){
    alert(`你输入的[${numb}]不是合法数字类型!`);
}else{
    if (num % 4 === 0 && num % 100 !== 100 || num % 400 === 0){
        alert(`你输入的[${num}]是闰年`);
    }else{
        alert(`你输入的[${num}]是平年`);
    }
}

// 3. 编写程序,获取用户输入的狗的年龄,然后通过程序显示相当于多少岁的人的年龄,如5岁的狗,相当于10.5 + 10.5 + 4 + 4 + 4 = 33岁
//	  即狗的头两年,每年相当于人类10.5岁,后续都是每年相当于人类长4岁,如果用户输入的是负数,请给出相应的提示
let age_str = prompt("输入你家狗的年龄:");
let age = Number(age_str);
if (isNaN(age)){
    alert(`你输入的[${age_str}]不是合法数字类型!`);
}else{
    if (age < 0){
        alert(`你输入的是[${age}],请输入正确的年龄`);
    }else {
        let r = age <= 2 ? 10.5 * age : 10.5 * 2 + (age - 2) * 4;
    	alert(`[${age}]岁的狗,相当于[${r}]岁的人`);
    }
}

条件语句switch

条件语句除了if语句之外,还有switch语句,用的不多,而且可以使用if语句来代替,也可以说是在某些情况下用来代替if语句的,是你的代码更简洁。

基本语法:

switch(条件表达式){
    case 表达式2:
        语句1;
        break;
    case 表达式2:
        语句2;
        break;
    .........
}

执行流程:自上而下,依次将switch后的条件表达式和每个case后的表达式进行全等性(===)运算,默认的,如果全等,则执行对应case中的代码块和后面case中的代码块,比如有如下场景:

// 根据num值的不同,返回其对应的大写形式
// 法1,使用if实现
let num = 3;
if (num === 1){
    console.log('壹');
}else if(num === 2){
    console.log('贰');
}else if(num === 3){
    console.log('叁');
}

// 使用switch实现
// 默认的,三个log都会输出,因为它符合第一个case的条件,按照默认机制,它先执行自己内部的代码块,
// 然后执行它后面的所有的case中的代码块.....这就是默认机制
let num = 1;
switch (num){
    case 1:
        console.log('壹');
    case 2:
        console.log('贰');
    case 3:
        console.log('叁');
}

// 如何避免这种机制,或者就想让输出结果if语句中一样,num等于谁,就单独执行自己内部的代码块不就完了吗
// 这就要使用break来阻断后续的case执行了
let num = 1;
switch (num){
    case 1:
        console.log('壹');
        break;
    case 2:
        console.log('贰');
        break;
    case 3:
        console.log('叁');
        break;
}

// 那么处理上述三种情况能处理,其它情况如何处理?这就用到了default语句了
let num = 10;
switch (num){
    case 1:
        console.log('壹');
        break;
    case 2:
        console.log('贰');
        break;
    case 3:
        console.log('叁');
        break;
    default: // default也可以写在最上面
        console.log('非法输入,请输入1`3中的数字');
}

小结:

  • switch和if语句功能类似,但开发中应以if为主。
  • switch语句适用于全等性条件较多时使用。

循环语句while

while循环有两种:

  • while语句。
  • do-while语句。

while语句

基本语法:

// 条件表达式为true,执行while中的循环体;否则就结束循环
// 当条件永为true时,while循环永不结束,这种循环称为死循环
while (条件表达式){
    循环体
}

示例:

// 打印1-10
// while循环的三要素,初始化表达式;条件表达式;更新表达式
let num = 1; // 初始化表达式,创建一个变量来控制循环的执行
while (num <= 10){ // 条件表达式,设置循环成立的条件
    console.log(num);
    num++; // 更新表达式,循环结束的条件
}

// 打印1-10,跳过7
let num = 1;
while (num <= 10){
    if (num === 7){
        num++;
        continue;
    }
    console.log(num);
    num++;
}

// // 打印1-10,当遇到7时,结束循环
let num = 1;
while (num <= 10){
    console.log(num);
    if (num === 7){
        num++;
        break;
    }
    num++;
}

上面的示例中:

  • continue表示退出本次循环,进入下一次循环,continue后面的代码不执行。
  • break表示结束当前循环,break后面的代码不执行。

do-while

do-while和while的区别:

  • do-while先执行后判断,这种机制之下,可以确保循环至少执行一次。
  • while先判断再执行。

基本语法:

do{
   语句 
}while (条件表达式){
   语句
}

正常循环,二者基本没啥区别:

// 打印1-9,下面两种循环结果一样的
let num = 1;
while (num < 10){ 
    console.log(num);
    num++;
}

let num = 1;
do{
    console.log(num);
    num++;
}while (num < 10);

但是下面这种情况就有区别了:

// 下面循环不执行
let num = 10;
while (num < 10){
    console.log(num);
    num++;
}

// 下面循环执行一次
let num = 10;
do{
    console.log(num);
    num++;
}while (num < 10);

所以,do-while和while的最大区别就是,do-while可以确保循环至少循环一次,其他时候,跟while循环没啥区别。

循环语句for

在js的循环中,for循环可以说是更简洁的while循环。

基本语法:

for(初始化表达式; 条件表达式; 更新表达式){
    语句
}

从基本语法示例中可以看到,while循环的三个要素原来是写在不同的三处的,而for循环中,将它们三者简写到一行上了。

示例:

// 打印1-10
for(let i = 1; i <= 10; i++){
    console.log(i);
}

// 其它写法
for(let i = 1; i <= 10;){
    console.log(i);
    i++
}

for循环中的表达式都可以省略不写,那就是死循环。

for(;;){
    console.log('hello');
}

注意,for循环中也有continue和break语句,其功能和while中一样,这里不再多表。
for循环字典

<script>
    let obj = {"k1":"v1", "k2":"v2", "k3":"v3"}
    for(let key in obj){
        console.log(key, obj[key])   // 打印的是 key和value
    }
</script>

循环嵌套

循环嵌套,也叫嵌套循环,也就是循环中套循环。

示例,打印99乘法表:

for(let i = 1; i <= 9; i++){
    for(let j=1;j<=i; j++){
        document.write(`${j} * ${i} = ${i * j}&nbsp;&nbsp;`);
    }
    document.write('<br>')
}

对象Object

对象也是js中的一种数据类型。

对象和基础属性类型的区别

基本的数据类型都是一个个独立的值,值与值之间都是独立的,如:

let name = "张开";
let age = 18;
let gender = "male";

上面几个变量有什么联系么?你可能说它们不是一个人的个人信息么?这是我们认为的,对于程序来说,它们三个都是一个个独立的变量而已。

所以,这也就导致了仅凭基础数据类型是无法在程序中表示复杂的数据结构的。

而对象就相当于容器,在对象中可以存储不同类型的数据,这就构造处复杂的数据结构了。

对象的基本操作

创建对象:

// 声明一个空对象,即空容器
// let obj = new Object(); // 空对象
let obj = Object(); // new 可以省略不写
console.log(obj); // {}

// 其他声明对象的方式
let obj1 = new Object();
let obj2 = {};
let obj3 = {"name": "张开", "age": 18}; // 也可以直接添加上初始值
let obj4 = {name: "张开", age: 18}; // 属性名也可以不用加引号
console.log(obj1, typeof obj1); // {} object
console.log(obj2, typeof obj2); // {} object
console.log(obj3, typeof obj3); // { name: '张开', age: 18 } object
console.log(obj4, typeof obj4); // { name: '张开', age: 18 } object

现在,空对象有了,那如何向这个空对象中添加"东西"呢?

向对象中添加"东西",称为添加属性,来看基础操作:

// 以 key:value的形式添加属性
let obj = new Object();
obj.name = "张开";
obj.age = 18;
obj.name = "王张开";  // 属性重复的话,新的属性值会覆盖掉前面的属性值
obj["gender"] = "male"; // 也可以通过这种方式来添加属性
console.log(obj); // { name: '王张开', age: 18, gender: 'male' }

// 获取对象的指定属性
console.log(obj.name); // 王张开
console.log(obj.name2); // undefined 访问一个不存在的属性时,返回undefined
console.log(obj["name"]); // 王张开 也可以通过这种方式来获取属性值

// 删除属性
delete obj.age;
delete obj.age2; // 删除不存在的属性,没有返回,也不报错
delete obj; // 但不能通过delete删除对象,这个操作无效
console.log(obj); // { name: '王张开', gender: 'male' }

// 判断指定属性名是否在对象中存在
console.log('name' in obj); // true

注意,对于对象中的属性名来说,可以参考标识符的命名规范;而对于属性值来说,可以是任意的数据类型。

对象种也可以嵌套对象:

let obj = new Object();
obj.name = "张开";
obj.info = new Object();
obj.info.city = "北京";
obj.info.phone = "12323";
console.log(obj); // { name: '张开', info: { city: '北京', phone: '12323' } }
console.log(obj.info.city); // 北京

对象的可变性

所谓对象的可变性,也就是说,对象中的属性被修改时,那么所有指向它的变量都会受影响。

什么意思呢?之前在变量部分说过,当一个变量的值被修改时,只会影响它自己,对其他变量不会产生任何影响。

let a = 10;
let b = a;
console.log(a, b); // 10 10
a++;
console.log(a, b); // 11 10

但这在对象中不适用,对象在内存中的存储如下图所示。

变量对于对象是一种引用关系,只要对象所在的内存地址不变,对象内的属性变动都会影响到指向它的变量。

let a = 10;
let b = 20;
let p1 = {"name": "张开", "age": 18};
let p2 = p1;
let p3 = {"name": "赵开", "age": 20};
console.log(p1); // { name: '张开', age: 18 }
console.log(p2); // { name: '张开', age: 18 }
console.log(p3); // { name: '赵开', age: 20 }

// 修改了p1对象的某个属性值后,p2也受影响
p1.age = 28;
console.log(p1); // { name: '张开', age: 28 }
console.log(p2); // { name: '张开', age: 28 }

对象的等性判断

对象等性判断时,比较的是对象的内存地址。

let obj1 = {name:"张开"};
let obj2 = {name:"张开"};
console.log(obj1 == obj2); // false
console.log(obj1 === obj2); // false
console.log(obj1.name === obj2.name); // true 但比较其中的值是可以的

对象中属性的枚举

通过for-in将对象中的属性都取出来。

let obj = {name:"张开", age: 18, gender: "male"};
for(let i in obj){
    // i是属性名
    console.log(i, obj[i])
}
/*
name 张开
age 18
gender male
*/

解构赋值

let obj = {
    a: 1,
    b: {
        c: 2,
        d: {
            x: 3
        }
    }
};

// 标准解构赋值
// let {c} = obj.b;
// console.log(c); // 2

// 连续解构赋值c
// let {b:{c}} = obj;
// console.log(c);

// 连续解构赋值,加重命名,即将变量c重命名为value
let {b: {c:value}} = obj;
console.log(value);

console.log.time/console.logtimeEnd

通过console.time和console.timeEnd两个方法能测出其中间部分代码的执行时间。

console.time("99乘法表运行共耗时: ");
for(let i = 1; i <= 9; i++){
    for(let j=1;j<=i; j++){
        document.write(`${j} * ${i} = ${i * j}&nbsp;&nbsp;`);
    }
    document.write('<br>')
}
console.timeEnd("99乘法表运行共耗时: ");
// 99乘法表运行共耗时: : 1.740234375 ms
posted @ 2022-02-15 11:33  听雨危楼  阅读(220)  评论(0编辑  收藏  举报