其他分享
首页 > 其他分享> > ECMAScript学习笔记(一)

ECMAScript学习笔记(一)

作者:互联网

目录

什么是ECMAScript

基本概念

语法

关键字与保留字

变量声明,var / let / const

数据类型

操作符

语句

函数


什么是ECMAScript

虽然有时候JavaScript和ECMAScript基本上是同义词,但是JavaScript不限于ECMAScript。完整的JavaScript实现包含以下几个部分:

ECMAScript,即 ECMA-262 定义的语言,并不局限于 Web 浏览器。它定义的内容有:

基本概念

语法

1. 区分大小写

无论变量、函数名还是操作符,都区分大小写。

2. 标识符

变量、函数、属性或函数参数的名称。

按照惯例,ECMAScript 标识符使用驼峰大小写形式,即第一个单词的首字母小写,后面每个单词的首字母大写。非强制性,算是一种最佳实践。

3. 注释

// 单行注释

/* 多行

注释 */

4. 严格模式

ES5中增加了严格模式的概念。严格模式是一种不同的 JavaScript 解析和执行模型,ECMAScript 3 的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。

对整个脚本开启严格模式,在脚本开头加上:

"use strict";

也可以单独指定一个函数在严格模式下执行,只需要在函数体开头加上:

function doSomething() {

  "use strict";

  // do something

}

5. 语句

ECMAScript 中的语句以分号结尾。省略分号意味着由解析器确定语句在哪里结尾。

多条语句可以合并到一个代码块中,代码块使用{}标识。

关键字与保留字

保留的关键字不能用作标识符或属性名。

这些词汇不能用作标识符,但现在还可以用作对象的属性名。一般来说,最好还是不要使用关键字

和保留字作为标识符和属性名,以确保兼容过去和未来的 ECMAScript 版本。

变量声明,var / let / const

ECMAScript 变量是松散类型的,意思是变量可以用于保存任何类型的数据。每个变量只不过是一 个用于保存任意值的命名占位符。有 3 个关键字可以声明变量:var、const 和 let。其中,var 在 ECMAScript 的所有版本中都可以使用,而 const 和 let 只能在 ECMAScript 6 及更晚的版本中使用。

1. var 声明

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

function test() {

  var message = "hi"; // 局部变量

}

test();

console.log(message); // 出错!

不过,在函数内定义变量时省略 var 操作符,可以创建一个全局变量,只要调用一次该函数,就会定义这个变量,并且可以再函数外部访问到。

使用 var 声明的变量会自动提升到函数作用域顶部。所谓“提升”,就是把所有变量声明都拉到函数作用域的顶部。

function foo() {

  console.log(age);

  var age = 26;

}

foo(); // undefined

ECMAScript 运行时把它看成等价于如下代码:

function foo() {

  var age;

  console.log(age);

  age = 26;

}

foo(); // undefined

此外,反复多次使用 var 声明同一个变量也没有问题:

function foo() {

  var age = 16;

  var age = 26;

  var age = 36;

  console.log(age);

}

foo(); // 36

2. let 声明

与 var 不同的是,let 声明的范围是块作用域。块作用域是函数作用域的子集,所以适用于 var 的作用域限制同样也适用于 let。

let 也不允许在同一个块作用域中出现冗余声明,即不能重复声明同一个变量。JavaScript 引擎会记录用于变量声明的标识符及其所在的块作用域,因此嵌套使用相同的标识符不会报错,而这是因为同一个块中没有重复声明。

let age;

let age; // SyntaxError


let age = 30;

console.log(age); // 30

if (true) {

  let age = 26;

  console.log(age); //26

}

对声明冗余报错不会因混用 let 和 var 而受影响(不能同时用 var 和 let 声明同一个变量)。这两个关键字声明的并不是不同类型的变量, 它们只是指出变量在相关作用域如何存在。

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

使用 var 声明的全局变量会挂载到 window 对象上,而使用 let 声明的变量不会称为 window 对象的属性。不过,let 声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续。因此,为了避免 SyntaxError,必须确保页面不会重复声明同一个变量。

不能使用 let 进行条件声明是件好事,因为条件声明是一种反模式,它让程序变得更难理解。

在 let 出现之前,for 循环定义的迭代变量会渗透到循环体之外:

for (var i = 0; i < 5; ++i) {

  // do something

}

console.log(i); // 5

改用 let 之后,迭代变量的作用域仅限于 for 循环内部:

for (let i = 0; i < 5; ++i) {

  // do something

}

console.log(i); // ReferenceError

在使用 var 的时候,最常见的问题就是对迭代变量的奇特声明和修改:

for (var i = 0; i < 5; ++i) {

  setTimeout(() => console.log(i), 0);

}

// 5, 5, 5, 5, 5

3. const 声明

const 的行为与 let 基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改 const 声明的变量会导致运行时错误。

const 声明的限制只适用于它指向的变量的引用。换句话说,如果 const 变量引用的是一个对象, 那么修改这个对象内部的属性并不违反 const 的限制。

JavaScript 引擎会为 for 循环中的 let 声明分别创建独立的变量实例,虽然 const 变量跟 let 变量很相似,但是不能用 const 来声明迭代变量(因为迭代变量会自增)。

不过,如果你只想用 const 声明一个不会被修改的 for 循环变量,那也是可以的。也就是说,每 次迭代只是创建一个新变量。这对 for-of 和 for-in 循环特别有意义:

for (const key in {a: 1, b: 2}) {

  console.log(key);

}

// a, b

for (const value of [1,2,3,4,5]) {

  console.log(value);

}

// 1, 2, 3, 4, 5

4. 声明风格及最佳实践

数据类型

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

简单数据类型/基本数据类型,在存储时变量中存储的是值本身,因此叫做值类型。

2. 复杂数据类型

复杂数据类型,在存储时变量中存储的仅仅是地址(引用),因此也叫做引用数据类型。通过 new 关键字创建的对象(系统对象、自定义对象),如:

3. 内存分配

简单类型变量的数据直接存放在变量(栈空间)中。

引用类型变量(栈空间)里存放的是地址,真正的对象实例存放在堆空间中。

4. 复制值

简单类型变量复制到另一个变量时,其原始值会被复制到新变量的位置,这两个变量是相互独立的,互不干扰。

在把引用类型变量从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象。操作完成后,两个变量实际上指向同一个对象,因此一个对象上面的变化会在另一个对象上反映出来。

5. 函数传参

函数的形参也可以看作是一个变量,当我们把一个简单类型变量作为参数传给函数的形参时,其实是把变量在栈空间里的值复制了一份给形参,那么在方法内部对形参做任何修改,都不会影响到外部变量。

当我们把引用类型变量传给形参时,其实是把变量在栈空间里保存的堆地址复制给了形参,形参和实参其实保存的是同一个堆地址,所以操作的是同一个对象

6. 判断数据类型

typeof 取值有:

typeof 对于 null 和引用类型变量,返回值均为 object,不能进一步判断他们的类型。

使用 typeof 的例子:

let message = "something";

console.log(typeof message); // "string"

console.log(typeof(message)); // "string"

console.log(typeof 95); // "number"

注意,虽然 typeof 是一个操作符,不是函数,但是可以使用参数。

操作符 instanceof 用于检测继承关系。instanceof 不能区别 undefined 和 null,而且对于基本类型如果不是用 new 声明的则也测试不出来,对于是使用 new 声明的类型,它还可以检测出多层继承关系。下面是使用 instanceof 的一些例子:

console.log(false instanceof Boolean);// false

console.log(95 instanceof Number);// false

console.log("string" instanceof String);// false

console.log(undefined instanceof Object);// false

console.log([1, 2, 3] instanceof Array);// true

console.log(null instanceof Object);// false

console.log({a: 1} instanceof Object);// true

console.log((() => {}) instanceof Function);// true

var bool = new Boolean();

console.log(bool instanceof Boolean);// true

var num = new Number();

console.log(num instanceof Number);// true

var str = new String();

console.log(str instanceof String);// true

function Person(){};

var per = new Person();

console.log(per instanceof Person);// true

function Student(){};

Student.prototype = new Person();

var stu = new Student();

console.log(stu instanceof Student);// true

console.log(stu instanceof Person);// true

在任何值上调用 Object 原生的 toString() 方法,都会返回一个 [object NativeConstructorName] 格式的字符串。每个类在内部都有一个 [[Class]] 属性,这个属性中就指定了上述字符串中的构造函数名。

但是它不能检测非原生构造函数的构造函数名。

例子:

console.log(Object.prototype.toString.call(false));//[object Boolean]

console.log(Object.prototype.toString.call(95));//[object Number]

console.log(Object.prototype.toString.call("string"));//[object String]

console.log(Object.prototype.toString.call(undefined));//[object Undefined]

console.log(Object.prototype.toString.call(null));//[object Null]

console.log(Object.prototype.toString.call([1, 2, 3]));//[object Array]

console.log(Object.prototype.toString.call({a: 1}));//[object Object]

console.log(Object.prototype.toString.call(() => {});//[object Function]

function Person(){};

function Student(){};

Student.prototype = new Person();

var stu = new Student();

console.log(Object.prototype.toString.call(stu));//[object Object]

操作符

1. 一元操作符

递增(++)、递减(--)操作符

对于递增和递减操作符,遵循如下规则:

一元加(+)和减(-)

2. 位操作符

ECMAScript 中的所有数值都以 IEEE 754 64 位格式存储,但位操作并不直接应用到 64 位表示,而是先把值转换为 32 位整数,再进行位操作,之后再把结果转换为 64 位。对开发者而言,就好像只有 32 位整数一样,因为 64 位整数存储格式是不可见的。既然知道了这些,就只需要考虑 32 位整数即可。

有符号整数使用 32 位的前 31 位表示整数值。第 32 位表示数值的符号,如 0 表示正,1 表示负。这一位称为符号位(sign bit),它的值决定了数值其余部分的格式。正值以真正的二进制格式存储,即 31 位中的每一位都代表 2 的幂。负值以二补数(或补码)的二进制编码存储。

注意,由于位操作会将 64 位数值转为 32 位计算后再转回 64 位,从而导致了一个副作用,即特殊值 NaN 和 Infinity 在位操作中都会被当成 0 处理。

按位非(~)

按位非的作用是返回数值的一补数,换句话说,就是每个 0 都变成 1,每个 1 都变成 0。

按位与(&)

按位与有两个操作数,本质上,按位与就是将两个数的每一个位对齐,然后基于真值表中的规则,对每一位执行相应的与操作。

a

b

a & b

1

1

1

1

0

0

0

1

0

0

0

0

按位或(|)

同按位与,按位或遵循如下真值表:

a

b

a | b

1

1

1

1

0

1

0

1

1

0

0

0

按位异或(^)

同按位与,按位异或遵循如下真值表:

a

b

a ^ b

1

1

0

1

0

1

0

1

1

0

0

0

左移(<<)

左移操作符会按照指定的位数将数值的所有位向左移动。比如,如果数值 2(二进制 10)向左移 5 位,就会得到 64(二进制 1000000)。左移会以 0 填充右端出现的空位。

注意,左移会保留数值的符号位。

有符号右移(>>)

有符号右移会将数值的所有 32 位都向右移,同时保留符号(正或负)。 有符号右移实际上是左移的逆运算。比如,如果将 64 右移 5 位,那就是 2。有符号右移会以符号位填充左侧出现的空位。

无符号右移(>>>)

无符号右移会将数值的所有 32 位都向右移,但是左侧出现的空位会以 0 来填充。

3. 布尔操作符

逻辑非(!)

逻辑非操作符可应用给 ECMAScript 中的任何值。这个操作符始终返回布尔值,无论应用到的是什么数据类型。逻辑非操作符首先将操作数转换为布尔值,然后再对其取反。换句话说,逻辑非操作符会遵循如下规则:

逻辑非也可以用于把任意值转为布尔值,同时使用两个叹号(!!),相当于调用了转型函数 Boolean()。

逻辑与(&&)

逻辑与可以对两个布尔值进行与运算。

逻辑与操作符也可用于任意类型的操作数,如果有操作数不是布尔值,则遵循如下规则:

逻辑与操作符是一种短路操作符,如果第一个操作数决定了结果,那么永远不会对第二个

操作数求值。

逻辑或(||)

逻辑或可以对两个布尔值进行或运算。

与逻辑与类似,如果有一个操作数不是布尔值,那么逻辑或操作符也不一定返回布尔值。它遵循如下规则:

同样与逻辑与类似,逻辑或操作符也具有短路的特性。只不过对逻辑或而言,第一个操作数求值为

true,第二个操作数就不会再被求值了。

4. 乘性操作符

乘法操作符(*)

除法操作符(/)

取模操作符(%)

取模即取余数。

5. 指数操作符

ES 7 新增了指数操作符(**),效果相当于 Math.pow()。

不仅如此,指数操作符也有自己的指数赋值操作符(**=)

6. 加性操作符

加法操作符(+)

如果两个操作数都是数值,加法操作符执行加法运算并根据如下规则返回结果:

不过,如果有一个操作数是字符串,则要应用如下规则:

此外,布尔值在与布尔值或者数值运算时,true为1,false为0。

减法操作符(-)

7. 关系操作符

关系操作符执行比较两个值的操作,包括小于(<)大于(>)小于等于(<=)大于等于(>=),用法跟数学课上学的一样。这几个操作符都返回布尔值。

8. 相等操作符

等于(==)和不等于(!=)

这两个操作符都会先进行类型转换(通常称为强制类型转换)再确定操作数是否相等。

在转换操作数的类型时,相等和不相等操作符遵循如下规则:

在进行比较时,这两个操作符会遵循如下规则:

全等(===)和不全等(!==)

比较相等时,不对操作数进行类型转换。

9. 条件操作符

variable = boolean_expression ? true_value : false_value;

根据条件表达式 boolean_expression 的值决定将哪个值赋值给变量 variable。

10. 赋值操作符

简单赋值(=)

复合赋值

11. 逗号操作符

逗号操作符可以用来在一条语句中执行多个操作。

使用场景:

let num1 =1, num2 = 2, num3 = 3;

let num = (1, 2, 3, 4, 5, 0); // num 的值为 0。(这种场景并不多见)

12. 链判断运算符

ES2020 引入了链判断运算符(optional chaining operator)(?.)

编程实务中,如果读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。比如,读取message.body.user.firstName这个属性,使用链判断运算符如下:

const firstName = message?.body?.user?.firstName || 'default';

在链式调用的时候判断,左侧的对象是否为 null 或 undefined。如果是的,就不再往下运算,而是返回undefined。

链判断运算符?.有三种写法:

链判断运算符有几个注意点:

短路机制

本质上,?.运算符相当于一种短路机制,只要不满足条件,就不再往下执行。

括号的影响

如果属性链有圆括号,链判断运算符对圆括号外部没有影响,只对圆括号内部有影响。

(a?.b).c

// 等价于

(a == null ? undefined : a.b).c

一般来说,使用?.运算符的场合,不应该使用圆括号。

报错场合

以下写法是禁止的,会报错。

// 构造函数

new a?.()

new a?.b()

// 链判断运算符的右侧有模板字符串

a?.`{b}`

a?.b`{c}`

// 链判断运算符的左侧是 super

super?.()

super?.foo

// 链运算符用于赋值运算符左侧

a?.b = c

右侧不得为十进制数值

为了保证兼容以前的代码,允许foo?.3:0被解析成foo ? .3 : 0,因此规定如果?.后面紧跟一个十进制数字,那么?.不再被看成是一个完整的运算符,而会按照三元运算符进行处理,也就是说,那个小数点会归属于后面的十进制数字,形成一个小数。

13. Null 判断运算符

读取对象属性的时候,如果某个属性的值是nullundefined,有时候需要为它们指定默认值。常见做法是通过||运算符指定默认值。

const headerText = response.settings.headerText || 'Hello, world!';

const animationDuration = response.settings.animationDuration || 300;

const showSplashScreen = response.settings.showSplashScreen || true;

上面的三行代码都通过||运算符指定默认值,但是这样写是错的。开发者的原意是,只要属性的值为nullundefined,默认值就会生效,但是属性的值如果为空字符串或false0,默认值也会生效。

为了避免这种情况,ES2020 引入了一个新的 Null 判断运算符??。它的行为类似||,但是只有运算符左侧的值为nullundefined时,才会返回右侧的值。

const headerText = response.settings.headerText ?? 'Hello, world!';

const animationDuration = response.settings.animationDuration ?? 300;

const showSplashScreen = response.settings.showSplashScreen ?? true;

上面代码中,默认值只有在左侧属性值为nullundefined时,才会生效。

14. 逻辑赋值运算符

ES2021 引入了三个新的逻辑赋值运算符(logical assignment operators),将逻辑运算符与赋值运算符进行结合。

// 或赋值运算符

x ||= y

// 等同于

x || (x = y)

// 与赋值运算符

x &&= y

// 等同于

x && (x = y)

// Null 赋值运算符

x ??= y

// 等同于

x ?? (x = y)

这三个运算符||=&&=??=相当于先进行逻辑运算,然后根据运算结果,再视情况进行赋值运算。

语句

1. if 语句

2. do-while 语句

do {

  // statement

} while (expression) // expression 为 true 继续循环

后测试循环,循环体代码至少执行一次

3. while 语句

4. for 语句

5. for-in 语句

一种严格的迭代语句,用于枚举对象中的非符号键属性。

6. for-of 语句

一种严格的迭代语句,用于遍历可迭代对象的元素。

7. break 和 continue 语句

8. with 语句

9. switch 语句

函数

1. 函数定义

2. 箭头函数

3. 函数名 name 属性

4. 函数参数

5. 参数默认值

6. 函数属性与方法

7. 递归和尾调用优化

8. 闭包

参考

JavaScript高级程序设计(第4版)

ECMAScript6入门--阮一峰

标签:操作数,Infinity,变量,笔记,学习,操作符,ECMAScript,console,log
来源: https://blog.csdn.net/SillyLee1/article/details/120729463