最近发现了JavaScript Garden这个JS黑魔法收集处,不过里面有一些东西并无说得很透彻,因而边看边查文档or作实验,写了一些笔记,顺手放在博客。等看完了You don't know JS讲this和prototype的部分,说不定又会再写一点。html
一般用匿名函数的地方,匿名函数也是能够带名字的(ES3开始)。便于debug时提供点额外信息/递归。git
foo(function bar(){ ... });
但这时候bar只能在bar里访问,不能在外面访问(not defined)。一样地:github
var foo = function bar() { bar(); // Works } bar(); // ReferenceError
这跟web
function bar() { ... }
的区别在于后者被赋给了window
(或其余global object),至关于api
bar = function() { ... }
前者的引用转给了foo
(第一段的引用则在其余地方都没法访问)。赋给了global object固然均可以访问。因为JS的name resolution,函数名能够在函数本身内访问。数组
追记:IE8-会leak掉这个bar到外面去=__=!!浏览器
在全局下直接用this,指的是global object,浏览器中闭包
console.log(this === window); // true
在以function foo()
形式声明的函数里指的也是global object(注意甚至函数声明内嵌在方法里都是这样,后面会讲到)app
function foo() { console.log(this === window); // true }; foo();
在以形如a.foo()
调用的时候,指的是调用的对象,点前面的东西(注意必定要出现括号才是以方法形式调用,不然调用时不是方法,依然是普通函数,看后文)webapp
var a = {}; a.foo = function() { console.log(this === a); // true }; a.foo();
在构造函数里指的是新new出来的对象。注意这里不能直接用this == b
检查,由于构造函数调用完以前和以后这个新构造的对象自己是有区别的,不过若是延迟一下再判断,等构造完以后就能够看出this指向的是被返回的那个新对象了。(用that保存而不是直接用this是由于setTimeout调用函数时用的是global object,看后文)
function foo() { var that = this; setTimeout(function(){console.log(that === b);}, 1000); // true } var b = new foo();
用apply
,call
,bind
是指哪打哪,这里不赘述
var foo = {}; foo.method = function() { function test() { console.log(this === window); // true } test(); } foo.method();
若是在方法里声明一个函数,这函数里的this又变成了global object,由于this是不会隐式传递的。this的值取决于函数如何调用,不是函数如何声明。所以若是test在调用的时候不是xx.test()的形式,那么就默认this指的是global object。
通常的workaround有:
常见的用that
var foo = {}; foo.method = function() { var that = this; var test = function test() { // store the outer this console.log(that == foo); // true } test(); } foo.method();
来让里面的函数也能用到外面的this。(注意that不是特殊名字,能够随便用)一般和闭包一块儿用,来将this传来传去
将内嵌函数绑在this上
var foo = {}; foo.method = function() { this._test = function() { console.log(this == foo); // true } this._test(); } foo.method();
不过这样一来foo就额外带上+暴露了一个外面不须要的函数
用bind
var foo = {}; foo.method = function() { var test = (function test() { console.log(this == foo); // true }).bind(this); test() } foo.method();
var bar = {} bar.baz = function() { console.log(this === bar); // false console.log(this === window); // true } var foo = bar.baz; foo();
在foo
里this又指回了foo所属对象——global object,由于调用的时候又不是xx.foo(),因而默认this又成了global object。注意赋值到foo的仅仅是一个函数引用,this的值没有跟过去——实际上闭包=函数引用+环境的“环境”也是不将this包括在内的。注意这种绑定这也是prototypal inheritance的基础
function Foo() {} Foo.prototype.method = function() { console.log(this === b); // b would be available when executed after b is declared! }; function Bar() {} Bar.prototype = Foo.prototype; var b = new Bar(); b.method();
在b.method()里this指的就是b了(Bar的实例),否则指的应该是一个Foo的实例……呵呵那就是implemation inheritance了。注意因为函数执行的时候已经有b,因此在foo里引用b的时候不会报错。JS的函数里的引用都推迟到执行时去找,声明时是不检查的。
function Foo() { this.value = 42; this.method = function() { // this refers to the global object console.log(this.value); // undefined console.log(this === window); // true }; setTimeout(this.method, 500); } new Foo();
setTimeout会脱离当前上下文,用global object调用第一个参数。事实上会觉得setTimeout(this.method, 500);
,无非是脑补成了会调用this.method()
,但事实上传进去的不过是一个没有bind过的函数引用,能够理解为:
method = this.method; // method belongs to the global object setTimeout(method, 500);
简单来讲,只要记得只有实际在代码里看到形如this.method()
(注意括号)的调用,才能认为函数执行时的this指向点前面的部分。没有看到括号,就不能这样想固然。
若是想要让this是直觉上的那个对象,能够用that+闭包来保证传进去的函数里的this是你想要的值
function Foo() { this.value = 42; var that = this; this.method = function() { // this refers to the new instance console.log(that.value); // 42 console.log(that === b); // true }; setTimeout(this.method, 500); } var b = new Foo();
或者用bind
:
function Foo() { this.value = 42; this.method = (function method() { console.log(this.value); // 42 console.log(this === b); // true }).bind(this); setTimeout(this.method, 500); } var b = new Foo();
setInterval只管调用函数,无论函数执行,因此若是被调用的函数阻塞了,并且阻塞的时间大于调用间隔,那么当这个函数执行完以后,可能会有一大波被调用还没开始执行的函数挤上来,像这样:
function foo(){ // something that blocks for 1 second } setInterval(foo, 100);
解决方法是
function foo(){ // something that blocks for 1 second setTimeout(foo, 1000); } foo();
这样会等到函数执行完以后,再等待间隔,再进行下一次调用。注意用setTimeout+传函数的方式递归的时候是不会stackoverflow的,由于setTimeout+传函数只是作标记要调用而不是真的要调用。传进去的函数在执行完以后会马上返回(setTimeout不会阻塞,因此不须要等待他返回),不会在栈上等着,天然也就不会stackoverflow了。
setTimeout
属于DOM的一部分(并且是DOM 0),因此在ECMAScript标准里没有说明,可是在各大浏览器中,setTimeout的ID事实上是越后的越大,因此能够马上setTimeout一下,获得当前的最大ID,而后逐个清除
// clear "all" timeouts var biggestTimeoutId = window.setTimeout(function(){}, 1), i; for(i = 1; i <= biggestTimeoutId; i++) { clearTimeout(i); }
可是由于标准里没有说,因此这个方法在将来不必定靠谱。HTML5对setTimeout作了规范,可是目前对这个返回的ID的规范是“a user-agent-defined integer that is greater than zero that will identify the timeout to be set by this call in the list of active timers.”也就是说只要是惟一的正整数就能够了,至于怎么变就是user-agent-defined,依然不靠谱啊噗
push
,pop
,slice
转换为数组
Array.prototype.slice.call(arguments);
可是这种作法 1.速度慢 2.解释器没法优化 因此不必的时候不要用
[]
来访问or修改优化杀手,尽可能不要用。
arguments.callee
一般用来reference函数自己,可是除非在用apply
、call
不然彻底能够用函数名代替。arguments.callee.caller
(同Function.caller
)一般用于reference调用这个函数的函数,可是这种用法显然破坏封装(函数的行为竟然要依赖被调用的上下文)。使用了arguments.callee
或者Function.caller
以后解释器很难肯定函数的行为,致使没法进行inline优化。
在ES5 strict mode下使用arguments.callee
会报错。