判断this
可以按照以下顺序来进行判断:
- 函数是否在
new
中调用?如果是的话this
绑定的是新创建的对象。 - 函数是否通过
call()、appl()或bind()
绑定?如果是的话this
绑定的是指定的对象。 - 函数是否通过在某个上下文对象中调用?如果是的话
this
绑定的是那个上下文对象。 - 以上规则都不满足,使用默认绑定。如果在严格模式下,就绑定到
undefined
,否则绑定到全局对象。 - 自执行函数的this
都指向window
调用位置
在理解this
绑定过程之前,首先理解调用位置:调用位置是函数在代码中被调用的位置(而不是声明的位置)。每个函数的this
是在调用时被绑定的,完全取决于函数的调用位置(也就是函数的调用方法)。
function baz(){
// 当前的调用栈: baz
console.log("baz");
bar(); // bar的调用位置
}
function bar(){
// 当前的调用栈: baz -- bar
console.log("bar");
foo(); // foo的调用位置
}
function foo(){
// 当前的调用栈: baz -- bar -- foo
console.log("foo");
}
baz(); // baz的调用位置
绑定规则
1.new关键字绑定
在JavaScript
中,包括内置对象函数(比如Number(...)
、Array(...)
)在内的所有函数都可以用new操作符来调用,这种函数调用被称为构造函数调用。实际上并不存在所谓的"构造函数",只有对函数的"构造调用"。
使用new来调用函数,或者发生构造函数调用时,会自动执行下面的操作:
- 1.在内存中创建一个新对象。
- 2.这个新对象会被执行[[原型]]连接,即新对象内部的__proto__特性会被赋值为"构造函数"的prototype属性。
- 3."构造函数"内部的this被赋值为这个对象(即this指向新对象)
- 4.如果"构造函数"返回非空对象,则返回该对象;否则,返回刚创建的新对象。
function createNew(base,...args){
var obj = {};
obj.__proto__ = base.prototype;
let res = base.apply(obj, args);
return res instanceof Object ? res : obj;
}
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function () {
console.log(this.name)
}
const p = createNew(Person,'jackson');
console.log(p.name) // jackson
p.sayName(); // jackson
以上是在通过new
调用函数时发生的操作。通过new
关键字调用函数时进行了this
的绑定行为,思考下面的代码:
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
console.log(this.name);
}
}
let person1 = new Person("bob",20,"Student");
let person2 = new Person("Jack", 36, "Doctor");
person1.sayName(); //bob
console.log(person1.age); //20
person2.sayName(); //Jack
console.log(person2.job); //Doctor
new
是一种可以影响函数调用时this
绑定行为的方法。使用new
来调用Person(...)
时,我们会创建一个新对象并把它绑定到Person(...)
调用中的this
上。
2.call()、apply()或bind()绑定调用
call()、apply()
var a = 11;
function foo(){
console.log(this.a);
}
const obj = {
a: 28
}
foo(); //11,this指向window
foo.call(obj); //28,this指向obj
通过foo.call(...)
,我们可以在调用foo
时强制把它的this
绑定到obj
上。call()
和apply()
它们的第一个参数是一个对象,它们会把这个对象绑定到this
,接着在调用这个函数时指定这个this
。
fun.call(obj, arg1, arg2, arg3,...); // call的第二个参数是数组里的元素,逐一列举
fun.apply(obj, [arg1, arg2, arg3,...]); // apply的第二个参数是一个参数数组
bind()
bind
会返回一个新函数,它会把参数设置为this
的上下文并调用原始函数。
function foo(num) {
console.log(this.a, num);
return this.a + num;
}
const obj = {
a: 11
};
const bar = foo.bind(obj);
const b = bar(28); // 11 28
console.log(b); // 39
3.隐式绑定
需要考虑的是函数是否在某个上下文对象中调用,如果是的话,this
绑定的是那个上下文对象。
function foo() {
console.log(this.a);
}
const obj1 = {
a: 11,
foo: foo
}
const obj2 = {
a :28,
foo: foo
}
obj1.foo(); //11 调用foo()时this被绑定到obj1对象上
obj2.foo(); //28 调用foo()时this被绑定到obj2对象上
上面的代码中,foo
函数作为引用属性被添加到obj
对象中,当foo
被调用时,它的落脚点就是obj
对象。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this
绑定到这个上下文对象。
4.默认绑定
最常见的函数调用类型是独立函数调用,可以把这条规则看作是无法应用其他规则时的默认规则。通过分析调用位置来看foo
是如何调用的。在以下代码中,foo()
是直接使用不带任何修饰的函数引用进行调用的,因此只能是默认绑定,无法应用其他规则。
window.a = 11;
function foo(){
console.log(this.a);
}
foo(); // 11 调用foo时,this.a被解析成全局变量,this的默认绑定指向全局对象
window.value = 1;
function foo() {
console.log(value); // 1
}
function bar() {
let value = 2;
foo();
// console.log(value); // 2
}
bar();
注:如果使用严格模式("use strict"),那么全局对象将无法使用默认绑定,会报错。
绑定例外
- 如果你把
null
或者undefined
作为this
的绑定对象传入call()
、apply()
或bind()
,这些值在调用时会被忽略,实际应用的是默认绑定规则。 - 自执行函数
var a = 1; (function() { console.log(a+this.a); // NaN ---- undefined转成数字是NaN---隐式转换 // var a; 变量提升,所以a是undefined;自调用函数的this指向 window, 所以 this.a=1; var a = '2'; console.log(a+this.a); // 21 ---- string // a = '2';自调用函数的this指向 window,this.a = 1,隐式转换为字符串 })()
- 箭头函数不使用以上规则,而是根据外层(函数、全局或块)作用域来决定
this
,且绑定后无法修改。箭头函数没有自己的this
指针,调用时并不会生成自身作用域下的this
,它只会从自己的作用域链的上一层继承this
。window.name = 11; var obj = { name: 28, say: function(){ const foo = () => { return this.name; } console.log(foo()); // 28 // 直接调用者为window 但是由于箭头函数不绑定this所以取得上下文中的this即obj对象 const bar = function(){ return this.name; } console.log(bar()); // 11// 直接调用者为window 普通函数 return this.name; } } console.log(obj.say()); // 28 // 直接调用者为obj 执行过程中的函数内上下文的this为obj对象
var a = 1 function foo () { var a = 2 function inner () { console.log(this.a) // 1 } inner() } foo() // 在全局作用域中调用,this指向window
普通函数和箭头函数的区别
- 箭头函数定义更简洁,箭头函数没有prototype,所以箭头函数本身没有this
- 箭头函数的this指向在定义的时候继承外层第一个普通函数的this,所以,箭头函数中this的指向在它被定义的时候就已经确定了,并且不会改变。
- call、apply,bind无法改变箭头函数中this的指向。
- 箭头函数不能作为构造器使用,用new调用时会报错。
- 箭头函数不绑定arguments,取而代之用rest参数...代替arguments,来访问箭头函数的参数列表。
- 箭头函数不能用作Generator函数,不能使用yield关键字。
const a = 10;
const obj = {
a: 13,
b: () => {
console.log(this.a);
},
c: function () {
console.log(this.a)
},
d: function () {
return () => {
console.log(this);
}
},
e: function () {
return this.b
}
}
obj.b() // undefined
obj.c() // 13
obj.e()() //undefined
obj.d()() // 箭头函数没有this,去外层找,此处指向obj对象
/* {
a: 13,
b: [Function: b],
c: [Function: c],
d: [Function: d],
e: [Function: e]
}
*/