当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this 就是这个记录的一个属性,会在函数执行的过程中用到。
# 为什么要用 this
随着你的使用模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱,使用 this 则不会这样
个人觉得 this 的最佳实践是:
- 在显而易见地使用某种设计模式时
- 在显而易见地使用面向对象技术的时
- 在处理原生 API 带来的副作用时
其他情况则一律不用,这三点其实是一个意思,尽量在有共识的实现中使用。
# this 全面解析
this 指向的是调用 this 所在的函数 (作用域) 的那个对象。所以只有在函数执行时才能确定 this 的指向。
更通俗的说,谁在使用这个作用域。this 就指向谁
function show () { | |
console.log(this) | |
} | |
window.show() // this => window or undefined | |
const a = { show: show } | |
const b = { display: show } | |
a.show() // this => a | |
b.display() // this => b |
function baz() { | |
bar() | |
} | |
function bar() { | |
foo() | |
} | |
function foo() { | |
console.log(this) | |
} | |
const o = { baz: baz, bar: bar, foo: foo } | |
baz() // this => window or undefined | |
o.baz() // this => window or undefined | |
o.bar() // this => window or undefined | |
o.foo() // this => o |
- 如果没有显式的对象对函数进行调用,则指向 window (严格模式下是 undefined)
- 严格模式下,即使通过 window 调用也会指向 undefined。这里有一种特殊情况就是代码声明不在严格模式中,但调用在严格模式中,this 的指向是
window
而不是undefined
# 绑定规则
# this 的绑定方式
1. 隐式绑定
函数被当做引用属性添加到某个对象中
function foo() { | |
console.log( this.a ); | |
} | |
var obj2 = { a: 42, foo: foo }; | |
var obj1 = { a: 2, obj2: obj2 }; | |
obj1.obj2.foo(); // 42 | |
// 隐式丢失【学习术语】 | |
var a = 3 | |
var baz = obj1.obj2.foo | |
baz() // 3 | |
setTimeout(obj1.obj2.foo, 100) // 3 |
解决隐式丢失的方法
- 使用箭头函数
- 使用硬绑定(特殊的显示绑定)
2. 显式绑定
call, apply
function foo() { | |
console.log( this.a ) | |
} | |
var a = 4 | |
var obj = { | |
a:2 | |
} | |
foo.call(obj) // 2 |
2.1 硬绑定:实际上是两层显式绑定 。这样调用时只能修改外层的绑定,不会影响到内层的绑定
function foo(something) { | |
console.log( this.a, something ) | |
return this.a + something | |
} | |
// 简单的辅助绑定函数 | |
function bind(fn, obj) { | |
// 无论外部怎么使用此函数 apply 都把 fn 绑定到了 obj 上 | |
return function() { | |
return fn.apply( obj, arguments ) | |
} | |
} |
3. new 绑定
function foo(a) { | |
this.a = a | |
} | |
var bar = new foo(2) | |
console.log( bar.a ) // 2 |
使用 new
来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
- 创建(或者说构造)一个全新的对象。
- 这个新对象会被执行
[[Prototype]]
连接。 - 这个新对象会绑定到函数调用的 this。
- 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。
# 绑定的优先级
从高到低 new 绑定
> 显式绑定
> 隐式绑定
> 默认绑定
var bar = new foo() // bar | |
var bar = foo.call(obj2) // foo | |
var bar = obj1.foo() // obj1 | |
var bar = foo() // global or undefined |
# 软绑定
软绑定用来解决,函数硬绑定之后。无法再进行隐式绑定的问题。
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 === (window || global)) ? | |
obj : this, | |
curried.concat.apply( curried, arguments ) | |
); | |
}; | |
bound.prototype = Object.create( fn.prototype ); | |
return bound; | |
}; | |
} |
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 <---- 应用了软绑定 |
# 关于箭头函数
箭头函数没有绑定,也无法通过任意方式修改绑定。其内部的 this
其实是指向上一层的函数作用域。
箭头函数与 this 是两种风格的代码风格,应该尽量避免使用一起使用