首页 > 其他 > 详细

类型、值和变量

时间:2019-10-19 11:06:35      阅读:40      评论:0      收藏:0      [点我收藏+]

数字

JavaScript不区分整数和浮点数,所有数字都用浮点数表示。

  • 能够表示最大值是 -253 ~ 253,包含边界。超过范围的数无法保证低位数字的精度。
  • JavaScript能直接识别十进制的整型直接量和十六进制值(以0x0X为前缀,由0~9a(A)~f(F)构成,如:0xff,即15 * 16 + 15 = 255)。ECMAScript标准不支持八进制直接量。
  • 浮点数直接量表达式:[digits][.digits][(E|e)(+|-)digits],如:3.12E12 => 3.1212, 浮点数值的最高精度是 17 位小数
  • 算数运算符:加+、减-、乘*、除/、取余%
  • JavaScript中算数运算在溢出、下溢(无限接近于零并比JavaScript能表示的最小值还小的数,JavaScript将会返回0)或被零整除时不会报错(返回正(或负)无穷,例外:零除以零,结果是一个非数字,用NaN表示)。溢出时,结果为无穷大(Infinity)值;下溢结果为负无穷(-Infinity)。基于无穷大的加减乘除运算结果还是无穷大(保留正负号)。
  • NaN表示非数字值,它和任何值都不相等,包括自身。判断一个值是否为NaN:x != x,当切只当x为NaN时,表达式为true。

    函数isNaN(),如果参数为NaN或者非数字则返回true;函数isFinite(),在参数不是NaNInfinity-Infinity的时候返回true

  • JavaScript采用了IEEE-754浮点数表示法(几乎所有现代编程语言所采用),这是一种二进制表示法,可以精确的表示分数,如:1/2、1/8和1/1024。但在现实生活中通常用十进制分数,1/10、1/100等。二进制表达式具有足够的精度,但并不能精确的表示类似0.1这样的简单数字。要避免这些舍入问题(特别时金融领域),尽量使用大整数进行计算,例如:乘以相同的倍数,结果再除以相同倍数
var x = .3 - .2;
var y = .2 - .1;
x == y // false
x == .1 //false
y == .1 // true
// 由于舍入误差,0.3与0.2的近似差值并不等于0.2与0.1的近似差值
var a = (.3 * 10 - .2 * 10) / 10
var b = (.2 * 10 - .1 * 10) / 10
a == b // true
a == .1 // true
b == .1 // true

除了以十进制表示外,整数还可以通过八进制(以 8 为基数)或十六进制(以 16 为基数)的字面值 来表示。其中,八进制字面值的第一位必须是零(0),然后是八进制数字序列(0~7)。如果字面值中的 数值超出了范围,那么前导零将被忽略,后面的数值将被当作十进制数值解析。

var octalNum1 = 070; // 八进制的 56
var octalNum2 = 079; // 无效的八进制数值——解析为 79
var octalNum3 = 08;  // 无效的八进制数值——解析为 8

!> 八进制字面量在严格模式下是无效的,会导致支持的 JavaScript 引擎抛出错误。

十六进制字面值的前两位必须是 0x,后跟任何十六进制数字(0~9 及 A~F)。其中,字母 A~F 可以大写,也可以小写。如下面的例子所示:

var hexNum1 = 0xA; // 十六进制的 10
var hexNum2 = 0x1f; // 十六进制的 31

在进行算术计算时,所有以八进制和十六进制表示的数值最终都将被转换成十进制数值。

NaN

NaN,即非数值(Not a Number)是一个特殊的数值,这个数值用于表示一个本来要返回数值的操作数,未返回数值的情况。其有两个特点:

  1. 任何涉及 NaN 的操作(例如 NaN/10)都会返回 NaN
  2. NaN 与任何值都不相等,包括 NaN 本身

ECMAScript 定义了 isNaN()函数。其会尝试 将接收到的值转换为数值,若能转为数值则返回false,否则返回true。

alert(isNaN(NaN)); //true
alert(isNaN(10)); //false(10 是一个数值)
alert(isNaN("10"));  //false(可以被转换成数值 10)
alert(isNaN("blue")); //true(不能转换成数值)
alert(isNaN(true)); //false(可以被转换成数值 1)

数值转换

有 3 个函数可以把非数值转换为数值:Number()、parseInt()和 parseFloat()。

Number()

如果是 Boolean 值,true 和 false 将分别被转换为 1 和 0。
如果是数字值,只是简单的传入和返回。
如果是 null 值,返回 0。
如果是 undefined,返回 NaN。
如果是字符串,遵循下列规则:
    如果字符串中只包含数字(包括前面带正号或负号的情况),则将其转换为十进制数值,即"1" 会变成 1,"123"会变成 123,而"011"会变成 11(注意:前导的零被忽略了);
    如果字符串中包含有效的浮点格式,如"1.1",则将其转换为对应的浮点数值(同样,也会忽 略前导零);
    如果字符串中包含有效的十六进制格式,例如"0xf",则将其转换为相同大小的十进制整 数值;
    如果字符串是空的(不包含任何字符),则将其转换为 0;
    如果字符串中包含除上述格式之外的字符,则将其转换为 NaN。
如果是对象,则调用对象的 valueOf()方法,然后依照前面的规则转换返回的值。如果转换的结果是 NaN,则调用对象的 toString()方法,然后再次依照前面的规则转换返回的字符串值。

!> 一元加操作符的操作与 Number()函数相同。

parseInt()

parseInt()函数在转换字符串时,更多的是看其是否符合数值模式。它会忽略字 符串前面的空格,直至找到第一个非空格字符。如果第一个字符不是数字字符或者负号,parseInt() 就会返回 NaN;也就是说,用 parseInt()转换空字符串会返回 NaN(Number()对空字符返回 0)。如 果第一个字符是数字字符,parseInt()会继续解析第二个字符,直到解析完所有后续字符或者遇到了 一个非数字字符。
如果字符串中的第一个字符是数字字符,parseInt()也能够识别出各种整数格式(即前面讨论的 十进制、八进制和十六进制数)。也就是说,如果字符串以"0x"开头且后跟数字字符,就会将其当作一 5 个十六进制整数;如果字符串以"0"开头且后跟数字字符,则会将其当作一个八进制数来解析。

var num1 = parseInt("1234blue"); // 1234
var num2 = parseInt(""); // NaN
var num3 = parseInt("0xA"); // 10(十六进制数)
var num4 = parseInt(22.5); // 22
var num5 = parseInt("070"); // 56(八进制数)
var num6 = parseInt("70"); // 70(十进制数)
var num7 = parseInt("0xf"); // 15(十六进制数

!> ECMAScript 5 中,即使是在非严格 模式下parseInt()已经不具有解析八进制值的能力,为此转换时可以提供第二个参数--使用的基数(即多少进制)。

var mum8 = parseInt("0xAF", 16); // 175
// 或
var mum9 = parseInt("AF", 16); // 175
var num10 = parseInt("070", 6); // 0

parseFloat()

parseFloat()也是从第一个字符(位置 0)开始解析每个字符。而且 也是一直解析到字符串末尾,或者解析到遇见一个无效的浮点数字字符为止。需要注意的点:

  1. 字符串中的第一个小数点是有效的,而第二个小数点就是无效的了,因此它后面的字符串将被忽略。举例来说, "22.34.5"将会被转换为 22.34。
  2. 始终都会忽略前导 的零。parseFloat()可以识别前面讨论过的所有浮点数值格式,也包括十进制整数格式。但十六进制格 式的字符串则始终会被转换成 0。由于 parseFloat()只解析十进制值,因此它没有用第二个参数指定基 数的用法。
  3. 如果字符串包含的是一个可解析为整数的数(没有小数点,或者小数点后 都是零),parseFloat()会返回整数。
var num1 = parseFloat("1234blue");  //1234(整数)
var num2 = parseFloat("0xA");   //0
var num3 = parseFloat("22.5"); //22.5
var num4 = parseFloat("22.34.5"); //22.34
var num5 = parseFloat("0908.5"); //908.5
var num6 = parseFloat("3.125e7"); //31250000

字符串

JavaScript的字符直接量,是由单引号或双引号括起来的字符序列。ECMAScript 中的字符串是不可变的,也就是说,字符串一旦创建,它们的值就不能改变。要改变 某个变量保存的字符串,首先要销毁原来的字符串,然后再用另一个包含新值的字符串填充该变量

  • ES3中,字符串直接量必须写在一行中;ES5可以拆分数行,行间必须以反斜线(\)分割
var a = "nice to meet you";
console.log(a); // nice to meet you
  • 转义字符\,避免常规的字符串解析,转义符号。

!> 如果\字符位于没有在下表中的字符前,则忽略\

代码 输出
\o NUL字符
\‘ 单引号
\" 双引号
\& 和号
\\ 反斜杠
\n 换行符
\r 回车符
\t 水平制表符
\v 垂直制表符
\b 退格符
\f 换页符
\xXX 由两位十六进制数XX指定的Latin-1字符
\uXXXX 由4位十六进制数XXXX指定的Unicode字符
var a = 'you\'re right' // you're right

转换为字符串

要把一个值转换为一个字符串有两种方式:

  1. 第一种是使用几乎每个值都有的 toString()方法

!> 除nullundefined无toString方法外(调用toString方法会报错),几乎所有的js对象、变量都支持toString方法

特别的,数值在调用toString方法时可以传递一个参数:输出数值的基数)。默认情况下toString方法以十进制格式返回字符串标识,传递基数可以输出任意有效进制格式的字符串值。

var num = 10;
alert(num.toString()); // "10"
alert(num.toString(2)); // "1010"
alert(num.toString(8)); // "12"
alert(num.toString(10)); // "10"
alert(num.toString(16)); // "a"

在不知道要转换的值是不是 null 或 undefined 的情况下,还可以使用转型函数 String(),这个 函数能够将任何类型的值转换为字符串。String()函数遵循下列转换规则:

如果值有 toString()方法,则调用该方法(没有参数)并返回相应的结果;
如果值是 null,则返回"null";
如果值是 undefined,则返回"undefined"。
// 数值和布尔值的转换结果与调用 toString()方法得到的结果相同。
String(10) // "10"
String(true) // "true"
// null 和 undefined 没有 toString()方法,所以 String() 函数就返回了这两个值的字面量。
String(undefined) // "undefined"
String(null) // "null"
let a = "test", b;
a + b = "testundefined"

!> tips: 将某个值转为字符串,可以使用加号操作符,即将其与一个空串(‘‘)加在一起

字符串方法

详见MDN字符串

!> 在JavaScript中字符串时固定不变的,类似replace()toUpperCase()的方法都返回新字符串,原字符串本身并没有发生改变

在ES5中,字符串可以当作只读数组,除了使用charAt()方法,也可以使用方括号来访问字符串中的单个字符(16位值):

s = "hello"
s[0] // => 'h'
s[s.length - 1] // => o

布尔值

布尔值指代真或假、开或关、是或否。这个类型只有两个值,保留字true和false。
任意的Javascript的值都可以转换位布尔值,下面这些值或被转换成false:

false
undefined
null
0
-0
NaN
"" //空字符串

其他所有值,包括所有对象(数组)都会转换成true。JavaScript期望使用一个布尔值时,会将自动转换

布尔运算符

&& // 与
|| // 或
! // 非

nullundefined

Undefined 类型只有一个值,即特殊的 undefined。在使用 var 声明变量但未对其加以初始化时,这个变量的值就是 undefined,例如:

    var message; // 这个变量声明之后默认取得了 undefined 值
    // 下面这个变量并没有声明
     // var age
    alert(message);
    alert(age);
    // "undefined" // 产生错误

由于 undefined 并不是 JavaScript 的关键字,所以我们在赋值某个变量为 undefined 时可能会有点意想不到的结果。

function t(){
    var undefined = 10;
    console.log(undefined);
}
t(); // 大多数浏览器下都是10

如上代码我们可能希望赋值为 undefined,但却得到了 10 这个莫名其妙的情况。所以我们可以使用使用 void 替换 undefined。void 运算符对给定的表达式进行求值,然后返回 undefined。

Null 类型类型只有一个值,即特殊的 null,从逻辑角度来看,null 值表 示一个空对象指针,而这也正是使用 typeof 操作符检测 null 值时会返回"object"的原因。早期的js只有null,第 3 版引入undefined是为了正式区分空对象指针与未经初始化的变量。

特殊的:

null == undefined // true,undefined 值是派生自 null 值的,因此 ECMA-262 规定对它们的相等性测试要返回 true,这其中 == 做了隐式转换

null === undefined // false

Object类型

ECMAScript 中的对象其实就是一组数据和功能的集合。创建对象的方式:

let a = new Object()
let b = new Object // 如果不给构造函数传递参数,则可 以省略后面的那一对圆括号。不推荐

在 ECMAScript 中, (就像 Java 中的 java.lang.Object 对象一样)Object 类型是所有它的实例的基础。换句话说, 7
Object 类型所具有的任何属性和方法也同样存在于更具体的对象中。Object 的每个实例都具有下列属性和方法。

constructor:保存着用于创建当前对象的函数。对于前面的例子而言,构造函数(constructor) 8 就是 Object()。
hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中(而不是在实例 的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定(例 如:o.hasOwnProperty("name"))。
isPrototypeOf(object):用于检查传入的对象是否是传入对象的原型(第 5 章将讨论原 型)。
propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用 for-in 语句 (本章后面将会讨论)来枚举。与 hasOwnProperty()方法一样,作为参数的属性名必须以字符
串形式指定。
toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应。
toString():返回对象的字符串表示。
valueOf():返回对象的字符串、数值或布尔值表示。通常与 toString()方法的返回值相同。

全局对象

全局对象的属性时全局定义的符号,JavaScript程序可以直接使用。当JavaScript解释器启动时(或者任何浏览器加载新页面的时候),他将创建一个新的全局对象,并给他一组定义的初始属性:

  • 全局属性,如:undefinedInfinityNaN
  • 全局函数,如:isNaN()parseInt()eval()
  • 构造函数,如:Date()RegExp()String()Object()Array()
  • 全局对象,如:MathJSON

全局对象的初始属性并不是保留字,但他们应该当作保留字来对待。如果代码声明了一个全局变量,那么这个变量就是全局对象的一个属性。

包装对象

JavaScript对象是一种复合值:它是属性及已命名值的集合。通过.(或[])来引用属性值。当属性值是一个函数的时候,称其为方法。如:

JSON['parse'](`{"a":1}`)
JSON.parse(`{"a":1}`)

存取字符串、数字或布尔值的属性时创建的临时对象称作包装对象。

let str = 'test'
str.length = 1
console.log(str.length); // 4
str.len = 4; // 创建了一个临时字符串对象`new String(str)`,并给其`len`属性赋值,随机销毁这个对象
var t = str.len; // 通过原始的(没有被修改过)字符串值创建一个新的字符串对象,并尝试读取其`len`属性,这个值自然不存在,表达式结果为undefined
console.log(t); // undefined

!> 对于基本类型来说,如果使用字面量的方式,那么这个变量只是个字面量,只有在必要的时候才会转换为对应的临时对象(nullundefined没有包装对象),一旦引用结束,这个新创建的临时对象就会被销毁
需要注意的是,可以通过String()Number()Boolean()构造函数来显示的创建包装对象:

var a = new String('abc');
a.len = 9;
console.log(a.len); // 9

不可变的原始值和可变的对象引用

JavaScript的值可分为:

  • 原始值(undefinednullbooleannumberstring):存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。(js中的valueOf())。原始值是不可更改的:任何方法都无法更改一个原始值。原始值的比较是值的比较:只有他们的值相等时它们才相等。
  • 引用值(数组、对象等):存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存处。引用值可以变更:通过修改其属性;引用值的比较均是引用的比较:当且仅当它们引用同一个基对象时才相等。

类型转换

JavaScript会根据需要自行转换类型

字符串 数字 布尔值 对象
undefined "undefined" NaN false throw TypeError
null "null" 0 false throw TypeError
true "true" 1 new Boolean(true)
false "false" 0 new Boolean(false)
""(空串) 0 false new String("")
"2.3" 2.3 true new String("2.3")
"test" NaN true new String("test")
0 "0" false new Number(0)
-0 "0" false new Number(-0)
NaN "NaN" false new Number(NaN)
Infinity "Infinity" true new Number(Infinity)
-Infinity "-Infinity" true new Number(-Infinity)
1 "1" true new Number(1)
{} 见下方对象转化为原始值 见下方对象转化为原始值 true
[] "" 0 true
[1] "1" 1 true
[‘a‘] 使用join()方法 NaN true
function(){} NaN true
  • 除了nullundefined之外任何值都有toString()方法,这个方法的执行结果通常和String()方法一致
  • 所有的对象(包括数组和函数)进行bool转换时都转换为true,例:!!new Boolean(false),结果为true

对象转化为原始值

!> 对象到字符串和对象到数字的转换是通过调用待转换对象的一个方法完成的。Javascript对象有两个不同的方法来执行转换。

所有的对象继承了两个转换方法。

  1. 第一个是toString(),它的作用是返回一个反映这个对象的字符串。
    JavaScript中的本地对象往往定义了自己的toString()方法, 如:
toString()实现方式 示例
普通对象 返回类型 ({a:1}).toString() ==> "[object Object]"
数组类(Array class) 将每个数组元素转换为一个字符串,并用,连接 [1,2,3].toString() ==> "1,2,3"
函数类(Function class) 返回这个函数的实现定义的表示方式,即将用户定义的函数转换为JavaScript源代码字符串 (function() {console.log(1)}).toString() ==> "function() {console.log(1)}"
日期类(Date class) 返回一个可读的(可被Javascript解析的)日期和时间字符串 (new Date(2019, 5, 31)).toString ==> "Mon Jul 01 2019 00:00:00 GMT+0800 (中国标准时间)"
RegExp类(RegExp class) 返回表示正则表达式直接量的字符串 /\d+/g.toString() ==> "/\d+/g"
  1. 另一个转换对象的函数是valueOf()。这个方法的任务并未详细定义:如果存在任意原始值,它就将默认对象转换为表示它的原始值。对象是复合值,而且大多数对象无法真正表示为一个原始值,因此默认的valueOf()方法简单地返回对象本身,而不是返回一个原始值。

数组、函数和正则表达式简单地继承了这个默认的方法,调用这些类型的实例的valueOf()方法只是简单返回对象本身。
日期类定义的valueOf()方法会返回它的内部表示:1970年1月1日以来的毫秒数

(new Date(2019, 5, 31)).valueOf() // => 1561910400000

JavaScript中对象到字符串的转换过程

  1. 如果对象具有toString()方法,则调用这个方法。如果它返回一个原始值,JavaScript将这个值转换为字符串(如果本身不是字符串),并返回这个字符串结果。
  2. 如果对象没有toString()方法,或者这个方法并不返回一个原始值,那么Javascript会调用valueOf()方法。如果存在这个方法,则JavaScript调用它。如果返回值是原始值,JavaScript将这个值转换为字符串(如果本身不是字符串),并返回这个字符串结果。
  3. 否则,JavaScript无法从toString()valueOf()获得一个原始值,这时他将抛出一个类型错误异常

JavaScript中对象到数字的转换过程

对象到数字的转换过程与对象到字符串的转换过程类似,只是他会首先尝试使用valueOf()方法:

  1. 如果对象具有valueOf()方法,若后者返回一个原始值,则JavaScript将这原始值转换为数字(如果需要的话),并返回这个数字
  2. 否则,如果对象具有toString()方法,若后者返回一个原始值,则JavaScript将其转换为数字并返回
  3. 否则,抛出一个类型错误异常

示例

示例:空数组转换为数字0

+[] // => 0

示例:包含一个数字元素的数组转换为数字,结果为这个数字

+[100] // => 100

解释:数组继承了默认的valueOf()方法,这个方法返回一个对象而不是一个原始值,因此继续调用toString()方法;空数组转换为空字符串,空串再转换为数字0。同理,包含100的数组转换为字符串"100",
再转换为数字100

示例:

[] + [] // => '',在这个过程中先调用valueOf()方法,返回的数组的原始值还是一个数组,再调用toString()方法,即为:"" + "" =""
[1,2] + 1 // => '1,21', [1,2] 先调用valueOf(),返回的还是数组本身,在调用toString(),返回字符串"1,2",则 "1,2" + 1 ="1,21"
[1] + 1 // => 2
1 + {a:1} // => "1[object Object]"
{a:1} +1  // => 1, 这是因为js在解释代码时遇到{}会认为是一个代码块(代码区域),{a:1}已经结束的代码块,因此相当于原式=      +1   //  1
new Number(1) == 1 // => true

二原运算符+,如果其中一个操作数是对象,则JavaScript将使用特殊的方法(日期对象先尝试调用toString(),再尝试调用valueOf()
只用得到原始值则直接使用,而不会进一步转换为数字或字符串;除日期对象外,则先尝试调用valueOf(),然后调用toString(),同样的也不会进一步转换为数字或字符串)
将其转换为原始值进行运算;==!=及关系运算符与此相似,如果将一个对象同一个原始值比较,则转换遵照对象到原始值的转换方式进行;
示例:

let now = new Date();
now + 1 // "Fri May 31 2019 17:10:20 GMT+0800 (中国标准时间)1"
now - 1 // 1562034278023
now == now.toString() // true
now > (now - 1) // true
(!+[]+[]+![]).length // 9
(!(~+[]) + {})[--[~+""][+[]] * [~+[]] + ~~!+[]] + ({} + [])[[~!+[]] * ~+[]] // => sb
let a = {};
a.toString = () => 2
a.valueOf = () => 3
1 + a // 4
'1' + a // '13'

JavaScript真值表

技术分享图片

变量声明

var a;
var b;

var a, b;

var a = 1, b = 2;
var c = 3;

es6中的letconst 用法类似

  • var声明变量可重复用var声明, 但letconst 不可重复定义
  • 声明一个全局变量,实际上是定义了全局对象的一个属性,不可以通过delete删除
  • es5严格模式下,给一个没有声明的变量赋值会报错,在非严格模式下,给一个未声明的变量赋值,JavaScript
    实际上会给全局对象创建一个同名属性,并且像一个正确声明全局变量工作(但并不完全一样,这个变量是全局对象的可配值属性,可以通过delete删除)。
aa = 1;
var bb = 2;
delete aa; // => true
delete bb; // => false

变量作用域

  • 全局变量:在全局定义的变量,作用域是全局
  • 局部变量:在函数体內定义的变量,作用域是局部性的

函数作用域的创建步骤:

  1. 函数形参的声明。
  2. 函数变量的声明,函数变量会覆盖以前声明过的同名声明。
  3. 普通变量的声明。
  4. 函数内部的this指针赋值
  5. 函数内部代码开始执行!

!> 1. 作用域是一个函数在执行时期的执行环境,每一个函数在执行的时候都有着其特有的执行环境。

  1. 变量的作用域遵循就近原则,即局部变量优先级高于全局变量;局部变量的会优先选取最近的函数作用域;若在整个作用域链都未找到变量,则抛出引用错误(ReferenceError)异常。

声明提前(变量提升)

JavaScript的函数作用域是指在函数內声明的所有变量(使用var声明的变量)在函数体內始终是可见的。这意味着变量在声明之前甚至已经可用。
这个特性被称为声明提前(hoisting),即JavaScript函数里声明的所有变量(但不涉及赋值)都被"提前"至函数体的顶部
!> 声明提前,这步操作是在JavaScript引擎"预编译"时进行的,是在代码开始运行之前

作用域链(scope chain)

因为作用域是一个函数在执行时期的执行环境,每一个函数在执行的时候都有着其特有的执行环境。而在JS中,函数的可以允许嵌套的。即,在一个函数的内部声明另一个函数。
这种函数作用域的嵌套就组成了所谓的函数作用域链。当在自身作用域内找不到该变量的时候,会沿着作用域链逐步向上查找,若在全局作用域内部仍找不到该变量,则会抛出异常。

作用域链与闭包

Javascript采用词法作用域,也就是说,函数的执行以来于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。Javascript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域內,这种特性在计算机科学文献中称为"闭包"。
从技术角度讲所有的Javascript函数都是闭包:它们都是对象,它们都关联到作用域。简单的理解闭包:函数定义时的作用域链到函数执行时依然有效。

垃圾回收

Javascript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。其原理:找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间)
,周期性的执行这一操作。通常使用的策略有:标记清除、引用计数。

标记清除

垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后被加上标记的变量将视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。
到 2008 年为止,IE、Firefox、Opera、Chrome 和 Safari 的 JavaScript 实现使用的都是标记清除式的 垃圾收集策略(或类似的策略),只不过垃圾收集的时间间隔互有不同。

引用计数

引用计数的含义是跟踪记录每 个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是 1。 如果同一个值又被赋给另一个变量,则该值的引用次数加 1。相反,如果包含对这个值引用的变量又取 得了另外一个值,则这个值的引用次数减 1。当这个值的引用次数变成 0 时,则说明没有办法再访问这 个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那 些引用次数为零的值所占用的内存。
引用计数策略有一个严重的问题:循环引用。循环引用指的是对象 A 中包含一个指向对象 B 的指针,而对象 B 中也包含一个指向对象 A 的 引用。例:

function problem(){
    var objectA = new Object();
    var objectB = new Object();
    objectA.someOtherObject = objectB;
    objectB.anotherObject = objectA;
}

objectA 和 objectB 通过各自的属性相互引用;也就是说,这两个对象的引用次 数都是 2。在采用标记清除策略的实现中,由于函数执行之后,这两个对象都离开了作用域,因此这种 相互引用不是个问题。但在采用引用计数策略的实现中,当函数执行完毕后,objectA 和 objectB 还 将继续存在,因为它们的引用次数永远不会是 0。假如这个函数被重复多次调用,就会导致大量内存得 不到回收。

内存优化

确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行 3 中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为null来释放其引用——这个 做法叫做解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象的属性。局部变量只在函数执行的过程中存在。而在 这个过程中,会为局部变量在栈(或堆)内存上分配相应的空间,以便存储它们的值。然后在函数中使用这些变量,直至函数执行结束。此时,局部变量就没有存在的必要了,因此可以释放它们的内存以供将来使用。

类型、值和变量

原文:https://www.cnblogs.com/fanlinqiang/p/11703080.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!