1. 什么是 this(概念必不可少!!!)
-
this是 js 中的一个关键字
-
它是函数运行时,在函数体内部自动生成的一个对象,它只有在函数内部时才可以使用。this就是函数运行时所处的环境对象(即执行期上下文)
-
执行期上下文:所有的函数在被调用时都会创建一个执行期上下文,执行期上下文中记录着函数的调用栈、函数的调用方式等,而 this就是其中的一个属性
2. this 是什么时候绑定的
this是在函数被调用的时候绑定的 ! ! ! !
当没有调用函数的时候,this的指向是没有被绑定的,所以this的指向此时是无法判断的
例:
function test() {
console.log(this);
}
test(); //window,调用的时候才绑定的window
再看一个稍微复杂一点的例子:一个直接在定义里面调用,另一个存到外面之后再调用
function test() {
console.log(this);
}
var obj = {
name: '张三',
foo: test
};
obj.foo(); // 1. obj
var foo = obj.foo;
foo(); // 2. window
上面这个例子中都是同一个函数 test,但 this 的值一个是 obj,另一个是却是window呢
不知道有没有听过这样一个规则:谁调用,this就指向谁
所以第一个是 obj 调用的所以指向obj
第二个是将函数存到外面之后调用window.foo(), 所以指向window
知道这个规则可以解决遇到的大部分的this的指向问题,在没写这篇文章之前我凭借这个规则(咳咳咳,省略3000字)…
但是我们并不满足于此!!!
总结通过上面的例子我们可以得出的结论:
- this值的绑定和函数定义时的位置没有关系。
- this值的绑定和函数调用的方式有关,谁调用函数,this就绑定为谁(或者说是指向谁)。
- this是在运行时,才会别绑定的,光定义、不运行相当于不起作用。
我们来看一下this到底是如何绑定的,来深刻的理解谁调用,this就指向谁
3. this 是如何绑定的
绑定的方法大概有以下这几种
- 默认绑定,即函数独立调用,this 指向 window
- 隐式绑定,即函数通过某个对象进行调用,那么 this就指向这个调用的对象
- 显示绑定,即 call、apply、bind
- new绑定
a. 默认绑定
- 定义一个函数直接调用(函数单独的被调用,并没有绑定到那个对象上)
默认绑定的就是 window对象,例: window.test
// 1、函数直接被调用
function test() {`在这里插入代码片`
console.log(this)
}
test(); // window
- 一个函数调用另一个函数, 函数只是在另一个函数里面被单独的直接调用, 并没有绑定到那个对象上
所以还是默认绑定到 window 对象上。
function test1() {
console.log(this);
test2(); // window
}
function test2() {
console.log(this);
}
test1(); // window
- 将函数作为参数传递到另一个函数中
这个实际上跟第二个例子是类似的,函数 foo 里面调用了bar。
function foo(fun) {
fun();
}
function bar() {
console.log(this);
}
foo(bar); // window
b. 隐式绑定
-
在隐式绑定中,如果函数调用位置是在一串对象属性链中,this绑定的是最内层的对象。
-
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
例子:下面这个应该很熟悉,实际上这个 obj 就是 this 的上下文对象
var obj = {
name: 'obj',
foo() {
console.log(this);
}
};
obj.foo(); // obj
再看一个 obj 调用 obj 的例子:
var obj = {
name: 'obj',
foo() {
console.log(this)
}
};
var obj1 = {
name: 'obj1',
obj2: obj
};
obj1.obj2.foo(); // obj
乍一看不是很好理解,如果把它变成下面这个样子,是不是很好看出它的上下文对象是 obj2
var obj1 = {
name: 'obj1',
obj2: {
name: 'obj',
foo() {
console.log(this);
}
}
};
obj1.obj2.foo(); // obj
c. 显示绑定
为什么会有显示绑定?因为它是有限制的。
隐性绑定的限制:
如果上下文不包含我们的函数,用隐性绑定是要出错的,不可能每个对象都要加这个函数 ,那样的话扩展,维护性太差了。
如果说两个obj 都需要 foo方法 那么我们怎么办?
call apply bind
这里我们就要用到 js 给我们提供的函数 call 和 apply 它们的作用都是改变函数的this指向,第一个参数都是设置this对象。调用后直接执行。
function foo(){
console.log(this);
}
var obj = {
name: "Perry",
}
var obj1 = {
name: "Lisa",
}
foo(); // window
foo.call(obj) // obj
foo.call(obj1) // obj1
foo.apply(obj) // obj
foo.apply(obj1) // obj1
bind是将this绑定到某个obj上,但是不执行,需要我们自己手动的调用
let f = foo.bind(this对象, [参数])
f()
使用这三个显示绑定 this的注意点
- 使用过bind绑定this之后,再通过call、apply改变 this的绑定是无效的(例1)
- 原函数 foo,对其使用bind进行绑定后的返回的函数,再进行bind绑定是无效的(例2)
- 进行绑定时,如果this传的是null或undefined,那么此时绑定的 this是无效的(例3)
function foo() {
console.log(this);
}
var obj1 = {
name: 'obj1'
};
var obj2 = {
name: 'obj2'
}
// 例1
var bar = foo.bind(obj1);
bar(); // obj1
bar.call(obj2); // obj1
bar.apply(obj2); // obj1
// 例2
var bar = foo.bind(obj1);
bar(); // obj1
var bar1 = bar.bind(obj2);
bar1(); // obj1
// 例3
foo.call(null); //window
foo.apply(undefined); // window
var fun = foo.bind(null);
fun(); // window
d. new
function Foo() {
this.a = 10
}
let foo = new Foo()
console.log(foo.a);
实际上就是指向构造函数,与原型链有关
4. this绑定优先级
new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定
5. 箭头函数的this问题
箭头函数中的this指向的是定义时的this,而不是执行时的this。
箭头函数的 this 是不进行绑定的,即就是 通过call、apply、bind是不能改变其 this指向的
箭头函数的this是由定义时外围最接近一层的非箭头函数的 this来决定的,也就是平常所说的箭头函数的this是从上层作用域中去找的,一直找不到的话即为 window
var name = 'window';
var obj1 = {
name: 'obj1',
foo() {
return () => {
console.log(this.name);
}
}
};
var obj2 = {
name: 'obj2'
};
obj1.foo()(); // obj1
// 箭头函数的 this 是不能进行绑定的,它的 this 只会从上层作用域中去查找,所以给它进行绑定 this 是无效的
obj1.foo().call(obj2); //obj1
// 这里改变的是箭头函数上层作用域中的 this,也就是 foo 的 this,所以此时的 this指向的才是 obj2
obj1.foo.call(obj2)(); // obj2