JavaScript笔记(三)
读书笔记
《JavaScript高级程序设计(第4版)》
语言基础
JavaScript是通过<script>元素插入到HTML页面中的。
- 要包含外部JavaScript文件,必须将src属性设置为要包含文件的URL。文件可以跟网页在同一台服务器上,也可以位于完全不同的域。
- 所有<script>元素会依照它们在网页中出现的次序被解释。在不使用defer和async属性的情况下,包含在<script>元素中的代码必须严格按次序解释。
- 对不推迟执行的脚本,浏览器必须解释完位于<script>元素的代码,然后才能继续渲染页面的剩余部分。为此,通常应该把<script>元素放到页面末尾,介于主内容之后及</body>标签之前。
- 可以使用defer属性把脚本推迟到文档渲染完毕后再执行。推迟的脚本原则上按照它们被列出的次序执行。
- 可以使用async属性表示脚本不需要等待其他脚本,同时也不阻塞文档渲染,即异步加载。异步脚本不能保证按照它们在页面中出现的次序执行。
- 通过使用<noscript>元素,可以指定在浏览器不支持脚本时显示的内容。如果浏览器支持并启用脚本,则<noscript>元素中的任何内容都不会被渲染。
内容概要
3.1 语法
3.1.1 区分大小写
ECMAScript中一切都区分大小写。
3.1.2 标识符
标识符:变量、函数、属性或函数参数的名称。
- 第一个字符必须是一个字母、下划线(_)或美元符号($);
- 剩下的其他字符可以是字母、下划线、美元符号或数字。
注意:关键字、保留字、true、false和null不能作为标识符。
3.1.3 注释
ECMAScript采用C语言风格的注释,包括单行注释和块注释。
- // :单行注释
- /* 注释 */ :多行注释
3.1.4 严格模式
严格模式(strict mode)是一种不同的JavaScript解析和执行模型,ECMAScript3的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误。
要对整个脚本启用严格模式,在脚本开头加上这一行:
"use strict";
这是一个预处理指令。任何支持的JavaScript引擎看到它都会切换到严格模式。
也可以单独指定一个函数在严格模式下执行,只要把这个预处理指令放到函数体开头即可:
function doSomething() {
"use strict";
//函数体
}
3.1.5 语句
ECMAScript中的语句以分号结尾。
3.1.6 关键字与保留字
保留的关键字不能用作标识符或属性名。
3.3 变量
ECMAScript变量是松散类型的,意思是变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意值的命名占位符。
var在ECMAScript的所有版本中都可以使用,
const和let只能在ECMAScript6及更晚的版本中使用。
3.3.1 var关键字
- var声明作用域
使用var操作符定义的变量会成为包含它的函数的局部变量。
如果需要定义多个变量,可以在一条语句中用逗号分隔每个变量(及可选的初始化):
var message = "hi",
found = false,
age = 29;
因为ECMAScript是松散类型的,所以使用不同数据类型初始化的变量可以用一条语句来声明。插入换行和空格缩进并不是必需的,但这样有利于阅读理解。
> 在严格模式下,不能定义名为eval和arguments的变量,否则会导致语法错误。
- var声明提升
使用var时,使用这个关键字声明的变量会自动提升(hoist)到函数作用域顶部。
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
3.3.2 let声明
let和var的作用差不多,但有着非常重要的区别。
let声明的范围是块作用域,而var声明的范围是函数作用域。
if(true) {
var name = 'Matt';
console.log(name); //Matt
}
console.log(name); //Matt
if(true) {
let age = 26;
console.log(age); //26
}
console.log(age); //ReferenceError: age没有定义
let也不允许同一个块作用域中出现冗余声明。这样会导致报错:
var name;
var name;
let age;
let age; //SyntaxError:标识符age已经声明过了
-
暂时性死区
let声明的变量不会在作用域中被提升。在let声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出ReferenceError。 -
全局声明
与var关键字不同,使用let在全局作用域中声明的变量不会成为window对象的属性(var声明的变量则会)。var name = 'Matt'; console.log(window.name); //'Matt' let age = 26; console.log(window.age); //undefined
加粗样式let声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续。因此,为了避免SyntaxError,必须确保页面不会重复声明同一个变量。
-
条件声明
由于var声明会被提升,JavaScript引擎会自动将多余的声明在作用域顶部合并为一个声明。因为let的作用域是块,所以不可能检查前面是否已经使用let声明过同名变量,同时也就不可能在没有声明的情况下声明它。<script> var name = 'Nicholas'; let age = 26; </script> <script> var name = 'Matt'; //不需要检查之前是否声明过同名变量 let age = 36; //如果age之前声明过,这里会报错 </script>
-
for循环中的let声明
在let出现之前,for循环定义的迭代变量会渗透到循环体外部:for(var i=0;i<5;++i){ //循环逻辑 } console.log(i); //5
改成使用let之后,这个问题就消失了, 因为迭代变量的作用域仅限于for循环块内部:
for(let i=0;i<5;++i){ //循环逻辑 } console.log(i); //ReferenceError:i没有定义
在使用let声明迭代变量时,JavaScript引擎在后台会为每个迭代循环声明一个新的迭代变量。
3.3.3 const声明
const的行为与let基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误。
const age = 36;
age = 36; //TypeError:给常量赋值
//const也不允许重复声明
const name = 'Matt';
const name = 'Nicholas'; //SyntaxError
//const声明的作用域也是块
const name = 'Matt';
if(true){
const name = 'Nicholas';
}
const.log(name); //Matt
const声明的限制只适用于它指向的变量的引用。换句话说,如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制。
const person = {
};
person.name = 'Matt'; //ok
JavaScript引擎会为for循环中的let声明分别创建独立的变量实例,虽然const变量跟let变量很相似,但是不能用const来声明迭代变量(因为迭代变量会自增):
for(const i=0;i<10;++i){
} //TypeError:给常量赋值
可以用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
3.3.4声明风格及最佳实践
- 不使用var
只使用let和const有助于提升代码质量,因为变量有了明确的作用域、声明位置,以及不变的值。 - const优先,let次之
使用const声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。只在提前知道未来会有修改时,再使用let。
3.4 数据类型
ECMAScript有6种简单数据类型(原始类型):Undefined、Null、Boolean、Number、String和Symbol。1种复杂数据类型:Object(对象)。Object是一种无序名值对的集合。
3.4.1 typeof操作符
确定任意变量的数据类型。对一个值使用typeof操作符会返回下列字符串之一:
- “undefined”:值未定义
- “boolean”:值为布尔值
- “string”:值为字符串
- “number”:值为数值
- “object”:值为对象(而不是函数)或null
- “function”:值为函数
- “symbol”:值为符号
特殊值null被认为是一个对空对象的引用。
let message = "some string";
console.log(typeof messsage); //"string"
console.log(typeof(message)); //"string"
console.log(typeof 95); //"number"
注意:严格来讲,函数在ECMAScript中被认为是对象,并不代表一种数据类型。函数也有自己特殊的属性,通过typeof操作符来区分函数和其他对象。
3.4.2 Undefined类型
Undefined类型只有一个值,就是特殊值undefined。当使用var或let声明了变量但没有初始化时,就相当于给变量赋予了undefined值:
let message;
console.log(message == undefined); //true
在默认情况下,任何未经初始化的变量都会取得undefined值。
3.4.3 Null类型
Null类型同样只有一个值,即特殊值null。逻辑上讲,null值表示一个空对象指针,这也是给typeof传一个null会返回"object"的原因:
let car = null;
console.log(typeof car); //"object"
在定义将来要保存对象值的变量时,建议使用null来初始化,不要使用其他值。
if(car != null){
//car是一个对象的引用
}
undefined值是由null值派生而来的,因此ECMA-262将它们定义为表面上相等。
console.log(null == undefined); //true
永远不必显式地将变量值设置为 undefined。但 null 不是这样的。任何时候,只要变量要保存对象,而当时又没有那个对象可保存,就要用 null 来填充该变量。这样就可以保持 null 是空对象指针的语义,并进一步将其与 undefined 区分开来。
null 是一个假值。也有很多其他可能的值同样是假值。所以一定要明确自己想检测的就是 null 这个字面值,而不仅仅是假值。
let message = null;
let age;
if (message) {
// 这个块不会执行
}
if (!message) {
// 这个块会执行
}
if (age) {
// 这个块不会执行
}
if (!age) {
// 这个块会执行
}
3.4.4 Boolean 类型
布尔值字面量 true 和 false 是区分大小写的
要将一个其
他类型的值转换为布尔值,可以调用特定的 Boolean()转型函数:
let message = "Hello world!";
let messageAsBoolean = Boolean<