Skip to content

基础语法

一、基本概念

  • 区分大小写:ECMAScript 一切(变量、函数名、操作符)都是 区分大小写 的;例如:testTest变量不是同一个变量,typeOf 可作为函数名,但 typeof 是关键字不能作为函数名。

  • 标识符:指变量、函数、属性、参数的 名字;可以是一个或多个字符组合,但 第一个字符不能是数字,其他情况下可用 字母、下划线、美元符号、数字;建议采用 驼峰写法,即第一个字母小写后面单词首字母大写,其实 全部使用小写也可以(不太规范)

  • 注释

    js
    // 单行注释
    /*
     * 多行注释(为了提高可读性,多行注释前都带上*)
     */
  • 严格模式:ECMAScript5 提出的,对 3 版本的不确定行为加以处理,不安全的操作也会抛错,写法:

    js
    function doSomething() {
      "use strict";
      // 其他内容
    }
  • 语句:每句后面可以没有分号,但要是压缩代码就会出错,所以 推荐加上分号,if 语句后推荐使用{},即使只有一句,这样可以提高代码的可读性。

  • 关键字:具有特定用途,不能作为标识符。breakcasecatchclassconstcontinuedebuggerdefaultdeletedoelseexportfinallyforfunctionifimportininstanceofnewreturnswitchthisthrowtrytypeofvarvoidwhilewithyield

  • 保留字:将来可能被用作关键字,也不能作为标识符

    • 始终保留:enum
    • 严格模式下保留:implementsinterfaceletpackageprivateprotectedpublicstatic
    • 模块代码中保留:await

二、变量声明

2.1 var 声明

js 的变量可以用来保存任何类型的数据(松散类型),例如:

js
var message = "hi",
  found = false,
  age = 29;
  • 不推荐修改变量所保存值的类型,实际上是可以的;
  • var 的作用范围是它所在函数内部,如果在最外层则是全局作用域,并且会出现在 window 对象上(作为属性存在)。
  • 不推荐省略 var,虽然省略 var 可以作为全局变量,但很难维护,并且未经声明的变量赋值在严格模式抛 ReferenceError 错误;
  • 严格模式下变量名不要取“eval”和“argument”。

var 声明提升是一个值得注意的点,它会自动将变量提升到函数作用域顶部

js
function foo() {
  console.log(age);
  var age = 26;
}
foo(); // undefined;

等价于:

js
function foo() {
  var age;
  console.log(age);
  age = 26;
}
foo(); // undefined;

也就是把所有变量声明都拉到函数作用域的顶部。那么反复多次使用 var 声明同一个变量也没问题。

js
function foo() {
  var age = 16;
  var age = 26;
  var age = 36;
  console.log(age);
}
foo(); // 36;

2.2 let 声明

let 和 var 差不多,主要区别是 let 的作用范围是一个块。除此之外,let 不允许在同一个块级作用域里进行重复声明相同变量。最后,在全局作用域中使用 let 声明的变量不会作为 window 上的属性。

js
if (true) {
  let name = "Matt"; // 仅被限于if这个代码块中
  console.log(name); // Matt
}
console.log(name); // ReferenceError

let 的作用范围是一个块,这意味着它声明的变量不会在作用域中被提升

js
console.log(age); // ReferenceError
let age = 26;

但是严格来说,let 在 js 运行中也会被提升,但由于“临时性死区”的缘故,实际上不能在声明之前使用 let 变量(所以是表面上没有声明提升,实际是提升了,但是虚拟机阻止你在 let 前使用变量)。

2.3 const 声明

const 的行为与 let 基本相同,唯一区别是它声明时必须同时初始化变量,如果后续有修改该变量就会报错。

js
const age = 26;
age = 36; // TypeError

不过,如果用 const 声明一个对象,那修改它的属性是不会进行报错的。

js
const person = {};
person.name = "Matt";

三、数据类型

  • ECMAScript 有 6 种简单数据类型,Undefined、Null、Boolean、Number、String 和 Symbol。而 Object 是一种复杂数据类型。
  • 因 ECMAScript 的变量是松散类型,我们可用 typeof 操作符 来检测变量的数据类型,写法:typeof messagetypeof(message)。它会返回这些字符串:"undefined""boolean""string""number""object""function""symbol"

3.1 undefined 和 null

js
let message; // 这里相当于let message = undefined;
console.log(message == undefined); // true。message === null的结果就是false
console.log(typeof age); // "undefined"

let car = null; // 常规写法,声明变量一般都会初始化,不知道赋什么那就赋个null也是可以的
console.log(typeof car); // "object"。null值表示空对象指针,它实际上是个object类型
console.log(null == undefined); // true。undefined派生自null,但这只是出于比较的目的

事实上 null 和 undefined 完全不同,我们没有必要将变量显式设为 undefined,若要展示保存而未知其值可以 null 临时将其保存,所以不推荐let message;这种写法,可以let message = null;

3.2 boolean

布尔类型 Boolean:只有两个字面量,true(真)和false(假)。

任何数据类型的值调用Boolean()函数都会返回一个 Boolean 值:

Boolean()转换为 true 值转换为 false 值
String非空字符串""(空字符串)
Number非零数值(包含无穷大)0 和 NaN(非数值)
Object任何对象null
Undefinedn/a 或 N/A(不存在)undefined

流控制语句(例如 if 语句)会自动执行相应的 Boolean 转换

js
const message = "hello world";
if (message) {
  // 非空字符串转换为true值
  console.log("Value is true");
}

3.3 number

该类型比较受关注,用 IEEE754 格式来表示 整数和浮点数值,ECMA-262 定义了 十进制、八进制、十六进制 三种数字字面量格式。
其中八进制在严格模式下是无效的;在非严格模式下类似 079、08 会被解析成十进制的 79 和 9;另外十六进制 A-F 可大写也可小写。

3.3.1 浮点数值

  • 该数字要有一个小数点且小数点后至少要有一个数值,但不推荐.1这种写法,即不要省略整数位;
  • 为节省内存1.10.0这样的值会被转换为整数;
  • 对极大或极小值通常用科学计数法,像 0.0000003 在默认情况下(带 6 个零及以上的浮点数)自动转化为 e 表示法;
  • 需要注意的一点,由于 IEEE754 浮点计算通病0.1+0.2是等于0.30000000000000004(17 为小数),0.05+0.250.15+0.15不会出现问题,不要直接加,可以使用精度来转化。

3.3.2 数值范围

  • Number.MIN_VALUE:能表示的最小数值,通常是 5e-324;
  • Number.MAX_VALUE:能表示的最大数值,通常是 1.7976931348623157e+308;
  • Number.NEGATIVE_INFINITY:-Infinity 负无穷
  • Number.POSITIVE_INFINITY:Infinity 正无穷

isFinite()函数在参数位于最小与最大数值之间时会返回 true,例如:

js
const result = Number.MAX_VALUE + Number.MAX_VALUE;
console.log(isFinite(result)); // false

3.3.3 NaN

NaN是非数值(Not a Number),表示一个本来要返回数值的操作数但未返回数值的情况。

有两个特点:

  • 任何涉及 NaN 的操作(例如NaN/10)都会返回NaN
  • NaN与任何值都不相等,包括NaN本身,NaN == NaN为 false。

针对这两个特点定义了isNaN()函数,判断参数是否“不是数值”,函数拿到这个入参时会将这个值 尝试转换为数值,其实调用的是Number()函数。

3.3.4 数值转换

使用parseInt()parseFloat()可以把字符串转化为 整数浮点数

而使用Number()可以将任何数据类型转化为数值,其规则是:

  • 如果是 Boolean 值,true 将被转换为 1,false 将被转换为 0;
  • 如果是 null 值,返回 0,如果是 undefined,返回 NaN;
  • 如果是字符串:
    • 有效浮点格式的字符串会被转换为浮点数;
    • 整数型的字符串八进制的不会被视为八进制(011会转换为11),只会将十六进制和十进制格式的字符串转换为相等大小的十进制整数数值;
    • 空字符串""转换为 0;
    • 其他情况的字符串会转换为 NaN;
  • 如果是对象,先调用对象的valueOf()再按照上述规则转换,然后若得到 NaN 就去调用toString()再按照上述规则转换
js
Number("hello"); // NaN
Number(""); // 0
Number(true); // 1

可以看出Number()在转化字符串时比较复杂,我们推荐parseInt()来处理整数格式的字符串

  • parseInt()在解析字符串时会忽略前面的空格,直到找到 第一个数字或正负号,找不到也就是 非整数格式空字符串 时会返回 NaN(与 Number()的区别,它对空字符串也转为 NaN);
  • 在找到一个数字字符后会继续解析直到遇到 非数字字符
  • parseInt()第二参数用于指定底数(进制数),那么第一个参数就可以省略0x0o等。
js
parseInt("   1234blue"); // 1234
parseInt(""); // 空字符串使用parseInt()会解析成NaN,而Number()是0
parseInt("0xAF", 16); // 按基数16来解析,175
parseInt("AF"); // 基数16和0x必须要有一个,NaN
parseInt("10", 2); // 按二进制解析,结果为2,我们非常推荐使用这种写法

parseFloat()parseInt()类似,主要区别是parseFloat()会始终忽略字符串开头的零,因为它只解析十进制数。

js
parseFloat("1234blue"); // 1234
parseFloat("0xA"); // 0
parseFloat("0908.5"); // 908.8

3.4 String 类型

String 类型用于表示由零或多个 16 位 Unicode 字符组成的字符串。

3.4.1 转义序列

\n换行,\t制表,\b退格,\r回车,\f换页,\\反斜杠,\'单引号,\"双引号,
\xnn 以十六进制代码 nn 表示的一个字符(其中 n 为0~F),例如\x41表示A
\unnnn 以十六进制代码 nnnn 表示的一个 Unicode 字符(其中 n 为0~F),例如\u03a3表示Σ
其中 4 个字符长的转义序列和 6 个字符长的转义序列在字符串中只占 1 位。

3.4.2 字符串的特点

字符串的值是不可变的,有些例子看起来是可变,其实原字符串销毁新字符串创建是在后台完成的。

3.4.3 字符串转换

将一个值转换成字符串有两种方式,第一种是使用几乎每个值都有的toString()方法:

js
const age = 11,
  num = 10;
age.toString(); // "11"
num + ""; // "10"

toString()方法在对数值使用时可以加上参数,表示转换成几进制的数字字符串:

js
const num = 10;
num.toString(2); // "1010"

null 和 undefined 值是没有 toString()方法的,那么可以使用字符串转换的第二种方式String(),其规则是:传入的值有 toString()则会调用 toString(),若传入的值是 null、undefined 则返回"null""undefined"这样的字符串。

其实转化为字符串有种更快捷的方法就是xxx + ""

3.4.4 模板字面量

es6 新增了使用模板字面量定义字符串的能力(是一个特殊表达式,求值是一个字符串)。它保留换行字符,如果后面几行有缩进那就会保留这些空格字符。

js
const s1 = "first linet\nsecond line";
const s2 = `first line
second line`;
console.log(s1 === s2); // true

const s3 = `
<div>
  <a href="#">
    <span>Jake</span>
  </a>
</div>`;
console.log(s3[0] === "\n"); // true

模板字面量最常用的一个特性是支持字符串插值,插入的变量是从作用域中找值,这个变量是隐式调用了toString(),当然,也可以是表达式(运算或函数调用)。

js
const value = 5;
let exponent = "second";
let s = `${value} to the ${exponent} power is ${value * value}`;
console.log(s); // 5 to the second power is 25

模板字面量也支持定义标签函数,通过标签函数可以自定义插值行为。标签函数会接收被插的字符串数组形式,以及插入的值。

js
const a = 6,
  b = 9;
function simpleTag(strings, ...expressions) {
  console.log(strings);
  for (const expression of expressions) {
    console.log(expression);
  }
  return "foobar";
}
const taggedResult = simpleTag`${a} + ${b} = ${a + b}`;
// ["", " + ", " = ", ""]
// 6
// 9
// 15
console.log(taggedResult); // "foobar"

如果想把这些字符串和对表达式求值的结果拼接起来作为默认值返回的字符串:

js
const a = 6,
  b = 9;
function zipTag(strings, ...expressions) {
  return strings[0] + expressions.map((item, index) => `${item}${strings[index + 1]}`).join("");
}
const untaggedRlt = `${a} + ${b} = ${a + b}`;
const taggedResult = zipTag`${a} + ${b} = ${a + b}`;
console.log(untaggedRlt); // "6 + 9 = 15"
console.log(taggedResult); // "6 + 9 = 15"

使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或 Unicode 字符),而不是被转换后的字符表示,具体是String.raw标签函数。

js
console.log(`\u00A9`); // ©
console.log(String.raw`\u00A9`); // \u00A9

// 对于实际的换行符来说是不行的
console.log(String.raw`first line
second line`);

3.5 Symbol 类型

es6 新增了 Symbol 类型(符号类型),符号是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突。

在使用 Symbol 类型时,不能使用 new 关键字初识化它,只能是const sym = Symbol()这样的使用。它可以接收一个参数,作为对符号的描述,这个参数与符号定义或标识完全无关。

js
const fooSymbol = Symbol("foo");
const otherSymbol = Symbol("foo");
console.log(fooSymbol === otherSymbol); // false

如果需要共享和重用符号实例,那么可以使用Symbol.for(param),其中 param 是键(只能是字符串)。它在调用时会检查全局符号注册表,如果不存在对应的符号,就会生成一个新符号实例放入注册表中,存在就会返回该符号实例。如果你想通过符号实例反过来查询键,可以使用Symbol.keyFor(param),其中 param 是某个符号实例。

js
const fooSymbol = Symbol.for("foo");
const otherSymbol = Symbol.for("foo");
console.log(fooSymbol === otherSymbol); // true
console.log(Symbol.keyFor(fooSymbol)); // "foo"

符号可以在对象字面量、Object.defineProperty()Object.definedProperties()里定义唯一属性。

js
const s1 = Symbol("foo");
const o = { [s1]: "foo val" };
console.log(o); // {Symbol(foo): 'foo val'}

const s2 = Symbol("bar");
Object.defineProperty(o, s2, { value: "bar val" });
console.log(o); //{Symbol(foo): 'foo val', Symbol(bar): 'bar val'}

const s3 = Symbol("baz");
const s4 = Symbol("qux");
Object.defineProperties(o, {
  [s3]: { value: "baz val" },
  [s4]: { value: "qux val" },
});
// {Symbol(foo): 'foo val', Symbol(bar): 'bar val', Symbol(baz): 'baz val', Symbol(qux): 'qux val'}
console.log(o);

Object.getOwnPropertyNames()返回对象实例的常规属性数组,Object.getOwnPropertySymbols()返回对象实例的符号属性数组,Object.getOwnDescriptors()返回对象的常规属性和符合属性组成的一个新对象,Reflect.ownKeys()返回两种类型的键。

js
const s1 = Symbol("foo"),
  s2 = Symbol("bar");
const o = {
  [s1]: "foo val",
  [s2]: "bar val",
  baz: "baz val",
  qux: "qux val",
};
// ['baz', 'qux']
console.log(Object.getOwnPropertyNames(o));
// [Symbol(foo), Symbol(bar)]
console.log(Object.getOwnPropertySymbols(o));
// {baz: {…}, qux: {…}, Symbol(foo): {…}, Symbol(bar): {…}}
console.log(Object.getOwnDescriptors(o));
// ['baz', 'qux', Symbol(foo), Symbol(bar)]
console.log(Reflect.ownKeys(o));

3.6 Object 类型

ECMAScript 中的对象其实就是一组 数据和功能 的集合。对象可以通过执行 new 操作符后跟要创建类型的名称来创建。Object 类是派生其他对象的基类,它所具有的属性和方法在派生的对象上同样存在。

Object 的每个实例都具有以下属性和方法:

  • constructor:构造函数;
  • hasOwnProperty(propertyName):用于判断某个属性是在实例上还是在原型上;
  • isPrototypeOf(object):用于检查传入的对象,是否是当前对象的原型;
  • propertyIsEnumerable(propertyName):用于检查给定的属性是否能使用 for-in 枚举;
  • toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应;
  • toString():返回对象的字符串表示;
  • valueOf():返回对象的字符串、数值或布尔值表示,通常与toString()方法的返回值相同。

四、操作符

可以操作字符串、数值、布尔值、对象(调用 valueOf()和 toString())

4.1 一元操作符

只能操作一个值。

4.1.1 递增和递减操作符

分为前置型和后置型。

js
const num1 = 2,
  num2 = 20;
const num3 = --num1 + num2; // 等于21,前置型会先自己“减减”再去参与外界计算
const num4 = num1++ + num2; // 等于21,上一步num1为1,这一步是先参与外界计算,后才自己去“加加”

上述对于数值,对于 其他类型也可用,只是自动调用了Number()函数,再去操作“++”、“--”

4.1.2 一元加和一元减操作符

加号(+)放在数值前面对数值没有影响,而一元减相当于添加了符号。在一元加和一元减对非数值应用时跟一元操作符类似也调用了Number()函数。

js
let s1 = "01";
s1 = -s1; // s1的值变为了 -1
let s2 = "1.1";
s2 = -s2; // s2的值变为了 -1.1
let s3 = "z";
s3 = -s3; // s3的值变为了 NaN
let b = false;
b = -b; // b的值变为了 0
let f = 1.1;
f = -f; // f的值变为了 -1.1
let o = {
  valueOf: function () {
    return -1;
  },
};
o = -o; // o的值变为了 1

4.2 位操作符

  • ECMAScript 中所有数字都以 64 位格式存储,但位操作符是操作 32 位格式的,所以操作前后是转换的;第 32 位是符号位:0 表示正数,1 表示负数;
  • 负数在二进制中是以“二进制补码”格式存储的,计算补码的步骤:
    • 求这个数值绝对值的二进制码
    • 求二进制反码
    • 在二进制反码上加 1
  • 而 ECMAScript 会尽力隐藏这些信息,一般是将这个负数的绝对值的二进制码前加一个负号,例如:const num = -18; num.toString(2); // “-10010”
  • NaN 和 Infinity 值应用位操作符时会被当成 0 处理,而对于非数值应用操作符会先用Number()函数将值转化为数值(自动完成的),后再用位操作符。

4.2.1 按位非(NOT)

按位非操作符由一个波浪线(~)表示,按位非歧视就是求反码。

js
const num1 = 25;
const num2 = ~num1; // 等价于 const num2 = -num - 1;即操作数的负值减1
console.log(num2); // -26   ~在底层执行速度要更快

4.2.2 按位与(AND)

按位与操作符由一个和号(&)表示,它有两个操作数。
按位与操作只在两个数值的对应位都是 1 时才返回 1,任意一位是 0 都返回 0。

4.2.3 按位或(OR)

按位或操作符由一个竖线(|)表示,它也有两个操作数。
按位或操作在两个数值的对应位至少有一个 1 就可返回 1,都是 0 才返回 0。

4.2.4 按位异或(XOR)

按位异或操作符由一个插入符号(^)表示,它也有两个操作数。
按位异或操作在两个数值的对应位不同时返回 1,都是 1 或都是 0 才返回 0.

4.2.5 左移

左移操作符由两个小于号(<<)表示,符号右侧是要向左移的位数。
这个左移不会影响操作数的符号位,2 左移 5 位是 64,而-2 就是-64。

4.2.6 有符号的右移

有符号的右移操作符由两个大于号(>>)表示,它右移空出的位置是用符号位的值来填充的。

4.2.7 无符号右移

无符号右移操作符由 3 个大于号(>>>)表示面对整数来说>>>>>结果相同。但对负数来说,右移空出的位置是用 0 填充的不再是符号位(负数符号位是 1)。

js
const value1 = -64; // 二进制11111111111111111111111111000000
const value2 = value1 >> 5; // 二进制11111111111111111111111111111110
const value3 = value1 >>> 5; // 二进制00000111111111111111111111111110

4.3 布尔操作符

4.3.1 逻辑非

逻辑非操作符由一个叹号(!)表示,其实它相当于先调用Boolean()函数再取反,而!!""里的两个!就相当于Boolean()了,Boolean()函数转换规则可以参考之前的3.2 boolean

4.3.2 逻辑与

逻辑与操作符由两个和号(&&)表示,有两个操作数,真值表类似按位与。

  • 如果第一个操作数是对象或第一个操作数(可能是表达式)求值结果为 true 时,它会返回第二操作数,否则返回一个操作数;
  • 如果第一个操作数就是 null、NaN、undefined,就直接返回 null、NaN、undefined;
  • 如果第一个操作数结果为 true 或对象,第二个操作数不能是未定义,否则会报错。

4.3.3 逻辑或

逻辑或操作符由两个竖线符号(||)表示,有两个操作符,真值表类似按位或。

  • 如果第一个操作数是空对象(null)或第一个操作数(可能是表达式)求值结果为 false,会返回第二个操作数,否则返回第一个操作数;
  • 如果两个操作数都是 null、NaN、undefined,则返回 null、NaN、undefined;
  • 如果第一个操作数结果为 false 或空对象(null),第二个操作数不能是未定义的,否则会报错。

4.4 乘性操作符

ECMAScript 定义了 3 个乘性操作符:乘法、除法和求模。操作数为非数值时会自动调用Number()函数来转换。

4.4.1 乘法

乘法操作符由一个星号(*)表示,特殊规则:

  • 如果有一个操作数是 NaN,则结果是 NaN(之前小节里有说过);
  • 如果 Infinity 与 0 相乘,则结果是 NaN(数学上规定∞ * 00/0∞/∞是没有意义的,计算不出来);
  • 如果 Infinity 与非 0 数值相乘,结果是 Infinity 或-Infinity;
  • 如果 Infinity 与 Infinity 相乘,结果是 Infinity。

4.4.2 除法

除法操作符由一个斜线符号(/)表示,特殊规则:

  • 如果有一个操作数是 NaN,则结果是 NaN(之前小节里有说过);
  • 如果零被零除,则结果是 NaN(数学上规定∞ * 00/0∞/∞是没有意义的,计算不出来);
  • 如果是非 0 数值被零除,结果是 Infinity 或-Infinity;
  • 如果是 Infinity 被 Infinity 除,结果是 NaN。

4.4.3 求模

求模(余数)操作符由一个百分号(%)表示,特殊规则:

  • 如果被除数是无穷大而除数是有限大的数字,则结果是 NaN;
  • 如果被除数是有限大的数值而除数是 0,则结果是 NaN;
  • 如果是 Infinity 被 Infinity 除,则结果是 NaN;
  • 如果被除数是有限大的数值而除数是无穷大的数值,结果是被除数;

4.5 加性操作符

4.5.1 加法

加法操作符(+)的用法:

  • 如果有一个操作数是 NaN,则结果是 NaN(之前小节里有说过);
  • 同号的 Infinity 相加结果还是同号的 Infinity;
  • 异号的 Infinity 相加则是 NaN;
  • 同号 0 相加还是同号 0(结果与第一个操作数保持一致);
  • 异号 0 相加却是+0
  • 如果其中一个操作数是字符串,另一个操作数是对象、数值或布尔值就会自动调用toString()方法取得字符串值并进行拼接,但另一个操作数是 null、undefined 则会自动调用String()函数取得字符串并进行拼接。
  • 如果操作数为对象({})或者是数组([])这种复杂的数据类型,那么就将两个操作数都转换为字符串,进行拼接

隐式转换面试题:

js
[] + []; // []转化为字符串是"",有最后拼起来结果就是""
[] + {}; // []转化为字符串是"",{}转化为字符串是"[object Object]",所有最后拼起来结果就是"[object Object]"
{
}
+[]; // 编译器将开头的{}理解为空代码块,那么这里“{} + []”相当于“+[]”,又相当于“Number([])”,那结果就是0;有些编译器会高级一点那就跟“[] + {}”结果一样是"[object Object]"
{
}
+{}; // 跟上面同样道理,相当于“+{}”又相当于Number({}),其结果就是NaN;有些编译器就是两个{}的字符串的拼接,其结果是"[object Object][object Object]"

JS 的{} + {}与{} + []的结果是什么?

4.5.2 减法

减法操作符(-)的用法:

  • 如果有一个操作数是 NaN,则结果是 NaN(之前小节里有说过);
  • 同号的 Infinity 相减则是 NaN;
  • 异号的 Infinity 相减是 Infinity,符号视情况而定;
  • 同号 0 相减是+0;异号 0 相减结果与第一个操作数保持一致;
  • 如果其中一个操作数是字符串、布尔值、null、undefined,则先调用 Number()函数转换为数值再执行减法计算,担忧一个操作数是对象,则会调用 valueOf()方法,如果没有 valueOf()方法则会调用 toString()得到字符串再转化为数值去计算。

4.6 关系操作符

小于(<)、大于(>、小于等于(<=)、大于等于(>=,相应规则:

  • 如果一个操作数是数值,则将另一个操作数转换为一个数值;
  • 如果一个操作数是对象,则调用这个对象的 valueOf()方法,如果没有 valueOf()方法则调用 toString()方法;
  • 如果两个操作数都是字符串,则比较两个字符串对应位置的字符编码值。
js
const result = "Brick" < "alphabet"; // true,B的字符编码是66,a的字符编码是97
const result = "Brick".toLowerCase() < "alphabet".toLowerCase(); // false,都转换为小写,b的字符编码大于a的
const result = "23" < "3"; // true,因为先比较第一位也就是"2"比"3"
const result = "23" < 3; // false,如果一个操作数是数值,则将另一个操作数转换为一个数值
const result = "a" < 3; // false,任何操作数与NaN进行关系比较时,其结果都为false

4.7 相等操作符

4.7.1 相等和不相等

两个等于号(==)表示相等操作符,叹号后跟等于号(!=)表示不相等操作符,这两个操作符都会先转换操作数(强制转型),然后再比较他们的相等性。

  • 如果一个操作数是数值,另一个操作数是布尔值或字符串会先调用 Number()函数转换成数值去比较相等性;
  • null 和 undefined 比较相等性,是相等的,他们比较时没有去转换换成数值的;
  • 如果有一个操作数是 NaN,不管第二个操作数还是不是 NaN,其相等操作符都会返回 false,不相等操作符返回 true(NaN 不等于 NaN);
  • 如果两个操作数都是对象,则比较他们是不是同一个对象,如果两个操作数都指向同一个对象,则相等操作符返回 true,否则返回 false。

4.7.2 全等和不全等

三个等于号(===)表示全等操作符,叹号后跟两个等于号(!==)表示不全等操作符,这两个操作符不但不转换操作数并且会先比较类型是否相同再比较相等性。null == undefined 返回 true,null === undefined 返回 false。

4.8 条件操作符

也是三元运算符xxx ? xxx : xxx,例如:const max = (num1 > num2) ? num 1 : num2;如果 num1 大于 num2,那就返回中间 num1 的值,否则返回最后一个 num2 的值。

4.9 赋值操作符

*=/=%=+=-=<<=>>=>>>=,主要是简化写法,其实没有任何性能的提升。

4.10 逗号操作符

可以执行多个操作、赋值

js
const num1 = 1,
  num2 = 2,
  num3 = 3;
const num = (5, 1, 4, 8, 0); // num的值为0

五、语句

5.1 if 语句

js
if (i > 25) {
  console.log("Greater than 25.");
} else {
  console.log("Less than or equal to 25.");
}

推荐使用代码块,即使只有一行代码执行

5.2 do-while 语句

js
let i = 0;
do {
  i += 2;
} while (i < 10); // i 小于10就会继续循环
console.log(i);

不管表达式的结果如何,循环体内的代码至少会执行一次

5.3 while 语句

js
let i = 0;
while (i < 10) {
  i += 2;
}

只有表达式结果为 true 时才会去执行循环体内的代码

5.4 for 语句

js
const count = 10;
for (let i = 0; i < count; i++) {
  console.log(i);
}
console.log(i); // 10

有三个表达式“初始化语句”、“循环条件”、“每执行完一次后的更变语句”。
由于 ECMAScript 中不存在 块级作用域,因此在循环内部定义的变量也可以在外部访问到。

5.5 for-in 语句

js
for (const propName in window) {
  document.write(propName);
}

精准的迭代语句,可以用来枚举对象的属性,对 null 和 undefined 使用 for-in 会报错,所以使用前要判断对象是否为 null 或 undefined。

5.6 label 语句

在代码中添加标签,一遍将来使用,一般与 break 或 continue 配合使用

js
let a = 0;
label1: for (let i = 0; i < 10; i++) {
  for (let j = 0; j < 20; j++) {
    a = i + "," + j;
    if (i === 3 && j === 2) {
      break label1;
    }
  }
}
console.log(a);

5.7 break 和 continue 语句

break 语句会立即退出这一层的循环(这层的 for 循环),而 continue 是立即退出本次循环(for 循环里会循环好几次,跳出这一次的)。

5.8 switch 语句

js
switch (i) {
  case 25:
    console.log(25);
    break;
  case 35:
    console.log(35);
    break;
  case 45:
    console.log(45);
    break;
  default:
    console.log("other");
}

case 里要是不带 break 的话,就会继续执行后面的 case;default 相当于 else 它不需要 break; switch 语句可以使用任何数据类型,case 的值可以是变量或表达式; switch 去比较使用的是全等“===”; 它在一些情况下可以替代if () {} else if () {} else {}这样逻辑。

六、函数

ECMAScript 中的函数使用 function 关键字来声明,后跟一组参数及函数体。

js
function sagHi(name, message) {
  console.log("Hello " + name + "," + message);
}
sagHi("Nicholas", "how are you today?"); // "Hello Nicholas,how are you today?"

任何函数都可使用 return 语句跟要返回的值来实现函数返回某值,并且函数都不需要定义返回值类型,return;后的语句是不可能再执行的

严格模式对函数有一些限制:

  • 不能把函数或参数名取为evalarguments
  • 不能出现两个命名参数同名的情况。

6.1 理解函数

ECMAScript 函数有一个重要特点:命名的参数只提供便利,但不是必须的。意思是函数命名的参数只是为了告诉外部我大概需要几个什么样的参数,实际上调用函数时解析器不会拿传参和命名时的参数去做比较,不关心你传进来几个参数也不关心类型,只会把你的入参放在类似数组的 arguments 对象中,可以通过 arguments[n]来访问第 n 个参数可以通过 length 属性来确定多少个参数传进来了。

js
function doAdd() {
  if (arguments.length == 1) {
    console.log(arguments[0] + 10);
  } else if (arguments.length == 2) {
    console.log(arguments[0] + arguments[1]);
  }
}
doAdd(10); // 20
doAdd(30, 20); // 50

这个特性算不上完美的重载,但也足够弥补 ECMAScript 的这一缺憾了。

js
function doAdd(num1, num2) {
  arguments[1] = 10;
  console.log(arguments[0] + num2);
}

每次执行 doAdd()函数都会重写第二个参数,arguments[1]和 num2 的值都会变为 10,但他们的内存空间是独立只是值同步。如果 num2 在外部没有传值进来时是赋予的 undefined。还有严格模式下 arguments 的值不能被重写。

6.2 没有重载

  • 重写 子类覆盖父类的方法(返回值和形参都不能改变,方法体改变了,也就是核心变了),在 js 里重写是可以的。
  • 重载:在同一个类里(执行环境),方法名相同而形参列表和方法体不一样的函数叫重载,这在 js 里是不存在的。
  • 因为 js 里使用 var 和 function 这样的关键字来声明 同名变量 时,都会以最后一个声明为准,它会覆盖之前所有 同名 的声明;js 里的同名函数的声明就是重载的写法,因为在同一个执行环境里,但由于同名的会被覆盖,那么 js 里没有函数重载这一说法。
js
function addSomeNumber(num) {
  return num + 100;
}
function addSomeNumber(num) {
  return num + 200;
}
var result = addSomeNumber(100); // addSomeNumber被定义了2次,后定义的会覆盖先定义的

MIT Licensed | Copyright © 2023-present LiawnLiu