一、变量-作用域-内存问题
1.1、基本类型 和 引用类型 的值
- 基本类型:指简单的数据段, 保存的变量中的 实际的 值
- 引用类型 :指可能由多个值构成的对象,保存在 内存中 的对象; 因此引用类型的值, 当复制保存对象的某个变量时,操作的是对象的 引用 ; 但在为对象添加属性时,操作的是 实际的对象
1.1.1、 动态的属性
定义 基本类型值 和 引用类型值 的方式类似: 创建变量、 为该变量赋值
注: 只能给 引用类型值 动态添加属性
1.1.2、复制变量值
基本类型:
从一个变量 向 另一个比变量 复制基本类型的值, 会在变量对象 上创建 一个新值,
然后把该值 复制的到为新变量分配的位置上。(复制后 两个变量值 对应的 5 是完全独立的)
引用类型:
从一个变量 向 另一个比变量 复制引用类型的值, 同样会将存储的变量对象中的值 复制一份 到新变量分配的空间中。 不同的是, 这个值的副本其实是一个 指针,而这个指针 指向存储在 堆中的一个对象
1.1.3、传递参数
ECMAScript 中所有函数的参数都是按 值 传递的。
基本类型:
引用类型:
1.1.4、检测类型
检测基本类型:
检测引用类型:
1.2、执行环境 及 作用域
执行环境(execution context, 简称 “环境”):
- 是 JavaScript 中重要的一个概念。执行环境定义了变量 或 函数 有权访问的其他数据,决定了它们各自的行为。
- 每个执行环境 都有一个与之关联的 变量对象, 环境中定义的所有变量和函数都保存在这个对象中。(Web浏览器中, 全局执行环境是 window 对象, 之后详解 )
每个函数都有自己的 执行环境。 (当执行流进入一个函数时,函数的环境就被推入一个环境栈中。 而在函数执行之后,栈将环境弹出, 把控制权返回给之前的执行环境。)
当代码在一个环境中执行时,会创建变量对象的一个 作用域链。
作用域链的用途: 保证对执行环境有权访问的所有变量 和 函数的 有序访问。
- 作用域链的前端, 始终是当前执行的代码所在环境的 变量对象。
- 如果这个环境是函数, 则将其活动对象 作为 变量对象。 活动对象在最开始时只包含一个变量, 即 arguments 对象(此对象在全局环境中是不存在的)。
- 作用域链中的下一个变量来自包含 (外部)环境, 而再下一个对象则来自下一个包含环境。就这样一直延续到全局执行环境; 全局执行环境的变量对象始终都是作用域链中的最后一个对象。
对于 swapColor() 环境而言, 包含三个 对象:
swapColor()的变量对象、 changeColor() 的变量对象 和 全局变量对象。
(搜索变量或函数时, 会先在直接的环境中搜索, 若搜不到 再搜索上一级作用域链。)
小结:
- 内部环境 可以通过作用域链 访问所有的外部环境; 但外部环境 不能访问内部环境 中的任何变量和函数;
- 环境直接的联系是 线性、有次序的;
- 每个环境都可以向上搜索作用域链, 以查询变量和函数名; 但 任何环境 都不能通过 向下 搜索作用域链 而进入另一个执行环境。
1.2.1、延长作用域链
方法:
try - catch 语句的 catch 块: 会创建一个新的变量对象,其中包含的时被抛出的错误对象的声明;
with 语句(不推荐使用):将指定的对象 添加到 作用域链中。
1.2.2、没有块级作用域
JavaScript 没有 块级作用域经常会导致理解上的困惑。 在其他类 Java 的语言中,由花括号封闭的代码块都有自己的作用域(如果用ECMAScript的话来讲,就是它们自己的执行环境), 因而支持根据条件来定义变量
如下图:
这里是在一个 if 语句中定义了变量 color 。 如果是在C、C++或Java中, color会在if语句执行完毕后被销毁。但在JavaScript中, if 语句中的变量声明会将变量添加到当前的执行环境(在这里是全局环境)中。
对于有块级作用域的语言来说(如 Java),for 语句初始化变量的表达式所定义的变量,只会存在于循环的环境之中。
【牢记!! ! 】而对于JavaScript来说,由 for 语句创建的变量 i 即使在for循环执行结束后,也依旧会存在于循环外部的执行环境中。
【声明变量:】
- 使用 var 声明的变量会自动被添加到最接近的环境中。
- 在函数内部,最接近的环境就是函数的局部环境; 在with语句中,最接近的环境是函数环境。
- 如果初始化变量时没有使用var声明,该变量会自动被添加到全局环境。
【查询标识符:】
- 当在某个环境中为T读取或写入而引用一个标识符时, 必须通过搜索来确定该标识符实际代表什么。
- 搜索过程从作用域链的前端开始,向上逐级查询与给定名字匹配的标识符。
- 如果在局部环境中找到了该标识符,搜索过程停止,变量就绪。
- 如果在局部环境中没有找到该变量名,则继续沿作用域链向上搜索。
- 搜索过程将一直追溯到全局环境的变量对象。
- 如果在全局环境中也没有找到这个标识符,则意味着该变量尚未声明。
在这个搜索过程中,如果存在一个局部的变量的定义,则搜索会自动停止,不再进入另一个变量对象。
即如果局部环境中存在着同名标识符,就不会使用位于父环境中的标识符
【区别】
即任何位于局部变量 color 的通过 var 声明之后的代码, 如果不使用window.color都无法访问全局color变量。
1.3、垃圾收集
JavaScript 具有自动垃圾收集机制,即 执行环境会负责管理代码执行过程中使用的内存。
原理:
找出那些不再继续使用的变量, 然后释放内存。
1.3.1、标记清除
JavaScript 最常用的垃圾收集方式是标记清除
原理:
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。
而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。
最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间
1.3.2、引用计数
不太常见的垃圾收集策略叫做引用计数( eferenee counting), 引用计数的含义是跟踪记录每个值被引用的次数。
此方法,容易导致循环引用的情况,从而出现内存泄漏现象。(如 element等DOM元素 和 Object 等类型数据,互相引用, element.someObject= object ; object.someProp =element )
1.3.3、性能问题
垃圾收集器是周期性运行的,而且如果为变量分配的内存数量很可观,那么回收工作量也是相当大的。
1.3.4、管理内存
使用具备垃圾收集机制的语言编写程序,开发人员一般不必操心内存管理的问题。
但是,JavaScript 在进行内存管理及垃圾收集时面临的问题还是有点与众不同。
其中最主要的一个问题, 就是分配给Web浏览器的可用内存数量通常要比分配给桌面应用程序的少。
这样做的目的主要是出于安全方面的考虑,目的是防止运行JavaScript的网页耗尽全部系统内存而导致系统崩溃。
内存限制问题不仅会影响给变量分配内存,同时还会影响调用栈以及在一个线 程中能够同时执行的语句数量。
因此,确保占用最少的内存可以让页面获得更好的性能。
而优化内存古用的最佳方式,就是为执行中的代码只保存必要的数据。
一旦数据不再有用,最好通过将其值设置为 null 来释放其引用——此做法叫 解除引用
不过,解除一个值的引用并不意味着自动回收该值所占用的内存 。
解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。
1.4、小结
JavaScript 变量可以用来保存两种类型的值:
基本类型值 和 引用类型值;
基本类型的值源自以下 6 种基本数据类型:
Undefined、 Null、 Boolean、 Number 、String和 symbol;
基本类型值和引用类型值具有以下特点:
- 基本类型值在内存中占据固定大小的空间,因此被保存在栈内存中;
- 从个变量向另-个变量 复制 基本类型的值,会创建这个值的一个副本;
- 引用类型的值是对象,保存在堆内存中;
- 包含引用类型值的变量实际上包含的并不是对象本身,而是个指向该对象的指针;
- 从一个变量向另一个变量 复制 引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象;
- 确定一个值是哪种引用类型可以使用 typeof 操作符, 而确定一个值是哪种引用类型可以使用 instanceof 操作符。
所有变量(包括基本类型和引用类型)都在于一个执行环境(也称为作用域)当中, 这个执行环境决定了变量的生命周期,以及哪一部分代码可以访问其中的变量。
以下是执行环境的几点总结:
- 执行环境有全局执行环境(也称为全局环境) 和 函数执行环境之分;
- 每次进人一个新执行环境,都会创建用于搜索变量 和 函数的作用域链;
- 函数的局部环境不仅有权访问的函数作用城中的变量,而且有权访问其包含(父)环境,乃至全局环境;
- 全局环境只能访问在全局环境中定义的变量和函数,而不能直接访问局部环境中的任何数据;
- 变量的执行环境有助于确定应该何时释放内存。
JavaScript 具有自动垃圾收集机制的编程语言, 开发人员不必关心内存分配和回收问题。
- 可以对JavaScript 的垃圾收集例程作如下总结:
- 离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除;
- 标记清除”是目前主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,然后再回收其内存;
- 另种垃圾收集算法 是"引用计数”,这种算法的思想是跟踪记录所有值被引用的次数。JavaScript引擎目前都不再使用这种算法,但在IE中访问非原生JavaScript对象(如DOM元素)时,这种算法仍然可能会导致问题。
- 当代码中存在循环引用现象时,“引用计数”算法就会导致问题;
- 解除变量的引用不仅有助消除循环引用现象,而且对垃圾收集也有好处,为了确保有效地回收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用。
二、JavaScript引用类型(Object类型、Array类型、Function类型等)等
2.1、引用类型 Object类型
创建方式:
1、 使用 new 操作符后跟 Object 构造函数: var person = new Object(); person.age = 12;
2、使用 对象字面量 表示法(常用): var person = { "age" : 12 }
2.2、引用类型 Array类型
ECMAScript 数组的大小可以动态调整的,即可以随着数据的添加自动增长以容纳新增数据。
创建方式(数组最多可以包含 4 294 967 295 个项):
1、使用 Array 构造函数: var colors = new Array(); var colors2 = new Array(20); // 设置数组长度为20
var colors3 = Array(3); // 省略new关键字 var color4 = Array('Tom'); // 包含一项
2、使用 数组字面量 表示法:var colors = ["red", "blue", "yellow"]
2.2.1、 检测数组 : instanceof、 Array.isArray()
2.2.2、转换方法: toLocaleString() 、 toString()、 valueOf()
2.2.3、栈方法:栈是一种 LIFO(Last-In-First-Out,后进先出)的数据结构,即最新添加的项最早被移除。
2.2.4、队列方法: 队列是一种 FIFO(First-In-First-Out,先进先出)的数据结构,即队列在列表末端添加项, 从列表的前端移除项。
2.2.5、重排序方法: reverse() 、 sort()
2.2.6、操作方法: concat()、 slice()、splice()
2.2.7、位置方法: indexOf()、 lastIndexOf()
2.2.8、迭代方法: every()、 filter()、forEach() 、map() 、some() ;
- 以上五个方法 都包含三个参数: 数组项的值、该项在数组中的位置 和 数组对象本身;
- 以上五个方法 都不会改变 原数组中 包含的值;
2.2.9、归并方法:reduce() 、reduceRight() ;
- reduce() 从数组的第一项开始,逐个遍历到最后;
- reduceRight() 从数组的最后一项开始,向前逐个遍历;
- 这两个方法都会迭代数组的所有项, 然后构建一个最终返回的值;
- 这两个方法接收的两个参数: 在每一项上调用的函数(前一个值、当前值、项的索引和数组对象;函数返回值作为第一个参数自动传给下一项) 和 作为归并基础的初始值;
2.3、引用类型 Date类型
创建日期对象: var date = new Date();
Date.parse() 接收表示日期的字符串参数
Date.UTC() 参数分别是: 年份、基于0的月份(一月是0,二月是1...)、月中的哪一天(1 到 31)、小时数(0 到 23)、分钟、秒 以及 毫秒数。
2.3.1、继承的方法
2.3.2、日期格式化方法
2.3.3、日期/时间组件方法,详细参考这里
2.4、引用类型 RegExp类型,参考这里
2.5、引用类型 Function类型
函数是对象,函数名是指针;
2.5.1、没有重载!!!
2.5.2、函数声明 和 函数表达式
2.5.3、作为值的函数: 将函数作为参数传递给另一个函数、 将一个函数作为另一个函数的结果返回
2.5.4、函数内部属性
- 两个特殊的对象: arguments 和 this
- argumenus.callee 属性是一个指针, 指向拥有argumenus 对象的函数;
- 函数名.caller 属性保持着调用当前函数的 函数的引用;
- this 引用的是函数执行的环境对象 (如当网页的全局作用域中调用函数时, this 对象引用的就是 window);
2.5.5、函数的属性和方法
两个属性: length 和 prototype
每个函数都包含以下方法:
- apply():两个参数,在其中运行函数的作用域、参数数组(Array的实例、arguments 对象);
- call():参数一是在其中运行函数的作用域、其余参数直接传递给函数;
- bind():IE9+以上浏览器可见,此方法会创建一个函数实例,其 this 值会被绑定到传给 bind() 函数的值;
2.6、引用类型 基本包装类型
3 种特殊引用类型: Boolean、Number 和 String
引用类型 和 基本包装类型的主要区别: 对象的生存期
2.6.1、Boolean 类型
2.6.2、Number 类型
2.6.3、String 类型: String 对象的方法也可以在所有基本的字符串中访问到。
2.6.3.1、字符方法: charAt() 和 charCodeAt()
2.6.3.2、字符串操作方法:concat() 、slice() 、 substr() 和 substring()
2.6.3.3、字符串位置方法: indexOf() 和 lastIndexOf()
2.6.3.4、 trim() 方法
2.6.3.5、字符串大小写转换方法: toLowerCase() 、toLocaleLowerCase() 、toUpperCase() 和 toLocaleUpperCase()
2.6.3.6、字符串的模式匹配方法: match() 、search() 、replace() 和 split()
注: replace() 方法的第二个参数也可以是一个函数: 该函数有三个参数(模式的匹配项、 模式匹配项在字符串中的位置 和 原始字符串 )
2.6.3.7、localCompare() 方法:比较两个字符串,并返回 正数/负数/0
2.6.3.8、fromCharCode() 方法: String构造函数本身的一个静态方法,这个方法任务是接收一 或 多个字符编码,任何将它们转换成一个字符串
2.7、引用类型 单体内置对象
如: Object 、 Array 和 String, 还有两个单体内置对象: Global 、 Math
2.7.1、 Global 对象
isNaN() 、 isFinite() 、parseInt() 和 parseFloat() 都是 Global 对象的方法
2.7.1.1、URI 编码方法
2.7.1.2、eval() 方法: 将传入的参数当作实际的 ECMAScript 语句解析, 然后把执行结果插入到原位置
2.7.1.3、Global 对象的属性
2.7.1.4、window 对象
- 在 web 浏览器都是讲全局对象作为 window 对象的一部分来实现。
- 因此,在全局作用域中声明的所有变量和函数,都成为了 window 对象的属性。
2.7.2、 Math 对象
2.7.2.1、Math 对象的属性
2.7.2.2、min() 和 max() 方法 , 用于确定一组数值中的最小值 和 最大值
2.7.2.3、舍入方法: Math.ceil() 、 Math.floor() 和 Math.round()
2.7.2.4、random() 方法: 返回 大于等于 0 小于 1 的一个随机数。( 0 =< x < 1 )
2.7.2.5、其他方法
2.8、引用类型 小结
对象在JavaScript中被称为引用类型的值,而且有一些内置的引用类型可以用,来创建特定的对象:
- 引用类型 与 传统面向对象程序设计中的类 相似,但实现不同;
- Object 是一个基础类型,其他所有类型都从 Object 继承了基本的行为;
- Array 类型是一 组值的有序列表,同时还提供了操作和转换这些值的功能;
- Date 类型提供了有关日期和时间的信息,包括当前日期和时间以及相关的计算功能;
- RegExp 类型是ECMASeript支持正则表达式的一个接口,提供了最基本的和一些高级的正则表达式功能。
函数实际上是Function类型的实例,因此函数也是对象; 而这一点正 是JavaScript 最有特色的地方。由于函数是对象,所以函数也拥有方法,可以用来增强其行为。
因为有了基本包装类型,所以JavaScript中的基本类型值可以被当作对象来访问。三种基本包装类型分别是: Boolean、 Number和String。以下是它们共同的特征:
- 每个包装类型都映射到同名的基本类型;
- 在读取模式下访问基本类型值时,就会创建对应的基本包装类型的一个对象,从而方便了数据操作;
- 操作基本类型值的语句一经执行完毕,就会立即销毁新创建的包装对象。
在所有代码执行之前,作用域中就已经存在两个内置对象: Global 和 Math。
在大多数ECMAScript实现中都不能直接访问Global对象;不过,Web浏览器实现了承担该角色的window对象。全局变量和函数都是Global对象的属性。Math对象提供了很多属性和方法,用于辅助完成复杂的数学计算任务。
写给自己的随笔,有问题欢迎指出(Θ▽Θ)