一、this机制

  • this是在运行时绑定的,而不是编写时候绑定的
  • this指向取决于函数的调用方式

二、this绑定规则

1. 默认绑定

首先是最常用得函数调用方式:独立函数调用

function foo(){
    console.log(this.a)
}
// 声明变量a, 也是全局对象的一个同名属性
var a=2;
foo(); // 2

因为foo()调用是没有引用任何修饰进行调用的,因此只能使用默认绑定

2. 隐式绑定

(1)调用位置是否有上下文对象,或者说是否被某个对象拥有
function foo(){
    console.log(this.a)
}
var obj={
    a:2,
    foo:foo
}
obj.foo() // 2

虽然函数foo并不属于obj对象,但是调用位置是使用obj上下文来引用函数,此时隐式绑定会this绑定到这个上下文对象

(2)另外, 对象引用中,只有最后一层在调用位置起作用
function foo(){
    console.log(this.a)
}
var obj2={
    a:42,
    foo:foo
}
var obj1={
    a:2,
    obj2:obj2
}

obj1.obj2.foo(); // 42  取决于函数被哪个对象调用的
(3)隐式丢失 (会转成默认绑定)
function foo(){
    console.log(this.a)
}
var obj={
    a:2,
    foo:foo
}
var bar=obj.foo;
var a="oops, global";
bar(); // "oops, global"

bar是obj.foo得一个引用,但实际上引用得是foo函数本身,bar调用不带任何修饰,因此还是默认绑定

function foo(){
    console.log(this.a)
}
var obj={
    a:2,
    foo:foo
}
function doFoo(fn){
    fn();  // 调用的位置
}

var a="oops, global";
doFoo(obj.foo); // "oops, global"
参数传递,只不过传的是函数,和上一个栗子一样
function foo(){
    console.log(this.a)
}
var obj={
    a:2,
    foo:foo
}
var a="oops, global";
setTimeout(obj.foo,100); // "oops, global"

结果是一样的,没有区别,setTimeout相当于

function setTimeout(fn,deplay){
    // 等待deplay毫秒后
    fn(); // 调用位置
}

3. 显式绑定(硬绑定)

就是通过call, apply函数改变this指向

function foo(){
    console.log(this.a)
}
var obj={
    a:2,
    foo:foo
}
var bar=function(){
    foo.call(obj);
}
bar(); // 2
setTimeout(bar,100); // 2
bar.call(window); // 2

可以看出硬绑定后,不能再修改它的this指向

4. new绑定

new调用时,如果函数没有返回其它对象,就会绑定到new出来的对象上

function foo(a){
    this.a=a;
}
var bar=new foo(2);
console.log(bar.a); //2

var obj={
    a:3
}
function boo(a){
    this.a=a;
    return obj; // 有返回对象
}
var baz=new boo(2);
console.log(baz.a); // 3

三、this绑定优先级

new绑定 > call/apply(显式绑定) > 隐式绑定 > 默认绑定
我们来一一证明

1. 显示绑定 vs 隐式绑定

function foo(){
    console.log(this.a);
}
var obj1={
    a:2,
    foo:foo
}
var obj2={
    a:3,
    foo:foo
}
obj1.foo(); // 2  隐式
obj2.foo(); // 3  隐式

obj1.foo.call(obj2); // 3
obj2.foo.call(obj1); // 3

上述证明:显示绑定 > 隐式绑定

2. new绑定 vs 隐式绑定

function foo(something){
    this.a=something;
}
var obj1={
    foo:foo
}
var obj2={}

obj1.foo(2); // 隐式
console.log(obj1.a); // 2

obj1.foo.call(obj2,3); // 显示
console.log(obj2.a); // 3

var bar=new obj1.foo(4);
console.log(obj1.a); // 2
console.log(bar.a); // 4

最后三行代码可以看出,new绑定 > 隐式绑定

3. new绑定 vs 显示绑定

需要知道的是,new绑定无法和call/apply一起使用,无法通过 new foo.call(obj1) 这种形式测试
我们用bind试试

function foo(something){
    this.a=something;
}
var obj1={};
var bar=foo.bind(obj1);
bar(2);
console.log(obj1.a); // 2

var baz=new bar(3);
console.log(obj1.a); // 2
console.log(bar.a); // 3

最后三行:

  • 虽然在这之前foo已经被绑到obj1上了,但是new bar(3)并没有像bar(2)那样,使得obj1.a发生改变,obj1.a还是2;
  • 反倒是bar.a成了3,说明new bar(3)中(new bar)是作为一个整体生效的;
  • 因此,new绑定 > 显示绑定

四、绑定例外

1. 被忽略的this

如果把null/undefined作为this的绑定对象传入call/apply/bind, 这些值调用会被忽略,转为默认绑定规则

function foo(){
    console.log(this.a);
}
var a=2;
foo.call(null); // 2

// 函数柯里化
function fo(a,b){
    console.log("a:"+a+",b:"+b);
}
var bar = fo.bind(null,2);
bar(3); // a:2,b:3

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的四种标准规则,而是根据外层作用域来决定this

function foo(){
    return (a)=>{
        console.log(this.a);
    }
}
var obj1={a:2};
var obj2={a:3};

var bar=foo.call(obj1);
bar.call(obj2); // 2 而不是 3

剪头函数绑定this后,无法被修改

function foo(){
    setTimeout(()=>{
        console.log(this.a);
    },100)
}

var obj={a:2};
foo.call(obj); // 2

六、经典鄙视题

函数作用域相关知识,移步 函数作用域详解

function Foo () {
    getName = function () {
        console.log(1);
    };
    return this;
}
Foo.getName = function () {
    console.log(2);
};
Foo.prototype.getName = function () {
    console.log(3);
};
var getName = function () {
    console.log(4);
};
function getName () {
    console.log(5);
}

Foo.getName(); // 2
getName(); // 4 
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); //2
new Foo().getName(); // 3 
new new Foo().getName(); // 3  

lihaixing
463 声望719 粉丝

前端就爱瞎折腾