前段时间做了些H5的日常需求,详见全球精选。觉得学好JS还是很有必要的,不管它是不是世界上最好的语言。Github上看到一本书You Dont know JS,这本书介绍的概念算是比较清楚的。
这周主要介绍JS的this
。
####this
先来做一个题目:
|
|
最后foo.count等于几呢 ?
答案是0。如果对this的概念不清楚的话一般会认为答案是10。
为什么呢?因为在for
循环里调用foo(i)
时,this
指向的不是foo
。如果你在游览器的环境里。可以把这段代码复制到console里测试一下。
下面介绍几个方法,可以把foo.count结果输出为10。
- 把
this.count++
改为foo.count++
。 使用对象来包装,代码如下
1234567891011121314function foo(num) {console.log( "foo: " + num );this.count++;}var foo = {bar: foo,count: 0}var i;for (i=0; i<10; i++) {foo.bar(i)}在
for
循环里把foo(i)
改为foo.call(foo)
或者foo.apply(foo)
, 代码如下:123456789101112function foo(num) {console.log( "foo: " + num);this.count++;}foo.count = 0;var i;for (i=0; i<10; i++) {foo.call(foo, i); // 或者foo.apply(foo, [i]);}使用bind,代码如下:
12345678910111213function foo(num) {console.log( "foo: " + num );this.count++;}foo.count = 0;var i;var bar = foo.bind(foo);for (i=0; i<10; i++) {bar( i );}
为什么在for
循环里调用foo(i)
时,this
指向的不是foo
,它指向的是什么呢?
现在可以重新打开一下游览器的console,再输入最开始的那段代码。接着在console里输入foo.count
,结果应该是0,再继续看下去。
然后console
里输入window.count
,结果是NaN。那么什么时候改了window.count的属性呢?(window没有count属性,可以重新打开游览器console,输入windown.count,结果应该是undefined).
就是在for
循环里调用foo(i)
的时候。
再来看一下解决方案2,用对象包装的方式,foo.bar(i)
调用,最后调用者foo
的count
属性被改变了。所以this
的指向决定于调用方法的调用地点(后面用call-site来指代这个)。
理解call-site先来看个例子:
|
|
所以理解了这个,再做个实验。还是将第一段代码copy进console,但是先等等,先在console输入window.count=0
。然后再把第一段把copy进console。再输入window.count
,可以看到结果是10。所以foo
方法里的this
指向的是call-site对象也就是js context的global对象window
。
但是,除了call-site外,我们看到前面的解决方法3和4。bind
, apply
, call
。那么这些规则和call-site的优先级呢?除了这些规则还有什么规则。下面就人肉列举一下:
#####默认绑定
默认绑定就是call-site绑定,用的时候需要注意一下js的’use strict’。
|
|
因为a是全局对象,foo()的call-site(window)调用a的时候,window对象在strict模式下不存在。所以a找不到,不然就会打印出2.
#####隐式绑定
还是由call-site的位置决定,如下例:
|
|
有风险的是,很容易会造成隐式绑定丢失的问题,如下例:
|
|
虽然bar看上去是obj.foo的引用,但其实它最后指向的是foo, foo虽然是obj的对象,但不属于obj。
所以这里就走到了默认绑定规则上去了。
#####显式绑定
就是之前提到的方案3和4,这里顺便介绍一下这三个方法:
|
|
- call 用法:
foo.call(obj, arg1, arg2, arg3)
apply 用法:
foo.call(obj, [arg1, arg2, arg3])
两个方法都显示指定了foo方法里this所指的对象是obj。不同的是call对foo方法的参数是一个一个的传,而apply是把他们放到一个数组里一下子传进去。apply最后参数拿出来也是按传的顺序拿出来的。
bind 用法:
var bar = foo.bind(obj, arg1, arg2, arg3);
它返回一个和foo实现一样的新函数,其他和call一样。
#####New 绑定
|
|
抛开传统的面向对象概念,这段代码做了以下事情:
- 一个全新的对象被创建了。
- 新对象的this就是MyClass方法里的this。
- 除非MyClass里返回的是其他对象,不然就是this对象。
如下:
|
|
#####顺序
按照规则来,绑定的优先级顺序是new binding > explicit binding > implicit binding > default binding
#####间接寻址
|
|
这里(p.foo = o.foo)
返回的是foo
对象而不是p.foo
所以log了全局的a对象。
写成这样就没有问题了。
|
|
#####ES6 箭头函数的this
|
|
为什么是this绑定的是obj1的对象而不是obj2的对象呢?
foo.call(obj1)
的时候已经绑定了obj1
是它的this对象,也就是bar
的this对象是obj1
, 箭头函数在其作用域内一旦被绑定this,那它就永远不会被修改,即使是new操作符。
###总结
虽然this的规则挺多挺绕的,但是优雅的js代码离不开它。