基础知识

代码规范

  1. 每条语句独占一行,以提高代码的可读性。
  2. 每条语句结束必须添加";"分号,明确语句结束。
  3. 注释。
    // 这行注释独占一行
    ...代码
    /* 两个消息的例子。
    这是一个多行注释。
    禁止多行注释嵌套多行注释!!!
    */
    /**
    * 这是函数的文档注释
    * @param {*} x 
    * @param {*} n 
    * @returns 
    */
    

现代模式

背景:ES5 规范增加了新的语言特性并且修改了一些已经存在的特性。为保障兼容性,大部分修改默认不生效,需要手动启用。

"use strict":处于脚本文件的顶部时,则整个脚本文件都将以“现代”模式进行工作。

可以被放在函数体的开头,这样只在该函数中启用严格模式。

classmodule 中自动启用严格模式。

变量规则

  1. 采用驼峰命名法。
    let myName = "Katy";
    
  2. 变量名称必须仅包含字母、数字、符号 $_
    let $ = "Test01";
    let _ = "Test02";
    
  3. 首字符必须非数字。
  4. 禁用let\class\return等保留字作为变量名称。
  5. 受保护属性使用_作为首字符进行声明。
    let _protectedProperty = true;
    
  6. 私有属性使用#作为首字符进行声明。
    // #为新增特性,老浏览器存在兼容性问题
    let #privatedProperty = true;
    
  7. 使用常量进行别名定义。
    // 常量的值在执行之前就已知,名称大写
    const COLOR_BLUE = "#00F";
    const COLOR_ORANGE = "#FF7F00";
    // 常量的值在执行之前未知,但经过计算赋值后不再修改,名称使用驼峰
    const pageLoadTime = loadCostCal();
    

数据类型

7 种原始类型和 1 种引用类型

Number 类型

原始类型

  1. 包含整数和浮点数,允许乘法 *、除法 /、加法 +、减法 -、取余 % 等操作。
  2. 特殊类型:
    • 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 类型

原始类型

仅存在两个值:truefalse,用于逻辑判断。

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

转换规则:

转换结果
undefinedNaN
null0
true 和 false1 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 > ba < b
  • 大于等于 / 小于等于:a >= ba <= b
  • 检查两个值的相等:a == b,请注意双等号 == 表示相等性检查,而单等号 a = b 表示赋值。
  • 检查两个值不相等:不相等在数学中的符号是 ,但在 JavaScript 中写成 a != b

字符串比较

JavaScript 会使用Unicode 编码顺序进行判定。

小写字母的字符索引值更大。

字符串的比较算法非常简单:

  1. 首先比较两个字符串的首位字符大小。
  2. 如果一方字符较大(或较小),则该字符串大于(或小于)另一个字符串。算法结束。
  3. 否则,如果两个字符串的首位字符相等,则继续取出两个字符串各自的后一位字符进行比较。
  4. 重复上述步骤进行比较,直到比较完成某字符串的所有字符为止。
  5. 如果两个字符串的字符同时用完,那么则判定它们相等,否则未结束(还有未比较的字符)的字符串更大。
// 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 );

nullundefined

使用数学式或其他比较方法 < > <= >= 时:

null 被转化为 0undefined 被转化为 NaN

NaN与任何值进行比较都会返回 false

undefinednull 在相等性检查 == 中不会进行任何的类型转换。

// 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.
  • 其对于nullundefined 的方式类似,因此这里统称为已定义的
// 如果 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

它们之间重要的区别是:

  • || 返回第一个 值。
  • ?? 返回第一个 已定义的 值。

||眼里,false0空字符串 ""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

(*) 和 (**) 行中被调用时,给定值被复制到了局部变量 fromtext,但函数修改的是复制的变量值副本,不会对外部变量产生影响。

术语:

  • 参数(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