判断this
可以按照以下顺序来进行判断:
- 函数是否在
new
中调用?如果是的话this
绑定的是新创建的对象。 - 函数是否通过
call()、appl()或bind()
绑定?如果是的话this
绑定的是指定的对象。 - 函数是否通过在某个上下文对象中调用?如果是的话
this
绑定的是那个上下文对象。 - 以上规则都不满足,使用默认绑定。如果在严格模式下,就绑定到
undefined
,否则绑定到全局对象。
调用位置
在理解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 _new(base,...args){
var obj = {};
obj.__proto__ = base.prototype;
base.apply(obj, args);
return obj;
}
var f2 = _new(Fun, 11);
console.log(f2.a); // 11
复制代码
以上是在通过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的默认绑定指向全局对象
复制代码
注:如果使用严格模式(“use strict”),那么全局对象将无法使用默认绑定,会报错。
绑定例外
- 如果你把
null
或者undefined
作为this
的绑定对象传入call()
、apply()
或bind()
,这些值在调用时会被忽略,实际应用的是默认绑定规则。 - 箭头函数不使用以上规则,而是根据外层(函数、全局或块)作用域来决定
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对象
复制代码