1. this
this是动态的,第一步☝️是明白this既不指向函数自身,也不指向函数的词法作用域,实际上是在函数被调用的时候发生的绑定,指向什么❓完全取决于函数在哪里被调用
2. 调用位置
那么我们需要找到函数的调用位置,就是要寻找“函数被调用的位置”
function baz() {
// 当前调用栈是baz
console.log("baz");
bar();// --> bar的调用位置,是在foo的作用域
}
function bar() {
// 当前调用栈是baz->bar
console.log("bar");
foo(); //--> foo的调用位置,是在bar的作用域
}
function foo(){
// 当前调用栈是baz->bar->foo
console.log("foo");
}
baz();//--> baz的调用位置,是在全局作用域
3. 绑定this的规则
默认绑定
function foo(){
console.log(this.a);
}
var a = 2;
foo();//2
上面👆调用foo()时,this.a被解析成了全局变量a,函数调用时this触发了默认绑定了,this指向了全局对象window
⚠️:在严格模式下(use strict),则不能将全局对象用于默认绑定,为导致this绑定到undefined
隐式绑定
这个规则是根据函数调用位置是否存在上下文对象而生效
"use strict";
function foo(){
console.log(this.a);
}
var obj1= {
a:2,
obj2:obj2
}
var obj2={
a:42,
foo:foo
}
obj1.obj2.foo(); //42
当foo()被调用的时候,函数前面被添加obj2的引用,这个时候就会引入了上下文的关系,this会绑定到这个上下文对象里面去
对象属性引用链中只有上一层或者说最后一层在调用位置起效
⚠️:当这个函数去赋值到一个变量,或者函数当作参数被传递之后,会触发隐式丢失
函数符赋值一个变量,会丢失原本的this绑定,设置是重新赋值到一个对象的属性
function foo() {
console.log(this.a)
}
var obj= {
a:2,
foo:foo
}
var bar = foo.foo; // 函数赋值
var a = "oops, global";
bar(); // "oops, global"
函数当参数传递,也会丢失原本的绑定
function foo() {
console.log(this.a)
}
var obj= {
a:2,
foo:foo
}
var a = "oops, global";
setTimeout(obj.foo, 100); // "oops, global"
显示绑定
能够处理this的绑定指向,就要用到.call(…)和.apply(…)
function foo(){
console.log(this.a)
}
var obj={a:2};
foo.call(obj); // 2
硬绑定,直接使用ES5提供的内置方法,Function.prototype.bind
function foo(something){
console.log(this.a, something);
return this.a + something;
}
var obj= {
a:2
}
var bar = foo.bind(obj);
var b = bar(3); //2 3
console.log(b);// 5
现在很多内置的函数(API),都提供了一个可选参数,通常被叫“上下文”,其作用和bind(…)一样,为了确保回调函数的this指向
function foo(el){
console.log(el, this.id);
}
var obj = {
id: "awesome"
}
[0,1,2].forEach(foo, obj);// 0 awesome 1 awesome 2 awesome
// 后面的可选参数,就是将this的绑定到要使用的对象上面👆去
new绑定
new操作符调用一个普通函数而已
function Foo(a){
this.a = a;
}
var bar = new Foo(2);
console.log(bar.a); // 2
使用一个new操作符来调用函数,或者说发生构造函数的调用,执行以下的步骤:
- 创建(构造)一个全新的对象
- 这个新对象会被执行[Prototype]连接
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
4. 被忽略的this
如果你把null或者undefined作为this的绑定对象传入到call,apply或者bind,在调用的时候会被忽略掉,直接变成默认绑定规则,绑到全局对象里面去
主要应用在哪里,就是使用apply(…)来展开一个数组,并当作参数出入一个函数,当然了,现在有ES6的语法…介入,可以使用foo(…[2,3]),扩展数组
function foo(a,b){
console.log(`a:${a},b:${b}`);
}
// 把数组展开成参数
foo.apply(null,[2,3]); //a:2,b:3
// 使用bind进行柯里化
var bar = foo.bind(null,2)
bar(3); //a:2,b:3
⚠️:这种不好的地方是将this绑定到全局对象,后面涉及到修改全局对象会许多难以分析和追踪的bug
所以可以更安全的做法是创造一个空的非委托的对象,利用Object.create(null),这个比{}”更空”
function foo(a,b){
console.log(`a:${a},b:${b}`);
}
// 创建空的非委托对象(ø一个开发者默认的符号,Mac下 option+o)
var ø = Object.create(null);
// 把数组展开
foo.apply(ø,[2,3]);//a:2,b:3
5. 软绑定
主要是解决硬绑定带来函数的灵活性,使用了硬绑定之后无法使用隐式绑定或者硬绑定了
所以主要思路是,封装个软绑定的函数,首先检查调用时的this,如果this绑定到全局对象或者undefined,那就把指定的默认对象obj绑定到this,否则不会修改this
6. this词法
箭头函数的this应用,它的this不受用在四种标准规则,只由外层的(函数或者全局)作用域来决定this
function foo() {
// 返回一个箭头函数
return (a)=>{
// this的继承自foo();
console.log(this.a);
}
}
var obj1={a:2};
var obj2={a:3};
var bar = foo.call(obj1);
bar.call(obj2);//2, 不是3!!!!
箭头函数最常用于回调函数,例如,事件处理器或者定时器⏲
function foo(){
setTimeout(() => {
// 这里的this在词法上面继承foo()
console.log(this.a);
},100)
}
var obj = {a:2};
foo.call(obj);// 2
提个醒,当你需要编写this风格代码的时候,不要和词法作用域风格混在一起使用
- 只使用词法作用域的并完全抛弃错误的this风格代码
- 完全采用this风格,在必要时使用bind(…),尽量避免使用self=this或者箭头函数