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

2017-07-30  本文已影响0人  程序不原

javascript中的this关键字

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

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

js王国中的this有会是指向谁了,先看看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》上卷

上一篇 下一篇

猜你喜欢

热点阅读