JavaScript高级程序设计第四版--第三章--语言基础

74 阅读8分钟

语法

ECMAScript的语法很大程度上借鉴了C语言何其它类C语言,如Java何Perl

大小写

ECMAScript中的一切都区分大小写,无论是变量、函数名还是操作符

标识符

第一个字符必须是一个字母、下划线或美元符号,剩下的其他字符可以是字母、下划线、美元符号或数字

标识符中的字母可以是扩展ASCII中的字母,也可以是Unicode的字母字符(但不推荐)

ECMAScript采取小驼峰命名

注释

ECMAScript采取C语言风格的注释

行注释

// 单行注释

块注释

/*
    块注释
*/

严格模式

ECMAScript5增加了严格模式(strict mode)的概念,是一种不同的JavaScript解析何执行模型,对于不安全的活动将抛出错误,通过“use strict”预处理指令开启。

ECMAScript3的一些不规范的写法在这种模式下会被处理,目的是不破坏ECMAScript3语法(不理解)

脚本启用

"use strict";

函数启用

function func(){
    "use strict";
}

语句

ECMAScript中的语句以分号结束,省略分号意味着由解析器确定语句在哪里结束。加分号有助于在某些情况下提升性能,因为解析器会尝试在合适的位置补上分号以纠正语法错误

关键字和保留字

最好还是不要使用关键字和保留字作为标识符和属性名,以确保兼容过去和未来的ECMAScript版本

变量

ECMAScript变量是松散类型的,var可以在ECMAScript的所有版本中都可以使用,而const和let只能在ECMAScript6及更晚的版本中使用

var、let都允许先定义(默认值为undefined)后赋值

var关键字

var、let都允许先定义(默认值为undefined)后赋值

作用域

使用var操作符定义的变量会成为包含它的函数的局部变量,意味着该变量将在函数退出时被销毁

在主函数中通过var声明的变量会成为全局变量(作为window对象的属性)

声明提升

使用var对一个变量进行声明或初始化一个变量时,该变量的所有声明会自动提升到函数作用域的顶部,所以可以多次使用var声明同一变量(不推荐)

let声明

var、let都允许先定义(默认值为undefined)后赋值

作用域

块作用域,不允许在同一个作用域中出现重复声明(包括var)

{
         
    let w = 2
    var w=1
    // Uncaught SyntaxError: Identifier 'w' has already been declared
}
var w=1
{
 
    let w = 2
     //不会报错
}

即便在主函数中通过let声明的变量会成为全局变量(作为window对象的属性)

暂时性死区

在解析代码时,JavaScript引擎也会注意出现在块后面的let声明,只不过在此之前不能以任何方式来引用未声明的变量。在let声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出ReferenceError

for循环中的let声明

主要解决var在for循环时,迭代变量会渗透到循环体外部问题,主要原理是利用let的作用域

const声明

const在定义时必须初始化,修改变量的值会报错,其它行为基本和let相同

省略声明

直接对一个未定义的变量进行赋值,会将该变量定义为全局变量(作为window对象的属性),并且该方式不会进行声明提升

未定义

对于未定义的变量只能执行以下两种操作(p31)

delete w;               // 严格模式会抛出错误
console.log(typeof w)   // "undefined"

数据类型

typeof

typeof是一个操作符而不是函数

简单数据类型(原始类型)

Undefined

取值范围:undefined

当使用var或let声明了变量但没有初始化时,就相当于给变量赋值了undefined

undefined是由null派生而来,因此ECMAScript-262将它们定义为表面上相等

Null

取值范围:null

null表示一个空对象指针,这也是typeof null返回“object”的原因

Boolean

取值范围:true、false,这两个布尔值不同于Number,所以true不等于1,false不等于0

类型转换

通过Boolean()函数可以将其它类型转换为Boolean类型,像if等流程控制语句会自动执行Boolean()函数实现类型转换

数据类型truefalse
Booleantruefalse
String非空串空串
Number非零值零值、NaN
Object任意对象null
Undefined不存在undefined

Number

Number类型使用IEEE754格式表示整数和浮点数(在某些语言中也叫双精度值),所以js中整数和浮点数同为Number类型

0 === 0.0   // true

整数

浮点数只支持十进制

由于Javascript保存数值的方式,实际中可能存在正零和负零,在所有情况下认为两者是相等的

八进制

0o(数字0字母o)

十进制
十六进制

0x(数字0字母x)

浮点数

由于使用IEEE754标准存储,所以浮点数也存在输入错误

因为存储浮点数使用的内存空间是存储整数的两倍,所以ECMAScript总是想方设法的把值转换为整数

let num1 = 1.; 	// 小数点后没有数字,当成整数1处理
let num2 = 1.0;	// 小数点后是0,,当成整数1处理
科学计数法

默认情况下,ECMAScript会将小数点后面至少6个零的浮点数转换为科学计数法,例如0.0000003会被转换为3e-7

Infinity

表示无穷,并且正Infinity不等于负Infinity。被除数不为零,除数为零,也会得到Infinity(正负性正常计算)

计算结果超过Number.MAX_VALUE会得到+Infinity

计算结果小于Number.MIN_VALUE会得到-Infinity

isFinite

通过isFinite函数可以确定数值是否在可表示范围

NaN

表示返回数值的操作失败了,需要注意的是在ECMAScript中0/0等于NaN

String

表示零个或多个16位Unicode字符串序列,并且单双引号都是表示字符串

模板字面量

使用``(反引号)包裹,在反引号内换行

字符串插值

在模板字面量中通过${变量}方式,将变量嵌入模板字面量,需要注意的是其原理是调用变量的toString方法。

let s1=1,s2=2;
console.log(`-${s1}-${s2}-`);   // 系统调用变量的toString方法,将返回值进行拼接
标签函数

注意标签函数不自动调用变量的toString方法

let s1=1,s2=2;
	function tagFunc(templateStrAr,...variableAr){  //templateStrAr:被变量分割的字符串数组  variableAr:参数数组
        console.log(templateStrAr)
        console.log(variableAr)
        	return ""
    }
tagFunc`-${s1}-${s2}-`

原始字符串

通过String.raw标签函数可以获得一个字符串(转义字符不被转义,原样输出)

Symbol

Symbol只能通过Symbol工厂函数获得,不能通过Symbol构造函数获得(只能Symbol()获得,不能new Symbol()),但可以通过Object(Symbol())方式将其包装成一个对象

符号实例是唯一的,不可变的

调用Symbol工厂函数时,可以传入一个描述信息,该信息是符号的描述一旦创建不能更改,通过符号可以有相同的描述所以不能通过描述信息标识一个符号

全局符号注册表

通过Symbol工厂函数创建符号对象需要程序员自己管理符号对象。但是通过Symbol.for()创建的符号可由系统来管理

Symbol.for()

Symbol.for()会将传入的字符串key作为索引,如果该key存在就返回对应的符号,如果不存在就以key为描述信息创建符号,并将其与key建立对应关系

Symbol.keyFor()

Symbol.keyFor()传入一个符号对象,如果该符号是Symbol.for()创建的返回对应的key,否则返回undefined

内置符号(well-known symbol)

用于暴露语言内部的行为,开发者可以直接访问、重写或模拟这些行为。比如for-of循环会在相关对象上使用Symbol.iterator属性,我们可以自定义Symbol.iterator的值,来改变在迭代该对象时的行为,达到类似C++的重载。

所有符内置号对象都是不可写、不可枚举、不可配置的

通过Symbol.属性获取

asyncIterator

表示异步对象的默认异步迭代器方法,使用for await of 时,执行该属性对应的生成器函数

const obj = {
  async *[Symbol.asyncIterator]() {
    yield 'one';
    yield 'two';
    yield 'three';
  }
};

(async function() {
  for await (const x of obj) {
    console.log(x);
  }
})();
hasInstance

用于指定某个构造函数的静态方法。当对一个对象使用 instance 运算符时,会调用这个对象的 Symbol.hasInstance(instance) 方法。如果返回 true,则说明 instance 属于这个对象的实例;反之,则不是。因此,我们可以通过重写 Symbol.hasInstance() 来实现自定义的 instanceof 操作

class MyClass {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}

const arr = [1, 2, 3];
console.log(arr instanceof MyClass); 
isConcatSpreadable

取值为布尔值或undefined,在执行Array.prototype.concat()拼接数组时,如果传入的参数是数组,其Symbol.isConcatSpreadable不等于false则将数组打平(非递归),如果传入的参数是类数组,其Symbol.isConcatSpreadable等于true则将数组打平(非递归)

const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const obj = { length: 3, 0: "hello", 1: "world", 2:[7,8,9],[Symbol.isConcatSpreadable]: true }

console.log(arr1.concat(arr2)) 
console.log(arr1.concat(obj))  
console.log(arr2.concat(obj))  
iterator

使用for of 时,执行该属性对应的生成器函数

const arr = ['foo', 'bar', 'baz'];
const iter = arr[Symbol.iterator]();

console.log(iter.next()); // { value: 'foo', done: false }
console.log(iter.next()); // { value: 'bar', done: false }
console.log(iter.next()); // { value: 'baz', done: false }
console.log(iter.next()); // { value: undefined, done: true }
match

当一个对象作为字符串表达式被 String.prototype.match() 方法匹配时,match() 会搜索该对象对应的原型链上是否具有名称为 Symbol.match 的方法。如果该方法存在,则 match() 将调用该方法并将其参数设为搜索的字符串,并返回调用结果;否则,match() 算法将终止执行并返回 null。

const obj = {
            [Symbol.match](str) {
                console.log(str)
                return str;
            }
        };
console.log('bar'.match(obj)); 
console.log('oo'.match(obj)); 
replace

字符串调用 replace() 方法时,JavaScript 引擎会在内部使用相应对象的 Symbol.replace 方法来执行实际替换操作。如果该方法未定义,则直接使用默认替换逻辑

const obj = {
            [Symbol.replace](str, replaceWith) {
                console.log(str,replaceWith)
                return str.toUpperCase().replace('WORLD', replaceWith);
            }
        };

console.log('Hello world!'.replace(obj, 'everyone'));
search

可以用来为String.prototype.search()方法定义新的行为,将其作为自定义对象的Symbol.search属性的值进行设置

let myObject = {
            [Symbol.search]: function (string) {
                console.log(string)
                return string.indexOf("hello");
            }
        };
let message = "hello world";
console.log(message.search(myObject)); // 输出: 0
species

用于定义一个对象的构造函数。它主要针对一些原生对象,比如Array、Map、Set等,可以用来指定这些对象在派生出新的对象时使用的构造函数

 class C extends Array {
            constructor() {
                super()
                console.log(66)
            }
        }
class MyArray extends Array {
            static get [Symbol.species]() { return C; } // 将MyArray的派生类的构造函数指定为C
        }

let myArr = new MyArray();
let newArr = myArr.slice(); //  myArr.slice()返回MyArray的派生类,执行C的constructor方法
console.log(newArr instanceof Array);
console.log(newArr instanceof C);
split

当一个自定义对象上实现了 Symbol.split 方法时,在调用此方法时会触发对象内部的 split() 逻辑,并返回相应的结果

const myObject = {
    [Symbol.split]: function (string) {
        console.log(string)
        const separatorIndex = string.indexOf(':');
        return [
            string.slice(0, separatorIndex),
            string.slice(separatorIndex + 1)
        ];
    }
};

const myString = 'hello:world';
const result = myString.split(myObject);
console.log(result); // ["hello", "world"]
toPrimitive

将一个对象转换为它对应的原始值。该方法可以被调用以处理隐式类型转换,或通过 Symbol.toPrimitive 符号进行自定义。规则如下

  1. 如果已经实现了 Symbol.toPrimitive 方法,直接调用该方法并返回相应值
  2. 否则,如果值为 Date 类型,则优先将其转换为字符串,并返回结果。
  3. 否则,如果值为原始类型的包装对象(例如 String、Number、Boolean),则获取其内部储存的原始值并返回。
  4. 否则,如果无法从以上三种情况中得到有效的值。则使用 valueOf()获取值,如果该值是原始值就不再执行toString()方法如果不是就执行toString() 方法来尝试获得原始值,最终返回首次有效得到的结果(需要注意的是,原始值在返回后会被操作符进行进行类型转换)。
let obj = {
      lvalueOf() { return 42; },
      toString() { return 'forty two'; },
      [Symbol.toPrimitive](hint) {  // hint:number、string、default
        console.log(hint)
        if (hint === 'number') { return 42; }
        if (hint === 'string') { return 'forty two'; }
        return true;
      }
    };

    console.log(+obj);
    console.log(`${obj}`);
    console.log(obj == true);
toStringTag

用于指定对象在调用 Object.prototype.toString() 方法时返回的字符串标签。通过定义对象的 @@toStringTag 属性,可以自定义其 toString() 方法返回的值,从而更好地反映对象的特征和用途

const myObj = {
  [Symbol.toStringTag]: "MyCustomObject"
};

console.log(Object.prototype.toString.call(myObj));  // 输出 "[object MyCustomObject]"
unscopables

JavaScript 引入了 unscopables 属性,可以通过设置对象的 unscopables 属性禁止 with 语句对指定属性进行绑定

const obj = {
  a: 1,
  b: 2,
  [Symbol.unscopables]: {
    a: true // 禁止对a的绑定,无法在with中对obj.a访问
  }
};
let a=0;
function foo() {
  with(obj) {
    a = 3   // 对变量a赋值,不是obj.a
    b = 6
  }
}

foo();
console.log(obj.a)
console.log(obj.b)
console.log(a)

复杂数据类型

Object

ECMAScript中对象是一组数据和功能的集合,js 中所有的类最终都继承自 Object.prototype,也就是 Object 原型对象

每个Object实例都有如下

属性

constructor

指向创建该对象的构造函数

let obj = new Object();
console.log(obj.constructor === Object); // true

方法

hasOwnProperty(propertyName)

用于判断某个属性名称是否存在于该对象中,并且不是从原型链中继承而来的。

let obj = {a: 1, b: 2};
console.log(obj.hasOwnProperty('a')); // true
console.log(obj.hasOwnProperty('toString')); // false,因为 toString 方法是从 Object.prototype 继承而来的
isPrototypeof(object)

用于判断某个对象是否是另一个对象的原型(或间接原型)

let obj1 = { x: 1 };
let obj2 = Object.create(obj1);
console.log(obj1.isPrototypeOf(obj2)); // true
propertyIsEnumerable(propertyName)

用于判断某个属性是否可枚举

let obj = {};
Object.defineProperties(obj, {
  prop1: { value: 'foo', enumerable: true },
  prop2: { value: 'bar', enumerable: true },
  prop3: { value: 'baz', enumerable: false },
});
console.log(obj.propertyIsEnumerable('prop1')); // true
console.log(obj.propertyIsEnumerable('prop2')); // true
console.log(obj.propertyIsEnumerable('prop3')); // false
toLocaleString()

用于将对象转换为本地化后的字符串

let num = 12345678.9;
console.log(num.toLocaleString()); // "12,345,678.9"  由于我的计算机设置的默认语言环境是中文简体(中国),因此该方法返回了一个以中文格式显示的带有千位分隔符的数字字符串
console.log(num.toLocaleString('en-US', { style: 'currency', currency: 'USD' })); // "$12,345,678.90"	toLocaleString() 方法的第一个参数设置为 'en-US',即英文(美国)语言环境,将其第二个参数设置为 { style: 'currency', currency: 'USD' },即显示货币格式,并将货币单位设置为美元
toString()

用于将对象转换为字符串

let num = 123;
console.log(typeof num.toString());	// 使用 toString() 方法将数字对象转换为字符串
valueOf()

用于返回对象的原始值

let num = new Number(123);
let val = num.valueOf();		// 使用 valueOf() 方法获取数字对象的原始值

let date = new Date();
let timestamp = date.valueOf();// 使用 valueOf() 方法获取日期对象的原始值(即时间戳)

let numObj = new Number(123);
let mySum = numObj + 456; 		// 等同于 (numObj.valueOf()) + 456

操作符

ECMAScript中操作符是独特的,因为它们可用于各种值。

在应用复杂数据类型时,操作符通常会调用valueOf和toString方法来取得可以计算的值。在应用简单数据类型时,操作符通常会调用对应的函数让其类型一致

一元操作符

typeof

typeof返回的是字符串,用于判断值类型

字符串意义
undefined未定义
boolean布尔值
string字符串
number数值
symbol符号
object对象或null
function函数

null表示一个空对象指针,这也是typeof null返回“object”的原因

函数在ECMAScript中被认为是对象,但是函数也有自己的特殊属性,有必要进行区分

  1. length 属性:表示函数定义时声明的参数个数。
  2. name 属性:表示函数的名称。
  3. prototype 属性:是一个显式原型对象,用于创建该函数的实例的原型链上。
  4. arguments 属性:在函数内部可用,是一个类似数组的对象,包含传递给函数的参数列表。

自增/减

前缀和后缀两种,作用和c语言一样,都会导致变量发生改变

字符串

有效数值格式,导致变量类型字符串变为数值,值增加或减少

无效数值格式,导致变量类型字符串变为数值,值NaN

布尔

变量类型从布尔变为数值假为0真为1

对象

通过对象的valueOf()获得可以操作的值,再将值应用上述规则

NaN

类型和值都不会变

一元加减

字符串

有效数值格式,值为对应正负,表达式类型为数值

无效数值格式,值为NaN(没有-NaN)

布尔

值为对应正负0和1,

对象

NaN(没有-NaN)

NaN

NaN(没有-NaN)

位操作符

ECMAScript中的所有数值(默认) 都以IEEE754 64位格式存储存储,但位操作并不能直接操作64位,只能操作32位。

所以需要将64位转换位32位再进行操作,再将结果从32位还原到64位。32位还原到64位不会造成数据丢失对于程序员来说没必要关心,接下来说明64位转32位的规则

64位转32位

  1. 首先,将浮点数的小数部分截断,保留它的整数部分
  2. 然后,将整数部分通过取模运算保留其在32位表示下的最低5位的值

总体来说,会导致小数部分丢失,超出32位能表示的整数最大范围 [-2147483648, 2147483647]丢失

注意点

  1. js的位操作符是包括符号位
  2. 特殊值NaN、Infinity在位操作符中都会被当成0处理
  3. 遇到非数值时,会使用Number函数转换,再进行位操作
  4. 存在无符号整数
  5. 位操作很快,因为是在底层表示上完成的

按位非

连同符号位全部取反

let num = 25;		// 						num:0...011001
let num1 = ~num;	// num1:num连同符号取反 = 	1...100110

按位与

比较操作数二进制(包括符号位),同1为1,否则为0

//	25:0...011001
//	 3:0...000011
//	 n:0...000001
let n = 25 & 3;

按位或

比较操作数二进制(包括符号位),同0为0,否则为1

//	25:0...011001
//	 3:0...000011
//	 n:0...011011
let n = 25 | 3;

按异或

比较操作数二进制(包括符号位),不同为1,否则为0

//	25:0...011001
//	 3:0...000011
//	 n:0...011010
let n = 25 ^ 3;

左移

<<

符号位不移动,末位补零

有符号右移

保留符号位,空位用符号位的值来填充

无符号右移

连同符号位一起右移,空位补零

布尔操作符

逻辑非

!

应用给ECMAScript中的任何值,会自动进行自动布尔类型转换再对其取反,最终结果一定是布尔

操作数返回值
对象false
空字符串true
非空字符串false
数值0true
非数值0(包括Infinity不包括NaN)false
nulltrue
NaNtrue
undefinedtrue

逻辑与

&&

最终结果不一定是布尔,而是短路时操作数本身

逻辑或

||

最终结果不一定是布尔,而是短路时操作数本身

乘性操作符

当操作数不是数值时,会自动通过Number()函数转换为数值

注意Infinity是可以有正负的,没强调默认满足数学标准可正可负

乘法

如果ECMAScript不能表示乘积,则返回Infinity或-Infinity

操作数x操作数y结果
NaNNaN
Infinity0NaN
Infinity非0有限数Infinity
InfinityInfinityInfinity

除法

如果ECMAScript不能表示商,则返回Infinity或-Infinity

操作数1操作数2结果
NaNNaN
NaNNaN
InfinityInfinityNaN
00NaN
非0有限数0Infinity
Infinity非NaNInfinity

取模

操作数1操作数2结果
Infinity有限数NaN
有限数0NaN
InfinityInfinityNaN
有限数Infinity操作数1
0非00

指数操作符

ECMAScript7新增了指数操作符,Math.pow()现在有了自己的操作符**,并且还新增了**=操作符

加性操作符

与乘性操作符类似,加性操作符在后台会发生不同数据类型的转换

操作数x操作数y结果
NaNNaN
-InfinityInfinityNaN
InfinityInfinityInfinity
-Infinity-Infinity-Infinity
000
-000
-0-0-0
操作数x操作数y结果
字符串将另一操作数转换为字符串再拼接

减性操作符

与乘性操作符类似,减性操作符在后台会发生不同数据类型的转换

操作数1操作数2结果
NaNNaN
NaNNaN
-Infinity-InfinityNaN
Infinity-InfinityInfinity
-InfinityInfinity-Infinity
000
0-00
-00-0
-0-00

关系操作符

包括小于、小于等于、大于、大于等于,注意不包括等于。返回值为布尔,需要注意的是,在进行多个关系符运算时,布尔值被类型转换的问题

操作数x操作数y结果
数值数值数值比较大小
字符串字符串逐个比较字符编码
数值将另一个操作数转换为数值再比较
布尔将布尔转换为数值,再适用规则
NaNNaN和任何数比较结果为false

相等操作符

等于、不等于

会将操作数进行类型转换,再确定操作数是否相等

操作数x操作数y结果
布尔将布尔转换为数值再比较
数值字符串字符串转数值再比较
对象对象是否指向同一对象
NaNundefined相等
NaNNaN

需要注意的是undefined是nulll派生而来,所以他们相等(但不全等)

全等、不全等

在不进行类型转换下的比较,注意undefined不全等nulll

条件操作符(三元运算符)

let n = 2 > 1 ? 1 : 0;
console.log(n);

赋值操作符

简单赋值

=

复合赋值

*=、/=、+=、-=、<<=、>>=、>>>=

注意,复合赋值只是简写并不会提升性能

逗号操作符

let n1 = 1, n2 = 2;

逗号表达式

最后一项为整个表达式的值

let n = (1, 2);	// 注意用括号包裹
console.log(n);

语句

ECMAScript会自动调用Boolean函数将条件表达式的值转换成布尔

if

do-while

循环至少会被执行一次,再进行判断是否循环

while

for

for-in

for-in是一种严格的迭代语句,用于枚举对象中的非符号属性,该属性的顺序因浏览器而异

for-of

for-in是一种严格的迭代语句,用于遍历可迭代对象的元素,该元素的顺序按照迭代对象的next产生顺序

标签

start: for(let i = 1 ; i < 5; i++){
  for(let j = 1; j < i; j++){
    console.log(`${j} + ${i} == ${j+i}`)
    if(j == 5 ){
        continue start; // 在start所标循环中执行continue
      }
    }
  }

break、continue

with

将代码作用域设置为特定对象

with语句影响性能和难于调试不推荐使用

switch

switch语句在比较每个条件时,会进行全等比较

函数

默认返回undefined

后续章节有详细介绍

以下函数都是直接调用(不通过new),返回的是原始值(不是对象)

Number

参数返回值
布尔true:1,false:0
数值直接返回
null0
undefinedNaN
只包含数值的字符串(支持进制格式,允许空格)十进制数(忽略前置0)
空串0
不全是数值的字符串NaN

parseInt

原理:从第一个非空格字符串开始转换,如果第一个字符不是数值字符、加减号,立即返回NaN,如果是开始转换直到末尾或遇到非数值字符

注意空串返回NaN、只保留整数部分

通过函数的第二个参数可以明确字符串数值的进制

parseFloat

原理:从第一个非空格字符串开始转换,如果第一个字符不是数值字符、加减号,立即返回NaN,如果是开始转换直到末尾或遇到无效浮点数字符为止

只能解析十进制数,总是转换为整数(1.00会被转为1)

String

在传入的参数为对象时,会调用对象的toString(不是valueOf)方法,将返回值作为参数传入

Boolean

数据类型truefalse
Booleantruefalse
String非空字符串空串
Number非零数值(包括无穷)0、NaN
Object任意对象null
Undefinedundefined