对象
javascript中所有变量都是对象,除了两个例外null和undefined。
一个常见的误解时数字的字面值不是对象。这是因为javascript解析器的一个错误,它试图将点操作符解析为浮点数字面值的一部分。
有很多变通方法可以让数字的字面值看起来像对象。
对象作为数据类型
javascript的对象可以作为哈希表使用,主要用来保存命名的键值的对应关系。
使用对象的字面语法-{}-可以创建一个简单的对象。这个新创建的对象从object.prototype继承下来,没有任何自定义属性。
访问属性
有两种方式来访问对象的属性,点操作符或者中括号操作符。
两种语法是等价的,但是中括号操作符在下面两种情况下依然有效-动态设置属性-属性名不是一个有效的变量名(比如属性名中包含空格,或者属性名是js的关键字)
删除属性
删除属性的唯一办法是使用delete操作符;设置属性为undefined或者null并不能真正删除属性,而仅仅是移除了属性和值得关联。
上面的输出结果有bar undefined和foo null -只有 baz 被真正的删除了,所以从输出结果中消失。
属性名的语法
对象的属性名可以使用字符串或者普通字符声明。但是由于javascript解析器的另一个错误设计,上面的第二种声明方式在ECMAScript5之前会抛出SyntaxError的错误。
这个错误的原因是delete是javascript语言的一个关键字;因此为了在更低版本的javascript也能正常运行,必须使用字符串字面值声明方式。
原型
javascript不包含传统的类继承模型,而是使用prototype原型模型。
虽然这经常被当作是javascript的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大。实现传统的类继承模型是很简单,但是实现javascript多种的原型继承则要困难的多。
第一个不同之处在于javascript使用原型链的继承方式。
上面的例子中,test 对象从 Bar.prototype 和 Foo.prototype 继承下来;因此, 它能访问 Foo 的原型方法 method。同时,它也能够访问那个定义在原型上的 Foo 实例属性 value。 需要注意的是 new Bar() 不会创造出一个新的 Foo 实例,而是 重复使用它原型上的那个实例;因此,所有的 Bar 实例都会共享相同的 value 属性。
属性查找
当查找一个对象的属性时,javascript会向上遍历原型链,知道找到给定名称的属性为止。
到查找到达原型链的顶部-也就是Object.prototype - 但是仍然没有找到指定的属性,就会返回undefined。
原型属性
当圆形属性用来创建原型链时,可以把任何类型的值赋给它(prototype)。
然而将原子类型赋给prototype的操作将会被忽略。
而将对象赋值给prototype,正如上面的例子所示,将会动态的创建原型链。
性能
如果一个属性在原型链上端,则对于查找时间带来不利影响。特别的,试图获取一个不存在的属性将会遍历整个原型链。
并且,当使用for in遍历对象的属性时,原型链上的所有属性都将被访问。
扩展内置类型的原型
一个错误特性被经常使用,那就是扩展Object.prototype或者其他内置类型的原型对象。
这种技术被称为monkey patching并且会破坏封装。虽然它被广泛的应用到一些javascript类库中比如prototype,但是我仍然认为为内置类型添加一些非标准的函数不是个好主意。
扩展内置类型的唯一理由是为了和新的javascript保持一致,比如Array,forEach.
总结
上面的例子中,test 对象从 Bar.prototype 和 Foo.prototype 继承下来;因此, 它能访问 Foo 的原型方法 me在写复杂的 JavaScript 应用之前,充分理解原型链继承的工作方式是每个 JavaScript 程序员必修的功课。 要提防原型链过长带来的性能问题,并知道如何通过缩短原型链来提高性能。 更进一步,绝对不要扩展内置类型的原型,除非是为了和新的 JavaScript 引擎兼容。
为了判断一个对象是否包含自定义属性而不是原型链上的属性,我们需要使用继承自Object.prototype的hasOwnProperty方法。
hasOwnProperty是javascript中唯一一个处理属性但是不查找原型链的函数。
只有hasOwnProperty可以给出正确和期望的结果,这在遍历对象的属性时会很有用。没有其他方法可以用来排除原型链上的属性,而不是定义在对象自身上的属性。
hasOwnProperty作为属性
javascript不会保护hasOwnProperty被非法占用,因此结果一个对象碰巧存在这个属性,就需要外部的hasOwnProperty函数来获取正确的结果。
结论
当检查对象上某个属性是否存在时,hasOwnProperty 是唯一可用的方法。 同时在使用 for in loop 遍历对象时,推荐总是使用 hasOwnProperty 方法, 这将会避免原型对象扩展带来的干扰。
for in循环
和in操作符一样,for in 循环同样在查找对象属性时遍历原型链上的所有属性。
由于不可能改变for in自身的行为,因此有必要过滤出那些不希望出现在循环体中的属性,这可以通过Object.prototype原型上的hasOwnProperty 函数来完成。
使用hasOwnProperty 过滤
这个版本的代码是唯一正确的写法。由于我们使用了hasOwnProperty ,所以这次只输出moo。如果不使用hasOwnProperty ,则这段代码在原生对象原型被扩展时可能会出错。
一个广泛使用的类库prototype就扩展了原生的javascript对象。因此当这个类库被包含在页面中时,不使用hasOwnProperty 过滤的for in 循环难免会出问题。
总结
推荐总是使用 hasOwnProperty。不要对代码运行的环境做任何假设,不要假设原生对象是否已经被扩展了。
函数
函数式javascript中的一等对象,这意味着可以把函数像其他值一样传递。一个常见的用法是把匿名函数作为回调函数传递到异步函数中。
函数声明
上面的方法会在执行前被解析,因此它存在于当前上下文的任意一个地方,即使在函数定义体的上面被调用也是对的。
函数赋值表达式
这个例子把一个匿名函数赋值给变量foo。
由于var定义了一个声明语句,对变量foo的解析式在代码运行之前,因此foo变量在代码运行时已经被定义过了。
但是由于赋值语句只在运行时执行,因此在相应代码之前,foo的值缺省为undefined。
命名函数的赋值表达式
另外一个特殊的情况是将命名函数赋值给一个变量。
bar函数声明外事不可见的,这是因为我们已经把函数赋值给了foo;然而在bar内部依然可见。这是由于javascript的命名处理所致,函数名在函数内总是可见的。
this的工作原理
javascript有一套完全不同于其他语言的对this的处理机制。在五种不同的情况下,this指向的各不相同。
全局范围内
当在全局范围内使用this,它将会指向全局对象。
函数调用
这里this也会指向全局对象
方法调用
这个例子中,this指向test对象。
调用构造函数
如果函数倾向于和new关键字一块使用,则我们称这个函数式构造函数。在函数内部,this指向新创建的对象。
显示的设置this
当使用function.prototype上的call或者apply方法时,函数内的this将会被显示设置为函数调用的第一参数。
因此函数调用的规则在上例中已经不适用了,在foo函数内this被设置成了bar。
常见误解
尽管大部分的情况说的过去,不过第一个规则(这里指的是应该是第二个规则,也就是直接调用函数时,this指向全局对象)被认为是javascript语言另一个错误设计的地方,因为它从来就没有实际用途。
一个常见的误解是test中的this将会指向foo对象,实际上不是这个样子的。
为了在test中获取对foo对象的引用,我们需要在method函数内部创建一个局部变量指向foo对象。
that只是我们随意起的名字,不过这个名字被广泛的用来指向外部的this对象。在闭包一节,我们可以看到that可以作为参数传递。
方法的赋值表达式
另一个看起来奇怪的地方时函数别名,也就是将一个方法赋值给一个变量。
上例中,test就像一个普通的函数被调用;因此,函数内的this将不再被指向到someObject对象。虽然this的晚绑定特性似乎并不友好,但是这确实基于原型继承赖以生存的土壤。
当method被调用时,this将会指向Bar的实例对象。
闭包和引用
闭包是javascript一个非常重要的特性,这意味着当前作用域总是能够访问外部作用域中的变量。因为函数式javascript中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数。
模拟私有变量
这里,Counter函数返回两个闭包,函数increment和函数get。这两个函数都维持着对外部作用域count的引用,因此总可以访问到此作用域内定义的变量count。
为什么不可以在外部访问私有变量
因为javascript不能对作用域进行引用或赋值,因此没有办法在外部访问count变量。唯一的途径就是通过那两个闭包。
上面的代码不会改变定义在counter作用域中的count变量的值,因为foo。hack没有定义在那个作用域内。它将会创建或者覆盖全局变量count。
循环中的闭包
一个常见的错误出现在循环中使用闭包,假设我们需要在每次循环中调用循环序号
上面的代码不会输出数字0-9,而是会输出数字10十次。
当console.log被调用时候,匿名函数保持对外部变量i的引用,此时for循环已经结束,i的值被修改成了10.
为了得到想要的结果,需要在每次循环中创建变量i的拷贝。
避免引用错误
为了正确的获得循环序号,最好使用匿名包裹器(自执行匿名函数)。
外部的匿名函数会立即执行,并把i作为它的参数,此时函数内e变量就拥有了i的一个拷贝。
当传递给setTimeount的匿名函数执行时,它就拥有了对e的引用,而这个值是不会被循环改变的。
有另一个方法完成同样的工作;那就是从匿名包装器中返回一个函数。这和上面的代码效果一样。
arguments对象
JavaScript 中每个函数内都能访问一个特别变量 arguments。这个变量维护着所有传递到这个函数中的参数列表。
arguments 变量不是一个数组(Array)。 尽管在语法上它有数组相关的属性 length,但它不从 Array.prototype 继承,实际上它是一个对象(Object)。
因此,无法对 arguments 变量使用标准的数组方法,比如 push, pop 或者 slice。 虽然使用 for 循环遍历也是可以的,但是为了更好的使用数组方法,最好把它转化为一个真正的数组。
转化为数组
下面的代码将会创建一个新的数组,包含所有arguments 对象中的元素。
传递参数
下面将参数从一个函数传递到另一个函数,是推荐的做法。
另一个技巧是同时使用 call 和 apply,创建一个快速的解绑定包装器。
上面的 Foo.method 函数和下面代码的效果是一样的:
自动更新
arguments 对象为其内部属性以及函数形式参数创建 getter 和 setter 方法。
因此,改变形参的值会影响到 arguments 对象的值,反之亦然。
arguments 对象总会被创建,除了两个特殊情况 - 作为局部变量声明和作为形式参数。 而不管它是否有被使用。
arguments 的 getters 和 setters 方法总会被创建;因此使用 arguments 对性能不会有什么影响。 除非是需要对 arguments 对象的属性进行多次访问。
在 MDC 中对 strict mode 模式下 arguments 的描述有助于我们的理解,请看下面代码
上面代码中,foo 不再是一个单纯的内联函数 inlining(译者注:这里指的是解析器可以做内联处理), 因为它需要知道它自己和它的调用者。 这不仅抵消了内联函数带来的性能提升,而且破坏了封装,因此现在函数可能要依赖于特定的上下文。
因此强烈建议大家不要使用 arguments.callee
和它的属性。
构造函数
JavaScript 中的构造函数和其它语言中的构造函数是不同的。 通过 new 关键字方式调用的函数都被认为是构造函数。
在构造函数内部 - 也就是被调用的函数内 - this 指向新创建的对象 Object。 这个新创建的对象的 prototype 被指向到构造函数的 prototype。
如果被调用的函数没有显式的 return 表达式,则隐式的会返回 this 对象 - 也就是新创建的对象。
上面代码把 Foo 作为构造函数调用,并设置新创建对象的 prototype 为 Foo.prototype。
显式的 return 表达式将会影响返回结果,但仅限于返回的是一个对象。
new Bar() 返回的是新创建的对象,而不是数字的字面值 2。 因此 new Bar().constructor === Bar,但是如果返回的是数字对象,结果就不同了,如下所示
这里得到的 new Test()是函数返回的对象,而不是通过new关键字新创建的对象,因此:
如果 new 被遗漏了,则函数不会返回新创建的对象。
虽然上例在有些情况下也能正常运行,但是由于 JavaScript 中 this 的工作原理, 这里的 this 指向全局对象。
工厂模式
为了不使用 new 关键字,构造函数必须显式的返回一个值。
还需要注意, new Bar() 并不会改变返回对象的原型(译者注:也就是返回对象的原型不会指向 Bar.prototype)。 因为构造函数的原型会被指向到刚刚创建的新对象,而这里的 Bar 没有把这个新对象返回(译者注:而是返回了一个包含 method 属性的自定义对象)。
在上面的例子中,使用或者不使用
new
关键字没有功能性的区别。
上面两种方式创建的对象不能访问 Bar 原型链上的属性,如下所示:
通过工厂模式创建新对象
上面两种方式创建我们常听到的一条忠告是不要使用 new 关键字来调用函数,因为如果忘记使用它就会导致错误。对象不能访问 Bar 原型链上的属性,如下所示:
为了创建新对象,我们可以创建一个工厂方法,并且在方法内构造一个新对象。
虽然上面的方式比起 new 的调用方式不容易出错,并且可以充分利用私有变量带来的便利, 但是随之而来的是一些不好的地方。
1.会占用更多的内存,因为新创建的对象不能共享原型上的方法。
2.为了实现继承,工厂方法需要从另外一个对象拷贝所有属性,或者把一个对象作为新创建对象的原型。
3.放弃原型链仅仅是因为防止遗漏 new 带来的问题,这似乎和语言本身的思想相违背。
总结
虽然遗漏 new 关键字可能会导致问题,但这并不是放弃使用原型链的借口。 最终使用哪种方式取决于应用程序的需求,选择一种代码书写风格并坚持下去才是最重要的。
作用域与命名空间
尽管 JavaScript 支持一对花括号创建的代码段,但是并不支持块级作用域; 而仅仅支持 函数作用域。
如果 return 对象的左括号和 return 不在一行上就会出错。
JavaScript 中没有显式的命名空间定义,这就意味着所有对象都定义在一个全局共享的命名空间下面。
每次引用一个变量,JavaScript 会向上遍历整个作用域直到找到这个变量为止。 如果到达全局作用域但是这个变量仍未找到,则会抛出 ReferenceError 异常。
隐式的全局变量
上面两段脚本效果不同。脚本 A 在全局作用域内定义了变量 foo,而脚本 B 在当前作用域内定义变量 foo。
再次强调,上面的效果完全不同,不使用 var 声明变量将会导致隐式的全局变量产生。
在函数 test 内不使用 var 关键字声明 foo 变量将会覆盖外部的同名变量。 起初这看起来并不是大问题,但是当有成千上万行代码时,不使用 var 声明变量将会带来难以跟踪的 BUG。
外部循环在第一次调用
subLoop
之后就会终止,因为
subLoop
覆盖了全局变量
i
。 在第二个
for
循环中使用
var
声明变量可以避免这种错误。 声明变量时
绝对不要
遗漏
var
关键字,除非这就是
期望
的影响外部作用域的行为。
局部变量
JavaScript 中局部变量只可能通过两种方式声明,一个是作为函数参数,另一个是通过 var
关键字声明。
foo
和
i
是函数
test
内的局部变量,而对
bar
的赋值将会覆盖全局作用域内的同名变量。
变量声明提升
JavaScript 会提升变量声明。这意味着 var
表达式和 function
声明都将会被提升到当前作用域的顶部。
var
表达式和
function
声明提升到当前作用域的顶部。
没有块级作用域不仅导致
var
表达式被从循环内移到外部,而且使一些
if
表达式更难看懂。
在原来代码中,
if
表达式看起来修改了
全部变量
goo
,实际上在提升规则被应用后,却是在修改
局部变量
。
如果没有提升规则(hoisting)的知识,下面的代码看起来会抛出异常
ReferenceError
。
实际上,上面的代码正常运行,因为
var
表达式会被提升到
全局作用域
的顶部。
在 Nettuts+ 网站有一篇介绍 hoisting 的 文章 ,其中的代码很有启发性。
名称解析顺序
JavaScript 中的所有作用域,包括 全局作用域 ,都有一个特别的名称
this
指向当前对象。
函数作用域内也有默认的变量 arguments
,其中包含了传递到函数中的参数。
比如,当访问函数内的 foo
变量时,JavaScript 会按照下面顺序查找:
- 当前作用域内是否有
var foo
的定义。 - 函数形式参数是否有使用
foo
名称的。 - 函数自身是否叫做
foo
。 - 回溯到上一级作用域,然后从 #1 重新开始。
结论
推荐使用 匿名包装器( 译者注:也就是自执行的匿名函数)来创建命名空间。这样不仅可以防止命名冲突, 而且有利于程序的模块化。
另外,使用全局变量被认为是 不好的习惯。这样的代码倾向于产生错误和带来高的维护成本。
for in
循环 遍历数组。 相反,有一些好的理由不去使用
for in
遍历数组。
for
循环。
l = list.length
来缓存数组的长度。
length
是数组的一个属性,但是在每次循环中访问它还是有性能开销。
可能最新的 JavaScript 引擎在这点上做了优化,但是我们没法保证自己的代码是否运行在这些最近的引擎之上。
length属性
length
属性的
getter 方式会简单的返回数组的长度,而
setter 方式会
截断数组。
foo
的值是:
[1, 2, 3, undefined, undefined, undefined]
但是这个结果并不准确,如果你在 Chrome 的控制台查看
foo
的结果,你会发现是这样的:
[1, 2, 3]
因为在 JavaScript 中
undefined
是一个变量,注意是变量不是关键字,因此上面两个结果的意义是完全不相同的。
length
设置一个更小的值会截断数组,但是增大
length
属性值不会对数组产生影响。
结论
为了更好的性能,推荐使用普通的
for
循环并缓存数组的
length
属性。 使用
for in
遍历数组被认为是不好的代码习惯并倾向于产生错误和导致性能问题。
Array
的构造函数在如何处理参数时有点模棱两可,因此总是推荐使用数组的字面语法 -
[]
- 来创建数组。
new Array(3);
这种调用方式),并且这个参数是数字,构造函数会返回一个
length
属性被设置为此参数的空数组。 需要特别注意的是,此时只有
length
属性被设置,真正的数组并没有生成。
这种优先于设置数组长度属性的做法只在少数几种情况下有用,比如需要循环字符串,可以避免
for
循环的麻烦。
结论
应该尽量避免使用数组构造函数创建新数组。推荐使用数组的字面语法。它们更加短小和简洁,因此增加了代码的可读性。
typeof
操作符(和
instanceof
一起)或许是 JavaScript 中最大的设计缺陷, 因为几乎不可能从它们那里得到想要的结果。
instanceof
还有一些极少数的应用场景,
typeof
只有一个实际的应用(
译者注:这个实际应用是用来检测一个对象是否已经定义或者是否已经赋值), 而这个应用却
不是用来检查对象的类型。
typeof
操作符的运算结果。可以看到,这个值在大多数情况下都返回 "object"。
[[Class]]
的值。
[[Class]]
,我们需要使用定义在
Object.prototype
上的方法
toString
。
[[Class]]
值的方法,那就是使用
Object.prototype.toString
。
Object.prototype.toString
返回一种标准格式字符串,所以上例可以通过
slice
截取指定位置的字符串,如下所示:
这种变化可以从 IE8 和 Firefox 4 中看出区别,如下所示:
测试为定义变量
上面代码会检测
foo
是否已经定义;如果没有定义而直接使用会导致
ReferenceError
的异常。 这是
typeof
唯一有用的地方。
Object.prototype.toString
方法; 因为这是唯一一个可依赖的方式。正如上面表格所示,
typeof
的一些返回值在标准文档中并未定义, 因此不同的引擎实现可能不同。
typeof
操作符。
instanceof
操作符用来比较两个操作数的构造函数。只有在比较自定义的对象时才有意义。 如果用来比较内置类型,将会和
typeof
操作符 一样用处不大。
instanceof
用来比较属于不同 JavaScript 上下文的对象(比如,浏览器中不同的文档结构)时将会出错, 因为它们的构造函数不会是同一个对象。
结论
instanceof
操作符应该
仅仅用来比较来自同一个 JavaScript 上下文的自定义对象。 正如
typeof
操作符一样,任何其它的用法都应该是避免的。
为了避免上面复杂的强制类型转换, 强烈推荐使用 严格的等于操作符。 虽然这可以避免大部分的问题,但 JavaScript 的弱类型系统仍然会导致一些其它问题。
内置类型的构造函数
Number
和
String
)的构造函数在被调用时,使用或者不使用
new
的结果完全不同。
Number
作为构造函数将会创建一个新的
Number
对象, 而在不使用
new
关键字的
Number
函数更像是一个数字转换器。
转换为字符串
转换为数字
字符串转换为数字的常用方法:
核心
eval
函数会在当前作用域中执行一段 JavaScript 代码字符串。
但是
eval
只在被
直接调用并且调用函数就是
eval
本身时,才在当前作用域中执行。
上面的代码等价于在全局作用域中调用
eval
,和下面两种写法效果一样:
在 任何情况下我们都应该避免使用
eval
函数。99.9% 使用
eval
的场景都有
不使用
eval
的解决方案。
伪装的eval
eval
也存在安全问题,因为它会执行
任意传给它的代码, 在代码字符串未知或者是来自一个不信任的源时,绝对不要使用
eval
函数。
eval
,任何使用它的代码都会在它的工作方式,性能和安全性方面受到质疑。 如果一些情况必须使用到
eval
才能正常工作,首先它的设计会受到质疑,这
不应该是首选的解决方案, 一个更好的不使用
eval
的解决方案应该得到充分考虑并优先采用。
undefined
。
undefined
是一个值为
undefined
的类型。
undefined
,这个变量也被称为
undefined
。 但是这个变量
不是一个常量,也不是一个关键字。这意味着它的
值可以轻易被覆盖。
undefined
值:
- 访问未修改的全局变量
undefined
。 - 由于没有定义
return
表达式的函数隐式返回。 return
表达式没有显式的返回任何内容。- 访问不存在的属性。
- 函数参数没有被显式的传递值。
- 任何被设置为
undefined
值的变量。
undefined
只是保存了
undefined
类型实际
值的副本, 因此对它赋新值
不会改变类型
undefined
的值。
undefined
做比较,我们需要事先获取类型
undefined
的值。
undefined
值的改变,一个常用的技巧是使用一个传递到
匿名包装器的额外参数。 在调用时,这个参数不会获取任何值。
这里唯一的区别是,在压缩后并且函数内没有其它需要使用
var
声明变量的情况下,这个版本的代码会多出 4 个字节的代码。
null的用处
undefined
的使用场景类似于其它语言中的
null,实际上 JavaScript 中的
null
是另外一种数据类型。
Foo.prototype = null
),但是大多数情况下都可以使用
undefined
来代替。
下面的代码没有分号,因此解析器需要自己判断需要在哪些地方插入分号。
下面是解析器"猜测"的结果。
上面代码被解析器转换为一行。
结论
建议 绝对不要省略分号,同时也提倡将花括号和相应的表达式放在一行, 对于只有一行代码的
if
或者
else
表达式,也不应该省略花括号。 这些良好的编程习惯不仅可以提到代码的一致性,而且可以防止解析器改变代码行为的错误处理。
setTimeout
和
setInterval
来计划执行函数。
setTimeout
被调用时,它会返回一个 ID 标识并且计划在将来
大约 1000 毫秒后调用
foo
函数。
foo
函数只会被执行
一次。
基于 JavaScript 引擎的计时策略,以及本质上的单线程运行方式,所以其它代码的运行可能会阻塞此线程。 因此 没法确保函数会在
setTimeout
指定的时刻被调用。
this
将会指向这个全局对象。
setTimeout
只会执行回调函数一次,不过
setInterval
- 正如名字建议的 - 会每隔
X
毫秒执行函数一次。 但是却不鼓励使用这个函数。
setInterval
仍然会发布更多的回调指令。在很小的定时间隔情况下,这会导致回调函数被堆积起来。
foo
会执行一次随后被阻塞了一分钟。
在
foo
被阻塞的时候,
setInterval
仍然在组织将来对回调函数的调用。 因此,当第一次
foo
函数调用结束时,已经有
10 次函数调用在等待执行。
处理可能的阻塞调用
setTimeout
函数。
setTimeout
回调函数,而且阻止了调用指令的堆积,可以有更多的控制。
foo
函数现在可以控制是否继续执行还是终止执行。
clearTimeout
或者
clearInterval
函数来清除定时, 至于使用哪个函数取决于调用的时候使用的是
setTimeout
还是
setInterval
。
可能还有些定时器不会在上面代码中被清除( 译者注:如果定时器调用时返回的 ID 值大于 1000), 因此我们可以事先保存所有的定时器 ID,然后一把清除。
隐藏使用eval
setTimeout
和
setInterval
也接受第一个参数为字符串的情况。 这个特性
绝对不要使用,因为它在内部使用了
eval
。
eval
在这种情况下不是被
直接调用,因此传递到
setTimeout
的字符串会自
全局作用域中执行; 因此,上面的回调函数使用的不是定义在
bar
作用域中的局部变量
foo
。
建议 不要在调用定时器函数时,为了向回调函数传递参数而使用字符串的形式。
绝对不要使用字符串作为
setTimeout
或者
setInterval
的第一个参数, 这么写的代码明显质量很差。当需要向回调函数传递参数时,可以创建一个
匿名函数,在函数内执行真实的回调函数。
setInterval
,因为它的定时执行不会被 JavaScript 阻塞。