关于this
this是一个很特别的关键字,被自动定义在所有函数的作用域中。
当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的 其中一个属性,会在函数执行的过程中用到。
this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调 用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
默认绑定
默认绑定,在不能应用其它绑定规则时使用的默认规则,通常是独立函数调用。
function foo () {
console.log(this.a);
}
var a = 10;
foo();
在调用foo()时,应用了默认绑定,this指向全局对象(非严格模式下),严格模式下,this指向undefined,undefined上没有this对象,会抛出错误。
上面的代码,如果在浏览器环境中运行,那么结果就是10
使用严格模式:
"use strict";
function foo () {
console.log(this.a);
}
var a = 10;
foo();
注意:
-
开启了严格模式,只是说使得函数内的
this
指向undefined
,它并不会改变全局中this
的指向。 -
另外,它也不会阻止
a
被绑定到window
对象上。
还有种情况也需要注意:
1. 定义在函数内部的函数,不带任何修饰的函数引用进行调用
function foo () {
var a = 100;
console.log(this.a);
function inner1 () {
console.log('---inner1 start---');
console.log(this);
console.log(this.a);
}
var inner2 = function () {
console.log('---inner2 start---');
console.log(this);
console.log(this.a);
}
inner1();
inner2();
}
var a = 10;
foo();
inner1, inner2函数虽然是在foo内部定义的,但是它仍然是一个普通的函数,this仍然指向window。 输出结果为:
输出结果:
2. 赋值表达式
function foo () {
console.log(this.a);
}
var a = 2;
var o = {a: 3, foo: foo};
var p = {a: 4};
o.foo(); // 3
(p.foo = o.foo)(); // 2
赋值表达式p.foo = o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo()。此处也是应用默认绑定。
隐式绑定
如果函数作为对象的一个属性时,并且作为对象的一个属性被调用时,函数中的this指向该对象。
function foo () {
console.log(this.a);
}
var obj = {
a: 100,
foo: foo
}
var a = 10;
obj.foo(); // 100
函数foo()
虽然是定义在window
下,但是我在obj
对象中引用了它,并将它重新赋值到obj.foo
上。且调用它的是obj
对象,因此打印出来的this.a
应该是obj
中的a
当函数引用有上下文对象时, 隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。
function foo () {
console.log(this.a);
};
var obj2 = {
a: 200,
foo: foo
};
var obj1 = {
a: 100,
obj2: obj2
};
var a = 10;
obj1.obj2.foo(); // 200
隐式绑定的隐式丢失问题
1. 赋值表达式
function foo () {
console.log(this.a);
}
var obj1 = {
a: 1,
foo: foo
}
var a = 'global';
var fn = obj1.foo; // 函数别名
var obj2 = {
a: 2,
foo: obj1.foo
}
obj1.foo(); // 1
fn(); // 'global' this指的是window
obj2.foo(); // 2 this指得是obj2
obj1.foo是引用属性,赋值给fn的实际上就是foo函数(即:fn指向foo本身)。
那么,实际的调用关系是:通过fn找到foo函数,进行调用。整个调用过程并没有obj的参数,所以是默认绑定,全局属性a。
2. 函数当成参数传递
function foo () {
console.log(this.a);
}
function bar (fn) {
console.log(this);
fn();
};
var obj = { a: 100, foo: foo };
var a = 10;
bar(obj.foo);
// this -> window 所以this.a是10
foo, bar函数是在全局作用域下定义的,还是相当于默认调用,所以this指向window
函数传入内置的setTimeout中
function foo () {
console.log(this.a);
}
var obj = {
a: 100,
foo: function () {
setTimeout(function() {
console.log(this.a);
}, 100)
}
};
var a = 10;
var obj2 = {
a: 20,
foo: foo
};
obj.foo(); // this -> window, 输出10
setTimeout(obj2.foo, 200); // this -> window, 输出10
setTimeout(function() {
obj2.foo();
}, 300);
// this -> obj2, 输出20
- 第一个输出很好理解,setTimeout的回调函数中,this使用的是默认绑定,非严格模式下,执行的是全局对象
- 第二个可以理解为将obj2.foo赋值给了一个变量,最后执行了变量,这个时候,foo中的this显然和obj2就没有关系了
- 第三个,隐式绑定,因此这是this指向的是obj2,跟当前的作用域没有任何关系
显式绑定
相对隐式绑定,this值在调用过程中会动态变化,可是我们就想绑定指定的对象,这时就用到了显示绑定。
显示绑定主要是通过改变对象的prototype关联对象,可以通过call()、apply()
方法直接指定this
的绑定对象
两者区别: call
接收若干个参数,而apply
接收的是一个数组
function foo () {
console.log(this.a);
}
var obj = { a: 100, foo: foo };
var a = 10;
var obj2 = {a: 20, foo: foo};
var fn = obj2.foo;
foo(); // 10
foo.call(obj); // this指向obj, 输出100
fn.apply(obj2); // this指向obj2, 输出20
fn.call(undefined); // this指向window, 输出10
obj2.foo.call(obj); // this指向obj, 输出100
bind硬绑定,与apply,call区别:
- 使用
.call()
或者.apply()
的函数是会直接执行的 bind()
是创建一个新的函数,需要手动调用才会执行
function foo () {
console.log(this.a);
}
var obj = { a: 100, foo: foo };
var a = 10;
var obj2 = {a: 20, foo: foo};
var obj3 = {a: 30, foo: foo};
var test = function (fn) {
fn();
}
var bar = function (fn) {
fn.call(this);
}
test.call(obj, foo); // this指向window, 输出10;
bar.call(obj2, foo); // this硬绑定为obj2, 输出20
obj1.foo.bind(obj2)(); // this硬绑定为obj2, 输出20
obj1.foo.bind(obj2).call(obj3); // this硬绑定为obj2, 输出20
总结:
- 使用
.call()
或者.apply()
的函数是会直接执行的 bind()
是创建一个新的函数,需要手动调用才会执行- 如果
call、apply、bind
接收到的第一个参数是空或者null、undefined
的话,则会忽略这个参数
new 绑定
js中的new操作符,和其他语言中(如JAVA)的new机制是不一样的。js中,它就是一个普通函数调用,只是被new修饰了而已。
使用new来调用函数,会自动执行如下操作:
- 创建一个空对象,构造函数中的this指向这个空对象
- 这个新对象被执行 [[原型]] 连接
- 执行构造函数方法,属性和方法被添加到this引用的对象中
- 如果构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象。
function createObj() {
// 先创建一个空对象
var obj = new Object();
// 获得构造函数
var constructorFn = [].shift.call(arguments); // 利用数组的shift方法
//链接到构造函数的原型
obj.__proto__ = constructorFn.prototype; // obj可以访问到构造函数原型中的属性
var result = constructorFn.apply(obj, arguments); // 改变this指向为新创建的obj, 这样可以访问到构造函数中的属性
return typeof result == "object" && result != null ? result: obj;
}
function foo (a) {
this.a = a;
return 1;
}
function bar (a) {
this.a = a;
return {};
}
var a = 10;
var f1 = new foo(100);
var b1 = new bar(2);
console.log(f1.a); // 100
console.log(b1.a); // undefined
箭头函数
箭头函数里面的this
是由外层作用域来决定的(箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined。),且指向函数定义时的this而非执行时。
箭头函数在使用时,需要注意以下几点:
1. 函数体内的this对象,继承的是外层代码块的this。
2. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
4. 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
5. 箭头函数没有自己的this,所以不能用call()、apply()、bind()这些方法去改变this的指向.
var a = 10;
var obj = {
a: 100,
foo1: () => {
console.log(this.a);
},
foo2: function() {
console.log(this.a);
return () => {
console.log(this.a);
}
}
};
obj.foo1(); // 10
obj.foo2()(); // 100 100
解析:
- 对于
obj.foo1()
函数的调用,它的外层作用域是window
,对象obj
当然不属于作用域了(我们知道作用域只有全局作用域window
和局部作用域函数) -
obj.foo2()()
,首先会执行obj.foo2()
,这不是个箭头函数,所以它里面的this
是调用它的obj
对象,因此打印出obj.a为100
,而返回的匿名函数是一个箭头函数,它的this
由外层作用域决定,那也就是函数foo2
咯,那也就是它的this
会和foo2
函数里的this
一样,就也打印出了obj.a
优先级
判断this
1. 是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象;需要注意的是构造函数中没有返回其它对象,那么返回this,即创建的这个的新对象,否则,返回构造函数中返回的对象
2. 是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是 指定的对象;需要注意的是在显示绑定中,对于null和undefined的绑定将不会生效;实际上会进行默认绑定,导致函数中可能会使用到全局变量
3. 是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象;
4. 都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象;
5. 如果是箭头函数,箭头函数的this是定义时上层作用域中的
this
(由外层作用域决定)
参考资料: