免费学习推荐:javascript学习教程
一、什么是JavaScript
1-1、JavaScript实现
虽然JavaScript和ECMAScript基本上是同义词,但JavaScript远不限于ECMA-262所定义的那样。完整的JavaScript实现包含:
Web浏览器只是ECMAScript实现可能存在的一种宿主环境(hostenvironment)。宿主环境提供ECMAScript的基准实现和与环境自身交互必需的扩展。扩展(比如DOM)使用ECMAScript核心类型和语法,提供特定于环境的额外功能。其他宿主环境还有服务器端JavaScript平台Node.js和即将被淘汰的Adobe Flash。
在基本层面,描述这门语言:语法,类型,语句,关键字,保留字,操作符,全局对象
ECMAScript只是对实现这个规范描述的所有方面的一门语言的称
呼。JavaScript实现了ECMAScript,而Adobe ActionScript同样也实现了
ECMAScript。
1-2、DOM
文档对象模型(DOM,Document Object Model)是一个应用编程接口(API),用于在HTML中使用扩展的XML。DOM将整个页面抽象为一组分层节点。HTML或XML页面的每个组成部分都是一种节
点,包含不同的数据。
DOM通过创建表示文档的树,让开发者可以随心所欲地控制网页的内容和结构。使用DOM API,可以轻松地删除、添加、替换、修改节点。
1-3、BOM
IE3和Netscape Navigator 3提供了浏览器对象模型(BOM) API,用于支持访问和操作浏览器的窗口。使用BOM,开发者可以操控浏览器显示页面之外的部分。而BOM真正独一无二的地方,当然也是问题最多的地方,就是它是唯一一个没有相关标准的JavaScript实现。HTML5改变了这个局面,这个版本的HTML以正式规范的形式涵盖了尽可能多的BOM特性。由于HTML5的出现,之前很多与BOM有关的问题都迎刃而解了。
总体来说,BOM主要针对浏览器窗口和子窗口(frame),不过
人们通常会把任何特定于浏览器的扩展都归在BOM的范畴内。下面就是这样一些扩展:
二、HTML中的JavaScript
2-1、script
元素
将JavaScript插入HTML的主要方法是使用 script 元素。这
个元素是由网景公司创造出来,并最早在Netscape Navigator 2中实现
的。后来,这个元素被正式加入到HTML规范。 script 元素有下
列8个属性。
async
:可选。表示应该立即开始下载脚本,但不能阻止其他页面动作,比如下载资源或等待其他脚本加载。只对外部脚本文件有效。
charset :可选。使用 src 属性指定的代码字符集。这个属性很少使用,因为大多数浏览器不在乎它的值。
crossorigin :可选。配置相关请求的CORS(跨源资源共享)设置。默认不使用CORS。crossorigin=“anonymous” 配置文件请求不必设置凭据标志。 crossorigin=“use-credentials” 设置凭据标志,意
味着出站请求会包含凭据。
defer :可选。表示在文档解析和显示完成后再执行脚本是没有问题的。只对外部脚本文件有效。在IE7及更早的版本中,对行内脚本也可以指定这个属性。
integrity :可选。允许比对接收到的资源和指定的加密签名以验证子资源完整性(SRI,Subresource Intergrity)。如果接收到的资源的签名与这个属性指定的签名不匹配,则页面会报错,脚本不会执行。这个属性可以用于确保内容分发网络(CDN,Content Delivery Network)不会提供恶意内容。
language :废弃。最初用于表示代码块中的脚本语言(如 “JavaScript” 、 “JavaScript1.2” 或 “VBScript” )。大多数浏览器都会忽略这个属性,不应该再使用它。
src :可选。表示包含要执行的代码的外部文件。
type :可选。代替 language ,表示代码块中脚本语言的内容类型(也称MIME类型)。
包含在 script 内的代码会被从上到下解释
2-2、
使用了 src 属性的 script 元素不应该再在 script 和 /script 标签中再包含其他JavaScript代码。如果两者都提供的话,则浏览器只会下载并执行脚本文件,从而忽略行内代码。
script 元素的一个最为强大、同时也备受争议的特性是,它可以包含来自外部域的JavaScript文件。跟 img 元素很像, script 元素的 src 属性可以是一个完整的URL,而且这个URL指向的资源可以跟包含它的HTML页面不在同一个域中
2-3、文档模式
可以使用 doctype 切换文档模式。最初的文档模式有两种:混杂模式(quirks mode)和标准模式(standards mode)。前者让IE像IE5一样(支持一些非标准的特性),后者让IE具有兼容标准的行为。虽然这两种模式的主要区别只体现在通过CSS渲染的内容方面,但对JavaScript也有一些关联影响,或称为副作用。本书会经常提到这些副作用。
IE初次支持文档模式切换以后,其他浏览器也跟着实现了。随着浏览器的普遍实现,又出现了第三种文档模式:准标准模式(almost
standards mode)。这种模式下的浏览器支持很多标准的特性,但是没有标准规定得那么严格。主要区别在于如何对待图片元素周围的空白(在表格中使用图片时最明显)。
混杂模式在所有浏览器中都以省略文档开头的 doctype 声明作为开关。这种约定并不合理,因为混杂模式在不同浏览器中的差异非常大,不使用黑科技基本上就没有浏览器一致性可言。
2-4、noscript 元素
针对早期浏览器不支持JavaScript的问题,需要一个页面优雅降级的处理方案。最终, 元素出现,被用于给不支持JavaScript的浏览器提供替代内容。虽然如今的浏览器已经100%支持JavaScript,但对于禁用JavaScript的浏览器来说,这个元素仍然有它的用处。
元素可以包含任何可以出现在 中的HTML元素,
三、语法基础
3-1、语法
首先要知道的是,ECMAScript中一切都区分大小写。无论是变量、函数名还是操作符,都区分大小写。换句话说,变量 test 和变量 Test 是两个不同的变量。类似地, typeof 不能作为函数名,因为它是一个关键字(后面会介绍)。但 Typeof 是一个完全有效的函数名。
3-2、标识符
所谓标识符,就是变量、函数、属性或函数参数的名称。标识符可以由一或多个下列字符组成:
标识符中的字母可以是扩展ASCII(Extended ASCII)中的字母,也可以是Unicode的字母字符,如À和Æ(但不推荐使用)。
按照惯例,ECMAScript标识符使用驼峰大小写形式,即第一个单词的首字母小写,后面每个单词的首字母大写
3-3、严格模式
ECMAScript 5增加了严格模式(strict mode)的概念。严格模式是一种不同的JavaScript解析和执行模型,ECMAScript 3的一些不规范写法在这种模式下会被处理,对于不安全的活动将抛出错误
3-4、关键字与保留字
ECMA-262描述了一组保留的关键字,这些关键字有特殊用途,比如表示控制语句的开始和结束,或者执行特定的操作
ECMA-262第6版规定的所有关键字:
break do in typeof case else instanceof var catch export new void class extends return while const finally super with continue for switch yield debugger function this default if throw delete import try
规范中也描述了一组未来的保留字,同样不能用作标识符或属性名。虽然保留字在语言中没有特定用途,但它们是保留给将来做关键字用的
3-5、变量
ECMAScript变量是松散类型的,意思是变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意值的命名占位符。有3个关键字可以声明变量: var 、 const 和 let 。其中, var 在CMAScript的所有版本中都可以使用,而 const 和 let 只能在ECMAScript 6及更晚的版本中使用
3-6、var关键字
var声明作用域
关键的问题在于,使用 var 操作符定义的变量会成为包含它的函数的局部变量。比如,使用 var 在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁
var 声明提升
使用 var 时,下面的代码不会报错。这是因为使用这个关键字声明的变量会自动提升到函数作用域顶部
3-7、let 声明
let 跟 var 的作用差不多,但有着非常重要的区别。最明显的区别是, let 声明的范围是块作用域,而 var 声明的范围是函数作用域。
暂时性死区
let 与 var 的另一个重要的区别,就是 let 声明的变量不会在作用域中被提升。
全局声明
与 var 关键字不同,使用 let 在全局作用域中声明的变量不会成为 window 对象的属性( var 声明的变量则会)
条件声明
在使用 var 声明变量时,由于声明会被提升,JavaScript引擎会自动将多余的声明在作用域顶部合并为一个声明。因为 let 的作用域是块,所以不可能检查前面是否已经使用 let 声明过同名变量,同时也就不可能在没有声明的情况下声明它
for 循环中的 let 声明
在 let 出现之前, for 循环定义的迭代变量会渗透到循环体外部
const 声明
const 的行为与 let 基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改 const 声明的变量会导致运行时错误。
3-8、声明风格及最佳实践
ECMAScript 6增加 let 和 const 从客观上为这门语言更精确地声明作用域和语义提供了更好的支持。行为怪异的 var 所造成的各种问题,已经让JavaScript社区为之苦恼了很多年。随着这两个新关键字的出现,新的有助于提升代码质量的最佳实践也逐渐显现。
四、数据类型
ECMAScript有6种简单数据类型(也称为原始类型):Undefined 、 Null 、 Boolean 、 Number 、 String 和Symbol 。 Symbol (符号)是ECMAScript 6新增的。还有一种复杂数据类型叫 Object (对象)。 Object 是一种无序名值对的集合。因为在ECMAScript中不能定义自己的数据类型,所有值都可以用上述7种数据类型之一来表示。只有7种数据类型似乎不足以表示全部数据。但ECMAScript的数据类型很灵活,一种数据类型可以当作多种数据类型来使用
4-1、typeof 操作符
因为ECMAScript的类型系统是松散的,所以需要一种手段来确定任意变量的数据类型。 typeof 操作符就是为此而生的。对一个值使用 typeof 操作符会返回下列字符串之一:
“undefined” 表示值未定义;
“boolean” 表示值为布尔值;
“string” 表示值为字符串;
“number” 表示值为数值;
“object” 表示值为对象(而不是函数)或 null ;
“function” 表示值为函数;
“symbol” 表示值为符号。
4-2、Undefined 类型
Undefined 类型只有一个值,就是特殊值 undefined 。当使用 var 或 let 声明了变量但没有初始化时,就相当于给变量赋予了 undefined 值
即使未初始化的变量会被自动赋予 undefined 值,但我们仍然建议在声明变量的同时进行初始化。这样,当 typeof 返回 “undefined” 时,你就会知道那是因为给定的变量尚未声明,而不是声明了但未初始化。
4-3、Null 类型
Null 类型同样只有一个值,即特殊值 null 。逻辑上讲,null 值表示一个空对象指针,这也是给 typeof 传一个 null 会返回 “object” 的原因
在定义将来要保存对象值的变量时,建议使用 null 来初始化,不要使用其他值。这样,只要检查这个变量的值是不是 null 就可以知道这个变量是否在后来被重新赋予了一个对象的引用
用等于操作符( == )比较 null 和 undefined 始终返回true 。但要注意,这个操作符会为了比较而转换它的操作数。
即使 null 和 undefined 有关系,它们的用途也是完全不一样的。如前所述,永远不必显式地将变量值设置为 undefined 。但null 不是这样的。任何时候,只要变量要保存对象,而当时又没有那个对象可保存,就要用 null 来填充该变量。这样就可以保持null 是空对象指针的语义,并进一步将其与 undefined 区分开来。
null 是一个假值。因此,如果需要,可以用更简洁的方式检测它。不过要记住,也有很多其他可能的值同样是假值。所以一定要明确自己想检测的就是 null 这个字面值,而不仅仅是假值
4-4、Boolean 类型
Boolean (布尔值)类型是ECMAScript中使用最频繁的类型之一,有两个字面值: true 和 false 。这两个布尔值不同于数值,因此 true 不等于1, false 不等于0
注意:布尔值字面量 true 和 false 是区分大小写的,因此True 和 False (及其他大小混写形式)是有效的标识符,但不是布尔值。
4-5、Number 类型
ECMAScript中最有意思的数据类型或许就是 Number 了。Number 类型使用IEEE 754格式表示整数和浮点值(在某些语言中也叫双精度值)。不同的数值类型相应地也有不同的数值字面量格式
浮点值
要定义浮点值,数值中必须包含小数点,而且小数点后面必须至
少有一个数字。虽然小数点前面不是必须有整数,但推荐加上。
值的范围
由于内存的限制,ECMAScript并不支持表示这个世界上的所有数值。ECMAScript可以表示的最小数值保存在Number.MIN_VALUE 中,这个值在多数浏览器中是5e-324;可以表示的最大数值保存在 Number.MAX_VALUE 中,这个值在多数浏览器中是1.797 693 134 862 315 7e+308。如果某个计算得到的数值结果超出了JavaScript可以表示的范围,那么这个数值会被自动转换为一个特殊的 Infinity (无穷)值。任何无法表示的负数以 -Infinity (负无穷大)表示,任何无法表示的正数以 Infinity (正无穷大)表示。如果计算返回正 Infinity 或负 Infinity ,则该值将不能再进一步用于任何计算。这是因为 Infinity 没有可用于计算的数值表示形式。要确定一个值是不是有限大(即介于JavaScript能表示的最小值和最大值之间),可以使用 isFinite() 函数
NaN
有一个特殊的数值叫 NaN ,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。比如,用0除任意数值在其他语言中通常都会导致错误,从而中止代码执行。但在ECMAScript中,0、+0或-0相除会返回NaN
数值转换
有3个函数可以将非数值转换为数值: Number() 、parseInt() 和 parseFloat() 。 Number() 是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。对于同样的参数,这3个函数执行的操作也不同。
4-6、 NaN
有一个特殊的数值叫 NaN ,意思是“不是数值”(Not aNumber),用于表示本来要返回数值的操作失败了(而不是抛出错误)。
4-7、数值转换
有3个函数可以将非数值转换为数值: Number() 、 parseInt() 和 parseFloat() 。 Number() 是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。对于同样的参数,这3个函数执行的操作也不同。Number() 函数基于如下规则执行转换。布尔值, true 转换为1, false 转换为0。
数值,直接返回。null ,返回0。 undefined ,返回 NaN 。
4-8、String 类型
String (字符串)数据类型表示零或多个16位Unicode字符序列。字符串可以使用双引号(")、单引号(’)或反引号(`)标示,
字符字面量
字符串数据类型包含一些字符字面量,用于表示非打印字符或有其他用途的字符
字符串的特点
ECMAScript中的字符串是不可变的(immutable),意思是一旦创建,它们的值就不能变了。要修改某个变量中的字符串值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量
转换为字符串
有两种方式把一个值转换为字符串。首先是使用几乎所有值都有的 toString() 方法。这个方法唯一的用途就是返回当前值的字符串等价物
模板字面量
ECMAScript 6新增了使用模板字面量定义字符串的能力。与使用单引号或双引号不同,模板字面量保留换行字符,可以跨行定义字符串
字符串插值
模板字面量最常用的一个特性是支持字符串插值,也就是可以在一个连续定义中插入一个或多个值。技术上讲,模板字面量不是字符串,而是一种特殊的JavaScript句法表达式,只不过求值后得到的是字符串。模板字面量在定义时立即求值并转换为字符串实例,任何插入的变量也会从它们最接近的作用域中取值
模板字面量标签函数
模板字面量也支持定义标签函数(tag function),而通过标签函数可以自定义插值行为。标签函数会接收被插值记号分隔后的模板和对每个表达式求值的结果。标签函数本身是一个常规函数,通过前缀到模板字面量来应用自定义行为,
原始字符串
使用模板字面量也可以直接获取原始的模板字面量内容(如换行符或Unicode字符),而不是被转换后的字符表示。为此,可以使用默认的 String.raw 标签函数
五、操作符
ECMA-262描述了一组可用于操作数据值的操作符,包括数学操作符(如加、减)、位操作符、关系操作符和相等操作符等。ECMAScript中的操作符是独特的,因为它们可用于各种值,包括字符串、数值、布尔值,甚至还有对象。在应用给对象时,操作符通常会调用 valueOf() 和 / 或 toString() 方法来取得可以计算的值。3.5.1 一元操作符只操作一个值的操作符叫一元操作符(unary operator)。一元操作符是ECMAScript中最简单的操作符。
5-1、一元操作符
2.一元加和减
一元加和减操作符对大多数开发者来说并不陌生,它们在ECMAScript中跟在高中数学中的用途一样。一元加由一个加号( + )表示,放在变量前头,对数值没有任何影响
5-2、位操作符
按位非
按位非操作符用波浪符( ~ )表示,它的作用是返回数值的一补数。按位非是ECMAScript中为数不多的几个二进制数学操作符之一
按位与
按位与操作符用和号( & )表示,有两个操作数。本质上,按位与就是将两个数的每一个位对齐,然后基于真值表中的规则,对每一位执行相应的与操作。
按位或
按位或操作符用管道符( | )表示,同样有两个操作数。
按位异或
按位异或用脱字符( ^ )表示,同样有两个操作数
左移
左移操作符用两个小于号( << )表示,会按照指定的位数将数值的所有位向左移动。比如,如果数值2(二进制10)向左移5位,就会得到64(二进制1000000)
有符号右移
有符号右移由两个大于号( >> )表示,会将数值的所有32位都向右移,同时保留符号(正或负)。有符号右移实际上是左移的逆运算
无符号右移
无符号右移用3个大于号表示( >>> ),会将数值的所有32位都向右移。对于正数,无符号右移与有符号右移结果相同
5-3、布尔操作符
逻辑非
逻辑非操作符由一个叹号( ! )表示,可应用给ECMAScript中的任何值。这个操作符始终返回布尔值,无论应用到的是什么数据类型。逻辑非操作符首先将操作数转换为布尔值,然后再对其取反
逻辑与
逻辑与操作符由两个和号( && )表示,应用到两个值
逻辑或
逻辑或操作符由两个管道符( || )表示
5-4、乘性操作符
乘法操作符
乘法操作符由一个星号( * )表示,可以用于计算两个数值的乘积。
除法操作符
除法操作符由一个斜杠( / )表示,用于计算第一个操作数除以第二个操作数的商
取模操作符
六、语句
ECMA-262描述了一些语句(也称为流控制语句),而ECMAScript中的大部分语法都体现在语句中。语句通常使用一或多个关键字完成既定的任务。语句可以简单,也可以复杂。简单的如告诉函数退出,复杂的如列出一堆要重复执行的指令。
6-1、do-while 语句
do-while 语句是一种后测试循环语句,即循环体中的代码执
行后才会对退出条件进行求值。换句话说,循环体内的代码至少执行
一次
6-2、while 语句
while 语句是一种先测试循环语句,即先检测退出条件,再执行循环体内的代码。因此, while 循环体内的代码有可能不会执行
七、函数
函数对任何语言来说都是核心组件,因为它们可以封装语句,然后在任何地方、任何时间执行。ECMAScript中的函数使用function 关键字声明,后跟一组参数,然后是函数体。
ECMAScript中的函数不需要指定是否返回值。任何函数在任何时间都可以使用 return 语句来返回函数的值,用法是后跟要返回的值
函数 sum() 会将两个值相加并返回结果。注意,除了 return语句之外没有任何特殊声明表明该函数有返回值
严格模式对函数也有一些限制:
函数不能以 eval 或 arguments 作为名称;
函数的参数不能叫 eval 或 arguments ;
两个函数的参数不能叫同一个名称。
八、变量,作用域与内存
4-1、原始值与引用值
ECMAScript变量可以包含两种不同类型的数据:原始值和引用值。原始值(primitive value)就是最简单的数据,引用值(referencevalue)则是由多个值构成的对象。
在把一个值赋给变量时,JavaScript引擎必须确定这个值是原始值还是引用值。上一章讨论了6种原始值: Undefined 、 Null 、Boolean 、 Number 、 String 和 Symbol 。保存原始值的变量是按值(by value)访问的,因为我们操作的就是存储在变量中的实际值。
引用值是保存在内存中的对象。与其他语言不同,JavaScript不允许直接访问内存位置,因此也就不能直接操作对象所在的内存空间。在操作对象时,实际上操作的是对该对象的引用(reference)而非实
际的对象本身。为此,保存引用值的变量是按引用(by reference)访问的
注意:在很多语言中,字符串是使用对象表示的,因此被认为是引用类型。ECMAScript打破了这个惯例。
动态属性
原始值和引用值的定义方式很类似,都是创建一个变量,然后给它赋一个值。不过,在变量保存了这个值之后,可以对这个值做什么,则大有不同。对于引用值而言,可以随时添加、修改和删除其属性和方法
复制值
除了存储方式不同,原始值和引用值在通过变量复制时也有所不同。在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置。
在按值传递参数时,值会被复制到一个局部变量(即一个命名参数,或者用ECMAScript的话说,就是 arguments 对象中的一个槽位)。在按引用传递参数时,值在内存中的位置会被保存在一个局部变量,这意味着对本地变量的修改会反映到函数外部
按照定义,所有引用值都是 Object 的实例,因此通过instanceof 操作符检测任何引用值和 Object 构造函数都会返回 true 。类似地,如果用 instanceof 检测原始值,则始终会返回 false ,因为原始值不是对象。
注意 typeof 操作符在用于检测函数时也会返回 “function” 。当在Safari(直到Safari 5)和Chrome(直到Chrome 7)中用于检测正则表达式时,由于实现细节的原因,typeof 也会返回 “function” 。ECMA-262规定,任何实现内部 [[Call]] 方法的对象都应该在 typeof 检测时返回 “function” 。因为上述浏览器中的正则表达式实现了这个方法,所以 typeof 对正则表达式也返回 “function” 。在IE和Firefox中, typeof 对正则表达式返回 “object” 。
4-2、执行上下文与作用域
执行上下文(以上简称“上下文”)的概念在JavaScript中是颇为重要的。变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。每个上下文都有一个关联的变量对象(variable object),而
这个上下文中定义的所有变量和函数都存在于这个对象上。虽然无法通过代码访问变量对象,但后台处理数据会用到它。
全局上下文是最外层的上下文。根据ECMAScript实现的宿主环境,表示全局上下文的对象可能不一样。在浏览器中,全局上下文就是我们常说的 window 对象(第12章会详细介绍),因此所有通过
var 定义的全局变量和函数都会成为 window 对象的属性和方法。使用 let 和 const 的顶级声明不会定义在全局上下文中,但在作用域链解析上效果是一样的。上下文在其所有代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数(全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器)。
每个函数调用都有自己的上下文。当代码执行流进入函数时,函数的上下文被推到一个上下文栈上。在函数执行完之后,上下文栈会弹出该函数上下文,将控制权返还给之前的执行上下文。ECMAScript
程序的执行流就是通过这个上下文栈进行控制的。上下文中的代码在执行的时候,会创建变量对象的一个作用域链(scope chain)。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。代码正在执行的上下文的变量对象始终位于作用域链的最前端。如果上下文是函数,则其活动对象(activationobject)用作变量对象。活动对象最初只有一个定义变量:arguments 。(全局上下文中没有这个变量。)作用域链中的下一个变量对象来自包含上下文,再下一个对象来自再下一个包含上下文。以此类推直至全局上下文;全局上下文的变量对象始终是作用域链的最后一个变量对象
作用域链增强
虽然执行上下文主要有全局上下文和函数上下文两种( eval()调用内部存在第三种上下文),但有其他方式来增强作用域链。某些语句会导致在作用域链前端临时添加一个上下文,这个上下文在代码
执行后会被删除。
变量声明
ES6之后,JavaScript的变量声明经历了翻天覆地的变化。直到
ECMAScript 5.1, var 都是声明变量的唯一关键字。ES6不仅增加了
let 和 const 两个关键字,而且还让这两个关键字压倒性地超越
var 成为首选。
使用 var 的函数作用域声明
在使用 var 声明变量时,变量会被自动添加到最接近的上下文。在函数中,最接近的上下文就是函数的局部上下文。在with 语句中,最接近的上下文也是函数上下文。如果变量未经声明就被初始化了,那么它就会自动被添加到全局上下文注意:未经声明而初始化变量是JavaScript编程中一个非常常见的错误,会导致很多问题。为此,读者在初始化变量之前一定要先声明变量。在严格模式下,未经声明就初始化变量会报错
var 声明会被拿到函数或全局作用域的顶部,位于作用域中所有代码之前。这个现象叫作“提升”(hoisting)。提升让同一作用域中的代码不必考虑变量是否已经声明就可以直接使用。可是在实践中,提升也会导致合法却奇怪的现象,即在变量声明之前使用变量。
使用 let 的块级作用域声明
ES6新增的 let 关键字跟 var 很相似,但它的作用域是块级的,这也是JavaScript中的新概念。块级作用域由最近的一对包含花括号 {} 界定。换句话说, if 块、 while 块、 function块,甚至连单独的块也是 let 声明变量的作用域。
使用 const 的常量声明
除了 let ,ES6同时还增加了 const 关键字。使用 const 声明的变量必须同时初始化为某个值。一经声明,在其生命周期的任何时候都不能再重新赋予新值
注意 开发实践表明,如果开发流程并不会因此而受很大影响,就应该尽可能地多使用 const 声明,除非确实需要一个将来会重新赋值的变量。这样可以从根本上保证提前发现重新赋值导致的bug
4-3、垃圾回收
JavaScript是使用垃圾回收的语言,也就是说执行环境负责在代码执行时管理内存。在C和C++等语言中,跟踪内存使用对开发者来说是个很大的负担,也是很多问题的来源。JavaScript为开发者卸下了这个负担,通过自动内存管理实现内存分配和闲置资源回收。基本思路很简单:确定哪个变量不会再使用,然后释放它占用的内存。这个过程是周期性的,即垃圾回收程序每隔一定时间(或者说在代码执行过程中某个预定的收集时间)就会自动运行。垃圾回收过程是一个近似且不完美的方案,因为某块内存是否还有用,属于“不可判定的”问题,意味着靠算法是解决不了的。
我们以函数中局部变量的正常生命周期为例。函数中的局部变量会在函数执行时存在。此时,栈(或堆)内存会分配空间以保存相应的值。函数在内部使用了变量,然后退出。此时,就不再需要那个局部变量了,它占用的内存可以释放,供后面使用。这种情况下显然不再需要局部变量了,但并不是所有时候都会这么明显。垃圾回收程序必须跟踪记录哪个变量还会使用,以及哪个变量不会再使用,以便回收内存。如何标记未使用的变量也许有不同的实现方式。不过,在浏览器的发展史上,用到过两种主要的标记策略:标记清理和引用计数。
标记清理
JavaScript最常用的垃圾回收策略是标记清理(mark-and-sweep)。当变量进入上下文,比如在函数内部声明一个变量时,这个变量会被加上存在于上下文中的标记。而不在上下文中的变量,逻辑上讲,永远不应该释放它们的内存,因为只要上下文中的代码在运行,就有可能用到它们。当变量离开上下文时,也会被加上离开上下文的标记。
给变量加标记的方式有很多种。比如,当变量进入上下文时,反转某一位;或者可以维护“在上下文中”和“不在上下文中”两个变量列表,可以把变量从一个列表转移到另一个列表。标记过程的实现并不重要,关键是策略。
垃圾回收程序运行的时候,会标记内存中存储的所有变量(记住,标记方法有很多种)。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存。
到了2008年,IE、Firefox、Opera、Chrome和Safari都在自己的JavaScript实现中采用标记清理(或其变体),只是在运行垃圾回收的频率上有所差异。
引用计数
另一种没那么常用的垃圾回收策略是引用计数(referencecounting)。其思路是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为1。如果同一个值又被赋给另一个变量,那么引用数加1。类似地,如果保存对该值引用的变量被其他值给覆盖了,那么引用数减1。当一个值的引用数为0时,就说明没办法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序
下次运行的时候就会释放引用数为0的值的内存。引用计数最早由Netscape Navigator 3.0采用,但很快就遇到了严重的问题:循环引用。所谓循环引用,就是对象A有一个指针指向对象B,而对象B也引用了对象A。
待更新。。。
相关免费学习推荐:javascript(视频)
以上是记录JavaScript的学习笔记的详细内容。更多信息请关注PHP中文网其他相关文章!