# 使用 this 的原因
在编写代码的时候,我们不得不经常使用当前的上下文对象来做一些事情,比如说对象赋值、调用方法。如果我们不使用 this,就需要经常传入一个上下文对象,非常的繁杂,并且随着使用模式和代码量的增长,显式传递上下文对象会让代码变得越来越混乱。
但是 this 提供一种更 “优雅” 的方式来隐式传递一个上下文对象。
# this 到底是什么
== 当一个函数被调用时,会创建一个执行上下文。== 这个上下文包含函数在哪里被调用、函数的调用方法、参数等信息,而 this 就是上下文的其中一个属性。
# this 的绑定机制
通常来说,this 通常时运行时绑定的,但在我的理解中,还有一种特殊情况,就是箭头函数。
箭头函数中的 this 会自动捕获定义时外层最近的上下文环境,而非运行时绑定。
# 绑定规则
this 是在函数被调用时绑定的,完全取决于函数的调用位置。
function add(a,b){ | |
return a+b; | |
} | |
add(1,2); |
所谓的调用位置,就像上述代码中 add 被调用的地方,它所在的执行上下文,才决定 this 的绑定。
# 默认绑定
默认绑定,就是独立函数调用时候的绑定方式。
来看这么一段代码
function foo(){ | |
console.log(this.a); | |
} | |
var a = 2; | |
foo(); // 会输出 2 |
foo 是不带任何修饰的函数调用,在非严格模式下,这个情况会归为默认绑定,自动绑定到全局对象处。
在严格模式下,全局对象无法使用默认绑定,这个情况下 this 会绑定到 undefined。
# 隐式绑定
看调用位置的上下文对象。
某个函数可能被某个对象包裹。简单的说,这个函数可能定义在某个对象内部,是某个对象的方法。
例:
function foo(){ | |
console.log(this.a); | |
} | |
var obj = { | |
a : 2, | |
foo: foo | |
}; | |
obj.foo(); //2 |
这也是非常常见的一种绑定方式,但是这种绑定方式有一个地方一定要小心,就是异步(回调函数的绑定问题)。异步的隐式绑定有可能会丢失 this,为了避免错误,后面会说到显式绑定的方法。
# 显式绑定
不多说,直接上代码
function foo(){ | |
console.log(this.a); | |
} | |
var obj = { | |
a:2 | |
} | |
foo.call(obj);//2 |
显式绑定是一种硬绑定,它会将函数的 this 强制绑定到某个上下文环境(对象)。
利用 call、apply 方法可以将函数绑定到某个上下文对象中并执行,它们的区别只是接收参数的形式不同,具体实现方式见附录。
但有时候我们只是想强制绑定 this,并不想马上执行,为了应对这一情况,es5 中提供了内置的方法 bind。
var bar = foo.bind(obj); |
这时候的 bar 就是 foo 强制绑定 obj 环境后的函数。
# new 绑定
使用 new 来调用某个函数的时候,会构造一个新对象,并且这个新对象会绑定到函数的 this。
# 优先级
上述四种绑定规则,分别有各自的优先级,按照这个顺序进行判断:
# new 绑定:函数是否在 new 中调用?
# 显式绑定:是否通过 call、apply 绑定?
# 隐式绑定:是否在某个对象中调用?
# 默认绑定:都不是前三种情况,在非严格模式下为全局对象,严格模式下为 undefined
# 总结
this 是 JavaScript 学习者最重要的基本功之一,一定要切实理解并熟悉。
# 附录
# 1、手撕 call、apply 方法
Function.prototype.call = function(context){ | |
if(typeof this !== 'function'){ | |
throw new TypeError('error'); | |
} | |
context = context || window; | |
context.fn = this; | |
const args = [...arguments].slice(1); | |
const result = context.fn(...args); | |
delete context.fn; | |
return result; | |
} |
Function.prototype.apply = function(context){ | |
if(typeof this !== 'function'){ | |
throw new TypeError('error'); | |
} | |
context = context || window; | |
context.fn = this; | |
let result; | |
if(arguments[1]){ | |
result = context.fn(...arguments[1]); | |
}else{ | |
result = context.fn(); | |
} | |
delete context.fn; | |
return result; | |
} |