JavaScript中this绑定详解

转载请申明出处 https://segmentfault.com/a/11…

this 能够说是 javascript 中最耐人寻味的一个特征,就像高中英语里种种时态,比方被动时态,过去时,现在时,过去举行时一样,不管弄错过多少次,下一次依旧能够弄错。本文启发于《你不知道的JavaScript上卷》,对 javasript 中的 this 举行一个总结。

进修 this 的第一步就是邃晓 this 既不是指向函数本身也不指向函数的作用域。this 实际上是在函数被挪用时发作的绑定,它指向什么地方完整取决于函数在那里被挪用。

默许绑定

在 javascript 中 ,最经常运用的函数挪用范例就是自力函数挪用,因而能够把这条划定规矩看做是没法运用其他划定规矩时的默许划定规矩。假如在挪用函数的时刻,函数不带任何润饰,也就是“光溜溜”的挪用,那就会运用默许绑定划定规矩, 默许绑定的指向的是全局作用域。

function sayLocation() {
    console.log(this.atWhere)
}

var atWhere = "I am in global"

sayLocation() // 默许绑定,this绑定在全局对象,输出 “I am in global”

再看一个例子

var name = "global"
function person() {
    console.log(this.name) //  (1) "global"
      person.name = 'inside'
    function sayName() {
        console.log(this.name) // (2) "global"  不是 "inside"
    }
    sayName() // 在person函数内部实行sayName函数,this指向的同样是全局的对象
}
person()

在这个例子中,person 函数在全局作用域中被挪用,因而第(1)句中的 this 就绑定在了全局对象上(在浏览器中是是window,在node中就是global),因而第(1)句天然输出的是一个全局对象的 name 属性,固然就是"global"了。sayName函数在person函数内挪用,纵然如许第(2)句中的this指代的仍然是全局对象,纵然 person 函数设置了 name 属性。

这就是默许绑定划定规矩,它是 javascript 中最罕见的一种函数挪用情势,this 的绑定划定规矩也是四种绑定划定规矩中最简朴的一种,就是绑定在全局作用域上

默许绑定里的严厉情势

在 javascript 中,假如运用了严厉情势,则 this 不能绑定到全局对象。照样以第一个例子,只不过此次加上了严厉情势声明

'use strict'
function sayLocation() {
    console.log(this.atWhere)
}
var atWhere = "I am in global"
sayLocation()
// Uncaught TypeError: Cannot read property 'atWhere' of undefined

能够看出,在严厉情势下,把 this 绑定到全局对象上时,实际上绑定的是 undefined ,因而上面这段代码会报错。

隐式绑定

当函数在挪用时,假如函数有所谓的“落脚点”,即有上下文对象时,隐式绑定划定规矩会把函数中的 this 绑定到这个上下文对象。假如以为上面这段话不够直白的话,照样来看代码。

function say() {
    console.log(this.name)
}
var obj1 = {
    name: "zxt",
    say: say
}

var obj2 = {
    name: "zxt1",
    say: say
}
obj1.say() // zxt
obj2.say() // zxt1

很简朴是否是。在上面这段代码中,obj1 , obj2 就是所谓的 say 函数的落脚点,专业一点的说法就是上下文对象,当给函数指定了这个上下文对象时,函数内部的this 天然指向了这个上下文对象。这也是很罕见的一种函数挪用情势。

隐式绑定时丧失上下文

function say() {
    console.log(this.name)
}
var name = "global"
var obj = {
    name: "inside",
    say: say
}
var alias = obj.say // 设置一个简写   (1) 
alias() // 函数挪用 输出"global"  (2)

能够看到这里输出的是 ”global“ ,为何就和上例中不一样,我们明显只是给 obj.say 换了个名字罢了?
起首我们来看上面第(1)句代码,由于在 javascript 中,函数是对象,对象之间是援用通报,而不是值通报。因而,第(1)句代码只是 alias = obj.say = say ,也就是 alias = sayobj.say 只是起了一个桥梁的作用,alias 终究援用的是 say 函数的地点,而与 obj 这个对象无关了。这就是所谓的”丧失上下文“。终究实行 alias 函数,只不过简朴的实行了say函数,输出"global"

显式绑定

显式绑定,望文生义,显现地将this绑定到一个上下文,javascript中,供应了三种显式绑定的要领,apply,call,bindapplycall的用法基础类似,它们之间的区别是:

apply(obj,[arg1,arg2,arg3,...] 被挪用函数的参数以数组的情势给出
call(obj,arg1,arg2,arg3,...) 被挪用函数的参数顺次给出

bind函数实行后,返回的是一个新函数。下面以代码申明。

// 不带参数
function speak() {
    console.log(this.name)
}

var name = "global"
var obj1 = {
    name: 'obj1'
}
var obj2 = {
    name: 'obj2'
}

speak() // global 等价于speak.call(window)
speak.call(window)

speak.call(obj1) // obj1
speak.call(obj2) // obj2

因而能够看出,apply, call 的作用就是给函数绑定一个实行上下文,且是显式绑定的。因而,函数内的this天然而然的绑定在了 call 或许 apply 所挪用的对象上面。

// 带参数
function count(num1, num2) {
    console.log(this.a * num1 + num2)
}

var obj1 = {
    a: 2
}
var obj2 = {
    a: 3
}

count.call(obj1, 1, 2) // 4
count.apply(obj1, [1, 2]) // 4

count.call(obj2, 1, 2) // 5
count.apply(obj2, [1, 2]) // 5

上面这个例子则申清楚明了 applycall 用法上的差别。
bind 函数,则返回一个绑定了指定的实行上下文的新函数。照样以上面这段代码为例

// 带参数
function count(num1, num2) {
    console.log(this.a * num1 + num2)
}

var obj1 = {
    a: 2
}

var bound1 = count.bind(obj1) // 未指定参数
bound1(1, 2) // 4

var bound2 = count.bind(obj1, 1) // 指定了一个参数
bound2(2) // 4 

var bound3 = count.bind(obj1, 1, 2) // 指定了两个参数
bound3() //4

var bound4 = count.bind(obj1, 1, 2, 3) // 指定了过剩的参数,过剩的参数会被疏忽
bound4() // 4

所以,bind 要领只是返回了一个新的函数,这个函数内的this指定了实行上下文,而返回这个新函数能够接收参数。

new 绑定

末了要讲的一种 this 绑定划定规矩,是指经由过程 new 操纵符挪用组织函数时发作的 this 绑定。起首要明白一点的是,在 javascript 中并没有其他言语那样的类的观点。组织函数也仅仅是一般的函数罢了,只不过组织函数的函数名以大写字母开首,也只不过它能够经由过程 new 操纵符挪用罢了.

function Person(name,age) {
    this.name = name
    this.age = age
    console.log("我也只不过是个一般函数")
}
Person("zxt",22) // "我也只不过是个一般函数"
console.log(name) // "zxt"
console.log(age) // 22

var zxt = new Person("zxt",22) // "我也只不过是个一般函数"
console.log(zxt.name) // "zxt"
console.log(zxt.age) // 22

上面这个例子中,起首定义了一个 Person 函数,既能够一般挪用,也能够以组织函数的情势的挪用。当一般挪用时,则依据一般的函数实行,输出一个字符串。 假如是经由过程一个new操纵符,则组织了一个新的对象。那末,接下来我们再看看两种挪用体式格局, this 离别绑定在了那边起首一般挪用时,前面已引见过,此时运用默许绑定划定规矩,this绑定在了全局对象上,此时全局对象上会离别增添 nameage 两个属性。当经由过程new操纵符挪用时,函数会返回一个对象,从输出效果上来看 this 对象绑定在了这个返回的对象上。
因而,所谓的new绑定是指经由过程new操纵符来挪用函数时,会发生一个新对象,而且会把组织函数内的this绑定到这个对象上。
事实上,在javascript中,运用new来挪用函数,会自动实行下面的操纵。

  1. 建立一个全新的对象

  2. 这个新对象会被实行原型衔接

  3. 这个新对象会绑定到函数挪用的this

  4. 假如函数没有返回其他对象,那末new表达式中的函数挪用会自动返回这个新对象

四种绑定的优先级

上面报告了javascript中四种this绑定划定规矩,这四种绑定划定规矩基础上涵盖了一切函数挪用状况。然则假如同时运用了这四种划定规矩中的两种以至更多,又该是怎样的一个状况,或许说这四种绑定的优先级递次又是怎样的。
起首,很轻易明白,默许绑定的优先级是最低的。这是由于只要在没法运用其他this绑定划定规矩的状况下,才会挪用默许绑定。那隐式绑定和显式绑定呢?照样上代码吧,代码可从来不会撒谎。

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

var obj1 = {
    name: 'obj1',
    speak: speak
}
var obj2 = {
    name: 'obj2'
}

obj1.speak() // obj1 (1)
obj1.speak.call(obj2) // obj2 (2)

所以在上面代码中,实行了obj1.speak(),speak函数内部的this指向了obj1,因而(1)处代码输出的固然就是obj1,然则当显式绑定了speak函数内的thisobj2上,输出效果就变成了obj2,一切从这个效果能够看出显式绑定的优先级是要高于隐式绑定的。事实上我们能够这么明白obj1.speak.call(obj2)这行代码,obj1.speak只是间接获得了speak函数的援用,这就有点像前面所说的隐式绑定丧失了上下文。好,既然显式绑定的优先级要高于隐式绑定,那末接下来再来比较一下new 绑定和显式绑定。

function foo(something) {
    this.a = something
}

var obj1 = {}
var bar = foo.bind(obj1)  // 返回一个新函数bar,这个新函数内的this指向了obj1  (1)
bar(2) // this绑定在了Obj1上,所以obj1.a === 2
console.log(obj1.a)

var baz = new bar(3)  // 挪用new 操纵符后,bar函数的this指向了返回的新实例baz  (2)

console.log(obj1.a)
console.log(baz.a) 

我们能够看到,在(1)处,bar函数内部的this底本指向的是obj1,然则在(2)处,由于经过了new操纵符挪用,bar函数内部的this却从新指向了返回的实例,这就可以够申明new 绑定的优先级是要高于显式绑定的。
至此,四种绑定划定规矩的优先级排序就已得出了,离别是

new 绑定 > 显式绑定 > 隐式绑定 > 默许绑定

箭头函数中的this绑定

箭头函数是ES6里一个主要的特征。
箭头函数的this是依据外层的(函数或许全局)作用域来决议的。函数体内的this对象指的是定义时地点的对象,而不是之前引见的挪用时绑定的对象。举一个例子

var a = 1
var foo = () => {
    console.log(this.a) // 定义在全局对象中,因而this绑定在全局作用域
}

var obj = {
    a: 2
}
foo() // 1 ,在全局对象中挪用
foo.call(obj) // 1,显现绑定,由obj对象来挪用,但基础不影响效果

从上面这个例子看出,箭头函数的 this 强制性的绑定在了箭头函数定义时地点的作用域,而且没法经由过程显现绑定,如apply,call要领来修正。在来看下面这个例子

// 定义一个组织函数
function Person(name,age) {
    this.name = name
    this.age = age 
    this.speak = function (){
        console.log(this.name)
        // 一般函数(非箭头函数),this绑定在挪用时的作用域
    }
    this.bornYear = () => {
        // 本文写于2016年,因而new Date().getFullYear()获得的是2016
        // 箭头函数,this绑定在实例内部
        console.log(new Date().getFullYear() - this.age)
        }
    }
}

var zxt = new Person("zxt",22)

zxt.speak() // "zxt"
zxt.bornYear() // 1994

// 到这里应当人人应当都没什么问题

var xiaoMing = {
    name: "xiaoming",
    age: 18  // 小明永久18岁
}

zxt.speak.call(xiaoMing)
// "xiaoming" this绑定的是xiaoMing这个对象
zxt.bornYear.call(xiaoMing)
// 1994 而不是 1998,这是由于this永久绑定的是zxt这个实例

因而 ES6 的箭头函数并不会运用四条规范的绑定划定规矩,而是依据当前的词法作用域来决议 this ,具体来讲就是,箭头函数会继续 外层函数挪用的this绑定 ,而不管外层函数的this绑定到那里。

小结

以上就是javascript中一切this绑定的状况,在es6之前,前面所说的四种绑定划定规矩能够涵盖任何的函数挪用状况,es6规范实行今后,关于函数的扩大新增了箭头函数,与之前差别的是,箭头函数的作用域位于箭头函数定义时地点的作用域

而关于之前的四种绑定划定规矩来讲,控制每种划定规矩的挪用前提就可以很好的明白this究竟是绑定在了哪一个作用域。

全文完

    原文作者:499311496
    原文地址: https://segmentfault.com/a/1190000007101339
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞