对于this的使用,我们最常遇到的主要有,在全局函数中,在对象方法中,call和apply时,闭包中,箭头函数中以及class中;

我们知道this对象是在运行时基于函数的执行环境绑定的,在调用函数之前,this的值并不确定,因此this会在代码执行过程中引用不同的对象。哪个对象实例调用this所在的函数,那么this就代表哪个对象实例。

1.    全局函数

在全局函数中,this等于window;

var name = "Tina";
function sayName() {
alert(this.name);
}
person();//Tina

在这里,由于函数person()是在全局环境中执行的,也是在全局作用域中window对象调用的person();故此时的this便指向window对象。而当把这个函数赋给对象o并调用o.sayName()时,this引用的是对象o,因此对this.name的求值就变成了对o.name求值。

var name = "Tina";
var o={name: "Tony"};
function sayName() {
alert(this.name);
}
o.sayName=sayName;
o.sayName();//"Tony"

2. 对象方法

       当函数被作为某个对象的方法调用时,this等于那个对象;

var name="Tina";
var obj={
name="Tony",
getName: function() {
alert(this.name);
}
};
obj.getName();//"Tony"

3.call()和apply()和bind()

      我们知道,call(ctx, parm1,parm2,parm3...)和apply(ctx,[parms])的用途都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值;

function sum(num1, num2)  {
return num1+num2;
}
function callSum1(num1, num2) {
return sum.apply(this, [num1, num2]);
}
function callSum2(num1,num2) {
return sum.call(this, num1, num2);
}
alert(callSum1(10, 10)); //
alert(callSum2(10, 10));//

在上面的例子中,callSum1()和callSum2()在执行函数sum()时传入了this作为this值(因为是在全局作用域中调用的,所以传入的就是window对象);事实上,call和apply最强大之处是能够扩充函数赖以运行的作用域;来看下面的例子:

window.color="red";
var o={ color: "blue"};
function sayColor() {
alert(this.color);
}
sayColor();//"red"
sayColor.call(this);//"red"
sayColor.call(window);//"red"
sayColor.call(o);//"blue"

bind()方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。例如:

window.color="red";
var o={ color: "blue" };
function sayColor() {
alert(this.color);
}
var objsayColor = sayColor.bind(o);
objsayColor();//"blue"

另一个使用场景是函数绑定,函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数,该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境。

var handler = {
message: "Event handled",
handleClick : function(event) {
alert(this.message);
}
};
var btn = document.getElementById("my_btn");
btn.addEventListener("click", handler.handleClick, false);

当按下该按钮时,就调用该函数,显示一个警告框,虽然貌似警告框应该显示Event handled,然而实际上显示的是undefined。原因在于没有保存handler.handleClick()的执行环境,所以this对象最后指向了DOM按钮而非handler(在IE8中,this指向window)。一种方法,可以使用一个闭包来修正这个问题。

var handler = {
message: "Event handled",
handleClick : function(event) {
alert(this.message);
}
};
var btn = document.getElementById("my_btn");
btn.addEventListener("click", function(event){
handler.handleClick(event);
}, false);

这个解决方案在onclick事件处理程序内使用了一个闭包直接调用handler.handleClick(),当然,这是特定与本段代码的解决方案。我们知道,创建多个闭包可能会令代码变得难以理解和调试。因此,很多JS库实现了一个可以将函数绑定到指定环境的函数,这个函数一般叫bind();ECMAScript 5为所有函数定义了一个原生的bind()方法,它的使用方式如下:

var handler = {
message: "Event handled",
handleClick : function(event) {
alert(this.message+":"+event.type);
}
};
var btn = document.getElementById("my_btn");
btn.addEventListenr("click", handler.handleClick.bind(handler), false);

一个简单的bind()函数接受一个参数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动地传递过去。语法如下:

function bind(fn, context) {
return function() {
return fn.apply(context, arguments);
};
}

这个函数在bind()中创建了一个闭包,闭包使用apply调用传入的函数,并给apply传递context对象个arguments对象数组,这里的arguments对象是内部函数(匿名函数)的,而非bind()的参数。当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数。

原生的bind()方法和前面自定义的bind()方法类似,都是要传入作为this值的对象。它们主要用于事件处理程序以及setTimeout()和setInterval()。bind绑定在react事件处理中也常常和箭头函数一样起到绑定this的效果。

4. 闭包

      有时候,由于编写闭包的方式不同,在闭包中使用this对象可能会导致一些问题;

var name = "The window";
var object = {
name: "My Object",
getNameFunc: function() {
return function() {
return this.name;
};
}
};
alert(object.getNameFunc()());//"The window"

由于getNameFunc()返回一个函数,因此调用object.getNameFunc()()就会立即调用它返回的函数,结果就是返回一个字符串"The window",即全局变量的值,此时匿名函数没有取得其包含作用域(外部作用域)的this对象。原因在于内部函数在搜索两个特殊变量this和arguments时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。这时,只需把把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。

var name = "The window";
var object = {
name: "My Object",
getNameFunc: function() {
var that = this;
return function() {
return that.name;
};
}
};
alert(object.getNameFunc()());//"My Object"
//节流
function throttle(fn, delay) {
var previous = Data.now();
return function() {
var ctx = this;
var args = arguments;
var now = Data.now();
var diff = now-previous-delay;
if(diff>=0) {
previous = now;
setTimeout(function() {
fn.apply(ctx, args);
}, delay);
}
};
}

5. 箭头函数

      我们知道,箭头函数有几个需要注意的点:

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象;

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误;

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用rest参数代替;

(4)不可以使用yield命令,因此箭头函数不能用作Generator函数;

这里我们只谈论第一点;this对象的指向是可变的,但在箭头函数中,它是固定的;

function foo() {
setTimeout(() => {
console.log('id: ', this.id);
}, 100);
}
var id=21;
foo.call({id: 31});//id: 31

上述代码中,setTimeout是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它真正加入到执行栈后还要等到100毫秒后才会执行,如果是普通函数,此时的this应该指向全局对象window,这时应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id:31})所以输出的是id: 31;

箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。下面是另一个例子:

function Timer() {
this.s1 = 0;
this.s2 = 0;
setInterval(() => this.s1++, 1000);
setInterval(function() {
this.s2++;
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);//s1: 3
setTimeout(() => console.log('s2: ', timer.s2), 3100);//s2: 0

上面代码中,Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的作用域(Timer函数),后者的this指向运行时所在的作用域(即全局对象)。所以,3100毫秒后,timer.s1被更新了3次,timer.s2一次都没更新。

箭头函数可以让this指向固定化,这种特性很有利于封装回调函数。下面代码将DOM事件的回调函数封装在一个对象里面。

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对象。否则,回调函数运行时,this.doSomething这一行会报错,因为此时this指向document对象。this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码的this。正是因为它没有this,所以也就不能用作构造函数。由于箭头函数没有自己的this,所以当然不能用call()、apply()、bind()改变this的指向。

6. class

      类的方法内部如果含有this,它默认指向类的实例。

class Logger {
/*constructor() {
this.printName = this.printName.bind(this);
}*/
printName(name = 'Nicolas') {
this.print(`Hello ${name}`);
}
print(text) {
console.log(text);
}
}
const logger = new Logger();
const { printName } = logger;
printName();

上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境,因为找不到print方法而导致报错。一种简单的解决方法就是在构造函数中绑定this。而另一种方法是使用箭头函数:

class Logger {
constructor() {
this.printName = (name='Nicolas') => {
this.print(`Hello ${name}`);
     }
}
print(text) {
console.log(text);
}
}
const logger = new Logger();
const { printName } = logger;
printName();

还有一种方法是使用Proxy,获取方法的时候,自动绑定this。

function selfish (target) {
const cache = new WeakMap();
const handler = {
get (target, key) {
const value = Reflect.get(target, key);
if (typeof value !== 'function') {
return value;
}
if (!cache.has(value)) {
cache.set(value, value.bind(target));
}
return cache.get(value);
}
};
const proxy = new Proxy(target, handler);
return proxy;
}
const logger = selfish(new Logger());

参考:

https://reactjs.org/docs/handling-events.html

http://es6.ruanyifeng.com/#docs/class

更多内容请参考:

http://www.ruanyifeng.com/blog/2018/06/javascript-this.html

Javascript中的this对象的更多相关文章

  1. JavaScript中的事件对象

    JavaScript中的事件对象 JavaScript中的事件对象是非常重要的,恐怕是我们在项目中使用的最多的了.在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含这所有与事件有 ...

  2. JavaScript中创建字典对象(dictionary)实例

    这篇文章主要介绍了JavaScript中创建字典对象(dictionary)实例,本文直接给出了实现的源码,并给出了使用示例,需要的朋友可以参考下 对于JavaScript来说,其自身的Array对象 ...

  3. Javascript学习1 - Javascript中的类型对象

    原文:Javascript学习1 - Javascript中的类型对象 1.1关于Numbers对象. 常用的方法:number.toString() 不用具体介绍,把数字转换为字符串,相应的还有一个 ...

  4. 简单使用JSON,JavaScript中创建 JSON 对象(一)

    JSON:JavaScript 对象表示法(JavaScript Object Notation). JSON 是存储和交换文本信息的语法.类似 XML. JSON 比 XML 更小.更快,更易解析. ...

  5. JavaScript中的window对象

    JavaScript中的window对象:http://www.cnblogs.com/kissdodog/archive/2013/01/01/2841464.html

  6. js:JavaScript中的ActiveXObject对象

    JavaScript中的ActiveXObject对象作用: https://blog.csdn.net/pl1612127/article/details/77862174

  7. 详解javascript中的this对象

    详解javascript中的this对象 前言 Javascript是一门基于对象的动态语言,也就是说,所有东西都是对象,一个很典型的例子就是函数也被视为普通的对象.Javascript可以通过一定的 ...

  8. javascript中如何获取对象名

    javascript中如何获取对象名 一.总结 一句话总结:将对象传入参数,看参数是否为函数(js中的对象和函数是一个意思么(函数肯定是对象)),对象参数.name属性即可获得 //版本4 funct ...

  9. JavaScript中的global对象,window对象以及document对象的区别和联系

    JavaScript中的global对象,window对象以及document对象的区别和联系 一.概念区分:JavaScript中的global对象,window对象以及document对象 1.g ...

  10. 【JavaScript】JavaScript中的ActiveXObject对象

    JavaScript中ActiveXObject对象是启用并返回 Automation 对象的引用.    使用方法: newObj = new ActiveXObject( servername.t ...

随机推荐

  1. Java 条件语句

    1.if...else 一个 if 语句包含一个布尔表达式和一条或多条语句. if(布尔表达式) { //如果布尔表达式为true将执行的语句 }else{ //如果布尔表达式为false将执行的语句 ...

  2. 继承Application管理生命周期

    继承Application实现Android数据共享 http://www.jianshu.com/p/75a5c24174b2 jessyan提出一个思路,用Application + 接口来管理扩 ...

  3. 对 Vue 的理解(一)

    一.什么是 Vue ? 首先,Vue 是一个 MVVM 框架,M -- 模型,就是用来定义驱动的数据,V -- 视图,是经过数据改变后的 html,VM -- 框架视图,就是用来实现双向绑定的中间桥梁 ...

  4. Django的模型层

    一.ORM简介 MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人 ...

  5. git如何进行远程分支切换

    git如何进行远程分支切换 git上查看远程分支命令: git branch -a  例如: 然后我想切换到daily/1.0.0远程分支:前提是必须要创建一个本地分支,并让它和远程分支进行关联,gi ...

  6. Android 时间范围选择器PickTimeDialog

    个人提供了自己封装的第三方时间选择器,能够自定义时间格式,以及设置时间范围. 基础使用 PickTimeDialog pickDilog= new PickTimeDialog(this).setMa ...

  7. 《ArcGIS Runtime SDK for Android开发笔记》——数据制作篇:发布具有同步能力的FeatureService服务

    1.前言 从ArcGIS 10.2.1开始推出离在线一体化技术之后,数据的离在线一体化编辑一直是大家所关注的一个热点.数据存储在企业级地理数据库中,通过ArcGIS桌面软件加载后配图处理,并发布到Ar ...

  8. libcurl 中使用curl_multi_perform()函数执行订阅类型url的问题

    前提概要 当需要同时处理多个url时,可采用curl_multi_perform方式执行,如下代码1: //初始化一个multi curl 对象 CURLM * curl_m = curl_multi ...

  9. 内存分配malloc函数注意事项。

    malloc的全称是memory allocation,中文叫动态内存分配,用于向系统申请分配指定字节的内存空间 原型:extern void *malloc(unsigned int num_byt ...

  10. SQL Server 数据库修改后不允许保存

    打开 工具 - > 选项 -> 设计器,确认[阻止保存要求重新创建表的更改]项是否选中,如果选中,取消即可.