js 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方法,但是由于环境不同,输出的结果也不同,根本原因是不同情况下的this是不一样的。
zs.describe(); // this === zs
ls.describe(); // this === ls
showName(); // this === window

绑定机制导致的this不同指代

<input type="button" name="按钮1" onclick="showName()" value="按钮1">
<input id="btn1" type="button" name="按钮2"  value="按钮2">
<input id="btn2" type="button" name="按钮3" value="按钮3">
<script>
window.name = 'window';
var showName = function() {
    console.log('My name is', this.name);
};
var btn1 = document.getElementById('btn1');
btn1.onclick = showName;
var btn2 = document.getElementById('btn2');
btn2.addEventListener('click', showName, false);
</script>

点击三个按钮,控制台输出结果分别是什么呢?
第一个为: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的使用归为一下几类。
    1.构造函数
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

2.对象的方法

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

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。

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

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' },从而输出的上面的结果。

使用注意点

1.避免多层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(…)

2.避免在回调函数中使用this

通常回调函数中的this都有其特定的, 如果在回调函数中使用this, 应该需要了解其含义, 否则可能出现意料之外的结果。
事件处理函数作为一种特殊的回调函数, 其this是指当前的DOM对象, 最开始的例子已经说明了这个问题。
回调函数本身是一个函数, 其作为另一个函数的参数传递进去, 然后在那个函数内部执行, 这本身已经构成了多层this, 此时this的指向是不确定的, 需要慎用。

3.绑定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方法的第一个参数为一个对象, 其表示要为函数所指定的运行上下文环境的对象( 当指定为undefined或null是默认传入window), 之后的参数依次作为原函数的参数。

  • function.prototype.apply()

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

  • function.prototype.bind()

ES5中有bind这样一个方法, 也可以指定函数的运行环境, 但是和call、 apply有所不同, bind方法可接收一个参数, 用于指定函数运行的上下文环境, 返回一个函数作为绑定指定上下文环境后的新函数。
这样bind和call、 apply的区别就出来了: 前者是根据指定的上下文环境返回一个新函数, 而后两者是使用指定的上下文坏境运行原函数。
其实bind和jQuery.proxy() 类似, 虽然没有后者处理多种情况, 但作为JavaScript原生方法, 更轻量、 高效。
用本文最开始的例子来演示此方法的使用, 某对象下有某方法, 我们要将此对象这个方法作为作为一个事件处理函数, 但不希望方法内部的this被改变:

< input id = "btn1" type = "button" name = "按钮1" value = "张三的名字是" > 
< input id = "btn2" type = "button" name = "按钮2" value = "张三的名字是" > 
< script > 
window.name = 'window';
var showName = function() { 
    console.log(this.name); 
};
var zs = { name: '张三' };
var btn1 = document.getElementById('btn1');
btn1.addEventListener('click', showName, false);
var btn2 = document.getElementById('btn2');
btn2.addEventListener('click', showName.bind(zs), false); 
< /script>

这样点击第二个按钮,将可以正确输出张三的名字。
bind
第一个参数为一个对象,为undefined或null是默认传入window
除此之外,bind还可以接收额外参数,用于在生成新函数时,从原函数的第一个参数开始替换一部分参数(和jQuery.proxy()
类似)。比如原函数要接收两个参数,使用bind产生新函数时,除了第一个参数的外,可以再传入一个参数,此参数将替换原函数的第一个参数,这样生成的新函数就只用接收一个参数了,详情见jQuery 工具方法简析jQuery.proxy( function, context [, additionalArguments ] )

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容

  • 请移步:https://blog.cdswyda.com/post/20161019
    依韵宵音阅读 583评论 5 19
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,517评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,514评论 18 399
  • 前言 javascript中的this,constructor ,prototype,都是老生常谈的问题,深入理解...
    Myselfyan阅读 406评论 0 1
  • HTML HTML5标签 媒体查询head部分写法 Doctype作用? 严格模式与混杂模式如何区分?它们有何意义...
    Mayo_阅读 633评论 0 8