JavaScript中的百变大咖~this

原文链接:http://www.jeffjade.com/2015/08/03/2015-08-03-javascript-this/

JavaScript作为一种脚本语言身份的存在,因此被很多人认为是简单易学的。然而情况恰恰相反,JavaScript支持函数式编程、闭包、基于原型的继承等高级功能。由于其运行期绑定的特性,JavaScript 中的 this 含义要丰富得多,它可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式。JavaScript中函数的调用有以下几种方式:作为对象方法调用,作为函数调用,作为构造函数调用,和使用 apply 或 call 调用。本文就采撷些例子以浅显说明在不同调用方式下的不同含义。

全局的this

全局this一般指向全局对象,浏览器中的全局对象就是 window。例如:

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

this.a = 91;
console.log(window.a); //91

一般函数的 this

function f1 () {
    return this;
}
console.log(f1() === window);//true, global object

可以看到一般函数的 this 也指向 window,在 nodeJS 中为 global object

function f2 () {
    "use strict";//使用严格模式
    return this;
}
console.log(f1() === undefined);//true

严格模式中,函数的 this 为 undefined

作为对象方法的函数的 this

var o = {
    prop: 37,
    f: function() {
        return this.prop;
    }
};
console.log(o.f()); // 37

上述代码通过字面量创建对象 o。

f 为对象 o 的方法。这个方法的 this 指向这个对象,在这里即对象 o。

var o = {
    prop: 37
};

function independent() {
    return this.prop;
}
o.f = independent;
console.log(o.f()); // 37

上面的代码,创建了对象 o,但是没有给对象 o,添加方法。而是通过 o.f = independent 临时添加了方法属性。这样这个方法中的 this 同样也指向这个对象 o。

作为函数调用

函数也可以直接被调用,此时 this 绑定到全局对象。在浏览器中,window 就是该全局对象。比如下面的例子:函数被调用时,this被绑定到全局对象,接下来执行赋值语句,相当于隐式的声明了一个全局变量,这显然不是调用者希望的。

function makeNoSense(x) { 
    this.x = x; 
} 
makeNoSense(5); 
x;// x 已经成为一个值为 5 的全局变量

对于内部函数,即声明在另外一个函数体内的函数,这种绑定到全局对象的方式会产生另外一个问题。以下面moveTo方法为例,内定义两个函数,分别将 x,y 坐标进行平移。结果可能出乎大家意料,不仅 point 对象没有移动,反而多出两个全局变量 x,y。

var point = { 
    x : 0, 
    y : 0, 
    moveTo : function(x, y) { 
        // 内部函数
        var moveX = function(x) { 
            this.x = x;//this 绑定到了哪里?
        }; 
        // 内部函数
        var moveY = function(y) { 
            this.y = y;//this 绑定到了哪里?
        }; 

        moveX(x); 
        moveY(y); 
    } 
}; 
point.moveTo(1, 1); 
console.log(point.x) //0
console.log(point.x) //0
console.log(x)       //1
console.log(y)       //1

这属于 JavaScript 的设计缺陷,正确的设计方式是内部函数的this应该绑定到其外层函数对应的对象上,为了规避这一设计缺陷,聪明的JavaScript程序员想出了变量替代的方法,约定俗成,该变量一般被命名为 that。

对象原型链上的this

var o = {
    f: function() {
        return this.a + this.b;
    }
};
var p = Object.create(o);
p.a = 1;
p.b = 2;
console.log(p.f()); //3

通过 var p = Object.create(o) 创建的对象,p 是基于原型 o 创建出的对象。

p 的原型是 o,调用 f() 的时候是调用了 o 上的方法 f(),这里面的 this 是可以指向当前对象的,即对象 p。

get/set 方法与 this

function modulus() {
    return Math.sqrt(this.re * this.re + this.im * this.im);
}
var o = {
    re: 1,
    im: -1,
    get phase() {
        return Math.atan2(this.im, this.re);
    }
};
Object.defineProperty(o, 'modulus', {
    get: modulus,
    enumerable: true,
    configurable: true
});
console.log(o.phase, o.modulus); // -0.78 1.4142

get/set 方法中的 this 也会指向 get/set 方法所在的对象的。

构造器中的 this

function MyClass() {
    this.a = 25;
}
var o = new MyClass();
console.log(o.a); //25

new MyClass() 的时候,MyClass()中的 this 会指向一个空对象,这个对象的原型会指向 MyClass.prototype。MyClass()没有返回值或者返回为基本类型时,默认将 this 返回。

function C2() {
    this.a = 26;
    return {
        a: 24
    };
}

o = new C2();
console.log(o.a); //24

因为返回了对象,将这个对象作为返回值

call/apply 方法与 this

function add(c, d) {
    return this.a + this.b + c + d;
}
var o = {
    a: 1,
    b: 3
};
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
function bar() {
    console.log(Object.prototype.toString.call(this));
}
bar.call(7); // "[object Number]"
bar.call(); //[object global]
bar.call("7");//[object String]
bar.call(true);//[object Boolean]
console.log(add.call(o,5,7));//16

bind 方法与 this

function f() {
    return this.a;
}
var g = f.bind({
    a: "test"
});
console.log(g()); // test
var o = {
    a: 37,
    f: f,
    g: g
};
console.log(o.f(), o.g()); // 37, test

绑定之后再调用时,仍然会按绑定时的内容走,所以 o.g() 结果是 test


JavaScript中this的些许看似怪异现象

<body>
    <!--JavaScript伪协议和内联事件对于this的指向不同-->
    <a href="#" onclick="alert(this.tagName);">click me</a> <!--弹出A-->
    <a href="javascript:alert(this.tagName);">click me</a>  <!--弹出undefined-->
    <a href="javascript:alert(this==window);">click me</a>  <!--弹出true-->

    <input id="btn" type="button" value="this demo" name="button"/>
</body>
var name = 'somebody';
var angela = {
    name: 'angela',
    say: function () {
        alert("I'm " + this.name);
    }
};
var btn = document.getElementById('btn');

setTimeout和setInterval也会改变this的指向

angela.say();//I'm  angela
setTimeout(angela.say, 1);  //I'm  somebody
setInterval(angela.say, 1); //I'm  somebody

on...也会改变this的指向

angela.say(); //I'm  angela
btn.onclick = angela.say; //I'm  button

click等回调也会改变this指向

$("#btn").click = angela.say;  // I'm  button
$("#btn").click(angela.say);   // I'm  button

如果在say中用了this,this会绑定在angela上么?显然这里不是,赋值以后,函数是在回调中执行的,this会绑定到$(“#btn”)元素上。这个函数被完整到onclick属性(现在成为了函数)。因此如果这个even thandler被执行,this将指向HTML元素;因此,结果显示的是"I'm button"。而,匿名函数可以调整this指向,EG:

$("#btn").click(function(){ 
    angela.say();  //I'm  angela
});

匿名函数调整this指向比如:

    setTimeout(function () { angela.say(); }, 1); //I'm  angela
    setInterval(function () { angela.say(); }, 1) //I'm  angela
    btn.onclick = function () { angela.say(); };     //I'm  angela
    setTimeout(function () { alert(this == window); }, 1);//true
    btn.onclick = function () { alert(this == btn); }//true

匿名函数赋值给了click属性(好吧,现在成了函数),此时这个匿名函数指向的即是Html属性。因此所调用的函数(比如angela.say())this上下文没有被更改,所以其打印出来的结果就是'I'm angela'。事实上,也用这样的方法来消解this在回调函数中不堪使用的'特色'。

$("#btn").click(function(){ 
    if(window == this){
        alert("window == this");
    }else{
        alert("window != this")  //弹出来
    }
    alert(this.name); // button
    angela.say();    //I'm  angela
});

将this指向的对象保存到变量(一般用that)

    var mydemo = {
        name: 'angela',
        say: function () { alert("I'm " + this.name); },
        init: function () {
            var that = this;
            document.getElementById('btn').onclick = function () {
                that.say();  //弹出Alert:I'm angela
                this.say();  //这儿报错: undefined is not a function (evaluating 'this.say()')  
            }
        }
    };
    mydemo.init();

Javascript中的eval 方法

JavaScript 中的 eval 方法可以将字符串转换为 JavaScript 代码,使用 eval 方法时,this 指向哪里呢?答案很简单,看谁在调用 eval 方法,调用者的执行环境(ExecutionContext)中的 this 就被 eval 方法继承下来了。(悪,还没用过,有待实践下)

后记:由于javascript的动态性(解释执行,当然也有简单的预编译过程),this的指向在运行时才确定,因此在只要足够留心其运行时的上下文,即可无痛挥霍this的强大。

参考AJavaScript 中的 this
参考BJavaScript中this的一些怪异现象
参考CJavascript的this用法-阮一峰
参考D深入浅出 JavaScript 中的 this

更多相关文章
  • 资料图据BGR网站8月4日消息,索尼加入众多公司阵营,成为想从莫迪政府"印度制造"新方案中受益的最新成员.4日,索尼公司称将与富士康结成同盟,在印度生产Bravia电视.富士康将在位于泰米尔纳德邦的斯克里伯鲁布德工厂生产Bravia电视,第一批该地生产的电视装置将于今天开始货运. ...
  •   Python是一种面向对象的解释性的计算机程序设计语言,也是一种功能强大且完善的通用型语言,已经有十多年的发展历史,成熟且稳定.Python 具有脚本语言中最丰富和强大的类库,足以支持绝大多数日常应用.Python语言有非常简捷.清晰的语法特点,适合完成各种高层任务,可以在所有操作系统中运行.目 ...
  • 易网科技讯 2月25日消息,在2014年MWC(移动通信世界大会)的第二天,中国移动宣布与韩国运营商KT正式开通4G商用双向国际漫游服务,包括数据漫游和语音漫游.据了解,语音漫游目前采用了现阶段国际主流方案CSFB,后续采用VoLTE话音解决方案后,可为用户提供更清晰.优质的话音通话服务.
  • 王长春   上海诚美董事长张恩,是中国自主化妆品业界小有名气的女企业家.2年代初,她在河南代理化妆品销售过程中,敏锐感知了中国女性群体顽强的美丽需求,通过与日本品牌合作,她逐步打造了温和滋润为主要功能的诚美体系,最终成为这个市场重要玩家. 然而,在成功获取第一个亿元级别财富后,张恩遭遇到了新挑战:互 ...
  • 沈南鹏,红杉资本中国基金创始及执行合伙人.周鸿祎说,沈南鹏是一个饥饿的人,他看到项目就像闻到了血腥味的狼一样,或者像鲨鱼闻到血腥味一样,他听到一点风声他就会去拼抢,会去追踪,是一个非常积极的人.口述/周鸿祎 采访.整理/ 邹玲我认识沈南鹏很早了,他在携程的时候就认识.我记得那会是我离开雅虎后不久,有 ...
  • 2.AreaRegistration的缓存 Area的注册(主要是基于Area的路由映射注册)通过具体的AreaRegistration来完成.在应用启动的时候,ASP.NET MVC会遍历通过调用BuildManager的静态方法GetReferencedAssemblies得到的程序集列表,并从 ...
一周排行
  • 以下内容由在论坛中讨论而成,在此总结一下: 首先问题来自这个函数: [cpp] int fun()  {      static int i=0;      i++;      return i;  }  当这个函数 ...
  •  一,ecshop安装,其实很简单,只要一直下一步下一步点击即可,这样总是没有错的,因为官方不可能给我们一个有问题的程序,尽量从简即可.  请注意一下两点 A:在安装ecshop的时候,不要将所有文件都设置成777. ...
  •   1. 重启电脑,按F8,进入带命令行的安全模式 2. 进入C盘根目录,运行以下命令:   attrib -S -H -R lpk.dll /s attrib -S -H -R usp10.dll /s del/s ...
  • 截止1分,小米天猫旗舰店支付金额已经突破9亿.今天0,0,0还有三场,备货很充足,大家不用着急,慢慢下单.感兴趣的朋友们赶快参与吧 用了4个小时就破了去年的记录,小米今年双11的战绩斐 ...
  • 今天上午技术部赶着测某个项目.其中一位说某个语句无法执行.该语句大致如下:select  param_valuefrom tablewhere param_type='bank'and  to_number(para ...
  • 在交换机上进行DHCP服务的设置,是我们将要讲解的内容.这次主要针对三层交换机配置DHCP的内容进行介绍.解决方案: 一,如果不用交换机的DHCP功能而是利用PC的DHCP功能! 1.在交换机配置DHCP服务器: i ...
  • 美国科技资讯网站ZDNet刊文称,微软在巴塞罗那移动世界大会(MWC)上发布了Windows 8的消费者预览版.微软希望Windows 8能成为普通消费者和企业用户之间的桥梁,而企业用户对安全性和管理功能要求更高. ...
  • 编程很酷.但在酷的背后对于很多人来说确实很难的.很多人在学习编程的早期阶段就放弃了.在你对编程不熟悉时,你可能发现自己不知道从哪里开始,首先做什么,哪里使用这些知识.一旦你度过了艰苦的学习阶段,你会发现一个新世界.下 ...
  • 使用Mvc里的插件jquery.unobtrusive-ajax.min.js 之前一直困在这里,一开始以为添加属性enctype="multipart/form-data"就可以 网上看到都是用 ...
  • 关于iuni n1手机上市时间和配置参数已经介绍完毕,相信大伙对这款新机有了一定了解,喜不喜欢看个人爱好了,那么这款iuni n1手机多少钱呢 关于iuni n1手机上市时间和配置参数已经介绍完毕,相信大伙对这款新机 ...