一, this 的绑定规则
针对于函数来来说, this 的绑定和函数声明的位置没有任何关系, 只取决于函数的调用方式, 它指向什么完全取决于函数在哪里被调用.
当一个函数被调用时, 会创建一个活动记录(有时候也称为执行上下文). 这个记录会包含函数在哪里被调用(调用栈), 函数的调用方式, 传入的参数等信息. this 就是这个记录的一个属性, 会在函数执行的过程中用到.
我们先来看下 this 的四条绑定规则, 然后解释多条规则都可用时它们的优先级如何排列.
1. 默认绑定
即独立函数调用.
- function foo(){
- console.log(this.a)
- }
- var a = 2;
- foo() // 2
在上述代码中, foo()是直接使用不带任何修饰的函数引用进行调用的, 即默认绑定到 Windows, 所以 this.a 等于 2.
如果 foo 内部使用严格模式, 则不能将全局对象用于默认绑定, this 会绑定到 undefind. 但是 foo 运行在严格模式下则不影响默认绑定.
2. 隐式绑定
隐式绑定需要看函数调用位置是否有上下文对象.
- function foo(){
- console.log(this.a)
- }
- var obj = {
- a:2,
- foo:foo
- }
- obj.foo() // 2
上面的代码片段中, 函数的调用位置会使用 obj 上下文来引用函数, 也就是说, 当 foo 被调用时, 它的前面加上了对 obj 的一弄, 当函数引用有上下文对象时, 隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象. 所以 this.a 就是 obj.a.
但是要注意不要掉入一些陷阱里面.
- function foo(){
- console.log(this.a)
- }
- var obj = {
- a:2,
- foo:foo
- }
- var bar = obj.foo()
- var a = 'oops,global' // a 是全局对象的属性
- bar() // oops,global
虽然 bar 是 obj.foo 的一个引用, 但是实际上, 它引用的是 foo 函数本身, 因此此时的 bar()其实是一个不带任何修饰的函数调用, 因此应用了默认绑定.
- function foo(){
- console.log(this.a)
- }
- function doFoo(fn){
- // fn 其实引用的是 foo
- fn() // 调用位置
- }
- var obj = {
- a:2,
- foo:foo
- }
- var a = 'oops,global' // a 是全局对象的属性
- doFoo(obj.foo() )
参数的传递其实就是一种隐性赋值, 所以这个例子跟上面的一样.
3. 显示绑定
JavaScript 提供的绝大多数函数以及你自己创建的所有函数都可以使用 call(..)和 apply(..)方法.
它们的第一个参数是一个对象, 是给 this 准备的, 接着在调用函数时将其绑定到 this. 两者的区别在于给函数传递参数的方式上.
- function foo(){
- console.log(this.a)
- }
- var obj = {
- a:2
- }
- foo.call(obj) // 2
通过 foo.call(..), 我们可以在调用 foo 时强制把它的 this 绑定到 obj 上.
通过 call 和 apply, 我们也可以解决隐式绑定中将函数传入回调函数中, this 绑定丢失的问题
- function foo(){
- console.log(this.a)
- }
- var obj = {
- a:2
- }
- var bar = function (){
- foo.call(obj)
- }
- bar() // 2
- setTimeout(bar, 100) // 2
- // 硬绑定的 bar 不可能再修改它的 this
- bar.call(Windows) // 2
这种绑定的常见应用场景, 创建一个可以重复使用的辅助函数
- function foo(somethisthing){
- console.log(this.a , something)
- return this.a + something
- }
- // 简单的辅助绑定函数
- function bind(fn,obj){
- return function(){
- return fn.apply(obj,arguments)
- }
- }
- var obj = {
- a:2
- }
- var bar = bind(foo,obj)
- var b = bar(3) // 2,3
- console.log(5)
4.new 绑定
JavaScript 中的 new 用来调用构造函数. 使用 new 来调用函数, 或者说发生构造函数调用时, 会自动执行下面的操作
1.创建 (或者说构造) 一个全新的对象.
2.这个新对象会被执行 [[Prototype]] 连接.
3.这个新对象会绑定到函数调用的 this.
4.如果函数没有返回其他对象, 那么 new 表达式中的函数调用会自动返回这个新对象.
- function foo(a){
- this.a = a
- }
- var bar = new foo(2)
- console.log(bar.a) // 2
使用 new 来调用 foo 时, 会构造一个新对象并把它绑定到 foo 调用中的 this 上.
二, 优先级
具体优先级的测试案例就不多说了, 推荐大家看原版书中写的很详细. 这里直接引用原文的结论.
判断 this, 按照如下顺序进行判断
1, 函数是否在 new 中调用(new 绑定)? 如果是的话 this 绑定的是最新创建的对象
var bar = new foo()
2, 函数是否通过 call,apply(显示绑定), 如果是的话, this 绑定的是指定的对象
var bar = foo.call(obj2)
3, 函数是否在某个上下文对象中调用(隐式绑定)? 如果是的话, this 绑定的是哪个上下文对象
var bar = obj1.foo()
4, 如果都不是的话, 使用默认绑定, 如果在严格模式下, 就绑定 undefind, 否则绑定到全局对象
var bar = foo()
三, 那些例外
凡事总有例外.
1, 如果把 null 或者 undefind 作为 this 的绑定对象传入 call,apply 或者 bind, 这些值在调用时会被忽略, 应用默认绑定规则.
- function foo(){
- console.log(this.a)
- }
- var a = 2;
- foo.call(null)
2, 创建了一个函数的'间接引用', 从而导致应用了默认规则
- function foo(){
- console.log(this.a)
- }
- var a = 2;
- var o = { a:3, foo:foo};
- o.foo() // 3
- (p.foo = o.foo)() // 2
赋值表达式 p.foo = o.foo 的返回值是目标函数的引用, 因此调用位置是 foo()而不是 p.foo()或者 o.foo(). 根据我们之前说过的, 这里会应用默认绑定.
3, 软绑定
给默认绑定指定一个全局对象和 undefind 以外的值, 实现和硬绑定相同的效果, 同时保留隐式绑定或者显示绑定修改 this 的能力.
- if(!Function.prototype.softBind){
- Function.prototype.softBind = function(obj){
- var fn = this;
- // 捕获所有 curried 参数
- var curried = [].slice.call(arguments,1)
- var bound = function(){
- return fn.apply( ( !this || this === (Windows || global)) ? obj : this , curried.concat.apply(curried,arguments)
- );
- }
- bound.prototype = Object.create(fn.prototype)
- return bound;
- }
- }
softBind(..)的其他原理和 ES5 内置的 bind(..)类似. 它会对指定的函数进行封装, 首先检查调用时的 this, 如果 this 绑定到全局对象或者 undefined, 那就把指定的默认对象 obj 绑定到 this, 否则不会修改 this.
- function foo(){
- console.log('name:'+this.name)
- }
- var obj = { name: 'obj'},obj2 = { name : 'obj2'}, obj3 = {name:'obj3'}
- var fooOBJ = foo.softBind(obj)
- fooOBJ() // name:obj
- obj2.foo = foo.softBind(obj)
- obj2.foo() // name:obj2
- fooOBJ.call(obj3); // name:obj3
- setTimeout(obj2.foo,10)
- // name:obj 应用了软绑定
可以看到, 软绑定版本的 foo()可以手动将 this 绑定到 obj2 或者 obj3 上, 但如果应用默认绑定, 则会将 this 绑定到 obj.
来源: http://www.jianshu.com/p/cfde6fce373c