JavaScript中的this详解

栏目: JavaScript · 发布时间: 7年前

内容简介:this是JavaScript这门语言中极其重要的一个知识点,特别是关于面向对象的相关的写法,可以说掌握了this的特性,相当于掌握了一大半JavaScript面向对象的编写能力。总的来说,JavaScript中的this大概有7种情况,理解到位了这些情况,基本上就掌握了这部分相关的内容,所有的高级写法,都是基于这些情况的演变。这7种情况分别是:我们所说的全局环境,其实指的就是window这个对象,也就是我们在浏览器中每打开一个页面,都会生成的一个window。先来看看最简单的全局调用。我们都知道,全局下使

this是JavaScript这门语言中极其重要的一个知识点,特别是关于面向对象的相关的写法,可以说掌握了this的特性,相当于掌握了一大半JavaScript面向对象的编写能力。总的来说,JavaScript中的this大概有7种情况,理解到位了这些情况,基本上就掌握了这部分相关的内容,所有的高级写法,都是基于这些情况的演变。这7种情况分别是:

  • 全局环境调用下的this
  • 事件处理函数中的this
  • 对象方法内的this
  • 构造函数中的this
  • 原型链上函数中的this
  • getter和setter中个this
  • 箭头函数中的this
    除了最后一个箭头函数中的this指向,是基于函数书写时候确定的,其它所有情况下,JavaScript中的this,都是由调用时决定的。很多时候,大家会很纳闷,这个this,到底是什么?this可以是全局对象window,可以是一个具体的元素比如<div></div>,也可以是一个对象比如{},还可以是一个实例等等。至于this到底是什么,就看函数执行的时候,到底是谁调用了它。

1.全局环境调用

我们所说的全局环境,其实指的就是window这个对象,也就是我们在浏览器中每打开一个页面,都会生成的一个window。先来看看最简单的全局调用。

function fn1() {
    console.log( this );
}

fn1();  // window

// 相当于
window.fn1();
复制代码

我们都知道,全局下使用var声明的变量,都会隐式的被创建为window对象的属性和方法。所以,当你看到一个函数被调用而没有前缀的时候(也就是说不是通过"."符号来调用),这其实就是全局对象window在调用它。因此,此时函数内部的this是指向window对象的。再来看个变化版本。

let o = {
    name: 'abc',
    fn: function() {
        console.log( this.a );
    }
}

let fn2 = o.fn;
fn2();  // undefined
复制代码

是的,虽然fn2拿到的是对象o里面的一个方法,但是,万变不离其宗,在执行fn2()的时候,仍然是没有前缀的,那是谁在调用fn2的?当然是window对象。所以这里的this也指向window。

1.1 严格模式和非严格模式的区别

我们现在知道,全局对象window调用的函数,内部的this就是指向window。但是这里有个问题需要注意一下。JavaScript有严格模式和非严格模式之分(严格模式就在代码的顶部加上一句"use strict")。在这两种情况下,this的指向是有区别的。

非严格模式下this指向我们已经讨论过了,指的是window对象,而严格模式下的全局调用,this指向的是undefined。

"use strict"
function fn1() {
    console.log( this );
}

fn1();  // undefined
复制代码

2.事件处理函数中的this

JavaScript中对于事件的处理是采用异步回调的方式,对一个元素绑定一个回调函数,当事件触发的时候去执行这个函数。而对于回调函数的绑定,有下面几种情况:

  • 元素标签内绑定
  • 动态绑定
  • 事件监听 这几种情况下,回调函数内的this分别又是什么呢?分别来看看。

2.1元素标签内绑定

<div id="div1" onclick="console.log( this )"></div>
复制代码

点击元素div1后,我们发现控制台打印的是"<div id="div1" onclick="console.log( this )">",可以知道的是,元素内联所执行的语句中的this,指向的是元素本身。但是,有一个特例,来改动一下方式。

<div id="div1" onclick="(function () {console.log( this )}()"></div>
复制代码

看明白了吗,元素内联的是一个匿名自执行函数,这个时候匿名自执行函数中的this,就不是指向元素本身了,而是window对象!虽然这种写法很无聊,但这就是内联写法我们需要注意的一个点。我们可以这样理解,匿名自执行函数有独立的作用域,相当于是window在调用它。这种情况,知道就好,无需太花力气死磕。

2.2 动态绑定

let div1 = document.getElementById("div1");

div1.onclick = function() {
    console.log( this );    // div1
}
复制代码

这是通过动态绑定的方式,给元素添加了事件,这种情况下,当回调函数执行的时候,是元素div1在调用它,所以此时函数内部的this,是指向元素div1的。

2.3 事件监听

let div1 = document.getElementById("div1");

div1.addEventListener("click", function() {
    console.log( this );    // div1
    }, false);
复制代码

同样的,通过事件监听器的方式绑定的回调函数,内部的this也是指向div1。所以我们可以总结一下得知:事件处理函数中的this,指向的是触发这个事件的元素。

3.对象方法中的this

在JavaScript中,对象是可以有属性和方法的,这个方法,其实就是函数。既然是函数,那么内部肯定也会有this,作为对象方法中的this,到底是指的什么呢?看个简单的例子。

var name = 'aaa';
let obj = {
    name: 'jack',
    fn: function() {
        console.log( this.name );
    }
}

let f1 = obj.fn;

obj.fn();   // jack
f1();       // aaa
复制代码

作为对象的方法调用的函数,它内部的this,就指向这个对象。在这个例子中,当通过obj.fn()的形式调用fn函数的时候,它内部的this指的就是obj这个对象了。至于第二种情况,先把obj.fn赋值给f1,然后通过执行f1来执行函数的情况,我们在上面已经说过,这个时候,其实是window对象在调用f1,因此它内部的this就是指向window对象,因而打印的就是'aaa'。

如果是一个对象中嵌套着比较深的方法,它内部的this又是什么呢?

let person = {
    name: 'jack',
    eat: {
        name: 'apple',
        fn1: function() {
            console.log( this.name );
        },
        obj: {
            name: 'grape',
            fn2: function() {
                console.log( this.name );
            }
        }
    }
}

person.eat.fn1();       // apple
person.eat.obj.fn2();   // grape
复制代码

这里遵守一个就近原则:如果是通过对象方法的方式调用函数,则函数内部的this指向离它最近一级的那个对象。在这个例子中,person.eat.fn1()这种调用,fn1中的this指的就是eat这个对象;person.eat.obj.fn2()这种调用方式,fn2中的this,指的就是obj这个对象。

4.构造函数中的this

构造函数其实就是普通的函数,只是它内部一般都书写了许多this,可以通过new的方式调用来生成实例,所以我们一般都用首字母大写的方式,来区分构造函数和一般的函数。构造函数,是JavaScript中书写面向对象的重要方式。

function Fn1(name) {
    this.name = name;
}

let n1 = new Fn1('abc');
n1.name; // abc
复制代码

这是一个非常简单的构造函数书写方式,以及对构造函数的调用。构造函数中的this,以及new调用的这种方式,其实都是为了能够创造实例服务的,否则也就没有意义了。那么,构造函数中的this也就很清楚了:它指向构造函数所创造的实例。当通过new方法调用构造函数的时候,构造函数内部的this就指向这实例,并将相应的属性和方法"生成"给这个实例。通过这个方法,生成的实例才能够获取属性和方法。

凡事总有例外嘛,构造函数中有这样一种例外,我们看看。

function Fn1(name) {
    this.name = name;
    return null;
}

function Fn2(name) {
    this.name = name;
    return {a: '123'};
}

let f1 = new Fn1("ttt");
console.log( f1 );  // {name: "ttt"}

let f2 = new Fn2("ggg");
console.log( f2 );  // {a: "123"}
复制代码

f1是通过new Fn1创建的一个实例,这没有问题。但f2为什么不是我们所想的结果呢? 当构造函数内部return的是一个对象类型的数据的时候,通过new所得到的,就是构造函数return出来的那个对象;当构造函数内部return的是基本类型数据(数字,字符串,布尔值,undefined,null),那么对于创建实例没有影响。

5.原型链函数中的this

原型链函数中个this,其实跟构造函数中的this一样,也是指向创建的那个实例。

function Fn() {
    this.name = '878978'
}

Fn.prototype.sum = function() {
    console.log(this)
    return this;
}

let f5 = new Fn();
let f6 = new Fn();

console.log( f5 === f5.sum() );     // true
console.log( f6 === f6.sum() );     // true
复制代码

6.getter和setter中的this

我们知道,JavaScript中getter和setter是作为对对象属性读取和修改的一种劫持。可以分别在读取和设置对象相应属性的时候触发。

let obj = {
    n: 1,
    m: 2,
    get sum() {
        console.log(this.n, this.m);
        return '正在尝试访问sum...';
    },
    set sum(k) {
        this.m = k;
        return '正在设置obj属性sum...';
    }
}

obj.sum;   // 1,2
obj.sum = 5;  // 正在设置obj属性sum..
复制代码

getter和setter中的this,规则跟作为对象方法调用时候函数内部的this指向是一样的,它指的就是这个对象本身。

7.箭头函数中的this

箭头函数是ES6中新推出的一种函数简写方法,跟ES5函数最大的区别,就要数它的this规则了。在ES5的函数中,this都是在函数调用的时候,才能确定具体的this指向。而箭头函数,其实是没有this的,但是它内部的这个所谓this,在箭头函数书写的时候,就已经绑定了(绑定父级的this),并且无法改变。看个例子。

let div1 = document.getElementById("div");

div1.onclick = function() {
    setTimeout(() => {
        console.log( this );    // div1
    }, 500);
}
复制代码

我们知道,setTimeout中所绑定的回调函数,其实是window在调用它,所以它内部的this指向的是window。但是,当回调函数是箭头函数的写法的时候,内部的this竟然是div1!这在箭头函数书写的时候,就已经决定了它内部的this指向,就是它父级的this。而它父级函数作用域中的this,其实就是元素div1。作为对象方法的箭头函数,其实也是类似的道理。

var name = 'aaa';
let obj = {
    name: 'jack',
    fn1: () => {
        console.log( this.name );
    }
}

obj.fn1();  // aaa
复制代码

没错,还是那句话,当我们写下箭头函数的时候,它内部的this就已经确定了,并且无法修改(call, apply, bind)。这个例子中,箭头函数最近的父级作用域显然是全局环境window,因此它的this就指向window。

8.call, apply, bind的用法

说到JavaScript中的this,就没法不说call, apply, bind这三个方法。在所有JavaScript函数的高级用法,或者是JavaScript框架中,都会有这三个方法的踪影。这三个方法都是Function.prototype上的方法,所以所有的函数都默认继承了这三个方法。现在具体说说这三个方法的分别用途。

8.1 call

call方法可以实现对函数的立即调用,并且显示的指定函数内部的this以及传参。

let obj = {
    color: 'green'
}

function Fn() {
    console.log( this.color );
}

Fn();   // undefined

Fn.call(obj);   // green
复制代码

call可以实现对函数的立即调用,并且改变函数内部的this指向。上面的例子中,直接调用函数Fn的时候,它内部的this指向window对象,因此打印的是undefined;当通过call指定函数内部的this指向obj的时候,它就能获取到obj上的属性和方法了。call调用还能实现调用时候的传参,请看。

let obj = {
    color: 'blue'
}

function Fn(height, width) {
    console.log(`the tree is ${this.color}, and the tall is ${height}, width is ${width}`);
}

Fn.call(obj, 20, 3);   // the tree is blue, and the tall is 20, width is 3
复制代码

8.2 apply

apply的作用和call是一模一样的,都是实现对函数内部this的改变,唯一的区别就是传参的方式不一样:call是通过一个一个参数的方式传递参数,而apply是通过数组的形式传递多个参数。

let obj = {
    color: 'orange'
}

function Fn(height, width) {
    console.log(`the tree is ${this.color}, and the tall is ${height}, width is ${width}`);
}

Fn.apply(obj, [16, 7]);   // the tree is orange, and the tall is 16, width is 7
复制代码

8.3 bind

call和apply都是实现对函数的立即调用,并且改变函数内部this的指向,如果说我只想改变函数内部的this,而不执行函数,该怎么办?这个时候,就需要用到bind。

let person = {
    name: 'jack'
}

function Person() {
    console.log(this.name);
}

let p1 = Person.bind(person);
p1();   // 'jack'
复制代码

当一个函数执行完bind方法后,会返回一个新的函数,而这个新的函数跟原函数相比,内部的this指向被显示的改变了。但是不会立即执行新的函数,而是在你需要的时候才去调用。 但是有一点需要注意,返回的新函数p1,它内部的this就无法再改变了。接着上面的例子。

let animal = {
    name: 'animal'
}

let p2 = p1.bind();
p2();   // 'jack'
复制代码

p2的this依然是指向obj,而非animal。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

产品型社群

产品型社群

李善友 / 机械工业出版社 / 2015-3-1 / CNY 69.00

传统模式企业正在直面一场空前的“降维战争”, 结局惨烈,或生或死。 传统模式很难避免悲惨下场, 诺基亚等昔日庞然大物轰然倒塌, 柯达发明了数码成像技术却依然破产, 新商业的兴起到底遵循的是什么模式? 微信轻而易举干掉了运营商的短信业务, “好未来”为何让传统教育不明觉厉? 花间堂为什么不是酒店,而是入口? 将来不会有互联网企业与传统企业之分, ......一起来看看 《产品型社群》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具