第3章 语言基础
第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') {
let name;
}
// name 被限制在 if {} 块的作用域内
// 因此这个赋值形同全局赋值
name = 'Matt';
try {
console.log(age); // 如果 age 没有声明过,则会报错
}
catch(error) {
let age;
}
// age 被限制在 catch {}块的作用域内
// 因此这个赋值形同全局赋值
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 = {};
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) {
// 这个块不会执行
}
if (!message) {
// 这个块会执行
}
if (age) {
// 这里会报错
}
null类型
null值表示一个空对象指针,这也是给typeof传一个null会返回object的原因。
在定义将来要保存对象值的变量时,建议使用null来初始化,不要使用其他值,这样,只要检查这个变量的值是不是null就可以知道这个变量是否在后来被重新赋予了一个对象的引用。
undefined值是由null值派生来的,null == undefined (true)null是一个假值,因此,如果需要,可以用更简洁的方式检测它。不过要记住,也有很多其他可 能的值同样是假值。所以一定要明确自己想检测的就是 null 这个字面值,而不仅仅是假值。
let message = null;
let age; // age现在是undefined
if (message) {
// 这个块不会执行
}
if (!message) {
// 这个块会执行
}
if (age) {
// 这个块不会执行
}
if (!age) {
// 这个块会执行
}
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) {
let qs = search.substring(1);
let hostName = hostname;
let url = href;
}
with语句用于连接location对象,这意味着在这个语句内部,每个变量首先会被认为是一个局部变量,如果没有找到局部变量,则会搜索location对象,看它是否有一个同名的属性。
严格模式下不允许使用with语句,否则会抛出错误,由于with语句影响性能且难于调试其中的代码,通常不推荐在产品代码中使用with语句
函数
ES中的函数不需要指定是否返回值,因为任何函数在任何时候返回任何值,不指定返回值的函数实际上会返回undefined。