最近发现了JavaScript Garden这个JS黑魔法收集处,不过里面有一些东西并没有说得很透彻,于是边看边查文档or做实验,写了一些笔记,顺手放在博客。等看完了You don't know JS讲this和prototype的部分,说不定又会再写一点。

函数名字是可选的

通常用匿名函数的地方,匿名函数也是可以带名字的(ES3开始)。便于debug时提供点额外信息/递归。

foo(function bar(){ ... });

但这时候bar只能在bar里访问,不能在外面访问(not defined)。同样地:

var foo = function bar() {
bar(); // Works
}
bar(); // ReferenceError

这跟

function bar() { ... }

的区别在于后者被赋给了window(或其他global object),相当于

bar = function() { ... }

前者的引用转给了foo(第一段的引用则在其他地方都无法访问)。赋给了global object当然都可以访问。由于JS的name resolution,函数名可以在函数自己内访问。

追记:IE8-会leak掉这个bar到外面去=__=!!

this的五种绑定

  1. 在全局下直接用this,指的是global object,浏览器中

    console.log(this === window); // true
  2. 在以function foo()形式声明的函数里指的也是global object(注意甚至函数声明内嵌在方法里都是这样,后面会讲到)

    function foo() {
    console.log(this === window); // true
    };
    foo();
  3. 在以形如a.foo()调用的时候,指的是调用的对象,点前面的东西(注意一定要出现括号才是以方法形式调用,否则调用时不是方法,依然是普通函数,看后文)

    var a = {};
    a.foo = function() {
    console.log(this === a); // true
    };
    a.foo();
  4. 在构造函数里指的是新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();
  5. applycallbind是指哪打哪,这里不赘述

内嵌函数的this绑定

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有:

  1. 常见的用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传来传去

  2. 将内嵌函数绑在this上

    var foo = {};
    foo.method = function() {
    this._test = function() {
    console.log(this == foo); // true
    }
    this._test();
    } foo.method();

    不过这样一来foo就额外带上+暴露了一个外面不需要的函数

  3. 用bind

    var foo = {};
    foo.method = function() {
    var test = (function test() {
    console.log(this == foo); // true
    }).bind(this);
    test()
    } foo.method();

this的延迟绑定

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的函数里的引用都推迟到执行时去找,声明时是不检查的。

setTimeout里的this

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); //
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); //
console.log(this === b); // true
}).bind(this);
setTimeout(this.method, 500);
}
var b = new Foo();

setTimeout v.s. setInterval

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了。

如何清除所有的timeout

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,依然不靠谱啊噗

arguments不是数组

  • 所以不能用pushpopslice
  • 可以用for-in
  • 转换为数组

    Array.prototype.slice.call(arguments);

    但是这种做法 1.速度慢 2.解释器无法优化 所以没必要的时候不要用

  • ES5 strict mode下arguments无法用[]来访问or修改

arguments.callee

优化杀手,尽量不要用

arguments.callee通常用来reference函数本身,但是除非在用applycall否则完全可以用函数名代替。arguments.callee.caller(同Function.caller)通常用于reference调用这个函数的函数,但是这种用法显然破坏封装(函数的行为居然要依赖被调用的上下文)。使用了arguments.callee或者Function.caller之后解释器很难确定函数的行为,导致无法进行inline优化。

在ES5 strict mode下使用arguments.callee会报错。

JS黑魔法之this, setTimeout/setInterval, arguments的更多相关文章

  1. js,onblur后下一个控件获取焦点判断、html当前活跃控件、jquery版本查看、jquery查看浏览器版本、setTimeout&setInterval

    需求: input控件在失去焦点后直接做验证,验证通不过的话,显示相应错误.但是如果失去焦点后点击的下个控件是比较特殊的控件(比如,退出系统),那么不执行验证操作,直接退出系统(防止在系统退出前,还显 ...

  2. js中setTimeout/setInterval定时器用法示例

    js中setTimeout(定时执行一次)和setInterval(间隔循环执行)用法介绍. setTimeout:在指定的毫秒数后调用指定的代码段或函数:setTimeout示例代码 functio ...

  3. js异步处理工作机制(setTimeout, setInterval)

    经常谈到异步,但是发现自己一直没深入理解setTimeout, setInterval,逛论坛的时候发现了这篇好文章,分享一下. ————————————————————以下为原文—————————— ...

  4. setTimeout,setInterval你不知道的…

    javascript线程解释(setTimeout,setInterval你不知道的事) 标签: javascript引擎任务浏览器functionxmlhttprequest 2011-11-21 ...

  5. setTimeout,setInterval你不知道的事

    javascript线程解释(setTimeout,setInterval你不知道的事) 标签: javascript引擎任务浏览器functionxmlhttprequest 2011-11-21 ...

  6. window对象方法之setTimeout(),setInterval()

    window中的这两个方法是比较重要的,在许多的设计中会使用到这两个方法.比如使用在倒计时抢购中. 首先来说说这两个方法的用法吧! 一:window.setTimeout(); setTimeout( ...

  7. JS异步执行之setTimeout 0的妙用

    最近在工作中遇到一些问题,大致是关于js执行问题的.由于没搞清执行顺序,导致出现了一些奇怪的bug. 所以这里整理一些有关异步执行的知识(冰山一角角)... 大家都知道js是单线程的,执行起来是顺序的 ...

  8. js基础篇——call/apply、arguments、undefined/null

    a.call和apply方法详解 call方法: 语法:call([thisObj[,arg1[, arg2[,   [,.argN]]]]]) 定义:调用一个对象的一个方法,以另一个对象替换当前对象 ...

  9. setTimeout/setInterval执行时机

    setTimeout()和setInterval()经常被用来处理延时和定时任务.setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式,而setInterval()则可以在每隔指定的 ...

随机推荐

  1. Docker简介与安装配置

    目录 Docker简介 什么是Docker 为啥要用容器 Docker Engine Docker架构说明 Docker安装 Docker版本介绍 Ubuntu安装docker-ce CentOS7安 ...

  2. "\n" 与"\r" 区别

    关于换行和回车其实平时我们不太在意,所以关于两者的区别也不太清楚,在平时开发时可能会遇到一些文件处理的问题,放到不同的操作系统上出现各种坑.那么回车和换行到底有哪些区别呢?今天咱们就来总结一下. 1. ...

  3. [USACO4.3]逢低吸纳Buy Low, Buy Lower

    https://daniu.luogu.org/problemnew/show/2687 求方案数: if(f[j]+1==f[i] && a[j]>a[i]) s[i]+=s[ ...

  4. 重新找回spyder3-editor 里的code completion

    升级到spyder3之后, 突然丢失了code autocompletion在editor context里. 觉得太不爽了. 虽然在ipython窗格里TAB键的自动完成功能依然完好. 仔细观察 T ...

  5. JavaScript中函数和构造函数的区别

    构造函数也是函数 构造函数和其它函数的唯一区别: 构造函数是通过new操作符来调用的. 也就是说如果构造函数不用new操作符来调用,那它就是普通函数,反过来说任何函数通过new操作符来调用就可以当做构 ...

  6. 《PHP和MySQL Web开发》读书笔记(下篇)

    又与大家见面了.继续<PHP和MySQL Web开发>的总结. Chapter8.设计Web数据库 ·回去看看数据卡那本书吧,这里就不累赘谈这个东西. Chapter9.创建Web数据库 ...

  7. 【leetcode 简单】 第九十五题 数字转换为十六进制数

    给定一个整数,编写一个算法将这个数转换为十六进制数.对于负整数,我们通常使用 补码运算 方法. 注意: 十六进制中所有字母(a-f)都必须是小写. 十六进制字符串中不能包含多余的前导零.如果要转化的数 ...

  8. Python练习-跨目录调用模块

    层级结构: dir1 ---hello.py dir2 ---main.py 其中,hello.py: def add(x,y): return x+y main.py如何能调用到hello.py中的 ...

  9. 【译】第一篇 Replication:复制简介

    本篇文章是SQL Server Replication系列的第一篇,详细内容请参考原文. 复制这个词来自拉丁语中的"replicare",意味着重复.Replication des ...

  10. JS设计模式——12.装饰者模式

    装饰者模式概述 本章讨论的是一种为对象添加特性的技术,她并不使用创建新子类这种手段. 装饰者模式可以用来透明的把对象包装在具有同样接口的另一个对象中.这样一来,就可以给一个方法添加一些行为,然后将方法 ...