ECMAScript中的基本概念

465 阅读14分钟

  之前的高程3也看了两三遍了,最近又看高程4,但总是对于基本的记得不是特别清楚,纸上得来终觉浅,还是花点时间总结下一下容易忘记的和觉得关键的知识点。(此篇是几年看高程3时写了一点,现在在之前的基础上修改并补充一下。)

一、语法

1.区分大小写

  不同于HTML,ECMAScript(后面简称ES)中的变量、函数名、操作符等一切都区分大小写.

var test = 1;
var Test = 2;
console.log(test, Test);    // 1 2

2.标识符

  标识符就是变量、函数、函数参数的名字 或者 属性的名字。ES标识符第一个字符可以是$(美元符号)、_(下划线)、字母,后面其他字符除了上面可选的还可以是数字。变量的命名一般采用小驼峰(testName),构造函数一般默认采用大驼峰(TestPerson)。

二、变量

  ES的变量是松散类型的,可以用来保存任何类型的数据,每个变量不过是用于保存任意值的占位符。有三个关键字可以声明变量,分别是 varletconstvarES的各个版本中都可以使用,letconst只能在 ES6及更晚的版本中使用。

1. var

使用var操作符声明变量如下,也可以在初始化为一种类型的数据后,改变成另外一种类型。

var msg = 'This is a test message';
console.log(typeof msg);    // string

msg = 123;
console.log(typeof msg);    //number

  像上面用var 操作符定义了变量,后面又改变了变量值的类型,虽然ES中这是有效的,为了养成好的编码习惯,在定义一个变量的时候就考虑好变量可能的类型,免得后面改变了变量值类型后再做过多的类型检查或是发生其他意外。

var声明作用域

var 操作符声明的变量会成为包含它的函数的局部变量。

function test() {
    var num = 123;
}

test();
console.log(num); // Uncaught ReferenceError: num is not defined

test函数调用完毕后,内部的变量num就被销毁了,后面在打印num就会导致错误。 可以在函数内声明变量时不加上前面的var 操作符,这样变量 num就成为了全局变量,可以在函数外部访问到了。

function test() {
    num = 123;
}

test();
console.log(num); // 123

虽然这样可行,也不会报错,但是不建议这样操作,局部作用域中定义全局变量不好追踪和维护,并且在严格模式下会导致 ReferenceError

var声明提升

如下代码也是可以的,不会报错。

function foo() {
    console.log(num); // undefined
    var num = 123;
}

foo();

上面第3行的 var num = 123;中关键字 var声明的变量会被自动提升到函数作用域的顶部,其等同于以下代码。

function foo() {
    var num;
    console.log(num); // undefined
    num = 123;
}

foo();

2. let

letvar 作用差不多,但他们之间也有区别。

序号let 操作符var 操作符
1声明的范围是块级作用域声明的范围是函数作用域
2不允许同一个块级作用域有冗余声明同一作用域中后面的相同声明覆盖前面的
3声明的变量不会被提升,有暂时性死区会被提升,无暂时性死区
4全局声明的变量不会成为window对象的属性全局声明的变量成为window对象的属性
5for循环声明迭代变量时会明一个独立的变量for循环声明迭代变量时不会声明一个独立的变量

以下为上面各个点的代码示例。

// 1. let 的块级作用域
if (true) {
    var name = 'Tom';
    console.log(name); // Tom
}
console.log(name); // Tom

if (true) {
    let num = 16; // 仅在块级内可以访问到
    console.log(num); // 16
}
console.log(num); // demo.html:51 Uncaught ReferenceError: num is not defined
2. let 的块级作用域内不能重复声明同一变量
var aaa;
var aaa = 111;

let bbb;
let bbb = 222; // Uncaught SyntaxError: Identifier 'bbb' has already been declared
// 2. 没在同一块级作用域内 let能重复声明同一变量
var name = 'Jack';
if (true) {
    var name = 'Tom';
    console.log(name); // Tom
}
console.log(name); // Tom

let num = 20;
if (true) {
    let num = 16;
    console.log(num); // 16
}
console.log(num); // 20
3. let 的暂时死区
console.log(name); // Chrome 115中为Jack,Node 18中为 undefined
var name = 'Jack';

console.log(age); // demo.html:76 Uncaught ReferenceError: Cannot access 'age' before initialization
let age = 20;
// 4. let 全局声明的变量不会成为window对象的属性
var name = 'Jack';
console.log(window.name); // Jack

let age = 20;
console.log(window.age); // undefined
// 5. for循环声明迭代变量
 for (var i = 0; i < 10; i ++) {
    // 
}
console.log(i); // 10

for (let n = 0; n < 10; n ++) {
    // 
}
console.log(n); // Uncaught ReferenceError: n is not defined

3. const

const 声明变量与 let基本相同,区别还是有的,const 声明变量时初始化时必须赋值,并且声明后不能修改这个声明的变量,如果声明的变量是一个对象,改变对象的属性是可以的。

// const 声明变量就应赋值
const test; // Uncaught SyntaxError: Missing initializer in const declaration
// const 声明的变量不能再修改
const aNum = 1;
aNum = 2; // Uncaught TypeError: Assignment to constant variable.
// const 声明的变量不能再修改
const obj = {};
obj = {}; // Uncaught TypeError: Assignment to constant variable.
// const 声明的变量为一个对象,能修改对象的属性
const obj = {};
console.log(obj); // {}

obj.s = 'A string.';
console.log(obj); // {s: 'A string.'}

obj.s = 'Another string.';
console.log(obj); // {s: 'Another string.'}

三、数据类型

1.数据类型分类

  ES中有5中简单数据类型,分别是:Undefined、Null、Boolean、String、Number,和一种复杂数据类型:Object。 使用操作符typeof可以检测给定变量的数据类型,对于个变量使用typeof操作符可能返回如下字符串:

  • 'undefined'--如果该值未定义(一般没必要把一个变量显示的设置为undefined);
  • 'boolean'--如果该值是布尔值;
  • 'string'--如果该值是字符串;
  • 'number'--如果该值是数值;
  • 'object'--如果该值是对象或null(常把一个变量显示赋值为null,表示已声明未赋值);
  • 'function'--如果这个值是函数。

2.Undefined类型和Null类型

  Undefined类型只有一个undefined值,表示定义一个变量时没有赋值,但我们也可以显示的赋值为undefined,但没必要。

没赋值和没定义还是有区别的,操作一个没定义的变量会报错,但有一个例外就是使用typeof操作符检测一个未定义的变量时会返回undefined。

var cat;

console.log(cat);   // undefined
console.log(fox);   // Uncaught ReferenceError: fox is not defined

console.log(typeof cat);    // undefined
console.log(typeof fox);    // undefined

  Null类型也只有一个值null,它表示一个空对象指针,常用于定义一个变量用于后面保存一个对象,因此用typeof操作符检测结果是object。

var timer = null;
console.log(typeof timer);  // object

其实undefined值是派生于null值,但他们的用途略有区别。 用相等符检测时会相等(比较操作符会转换数据类型)。

console.log(null == undefined);     // true
console.log(null == '');     // false
console.log(null == 0);     // false
console.log(undefined == '');     // false
console.log(undefined == 0);     // false

3.Boolean类型

  Boolean类型的字面量true和false是区分大小写的。True和False都不是Boolean,只是标识符。可以对任何数据类型的值调用Boolean函数,并且会返回一个Boolean值,返回值如下:

数据类型转换为true转换为false
Booleantruefalse
String非空字符串(包括' ')空字符串('')
Number任何非零字符串(包括无穷大)0和NaN
Object任何对象Null
Undefinedn/a (not applicable) 不适用undefined
console.log(Boolean(true), Boolean(false));   // true false
console.log(Boolean('123'), Boolean(''));     // true false
console.log(Boolean(Infinity), Boolean(NaN), Boolean(0));// true false false
console.log(Boolean(null), Boolean({}));      // false true
console.log(Boolean(undefined));       // false

能有相同作用的就是两个逻辑非操作符(!!)。

数据类型转换为true转换为false
Booleantruefalse
String非空字符串(包括' ')空字符串('')
Number任何非零字符串(包括无穷大)0和NaN
Object任何对象Null
Undefinedn/a (not applicable) 不适用undefined
console.log(!!true, !!false);   // true false
console.log(!!'123', !!'');     // true false
console.log(!!Infinity, !!NaN, !!0);    // true false false
console.log(!!null, !!{});      // false true
console.log(!!undefined);       // false

4.Number类型

4.1 数值字面量进制

  平时常用的数值字面量进制是十进制,整数还有八进制和十六进制格式。

  八进制第一位是0,数字序列是(0~7),如果有超过7的数字出现会被当做是十进制格式,而在严格模式下八进制格式是无效的,会导致浏览器报错。

  十六进制字母量前面两位是0x,后面可以是(09,af不区分大小写)的任何十六进制数字。

  在进行算术计算时,所有的八进制和十六进制表示的数字都会转换成十进制数字。

4.2 浮点数

  浮点数就是该数值中必须包含一个小数点,且小数点后至少有一位数,小数点前面是一位0时,可以省略,但不建议这样。 对于极大和极小的数值可以用科学计数法(e表示法)。

console.log(3e-8);  // 3e-8
console.log(3e-8 == 0.00000003);    // true
console.log(3e8);   // 300000000

  由于IEEE二进制浮点数算术标准(IEEE 754)的问题,在进行算术计算时,浮点数远不如整数高。浮点数的计算精度最高是17位小数,正如下面例子中的0.30000000000000004。

var a = 0.1, b = 0.2;
var c = a + b;
if (c === 0.3) {
    console.log('right');
} else {
    console.log('wrong');   // wrong
}
console.log(c);     // 0.30000000000000004

4.3 数值范围

  由于内存限制,ES保存的数值是有限的。ES表示的最大和最小数值分别是 Number.MAX_VALUE 和 Number.MIN_VALUE。如果超出这个范围,正数就是Infinite,负数就是-Infinite。 如下:

console.log(Number.MAX_VALUE);  // 1.7976931348623157e+308
console.log(Number.MIN_VALUE);  // 5e-324
console.log(isFinite(Number.MAX_VALUE + Number.MAX_VALUE)); // false
console.log(isFinite(Number.MIN_VALUE + Number.MAX_VALUE)); // true

  用 isFinite() 函数检查一个数值时,在最大和最小值之间就返回true,否则返回false。

4.4 NaN

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

特点: 任何涉及NaN的操作都会返回NaN。NaN不等于任何值,包括它本身。可以用isNaN()函数检测一个值是否是数值,该函数在接到一个参数后会尝试将该值转换成一个数值。

console.log(isNaN(NaN));    // true
console.log(isNaN(10));     // false
console.log(isNaN('10'));   // false
console.log(isNaN(''));     // false
console.log(isNaN(' '));    // false
console.log(isNaN('abc'));  // true
console.log(isNaN(true));   // false

4.5 数值转换

把非数值转换成数值有三个函数,Number() -- 可用于任何数据类型,parseInt() 和 parseFloat() 专门用于把字符串转换成数值。

Number()

转换规则如下:

  • 如果是Boolean值,true和false分别转换成1和0;
  • 数字值只是简单的返回原值;
  • null值返回0;
  • undefined返回NaN;
  • 字符串: 如果字符串只包含数字(包括浮点数数字),有效的十六进制格式,则转换为相应的十进制数值。 空字符串转换为0。包含除开以上格式外的字符,转换为NaN。
  • 如果是对象,先调用对象的valueOf()方法,依照上面的方法转换成数值。如果转换的结果是NaN,就调用对象的toString()方法,再依照上面的方法转换成数值。
console.log(Number(true), Number(false));   // 1 0
console.log(Number(null));      // 0
console.log(Number(undefined));     // NaN
console.log(Number('123'), Number('3.14'), Number('0xf'));  // 123 3.14 15
console.log(Number(''), Number(' '), Number('123abc'));     // 0 0 NaN
console.log(Number(new Date()));    // 1589608133573

var obj = {
    a: '138',
    b: '158',
    valueOf: function () {
        return this.a;
    }
}
var obj2 = {
    a: '138',
    b: '158',
    toString: function () {
        return this.b;
    }
}
console.log(Number(obj));   // 138
console.log(Number(obj2));  // 158
parseInt()

parseInt()函数处理整数时是看字符串是否符合数值模式。

  • 会忽略前面的空格,直到找到第一个非空格字符;
  • 如果第一个字符不是数值或是+、-,就返回NaN;
  • 如果第一个字符是数值,然后解析第二个字符,依次直到解析完或是遇到一个非字符;
  • 空字符串会返回NaN;
console.log(parseInt('abc'));   // NaN
console.log(parseInt(' 123abc'));   // 123
console.log(parseInt(' +123456'));  // 123456
console.log(parseInt(' -138'));     // -138
console.log(parseInt(''), parseInt(' '));    // NaN NaN
console.log(parseInt('0567')); // 567 (ES3认为是八进制,ES5认为是十进制)
console.log(parseInt('0xabc'));     // 2748

我们可以给parseInt()函数传递第二个参数来提供转换时使用的基数。

console.log(parseInt('10',2));  // 2
console.log(parseInt('10',8));  // 8
console.log(parseInt('10'));    // 10
console.log(parseInt('10',10)); // 10
console.log(parseInt('10',16)); // 16
parseFloat()

parseFloat()的解析同parseInt()相当类似,但也有不同。不同如下:

  • 遇到一个无效的浮点数止,即第一个小数点是有效的;
  • 始终会忽略前导的0,即不会解析八进制和十六进制的数值 -- 始终返回0;
  • 没有第二个参数作为基数;
  • 没有小数(解析为整数)或是小数点后是0,返回整数;
console.log(parseFloat('123.456.789'));     // 123.456
console.log(parseFloat('0789'),parseFloat('0xabc'));  // 789 0
console.log(parseFloat('10',16));   // 10
console.log(parseFloat('123.000')); // 123

5.String类型

  String类型表示由零个或是多个16位Unicode字符组成的字符序列。ES中字符串是不可变的,要改变某个变量保存的字符串,首先要销毁原来的字符串,再用另一个包含新值的字符串填充该变量。

转换为字符串

要把一个值转换成字符串,有两种方法:

toString方法

  几乎每一个值都有toString方法,像数值、布尔值、对象和字符串(返回字符串 的副本)都有一个toString方法来返回对应值得字符串表现。

let num = 16;
let s = 'a string';
let b = false;
let bt = true;
let obj = {
    a: 'What is string',
    b: 99
}
let obj2 = {
    a: 'What is string',
    b: 99,
    toString: function(){
        return num;
    }
}

console.log(num.toString());    // "16"
console.log(s.toString());  // "a string"
console.log(b.toString());  // "false"
console.log(bt.toString()); // "true"
console.log(obj.toString());    // "[object Object]"
console.log(typeof obj2.toString());    // number

但null和undefined值没有toString方法。对于数值的toString方法,可以传入一个转换基数,可以输出二进制、八进制、十六进制数值的字符串表示。

let numb = 20;
console.log(numb.toString(2));  // "10100"
console.log(numb.toString(8));  // "24"
console.log(numb.toString(10)); // "20"
console.log(numb.toString(16)); // "14"
String()转型函数

在不知道要转换的值是否是null和undefined时可以使用String()函数。

  • 值有toString方法时调用toString方法
  • 值是null,返回"null"
  • 值是undefined,返回"undefined"
let num = 16;
let s = 'a string';
let b = false;
let bt = true;
let obj = {
    a: 'What is string',
    b: 99
}
let obj2 = {
    a: 'What is string',
    b: 99,
    toString: function(){
        return num;
    }
}
let n = null;
let un = undefined;

console.log(String(num));    // "16"
console.log(String(s));  // "a string"
console.log(String(b));  // "false"
console.log(String(bt)); // "true"
console.log(String(obj));    // "[object Object]"
console.log(typeof String(obj2));    // string
console.log(String(n)); // "null"
console.log(String(un));    // "undefined"

四、操作符

  ES中操作数据值的操作符包括算术操作符、位操作符、关系操作符和相等操作符等。

位操作符

  位操作符用于在最基本的层次(内存中表示数值的位)操作数值。ES中所有的数值都是以IEEE~75464位格式存储的,但位操作符不直接操作64位的值,而是先将64位的值转换成32位的整数值,然后执行操作,最后再转回64位的值。

  对于有符号的整数,前31位表示整数的值,第32位为符号位表示符号,0表示正数,1表示负数。

如:18的二进制表示为00000000000000000000000000010010 或是10010。及18 = 1*2**4 + 0*2**3 + 0*2**2 + 1*2**1 + 0*2**0 = 16+0+0+2+0

负数同样以二进制码存储,但是使用二进制补码。计算一个数值的二进制补码步骤如下(以-18为例):

  • 先求这个值得绝对值的二进制码
  • 求二进制的反码
  • 对得到的二进制反码加1

00000000000000000000000000010010 (18的二进制码) 11111111111111111111111111101101 (18的二进制反码) 11111111111111111111111111101110 (反码加1)

我们在输出一个负数的二进制码时只是在这个数值的绝对值前面加上一个负号。

console.log((18).toString(2))   // "10010"
console.log((-18).toString(2))  // "-10010"