What is "this"?
作用域和闭包
相信接触过编程的人,大多都是知道作用域的,像下面两个关于全局变量和本地变量的例子:
1 | //定义全局变量 |
上面输出: Hello Minary. Wow, you are 21 years old.
1 | //定义本地变量 |
上面的例子会报 name is not defined 的错误。因为尝试从全局范围中获取本地变量。
作用域是闭包实现的关键,下面是 wiki 对闭包和作用域的解释:
In computer science, a closure is a first-class function with free variables that are bound in the lexical environment. Such a function is said to be “closed over” its free variables. A closure is defined within the scope of its free variables, and the extent of those variables is at least as long as the lifetime of the closure itself.
什么意思呢? 可以看下面的例子:
1 | var name = "outer"; |
也就是说本地作用域可以访问全局变量, 但是全局作用域不可以访问本地变量。 本地变量可以重新定义一个和全局变量一样名字的变量, 这时候再在本地作用域内访问像上面 name 一样的变量时,全局的 name 被本地覆盖。 但是出了这个本地作用域, 全局变量仍未改变。
那么要怎么访问本地作用域里的变量呢?看下面这个例子:
1 | // 新建一个函数并 return 一个闭包函数 |
name 和 age 变量是 MyModule 函数里的本地变量, 但是我们在全局作用域里通过执行 greeter 来访问,就不会报错。
这是因为 greet 函数的作用域里本来就有 name 和 age 这两个变量,所以直接访问不会报错。
基本上变量都是从它的作用域里获取所请求的变量。
this
除了作用域,JavaScript 增加了另一层特殊的作用域,通过特殊的关键字 this 来实现。这个关键字的用法除了不能被修改,其他看起来和普通的变量一样。
它作为一个对象,你可以通过正常点或括号,得到它的属性。神奇的是, this 的值取决于调用它的情况。一般情况下,它的值就是接收到的信息。例如:
1 | var Person = { |
上面的代码看起来 this 几乎像其他语言中的对象, 这说明被他骗了。作为这个 Person 对象的创造者, 你没有把握 this 会和 Person 一样, 比方说, 想在把 greeting 函数把保存在其他地方:
1 | var greeting = Person.greeting; |
this.name 和 this.age 就会为 undefined, 这是因为在 greeting 函数现在在全局对象里, 而不是 Person 对象里。 因此这里的 this.name 和 this.age 是在全局范围里找, 所以是找不到的。
所以下面这个例子就可以了:
1 | var Dog = { |
因为这时候 this 会在 Dog 作用域里找。
驯服 this
先看下面的例子:
1 | var Alien = { |
对比一下之前的例子, 上面这个 Alien 对象里没有半句关于 greeting 函数, 但是我们仍旧可以使用它。
这是因为我们 call 了 Person.greeting 函数, 但是却把 Alicn 的值作为 this 注入。
我们还可以用 apply ,用法和上面的例子类似在没有额外的参数下。
让我们写个类函数可以被其他包含有 name 和 age 的对象使用:
1 | function makeOlder(years, newname) { |
我们可以通过 call 或者 apply 来调用它:
1 | makeOlder.call(Person, 2, "Old Tim"); |
绑定 “this”
有时候我们更喜欢OPP风格的代码并想让JS也这样做。 我们并不喜欢 this 的改变取决于调用的时间。下面一个jQuery的简单例子:
1 | Cart = { |
虽然上面的看起来不错,其实有个大坑等着你。 尽管已经有了 Cart.onClick ,我们先不调用它。 jQuery代码将接受的是一些参数,在这一点上它没有办法知道 onClick 是来自 Cart 的对象。你的 this 最后并不会像你期望的那样被called。
结合之前的闭包和作用域的知识让这个 this 就像它在大多数面向对象的语言那样。
1 | $("#mybutton").click(function () { Cart.onClick() }); |
我们创建了一个闭包然后调用 Cart.onClick() , 现在没有任何的参数和返回值, 可以改成下面这样:
1 | $("#mybutton").click(function () { return Cart.onClick.apply(Cart, arguments) }); |
现在已经成功了,但是如果你不知道 arguments 是另一个关键字(一个类似数组的对象包含了当前最内部函数的参数
)代码就会变得难度难懂。
如果 Cart 是个全局访问的单个对象,我们可以只使用变量 Cart 而不是依靠 this 。 但往往并不会这样,当你有“类”的对象共享的功能。
可以按下面那样修改 Cart.onClick 来使这个 this 一直在 Cart 里。
1 | function bind(fn, scope) { |
这里有很多种方式都能实现这样的效果,事实上他不是最好的解决方法。在上面,我们创建了一个闭包并把范围嵌在里面, 然后我们通过绑定闭包和使用 apply 自动传入参数并且自动返回值来替换 Cart.onClick 。
link
http://howtonode.org/what-is-this