目录

类型、值和变量

Javascript类型可以分为两大类:原始类型和对象类型。JavaScript原始类型包括数字、字符串、布尔值、null和undefined。null和undefined是它们自己类型的唯一成员。任何不是原始类型的Javascript值都是一个对象。每个对象都是类型Object的成员,由一组属性的集合组成,每个属性有一个名字和一个值。

一个普通的JavaScript 对象是一组无序的键值集合。JavaScript还定义了一种特殊的对象,数组,代表有序的编号的值。JavaScript还定义了另一种特殊的对象,函数。函数在Javascript里面是值。用来初始化新创建的对象的函数称为构造函数。每个构造函数定义了一类对象。类可以认为是对象的子类型。除了Array和Function类,Javascript核心定义了三种其他有用的类。Date、RegExp、Error 。

JavaScript解释器自动执行垃圾回收。当一个对象不再可访问时,解释器自动回收它占有的内存。

JavaScript是一门面向对象的语言。只有JavaScript对象拥有方法,数字、字符串和布尔值表现得好像它们有方法。在JavaScript里面,只有null和undefined没有方法。

number string boolean null undefined object
isPrimitive Yes Yes Yes Yes Yes No
hasMethod Yes Yes Yes No No Yes
mutable No No No No No Yes

JavaScript自由地转换一种类型为另外一种。当程序期待某种类型的时候,它会将其他类型自动转换为需要的类型。

JavaScript变量是无类型的,你可以将任意类型的值赋给一个变量。JavaScript使用词法域,定义在函数之外的变量在任何地方可见,在函数中定义的变量只在函数中可见。

数字

JavaScript中所有数字都是使用IEEE 754标准表示的64位浮点数。 JavaScript数字格式允许你精确表示$ -2^{53} $和$ 2^{53} $之间的整数。注意JavaScript中有些操作是用32位整数执行的,比如数组下标和位运算符。

整型字面值

除了10进制整型字面值,JavaScript支持16进制数值。16进制字面值以"0x"或"0X"开头,后跟16进制数字。尽管ECMAScript标准不支持8进制数值,一些JavaScript实现允许你使用8进制数值(以0开头后跟8进制数字)。在ES5严格模式下,8进制数值被明确禁止。

浮点型字面值

语法为:[digits][.digits][(E|e)[(+|-)]digits]

数学运算

JavaScript提供几种算术运算符:+, -, *, /, %。Math对象支持更复杂的数学运算:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Math.pow(2,53) // => 9007199254740992: 2 to the power 53
Math.round(.6) // => 1.0: round to the nearest integer
Math.ceil(.6) // => 1.0: round up to an integer
Math.floor(.6) // => 0.0: round down to an integer
Math.abs(-5) // => 5: absolute value
Math.max(x,y,z) // Return the largest argument
Math.min(x,y,z) // Return the smallest argument
Math.random() // Pseudo-random number x where 0 <= x < 1.0
Math.PI // π: circumference of a circle / diameter
Math.E // e: The base of the natural logarithm
Math.sqrt(3) // The square root of 3
Math.pow(3, 1/3) // The cube root of 3
Math.sin(0) // Trigonometry: also Math.cos, Math.atan, etc.
Math.log(10) // Natural logarithm of 10
Math.log(100)/Math.LN10 // Base 10 logarithm of 100
Math.log(512)/Math.LN2 // Base 2 logarithm of 512
Math.exp(3) // Math.E cubed

JavaScript中算术运算不会抛出异常。当算术操作的值比最大可表示的值大时,得到Infinity,类似当一个负数比最小可表示的值小时,得到-Infinity。

1
2
3
4
5
6
7
0/0 //=>NaN
Math.sqrt(-1) //=>NaN
1 - 'a' //=>NaN  对不能转换为数字的运算对象使用算术运算符结果为NaN
Infinity + Infinity //=>Infinity
Infinity - Infinity //=>NaN
Infinity * Infinity //=>Infinity
Infinity / Infinity //=>NaN

JavaScript预定义了全局变量Infinity和NaN保存正无穷大和非数值。在ES3标准中,这些值是可以改变的。ES5中更正为只读。ES3中Number对象定义了只读的替代品:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Infinity // A read/write variable initialized to Infinity.
Number.POSITIVE_INFINITY // Same value, read-only.
1/0 // This is also the same value.
Number.MAX_VALUE + 1 // This also evaluates to Infinity.
Number.NEGATIVE_INFINITY // These expressions are negative infinity.
-Infinity
-1/0
-Number.MAX_VALUE - 1
NaN // A read/write variable initialized to NaN.
Number.NaN // A read-only property holding the same value.
0/0 // Evaluates to NaN.
Number.MIN_VALUE/2 // Underflow: evaluates to 0
-Number.MIN_VALUE/2 // Negative zero
-1/Infinity // Also negative 0
-0

NaN和任何值都不相等包括它自己。你可以使用x != x来判断是否是NaN。函数isNaN()判断参数是否为NaN,相关函数isFinite()判断函数参数是否是一个非NaN, Infinity或-Infinity的数字。

负0和正0只有用在除数上不一样,其他情况相等:

1
2
3
4
var zero = 0; // Regular zero
var negz = -0; // Negative zero
zero === negz // => true: zero and negative zero are equal
1/zero === 1/negz // => false: infinity and -infinity are not equal

二进制浮点数和舍入误差

浮点数的二进制表示不能精确表示简单如0.1的十进制浮点数。JavaScript未来也许会支持Decimal数字类型来避免舍入问题。

日期和时间

核心JavaScript包含一个Date()构造函数,用于创建表示日期和时间的对象:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
var then = new Date(2010, 0, 1); // The 1st day of the 1st month of 2010
var later = new Date(2010, 0, 1, 17, 10, 30); // Same day, at 5:10:30pm, local time
var now = new Date(); // The current date and time
var elapsed = now - then; // Date subtraction: interval in milliseconds
later.getFullYear() // => 2010
later.getMonth() // => 0: zero-based months
later.getDate() // => 1: one-based days
later.getDay() // => 5: day of week. 0 is Sunday 5 is Friday.
later.getHours() // => 17: 5pm, local time
later.getUTCHours() // hours in UTC time; depends on timezone

later.toString() // => "Fri Jan 01 2010 17:10:30 GMT-0800 (PST)"
later.toUTCString() // => "Sat, 02 Jan 2010 01:10:30 GMT"
later.toLocaleDateString() // => "01/01/2010"
later.toLocaleTimeString() // => "05:10:30 PM"
later.toISOString() // => "2010-01-02T01:10:30.000Z"; ES5 only

文本

字符串是由16位值组成的不可变的有序序列,每个值通常表示一个Unicode字符。字符串的长度是它包含的16位值的个数。JavaScript使用UTF-16字符集,JavaScript字符串是16位数值的序列。

字符串字面值

由单引号或双引号括起来的字符串。在ES3中字符串字面值必须写在一行上。在ES5中,你可以通过在每行末尾加上反斜杠来在多行之间分隔字符串字面值。

在客户端JavaScript编程中,像JavaScript一样,HTML使用单引号或双引号来分隔字符串。因此,在结合使用JavaScript和HTML时,最好对JavaScript使用一种引号,对HTML使用另一种引号。

转义序列

反斜杠在JavaScript字符串中有特殊的用途。与其后的字符组合,它表示字符串中不能表示的字符:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
\0 The NUL character ( \u0000 )
\b Backspace ( \u0008 )
\t Horizontal tab ( \u0009 )
\n Newline ( \u000A )
\v Vertical tab ( \u000B )
\f Form feed ( \u000C )
\r Carriage return ( \u000D )
\" Double quote ( \u0022 )
\' Apostrophe or single quote ( \u0027 )
\\ Backslash ( \u005C )
\x XX The Latin-1 character specified by the two hexadecimal digits XX
\u XXXX The Unicode character specified by the four hexadecimal digits XXXX

反斜杠后跟任何其它字符,反斜杠被忽略。

使用字符串

对字符串使用+连接字符串,使用length属性获取字符串的长度。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var s = "hello, world" // Start with some text.
s.charAt(0) // => "h": the first character.
s.charAt(s.length-1) // => "d": the last character.
s.substring(1,4) // => "ell": the 2nd, 3rd and 4th characters.
s.slice(1,4) // => "ell": same thing
s.slice(-3) // => "rld": last 3 characters
s.indexOf("l") // => 2: position of first letter l.
s.lastIndexOf("l") // => 10: position of last letter l.
s.indexOf("l", 3) // => 3: position of first "l" at or after 3
s.split(", ") // => ["hello", "world"] split into substrings
s.replace("h", "H") // => "Hello, world": replaces all instances
s.toUpperCase() // => "HELLO, WORLD"

在ES5中,字符串可以被认为是只读数组,可以使用下标访问单个字符:

1
2
3
s = "hello, world";
s[0] // => "h"
s[s.length-1] // => "d"

模式匹配

JavaScript定义了RegExp()构造函数,用于创建表示文本模式的对象。Javascript使用一对斜杠之间的文本组成正则表达式:

1
2
3
/^HTML/ // Match the letters HTML at the start of a string
/1-9*/ // Match a non-zero digit, followed by any # of digits
/\bjavascript\b/i // Match "javascript" as a word, case-insensitive

RegExp对象定义了许多有用的方法,字符串也有接受RegExp参数的方法:

1
2
3
4
5
6
7
var text = "testing: 1, 2, 3"; // Sample text
var pattern = /\d+/g // Matches all instances of one or more digits
pattern.test(text) // => true: a match exists
text.search(pattern) // => 9: position of first match
text.match(pattern) // => ["1", "2", "3"]: array of all matches
text.replace(pattern, "#"); // => "testing: #, #, #"
text.split(/\D+/); // => ["","1","2","3"]: split on non-digits

布尔值

布尔值只有2个值:true, false。任何JavaScript值都可以转换为布尔值,下面这些值转换为false:

1
2
3
4
5
6
undefined
null
0
-0
NaN
"" // the empty string

所有其他值转换为true。布尔值有个toString()方法将它们转换为"true"或"false"。

null和undefined

null undefined
isKeyword Yes No
typeof “object” “undefined”
Boolean false false
hasMethod No No

null表示"没有对象",即该处不应该有值。

**undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。**以下情况均为undefined:

  • 声明变量却没有赋值

  • 对象没有赋值的属性

  • 访问不存在的数组元素

  • 函数调用时没有提供参数

  • 函数没有返回值

undefined是一个预定义的全局变量,被初始化为undefined值。在ES3中,undefined是一个可读可写的变量。在ES5中被更正为只读。

==操作符认为null和undefined相等。使用.或[]访问它们的属性或方法引起TypeError。

你可以认为undefined表示系统级别、意外或类似错误的值缺失,而null表示程序级别、正常或预期的值缺失。

全局对象

全局对象是一个普通的JavaScript对象,它的属性是JavaScript程序可用的全局定义符号。当JavaScript解释器启动时(或当web浏览器加载新页面时),它创建一个新的全局对象,并为其提供一组初始属性:

  • 全局属性,如undefined,Infinity和NaN
  • 全局函数,如isNaN(),parseInt()
  • 构造函数,如Date(),RegExp(),String(),Object()和Array()
  • 全局对象,如Math和JSON

全局对象的初始属性不是保留字,但是它们应该被视为保留字。对于客户端JavaScript,window对象定义了其他全局变量。在顶级代码中(不属于函数的javascript代码),可以使用JavaScript关键字this来引用全局对象。

1
var global = this; // Define a global variable to refer to the global object

在客户端JavaScript中,window对象作为表示浏览器窗口的全局对象,定义了核心全局属性,但它也定义了许多特定于web浏览器和客户端JavaScript的全局属性。

当第一次创建时,全局对象定义了所有JavaScript预定义的全局变量值。但是它也包含了程序定义的全局变量。如果你的代码声明了一个全局变量,那么这个变量就是全局对象的一个属性。

包装对象

当你试图引用字符串s的属性时,JavaScript会将字符串转换为对象,就像调用new String(s)一样。一旦属性被解析,新创建的对象就会被丢弃。数字和布尔值具有与字符串相同的方法:使用Number()或Boolean()构造函数创建临时对象,并使用该临时对象解析该方法。

考虑下面这段代码:

1
2
3
var s = "test"; // Start with a string value.
s.len = 4; // Set a property on it.
var t = s.len; // Now query the property. t is undefined

当访问字符串、数字或布尔值的属性时创建的临时对象称为包装对象。JavaScript根据需要将包装对象转换为原始类型值。==运算符将基础值及其包装对象视为相等的,但是你可以使用===严格相等运算符来区分它们。typeof运算符也将显示基础值与其包装对象之间的区别。

不可变原始类型值和可变对象

原始类型值是不可变的,它们通过值进行比较。对象是可变的,它们通过引用进行比较。2个对象值是相同的当且仅当它们引用相同的底层对象。这段代码定义了一个用来比较两个数组的函数:

1
2
3
4
5
6
function equalArrays(a,b) {
    if (a.length != b.length) return false; // Different-size arrays not equal
    for(var i = 0; i < a.length; i++) // Loop through all elements
        if (a[i] !== b[i]) return false; // If any differ, arrays not equal
    return true; // Otherwise they are equal
}

类型转换

当JavaScript需要布尔值时,你可以提供任何类型的值,JavaScript将根据需要对其进行转换。如果JavaScript想要一个字符串,它会将你给它的任何值转换成字符串。如果JavaScript想要一个数字,它会尝试将你给它的值转换为一个数字(如果它不能执行有意义的转换,则转换为NaN)。

1
2
3
4
5
10 + " objects" // => "10 objects". Number 10 converts to a string
"7" * "4" // => 28: both strings convert to numbers

var n = 1 - "x"; // => NaN: string "x" can't convert to a number
n + " objects" // => "NaN objects": NaN converts to string "NaN"

/images/2018/10/28/conversion.png

转换和相等

因为JavaScript可以灵活地转换值,所以它的==等号操作符也可以灵活地转换相等的概念:

1
2
3
4
null == undefined // These two values are treated as equal.
"0" == 0 // String converts to a number before comparing.
0 == false // Boolean converts to number before comparing.
"0" == false // Both operands convert to numbers before comparing.

记住,一个值与另一个值的可转换并不意味着这两个值相等。

显式转换

最简单的执行显示类型转换的方式是使用Boolean(),Number() ,String() 或Object()函数。除了null和undefined,各个值的toString()返回的结果和使用String()返回的结果一般是相同的。对null或undefined使用Object()函数不会抛出异常,它只是返回一个新创建的空对象。

某些JavaScript操作符执行隐式类型转换,有时特意用于类型转换。如果+运算符的一个操作数是字符串,它将把另一个转换为字符串。一元+运算符将其操作数转换为数字。!运算符将其操作数转换为布尔值并否定它。

1
2
3
x + "" // Same as String(x)
+x // Same as Number(x). You may also see x-0
!!x // Same as Boolean(x). Note double !

Number类定义的toString()方法接受一个可选参数,该参数指定一个基数。默认以10进制进行转换,也可以使用在其他基数(2-36)。

1
2
3
4
var n = 17;
binary_string = n.toString(2); // Evaluates to "10001"
octal_string = "0" + n.toString(8); // Evaluates to "021"
hex_string = "0x" + n.toString(16); // Evaluates to "0x11"

toFixed()将数字转换为小数点后具有指定位数的字符串,不使用指数符号。

toExponential()使用指数符号将数字转换为字符串,小数点前有一位数字,小数点后有指定的位数。

toPrecision()将一个数字转换为一个具有指定的有效位数的字符串。如果有效数字的数量不足以显示该数字的整个整数部分,则使用指数符号。

这三个方法会适当地舍弃结尾数字或在后面添加0。

1
2
3
4
5
6
7
8
9
var n = 123456.789;
n.toFixed(0); // "123457"
n.toFixed(2); // "123456.79"
n.toFixed(5); // "123456.78900"
n.toExponential(1); // "1.2e+5"
n.toExponential(3); // "1.235e+5"
n.toPrecision(4); // "1.235e+5"
n.toPrecision(7); // "123456.8"
n.toPrecision(10); // "123456.7890"

Number()函数只适用于10进制数,且不允许后接不属于数字的字符。parseInt()和parseFloat()函数更灵活,它们是全局函数,不是任何类的方法。parseInt()只解析整数,而parseFloat()解析整数和浮点数。如果字符串以“0x”或“0x”开头,parseInt()将其解释为十六进制数。parseInt()和parseFloat()都跳过前导空白,尽可能多地解析数字字符,并忽略后面的任何内容。如果第一个非空格字符不是有效数字文字的一部分,则返回NaN:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
parseInt("3 blind mice") // => 3
parseFloat(" 3.14 meters") // => 3.14
parseInt("-12.34") // => -12
parseInt("0xFF") // => 255
parseInt("0xff") // => 255
parseInt("-0XFF") // => -255
parseFloat(".1") // => 0.1
parseInt("0.1") // => 0
parseInt(".1") // => NaN: integers can't start with "."
parseFloat("$72.47"); // => NaN: numbers can't start with "$"

parseInt()接受第二个可选参数,指定要解析数字的基数,合法值在2到36之间:

1
2
3
4
5
parseInt("11", 2); // => 3 (12 + 1)
parseInt("ff", 16); // => 255 (1516 + 15)
parseInt("zz", 36); // => 1295 (3536 + 35)
parseInt("077", 8); // => 63 (78 + 7)
parseInt("077", 10); // => 77 (7*10 + 7)

对象到原始类型的转换

对象到布尔转换很简单:所有对象都转换为true。甚至对于包装器对象也是如此:

1
if (new Boolean(false)) console.log(true); //=> true

所有对象都继承两个转换方法。第一个是toString(),它的任务是返回对象的字符串表示形式。

许多类定义了自己的toString()方法。数组的toString()方法将每个数组元素转换为字符串,并用逗号连接在一起。函数的toString()方法返回一个由实现定义的函数表示。Date类的toString()方法,返回人类可读的日期和时间字符串。RegExp类的toString()方法,将RegExp对象转换为一个看起来像RegExp字面值的字符串:

1
2
3
4
[1,2,3].toString() // => "1,2,3"
(function(x) { f(x); }).toString() // => "function(x) {\n f(x);\n}"
/\d+/g.toString() // => "/\d+/g"
new Date(2010,0,1).toString() // => "Fri Jan 01 2010 00:00:00 GMT-0800 (PST)"

另一个转换方法是valueOf()。包装类的valueOf()返回其原始类型。数组,函数和正则表达式只是继承默认方法。调用这些类型实例的valueOf()只是返回对象本身。Date类的valueOf()返回自1970年1月1日以来的毫秒数。

1
2
var d = new Date(2010, 0, 1); // January 1st, 2010, (Pacific time)
d.valueOf() // => 1262332800000

要将对象转换为字符串,JavaScript执行以下步骤:

  • 如果对象有toString()方法,JavaScript会调用它。如果返回一个原始值,JavaScript会将该值转换为字符串并返回转换的结果。
  • 如果对象没有toString()方法,或者该方法不返回原始值,那么JavaScript将查找valueOf()方法。如果该方法存在,JavaScript将调用它。如果返回值是一个原始值,JavaScript会将该值转换为字符串并返回转换后的结果。
  • 否则,JavaScript无法从toString()或valueOf()中获得原始值,并抛出TypeError。

要将一个对象转换为一个数字,JavaScript做同样的事情,但是它首先尝试valueOf()方法:

  • 如果对象的valueOf()方法返回原始值,JavaScript会将原始值转换为数字并返回结果。
  • 否则,如果对象有一个toString()方法返回一个原始值,JavaScript将转换并返回该值。
  • 否则JavaScript抛出TypeError。

JavaScript中的+运算符执行数字加法和字符串连接。如果它的任何一个操作数是一个对象,JavaScript将使用一个特殊的对象到原始类型的转换来转换对象。==运算符是相似的。如果将对象与原始值进行比较,则使用对象到原始类型的转换转换对象。对于所有非日期的对象,对象到原始类型的转换基本上是对象到数字的转换(valueof()优先)。而对于Date类则使用对象到字符串的转换(toString()优先)。由valueOf()或toString()返回的原始值直接使用不用再转换为数字或字符串。

<运算符和其他关系运算符执行对象到原始类型的转换时,任何对象都是通过先尝试valueOf()然后再尝试toString()来转换的。获得的任何原始值都直接使用,而无需进一步转换为数字或字符串。

只有+,==,!=和关系运算符是执行这种特殊的字符串到原始类型转换。其他操作符显示地转换为指定类型:

1
2
3
4
5
var now = new Date(); // Create a Date object
typeof (now + 1) // => "string": + converts dates to strings
typeof (now - 1) // => "number": - uses object-to-number conversion
now == now.toString() // => true: implicit and explicit string conversions
now > (now -1) // => true: > converts a Date to a number

变量声明

在JavaScript程序中使用变量之前,应该声明它。变量用var关键字声明,如下:

1
2
3
4
5
var i;
var sum;
var i, sum;
var message = "hello";
var i = 0, j = 0, k = 0;

重复和省略声明

如果试图读取未声明变量的值,JavaScript将产生一个错误。在ES5严格模式下,给未声明的变量赋值也是一个错误。然而在非严格模式下,如果您为未声明的变量赋值,JavaScript实际上会创建该变量作为全局对象的属性,它的工作方式与正确声明的全局变量非常相似。

变量作用域

变量的作用域是定义变量的程序源代码的区域。全局变量具有全局作用域,JavaScript代码中到处都可见。函数中声明的变量只在函数体中定义。它们是局部变量,具有局部作用域。函数参数也算作局部变量,并且仅在函数体中定义。

在函数体中,局部变量优先于同名的全局变量。如果声明了一个与全局变量同名的局部变量或函数参数,则可以有效地隐藏全局变量:

1
2
3
4
5
6
var scope = "global"; // Declare a global variable
function checkscope() {
    var scope = "local"; // Declare a local variable with the same name
    return scope; // Return the local value, not the global one
}
checkscope() // => "local"

虽然在全局作用域内编写代码时可以避免使用var语句,但是必须始终使用var来声明局部变量:

1
2
3
4
5
6
7
8
9
scope = "global"; // Declare a global variable, even without var.
function checkscope2() {
    scope = "local"; // Oops! We just changed the global variable.
    myscope = "local"; // This implicitly declares a new global variable.
    return [scope, myscope]; // Return two values.
}
checkscope2() // => ["local", "local"]: has side effects!
scope // => "local": global variable has changed.
myscope // => "local": global namespace cluttered up.

函数定义可以嵌套。每个函数都有自己的局部作用域,因此可以有几个嵌套的局部作用域:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var scope = "global scope"; // A global variable
function checkscope() {
    var scope = "local scope"; // A local variable
    function nested() {
        var scope = "nested scope"; // A nested scope of local variables
        return scope; // Return the value in scope here
    }
    return nested();
}
checkscope() // => "nested scope"

函数作用域和提升

JavaScript的函数作用域意味着函数中声明的所有变量在函数体中都是可见的。这意味着变量在声明之前是可见的。JavaScript的这个特性通常被称为hoisting:函数中的所有变量声明,不包括赋值,都被“提升”到函数的顶部。

1
2
3
4
5
6
var scope = "global";
function f() {
    console.log(scope); // Prints "undefined", not "global"
    var scope = "local"; // Variable initialized here, but defined everywhere
    console.log(scope); // Prints "local"
}

变量作为属性

当你声明一个全局JavaScript变量,你实际上做的是定义一个全局对象的属性,如果你使用var声明变量,创建的属性是不可配置的,这意味着它不能被删除。如果不使用严格模式,并且为未声明的变量赋值,JavaScript会自动为您创建一个全局变量。以这种方式创建的变量是全局对象的常规可配置属性,可以删除它们:

1
2
3
4
5
6
var truevar = 1; // A properly declared global variable, nondeletable.
fakevar = 2; // Creates a deletable property of the global object.
this.fakevar2 = 3; // This does the same thing.
delete truevar // => false: variable not deleted
delete fakevar // => true: variable deleted
delete this.fakevar2 // => true: variable deleted

JavaScript允许我们使用this关键字引用全局对象,但它没有提供任何方式引用存储本地变量的对象。

作用域链

如果我们认为局部变量是某种对象的属性,那么还有另一种考虑变量作用域的方法。每个JavaScript代码块(全局代码或函数)都有一个关联的作用域链。这个作用域链是一个对象链,它定义了代码的作用域内的变量。当JavaScript需要查找变量x的值时,它首先查看链中的第一个对象。如果该对象有一个名为x的属性,则使用该属性的值。如果第一个对象没有名为x的属性,JavaScript将继续使用链中的下一个对象进行搜索。如果第二个对象没有一个名为x的属性,则搜索继续到下一个对象,依此类推。如果x不是作用域链中任何对象的属性,则x不在该代码的作用域内,并产生ReferenceError。

理解如何创建这个对象链是很重要的。当一个函数被定义时,它存储有效作用域链。当调用该函数时,它创建一个新对象来存储其本地变量,并将该新对象添加到存储的作用域链中,以创建一个新的、更长的、表示该函数调用作用域的链。对于嵌套函数来说,这变得更加有趣,因为每次调用外部函数时,都会重新定义内部函数。由于作用域链在每次调用外部函数时都不同,每次定义内部函数时,内部函数都会略有不同——每次调用外部函数时,内部函数的代码都相同,但与该代码关联的作用域链会不同。