在每个方法中,关键字 this 表示隐式参数。 —— 《Java 核心技术 卷Ⅰ》javascript
了解 python 的同窗可能会知道,python 构造函数中老是会出现 self 参数。这个参数用来表示建立的实例对象。java
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
复制代码
在 JavaScript 和 Java 中这个参数被隐藏了。咱们没必要在参数列表中显式声明这个参数,就能够在函数中使用这个参数。这个参数就是 this 。python
function Student(name, score){
this.name = name
this.score = score
}
var studentA = new Student('a', 100)
console.log(studentA.name, studentA.score) // a 100
复制代码
援引 《Java 核心技术 卷Ⅰ》 中的一句话:在每个方法中,关键字 this 表示隐式参数。 所谓的隐式参数,就是没有在参数列表中显式声明的参数。隐式参数和参数列表中定义的显式参数统称为形式参数。与形式参数相对应的是实际参数。bash
形式参数,简称形参。形参就是在定义函数的时候使用的参数,用来接收调用该函数时传递的参数。如上述代码中的 name ,score 参数都是形参。闭包
实际参数,简称实参,实参就是调用该函数时传递的参数。如上述代码中的 'a' , 100 都是实参。app
《 你不知道的JavaScript(上卷)》中提了一个问题,问:为何采用词法做用域的 JavaScript 中的 this 的值是在调用时肯定的?函数
在理解了形参和实参以后,咱们便能很好地理解这个问题了。oop
由于 this 是一个形参,形参的值是由实参决定的。而传参这个操做时在调用时发生的,因此 this 的值是在调用时肯定的。ui
既然 this 的值是由实参的值决定的,那么这个实参的值究竟是什么呢?this
参考 《Java 核心技术 卷Ⅰ》 中的一句话:隐式参数的值是出如今函数名以前的对象。看成为构造函数时,this 用来表示建立的实例对象。来看两个例子:
function bar () {
console.log(this.name)
}
var foo = {
name: 'foo',
bar: bar
}
foo.bar() // foo
复制代码
this 指向函数名(bar)以前的 foo 对象
function Student(name, score){
this.name = name
this.score = score
}
var studentA = new Student('a', 100)
console.log(studentA.name, studentA.score) // a 100
复制代码
this 指向建立的实例对象 studentA
JavaScript 也提供了几个函数去改变 this 的值。这几个函数都会返回一个原函数的拷贝,并在这个拷贝上传递 this 的值。因此从结果上看,咱们能够看到原有的 this 会被覆盖。
function bar () {
console.log(this.name)
}
var foo = {
name: 'foo',
bar: bar
}
var obj = {
name: 'obj',
}
foo.bar.call(obj) // obj
复制代码
this 指向新的对象 obj 。
《 你不知道的JavaScript(上卷)》中描述了一种现象:this 丢失了原来的绑定对象,指向了全局对象。书中称为隐式丢失。来看示例:
function foo() {
console.log( this.a )
}
var obj = {
a: 2,
foo: foo
}
var bar = obj.foo // 赋值操做
var a = "oops, global"
bar() // "oops, global"
复制代码
JavaScript 只有值传递,没有引用传递。在赋值操做的时候,实际上是将一个引用的拷贝赋值给另一个变量。var bar = obj.foo
在这个语句中,没有传参操做,因此 this 的值是由 bar 函数在调用时传递的那个实参决定的。这个实参如未显式指定,那么即是指向全局对象。因此上述代码中的 this 指向了全局对象。
一样的,咱们在函数传参的过程当中,常常发现隐式丢失问题,缘由也是中间发生了一次赋值操做。代码示例以下:
var name = 'global'
function bar () {
console.log(this.name)
}
var foo = {
name: 'foo',
bar: bar
}
function callFunc(func){
func()
}
callFunc(foo.bar) // global
复制代码
在传参的过程当中,发生了func = foo.bar
的赋值操做,致使最后 this 的值指向了全局对象。
可是若是咱们使用 bind 绑定了 this 的值,那么在发生赋值操做时,this 的值将再也不改变。来看下面例子。
bind 和 call ,apply 有一点不一样的是 call,apply 返回的是调用结果,而 bind 返回的是绑定 this 后的函数对象。那么当绑定 this 后的函数做为实参传入函数时,与未绑定 this 的结果就彻底不一样了。
来看下面的例子。
var name = 'global'
function bar () {
console.log(this.name)
}
var foo = {
name: 'foo',
bar: bar
}
function callFunc(func){
func()
}
callFunc(foo.bar.bind(foo)) // foo
复制代码
将 bar 函数中的 this 绑定到 foo 再传入 callFunc 函数中,最后打印的结果是 foo 。
实际上, bind 函数内部维护了一个闭包,使得调用始终发生在函数内部,来保证 this 的值不变。来看 MDN 提供的 ployfill
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable')
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)))
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype
}
fBound.prototype = new fNOP()
return fBound
}
}
复制代码
// return 部分
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)))
复制代码
在 return 的时候使用了 apply 函数来改变 this ,若未发生 new 操做,那么这个 this 的值将绑定到 bind 函数提供的那个对象。
当发生 new 操做时,this 将绑定到这个实例对象。 从上面这个 ploy fill 能够看出 new 操做中的 this 值会覆盖原有 this 的值。来看例子
function bar () {
this.name = 'bar'
}
var foo = {
name: 'foo',
}
var a = bar.bind(foo)
a()
console.log(foo.name) // bar
var b = new a()
console.log(b.name) // bar
复制代码
当执行 new 操做以前,a 函数中的 this 指向 foo。当执行 new 操做以后,a 函数中的 this 指向了 b 。
new 操做会返回一个从新绑定 this 后的新对象。因此当发生 new 操做以后,原有的 this 发生了改变。具体步骤以下:
箭头函数中的 this 继承了父做用域的 this。
var name = 'global'
var foo = {
name: 'foo',
bar: () => {
console.log(this.name)
}
}
foo.bar() // global
复制代码
箭头函数的父做用域为全局做用域,全局做用域的 this 指向全局对象,因此 this 指向了全局对象。
var name = 'global'
var foo = {
name: 'foo',
bar: function () {
setTimeout(() => {
console.log(this.name)
},100)
}
}
foo.bar() // foo
复制代码
箭头函数的父做用域为 bar 函数,在调用时,父做用域 bar 函数中的 this 指向了 foo 函数,因此箭头函数中的 this 指向了 foo 。
严格模式下禁止 this 指向全局对象。在严格模式下当 this 指向全局对象的时候会变成 undefined 。