8. JavaScript中的this
8.1 this 使用
当我们要创建一个构造函数的实例时,需要使用 new
操作符,函数执行完成后,函数图中的 this 就指向了这个实例,通过下面的这个实例可以访问看绑定在 this 上的属性。
1 | function Person(name) { |
假如我们将 Person() 函数当成一个普通函数执行,其中 this 又指向谁呢?从哪个对象可以访问到定义的 name 属性的的值呢?
事实上,在 window
对象上,我们可以找到 name 属性的值,这表明函数体中的 this 指向了 window 对象。
1 | function Person(name) { |
为什么会出现这种情况呢?其实 this 抓鬼概念并不是 JavaScript 所特有的,在 java c++等面向对象的语言中也存在 this 关键字,它们中的 this 概念很好理解,this 指向的就是当前类的实例对象,而在 JavaScript 中,this 的指向是随着宿主环境的变化而变化的,在不同的地方对呀,返回的坑是不同的结果。
在大多数场景中,随着函数的运行,就会产生一个 this 的值,这个 this 存储着调用该函数的对象的值,因此我们可以认为,在 JavaScript 中,this 指向的永远是函数的调用者。
8.2 this 指向全局对象
当函数没有所属对象直接调用时, this 指向的就是全局对象。
1 | var value = 10 // 一定是 var,当let时,window.value 为 undefined |
首先我们定义一个全局的 value 属性为 10,相当于 window.value = 10。
然后定义一个 obj 对象,设置了 value 属性值为100, 然后设置 method 属性为一个函数,其method 属性中定义了一个函数表达式 foo ,并执行了 foo() 函数,最终返回 value 属性。
当我们调用 obj.method() 函数时, foo() 函数被执行,此时 foo() 函数执行时没有所属对象的,因此 this 会指向全局的 window 对象,在输出 this.value 时,实际输出 window.value,因此输出 “10”。
而 method() 函数的返回值是 this.value,method() 函数的调用体是 obj 对象,此时 this 就指向 obj 对象,而 obj.value = 100,因此调用 obj.method() 函数后会返回“100”。
8.2 this指向所属对象
当通过 new 操作符调用构造函数(生成对象)时,this 指向该实例。
1 | // 全局变量 |
在上面的这段代码中,我们定义了全局变量 nember 和实例变量 number,通过 new 操作符生成 Person 对象的实例 p 后,在调用 getNumber() 操作时,其中的 this 就指向该实例 p, 而实例 p 在初始化的时候被赋予 number 的值是30,所以输出的结果是 30。
8.3 this重新绑定对象
通过call()
函数、apply()
函数和bind()
函数可以改变函数的执行主体,如果函数中存在 this 关键字,则 this 也会指向 call() 函数、apply() 函数和 bind() 函数处理后的对象。
1 | // 全局变量 |
8.4 闭包中的this
函数的 this 变量只能被自身访问,其内部函数无法访问,因此在遇到闭包时,闭包内部的 this 关键字无法访问到外部变量函数的 this 变量。建议用箭头函数
代替普通函数。
参见: 7.2.3 作用域链问题
8.5 call apply和 bind 函数
call()
函数调用一个函数时,会将该函数的执行对象上下文改变为另一个对象,其语法如下:
1 | function.call(thisArg, arg1, arg2) |
- function 为需要调用的函数
- thisArg 表示的是新的对象上下文,函数中的 this 将指向 thisArg,如果 thisArg 为 null 或者 undefined,则 this 会指向全局对象。
- arg1,arg2…表示函数所接受的参数列表。
1 | // 定义一个函数 |
myAddCall() 函数本身是不具备运算能力,但是我们在 myAddCall 函数中,通过调用 add() 函数,并传入 this值,将执行 add() 函数主题改变为 myAddCall() 函数自身,然后传输参数 x, y 这就使得拥有 add() 函数计算求和的能力。
apply()
函数的作用域与 call() 函数时一样,只是在传递参数的形式上存在差异,语法格式如下:
1 | function.apply(thisArg, [arg1,arg2]) |
- function 为需要调用的函数
- thisArg 表示的是新的对象上下文,函数中的 this 将指向 thisArg,如果 thisArg 为 null 或者 undefined,则 this 会指向全局对象。
- 和 call() 函数的区别就是,在传值的时候,传递的事一个数组对象,如果传递的不是数组就会报错。
要想实现和 call() 函数一样的效果:
1 | // 定义一个 add() 函数 |
bind()
函数创建一个新的函数,在调用时设置 this 关键字为提供的值,在执行新函数时,将给定的参数列表作为原函数的参数序列,从前往后匹配,其语法格式如下:
1 | function.bind(thisArg, arg1, arg2) |
同 call()
函数参数相同。
- function 为需要调用的函数
- thisArg 表示的是新的对象上下文,函数中的 this 将指向 thisArg,如果 thisArg 为 null 或者 undefined,则 this 会指向全局对象。
- arg1,arg2…表示函数所接受的参数列表。
想要实现和 call() 函数一样的效果:
1 | // 定义一个 add() 函数 |
三者的相同之处是:都会改变函数调用的执行主体,修改 this 的指向。
不同之处:
- 关于函数立即执行, call() 函数与 apply() 函数在执行后会立即调用前面的函数,而 bind() 函数不会立即调用,它会返回一个新的函数,可以在任何时候调用。
- 关于参数传递,第一个参数表示将要改变的函数执行主体,即 this 的指向。从第二个参数开始,call() 函数与 bind() 函数相同都是函数接收的参数,apply() 第二个参数接收一个数组。
8.6 call apply和 bind 函数巧妙用法
8.6.1 求数组的最大项和最小项目
Array 数组本身没有 max() 函数和 min() 函数,无法直接获取最大值和最小值,但是 Math 却有最大值和最小值的 max() 函数和 min() 函数,我们可以使用 apply() 函数来改变 Math.max() 函数的执行主体,然后将数组作为参数传递给 max() 和 min() 函数。
1 | const arr = [3, 5, 7, 2, 9] |
apply() 函数第一个参数为 null,这是因为没有对象去调用这个函数,我们只需要这个函数帮助我们运算,得到返回结果。arr 是一个数组所以只能用 apply() 函数。
8.6.2 类数组对象转换为数组对象
函数的参数对象 arguments 是一个类数组对象,自身不能直接调用数组的方法,但是我们可以借助 call() 函数,让 arguments 对象调用数组的 slice() 函数,从而得到一个真实的数组,后面就能调用数组的函数。
1 | // 通用求和函数 |
8.6.3 bind函数配合setTimeOut
在默认情况下,使用 setTimeout() 函数的的时候, this 关键字会指向全局对象 window,当使用类的函数时,需要 this 引用类的实例,我们坑需要显示的把 this 绑定到回调函数一遍继续使用实例。
1 | function LateBloomer() { |