资讯专栏INFORMATION COLUMN

JavaScript this关键字

ghnor / 1557人阅读

摘要:第二种和第三种分别为级事件和级事件,其实质是给点击事件指定了一个回调函数,其为。也是和实例对象严格相等的,可以说明,构造函数中的是指代实例对象。参考链接关键字深入理解上下文在线转化构造函数原文发表在我的博客关键字,欢迎访问

涵义

this关键字是一个非常重要的语法点。毫不夸张地说,不理解它的含义,大部分开发任务都无法完成。

首先,this总是返回一个对象,简单说,就是返回属性或方法“当前执行环境”的对象。

var showName = function() {
    console.log("My name is", this.name);
};
var zs = {
        name: "Zhang San",
        describe: showName
    },
    ls = {
        name: "Li Si",
        describe: showName
    };
zs.describe();         // My name is Zhang San
ls.describe();         // My name is Li Si
showName();         // My name is 
name = "window";        // 等价于 window.name = "window"; 和this.name = "window"; 因为此时window===this
showName();         // My name is window

上面代码中定义了showName方法,将在控制台输出"My name is "并拼接上this.name,并将这个方法赋给了zs和li这两个对象的describe方法。

当通过这两个对象调用describe方法时,分别输出zs和ls的name属性。

直接在全局环境下调用showName方法并没有报错,但也没有输出任何内容。不报错的原因是此时的this指的是浏览器的window对象,window对象有name属性。没有输出内容的原因是window.name初始值是一个空字符串。

我们给name属性赋值为"window"后再次执行showName方法时,将输出: My name is window

以上示例中实际都是执行的showName方法,但是由于环境不同,输出的结果也不同,根本原因是不同情况下的this是不一样的。

zs.describe(); this === zs

ls.describe(); this === ls

showName(); this === window

再看一个例子:




点击三个按钮,控制台输出结果分别是什么呢?

第一个为:My name is window 第二个为:My name is 按钮2 ,第三个为My name is 按钮3

这是为什么呢?这个和绑定事件的机制有关系。第一种形式是HTML事件,onclick="showName()"表示在点击时执行showName方法,此时执行环境为全局环境,this为window,所以输出window。

第二种和第三种分别为DOM0级事件DOM2级事件,其实质是给点击事件指定了一个回调函数,其为showName。在点击事件的回调函数中,this是指当前这个dom元素,因此输出的值为这两个按钮的name属性。

使用场合

this的使用是很广泛的,其作用也非常强大。我们可以将this的使用归为一下几类。

构造函数

在构造函数中,this的出现频率是非常高的,它指的是实例对象。

function Person(name, gender) {    
    this.name = name;
    this.gender = gender;
}
zs = new Person( "zs" , "male" );
// {
//    name: "zs",
//    gender: "male"
// }

上面使用构造函数产生实例对象时,两个参数赋值给了实例对象就是通过this来完成的。

function Person(name, gender) {
    this.name = name;
    this.gender = gender;
}
Person.prototype.showSelf = function() {
    return this;
}
zs = new Person("zs", "male");     // {name: "zs", gender: "male"}
zs.showSelf();                     // {name: "zs", gender: "male"}
zs === zs.showSelf();             // true 

上面代码通过showSelf方法返回了构造函数里的this,它的输出内容和实例对象一致。也是和实例对象严格相等的,可以说明,构造函数中的this是指代实例对象。

对象的方法

在对象里面定义的方法中也经常看到this的身影,那么此时的this指的又是什么呢?

大多数情况下,this指的是当前的这个对象,比如:

var box = {
    id: +new Date(),
    name: "noName",
    setName: function(name) {
        this.name = name;
    },
    getName:function(){
        return this.name;
    }
}
box.getName();     // "noName"
box.setName("box1"); 
box;             // {id: 1476846238291, name: "box1"}

上面代码中在通过box对象来调用setNamegetName方法的情况下,this指的就是当前的这个对象,此处为box,也正是由于这种情况下的this指的是当前对象,我们才能通过这两个方法对box的name属性进行读写。

但是只有box.getName() box.setName("box1")这样使用时,this才指向当前对象。请看下面例子:

当将一个对象的一个方法赋给另一个对象时,this的指向也会改变。

var box = {
    name: "box",
    getName: function() {
        return this.name;
    }
};
var bag = {
    name: "bag"
};
bag.getName = box.getName;
bag.getName(); // "bag"

虽然bag.getName 实际是对box.getName 的一个引用,由于运行时使用的是bag.getName(),此时是在bag对象下运行的,this也就指的是bag了。

再看一点奇怪的:

// 注意 box.getName 没有括号
(false || box.getName)(); // window
(false ? alert : box.getName)(); // window

上面这两种情况下,输出的都不再是box对象的name属性,而是window(之前设置了window.name="window")。表示此时方法内部的this指向的是浏览器顶层对象window

可以这么理解:

box对象指向了一个地址M1box.getName作为box的一个方法,但本身也是对象,它自己也有一个地址M2,只有通过box.getName() 调用时,是从M1中调用M2,所以this指向的是box。上面两种情况都是直接拿到M2来调用,此时和M1已经没有任何关系了,this的指向当前代码块所在的对象。

全局环境

在全局环境中使用this,在浏览器中,指的就是顶层对象window

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

function thisIs() {
    console.log(this === window);
}

thisIs(); // true

上面代码说明,不管this是写在全局环境下,还是一个函数作用域内,只要是在全局环境下运行,this的指向都是顶层对象window

Node

在Node中,this的指向又分成两种情况。全局环境中,this指向全局对象global;模块环境中,this指向module.exports

ES6箭头函数

ES6中新增的箭头函数里面所使用的this和之前介绍的情况都不一样了,在箭头函数中this不随其运行环境的改变而改变,而是在声明箭头函数时,就已经固定下来了。箭头函数中this的指向就是声明箭头函数是所在的对象。

先看一个常规的例子:

function foo() {
    setTimeout(function() {
        console.log("name:", this.name);
    }, 100);
}

foo(); // name: window

foo.call({ name: "an obj" }); // name: window

定义一个函数foo内部使用定时器调用一个匿名函数,此时函数有多层了,this的指向应该是全局对象window,输出结果证明了这一点。使用foo.call结果也相同的原因是,call替换的是foo函数内的this指向,而输出的this是在定时器的回调中的,故结果依然是window

我们再看一下箭头函数中这一点的表现:

// ES6箭头函数
function arrow_foo() {
    setTimeout(() => {
        console.log("name:", this.name);
    }, 100);
}

arrow_foo(); // name: window

arrow_foo.call({ name: "an obj" }); // name: an obj

我们发现结果,居然和上面不一样了。为什么呢?我们将其转化成ES5的结果来看一下,上面代码转化后的结果是这样的:

function arrow_foo() {
    var $__1 = this;
    setTimeout(function() {
      console.log("name:", $__1.name);
    }, 100);
  }
  arrow_foo();
  arrow_foo.call({name: "an obj"});

看一下转换后的结果,原因就一目了然了,箭头函数中this直接固定成了其定义时所在的对象,此处为foo。实际在箭头函数中的所有this都是一个对象,这个对象就是其定义时所在对象的this,上面转换后的结果中在foo中首先使用一个变量记录下this,而在箭头函数中的this被替换成了之前存储this的那个变量。

因此直接运行时,this是指全局对象,而使用call时,将foo内的this替换成了指定的对象{name: "an obj"},从而输出的上面的结果。

使用注意点 避免多层this

由于this的指向是不确定的,所以切勿在函数中包含多层的this。

var box = {
    name: "box",
    size: {
        width: 300,
        height: 300
    },
    show: function() {
        console.log("name", this.name);
        (function() {
            console.log("size", this.size);
        })();
    }
};
box.show();
// name box
// size undefined

我们本意是想在show方法内部输出name,并输出size,但是结果却并不是想要的这样,这是因为在立即执行的函数内部,this的执行不再是box对象而变成了顶层对象window,因此第二行输出为undefined

解决方法为,在外层用一个变量记录下this,在要使用的地方使用那个变量。

将上例进行改写:

var box = {
    name: "box",
    size: {
        width: 300,
        height: 300
    },
    show: function() {
        console.log("name", this.name);
        var that = this;
        (function() {
            console.log("size", that.size);
        })();
    }
};
box.show();
// name box
// size Object {width: 300, height: 300}

这样就能得到我们想要的正确结果了。

还用一种做法是JavaScript提供的严格模式use strict,如果函数内部的this直接指向了顶层对象会直接报错。

var box = {
    name: "box",
    size: {
        width: 300,
        height: 300
    },
    show: function() {
        "use strict"
        console.log("name", this.name);        
        (function() {
            console.log("size", this.size);
        })();
    }
};
box.show();  
// Uncaught TypeError: Cannot read property "size" of undefined(…)
避免在回调函数中使用this

通常回调函数中的this都有其特定的,如果在回调函数中使用this,应该需要了解其含义,否则可能出现意料之外的结果。

事件处理函数作为一种特殊的回调函数,其this是指当前的DOM对象,最开始的例子已经说明了这个问题。

回调函数本身是一个函数,其作为另一个函数的参数传递进去,然后在那个函数内部执行,这本身已经构成了多层this,此时this的指向是不确定的,需要慎用。

绑定this的方法

this的动态性给JavaScript带来了很大的灵活性,但是前面所描述的内容中也表现出了其不确定性,因此有时希望能够将this固定下来。

function.prototype.call()

使用函数的call方法,可以指定函数内部this的指向,使其在指定的作用域中运行。

var obj = {};

var f = function () {
  return this;
};

f() === this;  // true this === window
f.call(obj) === obj;  // true

上面代码中,在全局环境运行函数f时,this指向全局环境;call方法可以改变this的指向,指定this指向对象obj,然后在对象obj的作用域中运行函数f

call方法的第一个参数为一个对象,其表示要为函数所指定的运行上下文环境的对象(当指定为undefinednull是默认传入window),之后的参数依次作为原函数的参数。

function.prototype.apply()

使用函数的apply方法同样可以指定函数运行的环境,作用和call相同,使用方法也类似,都是第一个参数传入要指定的上下文对象。不同点在于,apply方法最多接收两个参数,第二个参数为一个数组(无论原函数需要的参数是何种类型,此数组中的每个元素将依次传递给原函数),表示传递给原函数的参数,而call可以接收多个参数,从第二个参数开始,之后的所有参数都传递给原函数。

由于apply第二个参数接收的是数组,其有很多巧用。由于此文重点是描述this关键字,就不再赘述了。

function.prototype.bind()

ES5中有bind这样一个方法,也可以指定函数的运行环境,但是和callapply有所不同,bind方法可接收一个参数,用于指定函数运行的上下文环境,返回一个函数作为绑定指定上下文环境后的新函数。

这样bindcallapply的区别就出来了:前者是根据指定的上下文环境返回一个新函数,而后两者是使用指定的上下文坏境运行原函数

其实bindjQuery.proxy()类似,虽然没有后者处理多种情况,但作为JavaScript原生方法,更轻量、高效。

用本文最开始的例子来演示此方法的使用,某对象下有某方法,我们要将此对象这个方法作为作为一个事件处理函数,但不希望方法内部的this被改变:



这样点击第二个按钮,将可以正确输出张三的名字。

bind第一个参数为一个对象,为undefinednull是默认传入window

除此之外,bind还可以接收额外参数,用于在生成新函数时,从原函数的第一个参数开始替换一部分参数(和jQuery.proxy()类似)。比如原函数要接收两个参数,使用bind产生新函数时,除了第一个参数的外,可以再传入一个参数,此参数将替换原函数的第一个参数,这样生成的新函数就只用接收一个参数了,详情见jQuery 工具方法简析 (target=_blank)中jQuery.proxy( function, context [, additionalArguments ] )

参考链接

this 关键字 (target=_blank)

深入理解上下文this (target=_blank)

ES6在线转化 (target=_blank)

js构造函数 (target=_blank)

原文发表在我的博客JavaScript this关键字,欢迎访问!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/82151.html

相关文章

  • JavaScript 工厂函数 vs 构造函数

    摘要:当谈到语言与其他编程语言相比时,你可能会听到一些令人困惑东西,其中之一是工厂函数和构造函数。好的,让我们用构造函数做同样的实验。当我们使用工厂函数创建对象时,它的指向,而当从构造函数创建对象时,它指向它的构造函数原型对象。 showImg(https://segmentfault.com/img/bVbr58T?w=1600&h=900); 当谈到JavaScript语言与其他编程语言...

    RayKr 评论0 收藏0
  • javascript基础:this键字

    摘要:它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用类似的还有。总结关键字就是,谁调用我,我就指向谁。注意由于已经被定义为函数内的一个变量。因此通过关键字定义或者将声明为一个形式参数,都将导致原生的不会被创建。 题目 封装函数 f,使 f 的 this 指向指定的对象 。 输入例子 bindThis(function(a, b) { return this.test +...

    LeoHsiun 评论0 收藏0
  • Javascript 深入浅出This

    摘要:中函数的调用有以下几种方式作为对象方法调用,作为函数调用,作为构造函数调用,和使用或调用。作为构造函数调用中的构造函数也很特殊,构造函数,其实就是通过这个函数生成一个新对象,这时候的就会指向这个新对象如果不使用调用,则和普通函数一样。 this 是 JavaScript 比较特殊的关键字,本文将深入浅出的分析其在不同情况下的含义,可以这样说,正确掌握了 JavaScript 中的 th...

    Y3G 评论0 收藏0
  • 理解 JavaScript 中的 this 键字

    摘要:原文许多人被中的关键字给困扰住了,我想混乱的根源来自人们理所当然地认为中的应该像中的或中的一样工作。尽管有点难理解,但它的原理并不神秘。在浏览器中,全局对象是对象。运算符创建一个新对象并且设置函数中的指向调用函数的新对象。 原文:Understanding the this keyword in JavaScript 许多人被JavaScript中的this关键字给困扰住了,我想混乱的...

    jayzou 评论0 收藏0
  • javascriptthis的理解

    摘要:的关键字总是让人捉摸不透,关键字代表函数运行时,自动生成的一个内部对象,只能在函数内部使用,因为函数的调用场景不同,的指向也不同。其实只要理解语言的特性就很好理解。个人对中的关键字的理解如上,如有不正,望指正,谢谢。 javascript的this关键字总是让人捉摸不透,this关键字代表函数运行时,自动生成的一个内部对象,只能在函数内部使用,因为函数的调用场景不同,this的指向也不...

    jimhs 评论0 收藏0
  • 理解JavaScript的核心知识点:This

    摘要:关键字计算为当前执行上下文的属性的值。毫无疑问它将指向了这个前置的对象。构造函数也是同理。严格模式无论调用位置,只取显式给定的上下文绑定的,通过方法传入的第一参数,否则是。其实并不属于特殊规则,是由于各种事件监听定义方式本身造成的。 this 是 JavaScript 中非常重要且使用最广的一个关键字,它的值指向了一个对象的引用。这个引用的结果非常容易引起开发者的误判,所以必须对这个关...

    TerryCai 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<