为什么要应用this

在javascript中,this堪称是无处不在,它能够用来指向某些元素、对象,在适合的中央应用this,能让咱们缩小无用代码的编写

var user = {  name: "aclie",  sing: function () {    console.log(user.name + '在唱歌')  },  dance: function () {    console.log(user.name + '在跳舞')  },  study: function () {    console.log(user.name + '在学习')  },}

以上这段代码中,每个办法都须要用到user对象中的name属性,如果当user对象名称发生变化,那么所有办法都要改变,这种状况下,应用this是个很好的抉择

var user = {  name: "aclie",  sing: function () {    console.log(this.name + '在唱歌')  },  dance: function () {    console.log(this.name + '在跳舞')  },  study: function () {    console.log(this.name + '在学习')  },}

this的指向

this的指向和函数在哪里定义无关,和如何调用无关

以下foo函数调用形式不同,this的值也不同

 function foo(){  console.log(this)}foo()var obj = {  foo: foo}obj.foo() obj.foo.apply("hello")    

执行后果如下图所示

this四种绑定形式

一、默认绑定

当函数独立调用时,this默认绑定window
// 1、间接调用function foo() {  console.log(this)}foo()// 2、对象中的函数var obj1 = {  foo: foo}var fn1 = obj1.foofn1()// 3、被全局变量援用var obj2 = {  bar: function () {    console.log(this)  }}var fn2 = obj2.barfn2()// 4、函数嵌套调用function foo1() {  console.log('foo1', this)}function foo2() {  console.log('foo2', this)  foo1()}function foo3() {  console.log('foo3', this)  foo2()}foo3()// 5、通过闭包调用var obj2 = {  bar: function () {    return function () {      console.log(this)    }  }}obj2.bar()()

执行后果如下

以上五种调用形式全都属于默认绑定,因为他们最终都是独自的对函数进行调用

二、隐式绑定

调用的对象外部有对函数的援用
function foo() {  console.log(this)}var obj1 = {  name: 'obj1',  foo: foo}obj1.foo()var obj2 = {  name: 'obj2',  bar: function () {    console.log(this)  }}obj2.bar()var obj3 = {  name: 'obj3',  baz: obj2.bar}obj3.baz()

以上代码执行后果为

以上三种都属于隐式绑定,他们都是通过对象调用,this就指向了该对象

三、显式绑定

不心愿在对象外部蕴含这个函数的援用,但又心愿通过对象强制调用,应用call/apply/bind进行显式绑定
function foo() {  console.log(this)}var obj = {  name: 'obj1',}foo.call(obj)foo.apply(obj)foo.call("xxx")

以上代码的执行后果为

foo函数间接调用this应该指向window,这里通过call/apply来扭转了this的指向

四、new绑定

通过new关键字来创立构造函数的实例,绑定this
function Person(name, age) {  this.name = name  this.age = age}const p1 = new Person('alice', 20)const p2 = new Person('mogan', 24)console.log(p1)console.log(p2)

以上代码的执行后果如下

此时this指向的是通过new创立的实例对象

this绑定的优先级

一、隐式绑定高于默认绑定

function foo() {  console.log(this)}var obj = {  name: 'obj',  foo: foo}obj.foo()

以上代码执行后果为

foo函数默认绑定window对象,当同时存在隐式绑定和默认绑定时,隐式绑定优先级高于默认绑定

二、显示绑定高于隐式绑定

// 案例一var user = {  name: 'user',  foo: function(){    console.log(this)  }}user.foo.call('kiki')// 案例二function foo() {  console.log(this)}var obj = {  name: "obj",  foo: foo.bind("aclie")}obj.foo()

以上代码的执行后果为

如果隐式绑定优先级更高的话,this的指向应该都为对象,但依据以上执行后果得悉this绑定为显示绑定的后果,所以当同时存在隐式绑定和显示绑定时,显示绑定的优先级高于隐式绑定

三、new高于隐式绑定

var user = {  name: 'lisa',  foo: function () {    console.log(this)  }}new user.foo()

以上代码的执行后果如下

当同时存在于new关键字绑定和隐式绑定时,this绑定了foo构造函数,所以new关键字的优先级高于隐式绑定

四、new高于显示绑定

function bar(){  console.log(this)}var fn = bar.bind('hello')new fn()

以上代码的执行后果如下

当同时存在于new关键字绑定和显示绑定时,this绑定了bar构造函数,所以new关键字的优先级高于显示绑定

综上,以上四种绑定的优先级程序为

new关键字 > 显式绑定 > 隐式绑定 > 默认绑定

规定之外

还有几种非凡的绑定形式,不在上述四种绑定规定中

一、疏忽显示绑定

当显示绑定的值为 null/undefined 时,this间接绑定window
var user = {  name: 'alice',  foo: function () {    console.log(this)  }}user.foo()user.foo.call(null)user.foo.apply(undefined)

以上代码执行后果如下

二、间接函数援用

var obj1 = {  name: 'obj1',  foo: function () {    console.log(this)  }}var obj2 = {  name: 'obj2'};obj2.baz = obj1.foo;obj2.baz();(obj2.bar = obj1.foo)()

以上代码的执行后果为

两种形式所绑定的this不同,第二种形式进行了赋值调用,实际上是间接函数援用,(obj2.bar = obj1.foo)这里返回了赋值的后果,再加上一个小括号,就间接调用赋值的后果函数

三、箭头函数

箭头函数是不绑定this的,它的this来源于下级作用域
var user = {  name: 'kiki',  foo: () => {    console.log('箭头函数中的this',this)  }}user.foo()

以上代码的执行后果如下

这里调用foo函数,因为箭头函数不绑定this,所以去foo函数的下级查找this,找到了全局对象window

面试题

1、考查间接函数援用

var name = "window";var person = {  name: "person",  sayName: function () {    console.log(this.name);  }};function sayName() {  var sss = person.sayName;  sss();   person.sayName();   (person.sayName)();   (b = person.sayName)();}sayName();

执行sayName函数

  • 变量sss 被person.sayName办法赋值,执行sss函数,此时是独立函数调用,this指向全局window,全局中变量name被绑定到了window中,所以this.name为"window"
  • person.sayName() 为隐式绑定,this指向person对象,所以this.name为person.name,即"person"
  • (person.sayName)() 与前一个实质是一样的,隐式绑定,this指向person对象,所以this.name为person.name,即"person"
  • (b = person.sayName)() 是间接函数援用,person.sayName赋值给b变量,而小括号括起来的代表赋值的后果,this指向window,this.name为window.name,即"window"

所以执行后果为

2、定义对象时是不产生作用域的

var name = 'window'var person1 = {  name: 'person1',  foo1: function () {    console.log(this.name)  },  foo2: () => console.log(this.name),  foo3: function () {    return function () {      console.log(this.name)    }  },  foo4: function () {    return () => {      console.log(this.name)    }  }}var person2 = { name: 'person2' }person1.foo1();person1.foo1.call(person2);person1.foo2();person1.foo2.call(person2);person1.foo3()();person1.foo3.call(person2)();person1.foo3().call(person2);person1.foo4()();person1.foo4.call(person2)();person1.foo4().call(person2);

调用过程剖析

  1. foo1函数

    • person1.foo1() 隐式绑定,this指向person1,this.name为person1.name,即 "person1"
    • person1.foo1.call(person2) 隐式绑定+显示绑定person2,显示绑定优先级更高,所以this指向person2,this.name为person2.name,即 "person2"
  2. foo2函数

    • person1.foo2() 隐式绑定, 箭头函数没有本人的this,所以向下层作用域查找,找到了全局window(person1是对象,定义它的时候不产生作用域),全局变量name被绑定到了window中,this.name为window.name,即 "window"
    • person1.foo2.call(person) 隐式绑定+显示绑定,然而 箭头函数不绑定this,这里的显示绑定有效,没有本人的this,向下层作用域查找,找到全局window,this.name为window.name,即 "window"
  3. foo3函数

    • person1.foo3()() 这里相当于执行person1.foo()的返回函数,这里是独立函数调用,this指向全局window,this.name为window.name,即 "window"
    • person1.foo3.call(person2)() 这里通过call扭转的是foo3函数中this的指向,但最终执行的是foo3函数返回的闭包,闭包作为独立函数调用,this依然指向全局window,this.name为window.name,即'window"
    • person1.foo3().call(person2) 这里将foo3函数返回的闭包显示绑定了person2对象,this指向person2,this.name为person2.name,即"person2"
  4. foo4函数

    • person1.foo4()() 执行person1.foo()的返回值,返回的闭包是箭头函数没有this的,向下层作用域查找,找到了foo4函数,foo4的this指向person1,所以闭包的this也指向person1,thiss.name为person1.name,即 "person1"
    • person1.foo4.call(person2)() 返回的闭包没有this,向下层作用域找到了foo4函数,foo4函数的this通过显示绑定变成了person2,所以闭包的this也指向person2,this.name为person2.name,即"person2"
    • person1.foo4().call(person) 返回的闭包是箭头函数,无奈通过call进行显示绑定,间接向下级作用域查找,找到foo4函数,foo4的this指向person1,所以闭包的this指向person1,this.name为person1.name,即"person1"

上述代码的执行后果如下

3、构造函数中定义函数,该函数的下级作用域是构造函数

var name = 'window'function Person (name) {  this.name = name  this.foo1 = function () {    console.log(this.name)  },  this.foo2 = () => console.log(this.name),  this.foo3 = function () {    return function () {      console.log(this.name)    }  },  this.foo4 = function () {    return () => {      console.log(this.name)    }  }}var person1 = new Person('person1')var person2 = new Person('person2')person1.foo1()person1.foo1.call(person2)person1.foo2() person1.foo2.call(person2)person1.foo3()()person1.foo3.call(person2)()person1.foo3().call(person2)person1.foo4()()person1.foo4.call(person2)()person1.foo4().call(person2) 

调用剖析过程

  1. foo1函数

    • person1.foo1() 隐式绑定,this指向person1,person1创立实例时传入name为person1,所以this.name为person1
    • person1.foo1.call(person2) 隐式绑定+显示绑定,显示绑定优先级更高,绑定person2,person2创立实例时传入的name为person2,所以this.name为person2
  2. foo2函数

    • person1.foo2() 隐式绑定,但foo2是箭头函数,没有本人的this,向下层作用域查找,找到了Person构造函数,此时this是指向person1这个对象的,而person1实例化时传入的name为person1,所以this.name为person1
    • person1.foo2.call(person2) 隐式绑定+显式绑定,但foo2是箭头函数,不绑定this,所以this依然须要向下层作用域查找,找到Person构造函数,this指向person1对象,所以this.name为person1
  3. foo3函数

    • person1.foo3()() 执行person1.foo3的返回值,返回的函数是独立调用,this指向window,全局的name变量被绑定到window中,this.name为window.name,即 "window"
    • person1.foo3.call(person2)() 显式绑定更改的是foo3函数的this,最终执行的是foo3函数的返回值,依然是函数的独立调用,所以this指向window,this.name为window.name,即 "window"
    • person1.foo3().call(person2) foo3函数的返回函数通过显示绑定将this绑定到了person2中,person2创立实例时传入的name为person2,所以this.name为person2
  4. foo4函数

    • person1.foo4()() 执行foo4函数的返回值,返回函数为箭头函数,没有this,所以向下层作用域查找,找到foo4函数的this指向person1,所以箭头函数的this也指向person1,所以this.name为person1
    • person1.foo4.call(person2)() foo4通过显示绑定将this绑定成了person2,返回的函数为箭头函数,this与父级作用域foo4统一,所以箭头函数的this也指向person2,所以this.name为person2
    • person1.foo4().call(person2) foo4函数的返回值为箭头函数,不绑定this,这里显示绑定有效,向下级作用域查找this,找到foo4函数,this指向person1

执行后果如下

4、辨别作用域

var name = 'window'function Person (name) {  this.name = name  this.obj = {    name: 'obj',    foo1: function () {      return function () {        console.log(this.name)      }    },    foo2: function () {      return () => {        console.log(this.name)      }    }  }}var person1 = new Person('person1')var person2 = new Person('person2')person1.obj.foo1()() person1.obj.foo1.call(person2)() person1.obj.foo1().call(person2) person1.obj.foo2()() person1.obj.foo2.call(person2)() person1.obj.foo2().call(person2) 
  1. foo1函数

    • person1.obj.foo1()() 执行foo1函数的返回函数,此时该函数为独立函数调用,this指向window,全局变量name被增加到window中,这里的this.name指向window.name,即 "window"
    • person1.obj.foo1.call(person2)() 这里显示绑定扭转foo1中this的指向,但最终执行的是foo1函数的返回值,返回函数作为独立函数调用,this依然指向window,所以this.name为window.name,即 "window"
    • person1.obj.foo1().call(person2) 这里通过显示绑定更改foo1函数的返回函数中this的指向, 所以该函数this指向person2,而person2在实例化的时候传入name值为person2,所以this.name为person2
  2. foo2函数

    • person1.obj.foo2()() 执行foo2的返回函数,此时该函数为独立函数调用,但它本人没有this,要向下级作用域查找,找到foo2函数的this指向obj,所以该函数的this也指向obj,this.name为obj.name,即 "obj"
    • person1.obj.foo2.call(person2)() 执行foo2的返回函数,此时该函数为独立函数调用,但它本人没有this,要向下级作用域查找,foo2函数的this通过显示绑定变成person2,所以该函数的this也为person2,而person2在实例化的时候传入name值为person2,所以this.name为person2
    • person1.obj.foo2().call(person2) foo2的返回函数为箭头函数,不绑定this,显式绑定有效,也没有本人的this,要向下级作用域查找,找到foo2函数的this指向obj,所以该函数的this也指向obj,this.name为obj.name,即 "obj"

所以执行后果为

以上就是对于this指向的了解,对于js高级,还有很多须要开发者把握的中央,能够看看我写的其余博文,继续更新中~