JavaScript:this学习

在函数执行时,this 总是指向调用该函数的对象。要判断 this 的指向,其实就是判断this 所在的函数是谁在调用。至于函数在哪里定义,定义的方式(是声明方式还是表达式方式),都无关紧要。

  • 有对象就指向调用对象
  • 没调用对象就指向全局对象
  • 用new构造就指向新对象
  • 通过 applycallbind 来改变 this 的所指。

this指向调用的对象

  • 在何处或者如何定义调用函数完全不会影响到this的行为。谁调用函数才是关键。
  • 当对象嵌套时,this指向的是调用函数的那一个对象。至于外面嵌套的,以及里面被嵌套的,都不会去查找。
    比如下面的例子:
    b通过属性g()调用函数independent时,this就指向b。这时候b只管自己,一看没找到指定属性prop,就输出undefined。他不管外面的o或者里面的c有没有要找的属性prop;他不会去找的。
function independent() {
  return this.prop;   // 函数全局定义,但是this要调用的时候才能确定,现在不知道
}

var o = {
    prop: 37,
    f: independent,
    b: {
        g: independent,
        c: {
            prop: 50, 
            h: independent
        }
    }
};

// 这里的调用者是o,所以this指向o
console.log(o.f()); // logs: 37

// 这里的调用者是b,所以this指向b;对于外面的o,以及里面嵌套的c,都不可见
console.log(o.b.g()); // logs: undefined

// 这里的调用者是c,所以this指向c 
console.log(o.b.c.h());  // logs: 50
  • 原型链中的 this指向的也是调用函数的对象,至于函数是在子类中定义的还是在父类中定义的,无关紧要。
    比如下面的例子:
    子类p中的函数f是从父类o中继承来的。当p调用函数f的时候,this就指向p
var o = {
  f : function(){ 
    return this.a + this.b; 
  }
};
// 原型链:p --> o --> Object.prototype --> null
var p = Object.create(o);
p.a = 1;
p.b = 4;

// 调用者是p,this指向p
console.log(p.f()); // 5

// 调用者是o,this指向o;o没有a和b,所以输出NaN
console.log(o.f()); // NaN

this指向全局对象

  • 在全局运行上下文中(在任何函数体外部),this指代全局对象
console.log(this.document === document); // true

// 在浏览器中,全局对象为 window 对象:
console.log(this === window); // true

this.a = 37;
console.log(window.a); // 37
  • 在函数中使用this,不过调用函数的地方是全局的,此时this就是指向全局的。
function f1() {
    return this;
}
// 全局调用,this指向全局,在浏览器下是window
f1() === window; // true
  • 如果找不到调用函数的具体对象,(不是函数的作用域),那么默认就是全局对象。
function fn1() {
    var a = 2;
    fn2();
}
function fn2() {
    console.log( this.a );
}
// 调用fn2的是函数fn1,找不到具体的对象,所以fn2中的this指向全局对象window
fn1(); // log: undefined

this应该指向一个具体的对象,或者全局对象,比如window,跟函数的作用域{}一点关系没有。另外,this的确定是在运行时,而不是在定义的时候。所以哪个对象调用了函数才是确定this到底指向哪个对象的关键点。
下面代码对this的理解和使用都是错误的,应该避免这种用法

function fn1() {
    var a = 2;
    this.fn2(); // 以为this引用的是fn1的词法作用域是错误的,这里的this要删除,没有作用。
                // 另外这里是函数定义,this的具体指向不确定。比如本例中,下面代码调用fn1的是全局对象window,所以this指向了全局对象window
}
function fn2() {
    console.log( this.a );
}
// 调用fn2的是函数fn1,找不到具体的对象,所以fn2中的this指向全局对象window
// 调用fn1的是全局对象window,所以fn1中的this指向全局对象window
fn1(); // log: undefined
  • 失去隐式绑定的情况: 如果找不到具体的调用对象,就会指向默认的全局对象。下面这种情况也是需要注意区分的:
function fn() {
    console.log( this.a );
}

var a = "全局"; // 定义全局变量
var obj = {
    a: "obj内部", // 定义内部变量
    fn: fn
};
var bar = obj.fn; // 函数引用传递; 其实就是bar = fn; ==> bar和obj.fn都指向了fn

// 相当于fn(); 而不是obj.fn(); 调用者是全局,所以this指向全局
bar(); // "全局"
// 调用者是obj,所以this指向obj
obj.fn(); // "obj内部"

构造函数中的this

  • 当一个函数被作为一个构造函数来使用(使用new关键字),它的this与即将被创建的新对象绑定。
function C(){
  this.a = 37;   // 这里只是函数定义,还没有运行,this指谁还不确定
}
// 构造过程就是构造函数的执行,已经是运行时,(函数调用也是运行时了),this可以确定。这里和o绑定,this指向o
var o = new C();
console.log(o.a); // logs 37
  • js 中,构造函数、普通函数、对象方法、闭包,这四者没有明确界线。如果使用不当,构造函数就会改变其原来的意义。
    比如下面这种用法就强烈不推荐使用:
function C2(){
  this.a = 37;
  // 这里手动返回了对象,默认的this被取消;
  // new出来的对象和返回的这个对象绑定,而不是跟this。
  // 这就导致this.a = 37;成了“僵尸”代码。执行了,但是外界根本拿不到
  return {a:38};
}
// 这里o和对象{a:38}绑定,而不是this
o = new C2();
console.log(o.a); // logs 38
  • 构造函数功能和其他语言中的类差不多,ES6引入的class关键字,就可以看做是构造函数改头换面而来的。
    在构造函数的使用上,需要遵循一定的规范,充分发挥其本职功能。到ES6切换到class关键字,也就水到渠成。
    (1)定义用声明形式。不要用表达式形式。
    表达式格式的函数定义方式是为了让函数成为变量,可以当做参数,返回值等使用,这给函数式编程带来了很大的方便。这个适合普通函数,对于构造函数,强烈不推荐。
    (2)构造函数的命名采用大驼峰式,和类的命名习惯一致
    (3)构造函数不要有返回值
    (4)函数或者共享变量,定义在构造函数的原型上,节省内存;定义的方式应该在构造函数的外面。构造函数名.prototype = ...;

通过 applycallbind 来改变 this 的所指

  • 当一个函数的函数体中使用了this关键字时,通过所有函数都从Function对象的原型中继承的call()方法和apply()方法调用时,它的值可以绑定到一个指定的对象上。
function add(c, d){
  return this.a + this.b + c + d;
}

var o = {a:1, b:3};

// The first parameter is the object to use as 'this', subsequent parameters are passed as 
// arguments in the function call
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16

// The first parameter is the object to use as 'this', the second is an array whose
// members are used as the arguments in the function call
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
  • 使用 callapply 函数的时候要注意,如果传递的this值不是一个对象,JavaScript 将会尝试使用内部 ToObject操作将其转换为对象。
    因此,如果传递的值是一个原始值比如 7或 'foo' ,那么就会使用相关构造函数将它转换为对象,所以原始值7通过new Number(7)被转换为对象,而字符串'foo'使用 new String('foo')转化为对象,例如:
function bar() {
  console.log(Object.prototype.toString.call(this));
}
bar.call("foo");  // [object String]
bar.call(7);      // [object Number]
  • ECMAScript 5 引入了 Function.prototype.bind
    。调用f.bind(someObject)会创建一个与f具有相同函数体和作用域的函数,但是在这个新函数中,this将永久地被绑定到了bind的第一个参数,无论这个函数是如何被调用的。
function f(){
  return this.a;
}

// function g 与对象{a:"azerty"}绑定,并且不变
var g = f.bind({a:"azerty"}); 
// 全局调用g,this仍然指向{a:"azerty"}
console.log(g()); // azerty
// 对象o调用f,this指向o;对象o调用g,this仍然指向{a:"azerty"}
var o = {a:37, f:f, g:g};
console.log(o.f(), o.g()); // 37, azerty

内部函数

  • 在内部函数中,this没有按预想的绑定到外层函数对象上,而是绑定到了全局对象。这里普遍被认为是JavaScript语言的设计错误,因为没有人想让内部函数中的this指向全局对象。
var name = "global";  

var person = {  
    name : "person",  
    hello : function(sth) {  
        var sayhello = function(sth) {  
            console.log(this.name + " says " + sth);  
        };  
        sayhello(sth);  
    }  
}   
person.hello("hello world"); // global says hello world 
  • 一般的处理方式是将this作为变量保存下来,一般约定为that或者self
var name = "global";  

var person = {  
    name : "person",  
    hello : function(sth) {  
        var that = this; 
        var sayhello = function(sth) {  
            console.log(that.name + " says " + sth);  
        };  
        sayhello(sth);  
    }  
}   
person.hello("hello world"); // person says hello world 

参考文章

this
这篇文章的例子挺不错的

javascript中this的四种用法
开头总结的几句话不错,方便理解

详解 JavaScript 中的 this
内部函数的例子来自这里,不过格式错误,要稍微改一下

别再为了this发愁了:JS中的this机制
这篇文章中,那个this不是指函数作用域{}的例子很好。
this跟函数在哪里定义没有半毛钱关系,函数在哪里调用才决定了this到底引用的是啥。”这句话也讲得非常到位。

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

推荐阅读更多精彩内容