js专题之this关键字详解

对于那些没有投入时间学习this机制的前端来说,this的绑定一直是一件非常令人困惑的事。this是非常重要的,但是猜测,尝试并出错和盲目的从网上复制粘贴并不能让我们真正理解this的机制。

学习this的第一步是明白this既不指向自身也不指向函数的词法作用域,this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

1.this与全局函数(默认绑定)

function test(){
    console.log(this);  
}
test();  // window

当调用test()时,因为应用了this的默认绑定,this.a被解析成全局变量a,this指向全局对象window。所以结果为1。
怎么知道应用了默认绑定呢?当前test()是直接使用不带任何修饰的函数引用进行调用的。这个简单来说就是没有任何前缀啊等东西,很纯粹!而其调用位置是全局作用域,更能确定除了默认绑定,无法应用其他规则了。

将上面改一下,使用严格模式会怎样?

function test(){
    "use strict"
    console.log(this);  
}
test();  // undefined

虽然this的绑定规则完全取决于调用位置,但是只有test()运行在非严格模式下时,默认绑定才能绑定到全局对象;严格模式下与test()的调用位置无关;

2. this与对象中的方法(隐式绑定)

通俗地说就是一个函数,被当作引用属性添加到了一个对象中了,然后以 “对象名.函数名()” 形式进行调用,这时如果函数引用有上下文对象,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。

function test(){
    console.log(this.a);
}
var obj = {
    a:3,
    test:test
};
obj.test();   // 3

对象obj中包含两个属性a和test,其值分别是3和一个函数test()。
obj.test()表示函数引用时有上下文对象(也就是这里的obj)。
根据隐式绑定规则,会把test()中的this绑定到这个上下文对象,即this被绑定到obj,this.a===obj.a。
所以结果为3。

如果懂了,那么下面的例子也就会做了

function test(){
     console.log(this.a);
 }
 var obj2 = {
     a:4,
     test: test
 };
 var obj1 = {
     a:400,
     obj2: obj2
 }
 obj1.obj2.test()   // 4

看起来复杂了些,不过只要记住下面这个准则就可以了。

对象属性引用链中,只有上一层或者说最后一层在调用位置中起作用

2.1 隐式丢失

function foo(){
     console.log(this.a);
 }
 var obj = {
     a:5,
     foo:foo
 };
 var bar = obj.foo; // 函数别名
 var a = "global";
 bar(); // "global"

虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身,因此此时的bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。

再看一个例子:

function foo(){
     console.log(this.a);
 }
 function doFoo(fn){
     fn();
 }
 var obj = {
     a:5,
     foo:foo
 }
 var a = "oops, global";
 doFoo(obj.foo); // "oops, global"

其实和上面的没什么区别,只是这里把函数作为参数传递了,参数传递是一种隐式赋值。此时的this指向的是调用它的函数的对象即全局对象,因此应用了默认绑定。

如果把foo()函数传入语言内置的函数而不是传入自己声明的函数doFoo,又会怎样呢?

function foo() {
    console.log(this.a);
}
var obj = {
    a: 5,
    foo: foo
};

var a = "oops, global"
setTimeout(obj.foo, 100); // "oops, global" 

Javascript环境中内置的setTimeout()函数实现和下面的伪代码类似:

function setTimeout(fn, delay) {
    // 等待delay毫秒
    fn();   // <-- 调用位置
}

因此回调函数丢失this绑定是非常常见的,有时候调用回调函数的 函数还有可能会修改this。

3.this与apply,call,bind(显示绑定)

回顾一下隐式绑定,其关键是把函数当作引用属性添加到了对象中,通过这个属性间接引用这个函数,把this简介(隐式)绑定到这个对象上。

如果不想在对象内部包含函数引用,而想简单粗暴地在某个对象强制调用一个函数,这时就用到了函数的call()和apply()方法。
call和apply,以往我单纯理解为“控制this的指向”,现在才发现是原来是this绑定规则中的一种。

call和apply区别

apply接收的是数组参数,call接收的是连续参数。所以当传入的参数数目不确定时,多使用apply。

(tips:这里推荐个方法记忆apply和call各自接收的参数:apply为a开头,数组Array也是a开头,所以apply接收的是数组参数)

3.1 call

var xw = {
    name : "小王",
    gender : "男",
    age : 24,
    say : function(school,grade) {
        alert(this.name + " , " + this.gender + " ,今年" + this.age + " ,在" + school + "上" + grade);                                
    }
}
var xh = {
    name : "小红",
    gender : "女",
    age : 12
}

xw.say.call(xh, "实验小学", "六年级")

当调用say时强制把它的this绑定到xh上.

如果你传入了一个原始值(字符串类型,布尔类型或者数字类型)来当作this的绑定对象,这个原始值会被它的对象形式(也就是 new Strgin(...)、new Boolean(...)或者 new Number(...))。这通常被称为“装箱”。

3.2 apply

var xw = {
    name : "小王",
    gender : "男",
    age : 24,
    say : function(school,grade) {
        alert(this.name + " , " + this.gender + " ,今年" + this.age + " ,在" + school + "上" + grade);                                
    }
}
var xh = {
    name : "小红",
    gender : "女",
    age : 12
}

xw.say.apply(xh,["实验小学","六年级"]);

3.3 bind

var xw = {
    name : "小王",
    gender : "男",
    age : 24,
    say : function(school,grade) {
        alert(this.name + " , " + this.gender + " ,今年" + this.age + " ,在" + school + "上" + grade);                                
    }
}
var xh = {
    name : "小红",
    gender : "女",
    age : 12
}

xw.say.bind(xh)("实验小学","六年级");

这些例子也可以明显的看到call,apply,bind的区别。call后面的参数与say方法中是一一对应的,而apply的第二个参数是一个数组,数组中的元素是和say方法中一一对应的。而bind返回的仍然是一个函数,所以是在调用的时候再进行传参。

3.4 显示绑定的变种-硬绑定

扯远了,前面说到隐式丢失的问题,显示绑定的一个变种可以解决这种问题。

回顾当初隐式丢失的第二个例子。

 function foo(){
     console.log(this.a);
 }
 function doFoo(fn){
     fn();
 }
 var obj = {
     a:5,
     foo:foo
 }
 var a = "oops, global";
 doFoo(obj.foo); // "oops, global"

将其修改成:

function foo(){
     console.log(this.a);
 }
 function doFoo(fn){
     fn.call(obj);
 }
 var obj = {
     a:5,
     foo:foo
 }
 var a = "oops, global";
 doFoo(obj.foo);  // 5

依旧是创建了doFoo()这个函数,但是在其内部手动调用了obj.foo.call(obj),把foo()强制绑定到了obj对象,之后无论如何调用doFoo(),它总会手动在obj上调用foo.

再回顾隐式丢失的第一个例子

function foo(){
     console.log(this.a);
 }
 var obj = {
     a:5,
     foo:foo
 };
 var bar = obj.foo; // 函数别名
 var a = "global";
 bar(); // "global"

将其改成:

function foo(){
     console.log(this.a);
 }
 var obj = {
     a:5,
     foo:foo
 };
 var bar = obj.foo.bind(obj); // 函数别名
 var a = "global";
 bar(); // "5"

总结下“显示绑定三人组”

共同点:

1、都用于控制this指向;

2、第一个参数都是this需要指向的对象,也就是上下文;

3、都可以后续参数传递;

4、没有任何参数时,this都指向全局对象window >

区别:

1、call、apply绑定后立刻执行,bind是延迟执行。换言之,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,就使用bind()方法吧

4.this与new (new绑定)

使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:

  1. 创建(或者说构造)一个全新的对象
  2. 这个新对象会被执行[[原型]]链接。
  3. 这个新对象会绑定到函数调用的this.
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

在这里主要关心第1,3,4步。

function Foo(a) {
    this.a = a;
}
var bar = new Foo(2);
console.log(bar.a)  // 2

使用new来调用Foo(...)时,会构造一个新对象并把它绑定到foo(...)调用中的this上,new是最后一种可以影响到函数调用时this绑定行为的方法,称之为new绑定。

以上四中规则根据优先级从高到低排序如下:

  1. 函数在 new 中调用,this绑定的是这个新对象
  2. 函数通过 call、apply或bind 调用,this绑定的是指定对象
  3. 函数在某上下文对象中调用,this绑定的是这个上下文对象
  4. 以上都不是,使用默认绑定。(在全局作用域调用,this绑定window对象)

关于this还有很多其他知识,例如this与箭头函数,this与定时器,有硬绑定自然也会有软绑定。以后再补充。

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

推荐阅读更多精彩内容