this
call-stack & call-site
调用栈:函数调用的顺序。
调用点:调用函数时所在的调用栈。
结合下面这个示例,可以更好的理解这两个概念。
1 | function foo1 () { |
this的指向
this的指向是由函数运行是的调用点决定的。并且是遵循下面四个规则:
默认绑定
默认绑定会将this只想全局作用域,如果是在严格模式下,则不会只想全局作用域。同时,默认绑定是四个绑定规则中,优先级最低的。即,如果有其他形式的绑定规则出现,就不会使用默认绑定了。
下面是示例:
1
2
3
4
5
6
7
8// 全局作用域
function foo () {
console.log(this.a);
}
var a = 1;
foo(); // 1如果是严格模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 全局作用域
;
function foo () {
console.log(this.a);
}
var a = 1;
try {
foo();
} catch (err) {
console.log(err); // TypeError: Cannot read property 'a' of undefined
}隐式绑定
隐式绑定是指一个对象调用它内部的函数时,被调用的函数内部的this指向这个对象。
1
2
3
4
5
6
7
8
9
10function foo () {
console.log(this.a);
}
let obj = {
a: 1,
foo: foo
}
obj.foo(); // 隐式绑定,this指向obj,所以打印的是 1链式隐式绑定时,this指向的是最后一个调用函数的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function foo () {
console.log(this.a);
}
let obj1 = {
a: 1,
foo: foo
}
let obj2 = {
a: 2,
obj1: obj1
}
obj2.obj1.foo(); // this指向最后一个调用函数的对象obj1,打印的结果是 1隐式绑定丢失:
隐式绑定必须用
obj.func
的形式调用,如果期间发生引用赋值,被赋值的对象会发生隐式绑定丢失,因为被赋值的对象其实只是拿到了函数的引用,最后调用的时候使用的默认绑定的规则。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let obj = {
a: 'obj',
foo: function () { console.log(this.a); }
}
var a = 'global';
let bar = obj.foo; // 隐式绑定丢失
function baz(fn) {
fn(); // 默认绑定
}
obj.foo(); // 隐式绑定,打印结果:obj
bar(); // 默认绑定,打印结果:global
baz(obj.foo); // 隐式绑定丢失,打印结果:global如果foo是箭头函数咋整?箭头函数没有自己的this指针,而是与外层的this绑定在一起的,所以这个时候就需要确定外层的this的绑定。比如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function foo1 () {
console.log(this.a);
}
const foo2 = () => {
console.log(this.a); // this指向foo2所在的作用域,没有a的变量声明,打印结果是undefined
};
const obj = {
a: 1,
foo1,
foo2
};
obj.foo1(); // 隐式绑定,this指向obj,所以打印的是 1
obj.foo2(); // undefined更多细节在:YDKJSY 3-2, P26
显示绑定
显示绑定是通过apply、call、bind来实现的,可以指定this需要指向的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13function foo () {
console.log(this.a);
}
const obj1 = {
a: 1
}
const obj2 = {
a: 2
}
foo.call(obj1); // 1,将foo中的this指向obj1
foo.apply(obj2); // 2,将foo中的this指向obj2显然每次使用foo的时候都需要调用一次,如果this指向的对象是可变的,倒也还好。如果this指向的是固定不变的,这个时候apply和call就不方便了。所以,这个时候需要用bind。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function foo () {
console.log(this.a);
}
const obj1 = {
a: 1
}
const obj2 = {
a: 2
}
const bar = foo.bind(obj1);
bar(); // 1,这个时候不会发生类似隐式绑定的this丢失,this始终指向obj
bar.call(obj2); // 2,即使再用apply、bind也不会改变this的指向了具体可以看一下bind是如何用aplly、call实现的。
new 绑定
new
运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。new
关键字会进行如下的操作:- 创建一个空对象
- 将这个的对象的__ proto __指向构造函数的prototype
- 将构造函数的this指向新对象
- 如果构造函数有返回值,就返回值,没有这个新对象。
1
2
3
4
5
6
7
8
9
10
11
12
13function foo (a) {
this.a = a;
}
foo.prototype.name = 'foo';
foo.prototype.sayName = function () {
console.log(this.name);
};
const bar = new foo(2); // bar.__proto__ = foo.prototype
console.log(bar.a); // 2
console.log(bar.name); // foo
bar.sayName(); // foo
绑定的优先级
new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
如果显示绑定使用的传入的this指向是null
或者undefined
的话,就会退化成默认绑定。
1 | function foo () { |
apply实现bind
1 | if (!Function.prototype.bind) { |
由此可见,bind是返回了一个始终func.apply(args)
的函数,所以每次调用的时候,才不用再次显示调用apply了。
原文作者: Billy & Barney
原文链接: https://liangbilin.github.io/2020/02/11/Barney--this/
版权声明: 转载请注明出处(必须保留作者署名及链接)