JavaScript 高级程序设计学习笔记
第 1 章.HTML 中的 JavaScript
1.1 <script 元素>
将 javascript 插入 HTML 的主要方式是使用<script>
<script>
元素有以下八个属性
async
:可选,表示立即开始下载脚本。只对外部脚本有效。不阻塞文档渲染,但是要注意异步脚本不一定是按顺序执行的了。charset
:可选,使用 src 属性置顶的代码字符集,很少用,大多数浏览器不在乎这个值。crossorigin
:可选,配置相关请求的 cors 跨域设置。默认不使用 cors。defer
:可选,表示页面文档解析完成再执行脚本。推迟的脚本原则上还是从上往下执行。intergrity
:可选,比对接收到的资源与指定的加密签名以验证子资源完整性。如果不匹配页面会报错,该属性可用于确保内容分发网站(CDN)不会提供恶意内容。(没用过这个)language
:废弃了,么得用src
:可选,表示包含要执行的代码的外部文件。经常用哦这个type
:可选,代替 language,表示代码块中脚本语言的内容类型。一般惯例是type="text/javascript"
。注意如果这个值是type="module"
,代码会被当成 ES6模块,而且只有这时候代码里才能出现import
与export
。
<script>
属性最为强大的一点就是,src 属性指向的 URL 可以跟包含它的 HTML 页面不在同一个域中。比如下例
<script src="http://www.somewhere.com/afile.js"></script>
浏览器解析时会向 src 发送一个 get 请求。这个初始的请求不受浏览器同源策略的限制,这就是解决跨域的方式之一(但并不提倡用)。
注意不管包含的是什么代码浏览器都会按照
<script>
在页面中的顺序依次解释它们(没有 defer 与 async 属性的情况下),所以一般我们把 script 放在 html 结构的最下方以免阻塞页面。
第二章.语言基础
2.1 变量
ECMAScript 的变量是松散类型,意味着变量可以用于保存任何类型的数据。声明变量有三种方式,var
、const
、let
。
2.1.1 var 关键字
2.1.1.1 var 声明作用域
var 声明的变量会成为包含它的函数的局部变量,如下代码
function fn1(){
var a = 1;
}
fn1();
console.log(a) // 报错
该变量在函数退出时被销毁。注意在函数内部定义变量时如果省略 var,会创建一个全局变量,不要这么做!因为全局变量很难维护。如下例
function fn1(){
var a = 1;
}
fn1()
console.log(a) // 1
2.1.1.2 var 声明提升
先看一段代码
function fn1(){
console.log(age);
var age = 26;
}
fn1(); // undefined
这里并没有报错,因为使用 var 声明的变量会自动提升到函数作用域顶部,那么这段代码其实相当于
function fn1(){
var age;
console.log(age);
age=26;
}
fn1();
2.1.2 let 声明
let 与 var 类似,但是最重要的区别是 let 声明的范围是块作用域,而 var 是函数作用域
比如下面这段代码可以明显看到 var 与 let 的区别
if(true){
var a = 1;
}
console.log(a) // 1
if(true){
let a = 1;
}
console.log(a) // 报错
2.1.2.1 暂时性死区
let 与 var 另一个重要的区别,就是 let 声明的变量不会在作用域中提升。所以在 let 声明之前的执行瞬间被称为暂时性死区,在此阶段引用后面用 let 声明的变量就会报错。如下
console.log(a); // ReferenceError:a没有定义
let a = 1;
2.1.2.2 全局声明
var 如果在全局作用域中声明,就会成为全局变量。而 let 就不会成为 window 对象的属性。但是 let 声明依然是在全局作用域中发生的,如果后面再 let 同一个变量依然会 SyntaxError.
var a = 1;
console.log(window.a); // 1
let b = 1;
console.log(window.b); // undefined
2.1.2.3 for 循环中的 let 声明
let 出现之前,for 循环定义的迭代变量会渗透到循环体外部
for (var i = 0; i<5: i++;){
//循环逻辑
}
console.log(i) // 5
使用 let 后这个问题迎刃而解,迭代变量的作用域仅限于 for 循环块内部。
for (var i = 0; i<5: i++;){
//循环逻辑
}
console.log(i) // 报错 i没有定义
使用 var 时常见的问题是对迭代变量的奇特声明和修改。
for (var i = 0; i<5: i++;){
setTimeout(()=>{
console.log(i)
},0)
}
// 输出的不是1,2,3,4,5
// 输出的是5,5,5,5,5
之所以会这样是因为退出循环时,迭代变量保存的是 5。在之后执行超时逻辑的时候,所有的 i 都是 5。
而 let 则不同。
for (let i = 0; i<5: i++;){
setTimeout(()=>{
console.log(i)
},0)
}
// 输出1,2,3,4,5
使用 let 声明迭代变量时,javascript 引擎在后台为每个迭代循环声明一个新的迭代变量。每个 setTimeout 引用的都是不同的变量实例。
这种每次迭代声明一个独立变量实例的行为适用于所有的 for 循环,包括
for-in
和for-of
。
2.1.3 const 声明
const
与let
基本一致,重要的区别是 const 的时候必须初始化变量。同时需要注意的是,const
声明不能修改的限制只适用于指向变量的引用,比如下面这种不修改引用地址的行为并不会报错。
const person = {}
person.name = 'scq' // 并不会报错
2.2 数据类型
简单数据类型(原始类型)有 6 种:Undefined
,Null
,Boolean
,Number
,String
,Symbol
,还有一种复杂数据类型叫Object
。
2.2.1 typeof 操作符
简单的来说,typeof
操作符可以鉴别出Undefined
,Boolean
,,Number
,String
,Symbol
这 5 种简单数据类型。而对于Object
与Null
都会返回Object
。因为特殊值 null 被认为是对一个空对象的引用。
2.2.2 Undefined
Undefined
类型只有一个值,就是 Undefined。当使用 var 或 let 声明但没有初始化的时候,就相当于变量被赋予了 Undefined。(个人认为 undefined 用的不多)
2.2.3 Null
Null 类型也只有一个值就是 Null。在定义将来要保存对象值的变量时,可以使用 Null。或者是在手动清理垃圾变量的时候可以赋值 Null。一般也用不到。
2.2.4 Boolean
布尔值,有两个字面值:true
和false
。需要注意,也容易混淆的是,true 不等于 1,false 不等于 0!
注意流控制语句如 if 等会自动执行其他类型值道布尔值的转换,比如我们经常如下写
if(a){
...
}
// 等价于
if(true){
...
}
2.2.5 Number
Number 类型的值使用 IEEE 754 格式表示整数与浮点值(双精度值)。不同的数据类型相应有不同的数值字面量格式,但是其他进制在实际中运用不多,这里先不记了。
2.2.5.1 浮点值
浮点值就是数值种 2 包含小数点,且小数点后面必须至少有一个数字。
由于存储浮点值使用的内存空间是整数值的两倍,所以 js 在小数点后没有数字的情况下会自动将浮点值转换为整数。如下两种
let num1 = 1.
let num2 = 10.0
对于非常大或者非常小的数值,浮点值可以用科学记数法表示。如3.125e7
就是 3125000
一个非常需要注意的地方是,浮点值的精度最高 17 位小数,但是在算数计算中远不如整数精准。如 0.1+0.2 并不是 0.3,而是 0.300000000000000004。永远不要做下面这个事情!
if(0.1 + 0.2 === 0.3){
console.log('0.3')
}
存在这种舍入错误是因为使用了 IEEE 754 数值,像 0.1+0.2 这样的操作对于计算机来说转换为二进制之后将是两个无限循环的数。而对于计算机而言是不允许有无限的,进行四舍五入之后双精度浮点数保留 52 位,结果为 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 转为十进制就是 0.30000000000000004。