基础知识
代码规范
- 每条语句独占一行,以提高代码的可读性。
- 每条语句结束必须添加";"分号,明确语句结束。
- 注释。
// 这行注释独占一行 ...代码 /* 两个消息的例子。 这是一个多行注释。 禁止多行注释嵌套多行注释!!! */ /** * 这是函数的文档注释 * @param {*} x * @param {*} n * @returns */
现代模式
背景:ES5 规范增加了新的语言特性并且修改了一些已经存在的特性。为保障兼容性,大部分修改默认不生效,需要手动启用。
"use strict":处于脚本文件的顶部时,则整个脚本文件都将以“现代”模式进行工作。
可以被放在函数体的开头,这样只在该函数中启用严格模式。
class 和 module 中自动启用严格模式。
变量规则
- 采用驼峰命名法。
let myName = "Katy"; - 变量名称必须仅包含字母、数字、符号
$和_。let $ = "Test01"; let _ = "Test02"; - 首字符必须非数字。
- 禁用
let\class\return等保留字作为变量名称。 - 受保护属性使用
_作为首字符进行声明。let _protectedProperty = true; - 私有属性使用
#作为首字符进行声明。// #为新增特性,老浏览器存在兼容性问题 let #privatedProperty = true; - 使用常量进行别名定义。
// 常量的值在执行之前就已知,名称大写 const COLOR_BLUE = "#00F"; const COLOR_ORANGE = "#FF7F00"; // 常量的值在执行之前未知,但经过计算赋值后不再修改,名称使用驼峰 const pageLoadTime = loadCostCal();
数据类型
7 种原始类型和 1 种引用类型
Number 类型
原始类型
- 包含整数和浮点数,允许乘法
*、除法/、加法+、减法-、取余%等操作。 - 特殊类型:
Infinity:表示无穷大,比任何数字都大。NaN:表示一个计算错误结果。且具有粘性,对NaN的所有数学运算都返回NaN。- 例外:
NaN ** 0结果为1。
console.info( 1 / 0 ); // Infinity
console.info( Infinity ); // Infinity
console.info( "not a number" / 2 ); // NaN,这样的除法是错误的
console.info( 3 * NaN ); // NaN
BigInt 类型
原始类型
- 为了解决
Number类型无法安全地表示大于 (2*53-1),或小于 -(2*53-1) 的整数Number并非无法显示这些数据,只是不安全,存在精确度问题
// true
console.log(9007199254740991 + 1 == 9007199254740991 + 2);
通过在普通数字末尾添加n来表示BigInt
const bigInt = 1234567890123456789012345678901234567890n;
String 类型
原始类型
JS中无Char单字符类型
共有三种定义方式:
- 双引号:"This is a string."
- 单引号:'This is a string.'
- 反引号:`This is a string.`
反引号允许进行变量、表达式的模板嵌套
let name = "John";
console.log(`Hello, ${name}!`);
console.log(`the result is ${1 + 2}`);
Boolen 类型
原始类型
仅存在两个值:true和false,用于逻辑判断。
let nameIsChecked = true;
let ageIsChecked = false;
null 值
原始类型
特殊的 null 值不属于上述任何一种类型,是一个独立的类型,只包含 null 值。用于数据的初始化。
null不是一个对不存在的 object 的引用或者null 指针。
仅仅是一个代表无、空或值未知的特殊值。
let nameIsChecked = true;
let ageIsChecked = false;
undefined 值
原始类型
独立类型,表示未被赋值,用作未初始化数据的默认值。
Symbol 类型
原始类型
用于创建对象的唯一标识符。
Object 类型
引用类型
用于储存数据集合和更复杂的实体。
typeof 运算符
返回参数的类型。
typeof undefined // "undefined"
typeof 0 // "number"
typeof 10n // "bigint"
typeof true // "boolean"
typeof "foo" // "string"
typeof Symbol("id") // "symbol"
typeof Math // "object" (1)
typeof null // "object" (2)
// JavaScript 编程语言的一个错误,实际上它并不是一个 object
typeof console.info // "function" (3)
类型转换
字符串转换
console.info 会自动将任何值都转换为字符串以进行显示。被称为隐式转换。
显式转换:
let value = true;
console.info(typeof value); // boolean
value = String(value); // 现在,值是一个字符串形式的 "true"
console.info(typeof value); // string
数字型转换
在算术函数和表达式中,会自动进行
number类型隐式转换。
// 3, string 类型的值被自动转换成 number 类型后进行计算
console.info("6" / "2");
显式转换:
let str = "123";
console.info(typeof str); // string
let num = Number(str); // 变成 number 类型 123
console.info(typeof num); // number
存在错误的转换时结果会变成NaN
let num = Number("No number in the words.");
console.info(num); // NaN
转换规则:
| 值 | 转换结果 |
|---|---|
| undefined | NaN |
| null | 0 |
| true 和 false | 1 and 0 |
| string | 去掉首尾空白字符(空格、换行符 \n、制表符 \t 等)后的纯数字字符串中含有的数字。如果剩余字符串为空,则转换结果为 0。否则,将会从剩余字符串中“读取”数字。当类型转换出现 error 时返回 NaN。 |
布尔型转换
转换规则如下:
| 值 | 转换结果 |
|---|---|
0, null, undefined, NaN, "" | false |
| 其他值 | true |
console.info( Boolean(1) ); // true
console.info( Boolean(0) ); // false
// 非空的字符串总是 true
// 在PHP中"0"为 false
console.info( Boolean("0") ); // true
console.info( Boolean(" ") ); // true
console.info( Boolean("") ); // false
运算符
普通数学运算
- 加法
+ - 减法
- - 乘法
* - 除法
/ - 取余
%
a % b的结果是 a 整除 b 的 余数。console.info(5%3); console.info(6%4); - 求幂
**
a ** b将 a 提升至 a 的 b 次幂。console.info(5**3); // 也可以实现开根号操作 console.info(6**(1/2));
字符串拼接
+被应用于字符串,它将合并(连接)各个字符串。
只要任意一个运算元是字符串,那么另一个运算元也将被转化为字符串。
+是唯一一个以这种方式支持字符串的运算符。其他算术运算符只对数字起作用,并且总是将其运算元转换为数字。
// mystring
console.info("my" + "string");
// "41",不是 "221"
console.info(2 + 2 + '1');
// "122",不是 "14"
console.info('1' + 2 + 2);
数字转化
// 4,将 '2' 转换为数字
console.info(6 - '2');
// 3,将两个运算元都转换为数字
console.info('6' / '2');
// 1
console.info(+true);
// 0
console.info(+"");
// 5
console.info(+"2" + +"3");
赋值运算符
链式赋值(不建议)
let a, b, c;
a = b = c = 2 + 2;
console.info( a ); // 4
console.info( b ); // 4
console.info( c ); // 4
原地修改
let n = 2;
// 现在 n = 7(等同于 n = n + 5)
n += 5;
// 现在 n = 14(等同于 n = n * 2)
n *= 2;
console.info( n ); // 14
自增/自减
let n = 2;
// 用完再加
console.info( n++ ); // 2
// 加完再用
console.info( ++n ); // 4
// 同理
console.info( n-- ); // 4
console.info( --n ); // 2
位运算
位运算符把运算元当做 32 位整数,并在它们的二进制表现形式上操作。
- 按位与 (
&) - 按位或 (
|) - 按位异或 (
^) - 按位非 (
~) - 左移 (
<<) - 右移 (
>>) - 无符号右移 (
>>>)
详细学习点击:位操作符
逗号运算符
逗号运算符能处理多个表达式,使用 , 将它们分开。每个表达式都运行了,但是只有最后一个的结果会被返回。
逗号运算符的优先级非常低,比 = 还要低
let a = (1 + 2, 3 + 4);
console.info( a ); // 7(3 + 4 的结果)
运算符优先级
| 优先级 | 名称 | 符号 |
|---|---|---|
| 15 | 一元加号 | + |
| 15 | 一元负号 | - |
| 14 | 求幂 | ** |
| 13 | 乘号 | * |
| 13 | 除号 | / |
| 12 | 加号 | + |
| 12 | 减号 | - |
| 2 | 赋值符 | = |
- 数字越大,越先执行。如果优先级相同,则按照由左至右的顺序执行。
- 一元运算符优先级高于二元运算符。
比较操作
所有比较返回皆为
Boolean类型
常规数字比较
- 大于 / 小于:
a > b,a < b。 - 大于等于 / 小于等于:
a >= b,a <= b。 - 检查两个值的相等:
a == b,请注意双等号==表示相等性检查,而单等号a = b表示赋值。 - 检查两个值不相等:不相等在数学中的符号是
≠,但在 JavaScript 中写成a != b。
字符串比较
JavaScript 会使用Unicode 编码顺序进行判定。
小写字母的字符索引值更大。
字符串的比较算法非常简单:
- 首先比较两个字符串的首位字符大小。
- 如果一方字符较大(或较小),则该字符串大于(或小于)另一个字符串。算法结束。
- 否则,如果两个字符串的首位字符相等,则继续取出两个字符串各自的后一位字符进行比较。
- 重复上述步骤进行比较,直到比较完成某字符串的所有字符为止。
- 如果两个字符串的字符同时用完,那么则判定它们相等,否则未结束(还有未比较的字符)的字符串更大。
// true
console.info( 'Z' > 'A' );
// true
console.info( 'Glow' > 'Glee' );
// true
console.info( 'Bee' > 'Be' );
不同类型间比较
不同类型的值进行比较时,JavaScript 会首先将其转化为数字(number)再判定大小。
// true,字符串 '2' 会被转化为数字 2
console.info( '2' > 1 );
// true,字符串 '01' 会被转化为数字 1
console.info( '01' == 1 );
// true
console.info( true == 1 );
// true
console.info( false == 0 );
特殊情况:
直接比较结果相等,转为Boolean类型则完全相反。let a = 0; // false console.info( Boolean(a) ); let b = "0"; // true console.info( Boolean(b) ); // true! console.info(a == b);
严格相等 ===
由于不同类型比较会转为数字,因此存在缺陷问题:
// true
console.info( 0 == false );
// true
console.info( '' == false );
严格相等运算符 === 在进行比较时不会做任何的类型转换。
// false
console.info( 0 === false );
null与 undefined
使用数学式或其他比较方法 < > <= >= 时:
null 被转化为 0,undefined 被转化为 NaN。
NaN与任何值进行比较都会返回 false
而 undefined 和 null 在相等性检查 == 中不会进行任何的类型转换。
// false
console.info( null === undefined );
// true
console.info( null == undefined );
// null vs 0
console.info( null > 0 ); // (1) false
// null此时不做类型转换
console.info( null == 0 ); // (2) false
console.info( null >= 0 ); // (3) true
// undefined 不应该被与其他值进行比较
console.info( undefined > 0 ); // false (1)
console.info( undefined < 0 ); // false (2)
console.info( undefined == 0 ); // false (3)
条件分支
if
// 代码块逻辑
if (Boolean1) {
// 代码分支一
} else if (Boolean2) {
// 代码分支二
} else if (Boolean3) {
// 代码分支三
} ...
else {
...
}
三元运算符
let age = 18;
let message = (age < 3) ? 'Hi, baby!' :
(age < 18) ? 'Hello!' :
(age < 100) ? 'Greetings!' :
'What an unusual age!';
console.info( message );
// 代替 if执行代码,非常规使用
let company = 'Netscape';
(company == 'Netscape') ?
console.info('Right!') : console.info('Wrong.');
逻辑运算符
|| (或)
result = value1 || value2 || value3;
- 从左到右依次计算操作数。
- 处理每一个操作数时,都将其转化为布尔值。结果是 true,就停止计算(短路求值),返回这个操作数的初始值。
- 如果所有的操作数都被计算过,转换结果为false,则返回最后一个操作数。
&& (与)
与运算
&&的优先级比或运算||要高
result = value1 && value2 && value3;
- 从左到右依次计算操作数。
- 在处理每一个操作数时,都将其转化为布尔值。结果是 false,就停止计算(短路求值),并返回这个操作数的初始值。
- 如果所有的操作数都被计算过,都是真值true,则返回最后一个操作数。
! (非)
result = !value;
- 将操作数转化为布尔类型:true/false。
- 返回相反的值。
两个非运算 !! 用来将某个值转化为布尔类型:
// true
console.info( !!"non-empty string" );
// false
console.info( !!null );
?? (空值合并运算符)
- 新添加的 JavaScript 的特性,旧式浏览器可能需要 polyfills.
- 其对于
null和undefined的方式类似,因此这里统称为已定义的
// 如果 a 是已定义的,则结果为 a,
// 如果 a 不是已定义的,则结果为 b。
a ?? b;
// 等同于
(a !== null && a !== undefined) ? a : b;
// 从一系列的值中选择出第一个非 null/undefined 的值
let firstName = null;
let lastName = null;
let nickName = "Supercoder";
console.info(firstName ?? lastName ?? nickName ?? "匿名");
与 || 比较
优先级相同,都是
3
它们之间重要的区别是:
||返回第一个真值。??返回第一个已定义的值。
在
||眼里,false、0、空字符串 ""和null/undefined都是假值,无法有效区分。
// 数据为默认值
let height = 0;
// 返回真值
console.info(height || 100); // 100
// 返回已定义值
console.info(height ?? 100); // 0
JavaScript 禁止将
??与&&或||一起使用,必须使用括号进行优先级区分。// Syntax error let x = 1 && 2 ?? 3; // 正确定义 let x = (1 && 2) ?? 3;
循环
while
let i = 3;
while (i) { // 当 i 变成 0 时,条件为假,循环终止
console.info( i );
i--;
}
do-while
不管条件是否为真,循环体 至少执行一次
let i = 0;
do {
console.info( i );
i++;
} while (i < 3);
for
for (let i = 0; i < 3; i++) { // 结果为 0、1、2
console.info(i);
}
“计数”变量 i 是在循环中声明的。叫做“内联”变量声明。只在循环中可见。
循环跳出
break:强制退出,终止循环
let sum = 0;
while (true) {
let value = 4;
if (value > 0) break; // (*)
sum += value;
}
console.info( 'Sum: ' + sum );
continue:停止当前这一次迭代,并强制启动新一轮循环。
for (let i = 0; i < 10; i++) {
//如果为真,跳过循环体的剩余部分。
if (i % 2 == 0) continue;
console.info(i); // 1,然后 3,5,7,9
}
嵌套循环跳出标签
break <labelName> 语句跳出循环至标签处
outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
let input = "";
// 如果是空字符串或被取消,则中断并跳出这两个循环。
if (!input) break outer; // (*)
// 用得到的值做些事……
}
}
console.info('Done!');
- 标签不允许跳到代码的任意位置。
- 标签是
break/continue跳出嵌套循环以转到外部的唯一方法。
switch 多分支
case的值是严格相等===。被比较的值必须是相同的类型才能进行匹配。
// 类型不匹配,输出default部分内容
let arg = "3";
switch (arg) {
case '0':
case '1':
console.info( 'One or zero' );
break;
case '2':
console.info( 'Two' );
break;
case 3:
console.info( 'Never executes!' );
break;
default:
console.info( 'An unknown value' )
}
函数
函数声明
全局与局部变量:
// 全局变量或外部变量
let userName = 'John';
let age = 18;
function showMessage() {
// 局部变量
// 只在该函数内部可见
// 与外部变量重名时会遮蔽外部变量
let userName = "Bob";
// 对外部变量拥有全部的访问权限。函数也可以修改外部变量。
++age;
let message = 'Hello, ' + userName + " " + age; // Bob
console.info(message);
// 可以省略不返回,默认返回为undefined
// return true;
}
// 函数会创建并使用它自己的 userName
showMessage();
console.info(showMessage() == undefined); // true
// John,未被更改,函数没有访问外部变量。
console.info( userName );
参数:
function showMessage(from, text) { // 参数:from 和 text
from = '*' + from + '*';
console.info(from + ': ' + text);
}
let from = 'Ann';
showMessage(from, 'Hello!'); // *Ann*: Hello! (*)
showMessage(from, "What's up?"); // *Ann*: What's up? (**)
console.info( from ); // Ann
(*) 和 (**) 行中被调用时,给定值被复制到了局部变量 from 和 text,但函数修改的是复制的变量值副本,不会对外部变量产生影响。
术语:
- 参数(parameter)是函数声明中括号内列出的变量(它是函数声明时的术语)。
- 参数(argument)是调用函数时传递给函数的值(它是函数调用时的术语)。
函数命名规范:
- 清楚地描述函数的功能。进行函数调用时,依据函数名即可清楚知道这个函数的功能,会返回什么。
- 一个函数是一个行为,所以函数名通常是动词。
- 使用合适的函数名前缀,如
create…、show…、get…、check…等等
函数表达式
let sayHi = function() { // (1) 创建
console.info( "Hello" );
};
// 又将函数传递到别的变量中
let func = sayHi;
// 传递的是函数体
/**
function() { // (1) 创建
console.info( "Hello" );
}
*/
console.info(String(func));
// ...
末尾有分号是因为函数表达式是在赋值语句中,并非函数语法一部分。
回调函数
function ask(flag, yes, no) {
if (flag) yes()
else no();
}
// You agreed.
ask(
true,
function() { console.info("You agreed."); },
function() { console.info("You canceled the execution."); }
);
函数声明 vs 函数表达式
| 维度 | 函数声明(Function Declaration) | 函数表达式(Function Expression) |
|---|---|---|
| 语法 | 独立语句:function foo() {} | 表达式的一部分:const foo = function() {}; |
| 名称 | 必填(可用于递归调用) | 可选(匿名或具名) |
| 提升(Hoisting) | 完全提升 声明 + 函数体一起被提前 | 仅变量提升 初始化留在原地,调用时可能为 undefined |
| 创建时机 | 脚本/函数作用域“准备”阶段(预解析) | 代码执行到该行时才创建 |
| 调用时机 | 可在声明之前调用 | 只能在赋值之后调用 |
| 块级作用域 | ES6 起:块级作用域({ ... })块外不可见 | 遵循词法作用域规则(let/const 块级,var 函数级) |
| 严格模式 | 块内声明被局部化,块外 typeof 为 "undefined" | 不受此限制 |
| 适用场景 | 1. 通用、可复用函数 2. 需要提前调用 3. 代码可读性优先 | 1. 条件/按需创建 2. 回调/高阶函数 3. 需要封装为值(立即执行、传参、返回) |
箭头函数
let double = n => n * 2;
// 差不多等同于:let double = function(n) { return n * 2 }
console.info( double(3) ); // 6
// 或者
let sum = (a, b) => { // 花括号表示开始一个多行函数
let result = a + b;
// 如果我们使用了花括号,那么我们需要一个显式的 “return”
return result;
};
console.info(sum(1, 2)); // 3