第3章 语言基础

语法

区分大小写,ECMAScript中一切都区分大小写

标识符:变量、函数、属性或者函数参数的名称,可以由一个或者多个下列字符组成:

第一个字符必须是一个字母、下划线或者美元符号

剩下的其他字符可以是字母、下划线、美元符号或者数字

推荐使用驼峰大小写形式,不是强制性,但是这种形式跟ECMAscript内置函数和对象的命名方式一致,所以算是最佳实践。

严格模式:ES5新增的概念,严格模式是一种不同的js解析和执行模型,ES3的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。使用需要在脚本开头加上:

“use strict”;

也可以单独指定一个函数在严格模式下执行,只要把这个预处理指令放到函数体开头

function doSomething() { 
 "use strict"; 
 // 函数体 
} 

严格模式会影响 JavaScript 执行的很多方面,因此本书在用到它时会明确指出来。所有现代浏览器 都支持严格模式。

语句

1、尽量加上;(加分号有助于防止省略造成的问题,便于开发者通过删除空行来压缩代码,某些情况下可以提升性能)

2、尽量使用代码块,即使if中要执行的只有一条语句,在控制语句中使用的代码块可以让内容更加清晰,在需要修改代码时也可以减少出错的可能性。

关键字与保留字

关键字有特殊的用途,比如表示控制语句中的开始和结束,或者执行特定的操作,保留字在语言中没有特定的用途,但它们是保留给将来做关键字用的。

一般,都不要使用关键字和保留字作为标识符和属性名,以确保兼容过去和未来的ECMAScript版本。

变量

变量可以用于保存任何类型的数据,每个变量都只不过是一个用户保存任意值的命名占位符。

const和let只能在ES6或者更晚的版本中使用,var在所有版本中都可以使用

var声明作用域
function test() { 
 var message = "hi"; // 局部变量
} 
test(); 
console.log(message); // 出错!
function test() { 
 message = "hi"; // 全局变量 在函数外部可以访问 
} 
test(); 
console.log(message); // "hi" 

虽然可以通过省略 var 操作符定义全局变量,但不推荐这么做。在局部作用域中定 义的全局变量很难维护,也会造成困惑。这是因为不能一下子断定省略 var 是不是有意而 为之。在严格模式下,如果像这样给未声明的变量赋值,则会导致抛出 ReferenceError。

var声明提升

将var的变量声明都拉到当前作用域的顶部

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

// 上述代码等价于
function foo() { 
 var age; 
 console.log(age); 
 age = 26; 
} 
foo(); // undefined
let声明

let声明的范围是块作用域,而var声明的范围是函数作用域,块作用域是函数作用域的子集,因此适用于var的作用域限制同样也适用于let

let不允许同一个作用域中出现冗余声明

let age; 
let age; // SyntaxError;标识符 age 已经声明过了

// 但是下面的代码可以,因为age不在同一个作用域
let age = 30; 
console.log(age); // 30 
if (true) { 
 let age = 26; 
 console.log(age); // 26 
} 

并且对声明冗余报错不会因混用let和var而受影响

var name; 
let name; // SyntaxError 
let age; 
var age; // SyntaxError

1、暂时性死区

2、全局声明

let声明的变量不会在作用域中被提升,let在全局作用域中声明的变量不会成为window对象的属性(var声明的变量会)

var name = 'Matt'; 
console.log(window.name); // 'Matt' 
let age = 26; 
console.log(window.age); // undefined

3、条件声明

不能使用let进行条件声明

<script> 
 let name = 'Nicholas'; 
 let age = 36; 
</script> 
<script> 
 // 假设脚本不确定页面中是否已经声明了同名变量
 // 那它可以假设还没有声明过
 if (typeof name === 'undefined') &#123; 
 let name; 
 &#125; 
 // name 被限制在 if &#123;&#125; 块的作用域内
 // 因此这个赋值形同全局赋值
 name = 'Matt'; 
 try &#123; 
 console.log(age); // 如果 age 没有声明过,则会报错
 &#125; 
 catch(error) &#123; 
 let age; 
&#125; 
 // age 被限制在 catch &#123;&#125;块的作用域内
 // 因此这个赋值形同全局赋值
 age = 26; 
</script>
const声明

与let基本相同,但是它声明变量时必须同时初始化变量,且尝试修改const声明会导致运行时错误。

const age = 26; 
age = 36; // TypeError: 给常量赋值
// const 也不允许重复声明
const name = 'Matt'; 
const name = 'Nicholas'; // SyntaxError
// const 声明的限制只适用于它指向的变量的引用。换句话说,如果 const 变量引用的是一个对象,
// 那么修改这个对象内部的属性并不违反 const 的限制。
const person = &#123;&#125;; 
person.name = 'Matt'; // ok
声明风格以及最佳实践

1、不使用var

2、const优先,let次之

数据类型
typeof操作符

因为ES的类型系统是松散的,所以需要确定任意变量的数据类型

可以识别出基本数据类型(Number、Boolean、undefined、String、function、Symbol)

object类型和null会显示出object

但是调用typeof null返回的是object,这是因为特殊值null被认为是一个空对象的引用。

严格来讲,函数在 ECMAScript 中被认为是对象,并不代表一种数据类型。可是, 函数也有自己特殊的属性。为此,就有必要通过 typeof 操作符来区分函数和其他对象。

undefined 类型

Undefined 类型只有一个值,就是特殊值 undefined。当使用 var 或 let 声明了变量但没有初始 化时,就相当于给变量赋予了 undefined 值。

let message; // 这个变量被声明了,只是值为 undefined 
console.log(message); // "undefined" 
console.log(age); // 报错
console.log(typeof message); // "undefined" 
console.log(typeof age); // "undefined"

message声明了但是未赋值,age并没有被声明,即使未初始化的变量会被自动赋予 undefined 值,但我们仍然建议在声明变量的 同时进行初始化。这样,当 typeof 返回”undefined”时,你就会知道那是因为给定的变 量尚未声明,而不是声明了但未初始化。

let message; // 这个变量被声明了,只是值为 undefined 
// age 没有声明 
if (message) &#123; 
 // 这个块不会执行
&#125; 
if (!message) &#123; 
 // 这个块会执行
&#125; 
if (age) &#123; 
 // 这里会报错
&#125; 
null类型

null值表示一个空对象指针,这也是给typeof传一个null会返回object的原因。

在定义将来要保存对象值的变量时,建议使用null来初始化,不要使用其他值,这样,只要检查这个变量的值是不是null就可以知道这个变量是否在后来被重新赋予了一个对象的引用。

undefined值是由null值派生来的,null == undefined (true)null是一个假值,因此,如果需要,可以用更简洁的方式检测它。不过要记住,也有很多其他可 能的值同样是假值。所以一定要明确自己想检测的就是 null 这个字面值,而不仅仅是假值。

let message = null; 
let age; // age现在是undefined
if (message) &#123; 
 // 这个块不会执行
&#125; 
if (!message) &#123; 
 // 这个块会执行
&#125;
if (age) &#123; 
 // 这个块不会执行
&#125; 
if (!age) &#123; 
 // 这个块会执行
&#125;  
boolean类型

true和false

虽然布尔值只有两个,但是所有其他ES类型的值都有相应布尔值的等价形式,可以调用Boolean()将其他类型的值转换为布尔值。

let message = "Hello world!";
let messageToBoolean = Boolean(message);

理解上述转换非常重要,像if等流控制语句会自动执行其他类型值到布尔值的转换。

Number类型
let intNum = 56; // 整数
let octalNum = 070; // 八进制的56 前缀必须是0然后是相应的八进制数字 0-7
let octalNum2 = 079; // 含有非法数字9 所以会认为是无效的八进制值,当成79处理
// 八进制字面量在严格模式下是无效的,会导致javascript引擎抛出语法错误
let hexNUm1 = 0xA; // 前缀是0x(区分大小写) 然后是十六进制数字(0-9&& A-F)十六进制中大小写均可

由于 JavaScript 保存数值的方式,实际中可能存在正零(+0)和负零(0)。正零和 负零在所有情况下都被认为是等同的,这里特地说明一下。

1、浮点值

要定义浮点值,数值中必须包含小数点,而且小数点后面必须至少有一个数字。

因为存储浮点值使用的内存空间是存储整数值的两倍,所以 ECMAScript 总是想方设法把值转换为 整数。在小数点后面没有数字的情况下,数值就会变成整数。类似地,如果数值本身就是整数,只是小 数点后面跟着 0(如 1.0),那它也会被转换为整数。

let floatNum1 = 1.; // 小数点后面没有数字,当成整数 1 处理
let floatNum2 = 10.0; // 小数点后面是零,当成整数 10 处理

对于非常大或非常小的数值,浮点值可以用科学记数法来表示。科学记数法用于表示一个应该乘以 10 的给定次幂的数值。ECMAScript 中科学记数法的格式要求是一个数值(整数或浮点数)后跟一个大 写或小写的字母 e,再加上一个要乘的 10 的多少次幂。

let floatNum = 3.125e7; // 等于 31250000 

2、值的范围

ES可以表示的最小的数值:Number.MIN_VALUE,这个值在多数浏览器中是 5e-324;

ES可以表示的最大的数值:Number.MAX_VALUE

如果某个计算得到的数值超过了js可以表示的范围,那么这个值会被自动转换为一个特殊的Infinity(无穷值)

要确定一个值是不是有限大(即介于 JavaScript 能表示的 最小值和最大值之间),可以使用 isFinite()函数,如下所示:

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

3、NaN(Not a Number)

表示本来要返回数值的操作 失败了(而不是抛出错误)。

console.log(0/0); // NaN 
console.log(-0/+0); // NaN
console.log(5/0); // Infinity 
console.log(5/-0); // -Infinity
console.log(NaN == NaN); // false NaN 不等于包括 NaN 在内的任何值 任何涉及 NaN 的操作始终返回 NaN(如 NaN/10)
// ECMAScript 提供了 isNaN()函数。该函数接收一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值” 任何不能转换为数值的值都会导致这个函数返回true。
console.log(isNaN(NaN)); // true 
console.log(isNaN(10)); // false,10 是数值
console.log(isNaN("10")); // false,可以转换为数值 10 
console.log(isNaN("blue")); // true,不可以转换为数值
console.log(isNaN(true)); // false,可以转换为数值 1 

4、数值转换

Number()、parseInt()和 parseFloat()。Number()是 转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。

let num1 = parseInt("1234blue"); // 1234 
let num2 = parseInt(""); // NaN
let num = parseInt("0xAF", 16); // 175 指定了16进制的情况下,可以省略0x
let num2 = parseInt("10", 8); // 8,按八进制解析
String类型

1、字符字面量

字符字面量 含义
\n 换行
\t 制表
\b 退格
\r 回车
\f 换页
\\ 反斜杠
\‘ 单引号,在字符串以单引号标示时使用
\“ 双引号
\` 反引号
\xnn 以十六进制编码nn表示的字符,n是0-F
\unnnn 以十六进制编码nnnn表示的Unicode字符,n是0-F

2、字符串特点

字符串是不可变的(immutable)

let lan = "java";
lan = lan + "script";

整个过程首先会分配一个足够容纳 10 个字符的空间,然后填充上 “Java”和”Script”。最后销毁原始的字符串”Java”和字符串”Script”,因为这两个字符串都没有用 了。所有处理都是在后台发生的,而这也是一些早期的浏览器(如 Firefox 1.0 之前的版本和 IE6.0)在 拼接字符串时非常慢的原因。这些浏览器在后来的版本中都有针对性地解决了这个问题。

3、转换为字符串

toString(): null 和 undefined 值没有这个方法,数值、布尔值、对象和字符串值都有这个方法

let age = 11; 
let ageAsString = age.toString(); // 字符串"11" 
let found = true; 
let foundAsString = found.toString(); // 字符串"true" 
let num = 10; 
// 一般来说,toString不接收任何参数,不过,在对数值调用这个方法时,toString可以接收一个底数参数,即以什么底数来输出数值的字符串表示
console.log(num.toString()); // "10" 
console.log(num.toString(2)); // "1010" 
console.log(num.toString(8)); // "12" 
console.log(num.toString(10)); // "10" 
console.log(num.toString(16)); // "a"

String():如果不确定一个值是不是null或者undefined,可以使用该函数

  • 如果该值有toString方法,则会调用该方法并返回结果
  • 如果值是null,返回’null’
  • 如果值是undefined,返回’undefined’

+:用加号操作符给一个值加上一个空字符串” “也可以将其转换成字符串

4、模板字符串

ES6新增了使用模板字面量定义字符串的能力。与使用单引号或者双引号不同,模板字面量保留换行字符,可以跨行定义字符串:

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

模板字符串在定义模板时非常有用,比如下面的HTML模板:

let pageHTML = ` 
<div> 
 <a href="#"> 
 <span>Jake</span> 
 </a> 
</div>`; 

由于模板字面量会保持反引号内部的空格,因此在使用时需要格外注意

// 这个模板字面量在换行符之后有 25 个空格符
let myTemplateLiteral = `first line 
 second line`; 
console.log(myTemplateLiteral.length); // 47 
// 这个模板字面量以一个换行符开头
let secondTemplateLiteral = ` 
first line 
second line`; 
console.log(secondTemplateLiteral[0] === '\n'); // true 
// 这个模板字面量没有意料之外的字符
let thirdTemplateLiteral = `first line 
second line`; 
console.log(thirdTemplateLiteral); 
// first line 
// second line 

5、字符串插值

字符串插值通过在${}中使用一个 JavaScript 表达式实现,引号需要使用反引号

6、模板字面量标签函数

7、原始字符串

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

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"
Symbol类型

Symbol类型是ES6新增的数据类型,

1、基本使用

使用Symbol()函数初始化,因为符号本身就是原始类型,使用typeof操作符对符号返回symbol。

let genericSymbol = Symbol();

console.log(typeof genericSymbol);

最重要的是Symbol函数不能与new关键字一起作为构造函数使用。

let myBoolean = new Boolean();
console.log(myBoolean); // "object"

let mySymbol = new Symbol();
console.log(mySymbol); // TypeError:Symbol is not a constructor

2、使用全局符号注册表

。。。

Object类型

数据和功能的集合,ES中的Object是派生其他对象的基类,Object类型的属性和方法在派生的对象上同样存在。

constructor:用于创建当前对象的函数

hasOwnProperty(propertyName):用于判断当前对象实例上是否存在给定的属性

isPrototypeOf(object):用于判断当前对象是否为另一个对象的原型

propertyIsEnumerable(propertyName):用于判断给定的属性是否可以使用

toLocalString():返回对象的字符串表示,该字符串反映对象所在的本地化执行环境

toString():返回对象的字符串表示

valueof():返回对象对应的字符、数值、布尔值表示

操作符
一元运算符

++、–、+、-(放在变量前面表示正负,主要用于基本的算术,也可以用于数据类型转换)

位操作符

正值:以真正的二进制格式存储

负值:以补码的形式存储(正值的反码+1)

按位与、按位或…

布尔操作符
语句
for-in语句

它是一种严格的迭代语句,用于枚举对象中的非符号键属性,ES中对象的属性是无序的,因此for-in语句不能保证返回对象属性的顺序。

for-of语句

用于遍历可迭代对象的元素

with语句

将代码作用域设置为特定的对象,使用with语句的主要场景是针对一个对象反复操作

let qs = location.search.substring(1);
let hostName = lacation.hostname;
let url = location.href;

// 上述可以转换为
with(location) &#123; 
 let qs = search.substring(1); 
 let hostName = hostname; 
 let url = href; 
&#125; 

with语句用于连接location对象,这意味着在这个语句内部,每个变量首先会被认为是一个局部变量,如果没有找到局部变量,则会搜索location对象,看它是否有一个同名的属性。

严格模式下不允许使用with语句,否则会抛出错误,由于with语句影响性能且难于调试其中的代码,通常不推荐在产品代码中使用with语句

函数

ES中的函数不需要指定是否返回值,因为任何函数在任何时候返回任何值,不指定返回值的函数实际上会返回undefined。