【JS高级程序设计(第4版)学习笔记】第三章 语言基础
ECMAScript 的语法很大程度上借鉴了 C 语言和其他类 C 语言,如 Java和 Perl。
ECMAScript 中一切都区分大小写。无论是变量、函数名还是操作符,都区分大小写。
3.1标识符
ECMA标识符可以由一个或多个下列字符组成:第一个字符必须是一个字母、下划线( _ )或美元符号($),剩下的其它字符可以是字母、下划线、美元符号或数字。
ECMA标识符使用驼峰大小写形式,即第一个单词的首字母小写,后面每个单词的首字母大写。
关键字、保留字、true、false和null不能作为标识符。
3.2注释
ECMAScript采用C语言风格的注释,包括单行注释和块注释、如:
//单行注释
/*这是多行
注释*/
3.3严格模式
ECMAScript5增加了严格模式(strict mode)的概念。严格模式是一种不同的JavaScript解析和执行模型,ECMAScript3的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。要对整个脚本启用严格模式,在脚本开头加上一行:"use strict";
也可以单独指定一个函数在严格模式下执行,只要把这个预处理指令放到函数体开头即可:
function doSomething(){
"use strict";
//函数体
}
3.4变量
ECMAScript变量是松散类型的,意思是变量可以用于保存任何类型的数据。有3个关键字可以声明变量:var,const和let.其中,var在ECMAScript的所有版本中都可以使用,而const和let只能在ECMAScript6及更晚的版本中使用。
3.4.1 var 关键字
var关键字可以用它保存任何类型的值:var 变量名
在不初始化的情况下,变量会保存一个特殊值undefined。ECMAScript实现变量出事话,可以同时定义变量并设置它的值。
var a = 1;
不仅可以改变保存的值,还可以改变值的类型
a = "hello";
1.var声明作用域
使用var操作符定义的变量会成为包含它的函数的局部变量。比如,使用var在一个函数内部定义一个变量就意味着该变量将在函数退出时被销毁。
function test() { var message = "hi"; // 局部变量 } test(); console.log(message); // 出错!如果在函数内定义变量时省略var操作符,可以创建一个全局变量
function test() { message = "hi"; // 全局变量 } test(); console.log(message); // "hi"
2.声明提升(预解析)
// 1.我们js引擎运行js 分为两步:预解析 代码执行 // (1)预解析 js引擎会把js里面所有的var 还有function 提升到当前作用域的最前面 // (2)代码执行 按照代码书写的顺序从上往下执行 // 2.预解析分为 变量预解析(变量提升) 和函数预解析(函数提升) // (1)变量提升 就是把所有的变量声明提升到当前的作用域最前面 不提升赋值操作 // (2)函数提升 就是把所有的函数声明提升到当前作用域的最前面 不调用函数 // fn();// 11 // function fn(){ // console.log(11); // } // 预解析案例 // 案例1 var num = 10; fun(); function fun() { console.log(num); //undefined var num = 20; } // 案例2 var num = 10; function fn() { console.log(num); //undefined var num = 20; console.log(num); //20 } fn(); // 案例3 var a = 18; f1(); function f1() { var b = 9; console.log(a); //undefined console.log(b); //9 var a = '123'; } // 案例4 f2(); console.log(a); console.log(b); console.log(c); function f2() { var a = b = c = 9; console.log(a); console.log(b); console.log(c); } // 等价为以下代码 function f2() { var a; a = b = c = 9; // 相当于var a = 9;b = 9;c=9; b和c直接赋值 没有var 声明 当全局变量看 //集体声明 var a = 9,b=9,c=9;与上面不一样 console.log(a); //9 console.log(b); //9 console.log(c); //9 } f2(); console.log(c); //9 console.log(b); //9 console.log(a); //报错
3.4.2 let声明
let跟var的作用差不多,但有着非常重要的区别。最明显的区别是let声明的范围是块作用域,而var声明的范围是函数作用域。
1.暂时性死区
let与var的另一个区别就是let声明的变量不会在作用域中提升.
// age 不会被提升 console.log(age); // ReferenceError:age 没有定义 let age = 26;
2.全局声明
与var关键字不同,使用let在全局作用域中声明的变量不会成为window对象的属性
var name = 'Matt'; console.log(window.name); // 'Matt' let age = 26; console.log(window.age); // undefined
3. for 循环中的 let 声明
在使用 let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。
每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循
环执行过程中每个迭代变量的值。for (let i = 0; i < 5; ++i) { setTimeout(() => console.log(i), 0) } // 会输出 0、1、2、3、4这种每次迭代声明一个独立变量实例的行为适用于所有风格的 for 循环,包括 for-in 和 for-of循环。
3.4.3 const声明
const的行为与let基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误。(即const声明创建为常量)
但是如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制
const person = {}; person.name = 'Matt'; // ok只想用 const 声明一个不会被修改的 for 循环变量,那也是可以的。每次迭代只是创建一个新变量。这对 for-of 和 for-in 循环特别有意义:
let i = 0; for (const j = 7; i < 5; ++i) { console.log(j); } // 7, 7, 7, 7, 7 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不能用const来声明迭代变量(因为迭代变量会自增)
const声明的作用域也是块
3.5声明风格及最佳实践
1.不使用var:只使用let和const有助于提升代码质量,因为变量有了明确的作用域、声明位置及不变的值2.const优先,let次之
3.6数据类型
ECMAScript 有 6 种简单数据类型(也称为原始类型): Undefined 、 Null 、 Boolean 、 Number 、String 和 Symbol 。 Symbol (符号)是 ECMAScript 6 新增的。还有一种复杂数据类型叫 Object (对象)。 Object 是一种无序名值对的集合。
3.6.1 typeof操作符
typeof操作符可以确定任意变量的数据类型.
对一个值使用 typeof 操作符会返回下列字符串之一:
"undefined" 表示值未定义;
"boolean" 表示值为布尔值;
"string" 表示值为字符串;
"number" 表示值为数值;
"object" 表示值为对象(而不是函数)或 null ;
"function" 表示值为函数;
"symbol" 表示值为符号。var num = 10; console.log(typeof num);//number var str = 'pig'; console.log(typeof str);//string var flag = true; console.log(typeof flag);//boolean var vari = undefined; console.log(typeof vari);//undefined var timer = null; console.log(typeof timer);//object
3.6.2 Undefined类型
当使用var或let声明了变量但没有初始化时,就相当于给变量富裕了undefined值。
3.6.3 Null类型
逻辑上讲,null值表示一个空对象指针,这也算给typeof传一个null会返回"object"的原因
3.6.4 Boolean类型
有两个字面值:true和false。 这两个布尔值不同于数值。因此true不等于1,false不等于0
3.6.5 Nmber类型
最基本的数值字面量格式是十进制整数,整数也可以用八进制(以8为基数,第一个数字必须是零)或十六进制(以16为基数,数值前缀为0x)
let intNum = 55; // 整数 let octalNum1 = 070; // 八进制的 56 let octalNum2 = 079; // 无效的八进制值,当成 79 处理 let octalNum3 = 08; // 无效的八进制值,当成 8 处理 let hexNum1 = 0xA; // 十六进制 10 let hexNum2 = 0x1f; // 十六进制 31
由于内存的限制,ECMAScript 并不支持表示这个世界上的所有数值。ECMAScript 可以表示的最小数值保存在 Number.MIN_VALUE 中,这个值在多数浏览器中是 5e324;可以表示的最大数值保存在Number.MAX_VALUE 中,这个值在多数浏览器中是 1.797 693 134 862 315 7e+308。如果某个计算得到的数值结果超出了 JavaScript 可以表示的范围,那么这个数值会被自动转换为一个特殊的 Infinity (无穷)值。任何无法表示的负数以 Infinity (负无穷大)表示,任何无法表示的正数以 Infinity (正无穷大)表示。
因此,要确定一个值是不是有限大(即介于 JavaScript 能表示的最小值和最大值之间),可以使用 isFinite() 函数
NaN:Not a Number .用于表示本来要返回的数值的操作失败了(而不是抛出失误)。用isNaN()可以判断参数是否"不是数值"
3.6.6 String类型
String(字符串)数据类型表示零或多个16位Unicode字符序列。字符串可以使用双引号(")、单引号(')或反引号(`)标示
1.字符字面量
字面量 | 含义 |
\n | 换行 |
\t | 制表 |
\b | 退格 |
\r | 回车 |
\f | 换页 |
\\ | 反斜杠( \ ) |
\' | 单引号( ' ),在字符串以单引号标示时使用,例如 'He said, \'hey.\'' |
\" | 双引号( " ),在字符串以双引号标示时使用,例如 "He said, \"hey.\"" |
\` | 反引号( ` ),在字符串以反引号标示时使用,例如 `He said, \`hey.\`` |
字符串的长度可以通过其length属性获取。
2.转换为字符串
// 1.把其它型转换为字符串型 变量.toString() null和undefined值没有toString()方法 var num = 18; console.log(num.toString()); //2.利用String() 强制转换 console.log(String(num)); // 3.利用 + 拼接字符串实现转换为字符串型 console.log(num + '');
3.模板字面量
可以跨行定义字符串。在定义模板时特别有用。
let myMultiLineString = 'first line\nsecond line'; let myMultiLineTemplateLiteral = `first line second line`; console.log(myMultiLineString); // first line // second line" console.log(myMultiLineTemplateLiteral); // first line // second line console.log(myMultiLineString === myMultiLinetemplateLiteral); // true
4.字符串插值
模板字面量最常用的一个特性是支持字符串插值。字符串插值通过在${}中使用一个JavaScript表达式实现,所有插入的值都会用toString()强制转型为字符串,嵌套的模板字符串无须转义
let value = 5; let exponent = 'second'; let interpolatedTemplateLiteral = `${ value } to the ${ exponent } power is ${ value * value }`; console.log(interpolatedTemplateLiteral); // 5 to the second power is 25 console.log(`Hello, ${ `World` }!`); // Hello, World!
5.原始字符串
使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或Unicode字符),而不是被转换后的字符表示。为此,可以使用默认的String.raw标签函数
// Unicode 示例 // \u00A9 是版权符号 console.log(`\u00A9`); // © console.log(String.raw`\u00A9`); // \u00A9 // 换行符示例 console.log(`first line\nsecond line`); // first line // second line console.log(String.raw`first line\nsecond line`); // "first line\nsecond line"
3.6.7 Object类型
ECMAScript中的对象其实就是一组数据和功能的集合。对象通过 new 操作符后跟对象类型的名称来创建。开发者可以通过创建 Object 类型的实例来创建自己的对象,然后再给对象添加属性和方法。
3.6.8 Symbol类型
ECMAScript6新增的数据类型。符号是原始值,且符号实例是唯一、不可变的。符号的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。
3.7操作符
3.7.1 一元操作符: 递增/递减操作符(++/--)、一元加和减(+=/-=)
3.7.2 位操作符
1.按位非(~):返回数值的一补数(对数值取反并减1)
let num1 = 25; // 二进制 00000000000000000000000000011001 let num2 = ~num1; // 二进制 11111111111111111111111111100110 console.log(num2); // -262.按位与(&):将两个数的每一个位对齐,对每一位执行相应的与操作(都是1返回1,在任何一位是0时返回0)
let result = 25 & 3; console.log(result); // 1 /* 25 = 0000 0000 0000 0000 0000 0000 0001 1001 3 = 0000 0000 0000 0000 0000 0000 0000 0011 --------------------------------------------- AND = 0000 0000 0000 0000 0000 0000 0000 0001 */3.按位或(|):至少一位是1时返回1,两位都是0时返回0
let result = 25 | 3; console.log(result); // 27 /* 25 = 0000 0000 0000 0000 0000 0000 0001 1001 3 = 0000 0000 0000 0000 0000 0000 0000 0011 --------------------------------------------- OR = 0000 0000 0000 0000 0000 0000 0001 1011 */4.按位异或(^):只在一位上是1时返回1(相同为0,相异为1)
let result = 25 ^ 3; console.log(result); // 26 /* 25 = 0000 0000 0000 0000 0000 0000 0001 1001 3 = 0000 0000 0000 0000 0000 0000 0000 0011 --------------------------------------------- XOR = 0000 0000 0000 0000 0000 0000 0001 1010 */5.左移(<<):按照指定的位数将数值的所有位向左移动
let oldValue = 2; // 等于二进制 10 let newValue = oldValue << 5; // 等于二进制 1000000,即十进制 646.有符号右移(>>):按照指定的位数将数值的所有位向右移动,同时保留符号(正或负)
let oldValue = 64; // 等于二进制 1000000 let newValue = oldValue >> 5; // 等于二进制 10,即十进制 27.无符号右移(>>>):按照指定的位数将数值的所有位向右移动(对于负数,有时候差异会非常大)
let oldValue = 64; // 等于二进制 1000000 let newValue = oldValue >>> 5; // 等于二进制 10,即十进制 2
3.7.3 布尔操作符
1.逻辑非(!):将操作数转换为布尔值,再取反
逻辑非操作符会遵循如下规则。
如果操作数是对象,则返回 false 。
如果操作数是空字符串,则返回 true 。
如果操作数是非空字符串,则返回 false 。
如果操作数是数值 0,则返回 true 。
如果操作数是非 0 数值(包括 Infinity ),则返回 false 。
如果操作数是 null ,则返回 true 。
如果操作数是 NaN ,则返回 true 。
如果操作数是 undefined ,则返回 true 。2.逻辑与(&&):逻辑与操作符可用于任何类型的操作数,不限于布尔值。如果有操作数不是布尔值,则逻辑与并不一定会返回布尔值,而是遵循如下规则。
如果第一个操作数是对象,则返回第二个操作数。
如果第二个操作数是对象,则只有第一个操作数求值为 true 才会返回该对象。
如果两个操作数都是对象,则返回第二个操作数。
如果有一个操作数是 null ,则返回 null 。
如果有一个操作数是 NaN ,则返回 NaN 。
如果有一个操作数是 undefined ,则返回 undefined 。3.逻辑或(||):与逻辑与类似,如果有一个操作数不是布尔值,那么逻辑或操作符也不一定返回布尔值。它遵循如下规则。
如果第一个操作数是对象,则返回第一个操作数。
如果第一个操作数求值为 false ,则返回第二个操作数。
如果两个操作数都是对象,则返回第一个操作数。
如果两个操作数都是 null ,则返回 null 。
如果两个操作数都是 NaN ,则返回 NaN 。
如果两个操作数都是 undefined ,则返回 undefined 。
3.7.4 乘性操作符
1.乘法操作符(*):可用于计算两个数值的乘积
2.除法操作符(/):用于计算第一个操作数除以第二操作数的商
3.取模操作符(%):用于求余数
3.7.5 指数操作符
ECMAScript 7新增了指数操作符, Math.pow() 现在有了自己的操作符 ** ,结果是一样的:
console.log(Math.pow(3, 2); // 9 console.log(3 ** 2); // 9 console.log(Math.pow(16, 0.5); // 4 console.log(16** 0.5); // 4
3.7.6 加性操作符
(1)加法操作符+
如果两个操作数都是数值,加法操作符执行加法运算并根据如下规则返回结果:
如果有任一操作数是 NaN ,则返回 NaN ;
如果是 Infinity 加 Infinity ,则返回 Infinity ;
如果是 -Infinity 加 -Infinity ,则返回 -Infinity ;
如果是 Infinity 加 -Infinity ,则返回 NaN ;
如果是 +0 加 +0 ,则返回 +0 ;
如果是 -0 加 +0 ,则返回 +0 ;
如果是 -0 加 -0 ,则返回 -0 。(2)减法操作符-
与加法操作符一样,减法操作符也有一组规则用于处理 ECMAScript 中不同类型之间的转换。
如果两个操作数都是数值,则执行数学减法运算并返回结果。
如果有任一操作数是 NaN ,则返回 NaN 。
如果是 Infinity 减 Infinity ,则返回 NaN 。
如果是 -Infinity 减 -Infinity ,则返回 NaN 。
如果是 Infinity 减 -Infinity ,则返回 Infinity 。
如果是 -Infinity 减 Infinity ,则返回 -Infinity 。
如果是 +0 减 +0 ,则返回 +0 。
如果是 +0 减 -0 ,则返回 -0 。
如果是 -0 减 -0 ,则返回 +0 。
如果有任一操作数是字符串、布尔值、 null 或 undefined ,则先在后台使用 Number() 将其转换为数值,然后再根据前面的规则执行数学运算。如果转换结果是 NaN ,则减法计算的结果是NaN 。
3.7.7 关系操作符
小于<、大于>、小于等于≤、大于等于≥ 这几个操作符都返回布尔值
3.7.8 相等操作符
1.等于和不等于
表达式 结果 null == undefined true "NaN" == NaN false 5 == NaN false NaN == NaN false NaN != NaN true false == 0 true true == 1 true true == 2 false undefined == 0 false null == 0 false "5" == 5 true 2.全等和不全等
全等和不全等操作符与相等和不相等操作符类似,只不过它们在比较相等时不转换操作数。全等操作符由 3 个等于号( === )表示,只有两个操作数在不转换的前提下相等才返回 true ,比如:
let result1 = ("55" == 55); // true,转换后相等 let result2 = ("55" === 55); // false,不相等,因为数据类型不同
3.7.9 条件操作符
条件表达式?表达式1:表达式2
3.8语句
ECMA-262 描述了一些语句(也称为流控制语句),而 ECMAScript 中的大部分语法都体现在语句中。
3.8.1 if语句
//if 语句是使用最频繁的语句之一,语法如下: if (condition) statement1 else statement2 //可以像这样连续使用多个 if 语句: if (condition1) statement1 else if (condition2) statement2 else statement3
3.8.2 do-while语句
do-while 语句是一种后测试循环语句,即循环体中的代码执行后才会对退出条件进行求值。换句话说,循环体内的代码至少执行一次。 do-while 的语法如下:
do { statement } while (expression);
3.8.3 while语句
while 语句是一种先测试循环语句,即先检测退出条件,再执行循环体内的代码。因此, while 循环体内的代码有可能不会执行。下面是 while 循环的语法:
while(expression) statement
3.8.4 for语句
for(初始化;条件表达式;循环后表达式){
statement;
}
初始化、条件表达式和循环后表达式都不是必需的.
for (;;) { // 无穷循环
doSomething();
}
3.8.5 for-in语句
用于枚举对象中的非符号键属性
for(property in expression) statement
3.8.6 for-of语句
用于遍历可迭代对象的元素
for(property of expression) statement
3.8.7 标签语句
用于给语句加标签,典型应用是嵌套循环
label:statement
start: for (let i = 0; i < count; i++) { console.log(i); }
3.8.8 break和continue语句
break语句:立即退出整个循环体
continue语句:跳过当前循环的剩余语句,进入下一次循环
3.8.9 with语句
将代码作用域设置为特定的对象
with(expression) statement;
主要场景是针对一个对象反复操作,这时候将代码作用域设置为该对象能提供便利
3.8.10 switch语句
expression可以用于所有数据类型;条件的值不需要是常量,也可以是变量或表达式
switch(expression){
case value1:statement;
break;
case value2:statement;
break;
……
default:statement;
}
3.9函数
函数对任何语言来说都是核心组件,因为它们可以封装语句,然后在任何地方、任何时间执行。ECMAScript 中的函数使用 function 关键字声明,后跟一组参数,然后是函数体。
function functionName(arg0, arg1,...,argN) {
statements
}
要注意的是,只要碰到 return 语句,函数就会立即停止执行并退出。因此, return 语句后面的代码不会被执行
function sum(num1, num2) { return num1 + num2; console.log("Hello world"); // 不会执行 }
严格模式对函数也有一些限制:
函数不能以 eval 或 arguments 作为名称;
函数的参数不能叫 eval 或 arguments ;
两个命名参数不能拥有同一个名称。
如果违反上述规则,则会导致语法错误,代码也不会执行。