全面解析JavaScript - this作用域详解

805 阅读4分钟

一、简介

this 是 JavaScript 中的一个关键字,当一个函数被调用时,除了传入函数的显式参数以外,名为this 的隐式参数也被传入函数。

this 参数指向了一个自动生成的内部对象,这个内部对象被成为函数上下文

二、This的使用场景

1. 全局&调用普通函数

在全局环境中,this 永远指向 window。

console.log(this === window);     //true

普通函数在调用时候 (注意不是构造函数,前面不加new),其中的this也是指向window。

但是在严格模式下调用的话,会报错:

var x = 20;
function test(){
    console.log(this);     // undefined
    console.log(this.x);   // Uncaught TypeError: Cannot read property 'x' of undefined
}
test();
2. 构造函数

所谓的构造函数就是由一个函数 new 出来的对象,一般构造函数的函数名首字母大写,例如像 ObjectFunctionArray 这些都属于构造函数。

function Test(){
    console.log(this);     // undefined
    console.log(this.x);   // Uncaught TypeError: Cannot read property 'x' of undefined
}

var test = new Test();
console.log(test.x);      //10

在上述代码的执行中,如果函数作为构造函数使用,那么其中的 this 就代表它即将 new 出来的对象。

但是如果直接调用Test函数,而不是new Test(),那就是情况一一样,变成普通函数,指向window

function Test(){
    this.x = 10;
    console.log(this);    //Window
}
var test = Test();
console.log(test.x);      //undefined
3. 对象方法

如果函数作为对象的方法时,方法中的 this 指向该对象:

var obj = {
    x: 10,
    test: function () {
        console.log(this);        //Object
        console.log(this.x);      //10
    }
};
obj.test();

注意:若是在对象方法中定义函数,那么情况就不同了。

var obj = {
    x: 10,
    test: function () {
        function f(){
            console.log(this);      //Window
            console.log(this.x);    //undefined
        }
        f();
    }
}
obj.test();

在上述代码中,需要注意的是:

函数f是在 obj.test 内部定义的,但它仍然属于一个普通函数, this 仍指向 window 。 在这种情况下,如果想要调用上层作用域中的变量 obj.x,可以使用self 缓存外部this变量。

var obj = {
    x: 10,
    test: function () {
        var self = this;
        function f(){
            console.log(self);      //{x: 10}
            console.log(self.x);    //10
        }
        f();
    }
}
obj.test();

如果test函数不作为对象方法被调用,而是在外部赋值给一个全局变量,并没有作为obj的一个属性使用,那么此时的this的值是指向window:

var obj = {
    x: 10,
    test: function () {
        console.log(this);       //Window
        console.log(this.x);     //undefined
    }
};
var fn = obj.test;
fn();
4. 构造函数 prototype 属性
function Test(){
    this.x = 10;
}
Test.prototype.getX = function () {
    console.log(this);        //Foo {x: 10, getX: function}
    console.log(this.x);      //10
}
var test = new Test();
test.getX();

在上述代码中, 在Test.prototype.getX函数中,this 指向的test对象。 不仅仅如此,在整个原型链中,this代表的也是当前对象的值。

5. 函数用 call、apply或者 bind 调用

当一个函数使用callapply或者bind调用时,this的取值就是传入的对象的值,即obj

var obj = {
    x: 10
}
function test(){
    console.log(this);     //{x: 10}
    console.log(this.x);   //10
}
test.call(obj);
test.apply(obj);
test.bind(obj)();
6. DOM event this

在一个 HTML DOM 事件处理程序里,this 始终指向这个处理程序所绑定的 HTML DOM 节点:

function Listener(){   
    document.getElementById('test').addEventListener('click', this.handleClick);     //这里的 this 指向 Listener 这个对象。不是强调的是这里的 this
}
Listener.prototype.handleClick = function (event) {
    console.log(this);    //<div id="foo"></div>
}
var listener = new Listener();
document.getElementById('test').click();

这其实等同于使用函数传参,使handleClick运行时上下文改变了,如下:

var obj = {
    x: 10,
    fn: function() {
        console.log(this);         //Window
        console.log(this.x);       //undefined
    }
};
function test(fn) {
    fn();
} 
test(obj.fn);

同样的,也可以通过bind切换上下文的绑定:

function  Listener(){
    document.getElementById('test').addEventListener('click',this.handleClick.bind(this));      
}
Listener.prototype.handleClick = function (event) {
    console.log(this);    //Listener {}
}
var listener = new Listener();
document.getElementById('test').click();

其实以上6种情况,可以总结成一句话: this 指向调用该方法的对象

7. 箭头函数中的this

当使用箭头函数的时候,情况就有所不同了:

箭头函数内部的 this 是词法作用域,由上下文确定

var obj = {
    x: 10,
    test: function() {
        var fn = () => {
            return () => {
                return () => {
                    console.log(this);      //Object {x: 10}
                    console.log(this.x);    //10
                }
            }
        }
        fn()()();
    }
}
obj.test();

现在,函数完全修复了this的指向,this总是指向词法作用域,也就是外层调用者obj。 如果使用箭头函数,以前的这种hack写法,就不再需要了:

var self = this;

由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者 apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略:

var obj = {
    x: 10,
    test: function() {
        var fn = () => {
            return () => {
                return () => {
                    console.log(this);    // Object {x: 10}
                    console.log(this.x);  //10
                }
            }
        }
        fn.bind({x: 14})()()();
        fn.call({x: 14})()();
    }
}
obj.test();
8. 补充说明
  • this为保留字,你不能重写this
function test(){
    var this = {};     //Uncaught SyntaxError: Unexpected token this
}
  • 宿主对象

一门语言在运行的时候,需要一个环境,称为'宿主环境', 对于JavaScript,宿主环境最常见的是web浏览器,浏览器提供了一个JavaScript运行的环境,在这个环境里面,需要提供一些接口,好让JavaScript引擎能够和宿主环境对接。JavaScript 引擎 才是真正执行JavaScript代码的地方,常见的引擎有V8(目前最快 JavaScript 引擎、Google 生产)、JavaScript core。 在浏览器或者服务端(node.js) 都有自己的js引擎,在浏览器中,全局对象为window,而在node.js 中,全局对象为 global