【js】this问题

demo1:

var obj = {
    a: 10,
    b: () => {
        console.log(this.a); // undefined
        console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
    },
    c: function () {
        console.log(this.a); // 10
        console.log(this); // {a: 10, b: ƒ, c: ƒ}
    }
}
obj.b();
obj.c();

箭头函数中的this 指的是上下文   ,c中的this值的是调用它的对象

demo2:

var length = 10;
function fn(){
    console.log(this.length);   
}

var obj = {
    length:5,
    method:function(fn){
        fn();
        arguments[0]();
    }   

}
obj.method(fn,1);
// 10 2

 demo3:

var a = 10;
var obj = {
    a:5,
    b:{
        fn:function(){
            console.log(this.a);
        }
    }
}
obj.b.fn();

// undefined   

this 指的是调用它的对象b

 this指向问题

  • 全局环境、普通函数(非严格模式)指向 window
  • 普通函数(严格模式)指向 undefined
  • 函数作为对象方法及原型链指向的就是上一级的对象
  • 构造函数指向构造的对象
  • DOM 事件中指向触发事件的元素
  • 箭头函数...

 

1、全局环境

全局环境下,this 始终指向全局对象(window),无论是否严格模式;

// 在浏览器中,全局对象为 window 对象:
console.log(this === window); // true

this.a = 37;
console.log(window.a); // 37

 

2、函数上下文调用

2.1 普通函数

普通函数内部的 this 分两种情况,严格模式和非严格模式。

(1)非严格模式下,没有被上一级的对象所调用, this 默认指向全局对象 window。

function f1() {
    return this;
}
f1() === window; // true

 

(2)严格模式下,this 指向 undefined。

function f2() {
    "use strict"; // 这里是严格模式
    return this;
}
f2() === undefined; // true

 

2.2 函数作为对象的方法

(1)函数有被上一级的对象所调用,那么 this 指向的就是上一级的对象。

(2)多层嵌套的对象,内部方法的 this 指向离被调用函数最近的对象(window 也是对象,其内部对象调用方法的 this 指向内部对象, 而非 window)。

//方式1
var o = {
    prop: 37,
    f: function() {
        return this.prop;
    }
};
//当 o.f()被调用时,函数内的this将绑定到o对象。
console.log(o.f()); // logs 37

//方式2
var o = {
    prop: 37
};

function independent() {
    return this.prop;
}
//函数f作为o的成员方法调用
o.f = independent;
console.log(o.f()); // logs 37

//方式3
//this 的绑定只受最靠近的成员引用的影响
o.b = {
    g: independent,
    prop: 42
};
console.log(o.b.g()); // 42

 

特殊例子

// 例子1
var o = {
    a: 10,
    b: {
        // a:12,
        fn: function() {
            console.log(this.a); //undefined
            console.log(this); //{fn: ƒ}
        }
    }
};
o.b.fn();
// 例子2
var o = {
    a: 10,
    b: {
        a: 12,
        fn: function() {
            console.log(this.a); //undefined
            console.log(this); //window
        }
    }
};
var j = o.b.fn;
j();
// this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的,例子2中虽然函数fn是被对象b所引用,但是在将fn赋值给变量j的时候并没有执行所以最终指向的是window,这和例子1是不一样的,例子1是直接执行了fn

 

2.3 原型链中的 this

(1)如果该方法存在于一个对象的原型链上,那么 this 指向的是调用这个方法的对象,就像该方法在对象上一样。

var o = {
    f: function() {
        return this.a + this.b;
    }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;

console.log(p.f()); // 5

 

上述例子中,对象 p 没有属于它自己的 f 属性,它的 f 属性继承自它的原型。当执行 p.f()时,会查找 p 的原型链,找到 f 函数并执行。因为 f 是作为 p 的方法调用的,所以函数中的 this 指向 p。

(2)相同的概念也适用于当函数在一个 getter 或者 setter 中被调用。用作 getter 或 setter 的函数都会把 this 绑定到设置或获取属性的对象。

(3)call()和 apply()方法:当函数通过 Function 对象的原型中继承的方法 call() 和 apply() 方法调用时, 其函数内部的 this 值可绑定到 call() & apply() 方法指定的第一个对象上, 如果第一个参数不是对象,JavaScript 内部会尝试将其转换成对象然后指向它。

function add(c, d) {
    return this.a + this.b + c + d;
}
var o = {
    a: 1,
    b: 3
};

add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

function tt() {
    console.log(this);
}
// 第一个参数不是对象,JavaScript内部会尝试将其转换成对象然后指向它。
tt.call(5); // 内部转成 Number {[[PrimitiveValue]]: 5}
tt.call("asd"); // 内部转成 String {0: "a", 1: "s", 2: "d", length: 3, [[PrimitiveValue]]: "asd"}

 

(4)bind()方法:由 ES5 引入, 在 Function 的原型链上, Function.prototype.bind。通过 bind 方法绑定后, 函数将被永远绑定在其第一个参数对象上, 而无论其在什么情况下被调用。

function f() {
    return this.a;
}

var g = f.bind({
    a: "azerty"
});
console.log(g()); // azerty

var o = {
    a: 37,
    f: f,
    g: g
};
console.log(o.f(), o.g()); // 37, azerty

2.4 构造函数中的 this

当一个函数用作构造函数时(使用 new 关键字),它的 this 被绑定到正在构造的新对象。

构造器返回的默认值是 this 所指的那个对象,也可以手动返回其他的对象。

function C() {
    this.a = 37;
}

var o = new C();
console.log(o.a); // 37
// 为什么this会指向o?首先new关键字会创建一个空的对象,然后会自动调用一个函数apply方法,将this指向这个空对象,这样的话函数内部的this就会被这个空的对象替代。
function C2() {
    this.a = 37;
    return {
        a: 38
    }; // 手动设置返回{a:38}对象
}

o = new C2();
console.log(o.a); // 38

 

 
 

特殊例子

当 this 碰到 return 时

// 例子1
function fn() {
    this.user = "追梦子";
    return {};
}
var a = new fn();
console.log(a.user); //undefined
// 例子2
function fn() {
    this.user = "追梦子";
    return function() {};
}
var a = new fn();
console.log(a.user); //undefined
// 例子3
function fn() {
    this.user = "追梦子";
    return 1;
}
var a = new fn();
console.log(a.user); //追梦子
// 例子4
function fn() {
    this.user = "追梦子";
    return undefined;
}
var a = new fn();
console.log(a.user); //追梦子
// 例子5
function fn() {
    this.user = "追梦子";
    return undefined;
}
var a = new fn();
console.log(a); //fn {user: "追梦子"}
// 例子6
// 虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊
function fn() {
    this.user = "追梦子";
    return null;
}
var a = new fn();
console.log(a.user); //追梦子

// 总结:如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。

2.5 setTimeout & setInterval

(1)对于延时函数内部的回调函数的 this 指向全局对象 window;

(2)可以通过 bind()方法改变内部函数 this 指向。

//默认情况下代码
function Person() {
    this.age = 0;
    setTimeout(function() {
        console.log(this);
    }, 3000);
}

var p = new Person(); //3秒后返回 window 对象
//通过bind绑定
function Person() {
    this.age = 0;
    setTimeout(
        function() {
            console.log(this);
        }.bind(this),
        3000
    );
}

var p = new Person(); //3秒后返回构造函数新生成的对象 Person{...}

 

3、在 DOM 事件中

3.1 作为一个 DOM 事件处理函数

当函数被用作事件处理函数时,它的 this 指向触发事件的元素(针对 addEventListener 事件)。

// 被调用时,将关联的元素变成蓝色
function bluify(e) {
    //this指向所点击元素
    console.log("this === e.currentTarget", this === e.currentTarget); // 总是 true
    // 当 currentTarget 和 target 是同一个对象时为 true
    console.log("this === e.target", this === e.target);
    this.style.backgroundColor = "#A5D9F3";
}

// 获取文档中的所有元素的列表
var elements = document.getElementsByTagName("*");

// 将bluify作为元素的点击监听函数,当元素被点击时,就会变成蓝色
for (var i = 0; i < elements.length; i++) {
    elements[i].addEventListener("click", bluify, false);
}

 

3.2 作为一个内联事件处理函数

(1)当代码被内联处理函数调用时,它的 this 指向监听器所在的 DOM 元素;

(2)当代码被包括在函数内部执行时,其 this 指向等同于 普通函数直接调用的情况,即在非严格模式指向全局对象 window,在严格模式指向 undefined:

<button onclick="console.log(this)">show me</button>
<button onclick="(function () {console.log(this)})()">show inner this</button>
<button onclick="(function () {'use strict'; console.log(this)})()">
    use strict
</button>
 

// 控制台打印
<button onclick="console.log(this)">show me</button>
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
undefined

 

4、箭头函数

4.1 全局环境中

在全局代码中,箭头函数被设置为全局对象:

var globalObject = this;
var foo = () => this;
console.log(foo() === globalObject); // true

 

4.2 this 捕获上下文

箭头函数没有自己的 this,而是使用箭头函数所在的作用域的 this,即指向箭头函数定义时(而不是运行时)所在的作用域。

//1、箭头函数在函数内部,以非方法的方法使用
function Person() {
    this.age = 0;
    setInterval(() => {
        this.age++;
    }, 3000);
}
var p = new Person(); //Person{age: 0}

//普通函数作为内部函数
function Person() {
    this.age = 0;
    setInterval(function() {
        console.log(this);
        this.age++;
    }, 3000);
}
var p = new Person(); //Window{...}

4.2 this 捕获上下文

箭头函数没有自己的 this,而是使用箭头函数所在的作用域的 this,即指向箭头函数定义时(而不是运行时)所在的作用域。

//1、箭头函数在函数内部,以非方法的方法使用
function Person() {
    this.age = 0;
    setInterval(() => {
        console.log(this);
        this.age++;
    }, 3000);
}
var p = new Person(); //Person{age: 0}

//普通函数作为内部函数
function Person() {
    this.age = 0;
    setInterval(function() {
        console.log(this);
        this.age++;
    }, 3000);
}
var p = new Person(); //Window{...}

 

在 setTimeout 中的 this 指向了构造函数新生成的对象,而普通函数指向了全局 window 对象。

4.3 箭头函数作为对象的方法使用

箭头函数作为对象的方法使用,指向全局 window 对象;而普通函数作为对象的方法使用,则指向调用的对象。

var obj = {
    i: 10,
    b: () => console.log(this.i, this),
    c: function() {
        console.log(this.i, this);
    }
};
obj.b(); // undefined window{...}
obj.c(); // 10 Object {...}

4.4 箭头函数中,call()、apply()、bind()方法无效

var adder = {
    base: 1,
    //对象的方法内部定义箭头函数,this是箭头函数所在的作用域的this,
    //而方法add的this指向adder对象,所以箭头函数的this也指向adder对象。
    add: function(a) {
        var f = v => v + this.base;
        return f(a);
    },
    //普通函数f1的this指向window
    add1: function() {
        var f1 = function() {
            console.log(this);
        };
        return f1();
    },
    addThruCall: function inFun(a) {
        var f = v => v + this.base;
        var b = {
            base: 2
        };

        return f.call(b, a);
    }
};

console.log(adder.add(1)); // 输出 2
adder.add1(); //输出全局对象 window{...}
console.log(adder.addThruCall(1)); // 仍然输出 2(而不是3,其内部的this并没有因为call() 而改变,其this值仍然为函数inFun的this值,指向对象adder

4.5 this 指向固定化

箭头函数可以让 this 指向固定化,这种特性很有利于封装回调函数

var handler = {
    id: "123456",

    init: function() {
        document.addEventListener(
            "click",
            event => this.doSomething(event.type),
            false
        );
    },

    doSomething: function(type) {
        console.log("Handling " + type + " for " + this.id);
    }
};

 

上面代码的 init 方法中,使用了箭头函数,这导致这个箭头函数里面的 this,总是指向 handler 对象。如果不使用箭头函数则指向全局 document 对象。

4.6 箭头函是不适用场景

(1)箭头函数不适合定义对象的方法(方法内有 this),因为此时指向 window;

(2)需要动态 this 的时候,也不应使用箭头函数。

//例1,this指向定义箭头函数所在的作用域,它位于对象cat内,但cat不能构成一个作用域,所以指向全局window,改成普通函数后this指向cat对象。
const cat = {
    lives: 9,
    jumps: () => {
        this.lives--;
    }
};

//例2,此时this也是指向window,不能动态监听button,改成普通函数后this指向按钮对象。
var button = document.getElementById("press");
button.addEventListener("click", () => {
    this.classList.toggle("on");
});

 

posted on 2018-10-22 15:35  smile轉角  阅读(142)  评论(0编辑  收藏  举报

导航