《你不知道的javascript》学习总结I - this关键字

javascript中的this关键字

上一张初略版this分析导图先:

this在英文中是一个指示代词,指示将要表达中的名词。

js王国中的this有会是指向谁了,先看看this常有的误解认识

  • 误解1: this指向函数本身

    function fun(){
        this.count++
        console.log('invoke fun')
    }
    fun.count=0
    fun()
    console.log(fun.count)
    
    输出: 
    invoke fun
    0
    

    上面的代码并没有输出1, 可见this并非指向函数本身

  • 误解2: this指向当前作用域

    function f1(){
        var a = 'inner.f1'
        this.f2()
    }
    
    function f2(){
        console.log('a='+this.a)
    }
    
    var a = 'global'
    f1()
    输出:
    a=global
    

    同样上面的代码然而并没有输出'a=inner.f1',这样this也并非指向当前作用域。

上面已经描述this不是什么了,那么this到底该指向什么了。 其实在js中一个函数被调用时,引擎会创建一个执行上下文记录,该记录包含函数调用位置,函数调用方式,传入的参数信息。this就是执行上下文记录中的一个属性。 this是动态绑定的,并非编写代码时决定的。通过分析函数的调用位置,分析出this的绑定对象。这其中有四大绑定规则。

一、绑定规则:

  1. 默认绑定

    函数作为独立调用方式时,其采用默认绑定,或者无法应用其他绑定规则时的使用的默认绑定规则。默认绑定时,this指向全局对象。
    例如:

    function fun(){
        console.log('name:'+this.name)
    }
    var name = 'global'
    fun()
    输出: name:global
    

    fun函数在上面代码中使用独立调用方式,this指向了全局对象,name作为全局定义的对象。
    *** 当上述代码fun内部运行在strict mode下时,全局定义的对象不能用于默认绑定,同时会报错TypeError: this is undefined。

  2. 隐式绑定
    一个对象中含有指向函数的引用属性,并通过这个属性间接引用函数,从而把this隐式绑定到这个对象。这就是隐式绑定。简单点说:
    当函数有被对象包含着时,这是函数内的this便指向被包含对的对象,上代码示例:

    function fun(){
        console.log('fun name:'+this.name)
    }
    var obj = {
        name: 'obj',
        fun: fun,
        funX: function(){
            console.log('funX name:'+this.name)
        }
    }
    
    obj.fun()
    obj.funX()
    输出:
    fun name:obj
    funX name:obj
    

    可以看到上面代码中调用方式:obj.fun/obj.funX, 这是函数内部的this指向了obj对象,这样this.name就会使用了obj.name

  3. 显示绑定
    如果按照隐式绑定中的方法调用,但不想要this绑定到默认对象,可以通过js中另外两种调用方式apply/call,这两种方法中第一个参数都是为显示为函数内部this指向的对象,上代码示例:

    var objA={
        name: 'objA'
    }
    var objB={
        name: 'objB',
        fun: function(){
            console.log('name:' + this.name)
        }
    }
    
    objB.fun()
    objB.fun.call(objA)
    objB.fun.apply(objA)
    输出:
    name:objB
    name:objA
    name:objA
    

    上面代码中,objB.fun()使用了隐式绑定,输出了objB中的name属性值,另外两种调用方式则改变了函数中的this对象使其绑定到了对象objA.

    ***同时还有一种显示绑定方式bind,将其称为硬绑定。

    var objA={
        name: 'objA'
    }
    var objB={
        name: 'objB',
        fun: function(){
            console.log('name:' + this.name)
        }
    }
    
    var fun = objB.fun.bind(objA)
    fun()
    输出:
    name:objA
    

    上面代码中通过bind方式定义了一个函数引用fun,后续调用时,其中的this指向了定义时的对象,故而输出了objA,name值

  4. new绑定
    在函数作为构造函数方式调用时,函数内的this指向了新创建的对象。
    例如:

    function Fun(name){
        this.name = name
    }
    
    var fun = new Fun('fun')
    console.log(fun.name)
    输出:
    fun
    

    上述函数Fun作为构造函数方式调用(其实Fun也是js中一名普通的函数),其实此时发生了如下几件事:

    1. 创建一个对象
    2. 将新创建的对象绑定到函数中的this上
    3. 新创建的对象的[[prototype]]指向了Fun.prototype
    4. 如果函数内部没有显示返回值,那么new表达式执行完后默认返回该新创建的对象

    所以如上显示的那样,输出了'fun'

有了上面描述的四大绑定规则,那么在实际的函数方法中,该使用哪种绑定规则判断this了,此时还需要了解这其中的优先级关系。

二、优先级判断

唔有疑问,默认绑定无疑是四种优先级最低的,先搁置一边,探索下其余三种的优先级。
隐式绑定 VS 显示绑定


var objA={
    name: 'objA'
}
var objB={
    name: 'objB',
    fun: function(){
        console.log('name:' + this.name)
    }
}

objB.fun()
objB.fun.call(objA)
name:objB
name:objA

上述代码可以看出显示绑定优先级高于隐式绑定规则: 显示绑定 > 隐式绑定

接下来在看下隐式绑定与new绑定的优先级关系:
隐式绑定 VS new绑定


var obj={
    name: null,
    setName: function(name){
        this.name = name
    }
}

obj.setName('obj')
console.log('obj.name:' + obj.name)
var obj2 = new obj.setName('obj2')
console.log('obj.name:' + obj.name)
console.log('obj2.name:' + obj2.name)
输出:
obj.name:obj
obj.name:obj
obj2.name:obj2

上述代码中new obj.setName('obj2'), 并非更改了obj对象中的name属性,可见new绑定的优先级高于隐式绑定: new绑定 > 隐式绑定

最后看下显示绑定与new绑定方式优先级别:
显示绑定 VS new绑定方式


apply/call方式不能与new连用,此处使用硬绑定方式bind与new连用

var obj={
    name:null
}

function setName(name){
    this.name = name
}

var newSetName = setName.bind(obj)
var newObj = new newSetName('new')

console.log('obj.name:'+obj.name)
console.log('newObj.name:'+newObj.name)
输出:
obj.name:null
newObj.name:new

上面代码看出及时setName显示硬绑定this为obj,但是new方式调用时还是更改了setName中的this引用指向,由此可见new绑定优先级高于显示绑定:new绑定 > 显示绑定

那么总结上面的优先级规则:

new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

平常情况按照如上规则判断均会有效,但凡事均会有例外,再看下例外的规则

例外1. 显示绑定apply/call/bind时传入了null/undefined时,此时this并非指向了null/undefined,而是使用了默认绑定规则

function fun(){
    console.log(this.name)
}

var name = 'outer'

fun.call(null)
输出
outer

上述this.name输出了全局变量name值。
当然你会问为什么需要传入null值,有一种case如需要使用bind预设值一些参数值,如:

function multiple(mul, number){
    console.log('result:' + (mul * number))
}

var fun = multiple.bind(null, 5)

fun(2)
输出:
result:10

例外2. 函数赋值方式形成的间接调用,此时this也是使用了默认绑定规则

function print(){
    console.log(this.name)
}

var name = 'outer'
var obj={
    name: 'inner',
    print: print
}

var fun
(fun = obj.print)()
输出:
outer

写在最后

ES6中的箭头函数,this的规则比较特殊,它是继承了外层的this对象,由外层的作用域来决定。
上代码:

function fun(){
    return () => {
        console.log(this.name)
    }
}

var obj1={
    name:'obj1'
}

var obj2={
    name:'obj2'
}

var reFun = fun.call(obj1)
reFun.call(obj2)
输出:
obj1

上述代码可以看出并没有输出obj2,而是输出了fun函数调用时this指向的对象obj1对应的name属性,可以看出,箭头函数中的this指向了外层函数中的this对象。可以看出箭头函数有些反this机制,而是采用了“静态”绑定。

参考资料:[美]KYLE SIMPSON著 赵望野 梁杰 译 《你不知道的javascript》上卷

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

推荐阅读更多精彩内容